sync with master branch
[external/procps.git] / sysctl.c
1
2 /*
3  * Sysctl 1.01 - A utility to read and manipulate the sysctl parameters
4  *
5  *
6  * "Copyright 1999 George Staikos
7  * This file may be used subject to the terms and conditions of the
8  * GNU General Public License Version 2, or any later version
9  * at your option, as published by the Free Software Foundation.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details."
14  *
15  * Changelog:
16  *            v1.01:
17  *                   - added -p <preload> to preload values from a file
18  *            Horms: 
19  *                   - added -q to be quiet when modifying values
20  *
21  * Changes by Albert Cahalan, 2002.
22  */
23
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 #include <dirent.h>
31 #include <string.h>
32 #include <errno.h>
33 #include "proc/procps.h"
34 #include "proc/version.h"
35
36
37 // Proof that C++ causes brain damage:
38 typedef int bool;
39 static bool true  = 1;
40 static bool false = 0;
41
42 /*
43  *    Globals...
44  */
45
46 static const char PROC_PATH[] = "/proc/sys/";
47 static const char DEFAULT_PRELOAD[] = "/etc/sysctl.conf";
48 static bool NameOnly;
49 static bool PrintName;
50 static bool PrintNewline;
51 static bool IgnoreError;
52 static bool Quiet;
53
54 /* error messages */
55 static const char ERR_UNKNOWN_PARAMETER[] = "error: Unknown parameter \"%s\"\n";
56 static const char ERR_MALFORMED_SETTING[] = "error: Malformed setting \"%s\"\n";
57 static const char ERR_NO_EQUALS[] = "error: \"%s\" must be of the form name=value\n";
58 static const char ERR_INVALID_KEY[] = "error: \"%s\" is an unknown key\n";
59 static const char ERR_UNKNOWN_WRITING[] = "error: \"%s\" setting key \"%s\"\n";
60 static const char ERR_UNKNOWN_READING[] = "error: \"%s\" reading key \"%s\"\n";
61 static const char ERR_PERMISSION_DENIED[] = "error: permission denied on key '%s'\n";
62 static const char ERR_OPENING_DIR[] = "error: unable to open directory \"%s\"\n";
63 static const char ERR_PRELOAD_FILE[] = "error: unable to open preload file \"%s\"\n";
64 static const char WARN_BAD_LINE[] = "warning: %s(%d): invalid syntax, continuing...\n";
65
66
67 static void slashdot(char *restrict p, char old, char new){
68   p = strpbrk(p,"/.");
69   if(!p) return;            /* nothing -- can't be, but oh well */
70   if(*p==new) return;       /* already in desired format */
71   while(p){
72     char c = *p;
73     if(c==old) *p=new;
74     if(c==new) *p=old;
75     p = strpbrk(p+1,"/.");
76   }
77 }
78
79
80
81 /*
82  *     Display the usage format
83  *
84  */
85 static int Usage(const char *restrict const name) {
86    printf("usage:  %s [-n] [-e] variable ... \n"
87           "        %s [-n] [-e] [-q] -w variable=value ... \n" 
88           "        %s [-n] [-e] -a \n" 
89           "        %s [-n] [-e] [-q] -p <file>   (default /etc/sysctl.conf) \n"
90           "        %s [-n] [-e] -A\n", name, name, name, name, name);
91    return -1;
92 }
93
94
95 /*
96  *     Strip the leading and trailing spaces from a string
97  *
98  */
99 static char *StripLeadingAndTrailingSpaces(char *oneline) {
100    char *t;
101
102    if (!oneline || !*oneline)
103       return oneline;
104
105    t = oneline;
106    t += strlen(oneline)-1;
107
108    while ((*t==' ' || *t=='\t' || *t=='\n' || *t=='\r') && t!=oneline)
109       *t-- = 0;
110
111    t = oneline;
112
113    while ((*t==' ' || *t=='\t') && *t!=0)
114       t++;
115
116    return t;
117 }
118
119 static int DisplayAll(const char *restrict const path);
120
121 /*
122  *     Read a sysctl setting 
123  *
124  */
125 static int ReadSetting(const char *restrict const name) {
126    int rc = 0;
127    char *restrict tmpname;
128    char *restrict outname;
129    char inbuf[1025];
130    FILE *restrict fp;
131
132    if (!name || !*name) {
133       fprintf(stderr, ERR_INVALID_KEY, name);
134       return -1;
135    }
136
137    /* used to open the file */
138    tmpname = malloc(strlen(name)+strlen(PROC_PATH)+2);
139    strcpy(tmpname, PROC_PATH);
140    strcat(tmpname, name); 
141    slashdot(tmpname+strlen(PROC_PATH),'.','/'); /* change . to / */
142
143    /* used to display the output */
144    outname = strdup(name);
145    slashdot(outname,'/','.'); /* change / to . */
146
147    fp = fopen(tmpname, "r");
148
149    if (!fp) {
150       switch(errno) {
151       case ENOENT:
152          if (!IgnoreError) {
153             fprintf(stderr, ERR_INVALID_KEY, outname);
154             rc = -1;
155          }
156          break;
157       case EACCES:
158          fprintf(stderr, ERR_PERMISSION_DENIED, outname);
159          rc = -1;
160          break;
161       default:
162          fprintf(stderr, ERR_UNKNOWN_READING, strerror(errno), outname);
163          rc = -1;
164          break;
165       }
166    } else {
167       if(fgets(inbuf, sizeof inbuf - 1, fp)) {
168          // this loop is required, see
169          // /sbin/sysctl -a | egrep -6 dev.cdrom.info
170          do {
171             if (NameOnly) {
172                fprintf(stdout, "%s\n", outname);
173             } else {
174                /* already has the \n in it */
175                if (PrintName) {
176                   fprintf(stdout, "%s = %s", outname, inbuf);
177                } else {
178                   if (!PrintNewline) {
179                     char *nlptr = strchr(inbuf,'\n');
180                     if(nlptr) *nlptr='\0';
181                   }
182                   fprintf(stdout, "%s", inbuf);
183                }
184             }
185          } while(fgets(inbuf, sizeof inbuf - 1, fp));
186       } else {
187          switch(errno) {
188          case EACCES:
189             fprintf(stderr, ERR_PERMISSION_DENIED, outname);
190             rc = -1;
191             break;
192          case EISDIR:{
193             size_t len;
194             len = strlen(tmpname);
195             tmpname[len] = '/';
196             tmpname[len+1] = '\0';
197             rc = DisplayAll(tmpname);
198             break;
199          }
200          default:
201             fprintf(stderr, ERR_UNKNOWN_READING, strerror(errno), outname);
202             rc = -1;
203             break;
204          }
205       }
206       fclose(fp);
207    }
208
209    free(tmpname);
210    free(outname);
211    return rc;
212 }
213
214
215
216 /*
217  *     Display all the sysctl settings 
218  *
219  */
220 static int DisplayAll(const char *restrict const path) {
221    int rc = 0;
222    int rc2;
223    DIR *restrict dp;
224    struct dirent *restrict de;
225    struct stat ts;
226
227    dp = opendir(path);
228
229    if (!dp) {
230       fprintf(stderr, ERR_OPENING_DIR, path);
231       rc = -1;
232    } else {
233       readdir(dp);  // skip .
234       readdir(dp);  // skip ..
235       while (( de = readdir(dp) )) {
236          char *restrict tmpdir;
237          tmpdir = (char *restrict)malloc(strlen(path)+strlen(de->d_name)+2);
238          sprintf(tmpdir, "%s%s", path, de->d_name);
239          rc2 = stat(tmpdir, &ts);
240          if (rc2 != 0) {
241             perror(tmpdir);
242          } else {
243             if (S_ISDIR(ts.st_mode)) {
244                strcat(tmpdir, "/");
245                DisplayAll(tmpdir);
246             } else {
247                rc |= ReadSetting(tmpdir+strlen(PROC_PATH));
248             }
249          }
250          free(tmpdir);
251       }
252       closedir(dp);
253    }
254    return rc;
255 }
256
257
258 /*
259  *     Write a sysctl setting 
260  *
261  */
262 static int WriteSetting(const char *setting) {
263    int rc = 0;
264    const char *name = setting;
265    const char *value;
266    const char *equals;
267    char *tmpname;
268    FILE *fp;
269    char *outname;
270
271    if (!name) {        /* probably don't want to display this err */
272       return 0;
273    } /* end if */
274
275    equals = index(setting, '=');
276  
277    if (!equals) {
278       fprintf(stderr, ERR_NO_EQUALS, setting);
279       return -1;
280    }
281
282    value = equals + 1;      /* point to the value in name=value */   
283
284    if (!*name || !*value || name == equals) { 
285       fprintf(stderr, ERR_MALFORMED_SETTING, setting);
286       return -2;
287    }
288
289    /* used to open the file */
290    tmpname = malloc(equals-name+1+strlen(PROC_PATH));
291    strcpy(tmpname, PROC_PATH);
292    strncat(tmpname, name, (int)(equals-name)); 
293    tmpname[equals-name+strlen(PROC_PATH)] = 0;
294    slashdot(tmpname+strlen(PROC_PATH),'.','/'); /* change . to / */
295
296    /* used to display the output */
297    outname = malloc(equals-name+1);                       
298    strncpy(outname, name, (int)(equals-name)); 
299    outname[equals-name] = 0;
300    slashdot(outname,'/','.'); /* change / to . */
301  
302    fp = fopen(tmpname, "w");
303
304    if (!fp) {
305       switch(errno) {
306       case ENOENT:
307          if (!IgnoreError) {
308             fprintf(stderr, ERR_INVALID_KEY, outname);
309             rc = -1;
310          }
311          break;
312       case EACCES:
313          fprintf(stderr, ERR_PERMISSION_DENIED, outname);
314          rc = -1;
315          break;
316       default:
317          fprintf(stderr, ERR_UNKNOWN_WRITING, strerror(errno), outname);
318          rc = -1;
319          break;
320       }
321    } else {
322       rc = fprintf(fp, "%s\n", value);
323       if (rc < 0) {
324          fprintf(stderr, ERR_UNKNOWN_WRITING, strerror(errno), outname);
325          fclose(fp);
326       } else {
327          rc=fclose(fp);
328          if (rc != 0) 
329             fprintf(stderr, ERR_UNKNOWN_WRITING, strerror(errno), outname);
330       }
331       if (rc==0 && !Quiet) {
332          if (NameOnly) {
333             fprintf(stdout, "%s\n", outname);
334          } else {
335             if (PrintName) {
336                fprintf(stdout, "%s = %s\n", outname, value);
337             } else {
338                if (PrintNewline)
339                   fprintf(stdout, "%s\n", value);
340                else
341                   fprintf(stdout, "%s", value);
342             }
343          }
344       }
345    }
346
347    free(tmpname);
348    free(outname);
349    return rc;
350 }
351
352
353
354 /*
355  *     Preload the sysctl's from the conf file
356  *           - we parse the file and then reform it (strip out whitespace)
357  *
358  */
359 static int Preload(const char *restrict const filename) {
360    char oneline[256];
361    char buffer[256];
362    FILE *fp;
363    char *t;
364    int n = 0;
365    int rc = 0;
366    char *name, *value;
367
368    fp = (filename[0]=='-' && !filename[1])
369       ? stdin
370       : fopen(filename, "r")
371    ;
372
373    if (!fp) {
374       fprintf(stderr, ERR_PRELOAD_FILE, filename);
375       return -1;
376    }
377
378    while (fgets(oneline, sizeof oneline, fp)) {
379       n++;
380       t = StripLeadingAndTrailingSpaces(oneline);
381
382       if (strlen(t) < 2)
383          continue;
384
385       if (*t == '#' || *t == ';')
386          continue;
387
388       name = strtok(t, "=");
389       if (!name || !*name) {
390          fprintf(stderr, WARN_BAD_LINE, filename, n);
391          continue;
392       }
393
394       StripLeadingAndTrailingSpaces(name);
395
396       value = strtok(NULL, "\n\r");
397       if (!value || !*value) {
398          fprintf(stderr, WARN_BAD_LINE, filename, n);
399          continue;
400       }
401
402       while ((*value == ' ' || *value == '\t') && *value != 0)
403          value++;
404
405       // should NameOnly affect this?
406       sprintf(buffer, "%s=%s", name, value);
407       rc |= WriteSetting(buffer);
408    }
409
410    fclose(fp);
411    return rc;
412 }
413
414
415
416 /*
417  *    Main... 
418  *
419  */
420 int main(int argc, char *argv[]) {
421    const char *me = (const char *)basename(argv[0]);
422    bool SwitchesAllowed = true;
423    bool WriteMode = false;
424    int ReturnCode = 0;
425    const char *preloadfile = DEFAULT_PRELOAD;
426
427    PrintName = true;
428    PrintNewline = true;
429    IgnoreError = false;
430    Quiet = false;
431
432    if (argc < 2) {
433        return Usage(me);
434    }
435
436    argv++;
437
438    for (; argv && *argv && **argv; argv++) {
439       if (SwitchesAllowed && **argv == '-') {        /* we have a switch */
440          if ((*argv)[1] && (*argv)[2]){       // don't yet handle "sysctl -ew"
441               if (!strcmp("--help",*argv)) {
442                  Usage(me);
443                  exit(0);
444               }
445               if (!strcmp("--version",*argv)) {
446                  fprintf(stdout, "sysctl (%s)\n",procps_version);
447                  exit(0);
448               }
449               fprintf(stderr, ERR_UNKNOWN_PARAMETER, *argv);
450               return Usage(me);
451          }
452          switch((*argv)[1]) {
453          case 'b':
454               /* This is "binary" format, which means more for BSD. */
455               PrintNewline = false;
456            /* FALL THROUGH */
457          case 'n':
458               PrintName = false;
459            break;
460          case 'e':
461               // For FreeBSD, -e means a "%s=%s\n" format. ("%s: %s\n" default)
462               // We (and NetBSD) use "%s = %s\n" always, and -e to ignore errors.
463               IgnoreError = true;
464            break;
465          case 'N':
466               NameOnly = true;
467            break;
468          case 'w':
469               SwitchesAllowed = false;
470               WriteMode = true;
471            break;
472          case 'f':  // the NetBSD way
473          case 'p':
474               argv++;
475               if (argv && *argv && **argv) {
476                  preloadfile = *argv;
477               }
478               return Preload(preloadfile);
479          case 'q':
480               Quiet = true;
481            break;
482          case 'o':  // BSD: binary values too, 1st 16 bytes in hex
483          case 'x':  // BSD: binary values too, whole thing in hex
484               /* does nothing */ ;
485            break;
486          case 'a': // string and integer values (for Linux, all of them)
487          case 'A': // same as -a -o
488          case 'X': // same as -a -x
489               SwitchesAllowed = false;
490               return DisplayAll(PROC_PATH);
491          case 'V':
492               fprintf(stdout, "sysctl (%s)\n",procps_version);
493               exit(0);
494          case 'd':   // BSD: print description ("vm.kvm_size: Size of KVM")
495          case 'h':   // BSD: human-readable (did FreeBSD 5 make -e default?)
496          case '?':
497               return Usage(me);
498          default:
499               fprintf(stderr, ERR_UNKNOWN_PARAMETER, *argv);
500               return Usage(me);
501          }
502       } else {
503          if (NameOnly && Quiet)   // nonsense
504             return Usage(me);
505          SwitchesAllowed = false;
506          if (WriteMode || index(*argv, '='))
507             ReturnCode = WriteSetting(*argv);
508          else
509             ReturnCode = ReadSetting(*argv);
510       }
511    }
512
513    return ReturnCode;
514 }
515
516