Files
s390-tools/dump2tar/src/dump2tar.c
Peter Oberparleiter 55e428a4d7 dump2tar: Fix (null) output on help text
Fix the following incorrect help text line:

 -T, --file-timeout (null)  Stop reading file after SEC seconds

Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2021-10-01 14:59:56 +02:00

480 lines
11 KiB
C

/*
* 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 <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#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;
}