1/*
2 * Copyright 2017, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(DATA61_BSD)
11 */
12
13#include <cpio/cpio.h>
14
15#ifndef NULL
16#define NULL ((void *)0)
17#endif
18
19struct cpio_header_info {
20    const char *filename;
21    unsigned long filesize;
22    void *data;
23    struct cpio_header *next;
24};
25
26/* Align 'n' up to the value 'align', which must be a power of two. */
27static unsigned long align_up(unsigned long n, unsigned long align)
28{
29    return (n + align - 1) & (~(align - 1));
30}
31
32/* Parse an ASCII hex string into an integer. */
33static unsigned long parse_hex_str(char *s, unsigned int max_len)
34{
35    unsigned long r = 0;
36    unsigned long i;
37
38    for (i = 0; i < max_len; i++) {
39        r *= 16;
40        if (s[i] >= '0' && s[i] <= '9') {
41            r += s[i] - '0';
42        }  else if (s[i] >= 'a' && s[i] <= 'f') {
43            r += s[i] - 'a' + 10;
44        }  else if (s[i] >= 'A' && s[i] <= 'F') {
45            r += s[i] - 'A' + 10;
46        } else {
47            return r;
48        }
49        continue;
50    }
51    return r;
52}
53
54/*
55 * Compare up to 'n' characters in a string.
56 *
57 * We re-implement the wheel to avoid dependencies on 'libc', required for
58 * certain environments that are particularly impoverished.
59 */
60static int cpio_strncmp(const char *a, const char *b, unsigned long n)
61{
62    unsigned long i;
63    for (i = 0; i < n; i++) {
64        if (a[i] != b[i]) {
65            return a[i] - b[i];
66        }
67        if (a[i] == 0) {
68            return 0;
69        }
70    }
71    return 0;
72}
73
74/**
75 * This is an implementation of string copy because, cpi doesn't want to
76 * use string.h.
77 */
78static char* cpio_strcpy(char *to, const char *from) {
79    char *save = to;
80    while (*from != 0) {
81        *to = *from;
82        to++;
83        from++;
84    }
85    return save;
86}
87
88static unsigned int cpio_strlen(const char *str) {
89    const char *s;
90    for (s = str; *s; ++s) {}
91    return (s - str);
92}
93
94/* Calculate the remaining length in a CPIO file after reading a header. */
95static unsigned long cpio_len_next(unsigned long len, void *prev, void *next) {
96    unsigned long diff = (unsigned long) (next - prev);
97    if (len < diff) {
98        return 0;
99    }
100    return len;
101}
102
103/*
104 * Parse the header of the given CPIO entry.
105 *
106 * Return -1 if the header is not valid, 1 if it is EOF.
107 */
108int cpio_parse_header(struct cpio_header *archive, unsigned long len,
109        struct cpio_header_info *info)
110{
111    const char *filename;
112    unsigned long filesize;
113    unsigned long filename_length;
114    void *data;
115    struct cpio_header *next;
116
117    /* Ensure header is accessible */
118    if (len < sizeof(struct cpio_header)) {
119        return -1;
120    }
121
122    /* Ensure magic header exists. */
123    if (cpio_strncmp(archive->c_magic, CPIO_HEADER_MAGIC, sizeof(archive->c_magic)) != 0) {
124        return -1;
125    }
126
127    /* Get filename and file size. */
128    filesize = parse_hex_str(archive->c_filesize, sizeof(archive->c_filesize));
129    filename_length = parse_hex_str(archive->c_namesize, sizeof(archive->c_namesize));
130
131    /* Ensure header + filename + file contents are accessible */
132    if (len < sizeof(struct cpio_header) + filename_length + filesize) {
133        return -1;
134    }
135
136    filename = (char *) archive + sizeof(struct cpio_header);
137    /* Ensure filename is terminated */
138    if (filename[filename_length - 1] != 0) {
139        return -1;
140    }
141
142    /* Ensure filename is not the trailer indicating EOF. */
143    if (filename_length >= sizeof(CPIO_FOOTER_MAGIC) && cpio_strncmp(filename,
144                CPIO_FOOTER_MAGIC, sizeof(CPIO_FOOTER_MAGIC)) == 0) {
145        return 1;
146    }
147
148    /* Find offset to data. */
149    data = (void *) align_up((unsigned long) archive + sizeof(struct cpio_header) +
150            filename_length, CPIO_ALIGNMENT);
151    next = (struct cpio_header *) align_up((unsigned long) data + filesize, CPIO_ALIGNMENT);
152
153    if (info) {
154        info->filename = filename;
155        info->filesize = filesize;
156        info->data = data;
157        info->next = next;
158    }
159    return 0;
160}
161
162/*
163 * Get the location of the data in the n'th entry in the given archive file.
164 *
165 * We also return a pointer to the name of the file (not NUL terminated).
166 *
167 * Return NULL if the n'th entry doesn't exist.
168 *
169 * Runs in O(n) time.
170 */
171void *cpio_get_entry(void *archive, unsigned long len, int n, const char **name, unsigned long *size)
172{
173    struct cpio_header *header = archive;
174    struct cpio_header_info header_info;
175
176    /* Find n'th entry. */
177    for (int i = 0; i <= n; i++) {
178        int error = cpio_parse_header(header, len, &header_info);
179        if (error) {
180            return NULL;
181        }
182        len = cpio_len_next(len, header, header_info.next);
183        header = header_info.next;
184    }
185
186    if (name) {
187        *name = header_info.filename;
188    }
189    if (size) {
190        *size = header_info.filesize;
191    }
192    return header_info.data;
193}
194
195/*
196 * Find the location and size of the file named "name" in the given 'cpio'
197 * archive.
198 *
199 * Return NULL if the entry doesn't exist.
200 *
201 * Runs in O(n) time.
202 */
203void *cpio_get_file(void *archive, unsigned long len, const char *name, unsigned long *size)
204{
205    struct cpio_header *header = archive;
206    struct cpio_header_info header_info;
207
208    /* Find n'th entry. */
209    while (1) {
210        int error = cpio_parse_header(header, len, &header_info);
211        if (error) {
212            return NULL;
213        }
214        if (cpio_strncmp(header_info.filename, name, -1) == 0) {
215            break;
216        }
217        len = cpio_len_next(len, header, header_info.next);
218        header = header_info.next;
219    }
220
221    if (size) {
222        *size = header_info.filesize;
223    }
224    return header_info.data;
225}
226
227int cpio_info(void *archive, unsigned long len, struct cpio_info *info) {
228    struct cpio_header *header;
229    unsigned long current_path_sz;
230    struct cpio_header_info header_info;
231
232    if (info == NULL) return 1;
233    info->file_count = 0;
234    info->max_path_sz = 0;
235
236    header = archive;
237    while (1) {
238        int error = cpio_parse_header(header, len, &header_info);
239        if (error == -1) {
240            return error;
241        } else if (error == 1) {
242            /* EOF */
243            return 0;
244        }
245        info->file_count++;
246        len = cpio_len_next(len, header, header_info.next);
247        header = header_info.next;
248
249        // Check if this is the maximum file path size.
250        current_path_sz = cpio_strlen(header_info.filename);
251        if (current_path_sz > info->max_path_sz) {
252            info->max_path_sz = current_path_sz;
253        }
254    }
255
256    return 0;
257}
258
259void cpio_ls(void *archive, unsigned long len, char **buf, unsigned long buf_len) {
260    struct cpio_header *header;
261    struct cpio_header_info header_info;
262
263    header = archive;
264    for (unsigned long i = 0; i < buf_len; i++) {
265        int error = cpio_parse_header(header, len, &header_info);
266        // Break on an error or nothing left to read.
267        if (error) break;
268        cpio_strcpy(buf[i], header_info.filename);
269        len = cpio_len_next(len, header, header_info.next);
270        header = header_info.next;
271    }
272}
273