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