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
17/*
18 * Zip alignment tool
19 */
20#include "ZipFile.h"
21
22#include <stdio.h>
23#include <stdlib.h>
24#include <unistd.h>
25
26using namespace android;
27
28/*
29 * Show program usage.
30 */
31void usage(void)
32{
33    fprintf(stderr, "Zip alignment utility\n");
34    fprintf(stderr, "Copyright (C) 2009 The Android Open Source Project\n\n");
35    fprintf(stderr,
36        "Usage: zipalign [-f] [-p] [-v] [-z] <align> infile.zip outfile.zip\n"
37        "       zipalign -c [-p] [-v] <align> infile.zip\n\n" );
38    fprintf(stderr,
39        "  <align>: alignment in bytes, e.g. '4' provides 32-bit alignment\n");
40    fprintf(stderr, "  -c: check alignment only (does not modify file)\n");
41    fprintf(stderr, "  -f: overwrite existing outfile.zip\n");
42    fprintf(stderr, "  -p: memory page alignment for stored shared object files\n");
43    fprintf(stderr, "  -v: verbose output\n");
44    fprintf(stderr, "  -z: recompress using Zopfli\n");
45}
46
47static int getAlignment(bool pageAlignSharedLibs, int defaultAlignment,
48    ZipEntry* pEntry) {
49
50    static const int kPageAlignment = 4096;
51
52    if (!pageAlignSharedLibs) {
53        return defaultAlignment;
54    }
55
56    const char* ext = strrchr(pEntry->getFileName(), '.');
57    if (ext && strcmp(ext, ".so") == 0) {
58        return kPageAlignment;
59    }
60
61    return defaultAlignment;
62}
63
64/*
65 * Copy all entries from "pZin" to "pZout", aligning as needed.
66 */
67static int copyAndAlign(ZipFile* pZin, ZipFile* pZout, int alignment, bool zopfli,
68    bool pageAlignSharedLibs)
69{
70    int numEntries = pZin->getNumEntries();
71    ZipEntry* pEntry;
72    int bias = 0;
73    status_t status;
74
75    for (int i = 0; i < numEntries; i++) {
76        ZipEntry* pNewEntry;
77        int padding = 0;
78
79        pEntry = pZin->getEntryByIndex(i);
80        if (pEntry == NULL) {
81            fprintf(stderr, "ERROR: unable to retrieve entry %d\n", i);
82            return 1;
83        }
84
85        if (pEntry->isCompressed()) {
86            /* copy the entry without padding */
87            //printf("--- %s: orig at %ld len=%ld (compressed)\n",
88            //    pEntry->getFileName(), (long) pEntry->getFileOffset(),
89            //    (long) pEntry->getUncompressedLen());
90
91            if (zopfli) {
92                status = pZout->addRecompress(pZin, pEntry, &pNewEntry);
93                bias += pNewEntry->getCompressedLen() - pEntry->getCompressedLen();
94            } else {
95                status = pZout->add(pZin, pEntry, padding, &pNewEntry);
96            }
97        } else {
98            const int alignTo = getAlignment(pageAlignSharedLibs, alignment, pEntry);
99
100            /*
101             * Copy the entry, adjusting as required.  We assume that the
102             * file position in the new file will be equal to the file
103             * position in the original.
104             */
105            long newOffset = pEntry->getFileOffset() + bias;
106            padding = (alignTo - (newOffset % alignTo)) % alignTo;
107
108            //printf("--- %s: orig at %ld(+%d) len=%ld, adding pad=%d\n",
109            //    pEntry->getFileName(), (long) pEntry->getFileOffset(),
110            //    bias, (long) pEntry->getUncompressedLen(), padding);
111            status = pZout->add(pZin, pEntry, padding, &pNewEntry);
112        }
113
114        if (status != NO_ERROR)
115            return 1;
116        bias += padding;
117        //printf(" added '%s' at %ld (pad=%d)\n",
118        //    pNewEntry->getFileName(), (long) pNewEntry->getFileOffset(),
119        //    padding);
120    }
121
122    return 0;
123}
124
125/*
126 * Process a file.  We open the input and output files, failing if the
127 * output file exists and "force" wasn't specified.
128 */
129static int process(const char* inFileName, const char* outFileName,
130    int alignment, bool force, bool zopfli, bool pageAlignSharedLibs)
131{
132    ZipFile zin, zout;
133
134    //printf("PROCESS: align=%d in='%s' out='%s' force=%d\n",
135    //    alignment, inFileName, outFileName, force);
136
137    /* this mode isn't supported -- do a trivial check */
138    if (strcmp(inFileName, outFileName) == 0) {
139        fprintf(stderr, "Input and output can't be same file\n");
140        return 1;
141    }
142
143    /* don't overwrite existing unless given permission */
144    if (!force && access(outFileName, F_OK) == 0) {
145        fprintf(stderr, "Output file '%s' exists\n", outFileName);
146        return 1;
147    }
148
149    if (zin.open(inFileName, ZipFile::kOpenReadOnly) != NO_ERROR) {
150        fprintf(stderr, "Unable to open '%s' as zip archive\n", inFileName);
151        return 1;
152    }
153    if (zout.open(outFileName,
154            ZipFile::kOpenReadWrite|ZipFile::kOpenCreate|ZipFile::kOpenTruncate)
155        != NO_ERROR)
156    {
157        fprintf(stderr, "Unable to open '%s' as zip archive\n", outFileName);
158        return 1;
159    }
160
161    int result = copyAndAlign(&zin, &zout, alignment, zopfli, pageAlignSharedLibs);
162    if (result != 0) {
163        printf("zipalign: failed rewriting '%s' to '%s'\n",
164            inFileName, outFileName);
165    }
166    return result;
167}
168
169/*
170 * Verify the alignment of a zip archive.
171 */
172static int verify(const char* fileName, int alignment, bool verbose,
173    bool pageAlignSharedLibs)
174{
175    ZipFile zipFile;
176    bool foundBad = false;
177
178    if (verbose)
179        printf("Verifying alignment of %s (%d)...\n", fileName, alignment);
180
181    if (zipFile.open(fileName, ZipFile::kOpenReadOnly) != NO_ERROR) {
182        fprintf(stderr, "Unable to open '%s' for verification\n", fileName);
183        return 1;
184    }
185
186    int numEntries = zipFile.getNumEntries();
187    ZipEntry* pEntry;
188
189    for (int i = 0; i < numEntries; i++) {
190        pEntry = zipFile.getEntryByIndex(i);
191        if (pEntry->isCompressed()) {
192            if (verbose) {
193                printf("%8ld %s (OK - compressed)\n",
194                    (long) pEntry->getFileOffset(), pEntry->getFileName());
195            }
196        } else {
197            long offset = pEntry->getFileOffset();
198            const int alignTo = getAlignment(pageAlignSharedLibs, alignment, pEntry);
199            if ((offset % alignTo) != 0) {
200                if (verbose) {
201                    printf("%8ld %s (BAD - %ld)\n",
202                        (long) offset, pEntry->getFileName(),
203                        offset % alignTo);
204                }
205                foundBad = true;
206            } else {
207                if (verbose) {
208                    printf("%8ld %s (OK)\n",
209                        (long) offset, pEntry->getFileName());
210                }
211            }
212        }
213    }
214
215    if (verbose)
216        printf("Verification %s\n", foundBad ? "FAILED" : "succesful");
217
218    return foundBad ? 1 : 0;
219}
220
221/*
222 * Parse args.
223 */
224int main(int argc, char* const argv[])
225{
226    bool wantUsage = false;
227    bool check = false;
228    bool force = false;
229    bool verbose = false;
230    bool zopfli = false;
231    bool pageAlignSharedLibs = false;
232    int result = 1;
233    int alignment;
234    char* endp;
235
236    if (argc < 4) {
237        wantUsage = true;
238        goto bail;
239    }
240
241    argc--;
242    argv++;
243
244    while (argc && argv[0][0] == '-') {
245        const char* cp = argv[0] +1;
246
247        while (*cp != '\0') {
248            switch (*cp) {
249            case 'c':
250                check = true;
251                break;
252            case 'f':
253                force = true;
254                break;
255            case 'v':
256                verbose = true;
257                break;
258            case 'z':
259                zopfli = true;
260                break;
261            case 'p':
262                pageAlignSharedLibs = true;
263                break;
264            default:
265                fprintf(stderr, "ERROR: unknown flag -%c\n", *cp);
266                wantUsage = true;
267                goto bail;
268            }
269
270            cp++;
271        }
272
273        argc--;
274        argv++;
275    }
276
277    if (!((check && argc == 2) || (!check && argc == 3))) {
278        wantUsage = true;
279        goto bail;
280    }
281
282    alignment = strtol(argv[0], &endp, 10);
283    if (*endp != '\0' || alignment <= 0) {
284        fprintf(stderr, "Invalid value for alignment: %s\n", argv[0]);
285        wantUsage = true;
286        goto bail;
287    }
288
289    if (check) {
290        /* check existing archive for correct alignment */
291        result = verify(argv[1], alignment, verbose, pageAlignSharedLibs);
292    } else {
293        /* create the new archive */
294        result = process(argv[1], argv[2], alignment, force, zopfli, pageAlignSharedLibs);
295
296        /* trust, but verify */
297        if (result == 0) {
298            result = verify(argv[2], alignment, verbose, pageAlignSharedLibs);
299        }
300    }
301
302bail:
303    if (wantUsage) {
304        usage();
305        result = 2;
306    }
307
308    return result;
309}
310