ready for tagging
[util-vserver.git] / src / h2ext.c
1 // $Id$
2
3 // Copyright (C) 2007 Daniel Hokka Zakrisson <daniel@hozac.com>
4 //  
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2, or (at your option)
8 // any later version.
9 //  
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //  
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 #ifdef HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <string.h>
28 #include <sys/mman.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <getopt.h>
32 #include <errno.h>
33 #include <ctype.h>
34 #include <sys/wait.h>
35
36 #include "util.h"
37 #include "lib/internal.h"
38 #include "pathconfig.h"
39
40 #define ENSC_WRAPPERS_PREFIX "h2ext: "
41 #define ENSC_WRAPPERS_UNISTD 1
42 #define ENSC_WRAPPERS_FCNTL  1
43 #define ENSC_WRAPPERS_STAT   1
44 #include <wrappers.h>
45
46 #define MAX_PEEK_SIZE 4096
47 #define MIN(a,b)  (((a) > (b)) ? (b) : (a))
48 #define STRINGIFY_(x)   #x
49 #define STRINGIFY(x)    STRINGIFY_(x)
50
51 struct file_format {
52   /* where the value would be in the file */
53   long          offset;
54   /* type of match */
55   enum {
56     FFT_STRING = 1,
57     FFT_SHORT,
58     FFT_LONG,
59     FFT_LE = 0x4000,
60     FFT_BE = 0x8000,
61   }             type;
62   /* the value */
63   union {
64     char *      st;
65     uint16_t    sh;
66     uint32_t    lo;
67   }             value;
68   /* length of the value */
69   size_t        len;
70   /* program to use for extraction */
71   char *        extractor;
72   /* should we try to process the contents as well? */
73   int           peek_inside;
74
75   struct file_format *next;
76 };
77 typedef struct file_format file_format_t;
78
79 int wrapper_exit_code = 255;
80
81 #define CMD_HELP                0x4001
82 #define CMD_VERSION             0x4002
83
84 struct option const
85 CMDLINE_OPTIONS[] = {
86   { "help",       no_argument,       0, CMD_HELP },
87   { "version",    no_argument,       0, CMD_VERSION },
88   { "desc",       required_argument, 0, 'd' },
89   { "silent",     no_argument,       0, 'q' },
90   { 0,0,0,0 },
91 };
92
93 static void
94 showHelp(int fd, char const *cmd, int res)
95 {
96   WRITE_MSG(fd, "Usage:\n  ");
97   WRITE_STR(fd, cmd);
98   WRITE_MSG(fd,
99             " -d <descriptions file> <file1> [<file2>...]\n\n"
100             "Please report bugs to " PACKAGE_BUGREPORT "\n");
101
102   exit(res);
103 }
104
105 static void
106 showVersion()
107 {
108   WRITE_MSG(1,
109             "h2ext " VERSION " -- determines how to extract a file\n"
110             "This program is part of " PACKAGE_STRING "\n\n"
111             "Copyright (C) 2007 Daniel Hokka Zakrisson\n"
112             VERSION_COPYRIGHT_DISCLAIMER);
113   exit(0);
114 }
115
116 static file_format_t *
117 find_format(file_format_t *head, char *data)
118 {
119   file_format_t *i;
120
121   for (i = head; i; i = i->next) {
122     switch (i->type & ~(FFT_LE|FFT_BE)) {
123     case FFT_STRING:
124       if (memcmp(i->value.st, data + i->offset, i->len) == 0)
125         goto found;
126       break;
127     case FFT_SHORT:
128       if (i->value.sh == *((__typeof__(i->value.sh) *)data + i->offset))
129         goto found;
130       break;
131     case FFT_LONG:
132       if (i->value.lo == *((__typeof__(i->value.lo) *)data + i->offset))
133         goto found;
134       break;
135     }
136   }
137 found:
138   return i;
139 }
140
141 static int
142 process_file(file_format_t *head, const char *file, file_format_t *ret[2])
143 {
144   int fd;
145   void *mapping;
146   struct stat st;
147
148   fd = EopenD(file, O_RDONLY, 0);
149   Efstat(fd, &st);
150   mapping = mmap(NULL, MIN(st.st_size, MAX_PEEK_SIZE), PROT_READ, MAP_SHARED, fd, 0);
151   if (!mapping) {
152     perror("mmap()");
153     Eclose(fd);
154     return -1;
155   }
156
157   ret[0] = find_format(head, mapping);
158
159   munmap(mapping, MIN(st.st_size, MAX_PEEK_SIZE));
160
161   if (ret[0] && ret[0]->peek_inside) {
162     pid_t child;
163     int   fds[2];
164
165     Elseek(fd, 0, SEEK_SET);
166
167     Epipe(fds);
168     child = Efork();
169     if (child == 0) {
170       char *argv[3] = { PROG_H2EXT_WORKER, ret[0]->extractor, NULL };
171       dup2(fd, 0);
172       dup2(fds[1], 1);
173       EexecvpD(PROG_H2EXT_WORKER, argv);
174     }
175     else {
176       char *buf = calloc(MAX_PEEK_SIZE, sizeof(char)), *cur, *end;
177       ssize_t bytes_read;
178
179       /* read MAX_PEEK_SIZE bytes from the decompressor */
180       cur = buf;
181       end = buf + MAX_PEEK_SIZE;
182       while (cur < end && (bytes_read = Eread(fds[0], cur, end - cur - 1)) > 0)
183         cur += bytes_read;
184
185       /* get rid of the child */
186       kill(child, SIGTERM);
187       wait(NULL);
188
189       ret[1] = find_format(head, buf);
190       free(buf);
191     }
192   }
193   else
194     ret[1] = NULL;
195
196   Eclose(fd);
197
198   return 0;
199 }
200
201 static inline void
202 byteswap(void *p, size_t len)
203 {
204   size_t i;
205   char *buf = p, tmp;
206   for (i = 0; i < (len >> 1); i++) {
207     tmp = buf[len - i - 1];
208     buf[len - i - 1] = buf[i];
209     buf[i] = tmp;
210   }
211 }
212
213 static inline ALWAYSINLINE void
214 WRITE_INT(int fd, int num)
215 {
216   char   buf[sizeof(num)*3+2];
217   size_t l;
218
219   l = utilvserver_fmt_long(buf,num);
220
221   Vwrite(fd, buf, l);
222 }
223
224 static int
225 load_description(const char *file, file_format_t **head)
226 {
227   file_format_t *prev = NULL,
228                 *i = NULL;
229   int           fd,
230                 line_no = 0;
231   char          buf[512],
232                 *field,
233                 *end = buf,
234                 *ptr,
235                 *eol;
236   ssize_t       bytes_read;
237
238   fd = EopenD(file, O_RDONLY, 0);
239
240   *buf = '\0';
241   while (1) {
242     if ((eol = strchr(buf, '\n')) == NULL && (end - buf) < (sizeof(buf) - 1)) {
243       bytes_read = Eread(fd, end, sizeof(buf) - 1 - (end - buf));
244       /* EOF, implicit newline */
245       if (bytes_read == 0) {
246         if (end == buf)
247           break;
248         eol = end;
249         *(end++) = '\n';
250       }
251       end += bytes_read;
252       *end = '\0';
253       continue;
254     }
255     else if (eol == NULL) {
256       WRITE_MSG(2, ENSC_WRAPPERS_PREFIX);
257       WRITE_STR(2, file);
258       WRITE_MSG(2, ":");
259       WRITE_INT(2, line_no);
260       WRITE_MSG(2, " is a really long line\n");
261       Eclose(fd);
262       return -1;
263     }
264     *eol = '\0';
265     line_no++;
266
267     if (*buf == '#' || *buf == '\0')
268       goto new_line;
269     if (*head == NULL)
270       i = *head = calloc(1, sizeof(file_format_t));
271     else {
272       i->next = calloc(1, sizeof(file_format_t));
273       prev = i;
274       i = i->next;
275     }
276     i->next = NULL;
277
278 #define get_field()     if (*(ptr+1) == '\0') goto new_line_and_free; \
279                         for (ptr++; *ptr == '\t' && *ptr != '\0'; ptr++); \
280                         for (field = ptr; *ptr != '\t' && *ptr != '\0'; ptr++); \
281                         *ptr = '\0';
282     field = ptr = buf;
283     while (*ptr != '\t' && *ptr != '\0')
284       ptr++;
285     *ptr = '\0';
286     if (field == ptr)
287       goto new_line_and_free;
288     i->offset = strtol(field, NULL, 0);
289
290     get_field();
291     if (strcmp(field, "string") == 0)
292       i->type = FFT_STRING;
293     else if (strcmp(field, "short") == 0)
294       i->type = FFT_SHORT;
295     else if (strcmp(field, "long") == 0)
296       i->type = FFT_LONG;
297     else if (strcmp(field, "leshort") == 0)
298       i->type = FFT_SHORT|FFT_LE;
299     else if (strcmp(field, "beshort") == 0)
300       i->type = FFT_SHORT|FFT_BE;
301     else if (strcmp(field, "lelong") == 0)
302       i->type = FFT_LONG|FFT_LE;
303     else if (strcmp(field, "belong") == 0)
304       i->type = FFT_LONG|FFT_BE;
305     else {
306       WRITE_MSG(2, ENSC_WRAPPERS_PREFIX);
307       WRITE_STR(2, file);
308       WRITE_MSG(2, ":");
309       WRITE_INT(2, line_no);
310       WRITE_MSG(2, " has an unknown type: ");
311       WRITE_STR(2, field);
312       WRITE_MSG(2, "\n");
313       goto new_line_and_free;
314     }
315
316     get_field();
317     switch (i->type & ~(FFT_BE|FFT_LE)) {
318     case FFT_STRING:
319       {
320       char *c, *tmp;
321       i->value.st = tmp = calloc(strlen(field) + 1, sizeof(char));
322       for (c = field; *c; c++) {
323         if (*c == '\\') {
324           char *endptr;
325           *(tmp++) = (char)strtol(c + 1, &endptr, 8);
326           c = endptr - 1;
327         }
328         else
329           *(tmp++) = *c;
330       }
331       *tmp = '\0';
332       i->len = tmp - i->value.st;
333       }
334       break;
335     case FFT_SHORT:
336       i->len = sizeof(i->value.sh);
337       i->value.sh = (__typeof__(i->value.sh))strtol(field, NULL, 0);
338 #if BYTE_ORDER != BIG_ENDIAN
339       if (i->type & FFT_BE)
340 #elif BYTE_ORDER != LITTLE_ENDIAN
341       if (i->type & FFT_LE)
342 #else
343 #  error UNKNOWN BYTE ORDER
344 #endif
345         byteswap(&i->value.sh, i->len);
346       break;
347     case FFT_LONG:
348       i->len = sizeof(i->value.lo);
349       i->value.lo = (__typeof__(i->value.lo))strtol(field, NULL, 0);
350 #if BYTE_ORDER != BIG_ENDIAN
351       if (i->type & FFT_BE)
352 #elif BYTE_ORDER != LITTLE_ENDIAN
353       if (i->type & FFT_LE)
354 #else
355 #  error UNKNOWN BYTE ORDER
356 #endif
357         byteswap(&i->value.lo, i->len);
358       break;
359     }
360
361     get_field();
362     i->extractor = strdup(field);
363
364     get_field();
365     i->peek_inside = (int)strtol(field, NULL, 0);
366
367     /* sanity check the entry */
368     if (i->offset < 0) {
369       WRITE_MSG(2, ENSC_WRAPPERS_PREFIX);
370       WRITE_STR(2, file);
371       WRITE_MSG(2, ":");
372       WRITE_INT(2, line_no);
373       WRITE_MSG(2, " has an invalid offset: ");
374       WRITE_INT(2, i->offset);
375       WRITE_MSG(2, "\n");
376       goto new_line_and_free;
377     }
378     else if ((i->offset + i->len) > MAX_PEEK_SIZE) {
379       WRITE_MSG(2, ENSC_WRAPPERS_PREFIX);
380       WRITE_STR(2, file);
381       WRITE_MSG(2, ":");
382       WRITE_INT(2, line_no);
383       WRITE_MSG(2, " exceeds maximum offset (" STRINGIFY(MAX_PEEK_SIZE) ")\n");
384       goto new_line_and_free;
385     }
386 #undef get_field
387     goto new_line;
388
389 new_line_and_free:
390     free(i);
391     if (prev) {
392       i = prev;
393       free(i->next);
394       i->next = NULL;
395     }
396     else
397       *head = i = NULL;
398 new_line:
399     memmove(buf, eol + 1, end - (eol + 1));
400     end = buf + (end - (eol + 1));
401   }
402
403   Eclose(fd);
404   return 0;
405 }
406
407 int main(int argc, char *argv[])
408 {
409   char          **file = NULL,
410                 *desc = NULL;
411   file_format_t *head = NULL;
412   int           quiet = 0;
413
414   while (1) {
415     int c = getopt_long(argc, argv, "+d:q", CMDLINE_OPTIONS, 0);
416     if (c == -1) break;
417
418     switch (c) {
419     case CMD_HELP:      showHelp(1, argv[0], 0);
420     case CMD_VERSION:   showVersion();
421     case 'd':           desc = optarg; break;
422     case 'q':           quiet = 1;     break;
423     default:
424       WRITE_MSG(2, "Try '");
425       WRITE_STR(2, argv[0]);
426       WRITE_MSG(2, " --help' for more information.\n");
427       return wrapper_exit_code;
428     }
429   }
430
431   if (desc == NULL) {
432     WRITE_MSG(2, "No descriptions supplied, try '");
433     WRITE_STR(2, argv[0]);
434     WRITE_MSG(2, " --help' for more information.\n");
435     return wrapper_exit_code;
436   }
437
438   head = NULL;
439   if (load_description(desc, &head) == -1)
440     return EXIT_FAILURE;
441
442   for (file = argv + optind; *file; file++) {
443     file_format_t *formats[2];
444     if (!quiet) {
445       WRITE_STR(1, *file);
446       WRITE_MSG(1, ": ");
447     }
448     if (!process_file(head, *file, formats) && formats[0]) {
449       WRITE_STR(1, formats[0]->extractor);
450       if (formats[0]->peek_inside) {
451         WRITE_MSG(1, " | ");
452         WRITE_STR(1, formats[1] ? formats[1]->extractor : "unknown format");
453       }
454     }
455     else
456       WRITE_MSG(1, "unknown format");
457     WRITE_MSG(1, "\n");
458   }
459
460   return 0;
461 }