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.server;
18
19import android.content.pm.PackageStats;
20import android.net.LocalSocketAddress;
21import android.net.LocalSocket;
22import android.util.Config;
23import android.util.Slog;
24
25import java.io.IOException;
26import java.io.InputStream;
27import java.io.OutputStream;
28import java.net.Socket;
29
30
31class Installer {
32    private static final String TAG = "Installer";
33	InputStream mIn;
34	OutputStream mOut;
35	LocalSocket mSocket;
36
37	byte buf[] = new byte[1024];
38	int buflen = 0;
39
40    private boolean connect() {
41        if (mSocket != null) {
42            return true;
43        }
44        Slog.i(TAG, "connecting...");
45        try {
46            mSocket = new LocalSocket();
47
48            LocalSocketAddress address = new LocalSocketAddress(
49                "installd", LocalSocketAddress.Namespace.RESERVED);
50
51            mSocket.connect(address);
52
53            mIn = mSocket.getInputStream();
54            mOut = mSocket.getOutputStream();
55        } catch (IOException ex) {
56            disconnect();
57            return false;
58        }
59        return true;
60    }
61
62	private void disconnect() {
63        Slog.i(TAG,"disconnecting...");
64		try {
65			if (mSocket != null) mSocket.close();
66		} catch (IOException ex) { }
67		try {
68			if (mIn != null) mIn.close();
69		} catch (IOException ex) { }
70		try {
71			if (mOut != null) mOut.close();
72		} catch (IOException ex) { }
73		mSocket = null;
74		mIn = null;
75		mOut = null;
76	}
77
78	private boolean readBytes(byte buffer[], int len) {
79		int off = 0, count;
80        if (len < 0) return false;
81		while (off != len) {
82			try {
83				count = mIn.read(buffer, off, len - off);
84				if (count <= 0) {
85                    Slog.e(TAG, "read error " + count);
86                    break;
87                }
88				off += count;
89			} catch (IOException ex) {
90                Slog.e(TAG,"read exception");
91				break;
92			}
93		}
94//        Slog.i(TAG, "read "+len+" bytes");
95		if (off == len) return true;
96		disconnect();
97		return false;
98	}
99
100	private boolean readReply() {
101		int len;
102		buflen = 0;
103		if (!readBytes(buf, 2)) return false;
104		len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8);
105		if ((len < 1) || (len > 1024)) {
106            Slog.e(TAG,"invalid reply length ("+len+")");
107			disconnect();
108			return false;
109		}
110		if (!readBytes(buf, len)) return false;
111		buflen = len;
112		return true;
113	}
114
115	private boolean writeCommand(String _cmd) {
116		byte[] cmd = _cmd.getBytes();
117		int len = cmd.length;
118		if ((len < 1) || (len > 1024)) return false;
119		buf[0] = (byte) (len & 0xff);
120		buf[1] = (byte) ((len >> 8) & 0xff);
121		try {
122			mOut.write(buf, 0, 2);
123			mOut.write(cmd, 0, len);
124		} catch (IOException ex) {
125            Slog.e(TAG,"write error");
126			disconnect();
127			return false;
128		}
129		return true;
130	}
131
132	private synchronized String transaction(String cmd) {
133		if (!connect()) {
134            Slog.e(TAG, "connection failed");
135            return "-1";
136        }
137
138        if (!writeCommand(cmd)) {
139                /* If installd died and restarted in the background
140                 * (unlikely but possible) we'll fail on the next
141                 * write (this one).  Try to reconnect and write
142                 * the command one more time before giving up.
143                 */
144            Slog.e(TAG, "write command failed? reconnect!");
145            if (!connect() || !writeCommand(cmd)) {
146                return "-1";
147            }
148        }
149//        Slog.i(TAG,"send: '"+cmd+"'");
150		if (readReply()) {
151            String s = new String(buf, 0, buflen);
152//            Slog.i(TAG,"recv: '"+s+"'");
153			return s;
154		} else {
155//            Slog.i(TAG,"fail");
156			return "-1";
157		}
158	}
159
160	private int execute(String cmd) {
161		String res = transaction(cmd);
162		try {
163			return Integer.parseInt(res);
164		} catch (NumberFormatException ex) {
165			return -1;
166		}
167	}
168
169    public int install(String name, boolean useEncryptedFilesystem, int uid, int gid) {
170        StringBuilder builder = new StringBuilder("install");
171        builder.append(' ');
172        builder.append(name);
173        builder.append(' ');
174        if (useEncryptedFilesystem) {
175            builder.append('1');
176        } else {
177            builder.append('0');
178        }
179        builder.append(' ');
180        builder.append(uid);
181        builder.append(' ');
182        builder.append(gid);
183        return execute(builder.toString());
184    }
185
186    public int dexopt(String apkPath, int uid, boolean isPublic) {
187        StringBuilder builder = new StringBuilder("dexopt");
188        builder.append(' ');
189        builder.append(apkPath);
190        builder.append(' ');
191        builder.append(uid);
192        builder.append(isPublic ? " 1" : " 0");
193        return execute(builder.toString());
194    }
195
196    public int movedex(String srcPath, String dstPath) {
197        StringBuilder builder = new StringBuilder("movedex");
198        builder.append(' ');
199        builder.append(srcPath);
200        builder.append(' ');
201        builder.append(dstPath);
202        return execute(builder.toString());
203    }
204
205    public int rmdex(String codePath) {
206        StringBuilder builder = new StringBuilder("rmdex");
207        builder.append(' ');
208        builder.append(codePath);
209        return execute(builder.toString());
210    }
211
212    public int remove(String name, boolean useEncryptedFilesystem) {
213        StringBuilder builder = new StringBuilder("remove");
214        builder.append(' ');
215        builder.append(name);
216        builder.append(' ');
217        if (useEncryptedFilesystem) {
218            builder.append('1');
219        } else {
220            builder.append('0');
221        }
222        return execute(builder.toString());
223    }
224
225    public int rename(String oldname, String newname, boolean useEncryptedFilesystem) {
226        StringBuilder builder = new StringBuilder("rename");
227        builder.append(' ');
228        builder.append(oldname);
229        builder.append(' ');
230        builder.append(newname);
231        builder.append(' ');
232        if (useEncryptedFilesystem) {
233            builder.append('1');
234        } else {
235            builder.append('0');
236        }
237        return execute(builder.toString());
238    }
239
240    public int deleteCacheFiles(String name, boolean useEncryptedFilesystem) {
241        StringBuilder builder = new StringBuilder("rmcache");
242        builder.append(' ');
243        builder.append(name);
244        builder.append(' ');
245        if (useEncryptedFilesystem) {
246            builder.append('1');
247        } else {
248            builder.append('0');
249        }
250        return execute(builder.toString());
251    }
252
253    public int clearUserData(String name, boolean useEncryptedFilesystem) {
254        StringBuilder builder = new StringBuilder("rmuserdata");
255        builder.append(' ');
256        builder.append(name);
257        builder.append(' ');
258        if (useEncryptedFilesystem) {
259            builder.append('1');
260        } else {
261            builder.append('0');
262        }
263        return execute(builder.toString());
264    }
265
266    public boolean ping() {
267        if (execute("ping") < 0) {
268            return false;
269        } else {
270            return true;
271        }
272    }
273
274    public int freeCache(long freeStorageSize) {
275        StringBuilder builder = new StringBuilder("freecache");
276        builder.append(' ');
277        builder.append(String.valueOf(freeStorageSize));
278        return execute(builder.toString());
279    }
280
281    /*
282     * @param packagePathSuffix The name of the path relative to install
283     * directory. Say if the path name is /data/app/com.test-1.apk,
284     * the package suffix path will be com.test-1
285     */
286    public int setForwardLockPerm(String packagePathSuffix, int gid) {
287        StringBuilder builder = new StringBuilder("protect");
288        builder.append(' ');
289        builder.append(packagePathSuffix);
290        builder.append(' ');
291        builder.append(gid);
292        return execute(builder.toString());
293    }
294
295    public int getSizeInfo(String pkgName, String apkPath,
296            String fwdLockApkPath, PackageStats pStats, boolean useEncryptedFilesystem) {
297        StringBuilder builder = new StringBuilder("getsize");
298        builder.append(' ');
299        builder.append(pkgName);
300        builder.append(' ');
301        builder.append(apkPath);
302        builder.append(' ');
303        builder.append(fwdLockApkPath != null ? fwdLockApkPath : "!");
304        builder.append(' ');
305        if (useEncryptedFilesystem) {
306            builder.append('1');
307        } else {
308            builder.append('0');
309        }
310
311        String s = transaction(builder.toString());
312        String res[] = s.split(" ");
313
314        if((res == null) || (res.length != 4)) {
315            return -1;
316        }
317        try {
318            pStats.codeSize = Long.parseLong(res[1]);
319            pStats.dataSize = Long.parseLong(res[2]);
320            pStats.cacheSize = Long.parseLong(res[3]);
321            return Integer.parseInt(res[0]);
322        } catch (NumberFormatException e) {
323            return -1;
324        }
325    }
326
327    public int moveFiles() {
328        return execute("movefiles");
329    }
330
331    public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath) {
332        if (dataPath == null) {
333            Slog.e(TAG, "unlinkNativeLibraryDirectory dataPath is null");
334            return -1;
335        } else if (nativeLibPath == null) {
336            Slog.e(TAG, "unlinkNativeLibraryDirectory nativeLibPath is null");
337            return -1;
338        }
339
340        StringBuilder builder = new StringBuilder("linklib ");
341        builder.append(dataPath);
342        builder.append(' ');
343        builder.append(nativeLibPath);
344
345        return execute(builder.toString());
346    }
347
348    public int unlinkNativeLibraryDirectory(String dataPath) {
349        if (dataPath == null) {
350            Slog.e(TAG, "unlinkNativeLibraryDirectory dataPath is null");
351            return -1;
352        }
353
354        StringBuilder builder = new StringBuilder("unlinklib ");
355        builder.append(dataPath);
356
357        return execute(builder.toString());
358    }
359}
360