1 /*
2  * Copyright (C) 2021 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.content.pm.cts;
18 
19 import static android.system.OsConstants.EAGAIN;
20 import static android.system.OsConstants.EINTR;
21 
22 import static org.junit.Assert.assertNull;
23 
24 import android.annotation.NonNull;
25 import android.os.Bundle;
26 import android.os.ParcelFileDescriptor;
27 import android.os.RemoteException;
28 import android.os.ResultReceiver;
29 import android.os.ServiceManager;
30 import android.system.ErrnoException;
31 import android.system.Os;
32 import android.system.OsConstants;
33 import android.system.StructPollfd;
34 import android.util.Log;
35 import android.util.Slog;
36 
37 import com.android.incfs.install.IDeviceConnection;
38 import com.android.incfs.install.ILogger;
39 
40 import libcore.io.IoUtils;
41 
42 import java.io.ByteArrayOutputStream;
43 import java.io.FileDescriptor;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.nio.ByteBuffer;
47 
48 public class IncrementalDeviceConnection implements IDeviceConnection {
49     private static final String TAG = "IncrementalDeviceConnection";
50     private static final boolean DEBUG = false;
51 
52     private static final int POLL_TIMEOUT_MS = 300000;
53 
54     private enum ConnectionType {
55         RELIABLE,
56         UNRELIABLE,
57     }
58 
59     private ConnectionType mConnectionType;
60     private final Thread mShellCommand;
61     private final ParcelFileDescriptor mPfd;
62     private final FileDescriptor mFd;
63     private final StructPollfd[] mPollfds = new StructPollfd[]{
64             new StructPollfd()
65     };
66 
IncrementalDeviceConnection(ConnectionType connectionType, Thread shellCommand, ParcelFileDescriptor pfd)67     private IncrementalDeviceConnection(ConnectionType connectionType, Thread shellCommand,
68             ParcelFileDescriptor pfd) {
69         mConnectionType = connectionType;
70         mShellCommand = shellCommand;
71         mPfd = pfd;
72         mFd = pfd.getFileDescriptor();
73 
74         mPollfds[0].fd = mFd;
75         mPollfds[0].events = (short) OsConstants.POLLIN;
76         mPollfds[0].revents = 0;
77         mPollfds[0].userData = null;
78 
79         mShellCommand.start();
80     }
81 
82     @Override
read(ByteBuffer buffer, long timeoutMs)83     public int read(ByteBuffer buffer, long timeoutMs) throws IOException {
84         final boolean blocking = buffer.position() == 0;
85         while (true) {
86             try {
87                 int res = Os.poll(mPollfds, blocking ? POLL_TIMEOUT_MS : 0);
88                 if (res < 0) {
89                     return res;
90                 }
91                 if (res == 0) {
92                     if (blocking) {
93                         throw new IOException("timeout");
94                     }
95                     return 0;
96                 }
97                 return Os.read(mFd, buffer);
98             } catch (ErrnoException e) {
99                 if (e.errno == EINTR) {
100                     continue;
101                 }
102                 if (mConnectionType == ConnectionType.UNRELIABLE) {
103                     e.rethrowAsIOException();
104                 }
105                 if (e.errno == EAGAIN) {
106                     if (!blocking) {
107                         return 0;
108                     }
109                     continue;
110                 }
111                 e.rethrowAsIOException();
112             }
113         }
114     }
115 
116     @Override
write(ByteBuffer buffer, long timeoutMs)117     public int write(ByteBuffer buffer, long timeoutMs) throws IOException {
118         try {
119             return Os.write(mFd, buffer);
120         } catch (ErrnoException e) {
121             e.rethrowAsIOException();
122         }
123         return 0;
124     }
125 
126     @Override
close()127     public void close() throws Exception {
128         mShellCommand.join();
129         IoUtils.closeQuietly(mPfd);
130     }
131 
132     static class Logger implements ILogger {
133         @Override
error(Throwable t, String msgFormat, Object... args)134         public void error(Throwable t, String msgFormat, Object... args) {
135             Slog.e(TAG, String.format(msgFormat, args), t);
136         }
137 
138         @Override
warning(String msgFormat, Object... args)139         public void warning(String msgFormat, Object... args) {
140             Slog.w(TAG, String.format(msgFormat, args));
141         }
142 
143         @Override
info(String msgFormat, Object... args)144         public void info(String msgFormat, Object... args) {
145             Slog.i(TAG, String.format(msgFormat, args));
146         }
147 
148         @Override
verbose(String msgFormat, Object... args)149         public void verbose(String msgFormat, Object... args) {
150             if (!DEBUG) {
151                 return;
152             }
153             Slog.v(TAG, String.format(msgFormat, args));
154         }
155     }
156 
157     static class Factory implements IDeviceConnection.Factory {
158         private final ConnectionType mConnectionType;
159         private final boolean mExpectInstallationSuccess;
160 
reliable()161         static Factory reliable() {
162             return new Factory(ConnectionType.RELIABLE, true);
163         }
164 
ureliable()165         static Factory ureliable() {
166             return new Factory(ConnectionType.UNRELIABLE, false);
167         }
168 
reliableExpectInstallationFailure()169         static Factory reliableExpectInstallationFailure() {
170             return new Factory(ConnectionType.RELIABLE, false);
171         }
172 
Factory(ConnectionType connectionType, boolean expectInstallationSuccess)173         private Factory(ConnectionType connectionType, boolean expectInstallationSuccess) {
174             mConnectionType = connectionType;
175             mExpectInstallationSuccess = expectInstallationSuccess;
176         }
177 
178         @Override
connectToService(@onNull String service, @NonNull String[] parameters)179         public IDeviceConnection connectToService(@NonNull String service,
180                 @NonNull String[] parameters) throws IOException {
181             ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createSocketPair();
182             IoUtils.setBlocking(pipe[0].getFileDescriptor(), false);
183             IoUtils.setBlocking(pipe[1].getFileDescriptor(), false);
184 
185             final ParcelFileDescriptor localPfd = pipe[0];
186             final ParcelFileDescriptor processPfd = pipe[1];
187 
188             final ResultReceiver resultReceiver = new ResultReceiver(null) {
189                 @Override
190                 protected void onReceiveResult(int resultCode, Bundle resultData) {
191                     if (resultCode == 0) {
192                         return;
193                     }
194                     Log.i(TAG, (mExpectInstallationSuccess ? "" : "ERROR: ")
195                             + "Installation finished with code: " + resultCode + ", message: "
196                             + resultData);
197                 }
198             };
199 
200             final Thread shellCommand = new Thread(() -> {
201                 try {
202                     final FileDescriptor processFd = processPfd.getFileDescriptor();
203                     ServiceManager.getService(service).shellCommand(processFd, processFd, processFd,
204                             parameters, null, resultReceiver);
205                 } catch (RemoteException e) {
206                     if (mConnectionType == ConnectionType.RELIABLE) {
207                         assertNull(e);
208                     }
209                 } finally {
210                     IoUtils.closeQuietly(processPfd);
211                 }
212             });
213             return new IncrementalDeviceConnection(mConnectionType, shellCommand, localPfd);
214         }
215     }
216 
readFullStreamOrError(InputStream inputStream)217     private static String readFullStreamOrError(InputStream inputStream) {
218         try (ByteArrayOutputStream result = new ByteArrayOutputStream()) {
219             try {
220                 final byte[] buffer = new byte[1024];
221                 int length;
222                 while ((length = inputStream.read(buffer)) != -1) {
223                     result.write(buffer, 0, length);
224                 }
225             } catch (IOException e) {
226                 return result.toString("UTF-8") + " exception [" + e + "]";
227             }
228             return result.toString("UTF-8");
229         } catch (IOException e) {
230             return e.toString();
231         }
232     }
233 }
234