FileBridge.java revision 77d218e1869e69c8d436b09cd11dcfe45e50b2cf
1/*
2 * Copyright (C) 2014 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 static android.system.OsConstants.AF_UNIX;
20import static android.system.OsConstants.SOCK_STREAM;
21
22import android.system.ErrnoException;
23import android.system.Os;
24import android.util.Log;
25
26import libcore.io.IoBridge;
27import libcore.io.IoUtils;
28import libcore.io.Memory;
29import libcore.io.Streams;
30
31import java.io.FileDescriptor;
32import java.io.IOException;
33import java.io.OutputStream;
34import java.nio.ByteOrder;
35import java.util.Arrays;
36
37/**
38 * Simple bridge that allows file access across process boundaries without
39 * returning the underlying {@link FileDescriptor}. This is useful when the
40 * server side needs to strongly assert that a client side is completely
41 * hands-off.
42 *
43 * @hide
44 */
45public class FileBridge extends Thread {
46    private static final String TAG = "FileBridge";
47
48    // TODO: consider extending to support bidirectional IO
49
50    private static final int MSG_LENGTH = 8;
51
52    /** CMD_WRITE [len] [data] */
53    private static final int CMD_WRITE = 1;
54    /** CMD_FSYNC */
55    private static final int CMD_FSYNC = 2;
56    /** CMD_CLOSE */
57    private static final int CMD_CLOSE = 3;
58
59    private FileDescriptor mTarget;
60
61    private final FileDescriptor mServer = new FileDescriptor();
62    private final FileDescriptor mClient = new FileDescriptor();
63
64    private volatile boolean mClosed;
65
66    public FileBridge() {
67        try {
68            Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mServer, mClient);
69        } catch (ErrnoException e) {
70            throw new RuntimeException("Failed to create bridge");
71        }
72    }
73
74    public boolean isClosed() {
75        return mClosed;
76    }
77
78    public void forceClose() {
79        IoUtils.closeQuietly(mTarget);
80        IoUtils.closeQuietly(mServer);
81        IoUtils.closeQuietly(mClient);
82        mClosed = true;
83    }
84
85    public void setTargetFile(FileDescriptor target) {
86        mTarget = target;
87    }
88
89    public FileDescriptor getClientSocket() {
90        return mClient;
91    }
92
93    @Override
94    public void run() {
95        final byte[] temp = new byte[8192];
96        try {
97            while (IoBridge.read(mServer, temp, 0, MSG_LENGTH) == MSG_LENGTH) {
98                final int cmd = Memory.peekInt(temp, 0, ByteOrder.BIG_ENDIAN);
99                if (cmd == CMD_WRITE) {
100                    // Shuttle data into local file
101                    int len = Memory.peekInt(temp, 4, ByteOrder.BIG_ENDIAN);
102                    while (len > 0) {
103                        int n = IoBridge.read(mServer, temp, 0, Math.min(temp.length, len));
104                        if (n == -1) {
105                            throw new IOException(
106                                    "Unexpected EOF; still expected " + len + " bytes");
107                        }
108                        IoBridge.write(mTarget, temp, 0, n);
109                        len -= n;
110                    }
111
112                } else if (cmd == CMD_FSYNC) {
113                    // Sync and echo back to confirm
114                    Os.fsync(mTarget);
115                    IoBridge.write(mServer, temp, 0, MSG_LENGTH);
116
117                } else if (cmd == CMD_CLOSE) {
118                    // Close and echo back to confirm
119                    Os.fsync(mTarget);
120                    Os.close(mTarget);
121                    mClosed = true;
122                    IoBridge.write(mServer, temp, 0, MSG_LENGTH);
123                    break;
124                }
125            }
126
127        } catch (ErrnoException | IOException e) {
128            Log.wtf(TAG, "Failed during bridge", e);
129        } finally {
130            forceClose();
131        }
132    }
133
134    public static class FileBridgeOutputStream extends OutputStream {
135        private final ParcelFileDescriptor mClientPfd;
136        private final FileDescriptor mClient;
137        private final byte[] mTemp = new byte[MSG_LENGTH];
138
139        public FileBridgeOutputStream(ParcelFileDescriptor clientPfd) {
140            mClientPfd = clientPfd;
141            mClient = clientPfd.getFileDescriptor();
142        }
143
144        public FileBridgeOutputStream(FileDescriptor client) {
145            mClientPfd = null;
146            mClient = client;
147        }
148
149        @Override
150        public void close() throws IOException {
151            try {
152                writeCommandAndBlock(CMD_CLOSE, "close()");
153            } finally {
154                IoBridge.closeAndSignalBlockedThreads(mClient);
155                IoUtils.closeQuietly(mClientPfd);
156            }
157        }
158
159        public void fsync() throws IOException {
160            writeCommandAndBlock(CMD_FSYNC, "fsync()");
161        }
162
163        private void writeCommandAndBlock(int cmd, String cmdString) throws IOException {
164            Memory.pokeInt(mTemp, 0, cmd, ByteOrder.BIG_ENDIAN);
165            IoBridge.write(mClient, mTemp, 0, MSG_LENGTH);
166
167            // Wait for server to ack
168            if (IoBridge.read(mClient, mTemp, 0, MSG_LENGTH) == MSG_LENGTH) {
169                if (Memory.peekInt(mTemp, 0, ByteOrder.BIG_ENDIAN) == cmd) {
170                    return;
171                }
172            }
173
174            throw new IOException("Failed to execute " + cmdString + " across bridge");
175        }
176
177        @Override
178        public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException {
179            Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount);
180            Memory.pokeInt(mTemp, 0, CMD_WRITE, ByteOrder.BIG_ENDIAN);
181            Memory.pokeInt(mTemp, 4, byteCount, ByteOrder.BIG_ENDIAN);
182            IoBridge.write(mClient, mTemp, 0, MSG_LENGTH);
183            IoBridge.write(mClient, buffer, byteOffset, byteCount);
184        }
185
186        @Override
187        public void write(int oneByte) throws IOException {
188            Streams.writeSingleByte(this, oneByte);
189        }
190    }
191}
192