Blame SOURCES/sysctl-support-systemd-globs.patch

fecd69
diff -up ./NEWS.ori ./NEWS
fecd69
diff -up ./sysctl.c.ori ./sysctl.c
fecd69
--- ./sysctl.c.ori	2021-02-09 11:11:25.000000000 +0100
fecd69
+++ ./sysctl.c	2022-07-29 15:50:50.767380061 +0200
fecd69
@@ -17,6 +17,7 @@
fecd69
  * along with this program; if not, write to the Free Software
fecd69
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
fecd69
  *
fecd69
+ * Part of this code comes from systemd, especially sysctl.c
fecd69
  * Changelog:
fecd69
  *            v1.01:
fecd69
  *                   - added -p <preload> to preload values from a file
fecd69
@@ -40,6 +41,7 @@
fecd69
 #include <sys/stat.h>
fecd69
 #include <sys/types.h>
fecd69
 #include <unistd.h>
fecd69
+#include <ctype.h>
fecd69
 
fecd69
 #include "c.h"
fecd69
 #include "fileutils.h"
fecd69
@@ -66,12 +68,34 @@ static bool PrintName;
fecd69
 static bool PrintNewline;
fecd69
 static bool IgnoreError;
fecd69
 static bool Quiet;
fecd69
+static bool DryRun;
fecd69
 static char *pattern;
fecd69
 
fecd69
 #define LINELEN 4096
fecd69
 static char *iobuf;
fecd69
 static size_t iolen = LINELEN;
fecd69
 
fecd69
+typedef struct SysctlSetting {
fecd69
+    char *key;
fecd69
+    char *path;
fecd69
+    char *value;
fecd69
+    bool ignore_failure;
fecd69
+    bool glob_exclude;
fecd69
+    struct SysctlSetting *next;
fecd69
+} SysctlSetting;
fecd69
+
fecd69
+typedef struct SettingList {
fecd69
+    struct SysctlSetting *head;
fecd69
+    struct SysctlSetting *tail;
fecd69
+} SettingList;
fecd69
+
fecd69
+#define GLOB_CHARS "*?["
fecd69
+static inline bool string_is_glob(const char *p)
fecd69
+{
fecd69
+    return !!strpbrk(p, GLOB_CHARS);
fecd69
+}
fecd69
+
fecd69
+
fecd69
 /* Function prototypes. */
fecd69
 static int pattern_match(const char *string, const char *pat);
fecd69
 static int DisplayAll(const char *restrict const path);
fecd69
@@ -100,6 +124,81 @@ static void slashdot(char *restrict p, c
fecd69
 	}
fecd69
 }
fecd69
 
fecd69
+static void setting_free(SysctlSetting *s) {
fecd69
+    if (!s)
fecd69
+	return;
fecd69
+
fecd69
+    free(s->key);
fecd69
+    free(s->path);
fecd69
+    free(s->value);
fecd69
+    free(s);
fecd69
+}
fecd69
+
fecd69
+static SysctlSetting *setting_new(
fecd69
+	const char *key,
fecd69
+	const char *value,
fecd69
+	bool ignore_failure,
fecd69
+    bool glob_exclude) {
fecd69
+
fecd69
+    SysctlSetting *s = NULL;
fecd69
+    char *path = NULL;
fecd69
+    int proc_len;
fecd69
+
fecd69
+    proc_len = strlen(PROC_PATH);
fecd69
+    /* used to open the file */
fecd69
+    path = xmalloc(strlen(key) + proc_len + 2);
fecd69
+    strcpy(path, PROC_PATH);
fecd69
+    if (key[0] == '-')
fecd69
+        strcat(path + proc_len, key+1);
fecd69
+    else
fecd69
+        strcat(path + proc_len, key);
fecd69
+    /* change . to / */
fecd69
+    slashdot(path + proc_len, '.', '/');
fecd69
+
fecd69
+    s = xmalloc(sizeof(SysctlSetting));
fecd69
+
fecd69
+    *s = (SysctlSetting) {
fecd69
+        .key = strdup(key),
fecd69
+        .path = path,
fecd69
+        .value = value? strdup(value): NULL,
fecd69
+        .ignore_failure = ignore_failure,
fecd69
+        .glob_exclude = glob_exclude,
fecd69
+        .next = NULL,
fecd69
+    };
fecd69
+
fecd69
+    return s;
fecd69
+}
fecd69
+
fecd69
+static void settinglist_add(SettingList *l, SysctlSetting *s) {
fecd69
+    SysctlSetting *old_tail;
fecd69
+
fecd69
+    if (!l)
fecd69
+        return;
fecd69
+
fecd69
+    if (l->head == NULL)
fecd69
+        l->head = s;
fecd69
+
fecd69
+    if (l->tail != NULL) {
fecd69
+        old_tail = l->tail;
fecd69
+        old_tail->next = s;
fecd69
+    }
fecd69
+    l->tail = s;
fecd69
+}
fecd69
+
fecd69
+static SysctlSetting *settinglist_findpath(const SettingList *l, const char *path) {
fecd69
+    SysctlSetting *node;
fecd69
+
fecd69
+    for (node=l->head; node != NULL; node = node->next) {
fecd69
+        if (strcmp(node->path, path) == 0)
fecd69
+            return node;
fecd69
+    }
fecd69
+    return NULL;
fecd69
+}
fecd69
+
fecd69
+/* Function prototypes. */
fecd69
+static int pattern_match(const char *string, const char *pat);
fecd69
+static int DisplayAll(const char *restrict const path);
fecd69
+
fecd69
 /*
fecd69
  * Display the usage format
fecd69
  */
