blob: 4e5eaac3442f0192fd0d3f118bc4c8f136392cac [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
38 * that should be redacted (appearing as zeros to the reader).
39 *
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;
Jeff Sharkey494500d2018-06-14 16:04:00 -060047
48 private FileDescriptor mInner = null;
49 private ParcelFileDescriptor mOuter = null;
50
Jeff Sharkeycb394992018-12-01 18:26:43 -070051 private RedactingFileDescriptor(Context context, File file, int mode, long[] redactRanges)
Jeff Sharkey494500d2018-06-14 16:04:00 -060052 throws IOException {
53 mRedactRanges = checkRangesArgument(redactRanges);
54
55 try {
56 try {
Jeff Sharkeycb394992018-12-01 18:26:43 -070057 mInner = Os.open(file.getAbsolutePath(),
58 FileUtils.translateModePfdToPosix(mode), 0);
Jeff Sharkey494500d2018-06-14 16:04:00 -060059 mOuter = context.getSystemService(StorageManager.class)
Jeff Sharkeycb394992018-12-01 18:26:43 -070060 .openProxyFileDescriptor(mode, mCallback);
Jeff Sharkey494500d2018-06-14 16:04:00 -060061 } catch (ErrnoException e) {
62 throw e.rethrowAsIOException();
63 }
64 } catch (IOException e) {
65 IoUtils.closeQuietly(mInner);
66 IoUtils.closeQuietly(mOuter);
67 throw e;
68 }
69 }
70
71 private static long[] checkRangesArgument(long[] ranges) {
72 if (ranges.length % 2 != 0) {
73 throw new IllegalArgumentException();
74 }
75 for (int i = 0; i < ranges.length - 1; i += 2) {
76 if (ranges[i] > ranges[i + 1]) {
77 throw new IllegalArgumentException();
78 }
79 }
80 return ranges;
81 }
82
83 /**
84 * Open the given {@link File} and returns a {@link ParcelFileDescriptor}
Jeff Sharkeycb394992018-12-01 18:26:43 -070085 * that offers a redacted view of the underlying data. If a redacted region
86 * is written to, the newly written data can be read back correctly instead
87 * of continuing to be redacted.
Jeff Sharkey494500d2018-06-14 16:04:00 -060088 *
89 * @param file The underlying file to open.
Jeff Sharkeycb394992018-12-01 18:26:43 -070090 * @param mode The {@link ParcelFileDescriptor} mode to open with.
Jeff Sharkey494500d2018-06-14 16:04:00 -060091 * @param redactRanges List of file offsets that should be redacted, stored
92 * as {@code [start1, end1, start2, end2, ...]}. Start values are
93 * inclusive and end values are exclusive.
94 */
Jeff Sharkeycb394992018-12-01 18:26:43 -070095 public static ParcelFileDescriptor open(Context context, File file, int mode,
96 long[] redactRanges) throws IOException {
97 return new RedactingFileDescriptor(context, file, mode, redactRanges).mOuter;
98 }
99
100 /**
101 * Update the given ranges argument to remove any references to the given
102 * offset and length. This is typically used when a caller has written over
103 * a previously redacted region.
104 */
105 @VisibleForTesting
106 public static long[] removeRange(long[] ranges, long start, long end) {
107 if (start == end) {
108 return ranges;
109 } else if (start > end) {
110 throw new IllegalArgumentException();
111 }
112
113 long[] res = EmptyArray.LONG;
114 for (int i = 0; i < ranges.length; i += 2) {
115 if (start <= ranges[i] && end >= ranges[i + 1]) {
116 // Range entirely covered; remove it
117 } else if (start >= ranges[i] && end <= ranges[i + 1]) {
118 // Range partially covered; punch a hole
119 res = Arrays.copyOf(res, res.length + 4);
120 res[res.length - 4] = ranges[i];
121 res[res.length - 3] = start;
122 res[res.length - 2] = end;
123 res[res.length - 1] = ranges[i + 1];
124 } else {
125 // Range might covered; adjust edges if needed
126 res = Arrays.copyOf(res, res.length + 2);
127 if (end >= ranges[i] && end <= ranges[i + 1]) {
128 res[res.length - 2] = Math.max(ranges[i], end);
129 } else {
130 res[res.length - 2] = ranges[i];
131 }
132 if (start >= ranges[i] && start <= ranges[i + 1]) {
133 res[res.length - 1] = Math.min(ranges[i + 1], start);
134 } else {
135 res[res.length - 1] = ranges[i + 1];
136 }
137 }
138 }
139 return res;
Jeff Sharkey494500d2018-06-14 16:04:00 -0600140 }
141
142 private final ProxyFileDescriptorCallback mCallback = new ProxyFileDescriptorCallback() {
143 @Override
144 public long onGetSize() throws ErrnoException {
145 return Os.fstat(mInner).st_size;
146 }
147
148 @Override
149 public int onRead(long offset, int size, byte[] data) throws ErrnoException {
150 int n = 0;
151 while (n < size) {
152 try {
153 final int res = Os.pread(mInner, data, n, size - n, offset + n);
154 if (res == 0) {
155 break;
156 } else {
157 n += res;
158 }
159 } catch (InterruptedIOException e) {
160 n += e.bytesTransferred;
161 }
162 }
163
164 // Redact any relevant ranges before returning
165 final long[] ranges = mRedactRanges;
166 for (int i = 0; i < ranges.length; i += 2) {
167 final long start = Math.max(offset, ranges[i]);
168 final long end = Math.min(offset + size, ranges[i + 1]);
169 for (long j = start; j < end; j++) {
170 data[(int) (j - offset)] = 0;
171 }
172 }
173 return n;
174 }
175
176 @Override
177 public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
Jeff Sharkeycb394992018-12-01 18:26:43 -0700178 int n = 0;
179 while (n < size) {
180 try {
181 final int res = Os.pwrite(mInner, data, n, size - n, offset + n);
182 if (res == 0) {
183 break;
184 } else {
185 n += res;
186 }
187 } catch (InterruptedIOException e) {
188 n += e.bytesTransferred;
189 }
190 }
191
192 // Clear any relevant redaction ranges before returning, since the
193 // writer should have access to see the data they just overwrote
194 mRedactRanges = removeRange(mRedactRanges, offset, offset + n);
195 return n;
Jeff Sharkey494500d2018-06-14 16:04:00 -0600196 }
197
198 @Override
199 public void onFsync() throws ErrnoException {
200 Os.fsync(mInner);
201 }
202
203 @Override
204 public void onRelease() {
205 if (DEBUG) Slog.v(TAG, "onRelease()");
206 IoUtils.closeQuietly(mInner);
207 }
208 };
209}