/* $Id$ */ /* ----------------------------------------------------------------------- * * * Copyright 2001 H. Peter Anvin - All Rights Reserved * * 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, Inc., 675 Mass Ave, Cambridge MA 02139, * USA; either version 2 of the License, or (at your option) any later * version; incorporated herein by reference. * * ----------------------------------------------------------------------- */ /* * mkzffile.c * * - Generate block-compression of files for use with * the "ZF" extension to the iso9660/RockRidge filesystem. * * The file compression technique used is the "deflate" * algorithm used by the zlib library; each block must have a * valid (12-byte) zlib header. In addition, the file itself * has the following structure: * * Byte offset iso9660 type Contents * 0 (8 bytes) Magic number (37 E4 53 96 C9 DB D6 07) * 8 7.3.1 Uncompressed file size * 12 7.1.1 header_size >> 2 (currently 4) * 13 7.1.1 log2(block_size) * 14 (2 bytes) Reserved, must be zero * * The header may get expanded in the future, at which point the * header size field will be used to increase the space for the * header. * * All implementations are required to support a block_size of 32K * (byte 13 == 15). * * Note that bytes 12 and 13 and the uncompressed length are also * present in the ZF record; THE TWO MUST BOTH BE CONSISTENT AND * CORRECT. * * Given the uncompressed size, block_size, and header_size: * * Nblocks := ceil(size/block_size) * * After the header follow (nblock+1) 32-bit pointers, recorded as * iso9660 7.3.1 (littleendian); each indicate the byte offset (from * the start of the file) to one block and the first byte beyond the * end of the previous block; the first pointer thus point to the * start of the data area and the last pointer to the first byte * beyond it: * * block_no := floor(byte_offset/block_size) * * block_start := read_pointer_731( (header_size+block_no)*4 ) * block_end := read_pointer_731( (header_size+block_no+1)*4 ) * * The block data is compressed according to "zlib". */ #include "mkzftree.h" /* Must be included first! */ #include #include #include #include #include #include #include #include #include #ifdef HAVE_GETOPT_H #include #endif #include "version.h" /* Command line options */ struct cmdline_options opt = { 0, /* Force compression */ 9, /* Compression level */ 0, /* Parallelism (0 = strictly serial) */ 0, /* One filesystem only */ 0, /* One directory only */ 1, /* Create stub directories */ 0, /* Root may be a file */ 0, /* Be paranoid about metadata */ default_verbosity, /* Default verbosity */ block_compress_file /* Default transformation function */ }; /* Program name */ const char *program; /* Long options */ #define OPTSTRING "fz:up:xXC:lLFvqV:hws" #ifdef HAVE_GETOPT_LONG const struct option long_options[] = { { "force", 0, 0, 'f' }, { "level", 1, 0, 'z' }, { "uncompress", 0, 0, 'u' }, { "parallelism", 1, 0, 'p' }, { "one-filesystem", 0, 0, 'x' }, { "strict-one-filesystem", 0, 0, 'X' }, { "crib-tree", 1, 0, 'C' }, { "local", 0, 0, 'l' }, { "strict-local", 0, 0, 'L' }, { "file", 0, 0, 'F' }, { "verbose", 0, 0, 'v' }, { "quiet", 0, 0, 'q' }, { "verbosity", 1, 0, 'V' }, { "help", 0, 0, 'h' }, { "version", 0, 0, 'w' }, { "sloppy", 0, 0, 's' }, { 0, 0, 0, 0 } }; #define LO(X) X #else #define getopt_long(C,V,O,L,I) getopt(C,V,O) #define LO(X) #endif static void usage(enum verbosity level, int err) { message(level, "zisofs-tools " ZISOFS_TOOLS_VERSION "\n" "Usage: %s [options] intree outtree\n" LO(" --force ")" -f Always compress, even if result is larger\n" LO(" --level # ")" -z # Set compression level (1-9)\n" LO(" --uncompress ")" -u Uncompress an already compressed tree\n" LO(" --parallelism # ")" -p # Process up to # files in parallel\n" LO(" --one-filesystem ")" -x Do not cross filesystem boundaries\n" LO(" --strict-one-filesystem")" -X Same as -x, but don't create stubs dirs\n" LO(" --crib-tree ")" -C Steal \"crib\" files from an old tree\n" LO(" --local ")" -l Do not recurse into subdirectoires\n" LO(" --strict-local ")" -L Same as -l, but don't create stubs dirs\n" LO(" --file ")" -F Operate possibly on a single file\n" LO(" --sloppy ")" -s Don't abort if metadata cannot be set\n" LO(" --verbose ")" -v Increase message verbosity\n" LO(" --verbosity # ")" -V # Set message verbosity to # (default = %d)\n" LO(" --quiet ")" -q No messages, not even errors (-V 0)\n" LO(" --help ")" -h Display this message\n" LO(" --version ")" -w Display the program version\n" ,program, (int)default_verbosity); exit(err); } static int opt_atoi(const char *str) { char *endptr; long out; out = strtol(str, &endptr, 10); if ( *endptr ) usage(vl_error, EX_USAGE); return (int)out; } int main(int argc, char *argv[]) { const char *in, *out, *crib = NULL; struct stat st; int optch, err; program = argv[0]; while ( (optch = getopt_long(argc, argv, OPTSTRING, long_options, NULL)) != EOF ) { switch(optch) { case 'f': opt.force = 1; /* Always compress */ break; case 'z': opt.level = opt_atoi(optarg); if ( opt.level < 1 || opt.level > 9 ) { message(vl_error, "%s: invalid compression level: %d\n", program, optarg); exit(EX_USAGE); } break; case 'v': opt.verbosity++; break; case 'V': opt.verbosity = opt_atoi(optarg); break; case 'q': opt.verbosity = vl_quiet; break; case 'u': opt.munger = block_uncompress_file; break; case 'C': crib = optarg; break; case 'p': opt.parallel = opt_atoi(optarg); break; case 'x': opt.onefs = 1; opt.do_mkdir = 1; break; case 'l': opt.onedir = 1; opt.do_mkdir = 1; break; case 'X': opt.onefs = 1; opt.do_mkdir = 0; break; case 'L': opt.onedir = 1; opt.do_mkdir = 0; break; case 'F': opt.file_root = 1; break; case 's': opt.sloppy = 1; break; case 'h': usage(vl_quiet, 0); break; case 'w': message(vl_quiet, "zisofs-tools " ZISOFS_TOOLS_VERSION "\n"); exit(0); default: usage(vl_error, EX_USAGE); break; } } if ( (argc-optind) != 2 ) usage(vl_error, EX_USAGE); in = argv[optind]; /* Input tree */ out = argv[optind+1]; /* Output tree */ umask(077); if ( opt.file_root ) { if ( lstat(in, &st) ) { message(vl_error, "%s: %s: %s\n", program, in, strerror(errno)); exit(EX_NOINPUT); } err = munge_entry(in, out, crib, NULL); } else { /* Special case: we use stat() for the root, not lstat() */ if ( stat(in, &st) ) { message(vl_error, "%s: %s: %s\n", program, in, strerror(errno)); exit(EX_NOINPUT); } if ( !S_ISDIR(st.st_mode) ) { message(vl_error, "%s: %s: Not a directory\n", program, in); exit(EX_DATAERR); } err = munge_tree(in, out, crib); } wait_for_all_workers(); if ( err ) exit(err); if ( !opt.file_root ) { if ( chown(out, st.st_uid, st.st_gid) && !opt.sloppy ) { message(vl_error, "%s: %s: %s", program, out, strerror(errno)); err = EX_CANTCREAT; } if ( chmod(out, st.st_mode) && !opt.sloppy && !err ) { message(vl_error, "%s: %s: %s", program, out, strerror(errno)); err = EX_CANTCREAT; } if ( copytime(out, &st) && !opt.sloppy && !err ) { message(vl_error, "%s: %s: %s", program, out, strerror(errno)); err = EX_CANTCREAT; } } return err; }