fecd69
@@ -115,6 +214,7 @@ static void __attribute__ ((__noreturn__
fecd69
 	fputs(_("  -A                   alias of -a\n"), out);
fecd69
 	fputs(_("  -X                   alias of -a\n"), out);
fecd69
 	fputs(_("      --deprecated     include deprecated parameters to listing\n"), out);
fecd69
+	fputs(_("      --dry-run        Print the key and values but do not write\n"), out);
fecd69
 	fputs(_("  -b, --binary         print value without new line\n"), out);
fecd69
 	fputs(_("  -e, --ignore         ignore unknown variables errors\n"), out);
fecd69
 	fputs(_("  -N, --names          print variable names without values\n"), out);
fecd69
@@ -138,6 +238,39 @@ static void __attribute__ ((__noreturn__
fecd69
 }
fecd69
 
fecd69
 /*
fecd69
+ * Strip left/leading side of a string
fecd69
+ */
fecd69
+static char *lstrip(char *line)
fecd69
+{
fecd69
+    char *start;
fecd69
+
fecd69
+    if (!line || !*line)
fecd69
+        return line;
fecd69
+
fecd69
+    start = line;
fecd69
+    while(isspace(*start)) start++;
fecd69
+
fecd69
+    return start;
fecd69
+}
fecd69
+
fecd69
+/*
fecd69
+ * Strip right/trailing side of a string
fecd69
+ * by placing a \0
fecd69
+ */
fecd69
+static void rstrip(char *line)
fecd69
+{
fecd69
+    char *end;
fecd69
+
fecd69
+    if (!line || !*line)
fecd69
+        return;
fecd69
+
fecd69
+    end = line + strlen(line) - 1;
fecd69
+    while(end > line && isspace(*end)) end--;
fecd69
+
fecd69
+    end[1] = '\0';
fecd69
+}
fecd69
+
fecd69
+/*
fecd69
  * Strip the leading and trailing spaces from a string
fecd69
  */
fecd69
 static char *StripLeadingAndTrailingSpaces(char *oneline)
fecd69
@@ -166,7 +299,7 @@ static char *StripLeadingAndTrailingSpac
fecd69
  */
fecd69
 static int ReadSetting(const char *restrict const name)
fecd69
 {
fecd69
-	int rc = 0;
fecd69
+	int rc = EXIT_SUCCESS;
fecd69
 	char *restrict tmpname;
fecd69
 	char *restrict outname;
fecd69
 	ssize_t rlen;
fecd69
@@ -198,7 +331,7 @@ static int ReadSetting(const char *restr
fecd69
 	if (stat(tmpname, &ts) < 0) {
fecd69
 		if (!IgnoreError) {
fecd69
 			xwarn(_("cannot stat %s"), tmpname);
fecd69
-			rc = -1;
fecd69
+			rc = EXIT_FAILURE;
fecd69
 		}
fecd69
 		goto out;
fecd69
 	}
fecd69
@@ -215,7 +348,7 @@ static int ReadSetting(const char *restr
fecd69
 	}
fecd69
 
fecd69
 	if (pattern && !pattern_match(outname, pattern)) {
fecd69
-		rc = 0;
fecd69
+		rc = EXIT_SUCCESS;
fecd69
 		goto out;
fecd69
 	}
fecd69
 
fecd69
@@ -231,19 +364,19 @@ static int ReadSetting(const char *restr
fecd69
 		case ENOENT:
fecd69
 			if (!IgnoreError) {
fecd69
 				xwarnx(_("\"%s\" is an unknown key"), outname);
fecd69
-				rc = -1;
fecd69
+				rc = EXIT_FAILURE;
fecd69
 			}
fecd69
 			break;
fecd69
 		case EACCES:
fecd69
 			xwarnx(_("permission denied on key '%s'"), outname);
fecd69
-			rc = -1;
fecd69
+			rc = EXIT_FAILURE;
fecd69
 			break;
fecd69
 		case EIO:	    /* Ignore stable_secret below /proc/sys/net/ipv6/conf */
fecd69
-			rc = -1;
fecd69
+			rc = EXIT_FAILURE;
fecd69
 			break;
fecd69
 		default:
fecd69
 			xwarn(_("reading key \"%s\""), outname);
fecd69
-			rc = -1;
fecd69
+			rc = EXIT_FAILURE;
fecd69
 			break;
fecd69
 		}
fecd69
 	} else {
fecd69
@@ -279,7 +412,7 @@ static int ReadSetting(const char *restr
fecd69
 			case EACCES:
fecd69
 				xwarnx(_("permission denied on key '%s'"),
fecd69
 				       outname);
fecd69
-				rc = -1;
fecd69
+				rc = EXIT_FAILURE;
fecd69
 				break;
fecd69
 			case EISDIR: {
fecd69
 					size_t len;
fecd69
@@ -291,11 +424,11 @@ static int ReadSetting(const char *restr
fecd69
 					goto out;
fecd69
 				}
fecd69
 			case EIO:	    /* Ignore stable_secret below /proc/sys/net/ipv6/conf */
fecd69
-				rc = -1;
fecd69
+				rc = EXIT_FAILURE;
fecd69
 				break;
fecd69
 			default:
fecd69
 				xwarnx(_("reading key \"%s\""), outname);
fecd69
-				rc = -1;
fecd69
+				rc = EXIT_FAILURE;
fecd69
 			case 0:
fecd69
 				break;
fecd69
 			}
fecd69
@@ -323,7 +456,7 @@ static int is_deprecated(char *filename)
fecd69
  */
fecd69
 static int DisplayAll(const char *restrict const path)
fecd69
 {
fecd69
-	int rc = 0;
fecd69
+	int rc = EXIT_SUCCESS;
fecd69
 	int rc2;
fecd69
 	DIR *restrict dp;
fecd69
 	struct dirent *restrict de;
fecd69
@@ -333,7 +466,7 @@ static int DisplayAll(const char *restri
fecd69
 
fecd69
 	if (!dp) {
fecd69
 		xwarnx(_("unable to open directory \"%s\""), path);
fecd69
-		rc = -1;
fecd69
+		rc = EXIT_FAILURE;
fecd69
 	} else {
fecd69
 		readdir(dp);	/* skip .  */
fecd69
 		readdir(dp);	/* skip .. */
fecd69
@@ -369,130 +502,183 @@ static int DisplayAll(const char *restri
fecd69
 /*
fecd69
  * Write a sysctl setting
fecd69
  */
fecd69
-static int WriteSetting(const char *setting)
fecd69
-{
fecd69
-	int rc = 0;
fecd69
-	const char *name = setting;
fecd69
-	const char *value;
fecd69
-	const char *equals;
fecd69
-	char *tmpname;
fecd69
-	char *outname;
fecd69
-	char *last_dot;
fecd69
-	bool ignore_failure;
fecd69
+static int WriteSetting(
fecd69
+    const char *key,
fecd69
+    const char *path,
fecd69
+    const char *value,
fecd69
+    const bool ignore_failure) {
fecd69
 
fecd69
-	FILE *fp;
fecd69
+    int rc = EXIT_SUCCESS;
fecd69
+    FILE *fp;
fecd69
 	struct stat ts;
fecd69
 
fecd69
-	if (!name)
fecd69
-		/* probably don't want to display this err */
fecd69
-		return 0;
fecd69
-
fecd69
-	equals = strchr(setting, '=');
fecd69
-
fecd69
-	if (!equals) {
fecd69
-		xwarnx(_("\"%s\" must be of the form name=value"),
fecd69
-		       setting);
fecd69
-		return -1;
fecd69
-	}
fecd69
-
fecd69
-	/* point to the value in name=value */
fecd69
-	value = equals + 1;
fecd69
-
fecd69
-	if (!*name || name == equals) {
fecd69
-		xwarnx(_("malformed setting \"%s\""), setting);
fecd69
-		return -2;
fecd69
-	}
fecd69
-
fecd69
-	ignore_failure = name[0] == '-';
fecd69
-	if (ignore_failure)
fecd69
-	    name++;
fecd69
+    if (!key || !path)
fecd69
+        return rc;
fecd69
 
fecd69
-	/* used to open the file */
fecd69
-	tmpname = xmalloc(equals - name + 1 + strlen(PROC_PATH));
fecd69
-	strcpy(tmpname, PROC_PATH);
fecd69
-	strncat(tmpname, name, (int) (equals - name));
fecd69
-	tmpname[equals - name + strlen(PROC_PATH)] = 0;
fecd69
-	/* change . to / */
fecd69
-	slashdot(tmpname + strlen(PROC_PATH), '.', '/');
fecd69
-
fecd69
-	/* used to display the output */
fecd69
-	outname = xmalloc(equals - name + 1);
fecd69
-	strncpy(outname, name, (int) (equals - name));
fecd69
-	outname[equals - name] = 0;
fecd69
-	/* change / to . */
fecd69
-	slashdot(outname, '/', '.');
fecd69
-	last_dot = strrchr(outname, '.');
fecd69
-	if (last_dot != NULL && is_deprecated(last_dot + 1)) {
fecd69
-		xwarnx(_("%s is deprecated, value not set"), outname);
fecd69
-		goto out;
fecd69
-        }
fecd69
-
fecd69
-	if (stat(tmpname, &ts) < 0) {
fecd69
+	if (stat(path, &ts) < 0) {
fecd69
 		if (!IgnoreError) {
fecd69
-			xwarn(_("cannot stat %s"), tmpname);
fecd69
-			rc = -1;
fecd69
+			xwarn(_("cannot stat %s"), path);
fecd69
+			rc = EXIT_FAILURE;
fecd69
 		}
fecd69
-		goto out;
fecd69
+        return rc;
fecd69
 	}
fecd69
 
fecd69
 	if ((ts.st_mode & S_IWUSR) == 0) {
fecd69
-		xwarn(_("setting key \"%s\""), outname);
fecd69
-		goto out;
fecd69
+		xwarn(_("setting key \"%s\""), key);
fecd69
+        return rc;
fecd69
 	}
fecd69
 
fecd69
 	if (S_ISDIR(ts.st_mode)) {
fecd69
-		xwarn(_("setting key \"%s\""), outname);
fecd69
-		goto out;
fecd69
+		xwarn(_("setting key \"%s\""), key);
fecd69
+        return rc;
fecd69
 	}
fecd69
 
fecd69
-	fp = fprocopen(tmpname, "w");
fecd69
-
fecd69
-	if (!fp) {
fecd69
-		switch (errno) {
fecd69
-		case ENOENT:
fecd69
-			if (!IgnoreError) {
fecd69
-				xwarnx(_("\"%s\" is an unknown key%s"), outname, (ignore_failure?_(", ignoring"):""));
fecd69
+    if (!DryRun) {
fecd69
+        if ((fp = fprocopen(path, "w")) == NULL) {
fecd69
+            switch (errno) {
fecd69
+            case ENOENT:
fecd69
+                if (!IgnoreError) {
fecd69
+                    xwarnx(_("\"%s\" is an unknown key%s"),
fecd69
+                           key, (ignore_failure?_(", ignoring"):""));
fecd69
 				if (!ignore_failure)
fecd69
-				    rc = -1;
fecd69
+				    rc = EXIT_FAILURE;
fecd69
 			}
fecd69
 			break;
fecd69
-		case EPERM:
fecd69
-		case EROFS:
fecd69
-		case EACCES:
fecd69
-			xwarnx(_("permission denied on key \"%s\"%s"), outname, (ignore_failure?_(", ignoring"):""));
fecd69
-			break;
fecd69
-		default:
fecd69
-			xwarn(_("setting key \"%s\"%s"), outname, (ignore_failure?_(", ignoring"):""));
fecd69
-			break;
fecd69
-		}
fecd69
-		if (!ignore_failure && errno != ENOENT)
fecd69
-		    rc = -1;
fecd69
-	} else {
fecd69
-		rc = fprintf(fp, "%s\n", value);
fecd69
-		if (0 < rc)
fecd69
-			rc = 0;
fecd69
-		if (close_stream(fp) != 0)
fecd69
-			xwarn(_("setting key \"%s\""), outname);
fecd69
-		else if (rc == 0 && !Quiet) {
fecd69
-			if (NameOnly) {
fecd69
-				fprintf(stdout, "%s\n", outname);
fecd69
-			} else {
fecd69
-				if (PrintName) {
fecd69
-					fprintf(stdout, "%s = %s\n",
fecd69
-						outname, value);
fecd69
-				} else {
fecd69
-					if (PrintNewline)
fecd69
-						fprintf(stdout, "%s\n", value);
fecd69
-					else
fecd69
-						fprintf(stdout, "%s", value);
fecd69
-				}
fecd69
-			}
fecd69
-		}
fecd69
-	}
fecd69
-      out:
fecd69
-	free(tmpname);
fecd69
-	free(outname);
fecd69
-	return rc;
fecd69
+            case EPERM:
fecd69
+            case EROFS:
fecd69
+            case EACCES:
fecd69
+                xwarnx(_("permission denied on key \"%s\"%s"),
fecd69
+                       key, (ignore_failure?_(", ignoring"):""));
fecd69
+                break;
fecd69
+            default:
fecd69
+                xwarn(_("setting key \"%s\"%s"),
fecd69
+                      key, (ignore_failure?_(", ignoring"):""));
fecd69
+                break;
fecd69
+            }
fecd69
+            if (!ignore_failure && errno != ENOENT)
fecd69
+		    rc = EXIT_FAILURE;
fecd69
+        } else {
fecd69
+	    if (0 < fprintf(fp, "%s\n", value))
fecd69
+		rc = EXIT_SUCCESS;
fecd69
+            if (close_stream(fp) != 0) {
fecd69
+                xwarn(_("setting key \"%s\""), path);
fecd69
+                return rc;
fecd69
+            }
fecd69
+        }
fecd69
+    }
fecd69
+    if ((rc == EXIT_SUCCESS && !Quiet) || DryRun) {
fecd69
+        if (NameOnly) {
fecd69
+            printf("%s\n", value);
fecd69
+        } else {
fecd69
+            if (PrintName) {
fecd69
+                printf("%s = %s\n", path, value);
fecd69
+            } else {
fecd69
+                if (PrintNewline)
fecd69
+                    printf("%s\n", value);
fecd69
+                else
fecd69
+                    printf("%s", value);
fecd69
+            }
fecd69
+        }
fecd69
+    }
fecd69
+    return rc;
fecd69
+}
fecd69
+
fecd69
+/*
fecd69
+ * parse each configuration line, there are multiple ways of specifying
fecd69
+ * a key/value here:
fecd69
+ *
fecd69
+ * key = value                               simple setting
fecd69
+ * -key = value                              ignore errors
fecd69
+ * key.pattern.*.with.glob = value           set keys that match glob
fecd69
+ * -key.pattern.exclude.with.glob            dont set this value
fecd69
+ * key.pattern.override.with.glob = value    set this glob match to value
fecd69
+ *
fecd69
+ */
fecd69
+
fecd69
+static SysctlSetting *parse_setting_line(
fecd69
+    const char *path,
fecd69
+    const int linenum,
fecd69
+    char *line)
fecd69
+{
fecd69
+    SysctlSetting *s;
fecd69
+    char *key;
fecd69
+    char *value;
fecd69
+    bool glob_exclude = FALSE;
fecd69
+    bool ignore_failure = FALSE;
fecd69
+
fecd69
+    key = lstrip(line);
fecd69
+    if (strlen(key) < 2)
fecd69
+        return NULL;
fecd69
+
fecd69
+    /* skip over comments */
fecd69
+    if (key[0] == '#' || key[0] == ';')
fecd69
+        return NULL;
fecd69
+
fecd69
+    if (pattern && !pattern_match(key, pattern))
fecd69
+        return NULL;
fecd69
+
fecd69
+    value = strchr(key, '=');
fecd69
+    if (value == NULL) {
fecd69
+        if (key[0] == '-') {
fecd69
+            glob_exclude = TRUE;
fecd69
+            key++;
fecd69
+            value = NULL;
fecd69
+            rstrip(key);
fecd69
+        } else {
fecd69
+            xwarnx(_("%s(%d): invalid syntax, continuing..."),
fecd69
+                   path, linenum);
fecd69
+            return NULL;
fecd69
+        }
fecd69
+    } else {
fecd69
+        value[0]='\0';
fecd69
+        if (key[0] == '-') {
fecd69
+            ignore_failure = TRUE;
fecd69
+            key++;
fecd69
+        }
fecd69
+        value++; // skip over =
fecd69
+        value=lstrip(value);
fecd69
+        rstrip(value);
fecd69
+        rstrip(key);
fecd69
+    }
fecd69
+    return setting_new(key, value, ignore_failure, glob_exclude);
fecd69
+}
fecd69
+
fecd69
+/* Go through the setting list, expand and sort out
fecd69
+ * setting globs and actually write the settings out
fecd69
+ */
fecd69
+static int write_setting_list(const SettingList *sl)
fecd69
+{
fecd69
+    SysctlSetting *node;
fecd69
+    int rc = EXIT_SUCCESS;
fecd69
+
fecd69
+    for (node=sl->head; node != NULL; node=node->next) {
fecd69
+        if (node->glob_exclude)
fecd69
+            continue;
fecd69
+
fecd69
+        if (string_is_glob(node->path)) {
fecd69
+            char *gl_path;
fecd69
+            glob_t globbuf;
fecd69
+            int i;
fecd69
+
fecd69
+            if (glob(node->path, 0, NULL, &globbuf) != 0)
fecd69
+                continue;
fecd69
+
fecd69
+            for(i=0; i < globbuf.gl_pathc; i++) {
fecd69
+                if (settinglist_findpath(sl, globbuf.gl_pathv[i]))
fecd69
+                    continue; // override or exclude
fecd69
+
fecd69
+                rc |= WriteSetting(node->key, globbuf.gl_pathv[i], node->value,
fecd69
+                                   node->ignore_failure);
fecd69
+            }
fecd69
+        } else {
fecd69
+            rc |= WriteSetting(node->key, node->path, node->value,
fecd69
+                               node->ignore_failure);
fecd69
+        }
fecd69
+
fecd69
+
fecd69
+    }
fecd69
+
fecd69
+    return rc;
fecd69
 }
fecd69
 
fecd69
 static int pattern_match(const char *string, const char *pat)
fecd69
@@ -513,12 +699,12 @@ static int pattern_match(const char *str
fecd69
  * Preload the sysctl's from the conf file.  We parse the file and then
fecd69
  * reform it (strip out whitespace).
fecd69
  */
fecd69
-static int Preload(const char *restrict const filename)
fecd69
+static int Preload(SettingList *setlist, const char *restrict const filename)
fecd69
 {
fecd69
 	FILE *fp;
fecd69
 	char *t;
fecd69
 	int n = 0;
fecd69
-	int rc = 0;
fecd69
+	int rc = EXIT_SUCCESS;
fecd69
 	ssize_t rlen;
fecd69
 	char *name, *value;
fecd69
 	glob_t globbuf;
fecd69
@@ -547,62 +733,26 @@ static int Preload(const char *restrict
fecd69
 		    ? stdin : fopen(globbuf.gl_pathv[j], "r");
fecd69
 		if (!fp) {
fecd69
 			xwarn(_("cannot open \"%s\""), globbuf.gl_pathv[j]);
fecd69
-			rc = -1;
fecd69
-			goto out;
fecd69
+            return EXIT_FAILURE;
fecd69
 		}
fecd69
 
fecd69
 		while ((rlen =  getline(&iobuf, &iolen, fp)) > 0) {
fecd69
 			size_t offset;
fecd69
+            SysctlSetting *setting;
fecd69
 
fecd69
 			n++;
fecd69
 
fecd69
 			if (rlen < 2)
fecd69
 				continue;
fecd69
 
fecd69
-			t = StripLeadingAndTrailingSpaces(iobuf);
fecd69
-			if (strlen(t) < 2)
fecd69
-				continue;
fecd69
-
fecd69
-			if (*t == '#' || *t == ';')
fecd69
-				continue;
fecd69
-
fecd69
-			name = strtok(t, "=");
fecd69
-			if (!name || !*name) {
fecd69
-				xwarnx(_("%s(%d): invalid syntax, continuing..."),
fecd69
-				       globbuf.gl_pathv[j], n);
fecd69
-				continue;
fecd69
-			}
fecd69
-
fecd69
-			StripLeadingAndTrailingSpaces(name);
fecd69
-
fecd69
-			if (pattern && !pattern_match(name, pattern))
fecd69
-				continue;
fecd69
-
fecd69
-			offset = strlen(name);
fecd69
-			memmove(&iobuf[0], name, offset);
fecd69
-			iobuf[offset++] = '=';
fecd69
-
fecd69
-			value = strtok(NULL, "\n\r");
fecd69
-			if (!value || !*value) {
fecd69
-				xwarnx(_("%s(%d): invalid syntax, continuing..."),
fecd69
-				       globbuf.gl_pathv[j], n);
fecd69
-				continue;
fecd69
-			}
fecd69
-
fecd69
-			while ((*value == ' ' || *value == '\t') && *value != 0)
fecd69
-				value++;
fecd69
-
fecd69
-			/* should NameOnly affect this? */
fecd69
-			memmove(&iobuf[offset], value, strlen(value));
fecd69
-			offset += strlen(value);
fecd69
-			iobuf[offset] = '\0';
fecd69
-
fecd69
-			rc |= WriteSetting(iobuf);
fecd69
+            if ( (setting = parse_setting_line(globbuf.gl_pathv[j], n, iobuf))
fecd69
+                 == NULL)
fecd69
+                continue;
fecd69
+            settinglist_add(setlist, setting);
fecd69
 		}
fecd69
 
fecd69
 		fclose(fp);
fecd69
 	}
fecd69
-out:
fecd69
 	return rc;
fecd69
 }
fecd69
 
fecd69
@@ -618,7 +768,7 @@ static int sortpairs(const void *A, cons
fecd69
 	return strcmp(a->name, b->name);
fecd69
 }
fecd69
 
fecd69
-static int PreloadSystem(void)
fecd69
+static int PreloadSystem(SettingList *setlist)
fecd69
 {
fecd69
 	unsigned di, i;
fecd69
 	const char *dirs[] = {
fecd69
@@ -630,7 +780,7 @@ static int PreloadSystem(void)
fecd69
 	};
fecd69
 	struct pair **cfgs = NULL;
fecd69
 	unsigned ncfgs = 0;
fecd69
-	int rc = 0;
fecd69
+	int rc = EXIT_SUCCESS;
fecd69
 	struct stat ts;
fecd69
 	enum { nprealloc = 16 };
fecd69
 
fecd69
@@ -688,14 +838,14 @@ static int PreloadSystem(void)
fecd69
 	for (i = 0; i < ncfgs; ++i) {
fecd69
 		if (!Quiet)
fecd69
 			printf(_("* Applying %s ...\n"), cfgs[i]->value);
fecd69
-		rc |= Preload(cfgs[i]->value);
fecd69
+		rc |= Preload(setlist, cfgs[i]->value);
fecd69
 	}
fecd69
 
fecd69
 
fecd69
 	if (stat(DEFAULT_PRELOAD, &ts) == 0 && S_ISREG(ts.st_mode)) {
fecd69
 		if (!Quiet)
fecd69
 			printf(_("* Applying %s ...\n"), DEFAULT_PRELOAD);
fecd69
-		rc |= Preload(DEFAULT_PRELOAD);
fecd69
+		rc |= Preload(setlist, DEFAULT_PRELOAD);
fecd69
 	}
fecd69
 
fecd69
 	/* cleaning */
fecd69
@@ -717,15 +867,19 @@ int main(int argc, char *argv[])
fecd69
 	bool preloadfileOpt = false;
fecd69
 	int ReturnCode = 0;
fecd69
 	int c;
fecd69
+    int rc;
fecd69
 	const char *preloadfile = NULL;
fecd69
+    SettingList *setlist;
fecd69
 
fecd69
 	enum {
fecd69
 		DEPRECATED_OPTION = CHAR_MAX + 1,
fecd69
-		SYSTEM_OPTION
fecd69
+		SYSTEM_OPTION,
fecd69
+        DRYRUN_OPTION
fecd69
 	};
fecd69
 	static const struct option longopts[] = {
fecd69
 		{"all", no_argument, NULL, 'a'},
fecd69
 		{"deprecated", no_argument, NULL, DEPRECATED_OPTION},
fecd69
+		{"dry-run", no_argument, NULL, DRYRUN_OPTION},
fecd69
 		{"binary", no_argument, NULL, 'b'},
fecd69
 		{"ignore", no_argument, NULL, 'e'},
fecd69
 		{"names", no_argument, NULL, 'N'},
fecd69
@@ -753,6 +907,10 @@ int main(int argc, char *argv[])
fecd69
 	IgnoreError = false;
fecd69
 	Quiet = false;
fecd69
 	IgnoreDeprecated = true;
fecd69
+    DryRun = false;
fecd69
+    setlist = xmalloc(sizeof(SettingList));
fecd69
+    setlist->head = NULL;
fecd69
+    setlist->tail = NULL;
fecd69
 
fecd69
 	if (argc < 2)
fecd69
 		Usage(stderr);
fecd69
@@ -805,7 +963,12 @@ int main(int argc, char *argv[])
fecd69
 			break;
fecd69
 		case SYSTEM_OPTION:
fecd69
 			IgnoreError = true;
fecd69
-			return PreloadSystem();
fecd69
+			rc |= PreloadSystem(setlist);
fecd69
+            rc |= write_setting_list(setlist);
fecd69
+            return rc;
fecd69
+        case DRYRUN_OPTION:
fecd69
+            DryRun = true;
fecd69
+            break;
fecd69
 		case 'r':
fecd69
 			pattern = xstrdup(optarg);
fecd69
 			break;
fecd69
@@ -833,15 +996,16 @@ int main(int argc, char *argv[])
fecd69
 		int ret = EXIT_SUCCESS, i;
fecd69
 		if (!preloadfile) {
fecd69
 			if (!argc) {
fecd69
-				ret |= Preload(DEFAULT_PRELOAD);
fecd69
+				ret |= Preload(setlist, DEFAULT_PRELOAD);
fecd69
 			}
fecd69
 		} else {
fecd69
 			/* This happens when -pfile option is
fecd69
 			 * used without space. */
fecd69
-			ret |= Preload(preloadfile);
fecd69
+			ret |= Preload(setlist, preloadfile);
fecd69
 		}
fecd69
 		for (i = 0; i < argc; i++)
fecd69
-			ret |= Preload(argv[i]);
fecd69
+			ret |= Preload(setlist, argv[i]);
fecd69
+        ret |= write_setting_list(setlist);
fecd69
 		return ret;
fecd69
 	}
fecd69
 
fecd69
@@ -855,9 +1019,14 @@ int main(int argc, char *argv[])
fecd69
 		      program_invocation_short_name);
fecd69
 
fecd69
 	for ( ; *argv; argv++) {
fecd69
-		if (WriteMode || strchr(*argv, '='))
fecd69
-			ReturnCode += WriteSetting(*argv);
fecd69
-		else
fecd69
+		if (WriteMode || strchr(*argv, '=')) {
fecd69
+            SysctlSetting *s;
fecd69
+            if ( (s = parse_setting_line("command line", 0, *argv)) != NULL)
fecd69
+                ReturnCode |= WriteSetting(s->key, s->path, s->value,
fecd69
+                                           s->ignore_failure);
fecd69
+            else
fecd69
+                ReturnCode |= EXIT_FAILURE;
fecd69
+        } else
fecd69
 			ReturnCode += ReadSetting(*argv);
fecd69
 	}
fecd69
 	return ReturnCode;
fecd69
diff -up ./testsuite/config/unix.exp.ori ./testsuite/config/unix.exp
fecd69
--- ./testsuite/config/unix.exp.ori	2021-02-09 11:11:25.000000000 +0100
fecd69
+++ ./testsuite/config/unix.exp	2022-07-29 15:50:50.768380067 +0200
fecd69
@@ -136,6 +136,15 @@ proc expect_table_dsc { test match_heade
fecd69
     #}
fecd69
 }
fecd69
 
fecd69
+proc expect_spawn_retval { test retval } {
fecd69
+    foreach {pid spawnid os_error_flag value} [wait] break
fecd69
+
fecd69
+    if {$value == $retval} {
fecd69
+      return
fecd69
+    }
fecd69
+    fail "$test (exit value)"
fecd69
+}
fecd69
+
fecd69
 proc make_pipeproc { } {
fecd69
     global pipeproc_pid pipeproc_spawnid topdir
fecd69
 
fecd69
diff -up ./testsuite/sysctl_glob_test.conf.ori ./testsuite/sysctl_glob_test.conf
fecd69
--- ./testsuite/sysctl_glob_test.conf.ori	2022-07-29 15:50:50.768380067 +0200
fecd69
+++ ./testsuite/sysctl_glob_test.conf	2022-07-29 15:50:50.768380067 +0200
fecd69
@@ -0,0 +1,6 @@
fecd69
+#
fecd69
+# Test configuration for for glob in sysctl
fecd69
+#
fecd69
+fs.protected_* = 2
fecd69
+fs.protected_hardlinks = 1
fecd69
+-fs.protected_regular
fecd69
diff -up ./testsuite/sysctl.test/sysctl_write.exp.ori ./testsuite/sysctl.test/sysctl_write.exp
fecd69
--- ./testsuite/sysctl.test/sysctl_write.exp.ori	2022-07-29 15:50:50.768380067 +0200
fecd69
+++ ./testsuite/sysctl.test/sysctl_write.exp	2022-07-29 15:50:50.768380067 +0200
fecd69
@@ -0,0 +1,29 @@
fecd69
+
fecd69
+set sysctl ${topdir}sysctl
fecd69
+
fecd69
+set test "sysctl write from command line"
fecd69
+spawn $sysctl --dry-run kernel.hostname=procps-test
fecd69
+expect_pass "$test" "/proc/sys/kernel/hostname = procps-test"
fecd69
+
fecd69
+set test "sysctl write from configuration file"
fecd69
+spawn $sysctl --dry-run -f ${topdir}testsuite/sysctl_glob_test.conf
fecd69
+expect_pass "$test" "/proc/sys/fs/protected_fifos = 2\\s+/proc/sys/fs/protected_symlinks = 2\\s+/proc/sys/fs/protected_hardlinks = 1"
fecd69
+
fecd69
+set hostname_file "/proc/sys/kernel/hostname"
fecd69
+if {[file exists ${hostname_file}]} {
fecd69
+  if {[file writable ${hostname_file}]} {
fecd69
+    unsupported "sysctl write: hostname file is writable"
fecd69
+  } else {
fecd69
+    set test "sysctl write unwritable file"
fecd69
+    spawn $sysctl -q kernel.hostname=procpstest
fecd69
+    expect_pass "$test" "sysctl: permission denied on key \"kernel.hostname\"\\s*$"
fecd69
+    expect_spawn_retval "$test" 1
fecd69
+
fecd69
+    set test "sysctl write unwritable file ignored"
fecd69
+    spawn $sysctl -q -- -kernel.hostname=procpstest
fecd69
+    expect_pass "$test" "sysctl: permission denied on key \"kernel.hostname\", ignoring\\s*$"
fecd69
+    expect_spawn_retval "$test" 0
fecd69
+  }
fecd69
+} else {
fecd69
+  unsupported "sysctl write: hostname file doe not exist"
fecd69
+}