1
2#include <stdio.h>
3#include <stdlib.h>
4#include <unistd.h>
5#include <string.h>
6#include <ctype.h>
7
8#include <sys/types.h>
9#include <sys/stat.h>
10#include <dirent.h>
11
12#include <stdarg.h>
13#include <fcntl.h>
14
15#include <private/android_filesystem_config.h>
16
17/* NOTES
18**
19** - see buffer-format.txt from the linux kernel docs for
20**   an explanation of this file format
21** - dotfiles are ignored
22** - directories named 'root' are ignored
23** - device notes, pipes, etc are not supported (error)
24*/
25
26void die(const char *why, ...)
27{
28    va_list ap;
29
30    va_start(ap, why);
31    fprintf(stderr,"error: ");
32    vfprintf(stderr, why, ap);
33    fprintf(stderr,"\n");
34    va_end(ap);
35    exit(1);
36}
37
38struct fs_config_entry {
39    char* name;
40    int uid, gid, mode;
41};
42
43static struct fs_config_entry* canned_config = NULL;
44
45/* Each line in the canned file should be a path plus three ints (uid,
46 * gid, mode). */
47#ifdef PATH_MAX
48#define CANNED_LINE_LENGTH  (PATH_MAX+100)
49#else
50#define CANNED_LINE_LENGTH  (1024)
51#endif
52
53static int verbose = 0;
54static int total_size = 0;
55
56static void fix_stat(const char *path, struct stat *s)
57{
58    if (canned_config) {
59        // Use the list of file uid/gid/modes loaded from the file
60        // given with -f.
61
62        struct fs_config_entry* empty_path_config = NULL;
63        struct fs_config_entry* p;
64        for (p = canned_config; p->name; ++p) {
65            if (!p->name[0]) {
66                empty_path_config = p;
67            }
68            if (strcmp(p->name, path) == 0) {
69                s->st_uid = p->uid;
70                s->st_gid = p->gid;
71                s->st_mode = p->mode | (s->st_mode & ~07777);
72                return;
73            }
74        }
75        s->st_uid = empty_path_config->uid;
76        s->st_gid = empty_path_config->gid;
77        s->st_mode = empty_path_config->mode | (s->st_mode & ~07777);
78    } else {
79        // Use the compiled-in fs_config() function.
80
81        fs_config(path, S_ISDIR(s->st_mode), &s->st_uid, &s->st_gid, &s->st_mode);
82    }
83}
84
85static void _eject(struct stat *s, char *out, int olen, char *data, unsigned datasize)
86{
87    // Nothing is special about this value, just picked something in the
88    // approximate range that was being used already, and avoiding small
89    // values which may be special.
90    static unsigned next_inode = 300000;
91
92    while(total_size & 3) {
93        total_size++;
94        putchar(0);
95    }
96
97    fix_stat(out, s);
98//    fprintf(stderr, "_eject %s: mode=0%o\n", out, s->st_mode);
99
100    printf("%06x%08x%08x%08x%08x%08x%08x"
101           "%08x%08x%08x%08x%08x%08x%08x%s%c",
102           0x070701,
103           next_inode++,  //  s.st_ino,
104           s->st_mode,
105           0, // s.st_uid,
106           0, // s.st_gid,
107           1, // s.st_nlink,
108           0, // s.st_mtime,
109           datasize,
110           0, // volmajor
111           0, // volminor
112           0, // devmajor
113           0, // devminor,
114           olen + 1,
115           0,
116           out,
117           0
118           );
119
120    total_size += 6 + 8*13 + olen + 1;
121
122    if(strlen(out) != (unsigned int)olen) die("ACK!");
123
124    while(total_size & 3) {
125        total_size++;
126        putchar(0);
127    }
128
129    if(datasize) {
130        fwrite(data, datasize, 1, stdout);
131        total_size += datasize;
132    }
133}
134
135static void _eject_trailer()
136{
137    struct stat s;
138    memset(&s, 0, sizeof(s));
139    _eject(&s, "TRAILER!!!", 10, 0, 0);
140
141    while(total_size & 0xff) {
142        total_size++;
143        putchar(0);
144    }
145}
146
147static void _archive(char *in, char *out, int ilen, int olen);
148
149static int compare(const void* a, const void* b) {
150  return strcmp(*(const char**)a, *(const char**)b);
151}
152
153static void _archive_dir(char *in, char *out, int ilen, int olen)
154{
155    int i, t;
156    DIR *d;
157    struct dirent *de;
158
159    if(verbose) {
160        fprintf(stderr,"_archive_dir('%s','%s',%d,%d)\n",
161                in, out, ilen, olen);
162    }
163
164    d = opendir(in);
165    if(d == 0) die("cannot open directory '%s'", in);
166
167    int size = 32;
168    int entries = 0;
169    char** names = malloc(size * sizeof(char*));
170    if (names == NULL) {
171      fprintf(stderr, "failed to allocate dir names array (size %d)\n", size);
172      exit(1);
173    }
174
175    while((de = readdir(d)) != 0){
176            /* xxx: feature? maybe some dotfiles are okay */
177        if(de->d_name[0] == '.') continue;
178
179            /* xxx: hack. use a real exclude list */
180        if(!strcmp(de->d_name, "root")) continue;
181
182        if (entries >= size) {
183          size *= 2;
184          names = realloc(names, size * sizeof(char*));
185          if (names == NULL) {
186            fprintf(stderr, "failed to reallocate dir names array (size %d)\n",
187                    size);
188            exit(1);
189          }
190        }
191        names[entries] = strdup(de->d_name);
192        if (names[entries] == NULL) {
193          fprintf(stderr, "failed to strdup name \"%s\"\n",
194                  de->d_name);
195          exit(1);
196        }
197        ++entries;
198    }
199
200    qsort(names, entries, sizeof(char*), compare);
201
202    for (i = 0; i < entries; ++i) {
203        t = strlen(names[i]);
204        in[ilen] = '/';
205        memcpy(in + ilen + 1, names[i], t + 1);
206
207        if(olen > 0) {
208            out[olen] = '/';
209            memcpy(out + olen + 1, names[i], t + 1);
210            _archive(in, out, ilen + t + 1, olen + t + 1);
211        } else {
212            memcpy(out, names[i], t + 1);
213            _archive(in, out, ilen + t + 1, t);
214        }
215
216        in[ilen] = 0;
217        out[olen] = 0;
218
219        free(names[i]);
220    }
221    free(names);
222}
223
224static void _archive(char *in, char *out, int ilen, int olen)
225{
226    struct stat s;
227
228    if(verbose) {
229        fprintf(stderr,"_archive('%s','%s',%d,%d)\n",
230                in, out, ilen, olen);
231    }
232
233    if(lstat(in, &s)) die("could not stat '%s'\n", in);
234
235    if(S_ISREG(s.st_mode)){
236        char *tmp;
237        int fd;
238
239        fd = open(in, O_RDONLY);
240        if(fd < 0) die("cannot open '%s' for read", in);
241
242        tmp = (char*) malloc(s.st_size);
243        if(tmp == 0) die("cannot allocate %d bytes", s.st_size);
244
245        if(read(fd, tmp, s.st_size) != s.st_size) {
246            die("cannot read %d bytes", s.st_size);
247        }
248
249        _eject(&s, out, olen, tmp, s.st_size);
250
251        free(tmp);
252        close(fd);
253    } else if(S_ISDIR(s.st_mode)) {
254        _eject(&s, out, olen, 0, 0);
255        _archive_dir(in, out, ilen, olen);
256    } else if(S_ISLNK(s.st_mode)) {
257        char buf[1024];
258        int size;
259        size = readlink(in, buf, 1024);
260        if(size < 0) die("cannot read symlink '%s'", in);
261        _eject(&s, out, olen, buf, size);
262    } else {
263        die("Unknown '%s' (mode %d)?\n", in, s.st_mode);
264    }
265}
266
267void archive(const char *start, const char *prefix)
268{
269    char in[8192];
270    char out[8192];
271
272    strcpy(in, start);
273    strcpy(out, prefix);
274
275    _archive_dir(in, out, strlen(in), strlen(out));
276}
277
278static void read_canned_config(char* filename)
279{
280    int allocated = 8;
281    int used = 0;
282
283    canned_config =
284        (struct fs_config_entry*)malloc(allocated * sizeof(struct fs_config_entry));
285
286    char line[CANNED_LINE_LENGTH];
287    FILE* f = fopen(filename, "r");
288    if (f == NULL) die("failed to open canned file");
289
290    while (fgets(line, CANNED_LINE_LENGTH, f) != NULL) {
291        if (!line[0]) break;
292        if (used >= allocated) {
293            allocated *= 2;
294            canned_config = (struct fs_config_entry*)realloc(
295                canned_config, allocated * sizeof(struct fs_config_entry));
296        }
297
298        struct fs_config_entry* cc = canned_config + used;
299
300        if (isspace(line[0])) {
301            cc->name = strdup("");
302            cc->uid = atoi(strtok(line, " \n"));
303        } else {
304            cc->name = strdup(strtok(line, " \n"));
305            cc->uid = atoi(strtok(NULL, " \n"));
306        }
307        cc->gid = atoi(strtok(NULL, " \n"));
308        cc->mode = strtol(strtok(NULL, " \n"), NULL, 8);
309        ++used;
310    }
311    if (used >= allocated) {
312        ++allocated;
313        canned_config = (struct fs_config_entry*)realloc(
314            canned_config, allocated * sizeof(struct fs_config_entry));
315    }
316    canned_config[used].name = NULL;
317
318    fclose(f);
319}
320
321
322int main(int argc, char *argv[])
323{
324    argc--;
325    argv++;
326
327    if (argc > 1 && strcmp(argv[0], "-f") == 0) {
328        read_canned_config(argv[1]);
329        argc -= 2;
330        argv += 2;
331    }
332
333    if(argc == 0) die("no directories to process?!");
334
335    while(argc-- > 0){
336        char *x = strchr(*argv, '=');
337        if(x != 0) {
338            *x++ = 0;
339        } else {
340            x = "";
341        }
342
343        archive(*argv, x);
344
345        argv++;
346    }
347
348    _eject_trailer();
349
350    return 0;
351}
352