busybox/procps/sysctl.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Sysctl 1.01 - A utility to read and manipulate the sysctl parameters
   4 *
   5 * Copyright 1999 George Staikos
   6 *
   7 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
   8 *
   9 * Changelog:
  10 * v1.01   - added -p <preload> to preload values from a file
  11 * v1.01.1 - busybox applet aware by <solar@gentoo.org>
  12 */
  13//config:config BB_SYSCTL
  14//config:       bool "sysctl (7.4 kb)"
  15//config:       default y
  16//config:       help
  17//config:       Configure kernel parameters at runtime.
  18
  19//applet:IF_BB_SYSCTL(APPLET_NOEXEC(sysctl, sysctl, BB_DIR_SBIN, BB_SUID_DROP, sysctl))
  20
  21//kbuild:lib-$(CONFIG_BB_SYSCTL) += sysctl.o
  22
  23//usage:#define sysctl_trivial_usage
  24//usage:       "-p [-enq] [FILE...] / [-enqaw] [KEY[=VALUE]]..."
  25//usage:#define sysctl_full_usage "\n\n"
  26//usage:       "Show/set kernel parameters\n"
  27//usage:     "\n        -p      Set values from FILEs (default /etc/sysctl.conf)"
  28//usage:     "\n        -e      Don't warn about unknown keys"
  29//usage:     "\n        -n      Don't show key names"
  30//usage:     "\n        -q      Quiet"
  31//usage:     "\n        -a      Show all values"
  32/* Same as -a, no need to show it */
  33/* //usage:     "\n     -A      Show all values in table form" */
  34//usage:     "\n        -w      Set values"
  35//usage:
  36//usage:#define sysctl_example_usage
  37//usage:       "sysctl [-n] [-e] variable...\n"
  38//usage:       "sysctl [-n] [-e] [-q] -w variable=value...\n"
  39//usage:       "sysctl [-n] [-e] -a\n"
  40//usage:       "sysctl [-n] [-e] [-q] -p file   (default /etc/sysctl.conf)\n"
  41//usage:       "sysctl [-n] [-e] -A\n"
  42
  43#include "libbb.h"
  44
  45enum {
  46        FLAG_SHOW_KEYS       = 1 << 0,
  47        FLAG_SHOW_KEY_ERRORS = 1 << 1,
  48        FLAG_TABLE_FORMAT    = 1 << 2, /* not implemented */
  49        FLAG_SHOW_ALL        = 1 << 3,
  50        FLAG_PRELOAD_FILE    = 1 << 4,
  51        /* NB: procps 3.2.8 does not require -w for KEY=VAL to work, it only rejects non-KEY=VAL form */
  52        FLAG_WRITE           = 1 << 5,
  53        FLAG_QUIET           = 1 << 6,
  54};
  55#define OPTION_STR "neAapwq"
  56
  57static void sysctl_dots_to_slashes(char *name)
  58{
  59        char *cptr, *last_good, *end;
  60
  61        /* Convert minimum number of '.' to '/' so that
  62         * we end up with existing file's name.
  63         *
  64         * Example from bug 3894:
  65         * net.ipv4.conf.eth0.100.mc_forwarding ->
  66         * net/ipv4/conf/eth0.100/mc_forwarding
  67         * NB: net/ipv4/conf/eth0/mc_forwarding *also exists*,
  68         * therefore we must start from the end, and if
  69         * we replaced even one . -> /, start over again,
  70         * but never replace dots before the position
  71         * where last replacement occurred.
  72         *
  73         * Another bug we later had is that
  74         * net.ipv4.conf.eth0.100
  75         * (without .mc_forwarding) was mishandled.
  76         *
  77         * To set up testing: modprobe 8021q; vconfig add eth0 100
  78         */
  79        end = name + strlen(name);
  80        last_good = name - 1;
  81        *end = '.'; /* trick the loop into trying full name too */
  82
  83 again:
  84        cptr = end;
  85        while (cptr > last_good) {
  86                if (*cptr == '.') {
  87                        *cptr = '\0';
  88                        //bb_error_msg("trying:'%s'", name);
  89                        if (access(name, F_OK) == 0) {
  90                                *cptr = '/';
  91                                //bb_error_msg("replaced:'%s'", name);
  92                                last_good = cptr;
  93                                goto again;
  94                        }
  95                        *cptr = '.';
  96                }
  97                cptr--;
  98        }
  99        *end = '\0';
 100}
 101
 102static int sysctl_act_on_setting(char *setting)
 103{
 104        int fd, retval = EXIT_SUCCESS;
 105        char *cptr, *outname;
 106        char *value = value; /* for compiler */
 107        bool writing = (option_mask32 & FLAG_WRITE);
 108
 109        outname = xstrdup(setting);
 110
 111        cptr = outname;
 112        while (*cptr) {
 113                if (*cptr == '/')
 114                        *cptr = '.';
 115                cptr++;
 116        }
 117
 118        cptr = strchr(setting, '=');
 119        if (cptr)
 120                writing = 1;
 121        if (writing) {
 122                if (cptr == NULL) {
 123                        bb_error_msg("error: '%s' must be of the form name=value",
 124                                outname);
 125                        retval = EXIT_FAILURE;
 126                        goto end;
 127                }
 128                value = cptr + 1;  /* point to the value in name=value */
 129                if (setting == cptr || !*value) {
 130                        bb_error_msg("error: malformed setting '%s'", outname);
 131                        retval = EXIT_FAILURE;
 132                        goto end;
 133                }
 134                *cptr = '\0';
 135                outname[cptr - setting] = '\0';
 136                /* procps 3.2.7 actually uses these flags */
 137                fd = open(setting, O_WRONLY|O_CREAT|O_TRUNC, 0666);
 138        } else {
 139                fd = open(setting, O_RDONLY);
 140        }
 141
 142        if (fd < 0) {
 143                switch (errno) {
 144                case EACCES:
 145                        /* Happens for write-only settings, e.g. net.ipv6.route.flush */
 146                        goto end;
 147                case ENOENT:
 148                        if (option_mask32 & FLAG_SHOW_KEY_ERRORS)
 149                                bb_error_msg("error: '%s' is an unknown key", outname);
 150                        break;
 151                default:
 152                        bb_perror_msg("error %sing key '%s'",
 153                                        writing ?
 154                                                "sett" : "read",
 155                                        outname);
 156                        break;
 157                }
 158                retval = EXIT_FAILURE;
 159                goto end;
 160        }
 161
 162        if (writing) {
 163//TODO: procps 3.2.7 writes "value\n", note trailing "\n"
 164                xwrite_str(fd, value);
 165                close(fd);
 166                if (!(option_mask32 & FLAG_QUIET)) {
 167                        if (option_mask32 & FLAG_SHOW_KEYS)
 168                                printf("%s = ", outname);
 169                        puts(value);
 170                }
 171        } else {
 172                char c;
 173
 174                value = cptr = xmalloc_read(fd, NULL);
 175                close(fd);
 176                if (value == NULL) {
 177                        bb_perror_msg("error reading key '%s'", outname);
 178                        goto end;
 179                }
 180
 181                /* dev.cdrom.info and sunrpc.transports, for example,
 182                 * are multi-line. Try "sysctl sunrpc.transports"
 183                 */
 184                while ((c = *cptr) != '\0') {
 185                        if (option_mask32 & FLAG_SHOW_KEYS)
 186                                printf("%s = ", outname);
 187                        while (1) {
 188                                fputc(c, stdout);
 189                                cptr++;
 190                                if (c == '\n')
 191                                        break;
 192                                c = *cptr;
 193                                if (c == '\0')
 194                                        break;
 195                        }
 196                }
 197                free(value);
 198        }
 199 end:
 200        free(outname);
 201        return retval;
 202}
 203
 204static int sysctl_act_recursive(const char *path)
 205{
 206        DIR *dirp;
 207        struct stat buf;
 208        struct dirent *entry;
 209        char *next;
 210        int retval = 0;
 211
 212        stat(path, &buf);
 213        if (S_ISDIR(buf.st_mode) && !(option_mask32 & FLAG_WRITE)) {
 214                dirp = opendir(path);
 215                if (dirp == NULL)
 216                        return -1;
 217                while ((entry = readdir(dirp)) != NULL) {
 218                        next = concat_subpath_file(path, entry->d_name);
 219                        if (next == NULL)
 220                                continue; /* d_name is "." or ".." */
 221                        /* if path was ".", drop "./" prefix: */
 222                        retval |= sysctl_act_recursive((next[0] == '.' && next[1] == '/') ?
 223                                        next + 2 : next);
 224                        free(next);
 225                }
 226                closedir(dirp);
 227        } else {
 228                char *name = xstrdup(path);
 229                retval |= sysctl_act_on_setting(name);
 230                free(name);
 231        }
 232
 233        return retval;
 234}
 235
 236/* Set sysctl's from a conf file. Format example:
 237 * # Controls IP packet forwarding
 238 * net.ipv4.ip_forward = 0
 239 */
 240static int sysctl_handle_preload_file(const char *filename)
 241{
 242        char *token[2];
 243        parser_t *parser;
 244        int parse_flags;
 245
 246        parser = config_open(filename);
 247        /* Must do it _after_ config_open(): */
 248        xchdir("/proc/sys");
 249
 250        parse_flags = 0;
 251        parse_flags &= ~PARSE_COLLAPSE;   // NO (var==val is not var=val) - treat consecutive delimiters as one
 252        parse_flags &= ~PARSE_TRIM;       // NO - trim leading and trailing delimiters
 253        parse_flags |= PARSE_GREEDY;      // YES - last token takes entire remainder of the line
 254        parse_flags &= ~PARSE_MIN_DIE;    // NO - die if < min tokens found
 255        parse_flags &= ~PARSE_EOL_COMMENTS; // NO (only first char) - comments are recognized even if not first char
 256        parse_flags |= PARSE_ALT_COMMENTS;// YES - two comment chars: ';' and '#'
 257        /* <space><tab><space>#comment is also comment, not strictly 1st char only */
 258        parse_flags |= PARSE_WS_COMMENTS; // YES - comments are recognized even if there is whitespace before
 259        while (config_read(parser, token, 2, 2, ";#=", parse_flags)) {
 260                char *tp;
 261
 262                trim(token[1]);
 263                tp = trim(token[0]);
 264                sysctl_dots_to_slashes(token[0]);
 265                /* ^^^converted in-place. tp still points to NUL */
 266                /* now, add "=TOKEN1" */
 267                *tp++ = '=';
 268                overlapping_strcpy(tp, token[1]);
 269
 270                sysctl_act_on_setting(token[0]);
 271        }
 272        if (ENABLE_FEATURE_CLEAN_UP)
 273                config_close(parser);
 274        return 0;
 275}
 276
 277int sysctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 278int sysctl_main(int argc UNUSED_PARAM, char **argv)
 279{
 280        int retval;
 281        int opt;
 282
 283        opt = getopt32(argv, "+" OPTION_STR); /* '+' - stop on first non-option */
 284        argv += optind;
 285        opt ^= (FLAG_SHOW_KEYS | FLAG_SHOW_KEY_ERRORS);
 286        option_mask32 = opt;
 287
 288        if (opt & FLAG_PRELOAD_FILE) {
 289                int cur_dir_fd;
 290                option_mask32 |= FLAG_WRITE;
 291                if (!*argv)
 292                        *--argv = (char*)"/etc/sysctl.conf";
 293                cur_dir_fd = xopen(".", O_RDONLY | O_DIRECTORY);
 294                do {
 295                        /* xchdir("/proc/sys") is inside */
 296                        sysctl_handle_preload_file(*argv);
 297                        xfchdir(cur_dir_fd); /* files can be relative, must restore cwd */
 298                } while (*++argv);
 299                return 0; /* procps-ng 3.3.10 does not flag parse errors */
 300        }
 301        xchdir("/proc/sys");
 302        if (opt & (FLAG_TABLE_FORMAT | FLAG_SHOW_ALL)) {
 303                return sysctl_act_recursive(".");
 304        }
 305
 306        retval = 0;
 307        while (*argv) {
 308                sysctl_dots_to_slashes(*argv);
 309                retval |= sysctl_act_recursive(*argv);
 310                argv++;
 311        }
 312
 313        return retval;
 314}
 315