package com.lody.virtual.server.pm.installer; import android.annotation.TargetApi; import android.os.Build; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; import android.util.Log; import com.lody.virtual.helper.utils.ArrayUtils; import com.lody.virtual.helper.utils.FileUtils; import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteOrder; import static android.system.OsConstants.AF_UNIX; import static android.system.OsConstants.SOCK_STREAM; /** * Simple bridge that allows file access across process boundaries without * returning the underlying {@link FileDescriptor}. This is useful when the * server side needs to strongly assert that a client side is completely * hands-off. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class FileBridge extends Thread { private static final String TAG = "FileBridge"; // TODO: consider extending to support bidirectional IO private static final int MSG_LENGTH = 8; /** * CMD_WRITE [len] [data] */ private static final int CMD_WRITE = 1; /** * CMD_FSYNC */ private static final int CMD_FSYNC = 2; /** * CMD_CLOSE */ private static final int CMD_CLOSE = 3; private FileDescriptor mTarget; private final FileDescriptor mServer = new FileDescriptor(); private final FileDescriptor mClient = new FileDescriptor(); private volatile boolean mClosed; public FileBridge() { try { Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mServer, mClient); } catch (ErrnoException e) { throw new RuntimeException("Failed to create bridge"); } } public boolean isClosed() { return mClosed; } public void forceClose() { closeQuietly(mTarget); closeQuietly(mServer); closeQuietly(mClient); mClosed = true; } public void setTargetFile(FileDescriptor target) { mTarget = target; } public FileDescriptor getClientSocket() { return mClient; } @Override public void run() { final byte[] temp = new byte[8192]; try { while (read(mServer, temp, 0, MSG_LENGTH) == MSG_LENGTH) { final int cmd = FileUtils.peekInt(temp, 0, ByteOrder.BIG_ENDIAN); if (cmd == CMD_WRITE) { // Shuttle data into local file int len = FileUtils.peekInt(temp, 4, ByteOrder.BIG_ENDIAN); while (len > 0) { int n = read(mServer, temp, 0, Math.min(temp.length, len)); if (n == -1) { throw new IOException( "Unexpected EOF; still expected " + len + " bytes"); } write(mTarget, temp, 0, n); len -= n; } } else if (cmd == CMD_FSYNC) { // Sync and echo back to confirm Os.fsync(mTarget); write(mServer, temp, 0, MSG_LENGTH); } else if (cmd == CMD_CLOSE) { // Close and echo back to confirm Os.fsync(mTarget); Os.close(mTarget); mClosed = true; write(mServer, temp, 0, MSG_LENGTH); break; } } } catch (ErrnoException | IOException e) { Log.wtf(TAG, "Failed during bridge", e); } finally { forceClose(); } } public static void closeQuietly(FileDescriptor fd) { if (fd != null && fd.valid()) { try { Os.close(fd); } catch (ErrnoException e) { e.printStackTrace(); } } } /** * java.io thinks that a read at EOF is an error and should return -1, contrary to traditional * Unix practice where you'd read until you got 0 bytes (and any future read would return -1). */ public static int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException { ArrayUtils.checkOffsetAndCount(bytes.length, byteOffset, byteCount); if (byteCount == 0) { return 0; } try { int readCount = Os.read(fd, bytes, byteOffset, byteCount); if (readCount == 0) { return -1; } return readCount; } catch (ErrnoException errnoException) { if (errnoException.errno == OsConstants.EAGAIN) { // We return 0 rather than throw if we try to read from an empty non-blocking pipe. return 0; } throw new IOException(errnoException); } } /** * java.io always writes every byte it's asked to, or fails with an error. (That is, unlike * Unix it never just writes as many bytes as happens to be convenient.) */ public static void write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException { ArrayUtils.checkOffsetAndCount(bytes.length, byteOffset, byteCount); if (byteCount == 0) { return; } try { while (byteCount > 0) { int bytesWritten = Os.write(fd, bytes, byteOffset, byteCount); byteCount -= bytesWritten; byteOffset += bytesWritten; } } catch (ErrnoException errnoException) { throw new IOException(errnoException); } } }