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