]> granicus.if.org Git - procps-ng/commitdiff
sysctl: Support systemd glob patterns
authorCraig Small <csmall@dropbear.xyz>
Mon, 13 Sep 2021 12:07:37 +0000 (22:07 +1000)
committerCraig Small <csmall@dropbear.xyz>
Wed, 15 Sep 2021 10:07:32 +0000 (20:07 +1000)
systemd-sysctl handles glob patterns along with overrides and
exceptions. Now the procps sysctl does it too.

The return value for sysctl is consistently either 0 or 1.

Added tests to check sysctl functions.

References:
 procps-ng/procps#191

Signed-off-by: Craig Small <csmall@dropbear.xyz>
NEWS
sysctl.c
testsuite/config/unix.exp
testsuite/sysctl.test/sysctl_write.exp [new file with mode: 0644]
testsuite/sysctl_glob_test.conf [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index dd7ea3f3070f2c314f307bcb104d1180443bead9..f114fcf7b81e31e6361a0295071acf6eff899973 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,7 @@ procps-ng-NEXT
   * ps: Add PSS and USS fields                             issue #112
   * ps: Add two new autogroup fields
   * slabtop: Don't combine d and o options                 issue #160
+  * sysctl: Add support for systemd glob patterns          issue #191
   * sysctl: Check resolved path to be under /proc/sys      issue #179
   * top: added LOGID similar to 3.3.13 ps LUID
   * top: added EXE identical to 3.3.17 ps EXE
index d6173f26d2fe5ca03a42d7f6a7577619b2cae492..64676b2afc131e7e428a936d971b3b52d185408c 100644 (file)
--- a/sysctl.c
+++ b/sysctl.c
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  *
+ * Part of this code comes from systemd, especially sysctl.c
  * Changelog:
  *            v1.01:
  *                   - added -p <preload> to preload values from a file
@@ -40,6 +41,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <ctype.h>
 
 #include "c.h"
 #include "fileutils.h"
@@ -63,12 +65,34 @@ static bool PrintName;
 static bool PrintNewline;
 static bool IgnoreError;
 static bool Quiet;
+static bool DryRun;
 static char *pattern;
 
 #define LINELEN 4096
 static char *iobuf;
 static size_t iolen = LINELEN;
 
+typedef struct SysctlSetting {
+    char *key;
+    char *path;
+    char *value;
+    bool ignore_failure;
+    bool glob_exclude;
+    struct SysctlSetting *next;
+} SysctlSetting;
+
+typedef struct SettingList {
+    struct SysctlSetting *head;
+    struct SysctlSetting *tail;
+} SettingList;
+
+#define GLOB_CHARS "*?["
+static inline bool string_is_glob(const char *p)
+{
+    return !!strpbrk(p, GLOB_CHARS);
+}
+
+
 /* Function prototypes. */
 static int pattern_match(const char *string, const char *pat);
 static int DisplayAll(const char *restrict const path);
@@ -115,6 +139,81 @@ static void slashdot(char *restrict p, char old, char new)
        }
 }
 
+static void setting_free(SysctlSetting *s) {
+    if (!s)
+       return;
+
+    free(s->key);
+    free(s->path);
+    free(s->value);
+    free(s);
+}
+
+static SysctlSetting *setting_new(
+       const char *key,
+       const char *value,
+       bool ignore_failure,
+    bool glob_exclude) {
+
+    SysctlSetting *s = NULL;
+    char *path = NULL;
+    int proc_len;
+
+    proc_len = strlen(PROC_PATH);
+    /* used to open the file */
+    path = xmalloc(strlen(key) + proc_len + 2);
+    strcpy(path, PROC_PATH);
+    if (key[0] == '-')
+        strcat(path + proc_len, key+1);
+    else
+        strcat(path + proc_len, key);
+    /* change . to / */
+    slashdot(path + proc_len, '.', '/');
+
+    s = xmalloc(sizeof(SysctlSetting));
+
+    *s = (SysctlSetting) {
+        .key = strdup(key),
+        .path = path,
+        .value = value? strdup(value): NULL,
+        .ignore_failure = ignore_failure,
+        .glob_exclude = glob_exclude,
+        .next = NULL,
+    };
+
+    return s;
+}
+
+static void settinglist_add(SettingList *l, SysctlSetting *s) {
+    SysctlSetting *old_tail;
+
+    if (!l)
+        return;
+
+    if (l->head == NULL)
+        l->head = s;
+
+    if (l->tail != NULL) {
+        old_tail = l->tail;
+        old_tail->next = s;
+    }
+    l->tail = s;
+}
+
+static SysctlSetting *settinglist_findpath(const SettingList *l, const char *path) {
+    SysctlSetting *node;
+
+    for (node=l->head; node != NULL; node = node->next) {
+        if (strcmp(node->path, path) == 0)
+            return node;
+    }
+    return NULL;
+}
+
+/* Function prototypes. */
+static int pattern_match(const char *string, const char *pat);
+static int DisplayAll(const char *restrict const path);
+
 /*
  * Display the usage format
  */
