Change all http://www.gnu.org in license notices to https://
[gnupg.git] / tools / gpgtar-list.c
1 /* gpgtar-list.c - List a TAR archive
2  * Copyright (C) 2010 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <https://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <errno.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <assert.h>
26
27 #include "i18n.h"
28 #include "gpgtar.h"
29 #include "../common/exectool.h"
30 #include "../common/ccparray.h"
31
32
33 \f
34 static unsigned long long
35 parse_xoctal (const void *data, size_t length, const char *filename)
36 {
37   const unsigned char *p = data;
38   unsigned long long value;
39
40   if (!length)
41     value = 0;
42   else if ( (*p & 0x80))
43     {
44       /* Binary format.  */
45       value = (*p++ & 0x7f);
46       while (--length)
47         {
48           value <<= 8;
49           value |= *p++;
50         }
51     }
52   else
53     {
54       /* Octal format  */
55       value = 0;
56       /* Skip leading spaces and zeroes.  */
57       for (; length && (*p == ' ' || *p == '0'); length--, p++)
58         ;
59       for (; length && *p; length--, p++)
60         {
61           if (*p >= '0' && *p <= '7')
62             {
63               value <<= 3;
64               value += (*p - '0');
65             }
66           else
67             {
68               log_error ("%s: invalid octal number encountered - assuming 0\n",
69                          filename);
70               value = 0;
71               break;
72             }
73         }
74     }
75   return value;
76 }
77
78
79 static tar_header_t
80 parse_header (const void *record, const char *filename)
81 {
82   const struct ustar_raw_header *raw = record;
83   size_t n, namelen, prefixlen;
84   tar_header_t header;
85   int use_prefix;
86
87   use_prefix = (!memcmp (raw->magic, "ustar", 5)
88                 && (raw->magic[5] == ' ' || !raw->magic[5]));
89
90
91   for (namelen=0; namelen < sizeof raw->name && raw->name[namelen]; namelen++)
92     ;
93   if (namelen == sizeof raw->name)
94     log_info ("%s: warning: name not terminated by a nul byte\n", filename);
95   for (n=namelen+1; n < sizeof raw->name; n++)
96     if (raw->name[n])
97       {
98         log_info ("%s: warning: garbage after name\n", filename);
99         break;
100       }
101
102
103   if (use_prefix && raw->prefix[0])
104     {
105       for (prefixlen=0; (prefixlen < sizeof raw->prefix
106                          && raw->prefix[prefixlen]); prefixlen++)
107         ;
108       if (prefixlen == sizeof raw->prefix)
109         log_info ("%s: warning: prefix not terminated by a nul byte\n",
110                   filename);
111       for (n=prefixlen+1; n < sizeof raw->prefix; n++)
112         if (raw->prefix[n])
113           {
114             log_info ("%s: warning: garbage after prefix\n", filename);
115             break;
116           }
117     }
118   else
119     prefixlen = 0;
120
121   header = xtrycalloc (1, sizeof *header + prefixlen + 1 + namelen);
122   if (!header)
123     {
124       log_error ("%s: error allocating header: %s\n",
125                  filename, gpg_strerror (gpg_error_from_syserror ()));
126       return NULL;
127     }
128   if (prefixlen)
129     {
130       n = prefixlen;
131       memcpy (header->name, raw->prefix, n);
132       if (raw->prefix[n-1] != '/')
133         header->name[n++] = '/';
134     }
135   else
136     n = 0;
137   memcpy (header->name+n, raw->name, namelen);
138   header->name[n+namelen] = 0;
139
140   header->mode  = parse_xoctal (raw->mode, sizeof raw->mode, filename);
141   header->uid   = parse_xoctal (raw->uid, sizeof raw->uid, filename);
142   header->gid   = parse_xoctal (raw->gid, sizeof raw->gid, filename);
143   header->size  = parse_xoctal (raw->size, sizeof raw->size, filename);
144   header->mtime = parse_xoctal (raw->mtime, sizeof raw->mtime, filename);
145   /* checksum = */
146   switch (raw->typeflag[0])
147     {
148     case '0': header->typeflag = TF_REGULAR; break;
149     case '1': header->typeflag = TF_HARDLINK; break;
150     case '2': header->typeflag = TF_SYMLINK; break;
151     case '3': header->typeflag = TF_CHARDEV; break;
152     case '4': header->typeflag = TF_BLOCKDEV; break;
153     case '5': header->typeflag = TF_DIRECTORY; break;
154     case '6': header->typeflag = TF_FIFO; break;
155     case '7': header->typeflag = TF_RESERVED; break;
156     default:  header->typeflag = TF_UNKNOWN; break;
157     }
158
159
160   /* Compute the number of data records following this header.  */
161   if (header->typeflag == TF_REGULAR || header->typeflag == TF_UNKNOWN)
162     header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE;
163   else
164     header->nrecords = 0;
165
166
167   return header;
168 }
169
170
171 \f
172 /* Read the next block, assming it is a tar header.  Returns a header
173    object on success in R_HEADER, or an error.  If the stream is
174    consumed, R_HEADER is set to NULL.  In case of an error an error
175    message has been printed.  */
176 static gpg_error_t
177 read_header (estream_t stream, tar_header_t *r_header)
178 {
179   gpg_error_t err;
180   char record[RECORDSIZE];
181   int i;
182
183   err = read_record (stream, record);
184   if (err)
185     return err;
186
187   for (i=0; i < RECORDSIZE && !record[i]; i++)
188     ;
189   if (i == RECORDSIZE)
190     {
191       /* All zero header - check whether it is the first part of an
192          end of archive mark.  */
193       err = read_record (stream, record);
194       if (err)
195         return err;
196
197       for (i=0; i < RECORDSIZE && !record[i]; i++)
198         ;
199       if (i != RECORDSIZE)
200         log_info ("%s: warning: skipping empty header\n",
201                   es_fname_get (stream));
202       else
203         {
204           /* End of archive - FIXME: we might want to check for garbage.  */
205           *r_header = NULL;
206           return 0;
207         }
208     }
209
210   *r_header = parse_header (record, es_fname_get (stream));
211   return *r_header ? 0 : gpg_error_from_syserror ();
212 }
213
214
215 /* Skip the data records according to HEADER.  Prints an error message
216    on error and return -1. */
217 static int
218 skip_data (estream_t stream, tar_header_t header)
219 {
220   char record[RECORDSIZE];
221   unsigned long long n;
222
223   for (n=0; n < header->nrecords; n++)
224     {
225       if (read_record (stream, record))
226         return -1;
227     }
228
229   return 0;
230 }
231
232
233 \f
234 static void
235 print_header (tar_header_t header, estream_t out)
236 {
237   unsigned long mask;
238   char modestr[10+1];
239   int i;
240
241   *modestr = '?';
242   switch (header->typeflag)
243     {
244     case TF_REGULAR:  *modestr = '-'; break;
245     case TF_HARDLINK: *modestr = 'h'; break;
246     case TF_SYMLINK:  *modestr = 'l'; break;
247     case TF_CHARDEV:  *modestr = 'c'; break;
248     case TF_BLOCKDEV: *modestr = 'b'; break;
249     case TF_DIRECTORY:*modestr = 'd'; break;
250     case TF_FIFO:     *modestr = 'f'; break;
251     case TF_RESERVED: *modestr = '='; break;
252     case TF_UNKNOWN:  break;
253     case TF_NOTSUP:   break;
254     }
255   for (mask = 0400, i = 0; i < 9; i++, mask >>= 1)
256     modestr[1+i] = (header->mode & mask)? "rwxrwxrwx"[i]:'-';
257   if ((header->typeflag & 04000))
258     modestr[3] = modestr[3] == 'x'? 's':'S';
259   if ((header->typeflag & 02000))
260     modestr[6] = modestr[6] == 'x'? 's':'S';
261   if ((header->typeflag & 01000))
262     modestr[9] = modestr[9] == 'x'? 't':'T';
263   modestr[10] = 0;
264
265   es_fprintf (out, "%s %lu %lu/%lu %12llu %s %s\n",
266               modestr, header->nlink, header->uid, header->gid, header->size,
267               isotimestamp (header->mtime), header->name);
268 }
269
270
271 \f
272 /* List the tarball FILENAME or, if FILENAME is NULL, the tarball read
273    from stdin.  */
274 gpg_error_t
275 gpgtar_list (const char *filename, int decrypt)
276 {
277   gpg_error_t err;
278   estream_t stream;
279   estream_t cipher_stream = NULL;
280   tar_header_t header = NULL;
281
282   if (filename)
283     {
284       if (!strcmp (filename, "-"))
285         stream = es_stdin;
286       else
287         stream = es_fopen (filename, "rb");
288       if (!stream)
289         {
290           err = gpg_error_from_syserror ();
291           log_error ("error opening '%s': %s\n", filename, gpg_strerror (err));
292           return err;
293         }
294     }
295   else
296     stream = es_stdin;
297
298   if (stream == es_stdin)
299     es_set_binary (es_stdin);
300
301   if (decrypt)
302     {
303       strlist_t arg;
304       ccparray_t ccp;
305       const char **argv;
306
307       cipher_stream = stream;
308       stream = es_fopenmem (0, "rwb");
309       if (! stream)
310         {
311           err = gpg_error_from_syserror ();
312           goto leave;
313         }
314
315       ccparray_init (&ccp, 0);
316
317       ccparray_put (&ccp, "--decrypt");
318       for (arg = opt.gpg_arguments; arg; arg = arg->next)
319         ccparray_put (&ccp, arg->d);
320
321       ccparray_put (&ccp, NULL);
322       argv = ccparray_get (&ccp, NULL);
323       if (!argv)
324         {
325           err = gpg_error_from_syserror ();
326           goto leave;
327         }
328
329       err = gnupg_exec_tool_stream (opt.gpg_program, argv,
330                                     cipher_stream, NULL, stream, NULL, NULL);
331       xfree (argv);
332       if (err)
333         goto leave;
334
335       err = es_fseek (stream, 0, SEEK_SET);
336       if (err)
337         goto leave;
338     }
339
340   for (;;)
341     {
342       err = read_header (stream, &header);
343       if (err || header == NULL)
344         goto leave;
345
346       print_header (header, es_stdout);
347
348       if (skip_data (stream, header))
349         goto leave;
350       xfree (header);
351       header = NULL;
352     }
353
354
355  leave:
356   xfree (header);
357   if (stream != es_stdin)
358     es_fclose (stream);
359   if (stream != cipher_stream)
360     es_fclose (cipher_stream);
361   return err;
362 }
363
364 gpg_error_t
365 gpgtar_read_header (estream_t stream, tar_header_t *r_header)
366 {
367   return read_header (stream, r_header);
368 }
369
370 void
371 gpgtar_print_header (tar_header_t header, estream_t out)
372 {
373   if (header && out)
374     print_header (header, out);
375 }