| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <dirent.h> |
| |
| #include <stdarg.h> |
| #include <fcntl.h> |
| #include <termios.h> |
| |
| #include <zlib.h> // for adler32() |
| |
| static int verbose = 0; |
| |
| /* |
| * Android File Archive format: |
| * |
| * magic[5]: 'A' 'F' 'A' 'R' '\n' |
| * version[4]: 0x00 0x00 0x00 0x01 |
| * for each file: |
| * file magic[4]: 'F' 'I' 'L' 'E' |
| * namelen[4]: Length of file name, including NUL byte (big-endian) |
| * name[*]: NUL-terminated file name |
| * datalen[4]: Length of file (big-endian) |
| * data[*]: Unencoded file data |
| * adler32[4]: adler32 of the unencoded file data (big-endian) |
| * file end magic[4]: 'f' 'i' 'l' 'e' |
| * end magic[4]: 'E' 'N' 'D' 0x00 |
| * |
| * This format is about as simple as possible; it was designed to |
| * make it easier to transfer multiple files over an stdin/stdout |
| * pipe to another process, so word-alignment wasn't necessary. |
| */ |
| |
| static void |
| die(const char *why, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, why); |
| fprintf(stderr, "error: "); |
| vfprintf(stderr, why, ap); |
| fprintf(stderr, "\n"); |
| va_end(ap); |
| exit(1); |
| } |
| |
| static void |
| write_big_endian(size_t v) |
| { |
| putchar((v >> 24) & 0xff); |
| putchar((v >> 16) & 0xff); |
| putchar((v >> 8) & 0xff); |
| putchar( v & 0xff); |
| } |
| |
| static void |
| _eject(struct stat *s, char *out, int olen, char *data, size_t datasize) |
| { |
| unsigned long adler; |
| |
| /* File magic. |
| */ |
| printf("FILE"); |
| |
| /* Name length includes the NUL byte. |
| */ |
| write_big_endian(olen + 1); |
| |
| /* File name and terminating NUL. |
| */ |
| printf("%s", out); |
| putchar('\0'); |
| |
| /* File length. |
| */ |
| write_big_endian(datasize); |
| |
| /* File data. |
| */ |
| if (fwrite(data, 1, datasize, stdout) != datasize) { |
| die("Error writing file data"); |
| } |
| |
| /* Checksum. |
| */ |
| adler = adler32(0, NULL, 0); |
| adler = adler32(adler, (unsigned char *)data, datasize); |
| write_big_endian(adler); |
| |
| /* File end magic. |
| */ |
| printf("file"); |
| } |
| |
| static void _archive(char *in, int ilen); |
| |
| static void |
| _archive_dir(char *in, int ilen) |
| { |
| int t; |
| DIR *d; |
| struct dirent *de; |
| |
| if (verbose) { |
| fprintf(stderr, "_archive_dir('%s', %d)\n", in, ilen); |
| } |
| |
| d = opendir(in); |
| if (d == 0) { |
| die("cannot open directory '%s'", in); |
| } |
| |
| while ((de = readdir(d)) != 0) { |
| /* xxx: feature? maybe some dotfiles are okay */ |
| if (strcmp(de->d_name, ".") == 0 || |
| strcmp(de->d_name, "..") == 0) |
| { |
| continue; |
| } |
| |
| t = strlen(de->d_name); |
| in[ilen] = '/'; |
| memcpy(in + ilen + 1, de->d_name, t + 1); |
| |
| _archive(in, ilen + t + 1); |
| |
| in[ilen] = '\0'; |
| } |
| } |
| |
| static void |
| _archive(char *in, int ilen) |
| { |
| struct stat s; |
| |
| if (verbose) { |
| fprintf(stderr, "_archive('%s', %d)\n", in, ilen); |
| } |
| |
| if (lstat(in, &s)) { |
| die("could not stat '%s'\n", in); |
| } |
| |
| if (S_ISREG(s.st_mode)) { |
| char *tmp; |
| int fd; |
| |
| fd = open(in, O_RDONLY); |
| if (fd < 0) { |
| die("cannot open '%s' for read", in); |
| } |
| |
| tmp = (char*) malloc(s.st_size); |
| if (tmp == 0) { |
| die("cannot allocate %d bytes", s.st_size); |
| } |
| |
| if (read(fd, tmp, s.st_size) != s.st_size) { |
| die("cannot read %d bytes", s.st_size); |
| } |
| |
| _eject(&s, in, ilen, tmp, s.st_size); |
| |
| free(tmp); |
| close(fd); |
| } else if (S_ISDIR(s.st_mode)) { |
| _archive_dir(in, ilen); |
| } else { |
| /* We don't handle links, etc. */ |
| die("Unknown '%s' (mode %d)?\n", in, s.st_mode); |
| } |
| } |
| |
| void archive(const char *start) |
| { |
| char in[8192]; |
| |
| strcpy(in, start); |
| |
| _archive_dir(in, strlen(in)); |
| } |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| struct termios old_termios; |
| |
| if (argc == 1) { |
| die("usage: %s <dir-list>", argv[0]); |
| } |
| argc--; |
| argv++; |
| |
| /* Force stdout into raw mode. |
| */ |
| struct termios s; |
| if (tcgetattr(1, &s) < 0) { |
| die("Could not get termios for stdout"); |
| } |
| old_termios = s; |
| cfmakeraw(&s); |
| if (tcsetattr(1, TCSANOW, &s) < 0) { |
| die("Could not set termios for stdout"); |
| } |
| |
| /* Print format magic and version. |
| */ |
| printf("AFAR\n"); |
| write_big_endian(1); |
| |
| while (argc-- > 0) { |
| archive(*argv++); |
| } |
| |
| /* Print end magic. |
| */ |
| printf("END"); |
| putchar('\0'); |
| |
| /* Restore stdout. |
| */ |
| if (tcsetattr(1, TCSANOW, &old_termios) < 0) { |
| die("Could not restore termios for stdout"); |
| } |
| |
| return 0; |
| } |