/* * 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 "dump2tar_cli.h" #include "global.h" #include "idcache.h" #include "misc.h" #include "strarray.h" #define MIN_BUFFER_SIZE 4096 /* 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 }, }; /* 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; }