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