dhcpc: code shrink in good_hostname
[oweals/busybox.git] / procps / sysctl.c
index 7c72ac933aa5619c9a7e06ef284750fe1b162b48..6d77185caec8ef5b2c6887b2a1df85f749acab13 100644 (file)
  *
  * 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 <preload> to preload values from a file
- *     v1.01.1
- *             - busybox applet aware by <solar@gentoo.org>
- *
- */
-
-#include "busybox.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...
+ * v1.01   - added -p <preload> to preload values from a file
+ * v1.01.1 - busybox applet aware by <solar@gentoo.org>
  */
-static const char PROC_PATH[] = "/proc/sys/";
-static const char DEFAULT_PRELOAD[] = "/etc/sysctl.conf";
-
-/* error messages */
-static const char ERR_UNKNOWN_PARAMETER[] = "error: Unknown parameter '%s'\n";
-static const char ERR_MALFORMED_SETTING[] = "error: Malformed setting '%s'\n";
-static const char ERR_NO_EQUALS[] =
-       "error: '%s' must be of the form name=value\n";
-static const char ERR_INVALID_KEY[] = "error: '%s' is an unknown key\n";
-static const char ERR_UNKNOWN_WRITING[] =
-       "error: unknown error %d setting key '%s'\n";
-static const char ERR_UNKNOWN_READING[] =
-       "error: unknown error %d reading key '%s'\n";
-static const char ERR_PERMISSION_DENIED[] =
-       "error: permission denied on key '%s'\n";
-static const char ERR_PRELOAD_FILE[] =
-       "error: cannot open preload file '%s'\n";
-static const char WARN_BAD_LINE[] =
-       "warning: %s(%d): invalid syntax, continuing...\n";
-
-
-static void dwrite_str(int fd, const char *buf)
+//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"
+
+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)
 {
-       write(fd, buf, strlen(buf));
-}
-
-/*
- *    sysctl_main()...
- */
-int sysctl_main(int argc, char **argv);
-int sysctl_main(int argc, char **argv)
-{
-       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; 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) ? *argv :
-                                                                                DEFAULT_PRELOAD), output);
-                       case 'a':
-                       case 'A':
-                               switches_allowed = 0;
-                               return sysctl_display_all(PROC_PATH, output,
-                                                                                 ((*argv)[1] == 'a') ? 0 : 1);
-                       case 'h':
-                       case '?':
-                               bb_show_usage();
-                       default:
-                               bb_error_msg(ERR_UNKNOWN_PARAMETER, *argv);
-                               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;
+}
 
-int sysctl_preload_file(const char *filename, int output)
+static int sysctl_act_on_setting(char *setting)
 {
-       int lineno = 0;
-       char oneline[PRELOAD_BUF];
-       char buffer[PRELOAD_BUF];
-       char *name, *value, *ptr;
-       FILE *fp = NULL;
-
-       if (!filename || ((fp = fopen(filename, "r")) == 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++;
        }
 
-       while (fgets(oneline, sizeof(oneline) - 1, fp)) {
-               oneline[sizeof(oneline) - 1] = '\0';
-               lineno++;
-               trim(oneline);
-               ptr = (char *) oneline;
-
-               if (*ptr == '#' || *ptr == ';')
-                       continue;
-
-               if (strlen(ptr) < 2)
-                       continue;
-
-               name = strtok(ptr, "=");
-               if (!name || !*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);
-
-               value = strtok(NULL, "\n\r");
-               if (!value || !*value) {
-                       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;
                }
-
-               while ((*value == ' ' || *value == '\t') && *value != 0)
-                       value++;
-               /* 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
- */
-int sysctl_write_setting(const char *setting, int output)
-{
-       int retval = 0;
-       const char *name = setting;
-       const char *value;
-       const char *equals;
-       char *tmpname, *outname, *cptr;
-       int fd = -1;
-
-       if (!name)                      /* probably dont' want to display this  err */
-               return 0;
-
-       if (!(equals = strchr(setting, '='))) {
-               bb_error_msg(ERR_NO_EQUALS, setting);
-               return -1;
-       }
-
-       value = equals + sizeof(char);  /* point to the value in name=value */
-
-       if (!*name || !*value || name == equals) {
-               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_PATH, (int)(equals - name), name);
-       outname = xstrdup(tmpname + strlen(PROC_PATH));
-
-       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_error_msg(ERR_UNKNOWN_WRITING, errno, 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");
-       }
 
-       /* cleanup */
-       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
- *
- */
-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 = setting;
-       FILE *fp;
-
-       if (!setting || !*setting)
-               bb_error_msg(ERR_INVALID_KEY, setting);
-
-       tmpname = concat_path_file(PROC_PATH, name);
-       outname = xstrdup(tmpname + strlen(PROC_PATH));
-
-       while ((cptr = strchr(tmpname, '.')) != NULL)
-               *cptr = '/';
-       while ((cptr = strchr(outname, '/')) != NULL)
-               *cptr = '.';
 
-       if ((fp = fopen(tmpname, "r")) == 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_error_msg(ERR_UNKNOWN_READING, errno, 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
  */
-int sysctl_display_all(const char *path, int output, int show_table)
+static int sysctl_handle_preload_file(const char *filename)
 {
-       int retval = 0;
-       int retval2;
-       DIR *dp;
-       struct dirent *de;
-       char *tmpdir;
-       struct stat ts;
-
-       if (!(dp = opendir(path))) {
-               retval = -1;
-       } else {
-               while ((de = readdir(dp)) != NULL) {
-                       tmpdir = concat_subpath_file(path, de->d_name);
-                       if(tmpdir == NULL)
-                               continue;
-                       if ((retval2 = 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_PATH),
-                                                                                       output);
+       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 '#'
+       /* <space><tab><space>#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;
+}
 
-                       }
-                       free(tmpdir);
-               }                               /* end while */
-               closedir(dp);
+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++;
        }
 
        return retval;
-}                                              /* end sysctl_display_all() */
+}