mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Initial s390-tools-2.0.0 import
This commit is based on the s390-tools-1.39.0 version. Changes on top of s390-tools-1.39.0: - Add MIT license to all source files - Add LICENSE file - Transform REAMDE to README.md (markdown) - Add AUTHORS.md file - Add CONTRIBUTING.md file - Move changelog from README to CHANGELOG.md file Reviewed-by: Stefan Haberland <sth@linux.vnet.ibm.com> Signed-off-by: Michael Holzheu <holzheu@linux.vnet.ibm.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
# Common definitions
|
||||
include ../../common.mak
|
||||
|
||||
ALL_CPPFLAGS += -I../include -std=gnu99 -Wno-unused-parameter
|
||||
LDLIBS += -lpthread -lrt
|
||||
ifneq ($(HAVE_ZLIB),0)
|
||||
ALL_CPPFLAGS += -DHAVE_ZLIB
|
||||
LDLIBS += -lz
|
||||
endif
|
||||
|
||||
core_objects = buffer.o dref.o global.o dump.o idcache.o misc.o strarray.o tar.o
|
||||
libs = $(rootdir)/libutil/libutil.a
|
||||
|
||||
check_dep_zlib:
|
||||
$(call check_dep, \
|
||||
"dump2tar", \
|
||||
"zlib.h", \
|
||||
"zlib-devel or libz-dev", \
|
||||
"HAVE_ZLIB=0")
|
||||
|
||||
all: check_dep_zlib dump2tar
|
||||
|
||||
dump2tar: $(core_objects) dump2tar.o $(libs)
|
||||
|
||||
install: dump2tar
|
||||
$(INSTALL) -c dump2tar $(DESTDIR)$(USRBINDIR)
|
||||
|
||||
clean:
|
||||
@rm -f dump2tar *.o
|
||||
|
||||
.PHONY: all install clean
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* dump2tar - tool to dump files and command output into a tar archive
|
||||
*
|
||||
* Data buffering functions
|
||||
*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "buffer.h"
|
||||
#include "misc.h"
|
||||
|
||||
void buffer_print(struct buffer *buffer)
|
||||
{
|
||||
fprintf(stderr, "DEBUG: buffer at %p\n", (void *) buffer);
|
||||
if (!buffer)
|
||||
return;
|
||||
fprintf(stderr, "DEBUG: total=%zu\n", buffer->total);
|
||||
fprintf(stderr, "DEBUG: off=%zu\n", buffer->off);
|
||||
fprintf(stderr, "DEBUG: size=%zu\n", buffer->size);
|
||||
fprintf(stderr, "DEBUG: addr=%p\n", (void *) buffer->addr);
|
||||
fprintf(stderr, "DEBUG: fd_open=%d\n", buffer->fd_open);
|
||||
fprintf(stderr, "DEBUG: fd=%d\n", buffer->fd);
|
||||
if (buffer->fd_open) {
|
||||
fprintf(stderr, "DEBUG: fd->pos=%zu\n",
|
||||
lseek(buffer->fd, 0, SEEK_CUR));
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize @buffer to hold @size bytes in memory */
|
||||
void buffer_init(struct buffer *buffer, size_t size)
|
||||
{
|
||||
memset(buffer, 0, sizeof(struct buffer));
|
||||
buffer->addr = mmalloc(size);
|
||||
buffer->size = size;
|
||||
}
|
||||
|
||||
/* Allocate a new buffer for holding @size bytes in memory */
|
||||
struct buffer *buffer_alloc(size_t size)
|
||||
{
|
||||
struct buffer *buffer;
|
||||
|
||||
buffer = mmalloc(sizeof(struct buffer));
|
||||
buffer_init(buffer, size);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/* Forget about any data stored in @buffer */
|
||||
void buffer_reset(struct buffer *buffer)
|
||||
{
|
||||
buffer->total = 0;
|
||||
buffer->off = 0;
|
||||
if (buffer->fd_open) {
|
||||
if (ftruncate(buffer->fd, 0))
|
||||
mwarn("Cannot truncate temporary file");
|
||||
if (lseek(buffer->fd, 0, SEEK_SET) == (off_t) -1)
|
||||
mwarn("Cannot seek in temporary file");
|
||||
}
|
||||
}
|
||||
|
||||
/* Close buffer file associated with @buffer */
|
||||
void buffer_close(struct buffer *buffer)
|
||||
{
|
||||
if (!buffer->fd_open)
|
||||
return;
|
||||
|
||||
fclose(buffer->file);
|
||||
buffer->fd = 0;
|
||||
buffer->fd_open = false;
|
||||
}
|
||||
|
||||
/* Release all resources associated with @buffer. If @dyn is %true, also free
|
||||
* @buffer itself. */
|
||||
void buffer_free(struct buffer *buffer, bool dyn)
|
||||
{
|
||||
if (!buffer)
|
||||
return;
|
||||
buffer_reset(buffer);
|
||||
buffer_close(buffer);
|
||||
free(buffer->addr);
|
||||
if (dyn)
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
/* Open a buffer file for @buffer. Return %EXIT_OK on success, %EXIT_RUNTIME
|
||||
* otherwise. */
|
||||
int buffer_open(struct buffer *buffer)
|
||||
{
|
||||
if (buffer->fd_open)
|
||||
return EXIT_OK;
|
||||
|
||||
buffer->file = tmpfile();
|
||||
if (!buffer->file) {
|
||||
mwarn("Could not create temporary file");
|
||||
return EXIT_RUNTIME;
|
||||
}
|
||||
|
||||
buffer->fd = fileno(buffer->file);
|
||||
buffer->fd_open = true;
|
||||
|
||||
return EXIT_OK;
|
||||
}
|
||||
|
||||
/* Write data in memory of @buffer to buffer file. Return %EXIT_OK on success,
|
||||
* %EXIT_RUNTIME otherwise. */
|
||||
int buffer_flush(struct buffer *buffer)
|
||||
{
|
||||
if (buffer->off == 0)
|
||||
return EXIT_OK;
|
||||
if (buffer_open(buffer))
|
||||
return EXIT_RUNTIME;
|
||||
if (misc_write_data(buffer->fd, buffer->addr, buffer->off)) {
|
||||
mwarn("Could not write to temporary file");
|
||||
return EXIT_RUNTIME;
|
||||
}
|
||||
buffer->off = 0;
|
||||
|
||||
return EXIT_OK;
|
||||
}
|
||||
|
||||
/* Try to ensure that at least @size bytes are available at
|
||||
* @buffer->addr[buffer->off]. Return the actual number of bytes available or
|
||||
* @-1 on error. If @usefile is %true, make use of a buffer file if
|
||||
* the total buffer size exceeds @max_buffer_size. */
|
||||
ssize_t buffer_make_room(struct buffer *buffer, size_t size, bool usefile,
|
||||
size_t max_buffer_size)
|
||||
{
|
||||
size_t needsize;
|
||||
|
||||
if (size > max_buffer_size && usefile)
|
||||
size = max_buffer_size;
|
||||
|
||||
needsize = buffer->off + size;
|
||||
if (needsize <= buffer->size) {
|
||||
/* Room available */
|
||||
return size;
|
||||
}
|
||||
|
||||
if (needsize > max_buffer_size && usefile) {
|
||||
/* Need to write out memory buffer to buffer file */
|
||||
if (buffer_flush(buffer))
|
||||
return -1;
|
||||
if (size <= buffer->size)
|
||||
return size;
|
||||
needsize = size;
|
||||
}
|
||||
|
||||
/* Need to increase memory buffer size */
|
||||
buffer->size = needsize;
|
||||
buffer->addr = mrealloc(buffer->addr, buffer->size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/* Try to read @chunk bytes from @fd to @buffer. Return the number of bytes
|
||||
* read on success, %0 on EOF or %-1 on error. */
|
||||
ssize_t buffer_read_fd(struct buffer *buffer, int fd, size_t chunk,
|
||||
bool usefile, size_t max_buffer_size)
|
||||
{
|
||||
ssize_t c = buffer_make_room(buffer, chunk, usefile, max_buffer_size);
|
||||
|
||||
DBG("buffer_read_fd wanted %zd got %zd", chunk, c);
|
||||
if (c < 0)
|
||||
return c;
|
||||
|
||||
c = read(fd, buffer->addr + buffer->off, c);
|
||||
if (c > 0) {
|
||||
buffer->total += c;
|
||||
buffer->off += c;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Add @len bytes at @addr to @buffer. If @addr is %NULL, add zeroes. Return
|
||||
* %EXIT_OK on success, %EXIT_RUNTIME otherwise. */
|
||||
int buffer_add_data(struct buffer *buffer, char *addr, size_t len, bool usefile,
|
||||
size_t max_buffer_size)
|
||||
{
|
||||
ssize_t c;
|
||||
|
||||
while (len > 0) {
|
||||
c = buffer_make_room(buffer, len, usefile, max_buffer_size);
|
||||
if (c < 0)
|
||||
return EXIT_RUNTIME;
|
||||
if (addr) {
|
||||
memcpy(buffer->addr + buffer->off, addr, c);
|
||||
addr += c;
|
||||
} else {
|
||||
memset(buffer->addr + buffer->off, 0, c);
|
||||
}
|
||||
buffer->total += c;
|
||||
buffer->off += c;
|
||||
|
||||
len -= c;
|
||||
}
|
||||
|
||||
return EXIT_OK;
|
||||
}
|
||||
|
||||
/* Call @cb for all chunks of data in @buffer. @data is passed to @cb. */
|
||||
int buffer_iterate(struct buffer *buffer, buffer_cb_t cb, void *data)
|
||||
{
|
||||
int rc;
|
||||
ssize_t r;
|
||||
|
||||
if (buffer->total == 0)
|
||||
return EXIT_OK;
|
||||
|
||||
if (!buffer->fd_open)
|
||||
return cb(data, buffer->addr, buffer->off);
|
||||
|
||||
/* Free memory buffer to be used as copy buffer */
|
||||
if (buffer_flush(buffer))
|
||||
return EXIT_RUNTIME;
|
||||
if (lseek(buffer->fd, 0, SEEK_SET) == (off_t) -1) {
|
||||
mwarn("Cannot seek in temporary file");
|
||||
return EXIT_RUNTIME;
|
||||
}
|
||||
|
||||
/* Copy data from temporary file to target file */
|
||||
while ((r = misc_read_data(buffer->fd, buffer->addr,
|
||||
buffer->size)) != 0) {
|
||||
if (r < 0) {
|
||||
mwarn("Cannot read from temporary file");
|
||||
return EXIT_RUNTIME;
|
||||
}
|
||||
rc = cb(data, buffer->addr, r);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
|
||||
return EXIT_OK;
|
||||
}
|
||||
|
||||
/* Truncate @buffer to at most @len bytes */
|
||||
int buffer_truncate(struct buffer *buffer, size_t len)
|
||||
{
|
||||
size_t delta;
|
||||
|
||||
if (buffer->total <= len)
|
||||
return EXIT_OK;
|
||||
|
||||
delta = buffer->total - len;
|
||||
|
||||
buffer->total = len;
|
||||
if (buffer->fd_open && delta > buffer->off) {
|
||||
/* All of memory and some of file buffer is truncated */
|
||||
buffer->off = 0;
|
||||
if (ftruncate(buffer->fd, len)) {
|
||||
mwarn("Cannot truncate temporary file");
|
||||
return EXIT_RUNTIME;
|
||||
}
|
||||
if (lseek(buffer->fd, len, SEEK_SET) == (off_t) -1) {
|
||||
mwarn("Cannot seek in temporary file");
|
||||
return EXIT_RUNTIME;
|
||||
}
|
||||
} else {
|
||||
/* Only memory buffer is truncated */
|
||||
buffer->off -= delta;
|
||||
}
|
||||
|
||||
return EXIT_OK;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* dump2tar - tool to dump files and command output into a tar archive
|
||||
*
|
||||
* Reference counting for directory handles
|
||||
*
|
||||
* 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 <dirent.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "dref.h"
|
||||
#include "global.h"
|
||||
#include "misc.h"
|
||||
|
||||
/* dref_mutex serializes access to drefs */
|
||||
static pthread_mutex_t dref_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static unsigned long num_open_dirs;
|
||||
static unsigned long num_open_dirs_max;
|
||||
|
||||
/* Lock dref mutex */
|
||||
static void dref_lock(void)
|
||||
{
|
||||
if (!global_threaded)
|
||||
return;
|
||||
pthread_mutex_lock(&dref_mutex);
|
||||
}
|
||||
|
||||
/* Unlock dref mutex */
|
||||
static void dref_unlock(void)
|
||||
{
|
||||
if (!global_threaded)
|
||||
return;
|
||||
pthread_mutex_unlock(&dref_mutex);
|
||||
}
|
||||
|
||||
/* Create a reference count managed directory handle for @dirname */
|
||||
struct dref *dref_create(const char *dirname)
|
||||
{
|
||||
struct dref *dref;
|
||||
DIR *dd;
|
||||
|
||||
dd = opendir(dirname);
|
||||
DBG("opendir(%s)=%p (total=%lu)", dirname, dd, ++num_open_dirs);
|
||||
if (!dd) {
|
||||
num_open_dirs--;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (num_open_dirs > num_open_dirs_max)
|
||||
num_open_dirs_max = num_open_dirs;
|
||||
|
||||
dref = mmalloc(sizeof(struct dref));
|
||||
dref->dd = dd;
|
||||
dref->dirfd = dirfd(dd);
|
||||
dref->count = 1;
|
||||
|
||||
return dref;
|
||||
}
|
||||
|
||||
/* Obtain a reference to @dref */
|
||||
struct dref *dref_get(struct dref *dref)
|
||||
{
|
||||
if (dref) {
|
||||
dref_lock();
|
||||
dref->count++;
|
||||
dref_unlock();
|
||||
}
|
||||
|
||||
return dref;
|
||||
}
|
||||
|
||||
/* Release a reference to @dref. If this was the last reference, lose the
|
||||
* associated directory handle and free @dref. */
|
||||
void dref_put(struct dref *dref)
|
||||
{
|
||||
if (dref) {
|
||||
dref_lock();
|
||||
dref->count--;
|
||||
if (dref->count == 0) {
|
||||
num_open_dirs--;
|
||||
DBG("closedir(%p) (total=%lu, max=%lu)", dref->dd,
|
||||
num_open_dirs, num_open_dirs_max);
|
||||
closedir(dref->dd);
|
||||
free(dref);
|
||||
}
|
||||
dref_unlock();
|
||||
}
|
||||
}
|
||||
+1859
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,478 @@
|
||||
/*
|
||||
* 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, 2016 },
|
||||
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",
|
||||
},
|
||||
{
|
||||
.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;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* dump2tar - tool to dump files and command output into a tar archive
|
||||
*
|
||||
* Global variables
|
||||
*
|
||||
* 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 "global.h"
|
||||
|
||||
/* Global settings */
|
||||
bool global_threaded;
|
||||
bool global_debug;
|
||||
bool global_verbose;
|
||||
bool global_quiet;
|
||||
bool global_timestamps;
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* dump2tar - tool to dump files and command output into a tar archive
|
||||
*
|
||||
* Caches for user and group ID lookups
|
||||
*
|
||||
* 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 <grp.h>
|
||||
#include <pthread.h>
|
||||
#include <pwd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "global.h"
|
||||
#include "idcache.h"
|
||||
#include "misc.h"
|
||||
|
||||
/* Maximum user and group name lengths as defined in tar header */
|
||||
#define ID_NAME_MAXLEN 32
|
||||
|
||||
/* Types for user and group ID caches */
|
||||
typedef uid_t generic_id_t; /* Assumes that uid_t == gid_t */
|
||||
|
||||
struct id_cache_entry {
|
||||
generic_id_t id;
|
||||
char name[ID_NAME_MAXLEN];
|
||||
};
|
||||
|
||||
struct id_cache {
|
||||
unsigned int num;
|
||||
struct id_cache_entry entries[];
|
||||
};
|
||||
|
||||
/* cache_mutex serializes access to cached uid and gid data */
|
||||
static pthread_mutex_t id_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static struct id_cache *id_cache_uid;
|
||||
static struct id_cache *id_cache_gid;
|
||||
|
||||
/* Lock cache mutex */
|
||||
static void cache_lock(void)
|
||||
{
|
||||
if (!global_threaded)
|
||||
return;
|
||||
pthread_mutex_lock(&id_cache_mutex);
|
||||
}
|
||||
|
||||
/* Unlock cache mutex */
|
||||
static void cache_unlock(void)
|
||||
{
|
||||
if (!global_threaded)
|
||||
return;
|
||||
pthread_mutex_unlock(&id_cache_mutex);
|
||||
}
|
||||
|
||||
/* Copy the name associated with @id in @cache to at most @len bytes at @dest.
|
||||
* Return %true if name was found in cache, %false otherwise. */
|
||||
static bool strncpy_id_cache_entry(char *dest, struct id_cache *cache,
|
||||
generic_id_t id, size_t len)
|
||||
{
|
||||
unsigned int i;
|
||||
bool hit = false;
|
||||
|
||||
cache_lock();
|
||||
if (cache) {
|
||||
for (i = 0; i < cache->num; i++) {
|
||||
if (cache->entries[i].id == id) {
|
||||
strncpy(dest, cache->entries[i].name, len);
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
cache_unlock();
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
/* Add a new entry consisting of @id and @name to ID cache in @*cache_ptr.
|
||||
* Update @cache_ptr if necessary. */
|
||||
static void add_id_cache_entry(struct id_cache **cache_ptr, generic_id_t id,
|
||||
char *name)
|
||||
{
|
||||
struct id_cache *cache;
|
||||
unsigned int cache_num;
|
||||
size_t new_size;
|
||||
struct id_cache *new_cache;
|
||||
|
||||
cache_lock();
|
||||
|
||||
cache = *cache_ptr;
|
||||
cache_num = cache ? cache->num : 0;
|
||||
new_size = sizeof(struct id_cache) +
|
||||
sizeof(struct id_cache_entry) * (cache_num + 1);
|
||||
new_cache = mrealloc(cache, new_size);
|
||||
if (cache_num == 0)
|
||||
new_cache->num = 0;
|
||||
new_cache->entries[cache_num].id = id;
|
||||
strncpy(new_cache->entries[cache_num].name, name, ID_NAME_MAXLEN);
|
||||
new_cache->num++;
|
||||
*cache_ptr = new_cache;
|
||||
|
||||
cache_unlock();
|
||||
}
|
||||
|
||||
/* Copy the user name corresponding to user ID @uid to at most @len bytes
|
||||
* at @name */
|
||||
void uid_to_name(uid_t uid, char *name, size_t len)
|
||||
{
|
||||
struct passwd pwd, *pwd_ptr;
|
||||
char buffer[PWD_BUFFER_SIZE], *result;
|
||||
|
||||
if (strncpy_id_cache_entry(name, id_cache_uid, uid, len))
|
||||
return;
|
||||
|
||||
/* getpwuid() can be slow so cache results */
|
||||
getpwuid_r(uid, &pwd, buffer, PWD_BUFFER_SIZE, &pwd_ptr);
|
||||
if (!pwd_ptr || !pwd_ptr->pw_name)
|
||||
return;
|
||||
result = pwd_ptr->pw_name;
|
||||
|
||||
add_id_cache_entry(&id_cache_uid, uid, result);
|
||||
|
||||
strncpy(name, result, len);
|
||||
}
|
||||
|
||||
/* Copy the group name corresponding to group ID @gid to at most @len bytes
|
||||
* at @name */
|
||||
void gid_to_name(gid_t gid, char *name, size_t len)
|
||||
{
|
||||
struct group grp, *grp_ptr;
|
||||
char buffer[GRP_BUFFER_SIZE], *result;
|
||||
|
||||
if (strncpy_id_cache_entry(name, id_cache_gid, gid, len))
|
||||
return;
|
||||
|
||||
/* getgrgid() can be slow so cache results */
|
||||
getgrgid_r(gid, &grp, buffer, GRP_BUFFER_SIZE, &grp_ptr);
|
||||
if (!grp_ptr || !grp_ptr->gr_name)
|
||||
return;
|
||||
result = grp_ptr->gr_name;
|
||||
|
||||
add_id_cache_entry(&id_cache_gid, gid, result);
|
||||
|
||||
strncpy(name, result, len);
|
||||
}
|
||||
|
||||
void idcache_cleanup(void)
|
||||
{
|
||||
free(id_cache_uid);
|
||||
free(id_cache_gid);
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
/*
|
||||
* dump2tar - tool to dump files and command output into a tar archive
|
||||
*
|
||||
* Helper functions
|
||||
*
|
||||
* 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 <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "dref.h"
|
||||
#include "global.h"
|
||||
#include "misc.h"
|
||||
|
||||
struct timespec main_start_ts;
|
||||
|
||||
static pthread_key_t thread_name_key;
|
||||
static bool stdout_data;
|
||||
|
||||
/* Write @len bytes at @addr to @fd. Return %EXIT_OK on success, %EXIT_RUNTIME
|
||||
* otherwise. */
|
||||
int misc_write_data(int fd, char *addr, size_t len)
|
||||
{
|
||||
ssize_t w;
|
||||
|
||||
while (len > 0) {
|
||||
w = write(fd, addr, len);
|
||||
if (w < 0)
|
||||
return EXIT_RUNTIME;
|
||||
len -= w;
|
||||
addr += w;
|
||||
}
|
||||
|
||||
return EXIT_OK;
|
||||
}
|
||||
|
||||
/* Read at most @len bytes from @fd to @addr. Return the number of bytes read
|
||||
* or %-1 on error. */
|
||||
ssize_t misc_read_data(int fd, char *addr, size_t len)
|
||||
{
|
||||
size_t done = 0;
|
||||
ssize_t r;
|
||||
|
||||
while (len > 0) {
|
||||
r = read(fd, addr, len);
|
||||
if (r < 0)
|
||||
return -1;
|
||||
if (r == 0)
|
||||
break;
|
||||
len -= r;
|
||||
addr += r;
|
||||
done += r;
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
/* Advance timespec @ts by @sec seconds and @nsec nanoseconds */
|
||||
void inc_timespec(struct timespec *ts, time_t sec, long nsec)
|
||||
{
|
||||
ts->tv_nsec += nsec;
|
||||
ts->tv_sec += sec;
|
||||
if (ts->tv_nsec > NSEC_PER_SEC) {
|
||||
ts->tv_nsec -= NSEC_PER_SEC;
|
||||
ts->tv_sec++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set timespec @ts to point to @sec seconds and @nsec nanoseconds in the
|
||||
* future */
|
||||
void set_timespec(struct timespec *ts, time_t sec, long nsec)
|
||||
{
|
||||
clock_gettime(CLOCK_MONOTONIC, ts);
|
||||
inc_timespec(ts, sec, nsec);
|
||||
}
|
||||
|
||||
/* Return true if timespec @a refers to a point in time before @b */
|
||||
bool ts_before(struct timespec *a, struct timespec *b)
|
||||
{
|
||||
if (a->tv_sec < b->tv_sec ||
|
||||
(a->tv_sec == b->tv_sec && a->tv_nsec < b->tv_nsec))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Store a string representing the time duration between @start and @end in
|
||||
* at most @len bytes of @buff. */
|
||||
int snprintf_duration(char *buff, size_t len, struct timespec *start,
|
||||
struct timespec *end)
|
||||
{
|
||||
time_t sec;
|
||||
long nsec, msec, s, m, h;
|
||||
|
||||
sec = end->tv_sec - start->tv_sec;
|
||||
nsec = end->tv_nsec - start->tv_nsec;
|
||||
|
||||
if (nsec < 0) {
|
||||
nsec += NSEC_PER_SEC;
|
||||
sec--;
|
||||
}
|
||||
|
||||
msec = nsec / NSEC_PER_MSEC;
|
||||
s = sec % 60;
|
||||
sec /= 60;
|
||||
m = sec % 60;
|
||||
sec /= 60;
|
||||
h = sec;
|
||||
|
||||
if (h > 0)
|
||||
return snprintf(buff, len, "%luh%lum%lu.%03lus", h, m, s, msec);
|
||||
else if (m > 0)
|
||||
return snprintf(buff, len, "%lum%lu.%03lus", m, s, msec);
|
||||
else
|
||||
return snprintf(buff, len, "%lu.%03lus", s, msec);
|
||||
}
|
||||
|
||||
/* Return the name of the current thread */
|
||||
char *get_threadname(void)
|
||||
{
|
||||
return pthread_getspecific(thread_name_key);
|
||||
}
|
||||
|
||||
static int snprintf_timestamp(char *str, size_t size)
|
||||
{
|
||||
struct timespec now_ts;
|
||||
|
||||
set_timespec(&now_ts, 0, 0);
|
||||
now_ts.tv_sec -= main_start_ts.tv_sec;
|
||||
now_ts.tv_nsec -= main_start_ts.tv_nsec;
|
||||
if (now_ts.tv_nsec < 0) {
|
||||
now_ts.tv_nsec += NSEC_PER_SEC;
|
||||
now_ts.tv_sec--;
|
||||
}
|
||||
|
||||
return snprintf(str, size, "[%3lu.%06lu] ", now_ts.tv_sec,
|
||||
now_ts.tv_nsec / NSEC_PER_USEC);
|
||||
}
|
||||
|
||||
/* When DUMP2TAR_DEBUG is set to non-zero, print debugging information */
|
||||
void debug(const char *file, unsigned long line, const char *format, ...)
|
||||
{
|
||||
char msg[MSG_LEN];
|
||||
size_t off = 0;
|
||||
int rc;
|
||||
va_list args;
|
||||
|
||||
/* Debug marker */
|
||||
rc = snprintf(&msg[off], MSG_LEN - off, "DEBUG: ");
|
||||
HANDLE_RC(rc, MSG_LEN, off, out);
|
||||
|
||||
/* Timestamp */
|
||||
rc = snprintf_timestamp(&msg[off], MSG_LEN - off);
|
||||
HANDLE_RC(rc, MSG_LEN, off, out);
|
||||
|
||||
/* Thread name */
|
||||
rc = snprintf(&msg[off], MSG_LEN - off, "%s: ", get_threadname());
|
||||
HANDLE_RC(rc, MSG_LEN, off, out);
|
||||
|
||||
/* Message */
|
||||
va_start(args, format);
|
||||
rc = vsnprintf(&msg[off], MSG_LEN - off, format, args);
|
||||
va_end(args);
|
||||
HANDLE_RC(rc, MSG_LEN, off, out);
|
||||
|
||||
/* Call site */
|
||||
rc = snprintf(&msg[off], MSG_LEN - off, " (%s:%lu)", file, line);
|
||||
|
||||
out:
|
||||
fprintf(stderr, "%s\n", msg);
|
||||
}
|
||||
|
||||
/* Print a warning message consisting of @format and variable arguments.
|
||||
* If @print_errno is true, also print the text corresponding to errno.
|
||||
* We're not using err.h's warn since we want timestamps and synchronized
|
||||
* output. */
|
||||
void _mwarn(bool print_errno, const char *format, ...)
|
||||
{
|
||||
char msg[MSG_LEN];
|
||||
size_t off = 0;
|
||||
int rc;
|
||||
va_list args;
|
||||
|
||||
if (global_timestamps) {
|
||||
rc = snprintf_timestamp(&msg[off], MSG_LEN - off);
|
||||
HANDLE_RC(rc, MSG_LEN, off, out);
|
||||
}
|
||||
|
||||
rc = snprintf(&msg[off], MSG_LEN - off, "%s: ",
|
||||
program_invocation_short_name);
|
||||
HANDLE_RC(rc, MSG_LEN, off, out);
|
||||
|
||||
va_start(args, format);
|
||||
rc = vsnprintf(&msg[off], MSG_LEN - off, format, args);
|
||||
va_end(args);
|
||||
HANDLE_RC(rc, MSG_LEN, off, out);
|
||||
|
||||
if (print_errno)
|
||||
snprintf(&msg[off], MSG_LEN - off, ": %s", strerror(errno));
|
||||
|
||||
out:
|
||||
fprintf(stderr, "%s\n", msg);
|
||||
}
|
||||
|
||||
/* Provide informational output if --verbose was specified */
|
||||
void verb(const char *format, ...)
|
||||
{
|
||||
char msg[MSG_LEN];
|
||||
size_t off = 0;
|
||||
int rc;
|
||||
va_list args;
|
||||
FILE *fd;
|
||||
|
||||
if (!global_verbose)
|
||||
return;
|
||||
if (stdout_data)
|
||||
fd = stderr;
|
||||
else
|
||||
fd = stdout;
|
||||
if (global_timestamps) {
|
||||
rc = snprintf_timestamp(&msg[off], MSG_LEN - off);
|
||||
HANDLE_RC(rc, MSG_LEN, off, out);
|
||||
}
|
||||
|
||||
va_start(args, format);
|
||||
rc = vsnprintf(&msg[off], MSG_LEN - off, format, args);
|
||||
va_end(args);
|
||||
|
||||
out:
|
||||
fprintf(fd, "%s", msg);
|
||||
}
|
||||
|
||||
/* Provide informational output. */
|
||||
void info(const char *format, ...)
|
||||
{
|
||||
char msg[MSG_LEN];
|
||||
size_t off = 0;
|
||||
int rc;
|
||||
va_list args;
|
||||
FILE *fd;
|
||||
|
||||
if (global_quiet)
|
||||
return;
|
||||
if (stdout_data)
|
||||
fd = stderr;
|
||||
else
|
||||
fd = stdout;
|
||||
|
||||
if (global_timestamps) {
|
||||
rc = snprintf_timestamp(&msg[off], MSG_LEN - off);
|
||||
HANDLE_RC(rc, MSG_LEN, off, out);
|
||||
}
|
||||
|
||||
va_start(args, format);
|
||||
rc = vsnprintf(&msg[off], MSG_LEN - off, format, args);
|
||||
va_end(args);
|
||||
|
||||
out:
|
||||
fprintf(fd, "%s", msg);
|
||||
}
|
||||
|
||||
/* Return a newly allocated buffer containing the result of the specified
|
||||
* string format arguments */
|
||||
char *__masprintf(const char *func, const char *file, int line, const char *fmt,
|
||||
...)
|
||||
{
|
||||
char *str;
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
__util_vasprintf(func, file, line, &str, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/* Set the internal name of the calling thread */
|
||||
void __set_threadname(const char *func, const char *file, int line,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
char *str;
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
__util_vasprintf(func, file, line, &str, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
pthread_setspecific(thread_name_key, str);
|
||||
}
|
||||
|
||||
/* Clear any previously set thread name */
|
||||
void clear_threadname(void)
|
||||
{
|
||||
void *addr = pthread_getspecific(thread_name_key);
|
||||
|
||||
if (addr) {
|
||||
pthread_setspecific(thread_name_key, NULL);
|
||||
free(addr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove any number of trailing characters @c in @str */
|
||||
void chomp(char *str, char *c)
|
||||
{
|
||||
ssize_t i;
|
||||
|
||||
for (i = strlen(str) - 1; i >= 0 && strchr(c, str[i]); i--)
|
||||
str[i] = 0;
|
||||
}
|
||||
|
||||
/* Remove any number of leading characters @c in @str */
|
||||
void lchomp(char *str, char *c)
|
||||
{
|
||||
char *from;
|
||||
|
||||
for (from = str; *from && strchr(c, *from); from++)
|
||||
;
|
||||
if (str != from)
|
||||
memmove(str, from, strlen(from) + 1);
|
||||
}
|
||||
|
||||
/* Perform a stat on file referenced by either @abs or @rel and @dref. Store
|
||||
* results in @stat and return stat()'s return code. */
|
||||
int stat_file(bool dereference, const char *abs, const char *rel,
|
||||
struct dref *dref, struct stat *st)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (dref) {
|
||||
if (dereference)
|
||||
rc = fstatat(dref->dirfd, rel, st, 0);
|
||||
else
|
||||
rc = fstatat(dref->dirfd, rel, st, AT_SYMLINK_NOFOLLOW);
|
||||
} else {
|
||||
if (dereference)
|
||||
rc = stat(abs, st);
|
||||
else
|
||||
rc = lstat(abs, st);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Fill stat buffer @st with dummy values. */
|
||||
void set_dummy_stat(struct stat *st)
|
||||
{
|
||||
/* Fake stat */
|
||||
memset(st, 0, sizeof(struct stat));
|
||||
st->st_mode = S_IRUSR | S_IWUSR | S_IFREG;
|
||||
st->st_uid = geteuid();
|
||||
st->st_gid = getegid();
|
||||
st->st_mtime = time(NULL);
|
||||
}
|
||||
|
||||
/* Redirect all output streams to @fd and execute command @CMD */
|
||||
int cmd_child(int fd, char *cmd)
|
||||
{
|
||||
char *argv[] = { "/bin/sh", "-c", NULL, NULL };
|
||||
char *env[] = { NULL };
|
||||
|
||||
argv[2] = cmd;
|
||||
if (dup2(fd, STDOUT_FILENO) == -1 || dup2(fd, STDERR_FILENO) == -1) {
|
||||
mwarn("Could not redirect command output");
|
||||
return EXIT_RUNTIME;
|
||||
}
|
||||
|
||||
execve("/bin/sh", argv, env);
|
||||
|
||||
return EXIT_RUNTIME;
|
||||
}
|
||||
|
||||
#define PIPE_READ 0
|
||||
#define PIPE_WRITE 1
|
||||
|
||||
/* Run command @cmd as a child process and store its PID in @pid_ptr. On
|
||||
* success, return a file descriptor that is an output pipe to the standard
|
||||
* output and standard error streams of the child process. Return %-1 on
|
||||
* error. */
|
||||
int cmd_open(char *cmd, pid_t *pid_ptr)
|
||||
{
|
||||
int pfd[2];
|
||||
pid_t pid;
|
||||
|
||||
if (pipe(pfd) < 0)
|
||||
return -1;
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
/* Fork error */
|
||||
close(pfd[PIPE_READ]);
|
||||
close(pfd[PIPE_WRITE]);
|
||||
return -1;
|
||||
} else if (pid == 0) {
|
||||
/* Child process */
|
||||
close(pfd[PIPE_READ]);
|
||||
exit(cmd_child(pfd[PIPE_WRITE], cmd));
|
||||
}
|
||||
|
||||
/* Parent process */
|
||||
close(pfd[PIPE_WRITE]);
|
||||
|
||||
*pid_ptr = pid;
|
||||
|
||||
return pfd[PIPE_READ];
|
||||
}
|
||||
|
||||
/* Close the file descriptor @fd and end the process with PID @pid. When
|
||||
* not %NULL, use @status_ptr to store the resulting process status. */
|
||||
int cmd_close(int fd, pid_t pid, int *status_ptr)
|
||||
{
|
||||
int status, rc = EXIT_OK;
|
||||
|
||||
close(fd);
|
||||
kill(pid, SIGQUIT);
|
||||
if (waitpid(pid, &status, 0) == -1) {
|
||||
status = -errno;
|
||||
rc = EXIT_RUNTIME;
|
||||
}
|
||||
if (status_ptr)
|
||||
*status_ptr = status;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void misc_init(void)
|
||||
{
|
||||
set_timespec(&main_start_ts, 0, 0);
|
||||
pthread_key_create(&thread_name_key, free);
|
||||
set_threadname("main");
|
||||
}
|
||||
|
||||
void misc_cleanup(void)
|
||||
{
|
||||
clear_threadname();
|
||||
pthread_key_delete(thread_name_key);
|
||||
}
|
||||
|
||||
void set_stdout_data(void)
|
||||
{
|
||||
stdout_data = true;
|
||||
}
|
||||
|
||||
bool starts_with(const char *str, const char *prefix)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
len = strlen(prefix);
|
||||
|
||||
if (strncmp(str, prefix, len) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ends_with(const char *str, const char *suffix)
|
||||
{
|
||||
size_t str_len, s_len;
|
||||
|
||||
str_len = strlen(str);
|
||||
s_len = strlen(suffix);
|
||||
|
||||
if (str_len < s_len)
|
||||
return false;
|
||||
if (strcmp(str + str_len - s_len, suffix) != 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Remove subsequent slashes in @str */
|
||||
void remove_double_slashes(char *str)
|
||||
{
|
||||
size_t i, to;
|
||||
char last;
|
||||
|
||||
last = 0;
|
||||
for (i = 0, to = 0; str[i]; i++) {
|
||||
if (last != '/' || str[i] != '/')
|
||||
last = str[to++] = str[i];
|
||||
}
|
||||
str[to] = 0;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* dump2tar - tool to dump files and command output into a tar archive
|
||||
*
|
||||
* Dynamically growing string arrays
|
||||
*
|
||||
* 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 <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "misc.h"
|
||||
#include "strarray.h"
|
||||
|
||||
/* Release resources associated with string array @array */
|
||||
void free_strarray(struct strarray *array)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < array->num; i++)
|
||||
free(array->str[i]);
|
||||
free(array->str);
|
||||
array->str = NULL;
|
||||
array->num = 0;
|
||||
}
|
||||
|
||||
/* Add string @str to string array @array */
|
||||
void add_str_to_strarray(struct strarray *array, const char *str)
|
||||
{
|
||||
array->str = mrealloc(array->str, sizeof(char *) * (array->num + 2));
|
||||
array->str[array->num + 1] = NULL;
|
||||
array->str[array->num] = mstrdup(str);
|
||||
array->num++;
|
||||
}
|
||||
|
||||
/* Add string resulting from @fmt and additional arguments to @array */
|
||||
void add_vstr_to_strarray(struct strarray *array, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
char *str;
|
||||
|
||||
va_start(args, fmt);
|
||||
util_vasprintf(&str, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
array->str = mrealloc(array->str, sizeof(char *) * (array->num + 2));
|
||||
array->str[array->num + 1] = NULL;
|
||||
array->str[array->num] = str;
|
||||
array->num++;
|
||||
}
|
||||
|
||||
/* Add all lines in file at @filename to @array */
|
||||
int add_file_to_strarray(struct strarray *array, const char *filename)
|
||||
{
|
||||
FILE *fd;
|
||||
char *line = NULL;
|
||||
size_t line_size;
|
||||
int rc = EXIT_OK;
|
||||
|
||||
fd = fopen(filename, "r");
|
||||
if (!fd) {
|
||||
mwarn("%s: Cannot open file", filename);
|
||||
return EXIT_RUNTIME;
|
||||
}
|
||||
|
||||
while (!feof(fd) && !ferror(fd)) {
|
||||
if (getline(&line, &line_size, fd) == -1)
|
||||
continue;
|
||||
chomp(line, "\n");
|
||||
add_str_to_strarray(array, line);
|
||||
}
|
||||
if (ferror(fd))
|
||||
rc = EXIT_RUNTIME;
|
||||
free(line);
|
||||
|
||||
fclose(fd);
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* dump2tar - tool to dump files and command output into a tar archive
|
||||
*
|
||||
* TAR file generation
|
||||
*
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "buffer.h"
|
||||
#include "idcache.h"
|
||||
#include "misc.h"
|
||||
#include "tar.h"
|
||||
|
||||
#define LONGLINK "././@LongLink"
|
||||
#define TYPE_LONGLINK 'K'
|
||||
#define TYPE_LONGNAME 'L'
|
||||
|
||||
#define BLOCKSIZE 512
|
||||
|
||||
/* Basic TAR header */
|
||||
struct tar_header {
|
||||
char name[100];
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
char gid[8];
|
||||
char size[12];
|
||||
char mtime[12];
|
||||
char chksum[8];
|
||||
char typeflag;
|
||||
char linkname[100];
|
||||
char magic[6];
|
||||
char version[2];
|
||||
char uname[32];
|
||||
char gname[32];
|
||||
char devmajor[8];
|
||||
char devminor[8];
|
||||
char prefix[155];
|
||||
};
|
||||
|
||||
/* Store the octal value of @value to at most @len bytes at @dest */
|
||||
static void set_octal(char *dest, size_t len, unsigned long value)
|
||||
{
|
||||
int i;
|
||||
|
||||
dest[len - 1] = 0;
|
||||
for (i = len - 2; i >= 0; i--) {
|
||||
dest[i] = '0' + (value & 7);
|
||||
value >>= 3;
|
||||
}
|
||||
}
|
||||
|
||||
/* Store time @value to at most @len bytes at @dest */
|
||||
static void set_time(char *dest, size_t len, time_t value)
|
||||
{
|
||||
time_t max = (1ULL << (3 * (len - 1))) - 1;
|
||||
|
||||
if (value >= 0 && value <= max) {
|
||||
set_octal(dest, len, value);
|
||||
return;
|
||||
}
|
||||
|
||||
for (; len > 0; len--) {
|
||||
dest[len - 1] = value & 0xff;
|
||||
value >>= 8;
|
||||
}
|
||||
|
||||
dest[0] |= 0x80;
|
||||
}
|
||||
|
||||
#define SET_FIELD(obj, name, value) \
|
||||
set_octal((obj)->name, sizeof((obj)->name), (unsigned long) (value))
|
||||
#define SET_TIME_FIELD(obj, name, value) \
|
||||
set_time((obj)->name, sizeof((obj)->name), (time_t) (value))
|
||||
#define SET_STR_FIELD(obj, name, value) \
|
||||
strncpy((obj)->name, (value), sizeof((obj)->name))
|
||||
|
||||
/* Initialize the tar file @header with the provided data */
|
||||
static void init_header(struct tar_header *header, const char *filename,
|
||||
const char *link, size_t len, struct stat *stat,
|
||||
char type)
|
||||
{
|
||||
unsigned int i, checksum;
|
||||
unsigned char *c;
|
||||
|
||||
memset(header, 0, sizeof(*header));
|
||||
|
||||
/* Fill in header fields */
|
||||
SET_STR_FIELD(header, name, filename);
|
||||
if (link)
|
||||
SET_STR_FIELD(header, linkname, link);
|
||||
SET_FIELD(header, size, len);
|
||||
if (stat) {
|
||||
SET_FIELD(header, mode, stat->st_mode & 07777);
|
||||
SET_FIELD(header, uid, stat->st_uid);
|
||||
SET_FIELD(header, gid, stat->st_gid);
|
||||
SET_TIME_FIELD(header, mtime, stat->st_mtime);
|
||||
uid_to_name(stat->st_uid, header->uname, sizeof(header->uname));
|
||||
gid_to_name(stat->st_gid, header->gname, sizeof(header->gname));
|
||||
} else {
|
||||
SET_FIELD(header, mode, 0644);
|
||||
SET_FIELD(header, uid, 0);
|
||||
SET_FIELD(header, gid, 0);
|
||||
SET_TIME_FIELD(header, mtime, 0);
|
||||
uid_to_name(0, header->uname, sizeof(header->uname));
|
||||
gid_to_name(0, header->gname, sizeof(header->gname));
|
||||
}
|
||||
header->typeflag = type;
|
||||
memcpy(header->magic, "ustar ", sizeof(header->magic));
|
||||
memcpy(header->version, " ", sizeof(header->version));
|
||||
|
||||
/* Calculate checksum */
|
||||
memset(header->chksum, ' ', sizeof(header->chksum));
|
||||
checksum = 0;
|
||||
c = (unsigned char *) header;
|
||||
for (i = 0; i < sizeof(*header); i++)
|
||||
checksum += c[i];
|
||||
snprintf(header->chksum, 7, "%06o", checksum);
|
||||
}
|
||||
|
||||
/* Emit zero bytes via @emit_cb to pad @len to a multiple of BLOCKSIZE */
|
||||
static int emit_padding(emit_cb_t emit_cb, void *data, size_t len)
|
||||
{
|
||||
size_t pad = BLOCKSIZE - len % BLOCKSIZE;
|
||||
char zeroes[BLOCKSIZE];
|
||||
|
||||
if (len % BLOCKSIZE > 0) {
|
||||
memset(zeroes, 0, BLOCKSIZE);
|
||||
return emit_cb(data, zeroes, pad);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Emit @len bytes at @addr via @emit_cb and pad data to BLOCKSIZE with zero
|
||||
* bytes */
|
||||
static int emit_data(emit_cb_t emit_cb, void *data, void *addr, size_t len)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
rc = emit_cb(data, addr, len);
|
||||
if (rc)
|
||||
return rc;
|
||||
return emit_padding(emit_cb, data, len);
|
||||
}
|
||||
|
||||
/* Emit a tar header via @emit_cb */
|
||||
static int emit_header(emit_cb_t emit_cb, void *data, char *filename,
|
||||
char *link, size_t len, struct stat *stat, char type)
|
||||
{
|
||||
struct tar_header header;
|
||||
size_t namelen = strlen(filename);
|
||||
size_t linklen;
|
||||
int rc;
|
||||
|
||||
/* /proc can contain unreadable links which causes tar to complain
|
||||
* during extract - use a dummy value to handle this more gracefully */
|
||||
if (link && !*link)
|
||||
link = " ";
|
||||
|
||||
linklen = link ? strlen(link) : 0;
|
||||
if (linklen > sizeof(header.linkname)) {
|
||||
rc = emit_header(emit_cb, data, LONGLINK, NULL, linklen + 1,
|
||||
NULL, TYPE_LONGLINK);
|
||||
if (rc)
|
||||
return rc;
|
||||
rc = emit_data(emit_cb, data, link, linklen + 1);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
if (namelen > sizeof(header.name)) {
|
||||
rc = emit_header(emit_cb, data, LONGLINK, NULL, namelen + 1,
|
||||
NULL, TYPE_LONGNAME);
|
||||
if (rc)
|
||||
return rc;
|
||||
rc = emit_data(emit_cb, data, filename, namelen + 1);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
|
||||
init_header(&header, filename, link, len, stat, type);
|
||||
return emit_data(emit_cb, data, &header, sizeof(header));
|
||||
}
|
||||
|
||||
struct emit_content_cb_data {
|
||||
emit_cb_t emit_cb;
|
||||
void *data;
|
||||
size_t len;
|
||||
int rc;
|
||||
};
|
||||
|
||||
/* Callback for emitting a single chunk of data of a buffer */
|
||||
static int emit_content_cb(void *data, void *addr, size_t len)
|
||||
{
|
||||
struct emit_content_cb_data *cb_data = data;
|
||||
|
||||
if (len > cb_data->len)
|
||||
len = cb_data->len;
|
||||
cb_data->len -= len;
|
||||
|
||||
cb_data->rc = cb_data->emit_cb(cb_data->data, addr, len);
|
||||
|
||||
if (cb_data->rc || cb_data->len == 0)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Emit at most @len bytes of contents of @buffer via @emit_cb and pad output
|
||||
* to BLOCKSIZE with zero bytes */
|
||||
static int emit_content(emit_cb_t emit_cb, void *data, struct buffer *buffer,
|
||||
size_t len)
|
||||
{
|
||||
struct emit_content_cb_data cb_data;
|
||||
|
||||
cb_data.emit_cb = emit_cb;
|
||||
cb_data.data = data;
|
||||
cb_data.len = len;
|
||||
cb_data.rc = 0;
|
||||
buffer_iterate(buffer, emit_content_cb, &cb_data);
|
||||
if (cb_data.rc)
|
||||
return cb_data.rc;
|
||||
|
||||
return emit_padding(emit_cb, data, buffer->total);
|
||||
}
|
||||
|
||||
/* Convert file meta data and content specified as @content into a
|
||||
* stream of bytes that is reported via the @emit_cb callback. @data is
|
||||
* passed through to the callback for arbitrary use. */
|
||||
int tar_emit_file_from_buffer(char *filename, char *link, size_t len,
|
||||
struct stat *stat, char type,
|
||||
struct buffer *content, emit_cb_t emit_cb,
|
||||
void *data)
|
||||
{
|
||||
int rc;
|
||||
|
||||
DBG("emit tar file=%s type=%d len=%zu", filename, type, len);
|
||||
rc = emit_header(emit_cb, data, filename, link, len, stat, type);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (content)
|
||||
rc = emit_content(emit_cb, data, content, len);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Convert file meta data and content specified as @addr and @len into a
|
||||
* stream of bytes that is reported via the @emit_cb callback. @data is
|
||||
* passed through to the callback for arbitrary use. */
|
||||
int tar_emit_file_from_data(char *filename, char *link, size_t len,
|
||||
struct stat *stat, char type, void *addr,
|
||||
emit_cb_t emit_cb, void *data)
|
||||
{
|
||||
int rc;
|
||||
|
||||
DBG("emit tar file=%s type=%d len=%zu", filename, type, len);
|
||||
rc = emit_header(emit_cb, data, filename, link, len, stat, type);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (addr)
|
||||
rc = emit_data(emit_cb, data, addr, len);
|
||||
|
||||
return rc;
|
||||
}
|
||||
Reference in New Issue
Block a user