X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=procps%2Fsysctl.c;h=6d77185caec8ef5b2c6887b2a1df85f749acab13;hb=22c75924daa41b7ea097796afd4baafa2fc99d05;hp=a1294801798eb6f3aebc2d42dea78bab5e3b6cf9;hpb=5a28a25b9dd81e0975532458723c4244ff532e58;p=oweals%2Fbusybox.git diff --git a/procps/sysctl.c b/procps/sysctl.c index a12948017..6d77185ca 100644 --- a/procps/sysctl.c +++ b/procps/sysctl.c @@ -4,317 +4,344 @@ * * Copyright 1999 George Staikos * - * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * Licensed under GPLv2 or later, see file LICENSE in this source tree. * * Changelog: - * v1.01: - * - added -p to preload values from a file - * v1.01.1 - * - busybox applet aware by - * + * v1.01 - added -p to preload values from a file + * v1.01.1 - busybox applet aware by */ +//config:config BB_SYSCTL +//config: bool "sysctl (7.4 kb)" +//config: default y +//config: help +//config: Configure kernel parameters at runtime. + +//applet:IF_BB_SYSCTL(APPLET_NOEXEC(sysctl, sysctl, BB_DIR_SBIN, BB_SUID_DROP, sysctl)) + +//kbuild:lib-$(CONFIG_BB_SYSCTL) += sysctl.o + +//usage:#define sysctl_trivial_usage +//usage: "-p [-enq] [FILE...] / [-enqaw] [KEY[=VALUE]]..." +//usage:#define sysctl_full_usage "\n\n" +//usage: "Show/set kernel parameters\n" +//usage: "\n -p Set values from FILEs (default /etc/sysctl.conf)" +//usage: "\n -e Don't warn about unknown keys" +//usage: "\n -n Don't show key names" +//usage: "\n -q Quiet" +//usage: "\n -a Show all values" +/* Same as -a, no need to show it */ +/* //usage: "\n -A Show all values in table form" */ +//usage: "\n -w Set values" +//usage: +//usage:#define sysctl_example_usage +//usage: "sysctl [-n] [-e] variable...\n" +//usage: "sysctl [-n] [-e] [-q] -w variable=value...\n" +//usage: "sysctl [-n] [-e] -a\n" +//usage: "sysctl [-n] [-e] [-q] -p file (default /etc/sysctl.conf)\n" +//usage: "sysctl [-n] [-e] -A\n" #include "libbb.h" -/* - * Function Prototypes - */ -static int sysctl_read_setting(const char *setting, int output); -static int sysctl_write_setting(const char *setting, int output); -static int sysctl_preload_file(const char *filename, int output); -static int sysctl_display_all(const char *path, int output, int show_table); - -/* - * Globals... - */ -static const char ETC_SYSCTL_CONF[] ALIGN1 = "/etc/sysctl.conf"; -static const char PROC_SYS[] ALIGN1 = "/proc/sys/"; -enum { strlen_PROC_SYS = sizeof(PROC_SYS) - 1 }; - -/* error messages */ -static const char ERR_UNKNOWN_PARAMETER[] ALIGN1 = - "error: unknown parameter '%s'"; -static const char ERR_MALFORMED_SETTING[] ALIGN1 = - "error: malformed setting '%s'"; -static const char ERR_NO_EQUALS[] ALIGN1 = - "error: '%s' must be of the form name=value"; -static const char ERR_INVALID_KEY[] ALIGN1 = - "error: '%s' is an unknown key"; -static const char ERR_UNKNOWN_WRITING[] ALIGN1 = - "error setting key '%s'"; -static const char ERR_UNKNOWN_READING[] ALIGN1 = - "error reading key '%s'"; -static const char ERR_PERMISSION_DENIED[] ALIGN1 = - "error: permission denied on key '%s'"; -static const char ERR_PRELOAD_FILE[] ALIGN1 = - "error: cannot open preload file '%s'"; -static const char WARN_BAD_LINE[] ALIGN1 = - "warning: %s(%d): invalid syntax, continuing"; - - -static void dwrite_str(int fd, const char *buf) -{ - write(fd, buf, strlen(buf)); -} - -/* - * sysctl_main()... - */ -int sysctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; -int sysctl_main(int argc, char **argv) +enum { + FLAG_SHOW_KEYS = 1 << 0, + FLAG_SHOW_KEY_ERRORS = 1 << 1, + FLAG_TABLE_FORMAT = 1 << 2, /* not implemented */ + FLAG_SHOW_ALL = 1 << 3, + FLAG_PRELOAD_FILE = 1 << 4, + /* NB: procps 3.2.8 does not require -w for KEY=VAL to work, it only rejects non-KEY=VAL form */ + FLAG_WRITE = 1 << 5, + FLAG_QUIET = 1 << 6, +}; +#define OPTION_STR "neAapwq" + +static void sysctl_dots_to_slashes(char *name) { - int retval = 0; - int output = 1; - int write_mode = 0; - int switches_allowed = 1; - - if (argc < 2) - bb_show_usage(); - - argv++; - - for (; *argv /*&& **argv*/; argv++) { - if (switches_allowed && **argv == '-') { /* we have a switch */ - switch ((*argv)[1]) { - case 'n': - output = 0; - break; - case 'w': - write_mode = 1; - switches_allowed = 0; - break; - case 'p': - argv++; - return sysctl_preload_file(((*argv /*&& **argv*/) ? *argv : ETC_SYSCTL_CONF), - output); - case 'a': - case 'A': - return sysctl_display_all(PROC_SYS, output, - ((*argv)[1] == 'A')); - default: - bb_error_msg(ERR_UNKNOWN_PARAMETER, *argv); - /* fall through */ - //case 'h': - //case '?': - bb_show_usage(); + char *cptr, *last_good, *end, *slash; + char end_ch; + + end = strchrnul(name, '='); + + slash = strchrnul(name, '/'); + if (slash < end + && strchrnul(name, '.') < slash + ) { + /* There are both dots and slashes, and 1st dot is + * before 1st slash. + * (IOW: not raw, unmangled a/b/c.d format) + * + * procps supports this syntax for names with dots: + * net.ipv4.conf.eth0/100.mc_forwarding + * (dots and slashes are simply swapped) + */ + while (end != name) { + end--; + if (*end == '.') *end = '/'; + else if (*end == '/') *end = '.'; + } + return; + } + /* else: use our old behavior: */ + + /* Convert minimum number of '.' to '/' so that + * we end up with existing file's name. + * + * Example from bug 3894: + * net.ipv4.conf.eth0.100.mc_forwarding -> + * net/ipv4/conf/eth0.100/mc_forwarding + * NB: net/ipv4/conf/eth0/mc_forwarding *also exists*, + * therefore we must start from the end, and if + * we replaced even one . -> /, start over again, + * but never replace dots before the position + * where last replacement occurred. + * + * Another bug we later had is that + * net.ipv4.conf.eth0.100 + * (without .mc_forwarding) was mishandled. + * + * To set up testing: modprobe 8021q; vconfig add eth0 100 + */ + end_ch = *end; + *end = '.'; /* trick the loop into trying full name too */ + + last_good = name - 1; + again: + cptr = end; + while (cptr > last_good) { + if (*cptr == '.') { + *cptr = '\0'; + //bb_error_msg("trying:'%s'", name); + if (access(name, F_OK) == 0) { + *cptr = '/'; + //bb_error_msg("replaced:'%s'", name); + last_good = cptr; + goto again; } - } else { - switches_allowed = 0; - if (write_mode) - retval |= sysctl_write_setting(*argv, output); - else - sysctl_read_setting(*argv, output); + *cptr = '.'; } + cptr--; } - return retval; -} /* end sysctl_main() */ - -/* - * sysctl_preload_file - * preload the sysctl's from a conf file - * - we parse the file and then reform it (strip out whitespace) - */ -#define PRELOAD_BUF 256 + *end = end_ch; +} -static int sysctl_preload_file(const char *filename, int output) +static int sysctl_act_on_setting(char *setting) { - int lineno; - char oneline[PRELOAD_BUF]; - char buffer[PRELOAD_BUF]; - char *name, *value; - FILE *fp; - - fp = fopen(filename, "r"); - if (fp == NULL) { - bb_error_msg_and_die(ERR_PRELOAD_FILE, filename); + int fd, retval = EXIT_SUCCESS; + char *cptr, *outname; + char *value = value; /* for compiler */ + bool writing = (option_mask32 & FLAG_WRITE); + + outname = xstrdup(setting); + + cptr = outname; + while (*cptr) { + if (*cptr == '/') + *cptr = '.'; + else if (*cptr == '.') + *cptr = '/'; + cptr++; } - lineno = 0; - while (fgets(oneline, sizeof(oneline) - 1, fp)) { - lineno++; - trim(oneline); - if (oneline[0] == '#' || oneline[0] == ';') - continue; - if (!oneline[0] || !oneline[1]) - continue; - - name = strtok(oneline, "="); - if (!name) { - bb_error_msg(WARN_BAD_LINE, filename, lineno); - continue; + cptr = strchr(setting, '='); + if (cptr) + writing = 1; + if (writing) { + if (!cptr) { + bb_error_msg("error: '%s' must be of the form name=value", + outname); + retval = EXIT_FAILURE; + goto end; } - trim(name); - if (!*name) { - bb_error_msg(WARN_BAD_LINE, filename, lineno); - continue; + value = cptr + 1; /* point to the value in name=value */ + if (setting == cptr /* "name" can't be empty */ + /* || !*value - WRONG: "sysctl net.ipv4.ip_local_reserved_ports=" is a valid syntax (clears the value) */ + ) { + bb_error_msg("error: malformed setting '%s'", outname); + retval = EXIT_FAILURE; + goto end; } - - value = strtok(NULL, "\n\r"); - if (!value) { - bb_error_msg(WARN_BAD_LINE, filename, lineno); - continue; - } - while (*value == ' ' || *value == '\t') - value++; - if (!*value) { - bb_error_msg(WARN_BAD_LINE, filename, lineno); - continue; - } - - /* safe because sizeof(oneline) == sizeof(buffer) */ - sprintf(buffer, "%s=%s", name, value); - sysctl_write_setting(buffer, output); - } - fclose(fp); - return 0; -} /* end sysctl_preload_file() */ - -/* - * Write a single sysctl setting - */ -static int sysctl_write_setting(const char *setting, int output) -{ - int retval = 0; - const char *name; - const char *value; - const char *equals; - char *tmpname, *outname, *cptr; - int fd = -1; - - name = setting; - equals = strchr(setting, '='); - if (!equals) { - bb_error_msg(ERR_NO_EQUALS, setting); - return -1; - } - - value = equals + 1; /* point to the value in name=value */ - if (name == equals || !*value) { - bb_error_msg(ERR_MALFORMED_SETTING, setting); - return -2; + *cptr = '\0'; + outname[cptr - setting] = '\0'; + /* procps 3.2.7 actually uses these flags */ + fd = open(setting, O_WRONLY|O_CREAT|O_TRUNC, 0666); + } else { + fd = open(setting, O_RDONLY); } - tmpname = xasprintf("%s%.*s", PROC_SYS, (int)(equals - name), name); - outname = xstrdup(tmpname + strlen_PROC_SYS); - - while ((cptr = strchr(tmpname, '.')) != NULL) - *cptr = '/'; - - while ((cptr = strchr(outname, '/')) != NULL) - *cptr = '.'; - - fd = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { switch (errno) { - case ENOENT: - bb_error_msg(ERR_INVALID_KEY, outname); - break; case EACCES: - bb_perror_msg(ERR_PERMISSION_DENIED, outname); + /* Happens for write-only settings, e.g. net.ipv6.route.flush */ + goto end; + case ENOENT: + if (option_mask32 & FLAG_SHOW_KEY_ERRORS) + bb_error_msg("error: '%s' is an unknown key", outname); break; default: - bb_perror_msg(ERR_UNKNOWN_WRITING, outname); + bb_perror_msg("error %sing key '%s'", + writing ? + "sett" : "read", + outname); break; } - retval = -1; + retval = EXIT_FAILURE; + goto end; + } + + if (writing) { +//TODO: procps 3.2.7 writes "value\n", note trailing "\n" + xwrite_str(fd, value); + close(fd); + if (!(option_mask32 & FLAG_QUIET)) { + if (option_mask32 & FLAG_SHOW_KEYS) + printf("%s = ", outname); + puts(value); + } } else { - dwrite_str(fd, value); + char c; + + value = cptr = xmalloc_read(fd, NULL); close(fd); - if (output) { - dwrite_str(STDOUT_FILENO, outname); - dwrite_str(STDOUT_FILENO, " = "); + if (value == NULL) { + bb_perror_msg("error reading key '%s'", outname); + retval = EXIT_FAILURE; + goto end; } - dwrite_str(STDOUT_FILENO, value); - dwrite_str(STDOUT_FILENO, "\n"); - } - free(tmpname); + /* dev.cdrom.info and sunrpc.transports, for example, + * are multi-line. Try "sysctl sunrpc.transports" + */ + while ((c = *cptr) != '\0') { + if (option_mask32 & FLAG_SHOW_KEYS) + printf("%s = ", outname); + while (1) { + fputc(c, stdout); + cptr++; + if (c == '\n') + break; + c = *cptr; + if (c == '\0') + break; + } + } + free(value); + } + end: free(outname); return retval; -} /* end sysctl_write_setting() */ +} -/* - * Read a sysctl setting - */ -static int sysctl_read_setting(const char *setting, int output) +static int sysctl_act_recursive(const char *path) { + struct stat buf; int retval = 0; - char *tmpname, *outname, *cptr; - char inbuf[1025]; - const char *name; - FILE *fp; - - if (!*setting) - bb_error_msg(ERR_INVALID_KEY, setting); - - name = setting; - tmpname = concat_path_file(PROC_SYS, name); - outname = xstrdup(tmpname + strlen_PROC_SYS); - - while ((cptr = strchr(tmpname, '.')) != NULL) - *cptr = '/'; - while ((cptr = strchr(outname, '/')) != NULL) - *cptr = '.'; - fp = fopen(tmpname, "r"); - if (fp == NULL) { - switch (errno) { - case ENOENT: - bb_error_msg(ERR_INVALID_KEY, outname); - break; - case EACCES: - bb_error_msg(ERR_PERMISSION_DENIED, outname); - break; - default: - bb_perror_msg(ERR_UNKNOWN_READING, outname); - break; + if (!(option_mask32 & FLAG_WRITE) + && stat(path, &buf) == 0 + && S_ISDIR(buf.st_mode) + ) { + struct dirent *entry; + DIR *dirp; + + dirp = opendir(path); + if (dirp == NULL) + return -1; + while ((entry = readdir(dirp)) != NULL) { + char *next = concat_subpath_file(path, entry->d_name); + if (next == NULL) + continue; /* d_name is "." or ".." */ + /* if path was ".", drop "./" prefix: */ + retval |= sysctl_act_recursive((next[0] == '.' && next[1] == '/') ? + next + 2 : next); + free(next); } - retval = -1; + closedir(dirp); } else { - while (fgets(inbuf, sizeof(inbuf) - 1, fp)) { - if (output) { - dwrite_str(STDOUT_FILENO, outname); - dwrite_str(STDOUT_FILENO, " = "); - } - dwrite_str(STDOUT_FILENO, inbuf); - } - fclose(fp); + char *name = xstrdup(path); + retval |= sysctl_act_on_setting(name); + free(name); } - free(tmpname); - free(outname); return retval; -} /* end sysctl_read_setting() */ +} -/* - * Display all the sysctl settings +/* Set sysctl's from a conf file. Format example: + * # Controls IP packet forwarding + * net.ipv4.ip_forward = 0 */ -static int sysctl_display_all(const char *path, int output, int show_table) +static int sysctl_handle_preload_file(const char *filename) { - int retval = 0; - DIR *dp; - struct dirent *de; - char *tmpdir; - struct stat ts; - - dp = opendir(path); - if (!dp) { - return -1; + char *token[2]; + parser_t *parser; + int parse_flags; + + parser = config_open(filename); + /* Must do it _after_ config_open(): */ + xchdir("/proc/sys"); + + parse_flags = 0; + parse_flags &= ~PARSE_COLLAPSE; // NO (var==val is not var=val) - treat consecutive delimiters as one + parse_flags &= ~PARSE_TRIM; // NO - trim leading and trailing delimiters + parse_flags |= PARSE_GREEDY; // YES - last token takes entire remainder of the line + parse_flags &= ~PARSE_MIN_DIE; // NO - die if < min tokens found + parse_flags &= ~PARSE_EOL_COMMENTS; // NO (only first char) - comments are recognized even if not first char + parse_flags |= PARSE_ALT_COMMENTS;// YES - two comment chars: ';' and '#' + /* #comment is also comment, not strictly 1st char only */ + parse_flags |= PARSE_WS_COMMENTS; // YES - comments are recognized even if there is whitespace before + while (config_read(parser, token, 2, 2, ";#=", parse_flags)) { + char *tp; + + trim(token[1]); + tp = trim(token[0]); + sysctl_dots_to_slashes(token[0]); + /* ^^^converted in-place. tp still points to NUL */ + /* now, add "=TOKEN1" */ + *tp++ = '='; + overlapping_strcpy(tp, token[1]); + + sysctl_act_on_setting(token[0]); + } + if (ENABLE_FEATURE_CLEAN_UP) + config_close(parser); + return 0; +} + +int sysctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int sysctl_main(int argc UNUSED_PARAM, char **argv) +{ + int retval; + int opt; + + opt = getopt32(argv, "+" OPTION_STR); /* '+' - stop on first non-option */ + argv += optind; + opt ^= (FLAG_SHOW_KEYS | FLAG_SHOW_KEY_ERRORS); + option_mask32 = opt; + + if (opt & FLAG_PRELOAD_FILE) { + int cur_dir_fd; + option_mask32 |= FLAG_WRITE; + if (!*argv) + *--argv = (char*)"/etc/sysctl.conf"; + cur_dir_fd = xopen(".", O_RDONLY | O_DIRECTORY); + do { + /* xchdir("/proc/sys") is inside */ + sysctl_handle_preload_file(*argv); + xfchdir(cur_dir_fd); /* files can be relative, must restore cwd */ + } while (*++argv); + return 0; /* procps-ng 3.3.10 does not flag parse errors */ + } + xchdir("/proc/sys"); + if (opt & (FLAG_TABLE_FORMAT | FLAG_SHOW_ALL)) { + return sysctl_act_recursive("."); + } + +//TODO: if(!argv[0]) bb_show_usage() ? + + retval = 0; + while (*argv) { + sysctl_dots_to_slashes(*argv); + retval |= sysctl_act_recursive(*argv); + argv++; } - while ((de = readdir(dp)) != NULL) { - tmpdir = concat_subpath_file(path, de->d_name); - if (tmpdir == NULL) - continue; - if (stat(tmpdir, &ts) != 0) { - bb_perror_msg(tmpdir); - } else if (S_ISDIR(ts.st_mode)) { - sysctl_display_all(tmpdir, output, show_table); - } else { - retval |= sysctl_read_setting(tmpdir + strlen_PROC_SYS, output); - } - free(tmpdir); - } /* end while */ - closedir(dp); return retval; -} /* end sysctl_display_all() */ +}