1 /*
2  * Copyright (C) 2017 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 com.android.server.backup.utils;
18 
19 import static com.android.server.backup.BackupManagerService.BACKUP_MANIFEST_VERSION;
20 import static com.android.server.backup.BackupManagerService.TAG;
21 
22 import android.content.pm.PackageInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.Signature;
25 import android.content.pm.SigningInfo;
26 import android.os.Build;
27 import android.os.ParcelFileDescriptor;
28 import android.util.Slog;
29 import android.util.StringBuilderPrinter;
30 
31 import java.io.DataInputStream;
32 import java.io.EOFException;
33 import java.io.File;
34 import java.io.FileInputStream;
35 import java.io.FileOutputStream;
36 import java.io.IOException;
37 import java.io.OutputStream;
38 
39 /**
40  * Low-level utility methods for full backup.
41  */
42 public class FullBackupUtils {
43     /**
44      * Reads data from pipe and writes it to the stream in chunks of up to 32KB.
45      *
46      * @param inPipe - pipe to read the data from.
47      * @param out - stream to write the data to.
48      * @throws IOException - in case of an error.
49      */
routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out)50     public static void routeSocketDataToOutput(ParcelFileDescriptor inPipe, OutputStream out)
51             throws IOException {
52         // We do not take close() responsibility for the pipe FD
53         FileInputStream raw = new FileInputStream(inPipe.getFileDescriptor());
54         DataInputStream in = new DataInputStream(raw);
55 
56         byte[] buffer = new byte[32 * 1024];
57         int chunkTotal;
58         while ((chunkTotal = in.readInt()) > 0) {
59             while (chunkTotal > 0) {
60                 int toRead = (chunkTotal > buffer.length) ? buffer.length : chunkTotal;
61                 int nRead = in.read(buffer, 0, toRead);
62                 if (nRead < 0) {
63                     Slog.e(TAG, "Unexpectedly reached end of file while reading data");
64                     throw new EOFException();
65                 }
66                 out.write(buffer, 0, nRead);
67                 chunkTotal -= nRead;
68             }
69         }
70     }
71 
72     /**
73      * Writes app manifest to the given manifest file.
74      *
75      * @param pkg - app package, which manifest to write.
76      * @param packageManager - {@link PackageManager} instance.
77      * @param manifestFile - target manifest file.
78      * @param withApk - whether include apk or not.
79      * @param withWidgets - whether to write widgets data.
80      * @throws IOException - in case of an error.
81      */
82     // TODO: withWidgets is not used, decide whether it is needed.
writeAppManifest(PackageInfo pkg, PackageManager packageManager, File manifestFile, boolean withApk, boolean withWidgets)83     public static void writeAppManifest(PackageInfo pkg, PackageManager packageManager,
84             File manifestFile, boolean withApk, boolean withWidgets) throws IOException {
85         // Manifest format. All data are strings ending in LF:
86         //     BACKUP_MANIFEST_VERSION, currently 1
87         //
88         // Version 1:
89         //     package name
90         //     package's versionCode
91         //     platform versionCode
92         //     getInstallerPackageName() for this package (maybe empty)
93         //     boolean: "1" if archive includes .apk; any other string means not
94         //     number of signatures == N
95         // N*:    signature byte array in ascii format per Signature.toCharsString()
96         StringBuilder builder = new StringBuilder(4096);
97         StringBuilderPrinter printer = new StringBuilderPrinter(builder);
98 
99         printer.println(Integer.toString(BACKUP_MANIFEST_VERSION));
100         printer.println(pkg.packageName);
101         printer.println(Long.toString(pkg.getLongVersionCode()));
102         printer.println(Integer.toString(Build.VERSION.SDK_INT));
103 
104         String installerName = packageManager.getInstallerPackageName(pkg.packageName);
105         printer.println((installerName != null) ? installerName : "");
106 
107         printer.println(withApk ? "1" : "0");
108 
109         // write the signature block
110         SigningInfo signingInfo = pkg.signingInfo;
111         if (signingInfo == null) {
112             printer.println("0");
113         } else {
114             // retrieve the newest sigs to write
115             // TODO (b/73988180) use entire signing history in case of rollbacks
116             Signature[] signatures = signingInfo.getApkContentsSigners();
117             printer.println(Integer.toString(signatures.length));
118             for (Signature sig : signatures) {
119                 printer.println(sig.toCharsString());
120             }
121         }
122 
123         FileOutputStream outstream = new FileOutputStream(manifestFile);
124         outstream.write(builder.toString().getBytes());
125         outstream.close();
126 
127         // We want the manifest block in the archive stream to be idempotent:
128         // each time we generate a backup stream for the app, we want the manifest
129         // block to be identical.  The underlying tar mechanism sees it as a file,
130         // though, and will propagate its mtime, causing the tar header to vary.
131         // Avoid this problem by pinning the mtime to zero.
132         manifestFile.setLastModified(0);
133     }
134 }
135