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 
17 package android.os;
18 
19 import static android.system.OsConstants.AF_UNIX;
20 import static android.system.OsConstants.SOCK_STREAM;
21 
22 import android.system.ErrnoException;
23 import android.system.Os;
24 import android.util.Log;
25 
26 import libcore.io.IoBridge;
27 import libcore.io.IoUtils;
28 import libcore.io.Memory;
29 import libcore.io.Streams;
30 
31 import java.io.FileDescriptor;
32 import java.io.IOException;
33 import java.io.OutputStream;
34 import java.nio.ByteOrder;
35 import 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  */
45 public 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 
FileBridge()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 
isClosed()74     public boolean isClosed() {
75         return mClosed;
76     }
77 
forceClose()78     public void forceClose() {
79         IoUtils.closeQuietly(mTarget);
80         IoUtils.closeQuietly(mServer);
81         IoUtils.closeQuietly(mClient);
82         mClosed = true;
83     }
84 
setTargetFile(FileDescriptor target)85     public void setTargetFile(FileDescriptor target) {
86         mTarget = target;
87     }
88 
getClientSocket()89     public FileDescriptor getClientSocket() {
90         return mClient;
91     }
92 
93     @Override
run()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 
FileBridgeOutputStream(ParcelFileDescriptor clientPfd)139         public FileBridgeOutputStream(ParcelFileDescriptor clientPfd) {
140             mClientPfd = clientPfd;
141             mClient = clientPfd.getFileDescriptor();
142         }
143 
FileBridgeOutputStream(FileDescriptor client)144         public FileBridgeOutputStream(FileDescriptor client) {
145             mClientPfd = null;
146             mClient = client;
147         }
148 
149         @Override
close()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 
fsync()159         public void fsync() throws IOException {
160             writeCommandAndBlock(CMD_FSYNC, "fsync()");
161         }
162 
writeCommandAndBlock(int cmd, String cmdString)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
write(byte[] buffer, int byteOffset, int byteCount)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
write(int oneByte)187         public void write(int oneByte) throws IOException {
188             Streams.writeSingleByte(this, oneByte);
189         }
190     }
191 }
192