Replace file with h2ext for the template build method.
[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
49 struct file_format {
50   /* where the value would be in the file */
51   long          offset;
52   /* type of match */
53   enum {
54     FFT_STRING = 1,
55     FFT_SHORT,
56     FFT_LONG,
57     FFT_LE = 0x4000,
58     FFT_BE = 0x8000,
59   }             type;
60   /* the value */
61   union {
62     char *      st;
63     uint16_t    sh;
64     uint32_t    lo;
65   }             value;
66   /* length of the value */
67   size_t        len;
68   /* program to use for extraction */
69   char *        extractor;
70   /* should we try to process the contents as well? */
71   int           peek_inside;
72
73   struct file_format *next;
74 };
75 typedef struct file_format file_format_t;
76
77 int wrapper_exit_code = 255;
78
79 #define CMD_HELP                0x4001
80 #define CMD_VERSION             0x4002
81
82 struct option const
83 CMDLINE_OPTIONS[] = {
84   { "help",       no_argument,       0, CMD_HELP },
85   { "version",    no_argument,       0, CMD_VERSION },
86   { "desc",       required_argument, 0, 'd' },
87   { "silent",     no_argument,       0, 'q' },
88   { 0,0,0,0 },
89 };
90
91 static void
92 showHelp(int fd, char const *cmd, int res)
93 {
94   WRITE_MSG(fd, "Usage:\n  ");
95   WRITE_STR(fd, cmd);
96   WRITE_MSG(fd,
97             " -d <descriptions file> <file1> [<file2>...]\n\n"
98             "Please report bugs to " PACKAGE_BUGREPORT "\n");
99
100   exit(res);
101 }
102
103 static void
104 showVersion()
105 {
106   WRITE_MSG(1,
107             "h2ext " VERSION " -- determines how to extract a file\n"
108             "This program is part of " PACKAGE_STRING "\n\n"
109             "Copyright (C) 2007 Daniel Hokka Zakrisson\n"
110             VERSION_COPYRIGHT_DISCLAIMER);
111   exit(0);
112 }
113
114 static file_format_t *
115 find_format(file_format_t *head, char *data)
116 {
117   file_format_t *i;
118
119   for (i = head; i; i = i->next) {
120     switch (i->type & ~(FFT_LE|FFT_BE)) {
121     case FFT_STRING:
122       if (memcmp(i->value.st, data + i->offset, i->len) == 0)
123         goto found;
124       break;
125     case FFT_SHORT:
126       if (i->value.sh == *((__typeof__(i->value.sh) *)data + i->offset))
127         goto found;
128       break;
129     case FFT_LONG:
130       if (i->value.lo == *((__typeof__(i->value.lo) *)data + i->offset))
131         goto found;
132       break;
133     }
134   }
135 found:
136   return i;
137 }
138
139 static int
140 process_file(file_format_t *head, const char *file, file_format_t *ret[2])
141 {
142   int fd;
143   void *mapping;
144   struct stat st;
145
146   fd = EopenD(file, O_RDONLY, 0);
147   Efstat(fd, &st);
148   mapping = mmap(NULL, MIN(st.st_size, MAX_PEEK_SIZE), PROT_READ, MAP_SHARED, fd, 0);
149   if (!mapping) {
150     perror("mmap()");
151     Eclose(fd);
152     return -1;
153   }
154
155   ret[0] = find_format(head, mapping);
156
157   munmap(mapping, MIN(st.st_size, MAX_PEEK_SIZE));
158
159   if (ret[0] && ret[0]->peek_inside) {
160     pid_t child;
161     int   fds[2];
162
163     Elseek(fd, 0, SEEK_SET);
164
165     Epipe(fds);
166     child = Efork();
167     if (child == 0) {
168       char *argv[3] = { PROG_H2EXT_WORKER, ret[0]->extractor, NULL };
169       dup2(fd, 0);
170       dup2(fds[1], 1);
171       EexecvpD(PROG_H2EXT_WORKER, argv);
172     }
173     else {
174       char *buf = calloc(MAX_PEEK_SIZE, sizeof(char)), *cur, *end;
175       ssize_t bytes_read;
176
177       /* read MAX_PEEK_SIZE bytes from the decompressor */
178       cur = buf;
179       end = buf + MAX_PEEK_SIZE;
180       while (cur < end && (bytes_read = Eread(fds[0], cur, end - cur - 1)) > 0)
181         cur += bytes_read;
182
183       /* get rid of the child */
184       kill(child, SIGTERM);
185       wait(NULL);
186
187       ret[1] = find_format(head, buf);
188       free(buf);
189     }
190   }
191   else
192     ret[1] = NULL;
193
194   Eclose(fd);
195
196   return 0;
197 }
198
199 static inline void
200 byteswap(void *p, size_t len)
201 {
202   size_t i;
203   char *buf = p, tmp;
204   for (i = 0; i < (len >> 1); i++) {
205     tmp = buf[len - i - 1];
206     buf[len - i - 1] = buf[i];
207     buf[i] = tmp;
208   }
209 }
210
211 static inline ALWAYSINLINE void
212 WRITE_INT(int fd, int num)
213 {
214   char   buf[sizeof(num)*3+2];
215   size_t l;
216
217   l = utilvserver_fmt_long(buf,num);
218
219   Vwrite(fd, buf, l);
220 }
221
222 static int
223 load_description(const char *file, file_format_t **head)
224 {
225   file_format_t *prev = NULL,
226                 *i = NULL;
227   int           fd,
228                 line_no = 0;
229   char          buf[512],
230                 *field,
231                 *end = buf,
232                 *ptr,
233                 *eol;
234   ssize_t       bytes_read;
235
236   fd = EopenD(file, O_RDONLY, 0);
237
238   *buf = '\0';
239   while (1) {
240     if ((eol = strchr(buf, '\n')) == NULL && (end - buf) < (sizeof(buf) - 1)) {
241       bytes_read = Eread(fd, end, sizeof(buf) - 1 - (end - buf));
242       /* EOF, implicit newline */
243       if (bytes_read == 0) {
244         if (end == buf)
245           break;
246         eol = end;
247         *(end++) = '\n';
248       }
249       end += bytes_read;
250       *end = '\0';
251       continue;
252     }
253     else if (eol == NULL) {
254       WRITE_MSG(2, ENSC_WRAPPERS_PREFIX);
255       WRITE_STR(2, file);
256       WRITE_MSG(2, ":");
257       WRITE_INT(2, line_no);
258       WRITE_MSG(2, " is a really long line\n");
259       Eclose(fd);
260       return -1;
261     }
262     *eol = '\0';
263     line_no++;
264
265     if (*buf == '#' || *buf == '\0')
266       goto new_line;
267     if (*head == NULL)
268       i = *head = calloc(1, sizeof(file_format_t));
269     else {
270       i->next = calloc(1, sizeof(file_format_t));
271       prev = i;
272       i = i->next;
273     }
274     i->next = NULL;
275
276 #define get_field()  for (ptr++; *ptr == '\t' && *ptr != '\0'; ptr++); \
277       for (field = ptr; *ptr != '\t' && *ptr != '\0'; ptr++); \
278       *ptr = '\0';
279     field = ptr = buf;
280     while (*ptr != '\t' && *ptr != '\0')
281       ptr++;
282     *ptr = '\0';
283     if (field == ptr)
284       goto new_line_and_free;
285     i->offset = strtol(field, NULL, 0);
286
287     get_field();
288     if (strcmp(field, "string") == 0)
289       i->type = FFT_STRING;
290     else if (strcmp(field, "short") == 0)
291       i->type = FFT_SHORT;
292     else if (strcmp(field, "long") == 0)
293       i->type = FFT_LONG;
294     else if (strcmp(field, "leshort") == 0)
295       i->type = FFT_SHORT|FFT_LE;
296     else if (strcmp(field, "beshort") == 0)
297       i->type = FFT_SHORT|FFT_BE;
298     else if (strcmp(field, "lelong") == 0)
299       i->type = FFT_LONG|FFT_LE;
300     else if (strcmp(field, "belong") == 0)
301       i->type = FFT_LONG|FFT_BE;
302     else {
303       WRITE_MSG(2, ENSC_WRAPPERS_PREFIX);
304       WRITE_STR(2, file);
305       WRITE_MSG(2, ":");
306       WRITE_INT(2, line_no);
307       WRITE_MSG(2, " has an unknown type: ");
308       WRITE_STR(2, field);
309       WRITE_MSG(2, "\n");
310       goto new_line_and_free;
311     }
312
313     get_field();
314     switch (i->type & ~(FFT_BE|FFT_LE)) {
315     case FFT_STRING:
316       {
317       char *c, *tmp;
318       i->value.st = tmp = calloc(strlen(field) + 1, sizeof(char));
319       for (c = field; *c; c++) {
320         if (*c == '\\') {
321           char *endptr;
322           *(tmp++) = (char)strtol(c + 1, &endptr, 8);
323           c = endptr - 1;
324         }
325         else
326           *(tmp++) = *c;
327       }
328       *tmp = '\0';
329       i->len = tmp - i->value.st;
330       }
331       break;
332     case FFT_SHORT:
333       i->len = sizeof(i->value.sh);
334       i->value.sh = (__typeof__(i->value.sh))strtol(field, NULL, 0);
335 #if BYTE_ORDER != BIG_ENDIAN
336       if (i->type & FFT_BE)
337 #elif BYTE_ORDER != LITTLE_ENDIAN
338       if (i->type & FFT_LE)
339 #else
340 #  error UNKNOWN BYTE ORDER
341 #endif
342         byteswap(&i->value.sh, i->len);
343       break;
344     case FFT_LONG:
345       i->len = sizeof(i->value.lo);
346       i->value.lo = (__typeof__(i->value.lo))strtol(field, NULL, 0);
347 #if BYTE_ORDER != BIG_ENDIAN
348       if (i->type & FFT_BE)
349 #elif BYTE_ORDER != LITTLE_ENDIAN
350       if (i->type & FFT_LE)
351 #else
352 #  error UNKNOWN BYTE ORDER
353 #endif
354         byteswap(&i->value.lo, i->len);
355       break;
356     }
357
358     get_field();
359     i->extractor = strdup(field);
360
361     get_field();
362     i->peek_inside = (int)strtol(field, NULL, 0);
363
364 #undef get_field
365     goto new_line;
366
367 new_line_and_free:
368     free(i);
369     if (prev) {
370       i = prev;
371       free(i->next);
372       i->next = NULL;
373     }
374     else
375       *head = i = NULL;
376 new_line:
377     memmove(buf, eol + 1, end - (eol + 1));
378     end = buf + (end - (eol + 1));
379   }
380
381   Eclose(fd);
382   return 0;
383 }
384
385 int main(int argc, char *argv[])
386 {
387   char          **file = NULL,
388                 *desc = NULL;
389   file_format_t *head = NULL;
390   int           quiet = 0;
391
392   while (1) {
393     int c = getopt_long(argc, argv, "+d:q", CMDLINE_OPTIONS, 0);
394     if (c == -1) break;
395
396     switch (c) {
397     case CMD_HELP:      showHelp(1, argv[0], 0);
398     case CMD_VERSION:   showVersion();
399     case 'd':           desc = optarg; break;
400     case 'q':           quiet = 1;     break;
401     default:
402       WRITE_MSG(2, "Try '");
403       WRITE_STR(2, argv[0]);
404       WRITE_MSG(2, " --help' for more information.\n");
405       return wrapper_exit_code;
406     }
407   }
408
409   if (desc == NULL) {
410     WRITE_MSG(2, "No descriptions supplied, try '");
411     WRITE_STR(2, argv[0]);
412     WRITE_MSG(2, " --help' for more information.\n");
413     return wrapper_exit_code;
414   }
415
416   head = NULL;
417   if (load_description(desc, &head) == -1)
418     return EXIT_FAILURE;
419
420   for (file = argv + optind; *file; file++) {
421     file_format_t *formats[2];
422     if (!quiet) {
423       WRITE_STR(1, *file);
424       WRITE_MSG(1, ": ");
425     }
426     if (!process_file(head, *file, formats) && formats[0]) {
427       WRITE_STR(1, formats[0]->extractor);
428       if (formats[0]->peek_inside) {
429         WRITE_MSG(1, " | ");
430         WRITE_STR(1, formats[1] ? formats[1]->extractor : "unknown format");
431       }
432     }
433     else
434       WRITE_MSG(1, "unknown format");
435     WRITE_MSG(1, "\n");
436   }
437
438   return 0;
439 }