| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.internal.os; |
| |
| import android.net.LocalSocket; |
| import android.net.LocalSocketAddress; |
| import android.os.SystemClock; |
| import android.text.TextUtils; |
| import android.util.Slog; |
| |
| import com.android.internal.util.Preconditions; |
| |
| import libcore.io.IoUtils; |
| import libcore.io.Streams; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.Arrays; |
| |
| /** |
| * Represents a connection to {@code installd}. Allows multiple connect and |
| * disconnect cycles. |
| * |
| * @hide for internal use only |
| */ |
| public class InstallerConnection { |
| private static final String TAG = "InstallerConnection"; |
| private static final boolean LOCAL_DEBUG = false; |
| |
| private InputStream mIn; |
| private OutputStream mOut; |
| private LocalSocket mSocket; |
| |
| private volatile Object mWarnIfHeld; |
| |
| private final byte buf[] = new byte[1024]; |
| |
| public InstallerConnection() { |
| } |
| |
| /** |
| * Yell loudly if someone tries making future calls while holding a lock on |
| * the given object. |
| */ |
| public void setWarnIfHeld(Object warnIfHeld) { |
| Preconditions.checkState(mWarnIfHeld == null); |
| mWarnIfHeld = Preconditions.checkNotNull(warnIfHeld); |
| } |
| |
| public synchronized String transact(String cmd) { |
| if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) { |
| Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x" |
| + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable()); |
| } |
| |
| if (!connect()) { |
| Slog.e(TAG, "connection failed"); |
| return "-1"; |
| } |
| |
| if (!writeCommand(cmd)) { |
| /* |
| * If installd died and restarted in the background (unlikely but |
| * possible) we'll fail on the next write (this one). Try to |
| * reconnect and write the command one more time before giving up. |
| */ |
| Slog.e(TAG, "write command failed? reconnect!"); |
| if (!connect() || !writeCommand(cmd)) { |
| return "-1"; |
| } |
| } |
| if (LOCAL_DEBUG) { |
| Slog.i(TAG, "send: '" + cmd + "'"); |
| } |
| |
| final int replyLength = readReply(); |
| if (replyLength > 0) { |
| String s = new String(buf, 0, replyLength); |
| if (LOCAL_DEBUG) { |
| Slog.i(TAG, "recv: '" + s + "'"); |
| } |
| return s; |
| } else { |
| if (LOCAL_DEBUG) { |
| Slog.i(TAG, "fail"); |
| } |
| return "-1"; |
| } |
| } |
| |
| public String[] execute(String cmd, Object... args) throws InstallerException { |
| final StringBuilder builder = new StringBuilder(cmd); |
| for (Object arg : args) { |
| String escaped; |
| if (arg == null) { |
| escaped = ""; |
| } else { |
| escaped = String.valueOf(arg); |
| } |
| if (escaped.indexOf('\0') != -1 || escaped.indexOf(' ') != -1 || "!".equals(escaped)) { |
| throw new InstallerException( |
| "Invalid argument while executing " + cmd + " " + Arrays.toString(args)); |
| } |
| if (TextUtils.isEmpty(escaped)) { |
| escaped = "!"; |
| } |
| builder.append(' ').append(escaped); |
| } |
| final String[] resRaw = transact(builder.toString()).split(" "); |
| int res = -1; |
| try { |
| res = Integer.parseInt(resRaw[0]); |
| } catch (ArrayIndexOutOfBoundsException | NumberFormatException ignored) { |
| } |
| if (res != 0) { |
| throw new InstallerException( |
| "Failed to execute " + cmd + " " + Arrays.toString(args) + ": " + res); |
| } |
| return resRaw; |
| } |
| |
| public void dexopt(String apkPath, int uid, String instructionSet, int dexoptNeeded, |
| int dexFlags, String compilerFilter, String volumeUuid, String sharedLibraries) |
| throws InstallerException { |
| dexopt(apkPath, uid, "*", instructionSet, dexoptNeeded, null /*outputPath*/, dexFlags, |
| compilerFilter, volumeUuid, sharedLibraries); |
| } |
| |
| public void dexopt(String apkPath, int uid, String pkgName, String instructionSet, |
| int dexoptNeeded, String outputPath, int dexFlags, String compilerFilter, |
| String volumeUuid, String sharedLibraries) throws InstallerException { |
| execute("dexopt", |
| apkPath, |
| uid, |
| pkgName, |
| instructionSet, |
| dexoptNeeded, |
| outputPath, |
| dexFlags, |
| compilerFilter, |
| volumeUuid, |
| sharedLibraries); |
| } |
| |
| public boolean mergeProfiles(int uid, String pkgName) throws InstallerException { |
| final String[] res = execute("merge_profiles", uid, pkgName); |
| |
| if ((res == null) || (res.length != 2)) { |
| throw new InstallerException("Invalid size result: " + Arrays.toString(res)); |
| } |
| |
| // Just as a sanity check. Anything != "true" will be interpreted as false by parseBoolean. |
| if (!res[1].equals("true") && !res[1].equals("false")) { |
| throw new InstallerException("Invalid boolean result: " + Arrays.toString(res)); |
| } |
| |
| return Boolean.parseBoolean(res[1]); |
| } |
| |
| private boolean connect() { |
| if (mSocket != null) { |
| return true; |
| } |
| Slog.i(TAG, "connecting..."); |
| try { |
| mSocket = new LocalSocket(); |
| |
| LocalSocketAddress address = new LocalSocketAddress("installd", |
| LocalSocketAddress.Namespace.RESERVED); |
| |
| mSocket.connect(address); |
| |
| mIn = mSocket.getInputStream(); |
| mOut = mSocket.getOutputStream(); |
| } catch (IOException ex) { |
| disconnect(); |
| return false; |
| } |
| return true; |
| } |
| |
| public void disconnect() { |
| Slog.i(TAG, "disconnecting..."); |
| IoUtils.closeQuietly(mSocket); |
| IoUtils.closeQuietly(mIn); |
| IoUtils.closeQuietly(mOut); |
| |
| mSocket = null; |
| mIn = null; |
| mOut = null; |
| } |
| |
| |
| private boolean readFully(byte[] buffer, int len) { |
| try { |
| Streams.readFully(mIn, buffer, 0, len); |
| } catch (IOException ioe) { |
| Slog.e(TAG, "read exception"); |
| disconnect(); |
| return false; |
| } |
| |
| if (LOCAL_DEBUG) { |
| Slog.i(TAG, "read " + len + " bytes"); |
| } |
| |
| return true; |
| } |
| |
| private int readReply() { |
| if (!readFully(buf, 2)) { |
| return -1; |
| } |
| |
| final int len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8); |
| if ((len < 1) || (len > buf.length)) { |
| Slog.e(TAG, "invalid reply length (" + len + ")"); |
| disconnect(); |
| return -1; |
| } |
| |
| if (!readFully(buf, len)) { |
| return -1; |
| } |
| |
| return len; |
| } |
| |
| private boolean writeCommand(String cmdString) { |
| final byte[] cmd = cmdString.getBytes(); |
| final int len = cmd.length; |
| if ((len < 1) || (len > buf.length)) { |
| return false; |
| } |
| |
| buf[0] = (byte) (len & 0xff); |
| buf[1] = (byte) ((len >> 8) & 0xff); |
| try { |
| mOut.write(buf, 0, 2); |
| mOut.write(cmd, 0, len); |
| } catch (IOException ex) { |
| Slog.e(TAG, "write error"); |
| disconnect(); |
| return false; |
| } |
| return true; |
| } |
| |
| public void waitForConnection() { |
| for (;;) { |
| try { |
| execute("ping"); |
| return; |
| } catch (InstallerException ignored) { |
| } |
| Slog.w(TAG, "installd not ready"); |
| SystemClock.sleep(1000); |
| } |
| } |
| |
| public static class InstallerException extends Exception { |
| public InstallerException(String detailMessage) { |
| super(detailMessage); |
| } |
| } |
| } |