procps-ng/sysctl-support-systemd-globs.patch

834 lines
22 KiB
Diff
Raw Normal View History

diff -up ./NEWS.ori ./NEWS
diff -up ./sysctl.c.ori ./sysctl.c
--- ./sysctl.c.ori 2021-02-09 11:11:25.000000000 +0100
+++ ./sysctl.c 2022-07-29 15:50:50.767380061 +0200
@@ -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"
@@ -66,12 +68,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);
@@ -100,6 +124,81 @@ static void slashdot(char *restrict p, c
}
}
+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
*/
@@ -115,6 +214,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);
@@ -138,6 +238,39 @@ static void __attribute__ ((__noreturn__
}
/*
+ * 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
*/
static char *StripLeadingAndTrailingSpaces(char *oneline)
@@ -166,7 +299,7 @@ static char *StripLeadingAndTrailingSpac
*/
static int ReadSetting(const char *restrict const name)
{
- int rc = 0;
+ int rc = EXIT_SUCCESS;
char *restrict tmpname;
char *restrict outname;
ssize_t rlen;
@@ -198,7 +331,7 @@ static int ReadSetting(const char *restr
if (stat(tmpname, &ts) < 0) {
if (!IgnoreError) {
xwarn(_("cannot stat %s"), tmpname);
- rc = -1;
+ rc = EXIT_FAILURE;
}
goto out;
}
@@ -215,7 +348,7 @@ static int ReadSetting(const char *restr
}
if (pattern && !pattern_match(outname, pattern)) {
- rc = 0;
+ rc = EXIT_SUCCESS;
goto out;
}
@@ -231,19 +364,19 @@ static int ReadSetting(const char *restr
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 {
@@ -279,7 +412,7 @@ static int ReadSetting(const char *restr
case EACCES:
xwarnx(_("permission denied on key '%s'"),
outname);
- rc = -1;
+ rc = EXIT_FAILURE;
break;
case EISDIR: {
size_t len;
@@ -291,11 +424,11 @@ static int ReadSetting(const char *restr
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;
}
@@ -323,7 +456,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;
@@ -333,7 +466,7 @@ static int DisplayAll(const char *restri
if (!dp) {
xwarnx(_("unable to open directory \"%s\""), path);
- rc = -1;
+ rc = EXIT_FAILURE;
} else {
readdir(dp); /* skip . */
readdir(dp); /* skip .. */
@@ -369,130 +502,183 @@ static int DisplayAll(const char *restri
/*
* 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;
+static int WriteSetting(
+ const char *key,
+ const char *path,
+ const char *value,
+ const bool ignore_failure) {
- FILE *fp;
+ 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++;
+ if (!key || !path)
+ return rc;
- /* 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 (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 ((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)
@@ -513,12 +699,12 @@ static int pattern_match(const char *str
* 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;
@@ -547,62 +733,26 @@ static int Preload(const char *restrict
? 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;
}
@@ -618,7 +768,7 @@ static int sortpairs(const void *A, cons
return strcmp(a->name, b->name);
}
-static int PreloadSystem(void)
+static int PreloadSystem(SettingList *setlist)
{
unsigned di, i;
const char *dirs[] = {
@@ -630,7 +780,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 };
@@ -688,14 +838,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 */
@@ -717,15 +867,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'},
@@ -753,6 +907,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);
@@ -805,7 +963,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;
@@ -833,15 +996,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;
}
@@ -855,9 +1019,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;
diff -up ./testsuite/config/unix.exp.ori ./testsuite/config/unix.exp
--- ./testsuite/config/unix.exp.ori 2021-02-09 11:11:25.000000000 +0100
+++ ./testsuite/config/unix.exp 2022-07-29 15:50:50.768380067 +0200
@@ -136,6 +136,15 @@ proc expect_table_dsc { test match_heade
#}
}
+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 -up ./testsuite/sysctl_glob_test.conf.ori ./testsuite/sysctl_glob_test.conf
--- ./testsuite/sysctl_glob_test.conf.ori 2022-07-29 15:50:50.768380067 +0200
+++ ./testsuite/sysctl_glob_test.conf 2022-07-29 15:50:50.768380067 +0200
@@ -0,0 +1,6 @@
+#
+# Test configuration for for glob in sysctl
+#
+fs.protected_* = 2
+fs.protected_hardlinks = 1
+-fs.protected_regular
diff -up ./testsuite/sysctl.test/sysctl_write.exp.ori ./testsuite/sysctl.test/sysctl_write.exp
--- ./testsuite/sysctl.test/sysctl_write.exp.ori 2022-07-29 15:50:50.768380067 +0200
+++ ./testsuite/sysctl.test/sysctl_write.exp 2022-07-29 15:50:50.768380067 +0200
@@ -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"
+}