@@ -130,6 +229,7 @@ static void __attribute__ ((__noreturn__))
        fputs(_("  -A                   alias of -a\n"), out);
        fputs(_("  -X                   alias of -a\n"), out);
        fputs(_("      --deprecated     include deprecated parameters to listing\n"), out);
+       fputs(_("      --dry-run        Print the key and values but do not write\n"), out);
        fputs(_("  -b, --binary         print value without new line\n"), out);
        fputs(_("  -e, --ignore         ignore unknown variables errors\n"), out);
        fputs(_("  -N, --names          print variable names without values\n"), out);
@@ -152,6 +252,39 @@ static void __attribute__ ((__noreturn__))
        exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
 }
 
+/*
+ * Strip left/leading side of a string
+ */
+static char *lstrip(char *line)
+{
+    char *start;
+
+    if (!line || !*line)
+        return line;
+
+    start = line;
+    while(isspace(*start)) start++;
+
+    return start;
+}
+
+/*
+ * Strip right/trailing side of a string
+ * by placing a \0
+ */
+static void rstrip(char *line)
+{
+    char *end;
+
+    if (!line || !*line)
+        return;
+
+    end = line + strlen(line) - 1;
+    while(end > line && isspace(*end)) end--;
+
+    end[1] = '\0';
+}
+
 /*
  * Strip the leading and trailing spaces from a string
  */
@@ -181,7 +314,7 @@ static char *StripLeadingAndTrailingSpaces(char *oneline)
  */
 static int ReadSetting(const char *restrict const name)
 {
-       int rc = 0;
+       int rc = EXIT_SUCCESS;
        char *restrict tmpname;
        char *restrict outname;
        ssize_t rlen;
@@ -213,7 +346,7 @@ static int ReadSetting(const char *restrict const name)
        if (stat(tmpname, &ts) < 0) {
                if (!IgnoreError) {
                        xwarn(_("cannot stat %s"), tmpname);
-                       rc = -1;
+                       rc = EXIT_FAILURE;
                }
                goto out;
        }
@@ -235,7 +368,7 @@ static int ReadSetting(const char *restrict const name)
        }
 
        if (pattern && !pattern_match(outname, pattern)) {
-               rc = 0;
+               rc = EXIT_SUCCESS;
                goto out;
        }
 
@@ -251,19 +384,19 @@ static int ReadSetting(const char *restrict const name)
                case ENOENT:
                        if (!IgnoreError) {
                                xwarnx(_("\"%s\" is an unknown key"), outname);
-                               rc = -1;
+                               rc = EXIT_FAILURE;
                        }
                        break;
                case EACCES:
                        xwarnx(_("permission denied on key '%s'"), outname);
-                       rc = -1;
+                       rc = EXIT_FAILURE;
                        break;
                case EIO:           /* Ignore stable_secret below /proc/sys/net/ipv6/conf */
-                       rc = -1;
+                       rc = EXIT_FAILURE;
                        break;
                default:
                        xwarn(_("reading key \"%s\""), outname);
