blob: a1ed2146b38e5717ead63c489dd00120bc60d381 [file] [log] [blame]
Jeff Sharkey494500d2018-06-14 16:04:00 -06001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.os;
18
19import android.content.Context;
20import android.os.storage.StorageManager;
21import android.system.ErrnoException;
22import android.system.Os;
Jeff Sharkey494500d2018-06-14 16:04:00 -060023import android.util.Slog;
24
Jeff Sharkeycb394992018-12-01 18:26:43 -070025import com.android.internal.annotations.VisibleForTesting;
26
Jeff Sharkey494500d2018-06-14 16:04:00 -060027import libcore.io.IoUtils;
Jeff Sharkeycb394992018-12-01 18:26:43 -070028import libcore.util.EmptyArray;
Jeff Sharkey494500d2018-06-14 16:04:00 -060029
30import java.io.File;
31import java.io.FileDescriptor;
32import java.io.IOException;
33import java.io.InterruptedIOException;
Jeff Sharkeycb394992018-12-01 18:26:43 -070034import java.util.Arrays;
Jeff Sharkey494500d2018-06-14 16:04:00 -060035
36/**
37 * Variant of {@link FileDescriptor} that allows its creator to specify regions
Andrew Lewis4ec5fda2019-07-10 13:14:24 +010038 * that should be redacted.
Jeff Sharkey494500d2018-06-14 16:04:00 -060039 *
40 * @hide
41 */
42public class RedactingFileDescriptor {
43 private static final String TAG = "RedactingFileDescriptor";
44 private static final boolean DEBUG = true;
45
Jeff Sharkeycb394992018-12-01 18:26:43 -070046 private volatile long[] mRedactRanges;
Andrew Lewis4ec5fda2019-07-10 13:14:24 +010047 private volatile long[] mFreeOffsets;
Jeff Sharkey494500d2018-06-14 16:04:00 -060048
49 private FileDescriptor mInner = null;
50 private ParcelFileDescriptor mOuter = null;
51
Andrew Lewis4ec5fda2019-07-10 13:14:24 +010052 private RedactingFileDescriptor(
53 Context context, File file, int mode, long[] redactRanges, long[] freeOffsets)
Jeff Sharkey494500d2018-06-14 16:04:00 -060054 throws IOException {
55 mRedactRanges = checkRangesArgument(redactRanges);
Andrew Lewis4ec5fda2019-07-10 13:14:24 +010056 mFreeOffsets = freeOffsets;
Jeff Sharkey494500d2018-06-14 16:04:00 -060057
58 try {
59 try {
Jeff Sharkeycb394992018-12-01 18:26:43 -070060 mInner = Os.open(file.getAbsolutePath(),
61 FileUtils.translateModePfdToPosix(mode), 0);
Jeff Sharkey494500d2018-06-14 16:04:00 -060062 mOuter = context.getSystemService(StorageManager.class)
Jeff Sharkeycb394992018-12-01 18:26:43 -070063 .openProxyFileDescriptor(mode, mCallback);
Jeff Sharkey494500d2018-06-14 16:04:00 -060064 } catch (ErrnoException e) {
65 throw e.rethrowAsIOException();
66 }
67 } catch (IOException e) {
68 IoUtils.closeQuietly(mInner);
69 IoUtils.closeQuietly(mOuter);
70 throw e;
71 }
72 }
73
74 private static long[] checkRangesArgument(long[] ranges) {
75 if (ranges.length % 2 != 0) {
76 throw new IllegalArgumentException();
77 }
78 for (int i = 0; i < ranges.length - 1; i += 2) {
79 if (ranges[i] > ranges[i + 1]) {
80 throw new IllegalArgumentException();
81 }
82 }
83 return ranges;
84 }
85
86 /**
87 * Open the given {@link File} and returns a {@link ParcelFileDescriptor}
Jeff Sharkeycb394992018-12-01 18:26:43 -070088 * that offers a redacted view of the underlying data. If a redacted region
89 * is written to, the newly written data can be read back correctly instead
90 * of continuing to be redacted.
Jeff Sharkey494500d2018-06-14 16:04:00 -060091 *
92 * @param file The underlying file to open.
Jeff Sharkeycb394992018-12-01 18:26:43 -070093 * @param mode The {@link ParcelFileDescriptor} mode to open with.
Andrew Lewis4ec5fda2019-07-10 13:14:24 +010094 * @param redactRanges List of file ranges that should be redacted, stored
Jeff Sharkey494500d2018-06-14 16:04:00 -060095 * as {@code [start1, end1, start2, end2, ...]}. Start values are
96 * inclusive and end values are exclusive.
Andrew Lewis4ec5fda2019-07-10 13:14:24 +010097 * @param freePositions List of file offsets at which the four byte value 'free' should be
98 * written instead of zeros within parts of the file covered by {@code redactRanges}.
99 * Non-redacted bytes will not be modified even if covered by a 'free'. This is
100 * useful for overwriting boxes in ISOBMFF files with padding data.
Jeff Sharkey494500d2018-06-14 16:04:00 -0600101 */
Jeff Sharkeycb394992018-12-01 18:26:43 -0700102 public static ParcelFileDescriptor open(Context context, File file, int mode,
Andrew Lewis4ec5fda2019-07-10 13:14:24 +0100103 long[] redactRanges, long[] freePositions) throws IOException {
104 return new RedactingFileDescriptor(context, file, mode, redactRanges, freePositions).mOuter;
Jeff Sharkeycb394992018-12-01 18:26:43 -0700105 }
106
107 /**
108 * Update the given ranges argument to remove any references to the given
109 * offset and length. This is typically used when a caller has written over
110 * a previously redacted region.
111 */
112 @VisibleForTesting
113 public static long[] removeRange(long[] ranges, long start, long end) {
114 if (start == end) {
115 return ranges;
116 } else if (start > end) {
117 throw new IllegalArgumentException();
118 }
119
120 long[] res = EmptyArray.LONG;
121 for (int i = 0; i < ranges.length; i += 2) {
122 if (start <= ranges[i] && end >= ranges[i + 1]) {
123 // Range entirely covered; remove it
124 } else if (start >= ranges[i] && end <= ranges[i + 1]) {
125 // Range partially covered; punch a hole
126 res = Arrays.copyOf(res, res.length + 4);
127 res[res.length - 4] = ranges[i];
128 res[res.length - 3] = start;
129 res[res.length - 2] = end;
130 res[res.length - 1] = ranges[i + 1];
131 } else {
132 // Range might covered; adjust edges if needed
133 res = Arrays.copyOf(res, res.length + 2);
134 if (end >= ranges[i] && end <= ranges[i + 1]) {
135 res[res.length - 2] = Math.max(ranges[i], end);
136 } else {
137 res[res.length - 2] = ranges[i];
138 }
139 if (start >= ranges[i] && start <= ranges[i + 1]) {
140 res[res.length - 1] = Math.min(ranges[i + 1], start);
141 } else {
142 res[res.length - 1] = ranges[i + 1];
143 }
144 }
145 }
146 return res;
Jeff Sharkey494500d2018-06-14 16:04:00 -0600147 }
148
149 private final ProxyFileDescriptorCallback mCallback = new ProxyFileDescriptorCallback() {
150 @Override
151 public long onGetSize() throws ErrnoException {
152 return Os.fstat(mInner).st_size;
153 }
154
155 @Override
156 public int onRead(long offset, int size, byte[] data) throws ErrnoException {
157 int n = 0;
158 while (n < size) {
159 try {
160 final int res = Os.pread(mInner, data, n, size - n, offset + n);
161 if (res == 0) {
162 break;
163 } else {
164 n += res;
165 }
166 } catch (InterruptedIOException e) {
167 n += e.bytesTransferred;
168 }
169 }
170
171 // Redact any relevant ranges before returning
172 final long[] ranges = mRedactRanges;
173 for (int i = 0; i < ranges.length; i += 2) {
174 final long start = Math.max(offset, ranges[i]);
175 final long end = Math.min(offset + size, ranges[i + 1]);
176 for (long j = start; j < end; j++) {
177 data[(int) (j - offset)] = 0;
178 }
Andrew Lewis4ec5fda2019-07-10 13:14:24 +0100179 // Overwrite data at 'free' offsets within the redaction ranges.
180 for (long freeOffset : mFreeOffsets) {
181 final long freeEnd = freeOffset + 4;
182 final long redactFreeStart = Math.max(freeOffset, start);
183 final long redactFreeEnd = Math.min(freeEnd, end);
184 for (long j = redactFreeStart; j < redactFreeEnd; j++) {
185 data[(int) (j - offset)] = (byte) "free".charAt((int) (j - freeOffset));
186 }
187 }
Jeff Sharkey494500d2018-06-14 16:04:00 -0600188 }
189 return n;
190 }
191
192 @Override
193 public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
Jeff Sharkeycb394992018-12-01 18:26:43 -0700194 int n = 0;
195 while (n < size) {
196 try {
197 final int res = Os.pwrite(mInner, data, n, size - n, offset + n);
198 if (res == 0) {
199 break;
200 } else {
201 n += res;
202 }
203 } catch (InterruptedIOException e) {
204 n += e.bytesTransferred;
205 }
206 }
207
208 // Clear any relevant redaction ranges before returning, since the
209 // writer should have access to see the data they just overwrote
210 mRedactRanges = removeRange(mRedactRanges, offset, offset + n);
211 return n;
Jeff Sharkey494500d2018-06-14 16:04:00 -0600212 }
213
214 @Override
215 public void onFsync() throws ErrnoException {
216 Os.fsync(mInner);
217 }
218
219 @Override
220 public void onRelease() {
221 if (DEBUG) Slog.v(TAG, "onRelease()");
222 IoUtils.closeQuietly(mInner);
223 }
224 };
225}