sysctl: do slash/dot conversions only on name, not value part
[oweals/busybox.git] / procps / sysctl.c
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
45 enum {
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
57 static void sysctl_dots_to_slashes(char *name)
58 {
59         char *cptr, *last_good, *end;
60         char end_ch;
61
62         /* Convert minimum number of '.' to '/' so that
63          * we end up with existing file's name.
64          *
65          * Example from bug 3894:
66          * net.ipv4.conf.eth0.100.mc_forwarding ->
67          * net/ipv4/conf/eth0.100/mc_forwarding
68          * NB: net/ipv4/conf/eth0/mc_forwarding *also exists*,
69          * therefore we must start from the end, and if
70          * we replaced even one . -> /, start over again,
71          * but never replace dots before the position
72          * where last replacement occurred.
73          *
74          * Another bug we later had is that
75          * net.ipv4.conf.eth0.100
76          * (without .mc_forwarding) was mishandled.
77          *
78          * To set up testing: modprobe 8021q; vconfig add eth0 100
79          */
80         end = strchrnul(name, '=');
81         end_ch = *end;
82         *end = '.'; /* trick the loop into trying full name too */
83
84         last_good = name - 1;
85  again:
86         cptr = end;
87         while (cptr > last_good) {
88                 if (*cptr == '.') {
89                         *cptr = '\0';
90                         //bb_error_msg("trying:'%s'", name);
91                         if (access(name, F_OK) == 0) {
92                                 *cptr = '/';
93                                 //bb_error_msg("replaced:'%s'", name);
94                                 last_good = cptr;
95                                 goto again;
96                         }
97                         *cptr = '.';
98                 }
99                 cptr--;
100         }
101         *end = end_ch;
102 }
103
104 static int sysctl_act_on_setting(char *setting)
105 {
106         int fd, retval = EXIT_SUCCESS;
107         char *cptr, *outname;
108         char *value = value; /* for compiler */
109         bool writing = (option_mask32 & FLAG_WRITE);
110
111         outname = xstrdup(setting);
112
113         cptr = outname;
114         while (*cptr) {
115                 if (*cptr == '/')
116                         *cptr = '.';
117                 cptr++;
118         }
119
120         cptr = strchr(setting, '=');
121         if (cptr)
122                 writing = 1;
123         if (writing) {
124                 if (!cptr) {
125                         bb_error_msg("error: '%s' must be of the form name=value",
126                                 outname);
127                         retval = EXIT_FAILURE;
128                         goto end;
129                 }
130                 value = cptr + 1;  /* point to the value in name=value */
131                 if (setting == cptr /* "name" can't be empty */
132                  /* || !*value - WRONG: "sysctl net.ipv4.ip_local_reserved_ports=" is a valid syntax (clears the value) */
133                 ) {
134                         bb_error_msg("error: malformed setting '%s'", outname);
135                         retval = EXIT_FAILURE;
136                         goto end;
137                 }
138                 *cptr = '\0';
139                 outname[cptr - setting] = '\0';
140                 /* procps 3.2.7 actually uses these flags */
141                 fd = open(setting, O_WRONLY|O_CREAT|O_TRUNC, 0666);
142         } else {
143                 fd = open(setting, O_RDONLY);
144         }
145
146         if (fd < 0) {
147                 switch (errno) {
148                 case EACCES:
149                         /* Happens for write-only settings, e.g. net.ipv6.route.flush */
150                         goto end;
151                 case ENOENT:
152                         if (option_mask32 & FLAG_SHOW_KEY_ERRORS)
153                                 bb_error_msg("error: '%s' is an unknown key", outname);
154                         break;
155                 default:
156                         bb_perror_msg("error %sing key '%s'",
157                                         writing ?
158                                                 "sett" : "read",
159                                         outname);
160                         break;
161                 }
162                 retval = EXIT_FAILURE;
163                 goto end;
164         }
165
166         if (writing) {
167 //TODO: procps 3.2.7 writes "value\n", note trailing "\n"
168                 xwrite_str(fd, value);
169                 close(fd);
170                 if (!(option_mask32 & FLAG_QUIET)) {
171                         if (option_mask32 & FLAG_SHOW_KEYS)
172                                 printf("%s = ", outname);
173                         puts(value);
174                 }
175         } else {
176                 char c;
177
178                 value = cptr = xmalloc_read(fd, NULL);
179                 close(fd);
180                 if (value == NULL) {
181                         bb_perror_msg("error reading key '%s'", outname);
182                         retval = EXIT_FAILURE;
183                         goto end;
184                 }
185
186                 /* dev.cdrom.info and sunrpc.transports, for example,
187                  * are multi-line. Try "sysctl sunrpc.transports"
188                  */
189                 while ((c = *cptr) != '\0') {
190                         if (option_mask32 & FLAG_SHOW_KEYS)
191                                 printf("%s = ", outname);
192                         while (1) {
193                                 fputc(c, stdout);
194                                 cptr++;
195                                 if (c == '\n')
196                                         break;
197                                 c = *cptr;
198                                 if (c == '\0')
199                                         break;
200                         }
201                 }
202                 free(value);
203         }
204  end:
205         free(outname);
206         return retval;
207 }
208
209 static int sysctl_act_recursive(const char *path)
210 {
211         struct stat buf;
212         int retval = 0;
213
214         if (!(option_mask32 & FLAG_WRITE)
215          && stat(path, &buf) == 0
216          && S_ISDIR(buf.st_mode)
217         ) {
218                 struct dirent *entry;
219                 DIR *dirp;
220
221                 dirp = opendir(path);
222                 if (dirp == NULL)
223                         return -1;
224                 while ((entry = readdir(dirp)) != NULL) {
225                         char *next = concat_subpath_file(path, entry->d_name);
226                         if (next == NULL)
227                                 continue; /* d_name is "." or ".." */
228                         /* if path was ".", drop "./" prefix: */
229                         retval |= sysctl_act_recursive((next[0] == '.' && next[1] == '/') ?
230                                         next + 2 : next);
231                         free(next);
232                 }
233                 closedir(dirp);
234         } else {
235                 char *name = xstrdup(path);
236                 retval |= sysctl_act_on_setting(name);
237                 free(name);
238         }
239
240         return retval;
241 }
242
243 /* Set sysctl's from a conf file. Format example:
244  * # Controls IP packet forwarding
245  * net.ipv4.ip_forward = 0
246  */
247 static int sysctl_handle_preload_file(const char *filename)
248 {
249         char *token[2];
250         parser_t *parser;
251         int parse_flags;
252
253         parser = config_open(filename);
254         /* Must do it _after_ config_open(): */
255         xchdir("/proc/sys");
256
257         parse_flags = 0;
258         parse_flags &= ~PARSE_COLLAPSE;   // NO (var==val is not var=val) - treat consecutive delimiters as one
259         parse_flags &= ~PARSE_TRIM;       // NO - trim leading and trailing delimiters
260         parse_flags |= PARSE_GREEDY;      // YES - last token takes entire remainder of the line
261         parse_flags &= ~PARSE_MIN_DIE;    // NO - die if < min tokens found
262         parse_flags &= ~PARSE_EOL_COMMENTS; // NO (only first char) - comments are recognized even if not first char
263         parse_flags |= PARSE_ALT_COMMENTS;// YES - two comment chars: ';' and '#'
264         /* <space><tab><space>#comment is also comment, not strictly 1st char only */
265         parse_flags |= PARSE_WS_COMMENTS; // YES - comments are recognized even if there is whitespace before
266         while (config_read(parser, token, 2, 2, ";#=", parse_flags)) {
267                 char *tp;
268
269                 trim(token[1]);
270                 tp = trim(token[0]);
271                 sysctl_dots_to_slashes(token[0]);
272                 /* ^^^converted in-place. tp still points to NUL */
273                 /* now, add "=TOKEN1" */
274                 *tp++ = '=';
275                 overlapping_strcpy(tp, token[1]);
276
277                 sysctl_act_on_setting(token[0]);
278         }
279         if (ENABLE_FEATURE_CLEAN_UP)
280                 config_close(parser);
281         return 0;
282 }
283
284 int sysctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
285 int sysctl_main(int argc UNUSED_PARAM, char **argv)
286 {
287         int retval;
288         int opt;
289
290         opt = getopt32(argv, "+" OPTION_STR); /* '+' - stop on first non-option */
291         argv += optind;
292         opt ^= (FLAG_SHOW_KEYS | FLAG_SHOW_KEY_ERRORS);
293         option_mask32 = opt;
294
295         if (opt & FLAG_PRELOAD_FILE) {
296                 int cur_dir_fd;
297                 option_mask32 |= FLAG_WRITE;
298                 if (!*argv)
299                         *--argv = (char*)"/etc/sysctl.conf";
300                 cur_dir_fd = xopen(".", O_RDONLY | O_DIRECTORY);
301                 do {
302                         /* xchdir("/proc/sys") is inside */
303                         sysctl_handle_preload_file(*argv);
304                         xfchdir(cur_dir_fd); /* files can be relative, must restore cwd */
305                 } while (*++argv);
306                 return 0; /* procps-ng 3.3.10 does not flag parse errors */
307         }
308         xchdir("/proc/sys");
309         if (opt & (FLAG_TABLE_FORMAT | FLAG_SHOW_ALL)) {
310                 return sysctl_act_recursive(".");
311         }
312
313 //TODO: if(!argv[0]) bb_show_usage() ?
314
315         retval = 0;
316         while (*argv) {
317                 sysctl_dots_to_slashes(*argv);
318                 retval |= sysctl_act_recursive(*argv);
319                 argv++;
320         }
321
322         return retval;
323 }