Files
s390-tools/zdev/src/misc.c
Peter Oberparleiter 5693c16894 zdev: Add helper to report warnings only once
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2019-07-31 16:00:40 +02:00

1761 lines
34 KiB
C

/*
* zdev - Modify and display the persistent configuration of devices
*
* Copyright IBM Corp. 2016, 2018
*
* 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 <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <execinfo.h>
#include <limits.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "dasd.h"
#include "device.h"
#include "devtype.h"
#include "misc.h"
#include "path.h"
#define DRYRUN_HEADER_BEGIN ((char) 0x01)
#define DRYRUN_HEADER_END ((char) 0x02)
#define DRYRUN_DATA_END ((char) 0x03)
#define DRYRUN_WRITE "WRITE:"
#define DRYRUN_CMD "CMD:"
#define LONGRUN_INITIAL_USEC 300000
#define LONGRUN_USEC 200000
/* Global variables. */
const char *toolname = NULL;
int verbose = 0;
int debug_enabled;
int quiet = 0;
int force = 0;
int yes = 0;
int found_forceable = 0;
int dryrun = 0;
int stdout_data = 0;
int delayed_errors = 0;
unsigned long longrun_total;
unsigned long longrun_current;
static struct util_list *delayed_messages;
static struct util_list *warn_once_messages;
static FILE *dryrun_file;
static FILE *dryrun_get_file(void)
{
if (!dryrun_file)
dryrun_file = tmpfile();
return dryrun_file;
}
static void dryrun_announce(const char *type, const char *data, ...)
{
va_list args;
char *str;
FILE *fd;
va_start(args, data);
if (vasprintf(&str, data, args) == -1)
oom();
va_end(args);
fd = dryrun_get_file();
if (fd) {
fprintf(fd, "%c%s:%s%c", DRYRUN_HEADER_BEGIN, type, str,
DRYRUN_HEADER_END);
}
free(str);
}
static void dryrun_end_data(void)
{
FILE *fd;
fd = dryrun_get_file();
if (fd)
fprintf(fd, "%c", DRYRUN_DATA_END);
}
#define READ_CHUNK_SIZE 4096
/* Read all data from @fd and return address of resulting buffer in
* @buffer_ptr. If @size_ptr is non-zero, use it to store the size of the
* resulting buffer. Return %EXIT_OK on success. */
exit_code_t misc_read_fd(FILE *fd, void **buffer_ptr, size_t *size_ptr)
{
char *buffer = NULL;
size_t done = 0;
while (!feof(fd)) {
buffer = realloc(buffer, done + READ_CHUNK_SIZE);
if (!buffer)
oom();
done += fread(&buffer[done], 1, READ_CHUNK_SIZE, fd);
if (ferror(fd)) {
free(buffer);
return EXIT_RUNTIME_ERROR;
}
}
buffer = realloc(buffer, done);
if (!buffer && done > 0)
oom();
*buffer_ptr = buffer;
if (size_ptr)
*size_ptr = done;
return EXIT_OK;
}
/* Read text from @fd and return resulting NULL-terminated text buffer.
* If @chomp is non-zero, remove trailing newline character. Return %NULL
* on error or when unprintable characters are read. */
static char *read_fd(FILE *fd, int chomp)
{
char *buffer;
size_t done, i;
if (misc_read_fd(fd, (void **) &buffer, &done))
return NULL;
/* Check if this is a text file at all (required to filter out
* binary sysfs attributes). */
for (i = 0; i < done; i++) {
if (!isgraph(buffer[i]) && !isspace(buffer[i])) {
free(buffer);
return NULL;
}
}
/* Remove trailing new-line character if requested. */
if (chomp && done > 0 && buffer[done - 1] == '\n')
done--;
/* NULL-terminate. */
buffer = realloc(buffer, done + 1);
if (!buffer)
oom();
buffer[done] = 0;
return buffer;
}
static int count_newline(const char *str)
{
int i, newline;
newline = 0;
for (i = 0; str[i]; i++) {
if (str[i] == '\n')
newline++;
}
return newline;
}
/* Display actions recorded during --dry-run. */
static void dryrun_print(void)
{
char *txt, *header, *data, *end, *file;
int cmd, newline;
fseek(dryrun_file, 0, SEEK_SET);
txt = read_fd(dryrun_file, 0);
if (!txt)
return;
info("Skipped actions due to --dry-run:\n");
cmd = 0;
header = txt;
while ((header = strchr(header, DRYRUN_HEADER_BEGIN)) &&
(data = strchr(header, DRYRUN_HEADER_END)) &&
(end = strchr(data, DRYRUN_DATA_END))) {
header++;
*(data++) = 0;
*end = 0;
if (starts_with(header, DRYRUN_WRITE)) {
newline = count_newline(data);
file = header + sizeof(DRYRUN_WRITE);
switch (newline) {
case 0:
/* Show as echo -n */
if (!cmd) {
info(" Run:\n");
cmd = 1;
}
indent(4, "echo -n \"%s\" > %s\n", data, file);
break;
case 1:
/* Show as echo */
if (!cmd) {
info(" Run:\n");
cmd = 1;
}
*(end - 1) = 0;
if (!*data)
indent(4, "echo > %s\n", file);
else {
indent(4, "echo \"%s\" > %s\n", data,
file);
}
break;
default:
/* Show as file write. */
cmd = 0;
info(" Write to %s\n", file);
indent(4, data);
break;
}
} else if (starts_with(header, DRYRUN_CMD)) {
if (!cmd) {
info(" Run:\n");
cmd = 1;
}
indent(4, header + sizeof(DRYRUN_CMD));
}
header = end + 1;
}
free(txt);
}
void misc_exit(void)
{
strlist_free(delayed_messages);
strlist_free(warn_once_messages);
if (dryrun_file) {
if (verbose)
dryrun_print();
fclose(dryrun_file);
}
}
/* Report an error. */
void error(const char *format, ...)
{
va_list args;
if (toolname)
fprintf(stderr, "%s: ", toolname);
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
/* Report an error and exit with the specified error code. */
void __attribute__((noreturn)) fatal(exit_code_t rc, const char *format, ...)
{
va_list args;
if (toolname)
fprintf(stderr, "%s: ", toolname);
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
exit(rc);
}
/* Report an error that can be overridden using --force */
void forceable(const char *format, ...)
{
va_list args;
char *str;
const char *star = force ? "" : " (*)";
int l;
found_forceable = 1;
/* Get error message. */
va_start(args, format);
if (vasprintf(&str, format, args) == -1)
oom();
va_end(args);
/* Remove newline. */
l = strlen(str);
if ((l > 0) && (str[l - 1] == '\n'))
str[l - 1] = 0;
/* Print message. */
if (toolname)
fprintf(stderr, "%s: %s%s\n", toolname, str, star);
else
fprintf(stderr, "%s%s\n", str, star);
free(str);
}
/* Report a warning that can be overridden using --force */
void forceable_warn(const char *format, ...)
{
va_list args;
char *str;
const char *star = force ? "" : " (*)";
int l;
found_forceable = 1;
/* Get error message. */
va_start(args, format);
if (vasprintf(&str, format, args) == -1)
oom();
va_end(args);
/* Remove newline. */
l = strlen(str);
if ((l > 0) && (str[l - 1] == '\n'))
str[l - 1] = 0;
/* Print message. */
fprintf(stderr, "%s%s\n", str, star);
free(str);
}
/* Report a syntax error: print an error message to standard error prefixed
* with toolname, followed by a reference to the --help option. */
void syntax(const char *format, ...)
{
va_list args;
if (toolname)
fprintf(stderr, "%s: ", toolname);
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
if (toolname) {
fprintf(stderr, "Use '%s --help' for more information\n",
toolname);
}
}
/* Report a warning: print an error message to standard error. */
void _warn(const char *format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
/* Report a warning: print an error message to standard error. Only print each
* warning once. */
void _warn_once(const char *format, ...)
{
va_list args;
char *str;
va_start(args, format);
if (vasprintf(&str, format, args) == -1)
oom();
va_end(args);
/* Check if message was printed before. */
if (!warn_once_messages)
warn_once_messages = strlist_new();
if (!strlist_find(warn_once_messages, str)) {
fprintf(stderr, "%s", str);
strlist_add(warn_once_messages, "%s", str);
}
free(str);
}
/* Print an error message indicating an out-of-memory situation and exit. */
void oom(void)
{
if (!toolname)
fprintf(stderr, "Out of memory\n");
else
fprintf(stderr, "%s: Out of memory\n", toolname);
/* We can't rely on our clean-up routines to work reliably during an
* OOM situation, so just exit here. */
exit(EXIT_OUT_OF_MEMORY);
}
#define LINE_SIZE 80
/* Ask user for confirmation. */
bool confirm(const char *format, ...)
{
va_list args;
char *str;
bool rc = false;
char line[LINE_SIZE];
int input;
va_start(args, format);
if (vasprintf(&str, format, args) == -1)
oom();
va_end(args);
if (yes) {
rc = true;
if (verbose)
info("%s - assuming 'yes' due to --yes\n", str);
goto out;
}
/* Get user input. */
input = -1;
while (input == -1) {
printf("%s (yes/no) ", str);
if (!fgets(line, sizeof(line), stdin)) {
warn("Operation canceled due to missing input\n");
input = 0;
} else if (strcasecmp(line, "yes\n") == 0 ||
strcasecmp(line, "y\n") == 0)
input = 1;
else if (strcasecmp(line, "no\n") == 0 ||
strcasecmp(line, "n\n") == 0) {
warn("Operation canceled on user request\n");
input = 0;
} else {
printf("Unrecognized input: %s\n", line);
printf("Please enter 'yes' or 'y' to continue or 'no' "
"or 'n' to abort\n");
}
}
if (input)
rc = true;
out:
free(str);
return rc;
}
/* Return a newly allocated copy of string S. Exit with an error message if
* memory could not be allocated. */
char *misc_strdup(const char *s)
{
char *result = strdup(s);
if (!result)
oom();
return result;
}
/* Return a newly allocated buffer with size SIZE. Exit with an error message
* if memory could not be allocated. */
void *misc_malloc(size_t size)
{
void *buffer;
buffer = malloc(size);
if (!buffer) {
verb("Failed to allocate %zu bytes\n", size);
oom();
}
memset(buffer, 0, size);
return buffer;
}
/* Return a newly allocated buffer containing the result of the specified
* string format arguments. */
char *misc_asprintf(const char *fmt, ...)
{
char *str;
va_list args;
int rc;
va_start(args, fmt);
rc = vasprintf(&str, fmt, args);
va_end(args);
if (rc == -1)
oom();
return str;
}
/* Run specified command and return exit code or < 0 on error. */
int misc_system(err_t err, const char *fmt, ...)
{
char *cmd;
va_list args;
int rc;
va_start(args, fmt);
rc = vasprintf(&cmd, fmt, args);
va_end(args);
if (rc == -1)
oom();
if (dryrun) {
dryrun_announce(DRYRUN_CMD, cmd);
dryrun_end_data();
free(cmd);
return 0;
}
debug("Running command: %s\n", cmd);
rc = system(cmd);
debug("rc=%d\n", rc);
if (rc == -1) {
err_t_print(err, "Could not start command: %s\n", cmd);
} else if (WIFEXITED(rc)) {
rc = WEXITSTATUS(rc);
if (rc != 0) {
err_t_print(err, "Command failed (exit code %d): %s\n",
rc, cmd);
}
} else if (WIFSIGNALED(rc)) {
err_t_print(err, "Command failed (killed by signal %d): %s\n",
WTERMSIG(rc), cmd);
rc = -2;
} else {
err_t_print(err, "Command failed (status %d): %s\n", rc, cmd);
rc = -3;
}
free(cmd);
return rc;
}
/* Create a new temporary file and return a newly allocated copy of its name
* and open file descriptor. */
exit_code_t misc_mktemp(char **filename, int *fd)
{
char template[] = "/tmp/chzdev.XXXXXX";
int rc;
debug("Creating temporary file\n");
rc = mkstemp(template);
if (rc == -1) {
delayed_err("Could not create temporary file: %s\n",
strerror(errno));
return EXIT_RUNTIME_ERROR;
}
if (fd)
*fd = rc;
else
close(rc);
if (filename)
*filename = misc_strdup(template);
return EXIT_OK;
}
/* Read a directory and add all contained filenames except . and .. to strlist
* LIST. Apply FILTER callback to filenames if supplied. */
bool misc_read_dir(const char *path, struct util_list *list,
bool (*filter)(const char *, void *), void *data)
{
DIR *dir;
struct dirent *de;
debug("Reading contents of directory %s\n", path);
dir = opendir(path);
if (!dir)
return false;
while ((de = readdir(dir))) {
if (de->d_name[0] == '.') {
if (de->d_name[1] == 0)
continue;
if (de->d_name[1] == '.' && de->d_name[2] == 0)
continue;
}
if (filter && !filter(de->d_name, data))
continue;
strlist_add(list, de->d_name);
}
closedir(dir);
return true;
}
/* Print a text string indented by I spaces. */
void indent(unsigned int i, const char *format, ...)
{
va_list args;
char *str, *curr, *next, *last;
/* Get text message. */
va_start(args, format);
if (vasprintf(&str, format, args) == -1)
oom();
va_end(args);
last = str + strlen(str);
next = str;
while ((curr = strsep(&next, "\n"))) {
/* Prevent extra newline at end. */
if (curr == last)
break;
if (!*curr)
printf("\n");
else
printf("%*s%s\n", i, "", curr);
}
free(str);
}
/* Allocate and initialize a new list of struct strlist_node. */
struct util_list *strlist_new(void)
{
struct util_list *list;
list = misc_malloc(sizeof(struct util_list));
util_list_init(list, struct strlist_node, node);
return list;
}
/* Release all resources associated with the specified list. */
void strlist_free(struct util_list *list)
{
struct strlist_node *s, *n;
if (!list)
return;
util_list_iterate_safe(list, s, n) {
util_list_remove(list, s);
free(s);
}
free(list);
}
/* Allocate and initialize a new list of struct strlist_node which is a
* copy of %list. */
struct util_list *strlist_copy(struct util_list *list)
{
struct util_list *copy;
struct strlist_node *s;
copy = strlist_new();
util_list_iterate(list, s)
strlist_add(copy, s->str);
return copy;
}
/* Add a string to the list. */
void strlist_add(struct util_list *list, const char *fmt, ...)
{
struct strlist_node *s;
char *str;
va_list args;
int rc;
va_start(args, fmt);
rc = vasprintf(&str, fmt, args);
va_end(args);
if (rc == -1)
oom();
s = misc_malloc(sizeof(struct strlist_node) + strlen(str) +
/* NUL */ 1);
strcpy(s->str, str);
free(str);
util_list_add_tail(list, s);
}
/* Add a string to the list only if it has not been previously added. Return
* %true if string was added, %false if string was already in @list. */
bool strlist_add_unique(struct util_list *list, const char *fmt, ...)
{
struct strlist_node *s;
char *str;
va_list args;
int rc;
va_start(args, fmt);
rc = vasprintf(&str, fmt, args);
va_end(args);
if (rc == -1)
oom();
if (strlist_find(list, str)) {
free(str);
return false;
}
s = misc_malloc(sizeof(struct strlist_node) + strlen(str) +
/* NUL */ 1);
strcpy(s->str, str);
free(str);
util_list_add_tail(list, s);
return true;
}
/* Parse STR as delimiter-separated list of strings and add each to LIST. */
void strlist_add_multi(struct util_list *list, const char *str,
const char *delim, int allow_empty)
{
char *copy, *curr, *next;
copy = misc_strdup(str);
next = copy;
while ((curr = strsep(&next, delim))) {
if (!*curr && !allow_empty)
continue;
strlist_add(list, "%s", curr);
}
free(copy);
}
/* Find a string in a strlist. */
struct strlist_node *strlist_find(struct util_list *list, const char *str)
{
struct strlist_node *s;
util_list_iterate(list, s) {
if (strcmp(s->str, str) == 0)
return s;
}
return NULL;
}
/* Return a string containing all strlist_nodes concatenated by delimiter. */
char *strlist_flatten(struct util_list *list, const char *delim)
{
size_t len, dlen, copied;
struct strlist_node *s;
int first;
char *str;
/* Determine length of resulting string. */
dlen = strlen(delim);
len = 1;
first = 1;
util_list_iterate(list, s) {
len += strlen(s->str);
if (first)
first = 0;
else
len += dlen;
}
/* Combine resulting string. */
str = misc_malloc(len);
first = 1;
copied = 0;
util_list_iterate(list, s) {
copied += sprintf(&str[copied], "%s%s", first ? "" : delim,
s->str);
first = 0;
}
return str;
}
static void strlist_to_argv(struct util_list *list, char ***argv_ptr,
size_t *argc_ptr)
{
char **argv;
size_t argc;
struct strlist_node *s;
size_t i;
argc = util_list_len(list);
argv = misc_malloc(argc * sizeof(char *));
i = 0;
util_list_iterate(list, s)
argv[i++] = misc_strdup(s->str);
*argv_ptr = argv;
*argc_ptr = argc;
}
/* Remove and release all elements from a strlist. */
static void strlist_clear(struct util_list *list)
{
struct strlist_node *s, *n;
util_list_iterate_safe(list, s, n) {
util_list_remove(list, s);
free(s);
}
}
typedef int (*qsort_cmp_t)(const void *, const void *);
/* Sort entries in strlist according to compare function CMP and remove
* duplicates. Note: CMP gets char **s as parameter. */
void strlist_sort_unique(struct util_list *list,
int (*cmp)(const void *, const void *))
{
char **argv;
size_t argc, i;
char *last;
if (util_list_is_empty(list))
return;
strlist_to_argv(list, &argv, &argc);
qsort(argv, argc, sizeof(char *), cmp);
strlist_clear(list);
last = NULL;
for (i = 0; i < argc; i++) {
if (last && strcmp(argv[i], last) == 0)
continue;
last = argv[i];
strlist_add(list, "%s", argv[i]);
}
for (i = 0; i < argc; i++)
free(argv[i]);
free(argv);
}
/* A sort function for sorting strings. */
int str_cmp(const void *a, const void *b)
{
return strcmp(*(char * const *) a, *(char * const *) b);
}
/* Allocate and initialize a new list of pointers. */
struct util_list *ptrlist_new(void)
{
struct util_list *list;
list = misc_malloc(sizeof(struct util_list));
util_list_init(list, struct ptrlist_node, node);
return list;
}
/* Release all resources associated with the specified list. If FREE_PTR is
* non-zero, also call free() for ptrlist_node->ptr. */
void ptrlist_free(struct util_list *list, int free_ptr)
{
struct ptrlist_node *p, *n;
if (!list)
return;
util_list_iterate_safe(list, p, n) {
util_list_remove(list, p);
if (free_ptr)
free(p->ptr);
free(p);
}
free(list);
}
/* Add pointer to the list. */
void ptrlist_add(struct util_list *list, void *ptr)
{
struct ptrlist_node *s;
s = misc_malloc(sizeof(struct ptrlist_node));
s->ptr = ptr;
util_list_add_tail(list, s);
}
/* Add pointer to the list before entry. */
void ptrlist_add_before(struct util_list *list, struct ptrlist_node *entry,
void *ptr)
{
struct ptrlist_node *s;
s = misc_malloc(sizeof(struct ptrlist_node));
s->ptr = ptr;
util_list_add_prev(list, s, entry);
}
/* Find a pointer. */
static struct ptrlist_node *ptrlist_find(struct util_list *list, void *ptr)
{
struct ptrlist_node *s;
util_list_iterate(list, s) {
if (s->ptr == ptr)
return s;
}
return NULL;
}
/* Remove a pointer from a ptrlist. */
void ptrlist_remove(struct util_list *list, void *ptr)
{
struct ptrlist_node *p;
p = ptrlist_find(list, ptr);
if (p)
util_list_remove(list, p);
}
/* Add a pointer to the list only if it hasn't been added before. */
void ptrlist_add_unique(struct util_list *list, void *ptr)
{
struct ptrlist_node *s;
if (ptrlist_find(list, ptr))
return;
s = misc_malloc(sizeof(struct ptrlist_node));
s->ptr = ptr;
util_list_add_tail(list, s);
}
/* Move ptrlist_node %node from list %from to %to. */
void ptrlist_move(struct util_list *to, struct util_list *from,
struct ptrlist_node *node)
{
util_list_remove(from, node);
util_list_add_tail(to, node);
}
/* Check if file is a block or character special file. */
bool file_is_devnode(const char *path)
{
struct stat s;
debug("Checking if file is a device node: %s\n", path);
if (stat(path, &s) != 0)
return false;
if (!S_ISBLK(s.st_mode) && !S_ISCHR(s.st_mode))
return false;
return true;
}
/* Remove file. */
exit_code_t remove_file(const char *path)
{
debug("Removing file %s\n", path);
if (dryrun) {
dryrun_announce(DRYRUN_CMD, "rm -f %s\n", path);
dryrun_end_data();
return EXIT_OK;
}
if (unlink(path) == 0)
return EXIT_OK;
delayed_err("Could not remove file %s: %s\n", path, strerror(errno));
return EXIT_RUNTIME_ERROR;
}
/* Read file as text and return NULL-terminated contents. Remove trailing
* newline if CHOMP is specified. Handle error messages according to @err. */
char *misc_read_text_file(const char *path, int chomp, err_t err)
{
char *buffer = NULL;
FILE *fd;
fd = misc_fopen(path, "r");
if (!fd)
goto out;
buffer = read_fd(fd, chomp);
misc_fclose(fd);
out:
if (!buffer) {
err_t_print(err, "Could not read file %s: %s\n", path,
strerror(errno));
}
return buffer;
}
/* Run a command and return its output as text string. Remove trailing
* newline if CHOMP is specified. Handle error messages according to @err. */
char *misc_read_cmd_output(const char *cmd, int chomp, err_t err)
{
char *buffer = NULL;
FILE *fd;
debug("Running command: %s\n", cmd);
fd = popen(cmd, "r");
if (!fd)
goto out;
buffer = read_fd(fd, chomp);
pclose(fd);
out:
if (!buffer) {
err_t_print(err, "Could not run command %s: %s\n", cmd,
strerror(errno));
}
return buffer;
}
/* Run a configuration modifying command and return its output as text string.
* Remove trailing newline if CHOMP is specified. Handle error messages
* according to @err. */
char *config_read_cmd_output(const char *cmd, int chomp, err_t err)
{
char *buffer = NULL;
FILE *fd;
fd = misc_popen(cmd, "r");
if (!fd)
goto out;
buffer = read_fd(fd, chomp);
misc_pclose(fd);
out:
if (!buffer) {
err_t_print(err, "Could not run command %s: %s\n", cmd,
strerror(errno));
}
return buffer;
}
/* Write text to file. Handle error messages according to @err. */
static int write_text(const char *path, const char *text, err_t err)
{
size_t len = strlen(text);
FILE *fd;
int rc;
fd = misc_fopen(path, "w");
if (!fd)
goto err;
if (fwrite(text, 1, len, fd) != len)
goto err;
if (misc_fclose(fd)) {
fd = NULL;
goto err;
}
return 0;
err:
rc = errno;
if (err == err_print)
error("Could not write file %s: %s\n", path, strerror(rc));
else if (err == err_delayed_print) {
delayed_err("Could not write file %s: %s\n", path,
strerror(rc));
}
if (fd)
misc_fclose(fd);
return rc;
}
exit_code_t misc_write_text_file(const char *path, const char *text, err_t err)
{
if (write_text(path, text, err))
return EXIT_RUNTIME_ERROR;
return EXIT_OK;
}
/* Write a text file. If writing fails with errno EAGAIN, retry the operation
* after a short delay. */
exit_code_t misc_write_text_file_retry(const char *path, const char *text,
err_t err)
{
long delay_ns[] = {
0,
100000000,
200000000,
500000000,
};
int rc;
unsigned int retry;
struct timespec ts;
for (retry = 0; retry < ARRAY_SIZE(delay_ns); retry++) {
if (delay_ns[retry] > 0) {
debug("Retrying write after %ld ns\n", delay_ns[retry]);
ts.tv_sec = 0;
ts.tv_nsec = delay_ns[retry];
nanosleep(&ts, NULL);
}
rc = write_text(path, text, err);
if (rc != EAGAIN)
break;
}
return rc == 0 ? EXIT_OK : EXIT_RUNTIME_ERROR;
}
#define READLINE_SIZE 4096
/* Return a newly allocated string containing the contents of the symbolic link
* at the specified path or NULL on error. */
char *misc_readlink(const char *path)
{
char *name, *name2;
ssize_t len;
debug("Reading link %s\n", path);
name = misc_malloc(READLINE_SIZE);
len = readlink(path, name, READLINE_SIZE - 1);
if (len < 0) {
free(name);
return NULL;
}
name[len++] = 0;
name2 = realloc(name, len);
return (name2) ? name2 : name;
}
/* Determine configuration set. */
config_t get_config(int active, int persistent, int autoconf)
{
config_t config = 0;
if (active)
config |= config_active;
if (persistent)
config |= config_persistent;
if (autoconf)
config |= config_autoconf;
return config;
}
/* Determine if program is running under z/VM. */
bool is_zvm(void)
{
static int done;
static bool zvm = false;
char *path, *sysinfo;
if (done)
return zvm;
path = path_get_proc("sysinfo");
sysinfo = misc_read_text_file(path, 0, err_ignore);
if (!sysinfo) {
warn("Failed to read %s: Could not determine if system is a "
"z/VM guest\n", path);
} else if (strstr(sysinfo, "\nVM00 Name:"))
zvm = true;
free(sysinfo);
free(path);
done = 1;
return zvm;
}
bool starts_with(const char *str, const char *s)
{
size_t len;
len = strlen(s);
if (strncmp(str, s, len) == 0)
return true;
return false;
}
bool starts_with_nocase(const char *str, const char *s)
{
size_t len;
len = strlen(s);
if (strncasecmp(str, s, len) == 0)
return true;
return false;
}
bool ends_with(const char *str, const char *s)
{
size_t str_len, s_len;
str_len = strlen(str);
s_len = strlen(s);
if (str_len < s_len)
return false;
if (strcmp(str + str_len - s_len, s) != 0)
return false;
return true;
}
/* Split a line into an array of arguments. */
void line_split(const char *line, int *argc, char ***argv)
{
char *t, *p, *copy, *c;
char **a;
int num, i;
/* Count arguments. */
copy = misc_strdup(line);
c = copy;
num = 0;
while (strtok_r(c, " \t\n", &p)) {
c = NULL;
num++;
}
free(copy);
/* Copy arguments. */
a = misc_malloc(sizeof(char *) * num);
copy = misc_strdup(line);
c = copy;
for (i = 0; i < num; i++) {
t = strtok_r(c, " \n\t", &p);
c = NULL;
a[i] = misc_strdup(t);
}
free(copy);
*argv = a;
*argc = num;
}
void line_free(int argc, char **argv)
{
int i;
if (!argv)
return;
for (i = 0; i < argc; i++)
free(argv[i]);
free(argv);
}
#define BACKTRACE_MAX 32
/* Used for debugging. */
void print_trace(void)
{
void *bt[BACKTRACE_MAX];
char **bt_sym;
int num, i;
fprintf(stderr, "DEBUG: Backtrace: \n");
num = backtrace(bt, ARRAY_SIZE(bt));
bt_sym = backtrace_symbols(bt, num);
for (i = 0; i < num; i++)
fprintf(stderr, "DEBUG: %s\n", bt_sym[i]);
free(bt_sym);
}
/* Return a textual representation of @config. */
const char *config_to_str(config_t config)
{
switch (config) {
case config_active:
return "active";
case config_persistent:
return "persistent";
case config_autoconf:
return "autoconf";
case config_all:
return "all";
}
return "multiple";
}
/* Convert textual config representation to config_t. */
bool str_to_config(const char *str, config_t *config_ptr)
{
if (strcasecmp(str, "active") == 0)
*config_ptr = config_active;
else if (strcasecmp(str, "persistent") == 0)
*config_ptr = config_persistent;
else if (strcasecmp(str, "autoconf") == 0)
*config_ptr = config_autoconf;
else if (strcasecmp(str, "all") == 0)
*config_ptr = config_all;
else
return false;
return true;
}
/* Prefix special characters (backslash, double quote, newline) with a
* backslash. Put double-quotes around string if it contains spaces or
* @force_quotes is set. */
char *quote_str(const char *value, int force_quotes)
{
int i, j, special, normal, spaces;
char *str, c;
special = spaces = normal = 0;
for (i = 0; (c = value[i]); i++) {
if (c == '\\' || c == '"' || c == '\n')
special++;
else if (isspace(c))
spaces++;
else
normal++;
}
/* Quick exit if nothing needs escaping. */
if (special == 0) {
if (force_quotes || spaces > 0 ||
(normal + special + spaces == 0))
return misc_asprintf("\"%s\"", value);
else
return misc_strdup(value);
}
/* Prefix all backslashes and double-quotes with a backslash. */
str = misc_malloc(special * 2 + spaces + normal + /* quotes */ 2 +
/* nil */ 1);
j = 0;
str[j++] = '"';
for (i = 0; (c = value[i]); i++) {
if (c == '\\' || c == '"' || c == '\n') {
str[j++] = '\\';
if (c == '\n')
c = 'n';
}
str[j++] = c;
}
str[j++] = '"';
return str;
}
/* Remove quotes and escaping backslashes. */
char *unquote_str(const char *value)
{
char *str, c;
int i, j, first, last;
str = misc_malloc(strlen(value) + 1);
/* Find first and last non-space characters. */
for (first = 0; value[first] && isspace(value[first]); first++);
for (last = strlen(value) - 1; last >= first && isspace(value[last]);
last--);
j = 0;
for (i = first; i <= last; i++) {
c = value[i];
if (c == '"' && i == first)
continue;
if (i == last) {
if (c == '"')
break;
} else if (c == '\\') {
c = value[++i];
if (c == 'n')
c = '\n';
}
str[j++] = c;
}
return str;
}
/* Return a copy of @str without any leading or trailing whitespaces. */
char *shrink_str(const char *str)
{
int first, last;
char *s;
for (first = 0; str[first] && isspace(str[first]); first++);
for (last = strlen(str) - 1; last >= first && isspace(str[last]);
last--);
s = misc_malloc(last - first + 2);
memcpy(s, &str[first], last - first + 1);
return s;
}
/* Mark standard output stream as being used for data. */
void set_stdout_data(void)
{
stdout_data = 1;
}
/* Store an error message for delayed reporting. */
void delayed_err(const char *format, ...)
{
va_list args;
char *str;
delayed_errors++;
/* Get error message. */
va_start(args, format);
if (vasprintf(&str, format, args) == -1)
oom();
va_end(args);
if (!delayed_messages)
delayed_messages = strlist_new();
strlist_add(delayed_messages, "Error: %s", str);
free(str);
}
/* Store a warning message for delayed reporting. */
void delayed_warn(const char *format, ...)
{
va_list args;
char *str;
/* Get warning message. */
va_start(args, format);
if (vasprintf(&str, format, args) == -1)
oom();
va_end(args);
if (!delayed_messages)
delayed_messages = strlist_new();
strlist_add(delayed_messages, "Warning: %s", str);
free(str);
}
/* Store an information message for delayed reporting. */
void delayed_info(const char *format, ...)
{
va_list args;
char *str;
if (quiet)
return;
/* Get info message. */
va_start(args, format);
if (vasprintf(&str, format, args) == -1)
oom();
va_end(args);
if (!delayed_messages)
delayed_messages = strlist_new();
strlist_add(delayed_messages, "%s", str);
free(str);
}
/* Store a message about an error that can be overridden using --force. */
void delayed_forceable(const char *format, ...)
{
va_list args;
char *str;
const char *star = force ? "" : " (*)";
const char *severity = force ? "Warning:" : "Error:";
int l;
found_forceable = 1;
delayed_errors++;
/* Get error message. */
va_start(args, format);
if (vasprintf(&str, format, args) == -1)
oom();
va_end(args);
/* Remove newline. */
l = strlen(str);
if ((l > 0) && (str[l - 1] == '\n'))
str[l - 1] = 0;
/* Add to delayed messages. */
if (!delayed_messages)
delayed_messages = strlist_new();
strlist_add(delayed_messages, "%s %s%s\n", severity, str, star);
free(str);
}
void delayed_print(int indent)
{
struct strlist_node *s, *n;
if (!delayed_messages)
return;
util_list_iterate_safe(delayed_messages, s, n) {
util_list_remove(delayed_messages, s);
if (starts_with(s->str, "Warning:") ||
starts_with(s->str, "Error:"))
fprintf(stderr, "%*s%s", indent, "", s->str);
else
info("%*s%s", indent, "", s->str);
free(s);
}
delayed_errors = 0;
}
void delayed_clear(void)
{
if (!delayed_messages)
return;
strlist_clear(delayed_messages);
delayed_errors = 0;
}
bool delayed_messages_available(void)
{
if (!delayed_messages || util_list_is_empty(delayed_messages))
return false;
return true;
}
/* Determine width of terminal. */
int get_columns(void)
{
struct winsize w;
static int columns = -1;
if (!is_terminal())
return INT_MAX;
if (columns == -1) {
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) < 0 || w.ws_col == 0)
columns = 80;
else
columns = w.ws_col;
}
return columns;
}
FILE *misc_fopen(const char *path, const char *mode)
{
debug("Opening file %s for mode %s\n", path, mode);
/* Redirect writes in case of --dry-run. */
if (dryrun && (strchr(mode, 'w') || strchr(mode, 'a'))) {
if (verbose) {
dryrun_announce(DRYRUN_WRITE, path);
return dryrun_get_file();
}
path = "/dev/null";
}
return fopen(path, mode);
}
FILE *misc_popen(const char *command, const char *type)
{
debug("Opening pipe to command %s type %s\n", command, type);
/* Ignore command in case of --dry-run. */
if (dryrun) {
if (verbose) {
dryrun_announce(DRYRUN_CMD, command);
dryrun_end_data();
}
return fopen("/dev/null", type);
}
return popen(command, type);
}
int misc_fclose(FILE *fd)
{
if (fd == dryrun_file) {
dryrun_end_data();
return 0;
}
return fclose(fd);
}
int misc_pclose(FILE *fd)
{
if (fd == dryrun_file) {
dryrun_end_data();
return 0;
}
return pclose(fd);
}
bool is_terminal(void)
{
static bool result;
static int done;
if (done)
return result;
if (isatty(fileno(stdout)))
result = true;
else
result = false;
done = 1;
return result;
}
static const char *longrun_text;
static int longrun_shown;
static int longrun_pairs;
static void setup_timer(long usec)
{
static struct itimerval timer;
memset(&timer, 0, sizeof(struct itimerval));
timer.it_value.tv_usec = usec;
setitimer(ITIMER_REAL, &timer, NULL);
}
static void longrun_update(void)
{
if (longrun_pairs) {
if (!quiet) {
printf("PROGRESS_TEXT=\"%s\" PROGRESS_CURR=\"%lu\" "
"PROGRESS_TOTAL=\"%lu\"\n", longrun_text,
longrun_current, longrun_total);
}
} else {
info("\r%s: %5.1f%% (%lu/%lu)", longrun_text,
(100.0 * longrun_current) / longrun_total, longrun_current,
longrun_total);
}
fflush(stdout);
longrun_shown = 1;
}
static void longrun_handler(int signal)
{
longrun_update();
setup_timer(LONGRUN_USEC);
}
void longrun_start(const char *text, int pairs)
{
struct sigaction act;
if (!is_terminal() && !pairs)
return;
longrun_text = text;
longrun_pairs = pairs;
longrun_current = 0;
longrun_shown = 0;
/* Setup timer for progress update. */
memset(&act, 0, sizeof(struct sigaction));
act.sa_handler = &longrun_handler;
sigaction(SIGALRM, &act, NULL);
setup_timer(LONGRUN_INITIAL_USEC);
}
void longrun_stop(void)
{
struct sigaction act;
if (!is_terminal() && !longrun_pairs)
return;
/* Reset timer. */
setup_timer(0);
memset(&act, 0, sizeof(struct sigaction));
act.sa_flags = SA_RESETHAND;
sigaction(SIGALRM, &act, NULL);
if (longrun_shown) {
longrun_current = longrun_total;
longrun_update();
if (!longrun_pairs)
info("\n");
}
}
/* /a/b/ -> /b/ */
char *skip_comp(char *s)
{
if (!s)
return NULL;
return strchr(s + 1, '/');
}
/* Swap @len bytes in @addr according to @pos. */
void byte_swap(uint8_t *addr, unsigned int *pos, unsigned num)
{
unsigned int i;
uint8_t s;
for (i = 0; i < num; i++) {
s = addr[i];
addr[i] = addr[pos[i]];
addr[pos[i]] = s;
}
}
/* Check if the specified text string @str is a valid hexadecimal number. */
bool valid_hex(const char *str)
{
char *err;
err = NULL;
strtoll(str, &err, 16);
if (err && *err == 0)
return true;
return false;
}
void debug_init(int argc, char *argv[])
{
int i;
char *zdev_debug;
zdev_debug = getenv("ZDEV_DEBUG");
if (zdev_debug)
debug_enabled = atoi(zdev_debug);
if (!debug_enabled)
return;
fprintf(stderr, "DEBUG: Tool version: %s\n", RELEASE_STRING);
fprintf(stderr, "DEBUG: Tool invocation: ");
for (i = 0; i < argc; i++)
fprintf(stderr, "%s\"%s\"", i > 0 ? ", " : "", argv[i]);
fprintf(stderr, "\n");
}
/* Return the last occurrence of @needle in @haystack, or %NULL if @needle
* was not found. */
char *misc_strrstr(const char *haystack, const char *needle)
{
char *result, *next;
result = strstr(haystack, needle);
if (result) {
while ((next = strstr(result + 1, needle)))
result = next;
}
return result;
}