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