/* Copyright (C) 2007-2012 Proxmox Server Solutions GmbH Copyright: vzdump is under GNU GPL, the GNU General Public License. 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; version 2 dated June, 1991. 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 St, Fifth Floor, Boston, MA 02110-1301, USA. Author: Dietmar Maurer NOTE: the tar specific code is copied from the GNU tar package (just slighly modified to fit our needs). */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.c" #define BLOCKSIZE 512 #define BUFFER_BLOCKS 32 static char *outname; struct writebuffer { int fd; char buffer[BUFFER_BLOCKS*BLOCKSIZE]; size_t bpos; size_t total; }; /* OLDGNU_MAGIC uses both magic and version fields, which are contiguous. */ #define OLDGNU_MAGIC "ustar " /* 7 chars and a null */ struct posix_header { /* byte offset */ char name[100]; /* 0 */ char mode[8]; /* 100 */ char uid[8]; /* 108 */ char gid[8]; /* 116 */ char size[12]; /* 124 */ char mtime[12]; /* 136 */ char chksum[8]; /* 148 */ char typeflag; /* 156 */ char linkname[100]; /* 157 */ char magic[6]; /* 257 */ char version[2]; /* 263 */ char uname[32]; /* 265 */ char gname[32]; /* 297 */ char devmajor[8]; /* 329 */ char devminor[8]; /* 337 */ char prefix[155]; /* 345 */ /* 500 */ }; struct sparse { /* byte offset */ char offset[12]; /* 0 */ char numbytes[12]; /* 12 */ /* 24 */ }; struct oldgnu_header { /* byte offset */ char unused_pad1[345]; /* 0 */ char atime[12]; /* 345 Incr. archive: atime of the file */ char ctime[12]; /* 357 Incr. archive: ctime of the file */ char offset[12]; /* 369 Multivolume archive: the offset of the start of this volume */ char longnames[4]; /* 381 Not used */ char unused_pad2; /* 385 */ struct sparse sp[4]; /* 386 */ char isextended; /* 482 Sparse file: Extension sparse header follows */ char realsize[12]; /* 483 Sparse file: Real size*/ /* 495 */ }; struct sparse_header { /* byte offset */ struct sparse sp[21]; /* 0 */ char isextended; /* 504 */ /* 505 */ }; union block { char buffer[BLOCKSIZE]; struct posix_header header; struct oldgnu_header oldgnu_header; struct sparse_header sparse_header; }; struct sp_entry { off_t offset; size_t bytes; }; struct sp_array { size_t real_size; size_t effective_size; size_t avail; size_t size; struct sp_entry *map; }; static void cleanup (void) { if (outname) unlink (outname); } void term_handler() { fprintf (stderr, "received signal - terminate process\n"); exit(-1); } struct sp_array* sparray_new (void) { struct sp_array *ma = malloc (sizeof (struct sp_array)); if (!ma) { fprintf (stderr, "ERROR: memory allocation failure\n"); exit (-1); } ma->real_size = 0; ma->effective_size = 0; ma->avail = 0; ma->size = 1024; ma->map = malloc (ma->size * sizeof (struct sp_entry)); if (!ma->map) { fprintf (stderr, "ERROR: memory allocation failure\n"); exit (-1); } return ma; } void sparray_resize (struct sp_array *ma) { ma->size += 1024; if (!(ma->map = realloc (ma->map, ma->size * sizeof (struct sp_entry)))) { fprintf (stderr, "ERROR: memory allocation failure\n"); exit (-1); } } void sparray_add (struct sp_array *ma, off_t offset, size_t bytes) { if (ma->avail == ma->size) { sparray_resize(ma); } ma->map[ma->avail].offset = offset; ma->map[ma->avail].bytes = bytes; ma->avail++; } static void to_base256 (uintmax_t value, char *where, size_t size) { uintmax_t v = value; size_t i = size - 1; where[0] = 1 << 7; do { where[i--] = v & ((1 << 8) - 1); v >>= 8; } while (i); } static void to_octal (uintmax_t value, char *where, size_t size) { uintmax_t v = value; size_t i = size - 1; where[i] = '\0'; do { where[--i] = '0' + (v & ((1 << 3) - 1)); v >>= 3; } while (i); } /* The maximum uintmax_t value that can be represented with DIGITS digits, assuming that each digit is BITS_PER_DIGIT wide. */ #define MAX_VAL_WITH_DIGITS(digits, bits_per_digit) \ ((digits) * (bits_per_digit) < sizeof (uintmax_t) * 8 \ ? ((uintmax_t) 1 << ((digits) * (bits_per_digit))) - 1 \ : (uintmax_t) -1) /* The maximum uintmax_t value that can be represented with octal digits and a trailing NUL in BUFFER. */ #define MAX_OCTAL_VAL(buffer) MAX_VAL_WITH_DIGITS (sizeof (buffer) - 1, 3) void off12_to_chars (char *p, off_t v) { if (v < 0) { fprintf (stderr, "ERROR: internal error - got negative offset\n"); exit (-1); } uintmax_t value = (uintmax_t) v; if (value <= MAX_VAL_WITH_DIGITS (11, 3)) { to_octal (value, p, 12); } else { to_base256 (value, p, 12); } } char * buffer_block(struct writebuffer *wbuf) { size_t space = sizeof (wbuf->buffer) - wbuf->bpos; char *blk; if (space >= BLOCKSIZE) { blk = wbuf->buffer + wbuf->bpos; wbuf->bpos += BLOCKSIZE; } else { full_write (wbuf->fd, wbuf->buffer, wbuf->bpos); wbuf->total += wbuf->bpos; wbuf->bpos = BLOCKSIZE; blk = wbuf->buffer; } return blk; } struct writebuffer* buffer_new(int fd) { struct writebuffer *wbuf = calloc (1, sizeof (struct writebuffer)); if (!wbuf) { fprintf (stderr, "ERROR: memory allocation failure\n"); exit (-1); } wbuf->fd = fd; return wbuf; } void buffer_flush(struct writebuffer *wbuf) { full_write (wbuf->fd, wbuf->buffer, wbuf->bpos); wbuf->total += wbuf->bpos; wbuf->bpos = 0; } void dump_header (struct writebuffer *wbuf, const char *filename, time_t mtime, struct sp_array *ma) { union block *blk = (union block *)buffer_block (wbuf); memset (blk->buffer, 0, BLOCKSIZE); if (strlen(filename)>98) { fprintf (stderr, "ERROR: filename '%s' too long\n", filename); exit (-1); } strncpy (blk->header.name, filename, 100); sprintf (blk->header.mode, "%07o", 0644); sprintf (blk->header.uid, "%07o", 0); sprintf (blk->header.gid, "%07o", 0); off12_to_chars (blk->header.mtime, mtime); memcpy (blk->header.chksum, " ", 8); blk->header.typeflag = ma->avail ? 'S' : '0'; sprintf (blk->header.magic, "%s", OLDGNU_MAGIC); sprintf (blk->header.uname, "%s", "root"); sprintf (blk->header.gname, "%s", "root"); size_t ind = 0; if (ind < ma->avail) { size_t i; for (i = 0;i < 4 && ind < ma->avail; i++, ind++) { off12_to_chars (blk->oldgnu_header.sp[i].offset, ma->map[ind].offset); off12_to_chars (blk->oldgnu_header.sp[i].numbytes, ma->map[ind].bytes); } } if (ma->avail > 4) blk->oldgnu_header.isextended = 1; off12_to_chars (blk->header.size, ma->effective_size); off12_to_chars (blk->oldgnu_header.realsize, ma->real_size); int sum = 0; char *p = blk->buffer; int i; for (i = BLOCKSIZE; i-- != 0; ) sum += 0xFF & *p++; sprintf (blk->header.chksum, "%6o", sum); while (ind < ma->avail) { blk = (union block *)buffer_block (wbuf); memset (blk->buffer, 0, BLOCKSIZE); size_t i; for (i = 0;i < 21 && ind < ma->avail; i++, ind++) { off12_to_chars (blk->sparse_header.sp[i].offset, ma->map[ind].offset); off12_to_chars (blk->sparse_header.sp[i].numbytes, ma->map[ind].bytes); } if (ind < ma->avail) blk->sparse_header.isextended = 1; } } int scan_sparse_file (int fd, struct sp_array *ma) { char buffer[BLOCKSIZE]; size_t count; off_t offset = 0; off_t file_size = 0; size_t sp_bytes = 0; off_t sp_offset = 0; if (lseek (fd, 0, SEEK_SET) < 0) return 0; while ((count = full_read (fd, buffer, sizeof (buffer))) > 0) { if (block_is_zero (buffer, count)) { if (sp_bytes) { sparray_add (ma, sp_offset, sp_bytes); sp_bytes = 0; } } else { file_size += count; if (!sp_bytes) sp_offset = offset; sp_bytes += count; } offset += count; } if (sp_bytes == 0) sp_offset = offset; sparray_add (ma, sp_offset, sp_bytes); ma->real_size = offset; ma->effective_size = file_size; return 1; } int dump_sparse_file (int fd, struct writebuffer *wbuf, struct sp_array *ma) { if (lseek (fd, 0, SEEK_SET) < 0) return 0; int i; size_t dumped_size = 0; for (i = 0; i < ma->avail; i++) { struct sp_entry *e = &ma->map[i]; if (lseek (fd, e->offset, SEEK_SET) < 0) return 0; off_t bytes_left = e->bytes; while (bytes_left > 0) { size_t bufsize = (bytes_left > BLOCKSIZE) ? BLOCKSIZE : bytes_left; size_t bytes_read; char *blkbuf = buffer_block (wbuf); if ((bytes_read = full_read (fd, blkbuf, bufsize)) < 0) { return 0; } if (!bytes_read) { fprintf (stderr, "ERROR: got unexpected EOF\n"); return 0; } memset (blkbuf + bytes_read, 0, BLOCKSIZE - bytes_read); dumped_size += bytes_read; bytes_left -= bytes_read; } } return 1; } int main (int argc, char **argv) { struct sigaction sa; int sparse = 0; while (1) { int option_index = 0; static struct option long_options[] = { {"sparse", 0, 0, 's'}, {"output", 1, 0, 'o'}, {0, 0, 0, 0} }; char c = getopt_long (argc, argv, "so:", long_options, &option_index); if (c == -1) break; switch (c) { case 's': sparse = 1; break; case 'o': outname = optarg; break; default: fprintf (stderr, "?? getopt returned character code 0%o ??\n", c); exit (-1); } } int numargs = argc - optind; if (numargs <= 0 || (numargs % 2)) { fprintf (stderr, "wrong number of arguments\n"); exit (-1); } time_t starttime = time(NULL); int outfd; if (outname) { if ((outfd = open(outname, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1) { fprintf (stderr, "unable to open archive '%s' - %s\n", outname, strerror (errno)); exit (-1); } atexit(cleanup); } else { outfd = fileno (stdout); } setsig(&sa, SIGINT, term_handler, SA_RESTART); setsig(&sa, SIGQUIT, term_handler, SA_RESTART); setsig(&sa, SIGTERM, term_handler, SA_RESTART); setsig(&sa, SIGPIPE, term_handler, SA_RESTART); int saved_optind = optind; while (optind < argc) { char *source = argv[optind]; optind += 2; struct stat fs; if (stat (source, &fs) != 0) { fprintf (stderr, "unable to read '%s' - %s\n", source, strerror (errno)); exit (-1); } if (!(S_ISREG(fs.st_mode) || S_ISBLK(fs.st_mode))) { fprintf (stderr, "unable to read '%s' - not a file or block device\n", source); exit (-1); } } optind = saved_optind; struct writebuffer *wbuf = buffer_new (outfd); while (optind < argc) { char *source = argv[optind++]; char *archivename = argv[optind++]; int fd; fprintf (stderr, "adding '%s' to archive ('%s')\n", source, archivename); if ((fd = open(source, O_RDONLY)) == -1) { fprintf (stderr, "unable to open '%s' - %s\n", source, strerror (errno)); exit (-1); } struct stat fs; if (fstat (fd, &fs) != 0) { fprintf (stderr, "unable to stat '%s' - %s\n", source, strerror (errno)); exit (-1); } time_t ctime = fs.st_mtime; struct sp_array *ma = sparray_new(); if (sparse) { if (!scan_sparse_file (fd, ma)) { fprintf (stderr, "scanning '%s' failed\n", source); exit (-1); } } else { off_t file_size = fs.st_size; sparray_add (ma, 0, file_size); ma->real_size = file_size; ma->effective_size = file_size; } dump_header (wbuf, archivename, ctime, ma); if (!dump_sparse_file (fd, wbuf, ma)) { fprintf (stderr, "writing '%s' to archive failed\n", source); exit (-1); } free (ma); close (fd); } // write tar end char *buf = buffer_block (wbuf); memset (buf, 0, BLOCKSIZE); buf = buffer_block (wbuf); memset (buf, 0, BLOCKSIZE); buffer_flush (wbuf); close (outfd); time_t delay = time(NULL) - starttime; if (delay <= 0) delay = 1; fprintf (stderr, "Total bytes written: %zu (%.2f MiB/s)\n", wbuf->total, (wbuf->total/(1024*1024))/(float)delay); outname = NULL; exit (0); }