diff --git a/scripts/xsparse.c b/scripts/xsparse.c new file mode 100644 index 00000000..2728bbce --- /dev/null +++ b/scripts/xsparse.c @@ -0,0 +1,469 @@ +/* xsparse - expands compressed sparse file images extracted from GNU tar + archives. + + Copyright (C) 2006 Free Software Foundation, Inc. + + Written by Sergey Poznyakoff + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Bound on length of the string representing an off_t. + See INT_STRLEN_BOUND in intprops.h for explanation */ +#define OFF_T_STRLEN_BOUND ((sizeof (off_t) * CHAR_BIT) * 146 / 485 + 1) +#define OFF_T_STRSIZE_BOUND (OFF_T_STRLEN_BOUND+1) + +#define BLOCKSIZE 512 + +struct sp_array +{ + off_t offset; + size_t numbytes; +}; + +char *progname; +int verbose; + +void +die (int code, char *fmt, ...) +{ + va_list ap; + + fprintf (stderr, "%s: ", progname); + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fprintf (stderr, "\n"); + exit (code); +} + +void * +emalloc (size_t size) +{ + char *p = malloc (size); + if (!p) + die (1, "not enough memory"); + return p; +} + +off_t +string_to_off (char *p, char **endp) +{ + off_t v = 0; + + for (; *p; p++) + { + int digit = *p - '0'; + off_t x = v * 10; + if (9 < (unsigned) digit) + { + if (endp) + { + *endp = p; + break; + } + die (1, "number parse error near %s", p); + } + else if (x / 10 != v) + die (1, "number out of allowed range, near %s", p); + v = x + digit; + if (v < 0) + die (1, "negative number"); + } + if (endp) + *endp = p; + return v; +} + +size_t +string_to_size (char *p, char **endp) +{ + off_t v = string_to_off (p, endp); + size_t ret = v; + if (ret != v) + die (1, "number too big"); + return ret; +} + +size_t sparse_map_size; +struct sp_array *sparse_map; + +void +get_line (char *s, int size, FILE *stream) +{ + char *p = fgets (s, size, stream); + size_t len; + + if (!p) + die (1, "unexpected end of file"); + len = strlen (p); + if (s[len - 1] != '\n') + die (1, "buffer overflow"); + s[len - 1] = 0; +} + +int +get_var (FILE *fp, char **name, char **value) +{ + static char *buffer; + static size_t bufsize = OFF_T_STRSIZE_BOUND; + char *p, *q; + + buffer = emalloc (bufsize); + do + { + size_t len, s; + + if (!fgets (buffer, bufsize, fp)) + return 0; + len = strlen (buffer); + if (len == 0) + return 0; + + s = string_to_size (buffer, &p); + if (*p != ' ') + die (1, "malformed header: expected space but found %s", p); + if (buffer[len-1] != '\n') + { + if (bufsize < s + 1) + { + bufsize = s + 1; + buffer = realloc (buffer, bufsize); + if (!buffer) + die (1, "not enough memory"); + } + if (!fgets (buffer + len, s - len + 1, fp)) + die (1, "unexpected end of file or read error"); + } + p++; + } + while (memcmp (p, "GNU.sparse.", 11)); + + p += 11; + q = strchr (p, '='); + if (!q) + die (1, "malformed header: expected `=' not found"); + *q++ = 0; + q[strlen (q) - 1] = 0; + *name = p; + *value = q; + return 1; +} + +char *outname; +off_t outsize; +unsigned version_major; +unsigned version_minor; + +void +read_xheader (char *name) +{ + char *kw, *val; + FILE *fp = fopen (name, "r"); + char *expect = NULL; + size_t i = 0; + + if (verbose) + printf ("Reading extended header file\n"); + + while (get_var (fp, &kw, &val)) + { + if (verbose) + printf ("Found variable GNU.sparse.%s = %s\n", kw, val); + + if (expect && strcmp (kw, expect)) + die (1, "bad keyword sequence: expected `%s' but found `%s'", + expect, kw); + expect = NULL; + if (strcmp (kw, "name") == 0) + { + outname = emalloc (strlen (val) + 1); + strcpy (outname, val); + } + else if (strcmp (kw, "major") == 0) + { + version_major = string_to_size (val, NULL); + } + else if (strcmp (kw, "minor") == 0) + { + version_minor = string_to_size (val, NULL); + } + else if (strcmp (kw, "realsize") == 0 + || strcmp (kw, "size") == 0) + { + outsize = string_to_off (val, NULL); + } + else if (strcmp (kw, "numblocks") == 0) + { + sparse_map_size = string_to_size (val, NULL); + sparse_map = emalloc (sparse_map_size * sizeof *sparse_map); + } + else if (strcmp (kw, "offset") == 0) + { + sparse_map[i].offset = string_to_off (val, NULL); + expect = "numbytes"; + } + else if (strcmp (kw, "numbytes") == 0) + { + sparse_map[i++].numbytes = string_to_size (val, NULL); + } + else if (strcmp (kw, "map") == 0) + { + for (i = 0; i < sparse_map_size; i++) + { + sparse_map[i].offset = string_to_off (val, &val); + if (*val != ',') + die (1, "bad GNU.sparse.map: expected `,' but found `%c'", + *val); + sparse_map[i].numbytes = string_to_size (val+1, &val); + if (*val != ',') + { + if (!(*val == 0 && i == sparse_map_size-1)) + die (1, "bad GNU.sparse.map: expected `,' but found `%c'", + *val); + } + else + val++; + } + if (*val) + die (1, "bad GNU.sparse.map: garbage at the end"); + } + } + if (expect) + die (1, "bad keyword sequence: expected `%s' not found", expect); + if (version_major == 0 && sparse_map_size == 0) + die (1, "size of the sparse map unknown"); + if (i != sparse_map_size) + die (1, "not all sparse entries supplied"); + fclose (fp); +} + +void +read_map (FILE *ifp) +{ + size_t i; + char nbuf[OFF_T_STRSIZE_BOUND]; + + if (verbose) + printf ("Reading v.1.0 sparse map\n"); + + get_line (nbuf, sizeof nbuf, ifp); + sparse_map_size = string_to_size (nbuf, NULL); + sparse_map = emalloc (sparse_map_size * sizeof *sparse_map); + + for (i = 0; i < sparse_map_size; i++) + { + get_line (nbuf, sizeof nbuf, ifp); + sparse_map[i].offset = string_to_off (nbuf, NULL); + get_line (nbuf, sizeof nbuf, ifp); + sparse_map[i].numbytes = string_to_size (nbuf, NULL); + } + + fseek (ifp, ((ftell (ifp) + BLOCKSIZE - 1) / BLOCKSIZE) * BLOCKSIZE, + SEEK_SET); +} + +void +expand_sparse (FILE *sfp, int ofd) +{ + size_t i; + size_t maxbytes = 0; + char *buffer; + + for (i = 0; i < sparse_map_size; i++) + if (maxbytes < sparse_map[i].numbytes) + maxbytes = sparse_map[i].numbytes; + + for (buffer = malloc (maxbytes); !buffer; maxbytes /= 2) + if (maxbytes == 0) + die (1, "not enough memory"); + + for (i = 0; i < sparse_map_size; i++) + { + size_t size = sparse_map[i].numbytes; + + lseek (ofd, sparse_map[i].offset, SEEK_SET); + while (size) + { + size_t rdsize = (size < maxbytes) ? size : maxbytes; + if (rdsize != fread (buffer, 1, rdsize, sfp)) + die (1, "read error (%d)", errno); + if (rdsize != write (ofd, buffer, rdsize)) + die (1, "write error (%d)", errno); + size -= rdsize; + } + } + free (buffer); +} + +void +usage (int code) +{ + printf ("Usage: %s [OPTIONS] infile [outfile]\n", progname); + printf ("%s: expand sparse files extracted from GNU archives\n", + progname); + printf ("\nOPTIONS are:\n\n"); + printf (" -h Display this help list\n"); + printf (" -n Dry run: do nothing, print what would have been done\n"); + printf (" -v Increase verbosity level\n"); + printf (" -x FILE Parse extended header FILE\n\n"); + + exit (code); +} + +void +guess_outname (char *name) +{ + char *p; + char *s; + + if (name[0] == '.' && name[1] == '/') + name += 2; + + p = name + strlen (name) - 1; + s = NULL; + + for (; p > name && *p != '/'; p--) + ; + if (*p == '/') + s = p + 1; + if (p != name) + { + for (p--; p > name && *p != '/'; p--) + ; + } + + if (*p != '/') + { + if (s) + outname = s; + else + { + outname = emalloc (4 + strlen (name)); + strcpy (outname, "../"); + strcpy (outname + 3, name); + } + } + else + { + size_t len = p - name + 1; + outname = emalloc (len + strlen (s) + 1); + memcpy (outname, name, len); + strcpy (outname + len, s); + } +} + +int +main (int argc, char **argv) +{ + int c; + int dry_run = 0; + char *xheader_file = NULL; + char *inname; + FILE *ifp; + struct stat st; + int ofd; + + progname = argv[0]; + while ((c = getopt (argc, argv, "hnvx:")) != EOF) + { + switch (c) + { + case 'h': + usage (0); + break; + + case 'x': + xheader_file = optarg; + break; + + case 'n': + dry_run = 1; + case 'v': + verbose++; + break; + + default: + exit (1); + } + } + + argc -= optind; + argv += optind; + + if (argc == 0 || argc > 2) + usage (1); + + if (xheader_file) + read_xheader (xheader_file); + + inname = argv[0]; + if (argv[1]) + outname = argv[1]; + + if (stat (inname, &st)) + die (1, "cannot stat %s (%d)", inname, errno); + + ifp = fopen (inname, "r"); + if (ifp == NULL) + die (1, "cannot open file %s (%d)", inname, errno); + + if (!xheader_file || version_major == 1) + read_map (ifp); + + if (!outname) + guess_outname (inname); + + ofd = open (outname, O_RDWR|O_CREAT|O_TRUNC, st.st_mode); + if (ofd == -1) + die (1, "cannot open file %s (%d)", outname, errno); + + if (verbose) + printf ("Expanding file `%s' to `%s'\n", inname, outname); + + if (dry_run) + { + printf ("Finished dry run\n"); + return 0; + } + + expand_sparse (ifp, ofd); + + fclose (ifp); + close (ofd); + + if (verbose) + printf ("Done\n"); + + if (outsize) + { + if (stat (outname, &st)) + die (1, "cannot stat output file %s (%d)", outname, errno); + if (st.st_size != outsize) + die (1, "expanded file has wrong size"); + } + + return 0; +} +