1// Copyright 2014 The Android Open Source Project
2//
3// This software is licensed under the terms of the GNU General Public
4// License version 2, as published by the Free Software Foundation, and
5// may be copied, distributed, and modified under those terms.
6//
7// This program is distributed in the hope that it will be useful,
8// but WITHOUT ANY WARRANTY; without even the implied warranty of
9// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10// GNU General Public License for more details.
11
12#include "android/filesystems/ramdisk_extractor.h"
13
14#include "android/base/Compiler.h"
15#include "android/base/Log.h"
16#include "android/base/String.h"
17
18#include <inttypes.h>
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22#include <zlib.h>
23
24#define DEBUG 0
25
26#if DEBUG
27#  define D(...)   printf(__VA_ARGS__), fflush(stdout)
28#else
29#  define D(...)   ((void)0)
30#endif
31
32// Ramdisk images are gzipped cpio archives using the new ASCII
33// format as described at [1]. Hence this source file first implements
34// a gzip-based input stream class, then
35//
36// [1] http://people.freebsd.org/~kientzle/libarchive/man/cpio.5.txt
37
38namespace {
39
40// Helper class used to implement a gzip-based input stream.
41// Usage is as follows:
42//
43//     GZipInputStream input(filePath);
44//     if (input.error()) {
45//        fprintf(stderr, "Could not open file: %s\n",
46//                filePath, strerror(input.error()));
47//     } else {
48//        uint8_t header[32];
49//        if (!doRead(&input, header, sizeof header)) {
50//           ... error, could not read header.
51//        }
52//     }
53//      .. stream is closed automatically on scope exit.
54class GZipInputStream {
55public:
56    // Open a new input stream to read from file |filePath|.
57    // The constructor can never fail, so call error() after
58    // this to see if an error occured.
59    explicit GZipInputStream(const char* filePath) {
60        mFile = gzopen(filePath, "rb");
61        if (mFile == Z_NULL) {
62            mFile = NULL;
63            mError = errno;
64        } else {
65            mError = 0;
66        }
67    }
68
69    // Return the last error that occured on this stream,
70    // or 0 if everything's well. Note that as soon as an
71    // error occurs, the stream cannot be used anymore.
72    int error() const { return mError; }
73
74    // Close the stream, note that this is called automatically
75    // from the destructor, but clients might want to do this
76    // before.
77    void close() {
78        if (mFile) {
79            gzclose(mFile);
80            mFile = NULL;
81        }
82    }
83
84    ~GZipInputStream() {
85        close();
86    }
87
88    // Try to read up to |len| bytes of data from the input
89    // stream into |buffer|. On success, return true and sets
90    // |*readBytes| to the number of bytes that were read.
91    // On failure, return false and set error().
92    bool tryRead(void* buffer, size_t len, size_t* readBytes) {
93        *readBytes = 0;
94
95        if (mError) {
96            return false;
97        }
98
99        if (len == 0) {
100            return true;
101        }
102
103        // Warning, gzread() takes an unsigned int parameter for
104        // the length, but will return an error if its value
105        // exceeds INT_MAX anyway.
106        char* buff = reinterpret_cast<char*>(buffer);
107
108        while (len > 0) {
109            size_t avail = len;
110            if (avail > INT_MAX)
111                avail = INT_MAX;
112
113            int ret = gzread(mFile, buff, static_cast<unsigned int>(avail));
114            if (ret < 0) {
115                gzerror(mFile, &mError);
116                break;
117            }
118            if (ret == 0) {
119                if (gzeof(mFile)) {
120                    break;
121                }
122                gzerror(mFile, &mError);
123                break;
124            }
125            len -= ret;
126            buff += ret;
127            *readBytes += ret;
128        }
129
130        return (!mError && *readBytes > 0);
131    }
132
133    // Read exactly |len| bytes of data into |buffer|. On success,
134    // return true, on failure, return false and set error().
135    bool doRead(void* buffer, size_t len) {
136        size_t readCount = 0;
137        if (!tryRead(buffer, len, &readCount)) {
138            return false;
139        }
140        if (readCount < len) {
141            mError = EIO;
142            return false;
143        }
144        return true;
145    }
146
147    bool doSkip(size_t len) {
148        if (mError) {
149            return false;
150        }
151        if (gzseek(mFile, len, SEEK_CUR) < 0) {
152            gzerror(mFile, &mError);
153            return false;
154        }
155        return true;
156    }
157
158private:
159    DISALLOW_COPY_AND_ASSIGN(GZipInputStream);
160
161    gzFile mFile;
162    int mError;
163};
164
165// Parse an hexadecimal string of 8 characters. On success,
166// return true and sets |*value| to its value. On failure,
167// return false.
168bool parse_hex8(const char* input, uint32_t* value) {
169    uint32_t result = 0;
170    for (int n = 0; n < 8; ++n) {
171        int c = input[n];
172        unsigned d = static_cast<unsigned>(c - '0');
173        if (d >= 10) {
174            d = static_cast<unsigned>(c - 'a');
175            if (d >= 6) {
176                d = static_cast<unsigned>(c - 'A');
177                if (d >= 6) {
178                    return false;
179                }
180            }
181            d += 10;
182        }
183        result = (result << 4) | d;
184    }
185    *value = result;
186    return true;
187}
188
189}  // namespace
190
191bool android_extractRamdiskFile(const char* ramdiskPath,
192                                const char* fileName,
193                                char** out,
194                                size_t* outSize) {
195    *out = NULL;
196    *outSize = 0;
197
198    GZipInputStream input(ramdiskPath);
199    if (input.error()) {
200        errno = input.error();
201        return false;
202    }
203
204    // Type of cpio new ASCII header.
205    struct cpio_newc_header {
206        char c_magic[6];
207        char c_ino[8];
208        char c_mode[8];
209        char c_uid[8];
210        char c_gid[8];
211        char c_nlink[8];
212        char c_mtime[8];
213        char c_filesize[8];
214        char c_devmajor[8];
215        char c_devminor[8];
216        char c_rdevmajor[8];
217        char c_rdevminor[8];
218        char c_namesize[8];
219        char c_check[8];
220    };
221
222    size_t fileNameLen = strlen(fileName);
223
224    for (;;) {
225        // Read the header then check it.
226        cpio_newc_header header;
227        if (!input.doRead(&header, sizeof header)) {
228            // Assume end of input here.
229            D("Could not find %s in ramdisk image at %s\n",
230              fileName, ramdiskPath);
231            return false;
232        }
233
234        D("HEADER %.6s\n", header.c_magic);
235        if (memcmp(header.c_magic, "070701", 6) != 0) {
236            D("Not a valid ramdisk image file: %s\n", ramdiskPath);
237            errno = EINVAL;
238            return false;
239        }
240
241        // Compare file names, note that files with a size of 0 are
242        // hard links and should be ignored.
243        uint32_t nameSize;
244        uint32_t entrySize;
245        if (!parse_hex8(header.c_namesize, &nameSize) ||
246            !parse_hex8(header.c_filesize, &entrySize)) {
247            D("Could not parse ramdisk file entry header!");
248            break;
249        }
250
251        D("---- %d nameSize=%d entrySize=%d\n", __LINE__, nameSize, entrySize);
252
253        // The header is followed by the name, followed by 4-byte padding
254        // with NUL bytes. Compute the number of bytes to skip over the
255        // name.
256        size_t skipName =
257                ((sizeof header + nameSize + 3) & ~3) - sizeof header;
258
259        // The file data is 4-byte padded with NUL bytes too.
260        size_t skipFile = (entrySize + 3) & ~3;
261        size_t skipCount = 0;
262
263        // Last record is named 'TRAILER!!!' and indicates end of archive.
264        static const char kTrailer[] = "TRAILER!!!";
265        static const size_t kTrailerSize = sizeof(kTrailer) - 1U;
266
267        if ((entrySize == 0 || nameSize != fileNameLen + 1U) &&
268            nameSize != kTrailerSize + 1U) {
269            D("---- %d Skipping\n", __LINE__);
270            skipCount = skipName + skipFile;
271        } else {
272            // Read the name and compare it.
273            nameSize -= 1U;
274            android::base::String entryName;
275            entryName.resize(nameSize);
276            if (!input.doRead(&entryName[0], nameSize)) {
277                D("Could not read ramdisk file entry name!");
278                break;
279            }
280            D("---- %d Name=[%s]\n", __LINE__, entryName.c_str());
281            skipCount -= nameSize;
282
283            // Check for last entry.
284            if (nameSize == kTrailerSize &&
285                !strcmp(entryName.c_str(), kTrailer)) {
286                D("End of archive reached. Could not find %s in ramdisk image at %s",
287                  fileName, ramdiskPath);
288                return false;
289            }
290
291            // Check for the search file name.
292            if (nameSize == entryName.size() &&
293                !strcmp(entryName.c_str(), fileName)) {
294                // Found it !! Skip over padding.
295                if (!input.doSkip(skipName - nameSize)) {
296                    D("Could not skip ramdisk name entry!");
297                    break;
298                }
299
300                // Then read data into head-allocated buffer.
301                *out = reinterpret_cast<char*>(malloc(entrySize));
302                *outSize = entrySize;
303
304                return input.doRead(*out, entrySize);
305            }
306        }
307        if (!input.doSkip(skipCount)) {
308            D("Could not skip ramdisk entry!");
309            break;
310        }
311    }
312
313    errno = input.error();
314    return false;
315}
316