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