Loading core/java/android/os/RedactingFileDescriptor.java 0 → 100644 +143 −0 Original line number Diff line number Diff line /* * 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.system.OsConstants; import android.util.Slog; import libcore.io.IoUtils; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.InterruptedIOException; /** * Variant of {@link FileDescriptor} that allows its creator to specify regions * that should be redacted (appearing as zeros to the reader). * * @hide */ public class RedactingFileDescriptor { private static final String TAG = "RedactingFileDescriptor"; private static final boolean DEBUG = true; private final long[] mRedactRanges; private FileDescriptor mInner = null; private ParcelFileDescriptor mOuter = null; private RedactingFileDescriptor(Context context, File file, long[] redactRanges) throws IOException { mRedactRanges = checkRangesArgument(redactRanges); try { try { mInner = Os.open(file.getAbsolutePath(), OsConstants.O_RDONLY, 0); mOuter = context.getSystemService(StorageManager.class) .openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_ONLY, 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, read-only view of the underlying data. * * @param file The underlying file to open. * @param redactRanges List of file offsets that should be redacted, stored * as {@code [start1, end1, start2, end2, ...]}. Start values are * inclusive and end values are exclusive. */ public static ParcelFileDescriptor open(Context context, File file, long[] redactRanges) throws IOException { return new RedactingFileDescriptor(context, file, redactRanges).mOuter; } 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; } } return n; } @Override public int onWrite(long offset, int size, byte[] data) throws ErrnoException { throw new ErrnoException(TAG, OsConstants.EBADF); } @Override public void onFsync() throws ErrnoException { Os.fsync(mInner); } @Override public void onRelease() { if (DEBUG) Slog.v(TAG, "onRelease()"); IoUtils.closeQuietly(mInner); } }; } core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java 0 → 100644 +109 −0 Original line number Diff line number Diff line /* * 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 static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.system.Os; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.Arrays; @RunWith(AndroidJUnit4.class) public class RedactingFileDescriptorTest { private Context mContext; private File mFile; @Before public void setUp() throws Exception { mContext = InstrumentationRegistry.getContext(); mFile = File.createTempFile("redacting", "dat"); try (FileOutputStream out = new FileOutputStream(mFile)) { final byte[] buf = new byte[1_000_000]; Arrays.fill(buf, (byte) 64); out.write(buf); } } @After public void tearDown() throws Exception { mFile.delete(); } @Test public void testSingleByte() throws Exception { final FileDescriptor fd = RedactingFileDescriptor .open(mContext, mFile, new long[] { 10, 11 }).getFileDescriptor(); final byte[] buf = new byte[1_000]; assertEquals(buf.length, Os.read(fd, buf, 0, buf.length)); for (int i = 0; i < buf.length; i++) { if (i == 10) { assertEquals(0, buf[i]); } else { assertEquals(64, buf[i]); } } } @Test public void testRanges() throws Exception { final FileDescriptor fd = RedactingFileDescriptor .open(mContext, mFile, new long[] { 100, 200, 300, 400 }).getFileDescriptor(); final byte[] buf = new byte[10]; assertEquals(buf.length, Os.pread(fd, buf, 0, 10, 90)); assertArrayEquals(new byte[] { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 }, buf); assertEquals(buf.length, Os.pread(fd, buf, 0, 10, 95)); assertArrayEquals(new byte[] { 64, 64, 64, 64, 64, 0, 0, 0, 0, 0 }, buf); assertEquals(buf.length, Os.pread(fd, buf, 0, 10, 100)); assertArrayEquals(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, buf); assertEquals(buf.length, Os.pread(fd, buf, 0, 10, 195)); assertArrayEquals(new byte[] { 0, 0, 0, 0, 0, 64, 64, 64, 64, 64 }, buf); assertEquals(buf.length, Os.pread(fd, buf, 0, 10, 395)); assertArrayEquals(new byte[] { 0, 0, 0, 0, 0, 64, 64, 64, 64, 64 }, buf); } @Test public void testEntireFile() throws Exception { final FileDescriptor fd = RedactingFileDescriptor .open(mContext, mFile, new long[] { 0, 5_000_000 }).getFileDescriptor(); try (FileInputStream in = new FileInputStream(fd)) { int val; while ((val = in.read()) != -1) { assertEquals(0, val); } } } } Loading
core/java/android/os/RedactingFileDescriptor.java 0 → 100644 +143 −0 Original line number Diff line number Diff line /* * 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.system.OsConstants; import android.util.Slog; import libcore.io.IoUtils; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.InterruptedIOException; /** * Variant of {@link FileDescriptor} that allows its creator to specify regions * that should be redacted (appearing as zeros to the reader). * * @hide */ public class RedactingFileDescriptor { private static final String TAG = "RedactingFileDescriptor"; private static final boolean DEBUG = true; private final long[] mRedactRanges; private FileDescriptor mInner = null; private ParcelFileDescriptor mOuter = null; private RedactingFileDescriptor(Context context, File file, long[] redactRanges) throws IOException { mRedactRanges = checkRangesArgument(redactRanges); try { try { mInner = Os.open(file.getAbsolutePath(), OsConstants.O_RDONLY, 0); mOuter = context.getSystemService(StorageManager.class) .openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_ONLY, 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, read-only view of the underlying data. * * @param file The underlying file to open. * @param redactRanges List of file offsets that should be redacted, stored * as {@code [start1, end1, start2, end2, ...]}. Start values are * inclusive and end values are exclusive. */ public static ParcelFileDescriptor open(Context context, File file, long[] redactRanges) throws IOException { return new RedactingFileDescriptor(context, file, redactRanges).mOuter; } 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; } } return n; } @Override public int onWrite(long offset, int size, byte[] data) throws ErrnoException { throw new ErrnoException(TAG, OsConstants.EBADF); } @Override public void onFsync() throws ErrnoException { Os.fsync(mInner); } @Override public void onRelease() { if (DEBUG) Slog.v(TAG, "onRelease()"); IoUtils.closeQuietly(mInner); } }; }
core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java 0 → 100644 +109 −0 Original line number Diff line number Diff line /* * 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 static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.system.Os; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.Arrays; @RunWith(AndroidJUnit4.class) public class RedactingFileDescriptorTest { private Context mContext; private File mFile; @Before public void setUp() throws Exception { mContext = InstrumentationRegistry.getContext(); mFile = File.createTempFile("redacting", "dat"); try (FileOutputStream out = new FileOutputStream(mFile)) { final byte[] buf = new byte[1_000_000]; Arrays.fill(buf, (byte) 64); out.write(buf); } } @After public void tearDown() throws Exception { mFile.delete(); } @Test public void testSingleByte() throws Exception { final FileDescriptor fd = RedactingFileDescriptor .open(mContext, mFile, new long[] { 10, 11 }).getFileDescriptor(); final byte[] buf = new byte[1_000]; assertEquals(buf.length, Os.read(fd, buf, 0, buf.length)); for (int i = 0; i < buf.length; i++) { if (i == 10) { assertEquals(0, buf[i]); } else { assertEquals(64, buf[i]); } } } @Test public void testRanges() throws Exception { final FileDescriptor fd = RedactingFileDescriptor .open(mContext, mFile, new long[] { 100, 200, 300, 400 }).getFileDescriptor(); final byte[] buf = new byte[10]; assertEquals(buf.length, Os.pread(fd, buf, 0, 10, 90)); assertArrayEquals(new byte[] { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 }, buf); assertEquals(buf.length, Os.pread(fd, buf, 0, 10, 95)); assertArrayEquals(new byte[] { 64, 64, 64, 64, 64, 0, 0, 0, 0, 0 }, buf); assertEquals(buf.length, Os.pread(fd, buf, 0, 10, 100)); assertArrayEquals(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, buf); assertEquals(buf.length, Os.pread(fd, buf, 0, 10, 195)); assertArrayEquals(new byte[] { 0, 0, 0, 0, 0, 64, 64, 64, 64, 64 }, buf); assertEquals(buf.length, Os.pread(fd, buf, 0, 10, 395)); assertArrayEquals(new byte[] { 0, 0, 0, 0, 0, 64, 64, 64, 64, 64 }, buf); } @Test public void testEntireFile() throws Exception { final FileDescriptor fd = RedactingFileDescriptor .open(mContext, mFile, new long[] { 0, 5_000_000 }).getFileDescriptor(); try (FileInputStream in = new FileInputStream(fd)) { int val; while ((val = in.read()) != -1) { assertEquals(0, val); } } } }