*: move get_sock_lsa and xwrite_str to libbb, use where appropriate
[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 tarball for details.
8  *
9  * Changelog:
10  *      v1.01:
11  *              - added -p <preload> to preload values from a file
12  *      v1.01.1
13  *              - busybox applet aware by <solar@gentoo.org>
14  *
15  */
16
17 #include "libbb.h"
18
19 static int sysctl_act_on_setting(char *setting);
20 static int sysctl_display_all(const char *path);
21 static int sysctl_handle_preload_file(const char *filename);
22 static void sysctl_dots_to_slashes(char *name);
23
24 enum {
25         FLAG_SHOW_KEYS       = 1 << 0,
26         FLAG_SHOW_KEY_ERRORS = 1 << 1,
27         FLAG_TABLE_FORMAT    = 1 << 2, /* not implemented */
28         FLAG_SHOW_ALL        = 1 << 3,
29         FLAG_PRELOAD_FILE    = 1 << 4,
30         FLAG_WRITE           = 1 << 5,
31 };
32
33 int sysctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
34 int sysctl_main(int argc UNUSED_PARAM, char **argv)
35 {
36         int retval;
37         int opt;
38
39         opt = getopt32(argv, "+neAapw"); /* '+' - stop on first non-option */
40         argv += optind;
41         opt ^= (FLAG_SHOW_KEYS | FLAG_SHOW_KEY_ERRORS);
42         option_mask32 = opt;
43
44         if (opt & FLAG_PRELOAD_FILE) {
45                 option_mask32 |= FLAG_WRITE;
46                 /* xchdir("/proc/sys") is inside */
47                 return sysctl_handle_preload_file(*argv ? *argv : "/etc/sysctl.conf");
48         }
49         xchdir("/proc/sys");
50         /* xchroot(".") - if you are paranoid */
51         if (opt & (FLAG_TABLE_FORMAT | FLAG_SHOW_ALL)) {
52                 return sysctl_display_all(".");
53         }
54
55         retval = 0;
56         while (*argv) {
57                 sysctl_dots_to_slashes(*argv);
58                 retval |= sysctl_display_all(*argv);
59                 argv++;
60         }
61
62         return retval;
63 }
64
65 /* Set sysctl's from a conf file. Format example:
66  * # Controls IP packet forwarding
67  * net.ipv4.ip_forward = 0
68  */
69 static int sysctl_handle_preload_file(const char *filename)
70 {
71         char *token[2];
72         parser_t *parser;
73
74         parser = config_open(filename);
75         /* Must do it _after_ config_open(): */
76         xchdir("/proc/sys");
77         /* xchroot(".") - if you are paranoid */
78
79 // TODO: ';' is comment char too
80         while (config_read(parser, token, 2, 2, "# \t=", PARSE_NORMAL)) {
81                 /* Save ~4 bytes by using parser internals */
82                 /* parser->line is big enough for sprintf */
83                 sprintf(parser->line, "%s=%s", token[0], token[1]);
84                 sysctl_dots_to_slashes(parser->line);
85                 sysctl_display_all(parser->line);
86         }
87         if (ENABLE_FEATURE_CLEAN_UP)
88                 config_close(parser);
89         return 0;
90 }
91
92 static int sysctl_act_on_setting(char *setting)
93 {
94         int fd, retval = EXIT_SUCCESS;
95         char *cptr, *outname;
96         char *value = value; /* for compiler */
97
98         outname = xstrdup(setting);
99
100         cptr = outname;
101         while (*cptr) {
102                 if (*cptr == '/')
103                         *cptr = '.';
104                 cptr++;
105         }
106
107         if (option_mask32 & FLAG_WRITE) {
108                 cptr = strchr(setting, '=');
109                 if (cptr == NULL) {
110                         bb_error_msg("error: '%s' must be of the form name=value",
111                                 outname);
112                         retval = EXIT_FAILURE;
113                         goto end;
114                 }
115                 value = cptr + 1;       /* point to the value in name=value */
116                 if (setting == cptr || !*value) {
117                         bb_error_msg("error: malformed setting '%s'", outname);
118                         retval = EXIT_FAILURE;
119                         goto end;
120                 }
121                 *cptr = '\0';
122                 fd = open(setting, O_WRONLY|O_CREAT|O_TRUNC, 0666);
123         } else {
124                 fd = open(setting, O_RDONLY);
125         }
126
127         if (fd < 0) {
128                 switch (errno) {
129                 case ENOENT:
130                         if (option_mask32 & FLAG_SHOW_KEY_ERRORS)
131                                 bb_error_msg("error: '%s' is an unknown key", outname);
132                         break;
133                 default:
134                         bb_perror_msg("error %sing key '%s'",
135                                         option_mask32 & FLAG_WRITE ?
136                                                 "sett" : "read",
137                                         outname);
138                         break;
139                 }
140                 retval = EXIT_FAILURE;
141                 goto end;
142         }
143
144         if (option_mask32 & FLAG_WRITE) {
145                 xwrite_str(fd, value);
146                 close(fd);
147                 if (option_mask32 & FLAG_SHOW_KEYS)
148                         printf("%s = ", outname);
149                 puts(value);
150         } else {
151                 char c;
152
153                 value = cptr = xmalloc_read(fd, NULL);
154                 close(fd);
155                 if (value == NULL) {
156                         bb_perror_msg("error reading key '%s'", outname);
157                         goto end;
158                 }
159
160                 /* dev.cdrom.info and sunrpc.transports, for example,
161                  * are multi-line. Try "sysctl sunrpc.transports"
162                  */
163                 while ((c = *cptr) != '\0') {
164                         if (option_mask32 & FLAG_SHOW_KEYS)
165                                 printf("%s = ", outname);
166                         while (1) {
167                                 fputc(c, stdout);
168                                 cptr++;
169                                 if (c == '\n')
170                                         break;
171                                 c = *cptr;
172                                 if (c == '\0')
173                                         break;
174                         }
175                 }
176                 free(value);
177         }
178  end:
179         free(outname);
180         return retval;
181 }
182
183 static int sysctl_display_all(const char *path)
184 {
185         DIR *dirp;
186         struct stat buf;
187         struct dirent *entry;
188         char *next;
189         int retval = 0;
190
191         stat(path, &buf);
192         if (S_ISDIR(buf.st_mode) && !(option_mask32 & FLAG_WRITE)) {
193                 dirp = opendir(path);
194                 if (dirp == NULL)
195                         return -1;
196                 while ((entry = readdir(dirp)) != NULL) {
197                         next = concat_subpath_file(
198                                 path, entry->d_name);
199                         if (next == NULL)
200                                 continue; /* d_name is "." or ".." */
201                         /* if path was ".", drop "./" prefix: */
202                         retval |= sysctl_display_all((next[0] == '.' && next[1] == '/') ?
203                                             next + 2 : next);
204                         free(next);
205                 }
206                 closedir(dirp);
207         } else {
208                 char *name = xstrdup(path);
209                 retval |= sysctl_act_on_setting(name);
210                 free(name);
211         }
212
213         return retval;
214 }
215
216 static void sysctl_dots_to_slashes(char *name)
217 {
218         char *cptr, *last_good, *end;
219
220         /* Convert minimum number of '.' to '/' so that
221          * we end up with existing file's name.
222          *
223          * Example from bug 3894:
224          * net.ipv4.conf.eth0.100.mc_forwarding ->
225          * net/ipv4/conf/eth0.100/mc_forwarding
226          * NB: net/ipv4/conf/eth0/mc_forwarding *also exists*,
227          * therefore we must start from the end, and if
228          * we replaced even one . -> /, start over again,
229          * but never replace dots before the position
230          * where last replacement occurred.
231          *
232          * Another bug we later had is that
233          * net.ipv4.conf.eth0.100
234          * (without .mc_forwarding) was mishandled.
235          *
236          * To set up testing: modprobe 8021q; vconfig add eth0 100
237          */
238         end = name + strlen(name);
239         last_good = name - 1;
240         *end = '.'; /* trick the loop into trying full name too */
241
242  again:
243         cptr = end;
244         while (cptr > last_good) {
245                 if (*cptr == '.') {
246                         *cptr = '\0';
247                         //bb_error_msg("trying:'%s'", name);
248                         if (access(name, F_OK) == 0) {
249                                 if (cptr != end) /* prevent trailing '/' */
250                                         *cptr = '/';
251                                 //bb_error_msg("replaced:'%s'", name);
252                                 last_good = cptr;
253                                 goto again;
254                         }
255                         *cptr = '.';
256                 }
257                 cptr--;
258         }
259         *end = '\0';
260 }