Version 1.19.1
[debian/tar] / scripts / xsparse.c
1 /* xsparse - expands compressed sparse file images extracted from GNU tar
2    archives.
3
4    Copyright (C) 2006, 2007 Free Software Foundation, Inc.
5
6    Written by Sergey Poznyakoff
7    
8    This program is free software; you can redistribute it and/or modify it
9    under the terms of the GNU General Public License as published by the
10    Free Software Foundation; either version 3, or (at your option) any later
11    version.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
16    Public License for more details.
17
18    You should have received a copy of the GNU General Public License along
19    with this program; if not, write to the Free Software Foundation, Inc.,
20    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
21
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <stdarg.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <sys/stat.h>
28 #include <limits.h>
29 #include <errno.h>
30
31 /* Bound on length of the string representing an off_t.
32    See INT_STRLEN_BOUND in intprops.h for explanation */
33 #define OFF_T_STRLEN_BOUND ((sizeof (off_t) * CHAR_BIT) * 146 / 485 + 1)
34 #define OFF_T_STRSIZE_BOUND (OFF_T_STRLEN_BOUND+1)
35
36 #define BLOCKSIZE 512
37
38 struct sp_array
39 {
40   off_t offset;
41   size_t numbytes;
42 };
43
44 char *progname;
45 int verbose;
46
47 void
48 die (int code, char *fmt, ...)
49 {
50   va_list ap;
51
52   fprintf (stderr, "%s: ", progname);
53   va_start (ap, fmt);
54   vfprintf (stderr, fmt, ap);
55   va_end (ap);
56   fprintf (stderr, "\n");
57   exit (code);
58 }
59
60 void *
61 emalloc (size_t size)
62 {
63   char *p = malloc (size);
64   if (!p)
65     die (1, "not enough memory");
66   return p;
67 }
68
69 off_t
70 string_to_off (char *p, char **endp)
71 {
72   off_t v = 0;
73
74   for (; *p; p++)
75     {
76       int digit = *p - '0';
77       off_t x = v * 10;
78       if (9 < (unsigned) digit)
79         {
80           if (endp)
81             {
82               *endp = p;
83               break;
84             }
85           die (1, "number parse error near %s", p);
86         }
87       else if (x / 10 != v)
88         die (1, "number out of allowed range, near %s", p);
89       v = x + digit;
90       if (v < 0)
91         die (1, "negative number");
92     }
93   if (endp)
94     *endp = p;
95   return v;
96 }
97
98 size_t
99 string_to_size (char *p, char **endp)
100 {
101   off_t v = string_to_off (p, endp);
102   size_t ret = v;
103   if (ret != v)
104     die (1, "number too big");
105   return ret;
106 }
107
108 size_t sparse_map_size;
109 struct sp_array *sparse_map;
110
111 void
112 get_line (char *s, int size, FILE *stream)
113 {
114   char *p = fgets (s, size, stream);
115   size_t len;
116
117   if (!p)
118     die (1, "unexpected end of file");
119   len = strlen (p);
120   if (s[len - 1] != '\n')
121     die (1, "buffer overflow");
122   s[len - 1] = 0;
123 }
124
125 int
126 get_var (FILE *fp, char **name, char **value)
127 {
128   static char *buffer;
129   static size_t bufsize = OFF_T_STRSIZE_BOUND;
130   char *p, *q;
131   
132   buffer = emalloc (bufsize);
133   do
134     {
135       size_t len, s;
136       
137       if (!fgets (buffer, bufsize, fp))
138         return 0;
139       len = strlen (buffer);
140       if (len == 0)
141         return 0;
142
143       s = string_to_size (buffer, &p);
144       if (*p != ' ')
145         die (1, "malformed header: expected space but found %s", p);
146       if (buffer[len-1] != '\n')
147         {
148           if (bufsize < s + 1)
149             {
150               bufsize = s + 1;
151               buffer = realloc (buffer, bufsize);
152               if (!buffer)
153                 die (1, "not enough memory");
154             }
155           if (!fgets (buffer + len, s - len + 1, fp))
156             die (1, "unexpected end of file or read error");
157         }
158       p++;
159     }
160   while (memcmp (p, "GNU.sparse.", 11));
161
162   p += 11;
163   q = strchr (p, '=');
164   if (!q)
165     die (1, "malformed header: expected `=' not found");
166   *q++ = 0;
167   q[strlen (q) - 1] = 0;
168   *name = p;
169   *value = q;
170   return 1;
171 }
172
173 char *outname;
174 off_t outsize;
175 unsigned version_major;
176 unsigned version_minor;
177
178 void
179 read_xheader (char *name)
180 {
181   char *kw, *val;
182   FILE *fp = fopen (name, "r");
183   char *expect = NULL;
184   size_t i = 0;
185
186   if (verbose)
187     printf ("Reading extended header file\n");
188   
189   while (get_var (fp, &kw, &val))
190     {
191       if (verbose)
192         printf ("Found variable GNU.sparse.%s = %s\n", kw, val);
193       
194       if (expect && strcmp (kw, expect))
195         die (1, "bad keyword sequence: expected `%s' but found `%s'",
196              expect, kw);
197       expect = NULL;
198       if (strcmp (kw, "name") == 0)
199         {
200           outname = emalloc (strlen (val) + 1);
201           strcpy (outname, val);
202         }
203       else if (strcmp (kw, "major") == 0)
204         {
205           version_major = string_to_size (val, NULL);
206         }
207       else if (strcmp (kw, "minor") == 0)
208         {
209           version_minor = string_to_size (val, NULL);
210         }
211       else if (strcmp (kw, "realsize") == 0
212                || strcmp (kw, "size") == 0)
213         {
214           outsize = string_to_off (val, NULL);
215         }
216       else if (strcmp (kw, "numblocks") == 0)
217         {
218           sparse_map_size = string_to_size (val, NULL);
219           sparse_map = emalloc (sparse_map_size * sizeof *sparse_map);
220         }
221       else if (strcmp (kw, "offset") == 0)
222         {
223           sparse_map[i].offset = string_to_off (val, NULL);
224           expect = "numbytes";
225         }
226       else if (strcmp (kw, "numbytes") == 0)
227         {
228           sparse_map[i++].numbytes = string_to_size (val, NULL);
229         }
230       else if (strcmp (kw, "map") == 0)
231         {
232           for (i = 0; i < sparse_map_size; i++)
233             {
234               sparse_map[i].offset = string_to_off (val, &val);
235               if (*val != ',')
236                 die (1, "bad GNU.sparse.map: expected `,' but found `%c'",
237                      *val);
238               sparse_map[i].numbytes = string_to_size (val+1, &val);
239               if (*val != ',')
240                 {
241                   if (!(*val == 0 && i == sparse_map_size-1))
242                     die (1, "bad GNU.sparse.map: expected `,' but found `%c'",
243                          *val);
244                 }
245               else
246                 val++;
247             }
248           if (*val)
249             die (1, "bad GNU.sparse.map: garbage at the end");
250         }
251     }
252   if (expect)
253     die (1, "bad keyword sequence: expected `%s' not found", expect);
254   if (version_major == 0 && sparse_map_size == 0)
255     die (1, "size of the sparse map unknown");
256   if (i != sparse_map_size)
257     die (1, "not all sparse entries supplied");
258   fclose (fp);
259 }
260
261 void
262 read_map (FILE *ifp)
263 {
264   size_t i;
265   char nbuf[OFF_T_STRSIZE_BOUND];
266
267   if (verbose)
268     printf ("Reading v.1.0 sparse map\n");
269   
270   get_line (nbuf, sizeof nbuf, ifp);
271   sparse_map_size = string_to_size (nbuf, NULL);
272   sparse_map = emalloc (sparse_map_size * sizeof *sparse_map);
273
274   for (i = 0; i < sparse_map_size; i++)
275     {
276       get_line (nbuf, sizeof nbuf, ifp);
277       sparse_map[i].offset = string_to_off (nbuf, NULL);
278       get_line (nbuf, sizeof nbuf, ifp);
279       sparse_map[i].numbytes = string_to_size (nbuf, NULL);
280     }
281
282   fseek (ifp, ((ftell (ifp) + BLOCKSIZE - 1) / BLOCKSIZE) * BLOCKSIZE,
283          SEEK_SET);
284 }  
285
286 void
287 expand_sparse (FILE *sfp, int ofd)
288 {
289   size_t i;
290   size_t maxbytes = 0;
291   char *buffer;
292
293   for (i = 0; i < sparse_map_size; i++)
294     if (maxbytes < sparse_map[i].numbytes)
295       maxbytes = sparse_map[i].numbytes;
296   
297   for (buffer = malloc (maxbytes); !buffer; maxbytes /= 2)
298     if (maxbytes == 0)
299       die (1, "not enough memory");
300   
301   for (i = 0; i < sparse_map_size; i++)
302     {
303       size_t size = sparse_map[i].numbytes;
304
305       if (size == 0)
306         ftruncate (ofd, sparse_map[i].offset);
307       else
308         {
309           lseek (ofd, sparse_map[i].offset, SEEK_SET);
310           while (size)
311             {
312               size_t rdsize = (size < maxbytes) ? size : maxbytes;
313               if (rdsize != fread (buffer, 1, rdsize, sfp))
314                 die (1, "read error (%d)", errno);
315               if (rdsize != write (ofd, buffer, rdsize))
316                 die (1, "write error (%d)", errno);
317               size -= rdsize;
318             }
319         }
320     }
321   free (buffer);
322 }
323
324 void
325 usage (int code)
326 {
327   printf ("Usage: %s [OPTIONS] infile [outfile]\n", progname);
328   printf ("%s: expand sparse files extracted from GNU archives\n",
329           progname);
330   printf ("\nOPTIONS are:\n\n");
331   printf ("  -h           Display this help list\n");
332   printf ("  -n           Dry run: do nothing, print what would have been done\n");
333   printf ("  -v           Increase verbosity level\n");
334   printf ("  -x FILE      Parse extended header FILE\n\n");
335
336   exit (code);
337 }
338
339 void
340 guess_outname (char *name)
341 {
342   char *p;
343   char *s;
344   
345   if (name[0] == '.' && name[1] == '/')
346     name += 2;
347
348   p = name + strlen (name) - 1;
349   s = NULL;
350       
351   for (; p > name && *p != '/'; p--)
352     ;
353   if (*p == '/')
354     s = p + 1;
355   if (p != name)
356     {
357       for (p--; p > name && *p != '/'; p--)
358         ;
359     }
360   
361   if (*p != '/')
362     {
363       if (s)
364         outname = s;
365       else
366         {
367           outname = emalloc (4 + strlen (name));
368           strcpy (outname, "../");
369           strcpy (outname + 3, name);
370         }
371     }
372   else
373     {
374       size_t len = p - name + 1;
375       outname = emalloc (len + strlen (s) + 1);
376       memcpy (outname, name, len);
377       strcpy (outname + len, s);
378     }
379 }
380
381 int
382 main (int argc, char **argv)
383 {
384   int c;
385   int dry_run = 0;
386   char *xheader_file = NULL;
387   char *inname;
388   FILE *ifp;
389   struct stat st;
390   int ofd;
391   
392   progname = argv[0];
393   while ((c = getopt (argc, argv, "hnvx:")) != EOF)
394     {
395       switch (c)
396         {
397         case 'h':
398           usage (0);
399           break;
400
401         case 'x':
402           xheader_file = optarg;
403           break;
404
405         case 'n':
406           dry_run = 1;
407         case 'v':
408           verbose++;
409           break;
410           
411         default:
412           exit (1);
413         }
414     }
415
416   argc -= optind;
417   argv += optind;
418
419   if (argc == 0 || argc > 2)
420     usage (1);
421
422   if (xheader_file)
423     read_xheader (xheader_file);
424
425   inname = argv[0];
426   if (argv[1])
427     outname = argv[1];
428
429   if (stat (inname, &st))
430     die (1, "cannot stat %s (%d)", inname, errno);
431   
432   ifp = fopen (inname, "r");
433   if (ifp == NULL)
434     die (1, "cannot open file %s (%d)", inname, errno);
435   
436   if (!xheader_file || version_major == 1)
437     read_map (ifp);
438
439   if (!outname)
440     guess_outname (inname);
441   
442   ofd = open (outname, O_RDWR|O_CREAT|O_TRUNC, st.st_mode);
443   if (ofd == -1)
444     die (1, "cannot open file %s (%d)", outname, errno);
445
446   if (verbose)
447     printf ("Expanding file `%s' to `%s'\n", inname, outname);
448
449   if (dry_run)
450     {
451       printf ("Finished dry run\n");
452       return 0;
453     }
454   
455   expand_sparse (ifp, ofd);
456
457   fclose (ifp);
458   close (ofd);
459
460   if (verbose)
461     printf ("Done\n");
462   
463   if (outsize)
464     {
465       if (stat (outname, &st))
466         die (1, "cannot stat output file %s (%d)", outname, errno);
467       if (st.st_size != outsize)
468         die (1, "expanded file has wrong size");
469     }
470   
471   return 0;
472 }
473