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