474847ed35
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>
1034 lines
24 KiB
C
1034 lines
24 KiB
C
/*
|
|
* Sysctl 1.01 - A utility to read and manipulate the sysctl parameters
|
|
*
|
|
* "Copyright 1999 George Staikos
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* 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
|
|
* Horms:
|
|
* - added -q to be quiet when modifying values
|
|
*
|
|
* Changes by Albert Cahalan, 2002.
|
|
*/
|
|
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <getopt.h>
|
|
#include <glob.h>
|
|
#include <libgen.h>
|
|
#include <limits.h>
|
|
#include <regex.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
|
|
#include "c.h"
|
|
#include "fileutils.h"
|
|
#include "nls.h"
|
|
#include "xalloc.h"
|
|
#include "proc/procps.h"
|
|
#include "proc/version.h"
|
|
|
|
extern FILE *fprocopen(const char *, const char *);
|
|
|
|
/*
|
|
* Globals...
|
|
*/
|
|
static const char PROC_PATH[] = "/proc/sys/";
|
|
static const char DEFAULT_PRELOAD[] = "/etc/sysctl.conf";
|
|
static const char *DEPRECATED[] = {
|
|
"base_reachable_time",
|
|
"retrans_time",
|
|
""
|
|
};
|
|
static bool IgnoreDeprecated;
|
|
static bool NameOnly;
|
|
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);
|
|
|
|
static void slashdot(char *restrict p, char old, char new)
|
|
{
|
|
int warned = 1;
|
|
p = strpbrk(p, "/.");
|
|
if (!p)
|
|
/* nothing -- can't be, but oh well */
|
|
return;
|
|
if (*p == new)
|
|
/* already in desired format */
|
|
return;
|
|
while (p) {
|
|
char c = *p;
|
|
if ((*(p + 1) == '/' || *(p + 1) == '.') && warned) {
|
|
xwarnx(_("separators should not be repeated: %s"), p);
|
|
warned = 0;
|
|
}
|
|
if (c == old)
|
|
*p = new;
|
|
if (c == new)
|
|
*p = old;
|
|
p = strpbrk(p + 1, "/.");
|
|
}
|
|
}
|
|
|
|
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
|
|
*/
|
|
static void __attribute__ ((__noreturn__))
|
|
Usage(FILE * out)
|
|
{
|
|
fputs(USAGE_HEADER, out);
|
|
fprintf(out,
|
|
_(" %s [options] [variable[=value] ...]\n"),
|
|
program_invocation_short_name);
|
|
fputs(USAGE_OPTIONS, out);
|
|
fputs(_(" -a, --all display all variables\n"), out);
|
|
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);
|
|
fputs(_(" -n, --values print only values of the given variable(s)\n"), out);
|
|
fputs(_(" -p, --load[=<file>] read values from file\n"), out);
|
|
fputs(_(" -f alias of -p\n"), out);
|
|
fputs(_(" --system read values from all system directories\n"), out);
|
|
fputs(_(" -r, --pattern <expression>\n"
|
|
" select setting that match expression\n"), out);
|
|
fputs(_(" -q, --quiet do not echo variable set\n"), out);
|
|
fputs(_(" -w, --write enable writing a value to variable\n"), out);
|
|
fputs(_(" -o does nothing\n"), out);
|
|
fputs(_(" -x does nothing\n"), out);
|
|
fputs(_(" -d alias of -h\n"), out);
|
|
fputs(USAGE_SEPARATOR, out);
|
|
fputs(USAGE_HELP, out);
|
|
fputs(USAGE_VERSION, out);
|
|
fprintf(out, USAGE_MAN_TAIL("sysctl(8)"));
|
|
|
|
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
|
|
*/
|
|
static char *StripLeadingAndTrailingSpaces(char *oneline)
|
|
{
|
|
char *t;
|
|
|
|
if (!oneline || !*oneline)
|
|
return oneline;
|
|
|
|
t = oneline;
|
|
t += strlen(oneline) - 1;
|
|
|
|
while ((*t == ' ' || *t == '\t' || *t == '\n' || *t == '\r') && t != oneline)
|
|
*t-- = 0;
|
|
|
|
t = oneline;
|
|
|
|
while ((*t == ' ' || *t == '\t') && *t != 0)
|
|
t++;
|
|
|
|
return t;
|
|
}
|
|
|
|
/*
|
|
* Read a sysctl setting
|
|
*/
|
|
static int ReadSetting(const char *restrict const name)
|
|
{
|
|
int rc = EXIT_SUCCESS;
|
|
char *restrict tmpname;
|
|
char *restrict outname;
|
|
ssize_t rlen;
|
|
FILE *restrict fp;
|
|
struct stat ts;
|
|
|
|
if (!name || !*name) {
|
|
xwarnx(_("\"%s\" is an unknown key"), name);
|
|
return -1;
|
|
}
|
|
|
|
/* used to display the output */
|
|
outname = xstrdup(name);
|
|
/* change / to . */
|
|
slashdot(outname, '/', '.');
|
|
|
|
/* used to open the file */
|
|
tmpname = xmalloc(strlen(name) + strlen(PROC_PATH) + 2);
|
|
strcpy(tmpname, PROC_PATH);
|
|
strcat(tmpname, name);
|
|
/* change . to / */
|
|
slashdot(tmpname + strlen(PROC_PATH), '.', '/');
|
|
|
|
/* used to display the output */
|
|
outname = xstrdup(name);
|
|
/* change / to . */
|
|
slashdot(outname, '/', '.');
|
|
|
|
if (stat(tmpname, &ts) < 0) {
|
|
if (!IgnoreError) {
|
|
xwarn(_("cannot stat %s"), tmpname);
|
|
rc = EXIT_FAILURE;
|
|
}
|
|
goto out;
|
|
}
|
|
if ((ts.st_mode & S_IRUSR) == 0)
|
|
goto out;
|
|
|
|
if (S_ISDIR(ts.st_mode)) {
|
|
size_t len;
|
|
len = strlen(tmpname);
|
|
tmpname[len] = '/';
|
|
tmpname[len + 1] = '\0';
|
|
rc = DisplayAll(tmpname);
|
|
goto out;
|
|
}
|
|
|
|
if (pattern && !pattern_match(outname, pattern)) {
|
|
rc = EXIT_SUCCESS;
|
|
goto out;
|
|
}
|
|
|
|
if (NameOnly) {
|
|
fprintf(stdout, "%s\n", outname);
|
|
goto out;
|
|
}
|
|
|
|
fp = fprocopen(tmpname, "r");
|
|
|
|
if (!fp) {
|
|
switch (errno) {
|
|
case ENOENT:
|
|
if (!IgnoreError) {
|
|
xwarnx(_("\"%s\" is an unknown key"), outname);
|
|
rc = EXIT_FAILURE;
|
|
}
|
|
break;
|
|
case EACCES:
|
|
xwarnx(_("permission denied on key '%s'"), outname);
|
|
rc = EXIT_FAILURE;
|
|
break;
|
|
case EIO: /* Ignore stable_secret below /proc/sys/net/ipv6/conf */
|
|
rc = EXIT_FAILURE;
|
|
break;
|
|
default:
|
|
xwarn(_("reading key \"%s\""), outname);
|
|
rc = EXIT_FAILURE;
|
|
break;
|
|
}
|
|
} else {
|
|
errno = 0;
|
|
if ((rlen = getline(&iobuf, &iolen, fp)) > 0) {
|
|
/* this loop is required, see
|
|
* /sbin/sysctl -a | egrep -6 dev.cdrom.info
|
|
*/
|
|
do {
|
|
char *nlptr;
|
|
if (PrintName) {
|
|
fprintf(stdout, "%s = ", outname);
|
|
do {
|
|
fprintf(stdout, "%s", iobuf);
|
|
nlptr = &iobuf[strlen(iobuf) - 1];
|
|
/* already has the \n in it */
|
|
if (*nlptr == '\n')
|
|
break;
|
|
} while ((rlen = getline(&iobuf, &iolen, fp)) > 0);
|
|
if (*nlptr != '\n')
|
|
putchar('\n');
|
|
} else {
|
|
if (!PrintNewline) {
|
|
nlptr = strchr(iobuf, '\n');
|
|
if (nlptr)
|
|
*nlptr = '\0';
|
|
}
|
|
fprintf(stdout, "%s", iobuf);
|
|
}
|
|
} while ((rlen = getline(&iobuf, &iolen, fp)) > 0);
|
|
} else {
|
|
switch (errno) {
|
|
case EACCES:
|
|
xwarnx(_("permission denied on key '%s'"),
|
|
outname);
|
|
rc = EXIT_FAILURE;
|
|
break;
|
|
case EISDIR: {
|
|
size_t len;
|
|
len = strlen(tmpname);
|
|
tmpname[len] = '/';
|
|
tmpname[len + 1] = '\0';
|
|
fclose(fp);
|
|
rc = DisplayAll(tmpname);
|
|
goto out;
|
|
}
|
|
case EIO: /* Ignore stable_secret below /proc/sys/net/ipv6/conf */
|
|
rc = EXIT_FAILURE;
|
|
break;
|
|
default:
|
|
xwarnx(_("reading key \"%s\""), outname);
|
|
rc = EXIT_FAILURE;
|
|
case 0:
|
|
break;
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
out:
|
|
free(tmpname);
|
|
free(outname);
|
|
return rc;
|
|
}
|
|
|
|
static int is_deprecated(char *filename)
|
|
{
|
|
int i;
|
|
for (i = 0; strlen(DEPRECATED[i]); i++) {
|
|
if (strcmp(DEPRECATED[i], filename) == 0)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Display all the sysctl settings
|
|
*/
|
|
static int DisplayAll(const char *restrict const path)
|
|
{
|
|
int rc = EXIT_SUCCESS;
|
|
int rc2;
|
|
DIR *restrict dp;
|
|
struct dirent *restrict de;
|
|
struct stat ts;
|
|
|
|
dp = opendir(path);
|
|
|
|
if (!dp) {
|
|
xwarnx(_("unable to open directory \"%s\""), path);
|
|
rc = EXIT_FAILURE;
|
|
} else {
|
|
readdir(dp); /* skip . */
|
|
readdir(dp); /* skip .. */
|
|
while ((de = readdir(dp))) {
|
|
char *restrict tmpdir;
|
|
if (IgnoreDeprecated && is_deprecated(de->d_name))
|
|
continue;
|
|
tmpdir =
|
|
(char *restrict) xmalloc(strlen(path) +
|
|
strlen(de->d_name) +
|
|
2);
|
|
sprintf(tmpdir, "%s%s", path, de->d_name);
|
|
rc2 = stat(tmpdir, &ts);
|
|
if (rc2 != 0) {
|
|
xwarn(_("cannot stat %s"), tmpdir);
|
|
} else {
|
|
if (S_ISDIR(ts.st_mode)) {
|
|
strcat(tmpdir, "/");
|
|
DisplayAll(tmpdir);
|
|
} else {
|
|
rc |=
|
|
ReadSetting(tmpdir +
|
|
strlen(PROC_PATH));
|
|
}
|
|
}
|
|
free(tmpdir);
|
|
}
|
|
closedir(dp);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Write a sysctl setting
|
|
*/
|
|
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 (!key || !path)
|
|
return rc;
|
|
|
|
if (stat(path, &ts) < 0) {
|
|
if (!IgnoreError) {
|
|
xwarn(_("cannot stat %s"), path);
|
|
rc = EXIT_FAILURE;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
if ((ts.st_mode & S_IWUSR) == 0) {
|
|
xwarn(_("setting key \"%s\""), key);
|
|
return rc;
|
|
}
|
|
|
|
if (S_ISDIR(ts.st_mode)) {
|
|
xwarn(_("setting key \"%s\""), key);
|
|
return rc;
|
|
}
|
|
|
|
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 = EXIT_FAILURE;
|
|
}
|
|
break;
|
|
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)
|
|
{
|
|
int status;
|
|
regex_t re;
|
|
|
|
if (regcomp(&re, pat, REG_EXTENDED | REG_NOSUB) != 0)
|
|
return (0);
|
|
status = regexec(&re, string, (size_t) 0, NULL, 0);
|
|
regfree(&re);
|
|
if (status != 0)
|
|
return (0);
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Preload the sysctl's from the conf file. We parse the file and then
|
|
* reform it (strip out whitespace).
|
|
*/
|
|
static int Preload(SettingList *setlist, const char *restrict const filename)
|
|
{
|
|
FILE *fp;
|
|
char *t;
|
|
int n = 0;
|
|
int rc = EXIT_SUCCESS;
|
|
ssize_t rlen;
|
|
char *name, *value;
|
|
glob_t globbuf;
|
|
int globerr;
|
|
int globflg;
|
|
int j;
|
|
|
|
globflg = GLOB_NOCHECK;
|
|
#ifdef GLOB_BRACE
|
|
globflg |= GLOB_BRACE;
|
|
#endif
|
|
#ifdef GLOB_TILDE
|
|
globflg |= GLOB_TILDE;
|
|
#else
|
|
if (filename[0] == '~')
|
|
xwarnx(_("GLOB_TILDE is not supported on your platform, "
|
|
"the tilde in \"%s\" won't be expanded."), filename);
|
|
#endif
|
|
globerr = glob(filename, globflg, NULL, &globbuf);
|
|
|
|
if (globerr != 0 && globerr != GLOB_NOMATCH)
|
|
xerr(EXIT_FAILURE, _("glob failed"));
|
|
|
|
for (j = 0; j < globbuf.gl_pathc; j++) {
|
|
fp = (globbuf.gl_pathv[j][0] == '-' && !globbuf.gl_pathv[j][1])
|
|
? stdin : fopen(globbuf.gl_pathv[j], "r");
|
|
if (!fp) {
|
|
xwarn(_("cannot open \"%s\""), globbuf.gl_pathv[j]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
while ((rlen = getline(&iobuf, &iolen, fp)) > 0) {
|
|
size_t offset;
|
|
SysctlSetting *setting;
|
|
|
|
n++;
|
|
|
|
if (rlen < 2)
|
|
continue;
|
|
|
|
if ( (setting = parse_setting_line(globbuf.gl_pathv[j], n, iobuf))
|
|
== NULL)
|
|
continue;
|
|
settinglist_add(setlist, setting);
|
|
}
|
|
|
|
fclose(fp);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
struct pair {
|
|
char *name;
|
|
char *value;
|
|
};
|
|
|
|
static int sortpairs(const void *A, const void *B)
|
|
{
|
|
const struct pair *a = *(struct pair * const *) A;
|
|
const struct pair *b = *(struct pair * const *) B;
|
|
return strcmp(a->name, b->name);
|
|
}
|
|
|
|
static int PreloadSystem(SettingList *setlist)
|
|
{
|
|
unsigned di, i;
|
|
const char *dirs[] = {
|
|
"/etc/sysctl.d",
|
|
"/run/sysctl.d",
|
|
"/usr/local/lib/sysctl.d",
|
|
"/usr/lib/sysctl.d",
|
|
"/lib/sysctl.d",
|
|
};
|
|
struct pair **cfgs = NULL;
|
|
unsigned ncfgs = 0;
|
|
int rc = EXIT_SUCCESS;
|
|
struct stat ts;
|
|
enum { nprealloc = 16 };
|
|
|
|
for (di = 0; di < sizeof(dirs) / sizeof(dirs[0]); ++di) {
|
|
struct dirent *de;
|
|
DIR *dp = opendir(dirs[di]);
|
|
if (!dp)
|
|
continue;
|
|
|
|
while ((de = readdir(dp))) {
|
|
if (!strcmp(de->d_name, ".")
|
|
|| !strcmp(de->d_name, ".."))
|
|
continue;
|
|
if (strlen(de->d_name) < 5
|
|
|| strcmp(de->d_name + strlen(de->d_name) - 5, ".conf"))
|
|
continue;
|
|
/* check if config already known */
|
|
for (i = 0; i < ncfgs; ++i) {
|
|
if (cfgs && !strcmp(cfgs[i]->name, de->d_name))
|
|
break;
|
|
}
|
|
if (i < ncfgs)
|
|
/* already in */
|
|
continue;
|
|
|
|
if (ncfgs % nprealloc == 0)
|
|
cfgs =
|
|
xrealloc(cfgs,
|
|
sizeof(struct pair *) * (ncfgs +
|
|
nprealloc));
|
|
|
|
if (cfgs) {
|
|
cfgs[ncfgs] =
|
|
xmalloc(sizeof(struct pair) +
|
|
strlen(de->d_name) * 2 + 2 +
|
|
strlen(dirs[di]) + 1);
|
|
cfgs[ncfgs]->name =
|
|
(char *)cfgs[ncfgs] + sizeof(struct pair);
|
|
strcpy(cfgs[ncfgs]->name, de->d_name);
|
|
cfgs[ncfgs]->value =
|
|
(char *)cfgs[ncfgs] + sizeof(struct pair) +
|
|
strlen(cfgs[ncfgs]->name) + 1;
|
|
sprintf(cfgs[ncfgs]->value, "%s/%s", dirs[di],
|
|
de->d_name);
|
|
ncfgs++;
|
|
} else {
|
|
xerrx(EXIT_FAILURE, _("internal error"));
|
|
}
|
|
|
|
}
|
|
closedir(dp);
|
|
}
|
|
qsort(cfgs, ncfgs, sizeof(struct cfg *), sortpairs);
|
|
|
|
for (i = 0; i < ncfgs; ++i) {
|
|
if (!Quiet)
|
|
printf(_("* Applying %s ...\n"), 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(setlist, DEFAULT_PRELOAD);
|
|
}
|
|
|
|
/* cleaning */
|
|
for (i = 0; i < ncfgs; ++i) {
|
|
free(cfgs[i]);
|
|
}
|
|
if (cfgs) free(cfgs);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Main...
|
|
*/
|
|
int main(int argc, char *argv[])
|
|
{
|
|
bool WriteMode = false;
|
|
bool DisplayAllOpt = false;
|
|
bool preloadfileOpt = false;
|
|
int ReturnCode = 0;
|
|
int c;
|
|
int rc;
|
|
const char *preloadfile = NULL;
|
|
SettingList *setlist;
|
|
|
|
enum {
|
|
DEPRECATED_OPTION = CHAR_MAX + 1,
|
|
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'},
|
|
{"values", no_argument, NULL, 'n'},
|
|
{"load", optional_argument, NULL, 'p'},
|
|
{"quiet", no_argument, NULL, 'q'},
|
|
{"write", no_argument, NULL, 'w'},
|
|
{"system", no_argument, NULL, SYSTEM_OPTION},
|
|
{"pattern", required_argument, NULL, 'r'},
|
|
{"help", no_argument, NULL, 'h'},
|
|
{"version", no_argument, NULL, 'V'},
|
|
{NULL, 0, NULL, 0}
|
|
};
|
|
|
|
#ifdef HAVE_PROGRAM_INVOCATION_NAME
|
|
program_invocation_name = program_invocation_short_name;
|
|
#endif
|
|
setlocale(LC_ALL, "");
|
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
|
textdomain(PACKAGE);
|
|
atexit(close_stdout);
|
|
|
|
PrintName = true;
|
|
PrintNewline = true;
|
|
IgnoreError = false;
|
|
Quiet = false;
|
|
IgnoreDeprecated = true;
|
|
DryRun = false;
|
|
setlist = xmalloc(sizeof(SettingList));
|
|
setlist->head = NULL;
|
|
setlist->tail = NULL;
|
|
|
|
if (argc < 2)
|
|
Usage(stderr);
|
|
|
|
while ((c =
|
|
getopt_long(argc, argv, "bneNwfp::qoxaAXr:Vdh", longopts,
|
|
NULL)) != -1) {
|
|
switch (c) {
|
|
case 'b':
|
|
/* This is "binary" format, which means more for BSD. */
|
|
PrintNewline = false;
|
|
/* FALL THROUGH */
|
|
case 'n':
|
|
PrintName = false;
|
|
break;
|
|
case 'e':
|
|
/*
|
|
* For FreeBSD, -e means a "%s=%s\n" format.
|
|
* ("%s: %s\n" default). We (and NetBSD) use
|
|
* "%s = %s\n" always, and -e to ignore errors.
|
|
*/
|
|
IgnoreError = true;
|
|
break;
|
|
case 'N':
|
|
NameOnly = true;
|
|
break;
|
|
case 'w':
|
|
WriteMode = true;
|
|
break;
|
|
case 'f': /* the NetBSD way */
|
|
case 'p':
|
|
preloadfileOpt = true;
|
|
if (optarg)
|
|
preloadfile = optarg;
|
|
break;
|
|
case 'q':
|
|
Quiet = true;
|
|
break;
|
|
case 'o': /* BSD: binary values too, 1st 16 bytes in hex */
|
|
case 'x': /* BSD: binary values too, whole thing in hex */
|
|
/* does nothing */ ;
|
|
break;
|
|
case 'a': /* string and integer values (for Linux, all of them) */
|
|
case 'A': /* same as -a -o */
|
|
case 'X': /* same as -a -x */
|
|
DisplayAllOpt = true;
|
|
break;
|
|
case DEPRECATED_OPTION:
|
|
IgnoreDeprecated = false;
|
|
break;
|
|
case SYSTEM_OPTION:
|
|
IgnoreError = true;
|
|
rc |= PreloadSystem(setlist);
|
|
rc |= write_setting_list(setlist);
|
|
return rc;
|
|
case DRYRUN_OPTION:
|
|
DryRun = true;
|
|
break;
|
|
case 'r':
|
|
pattern = xstrdup(optarg);
|
|
break;
|
|
case 'V':
|
|
printf(PROCPS_NG_VERSION);
|
|
return EXIT_SUCCESS;
|
|
case 'd': /* BSD: print description ("vm.kvm_size: Size of KVM") */
|
|
case 'h': /* BSD: human-readable (did FreeBSD 5 make -e default?) */
|
|
case '?':
|
|
Usage(stdout);
|
|
default:
|
|
Usage(stderr);
|
|
}
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
iobuf = xmalloc(iolen);
|
|
|
|
if (DisplayAllOpt)
|
|
return DisplayAll(PROC_PATH);
|
|
|
|
if (preloadfileOpt) {
|
|
int ret = EXIT_SUCCESS, i;
|
|
if (!preloadfile) {
|
|
if (!argc) {
|
|
ret |= Preload(setlist, DEFAULT_PRELOAD);
|
|
}
|
|
} else {
|
|
/* This happens when -pfile option is
|
|
* used without space. */
|
|
ret |= Preload(setlist, preloadfile);
|
|
}
|
|
for (i = 0; i < argc; i++)
|
|
ret |= Preload(setlist, argv[i]);
|
|
ret |= write_setting_list(setlist);
|
|
return ret;
|
|
}
|
|
|
|
if (argc < 1)
|
|
xerrx(EXIT_FAILURE, _("no variables specified\n"
|
|
"Try `%s --help' for more information."),
|
|
program_invocation_short_name);
|
|
if (NameOnly && Quiet)
|
|
xerrx(EXIT_FAILURE, _("options -N and -q cannot coexist\n"
|
|
"Try `%s --help' for more information."),
|
|
program_invocation_short_name);
|
|
|
|
for ( ; *argv; argv++) {
|
|
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;
|
|
}
|