/* * dump2tar - tool to dump files and command output into a tar archive * * Command line interface * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #include #include #include #include "lib/util_opt.h" #include "lib/util_prg.h" #include "dump.h" #include "global.h" #include "idcache.h" #include "misc.h" #include "strarray.h" #define MIN_BUFFER_SIZE 4096 #define OPT_NOSHORT_BASE 256 #define OPT_DEREFERENCE (OPT_NOSHORT_BASE + 0) #define OPT_NORECURSION (OPT_NOSHORT_BASE + 1) #define OPT_EXCLUDETYPE (OPT_NOSHORT_BASE + 2) /* Program description */ static const struct util_prg dump2tar_prg = { .desc = "Use dump2tar to create a tar archive from the contents " "of arbitrary files.\nIt works even when the size of actual " "file content is not known beforehand,\nsuch as with FIFOs, " "character devices or certain Linux debugfs or sysfs files.\n" "\nYou can also add files under different names and add " "command output using the\nformat described in section SPECS " "below. When no additional options are\nspecified, the " "resulting archive is written to the standard output stream\n" "in uncompressed tar format.", .args = "SPECS", .copyright_vec = { { "IBM Corp.", 2016, 2017 }, UTIL_PRG_COPYRIGHT_END }, }; /* Definition of command line options */ static struct util_opt dump2tar_opts[] = { UTIL_OPT_SECTION("OUTPUT OPTIONS"), { .option = { "output-file", required_argument, NULL, 'o' }, .argument = "FILE", .desc = "Write archive to FILE (default: standard output)", }, #ifdef HAVE_ZLIB { .option = { "gzip", no_argument, NULL, 'z' }, .desc = "Write a gzip compressed archive", }, #endif /* HAVE_ZLIB */ { .option = { "max-size", required_argument, NULL, 'm' }, .argument = "N", .desc = "Stop adding files when archive size exceeds N bytes", }, { .option = { "timeout", required_argument, NULL, 't' }, .argument = "SEC", .desc = "Stop adding files after SEC seconds", }, { .option = { "no-eof", no_argument, NULL, 131 }, .desc = "Do not write an end-of-file marker", .flags = UTIL_OPT_FLAG_NOSHORT, }, { .option = { "add-cmd-status", no_argument, NULL, 132 }, .desc = "Add status of commands as separate file", .flags = UTIL_OPT_FLAG_NOSHORT, }, { .option = { "append", no_argument, NULL, 133 }, .desc = "Append output to end of file", .flags = UTIL_OPT_FLAG_NOSHORT, }, UTIL_OPT_SECTION("INPUT OPTIONS"), { .option = { "files-from", required_argument, NULL, 'F' }, .argument = "FILE", .desc = "Read filenames from FILE (- for standard input)", }, { .option = { "ignore-failed-read", no_argument, NULL, 'i' }, .desc = "Continue after read errors", }, { .option = { "buffer-size", required_argument, NULL, 'b' }, .argument = "N", .desc = "Read data in chunks of N byte (default: 16384)", }, { .option = { "file-timeout", required_argument, NULL, 'T' }, .desc = "Stop reading file after SEC seconds", .argument = "SEC", }, { .option = { "file-max-size", required_argument, NULL, 'M' }, .argument = "N", .desc = "Stop reading file after N bytes", }, { .option = { "jobs", required_argument, NULL, 'j' }, .argument = "N", .desc = "Read N files in parallel (default: 1)", }, { .option = { "jobs-per-cpu", required_argument, NULL, 'J' }, .argument = "N", .desc = "Read N files per CPU in parallel", }, { .option = { "exclude", required_argument, NULL, 'x' }, .argument = "PATTERN", .desc = "Don't add files matching PATTERN", }, { .option = { "exclude-from", required_argument, NULL, 'X' }, .argument = "FILE", .desc = "Don't add files matching patterns in FILE", }, { .option = { "exclude-type", required_argument, NULL, OPT_EXCLUDETYPE }, .argument = "TYPE", .desc = "Don't add files of specified TYPE (one of: fdcbpls)", .flags = UTIL_OPT_FLAG_NOSHORT, }, { .option = { "dereference", no_argument, NULL, OPT_DEREFERENCE }, .desc = "Add link targets instead of links", .flags = UTIL_OPT_FLAG_NOSHORT, }, { .option = { "no-recursion", no_argument, NULL, OPT_NORECURSION }, .desc = "Don't add files from sub-directories", .flags = UTIL_OPT_FLAG_NOSHORT, }, UTIL_OPT_SECTION("MISC OPTIONS"), UTIL_OPT_HELP, UTIL_OPT_VERSION, { .option = { "verbose", no_argument, NULL, 'V' }, .desc = "Print additional informational output", }, { .option = { "quiet", no_argument, NULL, 'q' }, .desc = "Suppress printing of informational output", }, UTIL_OPT_END, }; /* Split buffer size specification in @arg into two numbers to be stored in * @from_ptr and @to_ptr. Return %EXIT_OK on success. */ static int parse_buffer_size(char *arg, size_t *from_ptr, size_t *to_ptr) { char *err; unsigned long from, to; if (!*arg) { mwarnx("Empty buffer size specified"); return EXIT_USAGE; } from = strtoul(arg, &err, 10); if (*err == '-') to = strtoul(err + 1, &err, 10); else to = *to_ptr; if (*err) { mwarnx("Invalid buffer size: %s", arg); return EXIT_USAGE; } if (from < MIN_BUFFER_SIZE || to < MIN_BUFFER_SIZE) { mwarnx("Buffer size too low (minimum %u)", MIN_BUFFER_SIZE); return EXIT_USAGE; } if (to < from) to = from; *from_ptr = from; *to_ptr = to; return EXIT_OK; } static void parse_and_add_spec(struct dump_opts *opts, const char *spec) { char *op, *s, *inname, *outname = NULL; bool is_cmd = false; s = mstrdup(spec); op = strstr(s, "|="); if (op) is_cmd = true; else op = strstr(s, ":="); if (op) { *op = 0; inname = op + 2; outname = s; } else { inname = s; } dump_opts_add_spec(opts, inname, outname, is_cmd); free(s); } static int add_specs_from_file(struct dump_opts *opts, const char *filename) { FILE *fd; char *line = NULL; size_t line_size; int rc = EXIT_RUNTIME; bool need_close = false, parse_spec = true; if (strcmp(filename, "-") == 0) fd = stdin; else { fd = fopen(filename, "r"); if (!fd) { mwarn("%s: Cannot open file", filename); goto out; } need_close = true; } while ((getline(&line, &line_size, fd) != -1)) { chomp(line, "\n"); if (line[0] == 0) continue; if (parse_spec && strcmp(line, "--") == 0) { /* After a line containing --, no more := or |= specs * are expected */ parse_spec = false; continue; } if (parse_spec) parse_and_add_spec(opts, line); else dump_opts_add_spec(opts, line, NULL, false); } if (ferror(fd)) mwarn("%s: Cannot read file", filename); else rc = EXIT_OK; out: if (need_close) fclose(fd); free(line); return rc; } static void print_help(void) { static const struct { const char *name; const char *desc; } specs[] = { { "PATH", "Add file or directory at PATH" }, { "NEWPATH:=PATH", "Add file or directory at PATH as NEWPATH" }, { "NEWPATH|=CMDLINE", "Add output of command line CMDLINE as " "NEWPATH" }, { NULL, NULL }, }; int i; util_prg_print_help(); printf("SPECS\n"); for (i = 0; specs[i].name; i++) util_opt_print_indented(specs[i].name, specs[i].desc); printf("\n"); util_opt_print_help(); } int main(int argc, char *argv[]) { int rc = EXIT_USAGE, opt; long i; struct dump_opts *opts; if (getenv("DUMP2TAR_DEBUG")) global_debug = true; util_prg_init(&dump2tar_prg); util_opt_init(dump2tar_opts, "-"); misc_init(); opts = dump_opts_new(); opterr = 0; while ((opt = util_opt_getopt_long(argc, argv)) != -1) { switch (opt) { case 'h': /* --help */ print_help(); rc = EXIT_OK; goto out; case 'v': /* --version */ util_prg_print_version(); rc = EXIT_OK; goto out; case 'V': /* --verbose */ global_verbose = true; global_quiet = false; opts->verbose = true; opts->quiet = false; break; case 'q': /* --quiet */ global_quiet = true; global_verbose = false; opts->quiet = true; opts->verbose = false; break; case 'i': /* --ignore-failed-read */ opts->ignore_failed_read = true; break; case 'j': /* --jobs N */ opts->jobs = atoi(optarg); if (opts->jobs < 1) { mwarnx("Invalid number of jobs: %s", optarg); goto out; } break; case 'J': /* --jobs-per-cpu N */ opts->jobs_per_cpu = atoi(optarg); if (opts->jobs_per_cpu < 1) { mwarnx("Invalid number of jobs: %s", optarg); goto out; } break; case 'b': /* --buffer-size N */ if (parse_buffer_size(optarg, &opts->read_chunk_size, &opts->max_buffer_size)) goto out; break; case 'x': /* --exclude PATTERN */ add_str_to_strarray(&opts->exclude, optarg); break; case 'X': /* --exclude-from FILE */ if (add_file_to_strarray(&opts->exclude, optarg)) goto out; break; case 'F': /* --files-from FILE */ if (add_specs_from_file(opts, optarg)) goto out; break; case 'o': /* --output-file FILE */ if (opts->output_file) { mwarnx("Output file specified multiple times"); goto out; } opts->output_file = optarg; break; case OPT_DEREFERENCE: /* --dereference */ opts->dereference = true; break; case OPT_NORECURSION: /* --no-recursion */ opts->recursive = false; break; case OPT_EXCLUDETYPE: /* --exclude-type TYPE */ for (i = 0; optarg[i]; i++) { if (dump_opts_set_type_excluded(opts, optarg[i])) break; } if (optarg[i]) { mwarnx("Unrecognized file type: %c", optarg[i]); goto out; } break; case 131: /* --no-eof */ opts->no_eof = true; break; case 132: /* --add-cmd-status */ opts->add_cmd_status = true; break; case 133: /* --append */ opts->append = true; break; case 't': /* --timeout VALUE */ opts->timeout = atoi(optarg); if (opts->timeout < 1) { mwarnx("Invalid timeout value: %s", optarg); goto out; } break; case 'T': /* --file-timeout VALUE */ opts->file_timeout = atoi(optarg); if (opts->file_timeout < 1) { mwarnx("Invalid timeout value: %s", optarg); goto out; } break; case 'm': /* --max-size N */ opts->max_size = atol(optarg); if (opts->max_size < 2) { mwarnx("Invalid maximum size: %s", optarg); goto out; } break; case 'M': /* --file-max-size N */ opts->file_max_size = atol(optarg); if (opts->file_max_size < 2) { mwarnx("Invalid maximum size: %s", optarg); goto out; } break; case 'z': /* --gzip */ opts->gzip = true; break; case 1: /* Filename specification or unrecognized option */ if (optarg[0] == '-') { mwarnx("Invalid option '%s'", optarg); goto out; } parse_and_add_spec(opts, optarg); break; case '?': /* Unrecognized option */ if (optopt) mwarnx("Invalid option '-%c'", optopt); else mwarnx("Invalid option '%s'", argv[optind - 1]); goto out; case ':': /* Missing argument */ mwarnx("Option '%s' requires an argument", argv[optind - 1]); goto out; default: break; } } if (optind >= argc && opts->num_specs == 0) { mwarnx("Please specify files to dump"); goto out; } for (i = optind; i < argc; i++) dump_opts_add_spec(opts, argv[i], NULL, false); rc = dump_to_tar(opts); out: idcache_cleanup(); misc_cleanup(); dump_opts_free(opts); if (rc == EXIT_USAGE) util_prg_print_parse_error(); return rc; }