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