From: Sergey Poznyakoff Date: Sun, 25 Jun 2006 12:44:04 +0000 (+0000) Subject: A sample utility to expand sparse files X-Git-Url: https://git.brokenzipper.com/gitweb?a=commitdiff_plain;h=bf5ba3dbefa4878d712b83ea16c95220c2197417;p=chaz%2Ftar A sample utility to expand sparse files extracted by third-party tars. It is not meant to be installed nor to be included in the distribution. It is here, so that it can be obtained either from CVS or from the tar web site. --- diff --git a/scripts/xsparse.c b/scripts/xsparse.c new file mode 100644 index 0000000..2728bbc --- /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; +} +