/*
 * Sysctl - A utility to read and manipulate the sysctl parameters
 *
 * Copyright © 2009-2025 Craig Small <csmall@dropbear.xyz>
 * Copyright © 2012-2023 Jim Warner <james.warner@comcast.net>
 * Copyright © 2017-2018 Werner Fink <werner@suse.de>
 * Copyright © 2014      Jaromir Capik <jcapik@redhat.com>
 * Copyright © 2011-2012 Sami Kerola <kerolasa@iki.fi>
 * Copyright © 2002-2007 Albert Cahalan
 * 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
 *
 */

#include <dirent.h>
#include <errno.h>
#include <getopt.h>
#include <glob.h>
#include <fnmatch.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 "procio.h"

/*
 *    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",
	""
};
/* Verboten parameters must never be read as they cause side-effects */
static const char *VERBOTEN[] = {
    "stat_refresh",
    NULL
};

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 inline bool is_proc_path(
	const char *path)
{
    char *resolved_path;

    if ( (resolved_path = realpath(path, NULL)) == NULL)
	return false;

    if (strncmp(PROC_PATH, resolved_path, strlen(PROC_PATH)) == 0) {
	free(resolved_path);
	return true;
    }

    warnx(_("Path is not under %s: %s"), PROC_PATH, path);
    free(resolved_path);
    return false;
}

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) {
			warnx(_("separators should not be repeated: %s"), p);
			warned = 0;
		}
		if (c == old)
			*p = new;
		if (c == new)
			*p = old;
		p = strpbrk(p + 1, "/.");
	}
}

#if 0  // avoid '-Wunused-function' warning
static void setting_free(SysctlSetting *s) {
    if (!s)
	return;

    free(s->key);
    free(s->path);
    free(s->value);
    free(s);
}
#endif

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 / for path */
    slashdot(path + proc_len, '.', '/');

    s = xmalloc(sizeof(SysctlSetting));

    *s = (SysctlSetting) {
        .key = xstrdup(key),
        .path = path,
        .value = value? xstrdup(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;
        if (node->glob_exclude && fnmatch(node->path, path, 0) == 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);
	// TODO: other tools in src/ use one leading blank
	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';
}

#if 0  // avoid '-Wunused-function' warning
/*
 * 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;
}
#endif

/*
 * 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) {
		warnx(_("\"%s\" is an unknown key"), name);
		return -1;
	}

	/* 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) {
			warn(_("cannot stat %s"), tmpname);
			rc = EXIT_FAILURE;
		}
		goto out;
	}
	if ((ts.st_mode & S_IRUSR) == 0)
		goto out;

	if (!is_proc_path(tmpname)) {
	    rc = -1;
	    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) {
				warnx(_("\"%s\" is an unknown key"), outname);
				rc = EXIT_FAILURE;
			}
			break;
		case EACCES:
			warnx(_("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:
			warn(_("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:
				warnx(_("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:
				warnx(_("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;
}

static bool is_verboten(const char *filename)
{
	int i;
	for (i = 0; VERBOTEN[i]; i++) {
		if (strcmp(VERBOTEN[i], filename) == 0)
			return TRUE;
	}
	return FALSE;
}

/*
 * 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) {
		warnx(_("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;
                        if (is_verboten(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) {
				warn(_("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;
    char *dotted_key;

    if (!key || !path)
        return rc;

    if (stat(path, &ts) < 0) {
        if (!IgnoreError) {
            warn(_("cannot stat %s"), path);
            rc = EXIT_FAILURE;
        }
        return rc;
    }

    if (!is_proc_path(path)) {
        return EXIT_FAILURE;
    }

    /* Convert the globbed path into a dotted key */
    if ( (dotted_key = strdup(path + strlen(PROC_PATH))) == NULL) {
	errx(EXIT_FAILURE, _("strdup key"));
	return EXIT_FAILURE;
    }
    slashdot(dotted_key, '/', '.');

    if ((ts.st_mode & S_IWUSR) == 0) {
        errno = EPERM;
        warn(_("setting key \"%s\""), dotted_key);
	free(dotted_key);
        return EXIT_FAILURE;
    }

    if (S_ISDIR(ts.st_mode)) {
        errno = EISDIR;
        warn(_("setting key \"%s\""), dotted_key);
	free(dotted_key);
        return EXIT_FAILURE;
    }

    if (!DryRun) {
        if ((fp = fprocopen(path, "w")) == NULL) {
            switch (errno) {
            case ENOENT:
                if (!IgnoreError) {
                    warnx(_("\"%s\" is an unknown key%s"),
                           dotted_key, (ignore_failure?_(", ignoring"):""));
                    if (!ignore_failure)
                        rc = EXIT_FAILURE;
                }
                break;
            case EPERM:
            case EROFS:
            case EACCES:
                warnx(_("permission denied on key \"%s\"%s"),
                       dotted_key, (ignore_failure?_(", ignoring"):""));
                break;
            default:
                warn(_("setting key \"%s\"%s"),
                      dotted_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) {
                warn(_("setting key \"%s\""), dotted_key);
		free(dotted_key);
                return EXIT_FAILURE;
            }
        }
    }
    if ((rc == EXIT_SUCCESS && !Quiet) || DryRun) {
        if (NameOnly) {
            printf("%s\n", dotted_key);
        } else {
            if (PrintName) {
                printf("%s = %s\n", dotted_key, value);
            } else {
                if (PrintNewline)
                    printf("%s\n", value);
                else
                    printf("%s", value);
            }
        }
    }
    free(dotted_key);
    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.exclude.*.glob               dont set values for keys matching glob
 * 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)
{
    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 {
            warnx(_("%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)) {
            glob_t globbuf;
            int i;

            if (glob(node->path, 0, NULL, &globbuf) != 0)
                continue;

            for(i=0; i < (int)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;
	int n = 0;
	int rc = EXIT_SUCCESS;
	ssize_t rlen;
	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] == '~')
		warnx(_("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)
		err(EXIT_FAILURE, _("glob failed"));

	for (j = 0; j < (int)globbuf.gl_pathc; j++) {
		fp = (globbuf.gl_pathv[j][0] == '-' && !globbuf.gl_pathv[j][1])
		    ? stdin : fopen(globbuf.gl_pathv[j], "r");
		if (!fp) {
			warn(_("cannot open \"%s\""), globbuf.gl_pathv[j]);
            return EXIT_FAILURE;
		}

		while ((rlen =  getline(&iobuf, &iolen, fp)) > 0) {
            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 {
				errx(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;
        bool SystemOpt = false;
	int ReturnCode = 0;
	int c;
	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;
                        SystemOpt = true;
                        break;
        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;
	} else if (SystemOpt) {
            ReturnCode |= PreloadSystem(setlist);
            ReturnCode |= write_setting_list(setlist);
            if (argc < 1)
                return ReturnCode;
        }

	if (argc < 1)
		errx(EXIT_FAILURE, _("no variables specified\n"
				      "Try `%s --help' for more information."),
		      program_invocation_short_name);
	if (NameOnly && Quiet)
		errx(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;
}
