/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }