Line data Source code
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 <http://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 :
30 :
31 :
32 : static unsigned long long
33 0 : parse_xoctal (const void *data, size_t length, const char *filename)
34 : {
35 0 : const unsigned char *p = data;
36 : unsigned long long value;
37 :
38 0 : if (!length)
39 0 : value = 0;
40 0 : else if ( (*p & 0x80))
41 : {
42 : /* Binary format. */
43 0 : value = (*p++ & 0x7f);
44 0 : while (--length)
45 : {
46 0 : value <<= 8;
47 0 : value |= *p++;
48 : }
49 : }
50 : else
51 : {
52 : /* Octal format */
53 0 : value = 0;
54 : /* Skip leading spaces and zeroes. */
55 0 : for (; length && (*p == ' ' || *p == '0'); length--, p++)
56 : ;
57 0 : for (; length && *p; length--, p++)
58 : {
59 0 : if (*p >= '0' && *p <= '7')
60 : {
61 0 : value <<= 3;
62 0 : value += (*p - '0');
63 : }
64 : else
65 : {
66 0 : log_error ("%s: invalid octal number encountered - assuming 0\n",
67 : filename);
68 0 : value = 0;
69 0 : break;
70 : }
71 : }
72 : }
73 0 : return value;
74 : }
75 :
76 :
77 : static tar_header_t
78 0 : parse_header (const void *record, const char *filename)
79 : {
80 0 : const struct ustar_raw_header *raw = record;
81 : size_t n, namelen, prefixlen;
82 : tar_header_t header;
83 : int use_prefix;
84 :
85 0 : use_prefix = (!memcmp (raw->magic, "ustar", 5)
86 0 : && (raw->magic[5] == ' ' || !raw->magic[5]));
87 :
88 :
89 0 : for (namelen=0; namelen < sizeof raw->name && raw->name[namelen]; namelen++)
90 : ;
91 0 : if (namelen == sizeof raw->name)
92 0 : log_info ("%s: warning: name not terminated by a nul byte\n", filename);
93 0 : for (n=namelen+1; n < sizeof raw->name; n++)
94 0 : if (raw->name[n])
95 : {
96 0 : log_info ("%s: warning: garbage after name\n", filename);
97 0 : break;
98 : }
99 :
100 :
101 0 : if (use_prefix && raw->prefix[0])
102 : {
103 0 : for (prefixlen=0; (prefixlen < sizeof raw->prefix
104 0 : && raw->prefix[prefixlen]); prefixlen++)
105 : ;
106 0 : if (prefixlen == sizeof raw->prefix)
107 0 : log_info ("%s: warning: prefix not terminated by a nul byte\n",
108 : filename);
109 0 : for (n=prefixlen+1; n < sizeof raw->prefix; n++)
110 0 : if (raw->prefix[n])
111 : {
112 0 : log_info ("%s: warning: garbage after prefix\n", filename);
113 0 : break;
114 : }
115 : }
116 : else
117 0 : prefixlen = 0;
118 :
119 0 : header = xtrycalloc (1, sizeof *header + prefixlen + 1 + namelen);
120 0 : if (!header)
121 : {
122 0 : log_error ("%s: error allocating header: %s\n",
123 : filename, gpg_strerror (gpg_error_from_syserror ()));
124 0 : return NULL;
125 : }
126 0 : if (prefixlen)
127 : {
128 0 : n = prefixlen;
129 0 : memcpy (header->name, raw->prefix, n);
130 0 : if (raw->prefix[n-1] != '/')
131 0 : header->name[n++] = '/';
132 : }
133 : else
134 0 : n = 0;
135 0 : memcpy (header->name+n, raw->name, namelen);
136 0 : header->name[n+namelen] = 0;
137 :
138 0 : header->mode = parse_xoctal (raw->mode, sizeof raw->mode, filename);
139 0 : header->uid = parse_xoctal (raw->uid, sizeof raw->uid, filename);
140 0 : header->gid = parse_xoctal (raw->gid, sizeof raw->gid, filename);
141 0 : header->size = parse_xoctal (raw->size, sizeof raw->size, filename);
142 0 : header->mtime = parse_xoctal (raw->mtime, sizeof raw->mtime, filename);
143 : /* checksum = */
144 0 : switch (raw->typeflag[0])
145 : {
146 0 : case '0': header->typeflag = TF_REGULAR; break;
147 0 : case '1': header->typeflag = TF_HARDLINK; break;
148 0 : case '2': header->typeflag = TF_SYMLINK; break;
149 0 : case '3': header->typeflag = TF_CHARDEV; break;
150 0 : case '4': header->typeflag = TF_BLOCKDEV; break;
151 0 : case '5': header->typeflag = TF_DIRECTORY; break;
152 0 : case '6': header->typeflag = TF_FIFO; break;
153 0 : case '7': header->typeflag = TF_RESERVED; break;
154 0 : default: header->typeflag = TF_UNKNOWN; break;
155 : }
156 :
157 :
158 : /* Compute the number of data records following this header. */
159 0 : if (header->typeflag == TF_REGULAR || header->typeflag == TF_UNKNOWN)
160 0 : header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE;
161 : else
162 0 : header->nrecords = 0;
163 :
164 :
165 0 : return header;
166 : }
167 :
168 :
169 :
170 : /* Read the next block, assming it is a tar header. Returns a header
171 : object on success or NULL one error. In case of an error an error
172 : message has been printed. */
173 : static tar_header_t
174 0 : read_header (estream_t stream)
175 : {
176 : gpg_error_t err;
177 : char record[RECORDSIZE];
178 : int i;
179 :
180 0 : err = read_record (stream, record);
181 0 : if (err)
182 0 : return NULL;
183 :
184 0 : for (i=0; i < RECORDSIZE && !record[i]; i++)
185 : ;
186 0 : if (i == RECORDSIZE)
187 : {
188 : /* All zero header - check whether it is the first part of an
189 : end of archive mark. */
190 0 : err = read_record (stream, record);
191 0 : if (err)
192 0 : return NULL;
193 :
194 0 : for (i=0; i < RECORDSIZE && !record[i]; i++)
195 : ;
196 0 : if (i != RECORDSIZE)
197 0 : log_info ("%s: warning: skipping empty header\n",
198 : es_fname_get (stream));
199 : else
200 : {
201 : /* End of archive - FIXME: we might want to check for garbage. */
202 0 : return NULL;
203 : }
204 : }
205 :
206 0 : return parse_header (record, es_fname_get (stream));
207 : }
208 :
209 :
210 : /* Skip the data records according to HEADER. Prints an error message
211 : on error and return -1. */
212 : static int
213 0 : skip_data (estream_t stream, tar_header_t header)
214 : {
215 : char record[RECORDSIZE];
216 : unsigned long long n;
217 :
218 0 : for (n=0; n < header->nrecords; n++)
219 : {
220 0 : if (read_record (stream, record))
221 0 : return -1;
222 : }
223 :
224 0 : return 0;
225 : }
226 :
227 :
228 :
229 : static void
230 0 : print_header (tar_header_t header, estream_t out)
231 : {
232 : unsigned long mask;
233 : char modestr[10+1];
234 : int i;
235 :
236 0 : *modestr = '?';
237 0 : switch (header->typeflag)
238 : {
239 0 : case TF_REGULAR: *modestr = '-'; break;
240 0 : case TF_HARDLINK: *modestr = 'h'; break;
241 0 : case TF_SYMLINK: *modestr = 'l'; break;
242 0 : case TF_CHARDEV: *modestr = 'c'; break;
243 0 : case TF_BLOCKDEV: *modestr = 'b'; break;
244 0 : case TF_DIRECTORY:*modestr = 'd'; break;
245 0 : case TF_FIFO: *modestr = 'f'; break;
246 0 : case TF_RESERVED: *modestr = '='; break;
247 0 : case TF_UNKNOWN: break;
248 0 : case TF_NOTSUP: break;
249 : }
250 0 : for (mask = 0400, i = 0; i < 9; i++, mask >>= 1)
251 0 : modestr[1+i] = (header->mode & mask)? "rwxrwxrwx"[i]:'-';
252 0 : if ((header->typeflag & 04000))
253 0 : modestr[3] = modestr[3] == 'x'? 's':'S';
254 0 : if ((header->typeflag & 02000))
255 0 : modestr[6] = modestr[6] == 'x'? 's':'S';
256 0 : if ((header->typeflag & 01000))
257 0 : modestr[9] = modestr[9] == 'x'? 't':'T';
258 0 : modestr[10] = 0;
259 :
260 0 : es_fprintf (out, "%s %lu %lu/%lu %12llu %s %s\n",
261 : modestr, header->nlink, header->uid, header->gid, header->size,
262 0 : isotimestamp (header->mtime), header->name);
263 0 : }
264 :
265 :
266 :
267 : /* List the tarball FILENAME or, if FILENAME is NULL, the tarball read
268 : from stdin. */
269 : void
270 0 : gpgtar_list (const char *filename)
271 : {
272 : gpg_error_t err;
273 : estream_t stream;
274 : tar_header_t header;
275 :
276 0 : if (filename)
277 : {
278 0 : if (!strcmp (filename, "-"))
279 0 : stream = es_stdout;
280 : else
281 0 : stream = es_fopen (filename, "rb");
282 0 : if (!stream)
283 : {
284 0 : err = gpg_error_from_syserror ();
285 0 : log_error ("error opening '%s': %s\n", filename, gpg_strerror (err));
286 0 : return;
287 : }
288 : }
289 : else
290 0 : stream = es_stdin;
291 :
292 0 : if (stream == es_stdin)
293 0 : es_set_binary (es_stdin);
294 :
295 : for (;;)
296 : {
297 0 : header = read_header (stream);
298 0 : if (!header)
299 0 : goto leave;
300 :
301 0 : print_header (header, es_stdout);
302 :
303 0 : if (skip_data (stream, header))
304 0 : goto leave;
305 0 : xfree (header);
306 0 : header = NULL;
307 0 : }
308 :
309 :
310 : leave:
311 0 : xfree (header);
312 0 : if (stream != es_stdin)
313 0 : es_fclose (stream);
314 0 : return;
315 : }
316 :
317 : tar_header_t
318 0 : gpgtar_read_header (estream_t stream)
319 : {
320 : /*FIXME: Change to return an error code. */
321 0 : return read_header (stream);
322 : }
323 :
324 : void
325 0 : gpgtar_print_header (tar_header_t header, estream_t out)
326 : {
327 0 : if (header && out)
328 0 : print_header (header, out);
329 0 : }
|