1/* 2 * Copyright (C) 2008 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 com.android.internal.os; 18 19import android.net.LocalSocket; 20import android.net.LocalSocketAddress; 21import android.os.SystemClock; 22import android.text.TextUtils; 23import android.util.Slog; 24 25import com.android.internal.util.Preconditions; 26 27import libcore.io.IoUtils; 28import libcore.io.Streams; 29 30import java.io.IOException; 31import java.io.InputStream; 32import java.io.OutputStream; 33import java.util.Arrays; 34 35/** 36 * Represents a connection to {@code installd}. Allows multiple connect and 37 * disconnect cycles. 38 * 39 * @hide for internal use only 40 */ 41public class InstallerConnection { 42 private static final String TAG = "InstallerConnection"; 43 private static final boolean LOCAL_DEBUG = false; 44 45 private InputStream mIn; 46 private OutputStream mOut; 47 private LocalSocket mSocket; 48 49 private volatile Object mWarnIfHeld; 50 51 private final byte buf[] = new byte[1024]; 52 53 public InstallerConnection() { 54 } 55 56 /** 57 * Yell loudly if someone tries making future calls while holding a lock on 58 * the given object. 59 */ 60 public void setWarnIfHeld(Object warnIfHeld) { 61 Preconditions.checkState(mWarnIfHeld == null); 62 mWarnIfHeld = Preconditions.checkNotNull(warnIfHeld); 63 } 64 65 public synchronized String transact(String cmd) { 66 if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) { 67 Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x" 68 + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable()); 69 } 70 71 if (!connect()) { 72 Slog.e(TAG, "connection failed"); 73 return "-1"; 74 } 75 76 if (!writeCommand(cmd)) { 77 /* 78 * If installd died and restarted in the background (unlikely but 79 * possible) we'll fail on the next write (this one). Try to 80 * reconnect and write the command one more time before giving up. 81 */ 82 Slog.e(TAG, "write command failed? reconnect!"); 83 if (!connect() || !writeCommand(cmd)) { 84 return "-1"; 85 } 86 } 87 if (LOCAL_DEBUG) { 88 Slog.i(TAG, "send: '" + cmd + "'"); 89 } 90 91 final int replyLength = readReply(); 92 if (replyLength > 0) { 93 String s = new String(buf, 0, replyLength); 94 if (LOCAL_DEBUG) { 95 Slog.i(TAG, "recv: '" + s + "'"); 96 } 97 return s; 98 } else { 99 if (LOCAL_DEBUG) { 100 Slog.i(TAG, "fail"); 101 } 102 return "-1"; 103 } 104 } 105 106 public String[] execute(String cmd, Object... args) throws InstallerException { 107 final StringBuilder builder = new StringBuilder(cmd); 108 for (Object arg : args) { 109 String escaped; 110 if (arg == null) { 111 escaped = ""; 112 } else { 113 escaped = String.valueOf(arg); 114 } 115 if (escaped.indexOf('\0') != -1 || escaped.indexOf(' ') != -1 || "!".equals(escaped)) { 116 throw new InstallerException( 117 "Invalid argument while executing " + cmd + " " + Arrays.toString(args)); 118 } 119 if (TextUtils.isEmpty(escaped)) { 120 escaped = "!"; 121 } 122 builder.append(' ').append(escaped); 123 } 124 final String[] resRaw = transact(builder.toString()).split(" "); 125 int res = -1; 126 try { 127 res = Integer.parseInt(resRaw[0]); 128 } catch (ArrayIndexOutOfBoundsException | NumberFormatException ignored) { 129 } 130 if (res != 0) { 131 throw new InstallerException( 132 "Failed to execute " + cmd + " " + Arrays.toString(args) + ": " + res); 133 } 134 return resRaw; 135 } 136 137 public void dexopt(String apkPath, int uid, String instructionSet, int dexoptNeeded, 138 int dexFlags, String compilerFilter, String volumeUuid, String sharedLibraries) 139 throws InstallerException { 140 dexopt(apkPath, uid, "*", instructionSet, dexoptNeeded, null /*outputPath*/, dexFlags, 141 compilerFilter, volumeUuid, sharedLibraries); 142 } 143 144 public void dexopt(String apkPath, int uid, String pkgName, String instructionSet, 145 int dexoptNeeded, String outputPath, int dexFlags, String compilerFilter, 146 String volumeUuid, String sharedLibraries) throws InstallerException { 147 execute("dexopt", 148 apkPath, 149 uid, 150 pkgName, 151 instructionSet, 152 dexoptNeeded, 153 outputPath, 154 dexFlags, 155 compilerFilter, 156 volumeUuid, 157 sharedLibraries); 158 } 159 160 private boolean safeParseBooleanResult(String[] res) throws InstallerException { 161 if ((res == null) || (res.length != 2)) { 162 throw new InstallerException("Invalid size result: " + Arrays.toString(res)); 163 } 164 165 // Just as a sanity check. Anything != "true" will be interpreted as false by parseBoolean. 166 if (!res[1].equals("true") && !res[1].equals("false")) { 167 throw new InstallerException("Invalid boolean result: " + Arrays.toString(res)); 168 } 169 170 return Boolean.parseBoolean(res[1]); 171 } 172 173 public boolean mergeProfiles(int uid, String pkgName) throws InstallerException { 174 final String[] res = execute("merge_profiles", uid, pkgName); 175 176 return safeParseBooleanResult(res); 177 } 178 179 public boolean dumpProfiles(String gid, String packageName, String codePaths) 180 throws InstallerException { 181 final String[] res = execute("dump_profiles", gid, packageName, codePaths); 182 183 return safeParseBooleanResult(res); 184 } 185 186 private boolean connect() { 187 if (mSocket != null) { 188 return true; 189 } 190 Slog.i(TAG, "connecting..."); 191 try { 192 mSocket = new LocalSocket(); 193 194 LocalSocketAddress address = new LocalSocketAddress("installd", 195 LocalSocketAddress.Namespace.RESERVED); 196 197 mSocket.connect(address); 198 199 mIn = mSocket.getInputStream(); 200 mOut = mSocket.getOutputStream(); 201 } catch (IOException ex) { 202 disconnect(); 203 return false; 204 } 205 return true; 206 } 207 208 public void disconnect() { 209 Slog.i(TAG, "disconnecting..."); 210 IoUtils.closeQuietly(mSocket); 211 IoUtils.closeQuietly(mIn); 212 IoUtils.closeQuietly(mOut); 213 214 mSocket = null; 215 mIn = null; 216 mOut = null; 217 } 218 219 220 private boolean readFully(byte[] buffer, int len) { 221 try { 222 Streams.readFully(mIn, buffer, 0, len); 223 } catch (IOException ioe) { 224 Slog.e(TAG, "read exception"); 225 disconnect(); 226 return false; 227 } 228 229 if (LOCAL_DEBUG) { 230 Slog.i(TAG, "read " + len + " bytes"); 231 } 232 233 return true; 234 } 235 236 private int readReply() { 237 if (!readFully(buf, 2)) { 238 return -1; 239 } 240 241 final int len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8); 242 if ((len < 1) || (len > buf.length)) { 243 Slog.e(TAG, "invalid reply length (" + len + ")"); 244 disconnect(); 245 return -1; 246 } 247 248 if (!readFully(buf, len)) { 249 return -1; 250 } 251 252 return len; 253 } 254 255 private boolean writeCommand(String cmdString) { 256 final byte[] cmd = cmdString.getBytes(); 257 final int len = cmd.length; 258 if ((len < 1) || (len > buf.length)) { 259 return false; 260 } 261 262 buf[0] = (byte) (len & 0xff); 263 buf[1] = (byte) ((len >> 8) & 0xff); 264 try { 265 mOut.write(buf, 0, 2); 266 mOut.write(cmd, 0, len); 267 } catch (IOException ex) { 268 Slog.e(TAG, "write error"); 269 disconnect(); 270 return false; 271 } 272 return true; 273 } 274 275 public void waitForConnection() { 276 for (;;) { 277 try { 278 execute("ping"); 279 return; 280 } catch (InstallerException ignored) { 281 } 282 Slog.w(TAG, "installd not ready"); 283 SystemClock.sleep(1000); 284 } 285 } 286 287 public static class InstallerException extends Exception { 288 public InstallerException(String detailMessage) { 289 super(detailMessage); 290 } 291 } 292} 293