sysctl: Support systemd glob patterns

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>
This commit is contained in:
Craig Small 2021-09-13 22:07:37 +10:00
parent 8c23dfdcd4
commit 6389deca5b
5 changed files with 389 additions and 175 deletions

1
NEWS
View File

@ -11,6 +11,7 @@ procps-ng-NEXT
* ps: Add PSS and USS fields issue #112 * ps: Add PSS and USS fields issue #112
* ps: Add two new autogroup fields * ps: Add two new autogroup fields
* slabtop: Don't combine d and o options issue #160 * 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 * sysctl: Check resolved path to be under /proc/sys issue #179
* top: added LOGID similar to 3.3.13 ps LUID * top: added LOGID similar to 3.3.13 ps LUID
* top: added EXE identical to 3.3.17 ps EXE * top: added EXE identical to 3.3.17 ps EXE

467
sysctl.c
View File

@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* *
* Part of this code comes from systemd, especially sysctl.c
* Changelog: * Changelog:
* v1.01: * v1.01:
* - added -p <preload> to preload values from a file * - added -p <preload> to preload values from a file
@ -40,6 +41,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <ctype.h>
#include "c.h" #include "c.h"
#include "fileutils.h" #include "fileutils.h"
@ -63,12 +65,34 @@ static bool PrintName;
static bool PrintNewline; static bool PrintNewline;
static bool IgnoreError; static bool IgnoreError;
static bool Quiet; static bool Quiet;
static bool DryRun;
static char *pattern; static char *pattern;
#define LINELEN 4096 #define LINELEN 4096
static char *iobuf; static char *iobuf;
static size_t iolen = LINELEN; 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. */ /* Function prototypes. */
static int pattern_match(const char *string, const char *pat); static int pattern_match(const char *string, const char *pat);
static int DisplayAll(const char *restrict const path); 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 * Display the usage format
*/ */
@ -130,6 +229,7 @@ static void __attribute__ ((__noreturn__))
fputs(_(" -A alias of -a\n"), out); fputs(_(" -A alias of -a\n"), out);
fputs(_(" -X alias of -a\n"), out); fputs(_(" -X alias of -a\n"), out);
fputs(_(" --deprecated include deprecated parameters to listing\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(_(" -b, --binary print value without new line\n"), out);
fputs(_(" -e, --ignore ignore unknown variables errors\n"), out); fputs(_(" -e, --ignore ignore unknown variables errors\n"), out);
fputs(_(" -N, --names print variable names without values\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); 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 * 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) static int ReadSetting(const char *restrict const name)
{ {
int rc = 0; int rc = EXIT_SUCCESS;
char *restrict tmpname; char *restrict tmpname;
char *restrict outname; char *restrict outname;
ssize_t rlen; ssize_t rlen;
@ -213,7 +346,7 @@ static int ReadSetting(const char *restrict const name)
if (stat(tmpname, &ts) < 0) { if (stat(tmpname, &ts) < 0) {
if (!IgnoreError) { if (!IgnoreError) {
xwarn(_("cannot stat %s"), tmpname); xwarn(_("cannot stat %s"), tmpname);
rc = -1; rc = EXIT_FAILURE;
} }
goto out; goto out;
} }
@ -235,7 +368,7 @@ static int ReadSetting(const char *restrict const name)
} }
if (pattern && !pattern_match(outname, pattern)) { if (pattern && !pattern_match(outname, pattern)) {
rc = 0; rc = EXIT_SUCCESS;
goto out; goto out;
} }
@ -251,19 +384,19 @@ static int ReadSetting(const char *restrict const name)
case ENOENT: case ENOENT:
if (!IgnoreError) { if (!IgnoreError) {
xwarnx(_("\"%s\" is an unknown key"), outname); xwarnx(_("\"%s\" is an unknown key"), outname);
rc = -1; rc = EXIT_FAILURE;
} }
break; break;
case EACCES: case EACCES:
xwarnx(_("permission denied on key '%s'"), outname); xwarnx(_("permission denied on key '%s'"), outname);
rc = -1; rc = EXIT_FAILURE;
break; break;
case EIO: /* Ignore stable_secret below /proc/sys/net/ipv6/conf */ case EIO: /* Ignore stable_secret below /proc/sys/net/ipv6/conf */
rc = -1; rc = EXIT_FAILURE;
break; break;
default: default:
xwarn(_("reading key \"%s\""), outname); xwarn(_("reading key \"%s\""), outname);
rc = -1; rc = EXIT_FAILURE;
break; break;
} }
} else { } else {
@ -299,7 +432,7 @@ static int ReadSetting(const char *restrict const name)
case EACCES: case EACCES:
xwarnx(_("permission denied on key '%s'"), xwarnx(_("permission denied on key '%s'"),
outname); outname);
rc = -1; rc = EXIT_FAILURE;
break; break;
case EISDIR: { case EISDIR: {
size_t len; size_t len;
@ -311,11 +444,11 @@ static int ReadSetting(const char *restrict const name)
goto out; goto out;
} }
case EIO: /* Ignore stable_secret below /proc/sys/net/ipv6/conf */ case EIO: /* Ignore stable_secret below /proc/sys/net/ipv6/conf */
rc = -1; rc = EXIT_FAILURE;
break; break;
default: default:
xwarnx(_("reading key \"%s\""), outname); xwarnx(_("reading key \"%s\""), outname);
rc = -1; rc = EXIT_FAILURE;
case 0: case 0:
break; break;
} }
@ -343,7 +476,7 @@ static int is_deprecated(char *filename)
*/ */
static int DisplayAll(const char *restrict const path) static int DisplayAll(const char *restrict const path)
{ {
int rc = 0; int rc = EXIT_SUCCESS;
int rc2; int rc2;
DIR *restrict dp; DIR *restrict dp;
struct dirent *restrict de; struct dirent *restrict de;
@ -353,7 +486,7 @@ static int DisplayAll(const char *restrict const path)
if (!dp) { if (!dp) {
xwarnx(_("unable to open directory \"%s\""), path); xwarnx(_("unable to open directory \"%s\""), path);
rc = -1; rc = EXIT_FAILURE;
} else { } else {
readdir(dp); /* skip . */ readdir(dp); /* skip . */
readdir(dp); /* skip .. */ readdir(dp); /* skip .. */
@ -389,70 +522,25 @@ static int DisplayAll(const char *restrict const path)
/* /*
* Write a sysctl setting * Write a sysctl setting
*/ */
static int WriteSetting(const char *setting) static int WriteSetting(
{ const char *key,
int rc = 0; const char *path,
const char *name = setting; const char *value,
const char *value; const bool ignore_failure) {
const char *equals;
char *tmpname;
char *outname;
char *last_dot;
bool ignore_failure;
int rc = EXIT_SUCCESS;
FILE *fp; FILE *fp;
struct stat ts; struct stat ts;
if (!name) if (!key || !path)
/* probably don't want to display this err */ return rc;
return 0;
equals = strchr(setting, '='); if (stat(path, &ts) < 0) {
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 (stat(tmpname, &ts) < 0) {
if (!IgnoreError) { if (!IgnoreError) {
xwarn(_("cannot stat %s"), tmpname); xwarn(_("cannot stat %s"), path);
rc = -1; rc = EXIT_FAILURE;
} }
goto out; return rc;
} }
if (!is_proc_path(tmpname)) { if (!is_proc_path(tmpname)) {
@ -461,62 +549,160 @@ static int WriteSetting(const char *setting)
} }
if ((ts.st_mode & S_IWUSR) == 0) { if ((ts.st_mode & S_IWUSR) == 0) {
xwarn(_("setting key \"%s\""), outname); xwarn(_("setting key \"%s\""), key);
goto out; return rc;
} }
if (S_ISDIR(ts.st_mode)) { if (S_ISDIR(ts.st_mode)) {
xwarn(_("setting key \"%s\""), outname); xwarn(_("setting key \"%s\""), key);
goto out; return rc;
} }
fp = fprocopen(tmpname, "w"); if (!DryRun) {
if ((fp = fprocopen(path, "w")) == NULL) {
if (!fp) {
switch (errno) { switch (errno) {
case ENOENT: case ENOENT:
if (!IgnoreError) { if (!IgnoreError) {
xwarnx(_("\"%s\" is an unknown key%s"), outname, (ignore_failure?_(", ignoring"):"")); xwarnx(_("\"%s\" is an unknown key%s"),
key, (ignore_failure?_(", ignoring"):""));
if (!ignore_failure) if (!ignore_failure)
rc = -1; rc = EXIT_FAILURE;
} }
break; break;
case EPERM: case EPERM:
case EROFS: case EROFS:
case EACCES: case EACCES:
xwarnx(_("permission denied on key \"%s\"%s"), outname, (ignore_failure?_(", ignoring"):"")); xwarnx(_("permission denied on key \"%s\"%s"),
key, (ignore_failure?_(", ignoring"):""));
break; break;
default: default:
xwarn(_("setting key \"%s\"%s"), outname, (ignore_failure?_(", ignoring"):"")); xwarn(_("setting key \"%s\"%s"),
key, (ignore_failure?_(", ignoring"):""));
break; break;
} }
if (!ignore_failure && errno != ENOENT) if (!ignore_failure && errno != ENOENT)
rc = -1; rc = EXIT_FAILURE;
} else { } else {
rc = fprintf(fp, "%s\n", value); if (0 < fprintf(fp, "%s\n", value))
if (0 < rc) rc = EXIT_SUCCESS;
rc = 0; if (close_stream(fp) != 0) {
if (close_stream(fp) != 0) xwarn(_("setting key \"%s\""), path);
xwarn(_("setting key \"%s\""), outname); return rc;
else if (rc == 0 && !Quiet) { }
}
}
if ((rc == EXIT_SUCCESS && !Quiet) || DryRun) {
if (NameOnly) { if (NameOnly) {
fprintf(stdout, "%s\n", outname); printf("%s\n", value);
} else { } else {
if (PrintName) { if (PrintName) {
fprintf(stdout, "%s = %s\n", printf("%s = %s\n", path, value);
outname, value);
} else { } else {
if (PrintNewline) if (PrintNewline)
fprintf(stdout, "%s\n", value); printf("%s\n", value);
else else
fprintf(stdout, "%s", value); printf("%s", value);
} }
} }
} }
return rc;
} }
out:
free(tmpname); /*
free(outname); * 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; return rc;
} }
@ -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 * Preload the sysctl's from the conf file. We parse the file and then
* reform it (strip out whitespace). * 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; FILE *fp;
char *t; char *t;
int n = 0; int n = 0;
int rc = 0; int rc = EXIT_SUCCESS;
ssize_t rlen; ssize_t rlen;
char *name, *value; char *name, *value;
glob_t globbuf; glob_t globbuf;
@ -572,62 +758,26 @@ static int Preload(const char *restrict const filename)
? stdin : fopen(globbuf.gl_pathv[j], "r"); ? stdin : fopen(globbuf.gl_pathv[j], "r");
if (!fp) { if (!fp) {
xwarn(_("cannot open \"%s\""), globbuf.gl_pathv[j]); xwarn(_("cannot open \"%s\""), globbuf.gl_pathv[j]);
rc = -1; return EXIT_FAILURE;
goto out;
} }
while ((rlen = getline(&iobuf, &iolen, fp)) > 0) { while ((rlen = getline(&iobuf, &iolen, fp)) > 0) {
size_t offset; size_t offset;
SysctlSetting *setting;
n++; n++;
if (rlen < 2) if (rlen < 2)
continue; continue;
t = StripLeadingAndTrailingSpaces(iobuf); if ( (setting = parse_setting_line(globbuf.gl_pathv[j], n, iobuf))
if (strlen(t) < 2) == NULL)
continue; continue;
settinglist_add(setlist, setting);
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);
} }
fclose(fp); fclose(fp);
} }
out:
return rc; return rc;
} }
@ -643,7 +793,7 @@ static int sortpairs(const void *A, const void *B)
return strcmp(a->name, b->name); return strcmp(a->name, b->name);
} }
static int PreloadSystem(void) static int PreloadSystem(SettingList *setlist)
{ {
unsigned di, i; unsigned di, i;
const char *dirs[] = { const char *dirs[] = {
@ -655,7 +805,7 @@ static int PreloadSystem(void)
}; };
struct pair **cfgs = NULL; struct pair **cfgs = NULL;
unsigned ncfgs = 0; unsigned ncfgs = 0;
int rc = 0; int rc = EXIT_SUCCESS;
struct stat ts; struct stat ts;
enum { nprealloc = 16 }; enum { nprealloc = 16 };
@ -713,14 +863,14 @@ static int PreloadSystem(void)
for (i = 0; i < ncfgs; ++i) { for (i = 0; i < ncfgs; ++i) {
if (!Quiet) if (!Quiet)
printf(_("* Applying %s ...\n"), cfgs[i]->value); 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 (stat(DEFAULT_PRELOAD, &ts) == 0 && S_ISREG(ts.st_mode)) {
if (!Quiet) if (!Quiet)
printf(_("* Applying %s ...\n"), DEFAULT_PRELOAD); printf(_("* Applying %s ...\n"), DEFAULT_PRELOAD);
rc |= Preload(DEFAULT_PRELOAD); rc |= Preload(setlist, DEFAULT_PRELOAD);
} }
/* cleaning */ /* cleaning */
@ -742,15 +892,19 @@ int main(int argc, char *argv[])
bool preloadfileOpt = false; bool preloadfileOpt = false;
int ReturnCode = 0; int ReturnCode = 0;
int c; int c;
int rc;
const char *preloadfile = NULL; const char *preloadfile = NULL;
SettingList *setlist;
enum { enum {
DEPRECATED_OPTION = CHAR_MAX + 1, DEPRECATED_OPTION = CHAR_MAX + 1,
SYSTEM_OPTION SYSTEM_OPTION,
DRYRUN_OPTION
}; };
static const struct option longopts[] = { static const struct option longopts[] = {
{"all", no_argument, NULL, 'a'}, {"all", no_argument, NULL, 'a'},
{"deprecated", no_argument, NULL, DEPRECATED_OPTION}, {"deprecated", no_argument, NULL, DEPRECATED_OPTION},
{"dry-run", no_argument, NULL, DRYRUN_OPTION},
{"binary", no_argument, NULL, 'b'}, {"binary", no_argument, NULL, 'b'},
{"ignore", no_argument, NULL, 'e'}, {"ignore", no_argument, NULL, 'e'},
{"names", no_argument, NULL, 'N'}, {"names", no_argument, NULL, 'N'},
@ -778,6 +932,10 @@ int main(int argc, char *argv[])
IgnoreError = false; IgnoreError = false;
Quiet = false; Quiet = false;
IgnoreDeprecated = true; IgnoreDeprecated = true;
DryRun = false;
setlist = xmalloc(sizeof(SettingList));
setlist->head = NULL;
setlist->tail = NULL;
if (argc < 2) if (argc < 2)
Usage(stderr); Usage(stderr);
@ -830,7 +988,12 @@ int main(int argc, char *argv[])
break; break;
case SYSTEM_OPTION: case SYSTEM_OPTION:
IgnoreError = true; IgnoreError = true;
return PreloadSystem(); rc |= PreloadSystem(setlist);
rc |= write_setting_list(setlist);
return rc;
case DRYRUN_OPTION:
DryRun = true;
break;
case 'r': case 'r':
pattern = xstrdup(optarg); pattern = xstrdup(optarg);
break; break;
@ -858,15 +1021,16 @@ int main(int argc, char *argv[])
int ret = EXIT_SUCCESS, i; int ret = EXIT_SUCCESS, i;
if (!preloadfile) { if (!preloadfile) {
if (!argc) { if (!argc) {
ret |= Preload(DEFAULT_PRELOAD); ret |= Preload(setlist, DEFAULT_PRELOAD);
} }
} else { } else {
/* This happens when -pfile option is /* This happens when -pfile option is
* used without space. */ * used without space. */
ret |= Preload(preloadfile); ret |= Preload(setlist, preloadfile);
} }
for (i = 0; i < argc; i++) for (i = 0; i < argc; i++)
ret |= Preload(argv[i]); ret |= Preload(setlist, argv[i]);
ret |= write_setting_list(setlist);
return ret; return ret;
} }
@ -880,9 +1044,14 @@ int main(int argc, char *argv[])
program_invocation_short_name); program_invocation_short_name);
for ( ; *argv; argv++) { for ( ; *argv; argv++) {
if (WriteMode || strchr(*argv, '=')) if (WriteMode || strchr(*argv, '=')) {
ReturnCode += WriteSetting(*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 else
ReturnCode |= EXIT_FAILURE;
} else
ReturnCode += ReadSetting(*argv); ReturnCode += ReadSetting(*argv);
} }
return ReturnCode; return ReturnCode;

View 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 { } { proc make_pipeproc { } {
global pipeproc_pid pipeproc_spawnid topdir global pipeproc_pid pipeproc_spawnid topdir

View File

@ -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"
}

View File

@ -0,0 +1,6 @@
#
# Test configuration for for glob in sysctl
#
fs.protected_* = 2
fs.protected_hardlinks = 1
-fs.protected_regular