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.util.Slog;
22import libcore.io.IoUtils;
23import libcore.io.Streams;
24
25import java.io.IOException;
26import java.io.InputStream;
27import java.io.OutputStream;
28
29/**
30 * Represents a connection to {@code installd}. Allows multiple connect and
31 * disconnect cycles.
32 *
33 * @hide for internal use only
34 */
35public class InstallerConnection {
36    private static final String TAG = "InstallerConnection";
37    private static final boolean LOCAL_DEBUG = false;
38
39    private InputStream mIn;
40    private OutputStream mOut;
41    private LocalSocket mSocket;
42
43    private final byte buf[] = new byte[1024];
44
45    public InstallerConnection() {
46    }
47
48    public synchronized String transact(String cmd) {
49        if (!connect()) {
50            Slog.e(TAG, "connection failed");
51            return "-1";
52        }
53
54        if (!writeCommand(cmd)) {
55            /*
56             * If installd died and restarted in the background (unlikely but
57             * possible) we'll fail on the next write (this one). Try to
58             * reconnect and write the command one more time before giving up.
59             */
60            Slog.e(TAG, "write command failed? reconnect!");
61            if (!connect() || !writeCommand(cmd)) {
62                return "-1";
63            }
64        }
65        if (LOCAL_DEBUG) {
66            Slog.i(TAG, "send: '" + cmd + "'");
67        }
68
69        final int replyLength = readReply();
70        if (replyLength > 0) {
71            String s = new String(buf, 0, replyLength);
72            if (LOCAL_DEBUG) {
73                Slog.i(TAG, "recv: '" + s + "'");
74            }
75            return s;
76        } else {
77            if (LOCAL_DEBUG) {
78                Slog.i(TAG, "fail");
79            }
80            return "-1";
81        }
82    }
83
84    public int execute(String cmd) {
85        String res = transact(cmd);
86        try {
87            return Integer.parseInt(res);
88        } catch (NumberFormatException ex) {
89            return -1;
90        }
91    }
92
93    public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) {
94        return dexopt(apkPath, uid, isPublic, "*", instructionSet, false);
95    }
96
97    public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName,
98            String instructionSet, boolean vmSafeMode) {
99        StringBuilder builder = new StringBuilder("dexopt");
100        builder.append(' ');
101        builder.append(apkPath);
102        builder.append(' ');
103        builder.append(uid);
104        builder.append(isPublic ? " 1" : " 0");
105        builder.append(' ');
106        builder.append(pkgName);
107        builder.append(' ');
108        builder.append(instructionSet);
109        builder.append(' ');
110        builder.append(vmSafeMode ? " 1" : " 0");
111        return execute(builder.toString());
112    }
113
114    public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) {
115        return patchoat(apkPath, uid, isPublic, "*", instructionSet);
116    }
117
118    public int patchoat(String apkPath, int uid, boolean isPublic, String pkgName,
119            String instructionSet) {
120        StringBuilder builder = new StringBuilder("patchoat");
121        builder.append(' ');
122        builder.append(apkPath);
123        builder.append(' ');
124        builder.append(uid);
125        builder.append(isPublic ? " 1" : " 0");
126        builder.append(' ');
127        builder.append(pkgName);
128        builder.append(' ');
129        builder.append(instructionSet);
130        return execute(builder.toString());
131    }
132
133    private boolean connect() {
134        if (mSocket != null) {
135            return true;
136        }
137        Slog.i(TAG, "connecting...");
138        try {
139            mSocket = new LocalSocket();
140
141            LocalSocketAddress address = new LocalSocketAddress("installd",
142                    LocalSocketAddress.Namespace.RESERVED);
143
144            mSocket.connect(address);
145
146            mIn = mSocket.getInputStream();
147            mOut = mSocket.getOutputStream();
148        } catch (IOException ex) {
149            disconnect();
150            return false;
151        }
152        return true;
153    }
154
155    public void disconnect() {
156        Slog.i(TAG, "disconnecting...");
157        IoUtils.closeQuietly(mSocket);
158        IoUtils.closeQuietly(mIn);
159        IoUtils.closeQuietly(mOut);
160
161        mSocket = null;
162        mIn = null;
163        mOut = null;
164    }
165
166
167    private boolean readFully(byte[] buffer, int len) {
168        try {
169            Streams.readFully(mIn, buffer, 0, len);
170        } catch (IOException ioe) {
171            Slog.e(TAG, "read exception");
172            disconnect();
173            return false;
174        }
175
176        if (LOCAL_DEBUG) {
177            Slog.i(TAG, "read " + len + " bytes");
178        }
179
180        return true;
181    }
182
183    private int readReply() {
184        if (!readFully(buf, 2)) {
185            return -1;
186        }
187
188        final int len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8);
189        if ((len < 1) || (len > buf.length)) {
190            Slog.e(TAG, "invalid reply length (" + len + ")");
191            disconnect();
192            return -1;
193        }
194
195        if (!readFully(buf, len)) {
196            return -1;
197        }
198
199        return len;
200    }
201
202    private boolean writeCommand(String cmdString) {
203        final byte[] cmd = cmdString.getBytes();
204        final int len = cmd.length;
205        if ((len < 1) || (len > buf.length)) {
206            return false;
207        }
208
209        buf[0] = (byte) (len & 0xff);
210        buf[1] = (byte) ((len >> 8) & 0xff);
211        try {
212            mOut.write(buf, 0, 2);
213            mOut.write(cmd, 0, len);
214        } catch (IOException ex) {
215            Slog.e(TAG, "write error");
216            disconnect();
217            return false;
218        }
219        return true;
220    }
221}
222