-                       rc = -1;
+                       rc = EXIT_FAILURE;
                        break;
                }
        } else {
@@ -299,7 +432,7 @@ static int ReadSetting(const char *restrict const name)
                        case EACCES:
                                xwarnx(_("permission denied on key '%s'"),
                                       outname);
-                               rc = -1;
+                               rc = EXIT_FAILURE;
                                break;
                        case EISDIR: {
                                        size_t len;
@@ -311,11 +444,11 @@ static int ReadSetting(const char *restrict const name)
                                        goto out;
                                }
                        case EIO:           /* Ignore stable_secret below /proc/sys/net/ipv6/conf */
-                               rc = -1;
+                               rc = EXIT_FAILURE;
                                break;
                        default:
                                xwarnx(_("reading key \"%s\""), outname);
-                               rc = -1;
+                               rc = EXIT_FAILURE;
                        case 0:
                                break;
                        }
@@ -343,7 +476,7 @@ static int is_deprecated(char *filename)
  */
 static int DisplayAll(const char *restrict const path)
 {
-       int rc = 0;
+       int rc = EXIT_SUCCESS;
        int rc2;
        DIR *restrict dp;
        struct dirent *restrict de;
@@ -353,7 +486,7 @@ static int DisplayAll(const char *restrict const path)
 
        if (!dp) {
                xwarnx(_("unable to open directory \"%s\""), path);
-               rc = -1;
+               rc = EXIT_FAILURE;
        } else {
                readdir(dp);    /* skip .  */
                readdir(dp);    /* skip .. */
@@ -389,70 +522,25 @@ static int DisplayAll(const char *restrict const path)
 /*
  * Write a sysctl setting
  */
-static int WriteSetting(const char *setting)
-{
-       int rc = 0;
-       const char *name = setting;
-       const char *value;
-       const char *equals;
-       char *tmpname;
-       char *outname;
-       char *last_dot;
-       bool ignore_failure;
-
-       FILE *fp;
+static int WriteSetting(
+    const char *key,
+    const char *path,
+    const char *value,
+    const bool ignore_failure) {
+
+    int rc = EXIT_SUCCESS;
+    FILE *fp;
        struct stat ts;
 
-       if (!name)
-               /* probably don't want to display this err */
-               return 0;
-
-       equals = strchr(setting, '=');
-
-       if (!equals) {
-               xwarnx(_("\"%s\" must be of the form name=value"),
-                      setting);
-               return -1;
-       }
-
-       /* point to the value in name=value */
-       value = equals + 1;
-
-       if (!*name || name == equals) {
-               xwarnx(_("malformed setting \"%s\""), setting);
-               return -2;
-       }
-
-       ignore_failure = name[0] == '-';
-       if (ignore_failure)
-           name++;
-
-       /* used to open the file */
-       tmpname = xmalloc(equals - name + 1 + strlen(PROC_PATH));
-       strcpy(tmpname, PROC_PATH);
-       strncat(tmpname, name, (int) (equals - name));
-       tmpname[equals - name + strlen(PROC_PATH)] = 0;
-       /* change . to / */
-       slashdot(tmpname + strlen(PROC_PATH), '.', '/');
-
-       /* used to display the output */
-       outname = xmalloc(equals - name + 1);
-       strncpy(outname, name, (int) (equals - name));
-       outname[equals - name] = 0;
-       /* change / to . */
-       slashdot(outname, '/', '.');
-       last_dot = strrchr(outname, '.');
-       if (last_dot != NULL && is_deprecated(last_dot + 1)) {
-               xwarnx(_("%s is deprecated, value not set"), outname);
-               goto out;
-        }
+    if (!key || !path)
+        return rc;
 
-       if (stat(tmpname, &ts) < 0) {
+       if (stat(path, &ts) < 0) {
                if (!IgnoreError) {
-                       xwarn(_("cannot stat %s"), tmpname);
-                       rc = -1;
+                       xwarn(_("cannot stat %s"), path);
+                       rc = EXIT_FAILURE;
                }
-               goto out;
+        return rc;
        }
 
        if (!is_proc_path(tmpname)) {
@@ -461,63 +549,161 @@ static int WriteSetting(const char *setting)
        }
 
        if ((ts.st_mode & S_IWUSR) == 0) {
-               xwarn(_("setting key \"%s\""), outname);
-               goto out;
+               xwarn(_("setting key \"%s\""), key);
+        return rc;
        }
 
        if (S_ISDIR(ts.st_mode)) {
-               xwarn(_("setting key \"%s\""), outname);
-               goto out;
+               xwarn(_("setting key \"%s\""), key);
+        return rc;
        }
 
-       fp = fprocopen(tmpname, "w");
-
-       if (!fp) {
-               switch (errno) {
-               case ENOENT:
-                       if (!IgnoreError) {
-                               xwarnx(_("\"%s\" is an unknown key%s"), outname, (ignore_failure?_(", ignoring"):""));
+    if (!DryRun) {
+        if ((fp = fprocopen(path, "w")) == NULL) {
+            switch (errno) {
+            case ENOENT:
+                if (!IgnoreError) {
+                    xwarnx(_("\"%s\" is an unknown key%s"),
+                           key, (ignore_failure?_(", ignoring"):""));
                                if (!ignore_failure)
-                                   rc = -1;
+                                   rc = EXIT_FAILURE;
                        }
                        break;
-               case EPERM:
-               case EROFS:
-               case EACCES:
-                       xwarnx(_("permission denied on key \"%s\"%s"), outname, (ignore_failure?_(", ignoring"):""));
-                       break;
-               default:
-                       xwarn(_("setting key \"%s\"%s"), outname, (ignore_failure?_(", ignoring"):""));
-                       break;
-               }
-               if (!ignore_failure && errno != ENOENT)
-                   rc = -1;
-       } else {
-               rc = fprintf(fp, "%s\n", value);
-               if (0 < rc)
-                       rc = 0;
-               if (close_stream(fp) != 0)
-                       xwarn(_("setting key \"%s\""), outname);
-               else if (rc == 0 && !Quiet) {
-                       if (NameOnly) {
-                               fprintf(stdout, "%s\n", outname);
-                       } else {
-                               if (PrintName) {
-                                       fprintf(stdout, "%s = %s\n",
-                                               outname, value);
-                               } else {
-                                       if (PrintNewline)
-                                               fprintf(stdout, "%s\n", value);
-                                       else
-                                               fprintf(stdout, "%s", value);
-                               }
-                       }
-               }
-       }
-      out:
-       free(tmpname);
-       free(outname);
-       return rc;
+            case EPERM:
+            case EROFS:
+            case EACCES:
+                xwarnx(_("permission denied on key \"%s\"%s"),
+                       key, (ignore_failure?_(", ignoring"):""));
+                break;
+            default:
+                xwarn(_("setting key \"%s\"%s"),
+                      key, (ignore_failure?_(", ignoring"):""));
+                break;
+            }
+            if (!ignore_failure && errno != ENOENT)
+                   rc = EXIT_FAILURE;
+        } else {
+           if (0 < fprintf(fp, "%s\n", value))
+               rc = EXIT_SUCCESS;
+            if (close_stream(fp) != 0) {
+                xwarn(_("setting key \"%s\""), path);
+                return rc;
+            }
+        }
+    }
+    if ((rc == EXIT_SUCCESS && !Quiet) || DryRun) {
+        if (NameOnly) {
+            printf("%s\n", value);
+        } else {
+            if (PrintName) {
+                printf("%s = %s\n", path, value);
+            } else {
+                if (PrintNewline)
+                    printf("%s\n", value);
+                else
+                    printf("%s", value);
+            }
+        }
+    }
+    return rc;
+}
+
+/*
+ * parse each configuration line, there are multiple ways of specifying
+ * a key/value here:
+ *
+ * key = value                               simple setting
+ * -key = value                              ignore errors
+ * key.pattern.*.with.glob = value           set keys that match glob
+ * -key.pattern.exclude.with.glob            dont set this value
+ * key.pattern.override.with.glob = value    set this glob match to value
+ *
+ */
+
+static SysctlSetting *parse_setting_line(
+    const char *path,
+    const int linenum,
+    char *line)
+{
+    SysctlSetting *s;
+    char *key;
+    char *value;
+    bool glob_exclude = FALSE;
+    bool ignore_failure = FALSE;
+
+    key = lstrip(line);
+    if (strlen(key) < 2)
+        return NULL;
+
+    /* skip over comments */
+    if (key[0] == '#' || key[0] == ';')
+        return NULL;
+
+    if (pattern && !pattern_match(key, pattern))
+        return NULL;
+
+    value = strchr(key, '=');
+    if (value == NULL) {
+        if (key[0] == '-') {
+            glob_exclude = TRUE;
+            key++;
+            value = NULL;
+            rstrip(key);
+        } else {
+            xwarnx(_("%s(%d): invalid syntax, continuing..."),
+                   path, linenum);
+            return NULL;
+        }
+    } else {
+        value[0]='\0';
+        if (key[0] == '-') {
+            ignore_failure = TRUE;
+            key++;
+        }
+        value++; // skip over =
+        value=lstrip(value);
+        rstrip(value);
+        rstrip(key);
+    }
+    return setting_new(key, value, ignore_failure, glob_exclude);
+}
+
+/* Go through the setting list, expand and sort out
+ * setting globs and actually write the settings out
+ */
+static int write_setting_list(const SettingList *sl)
+{
+    SysctlSetting *node;
+    int rc = EXIT_SUCCESS;
+
+    for (node=sl->head; node != NULL; node=node->next) {
+        if (node->glob_exclude)
+            continue;
+
+        if (string_is_glob(node->path)) {
+            char *gl_path;
+            glob_t globbuf;
+            int i;
+
+            if (glob(node->path, 0, NULL, &globbuf) != 0)
+                continue;
+
+            for(i=0; i < globbuf.gl_pathc; i++) {
+                if (settinglist_findpath(sl, globbuf.gl_pathv[i]))
+                    continue; // override or exclude
+
+                rc |= WriteSetting(node->key, globbuf.gl_pathv[i], node->value,
+                                   node->ignore_failure);
+            }
+        } else {
+            rc |= WriteSetting(node->key, node->path, node->value,
+                               node->ignore_failure);
+        }
+
+
+    }
+
+    return rc;
 }
 
 static int pattern_match(const char *string, const char *pat)
@@ -538,12 +724,12 @@ static int pattern_match(const char *string, const char *pat)
  * Preload the sysctl's from the conf file.  We parse the file and then
  * reform it (strip out whitespace).
  */
-static int Preload(const char *restrict const filename)
+static int Preload(SettingList *setlist, const char *restrict const filename)
 {
        FILE *fp;
        char *t;
        int n = 0;
-       int rc = 0;
+       int rc = EXIT_SUCCESS;
        ssize_t rlen;
        char *name, *value;
        glob_t globbuf;
@@ -572,62 +758,26 @@ static int Preload(const char *restrict const filename)
                    ? stdin : fopen(globbuf.gl_pathv[j], "r");
                if (!fp) {
                        xwarn(_("cannot open \"%s\""), globbuf.gl_pathv[j]);
-                       rc = -1;
-                       goto out;
+            return EXIT_FAILURE;
                }
 
                while ((rlen =  getline(&iobuf, &iolen, fp)) > 0) {
                        size_t offset;
+            SysctlSetting *setting;
 
                        n++;
 
                        if (rlen < 2)
                                continue;
 
-                       t = StripLeadingAndTrailingSpaces(iobuf);
-                       if (strlen(t) < 2)
-                               continue;
-
-                       if (*t == '#' || *t == ';')
-                               continue;
-
-                       name = strtok(t, "=");
-                       if (!name || !*name) {
-                               xwarnx(_("%s(%d): invalid syntax, continuing..."),
-                                      globbuf.gl_pathv[j], n);
-                               continue;
-                       }
-
-                       StripLeadingAndTrailingSpaces(name);
-
-                       if (pattern && !pattern_match(name, pattern))
-                               continue;
-
-                       offset = strlen(name);
-                       memmove(&iobuf[0], name, offset);
-                       iobuf[offset++] = '=';
-
-                       value = strtok(NULL, "\n\r");
-                       if (!value || !*value) {
-                               xwarnx(_("%s(%d): invalid syntax, continuing..."),
-                                      globbuf.gl_pathv[j], n);
-                               continue;
-                       }
-
-                       while ((*value == ' ' || *value == '\t') && *value != 0)
-                               value++;
-
-                       /* should NameOnly affect this? */
-                       memmove(&iobuf[offset], value, strlen(value));
-                       offset += strlen(value);
-                       iobuf[offset] = '\0';
-
-                       rc |= WriteSetting(iobuf);
+            if ( (setting = parse_setting_line(globbuf.gl_pathv[j], n, iobuf))
+                 == NULL)
+                continue;
+            settinglist_add(setlist, setting);
                }
 
                fclose(fp);
        }
-out:
        return rc;
 }
 
@@ -643,7 +793,7 @@ static int sortpairs(const void *A, const void *B)
        return strcmp(a->name, b->name);
 }
 
-static int PreloadSystem(void)
+static int PreloadSystem(SettingList *setlist)
 {
        unsigned di, i;
        const char *dirs[] = {
@@ -655,7 +805,7 @@ static int PreloadSystem(void)
        };
        struct pair **cfgs = NULL;
        unsigned ncfgs = 0;
-       int rc = 0;
+       int rc = EXIT_SUCCESS;
        struct stat ts;
        enum { nprealloc = 16 };
 
@@ -713,14 +863,14 @@ static int PreloadSystem(void)
        for (i = 0; i < ncfgs; ++i) {
                if (!Quiet)
                        printf(_("* Applying %s ...\n"), cfgs[i]->value);
-               rc |= Preload(cfgs[i]->value);
+               rc |= Preload(setlist, cfgs[i]->value);
        }
 
 
        if (stat(DEFAULT_PRELOAD, &ts) == 0 && S_ISREG(ts.st_mode)) {
                if (!Quiet)
                        printf(_("* Applying %s ...\n"), DEFAULT_PRELOAD);
-               rc |= Preload(DEFAULT_PRELOAD);
+               rc |= Preload(setlist, DEFAULT_PRELOAD);
        }
 
        /* cleaning */
@@ -742,15 +892,19 @@ int main(int argc, char *argv[])
        bool preloadfileOpt = false;
        int ReturnCode = 0;
        int c;
+    int rc;
        const char *preloadfile = NULL;
+    SettingList *setlist;
 
        enum {
                DEPRECATED_OPTION = CHAR_MAX + 1,
-               SYSTEM_OPTION
+               SYSTEM_OPTION,
+        DRYRUN_OPTION
        };
        static const struct option longopts[] = {
                {"all", no_argument, NULL, 'a'},
                {"deprecated", no_argument, NULL, DEPRECATED_OPTION},
+               {"dry-run", no_argument, NULL, DRYRUN_OPTION},
                {"binary", no_argument, NULL, 'b'},
                {"ignore", no_argument, NULL, 'e'},
                {"names", no_argument, NULL, 'N'},
@@ -778,6 +932,10 @@ int main(int argc, char *argv[])
        IgnoreError = false;
        Quiet = false;
        IgnoreDeprecated = true;
+    DryRun = false;
+    setlist = xmalloc(sizeof(SettingList));
+    setlist->head = NULL;
+    setlist->tail = NULL;
 
        if (argc < 2)
                Usage(stderr);
@@ -830,7 +988,12 @@ int main(int argc, char *argv[])
                        break;
                case SYSTEM_OPTION:
                        IgnoreError = true;
-                       return PreloadSystem();
+                       rc |= PreloadSystem(setlist);
+            rc |= write_setting_list(setlist);
+            return rc;
+        case DRYRUN_OPTION:
+            DryRun = true;
+            break;
                case 'r':
                        pattern = xstrdup(optarg);
                        break;
@@ -858,15 +1021,16 @@ int main(int argc, char *argv[])
                int ret = EXIT_SUCCESS, i;
                if (!preloadfile) {
                        if (!argc) {
-                               ret |= Preload(DEFAULT_PRELOAD);
+                               ret |= Preload(setlist, DEFAULT_PRELOAD);
                        }
                } else {
                        /* This happens when -pfile option is
                         * used without space. */
-                       ret |= Preload(preloadfile);
+                       ret |= Preload(setlist, preloadfile);
                }
                for (i = 0; i < argc; i++)
-                       ret |= Preload(argv[i]);
+                       ret |= Preload(setlist, argv[i]);
+        ret |= write_setting_list(setlist);
                return ret;
        }
 
@@ -880,9 +1044,14 @@ int main(int argc, char *argv[])
                      program_invocation_short_name);
 
        for ( ; *argv; argv++) {
-               if (WriteMode || strchr(*argv, '='))
-                       ReturnCode += WriteSetting(*argv);
-               else
+               if (WriteMode || strchr(*argv, '=')) {
+            SysctlSetting *s;
+            if ( (s = parse_setting_line("command line", 0, *argv)) != NULL)
+                ReturnCode |= WriteSetting(s->key, s->path, s->value,
+                                           s->ignore_failure);
+            else
+                ReturnCode |= EXIT_FAILURE;
+        } else
                        ReturnCode += ReadSetting(*argv);
        }
        return ReturnCode;
index 4156c3b903b28818240b624bda77e82aa301d4da..ecdc0bf7b86ba27361adf33679ebced0e7e1a6a4 100644 (file)
@@ -136,6 +136,15 @@ proc expect_table_dsc { test match_header match_item } {
     #}
 }
 
+proc expect_spawn_retval { test retval } {
+    foreach {pid spawnid os_error_flag value} [wait] break
+
+    if {$value == $retval} {
+      return
+    }
+    fail "$test (exit value)"
+}
+
 proc make_pipeproc { } {
     global pipeproc_pid pipeproc_spawnid topdir
 
diff --git a/testsuite/sysctl.test/sysctl_write.exp b/testsuite/sysctl.test/sysctl_write.exp
new file mode 100644 (file)
index 0000000..5a74dec
--- /dev/null
@@ -0,0 +1,29 @@
+
+set sysctl ${topdir}sysctl
+
+set test "sysctl write from command line"
+spawn $sysctl --dry-run kernel.hostname=procps-test
+expect_pass "$test" "/proc/sys/kernel/hostname = procps-test"
+
+set test "sysctl write from configuration file"
+spawn $sysctl --dry-run -f ${topdir}testsuite/sysctl_glob_test.conf
+expect_pass "$test" "/proc/sys/fs/protected_fifos = 2\\s+/proc/sys/fs/protected_symlinks = 2\\s+/proc/sys/fs/protected_hardlinks = 1"
+
+set hostname_file "/proc/sys/kernel/hostname"
+if {[file exists ${hostname_file}]} {
+  if {[file writable ${hostname_file}]} {
+    unsupported "sysctl write: hostname file is writable"
+  } else {
+    set test "sysctl write unwritable file"
+    spawn $sysctl -q kernel.hostname=procpstest
+    expect_pass "$test" "sysctl: permission denied on key \"kernel.hostname\"\\s*$"
+    expect_spawn_retval "$test" 1
+
+    set test "sysctl write unwritable file ignored"
+    spawn $sysctl -q -- -kernel.hostname=procpstest
+    expect_pass "$test" "sysctl: permission denied on key \"kernel.hostname\", ignoring\\s*$"
+    expect_spawn_retval "$test" 0
+  }
+} else {
+  unsupported "sysctl write: hostname file doe not exist"
+}
diff --git a/testsuite/sysctl_glob_test.conf b/testsuite/sysctl_glob_test.conf
new file mode 100644 (file)
index 0000000..45ae904
--- /dev/null
@@ -0,0 +1,6 @@
+#
+# Test configuration for for glob in sysctl
+#
+fs.protected_* = 2
+fs.protected_hardlinks = 1
+-fs.protected_regular