/* vi: set sw=4 ts=4: */
/*
 * Sysctl 1.01 - A utility to read and manipulate the sysctl parameters
 *
 * Copyright 1999 George Staikos
 *
 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
 *
 * Changelog:
 *	v1.01:
 *		- added -p <preload> to preload values from a file
 *	v1.01.1
 *		- busybox applet aware by <solar@gentoo.org>
 *
 */

#include "busybox.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

/*
 *    Function Prototypes
 */
static int sysctl_read_setting(const char *setting, int output);
static int sysctl_write_setting(const char *setting, int output);
static int sysctl_preload_file(const char *filename, int output);
static int sysctl_display_all(const char *path, int output, int show_table);

/*
 *    Globals...
 */
static const char PROC_PATH[] = "/proc/sys/";
static const char DEFAULT_PRELOAD[] = "/etc/sysctl.conf";

/* error messages */
static const char ERR_UNKNOWN_PARAMETER[] = "error: Unknown parameter '%s'\n";
static const char ERR_MALFORMED_SETTING[] = "error: Malformed setting '%s'\n";
static const char ERR_NO_EQUALS[] =
	"error: '%s' must be of the form name=value\n";
static const char ERR_INVALID_KEY[] = "error: '%s' is an unknown key\n";
static const char ERR_UNKNOWN_WRITING[] =
	"error: unknown error %d setting key '%s'\n";
static const char ERR_UNKNOWN_READING[] =
	"error: unknown error %d reading key '%s'\n";
static const char ERR_PERMISSION_DENIED[] =
	"error: permission denied on key '%s'\n";
static const char ERR_PRELOAD_FILE[] =
	"error: unable to open preload file '%s'\n";
static const char WARN_BAD_LINE[] =
	"warning: %s(%d): invalid syntax, continuing...\n";


static void dwrite_str(int fd, const char *buf)
{
	write(fd, buf, strlen(buf));
}

/*
 *    sysctl_main()...
 */
int sysctl_main(int argc, char **argv)
{
	int retval = 0;
	int output = 1;
	int write_mode = 0;
	int switches_allowed = 1;

	if (argc < 2)
		bb_show_usage();

	argv++;

	for (; argv && *argv && **argv; argv++) {
		if (switches_allowed && **argv == '-') {	/* we have a switch */
			switch ((*argv)[1]) {
			case 'n':
				output = 0;
				break;
			case 'w':
				write_mode = 1;
				switches_allowed = 0;
				break;
			case 'p':
				argv++;
				return
					sysctl_preload_file(((argv && *argv
										  && **argv) ? *argv :
										 DEFAULT_PRELOAD), output);
			case 'a':
			case 'A':
				switches_allowed = 0;
				return sysctl_display_all(PROC_PATH, output,
										  ((*argv)[1] == 'a') ? 0 : 1);
			case 'h':
			case '?':
				bb_show_usage();
			default:
				bb_error_msg(ERR_UNKNOWN_PARAMETER, *argv);
				bb_show_usage();
			}
		} else {
			switches_allowed = 0;
			if (write_mode)
				retval = sysctl_write_setting(*argv, output);
			else
				sysctl_read_setting(*argv, output);
		}
	}
	return retval;
}						/* end sysctl_main() */



/*
 *     sysctl_preload_file
 *	preload the sysctl's from a conf file
 *           - we parse the file and then reform it (strip out whitespace)
 */
#define PRELOAD_BUF 256

int sysctl_preload_file(const char *filename, int output)
{
	int lineno = 0;
	char oneline[PRELOAD_BUF];
	char buffer[PRELOAD_BUF];
	char *name, *value, *ptr;
	FILE *fp = NULL;

	if (!filename || ((fp = fopen(filename, "r")) == NULL)) {
		bb_error_msg_and_die(ERR_PRELOAD_FILE, filename);
	}

	while (fgets(oneline, sizeof(oneline) - 1, fp)) {
		oneline[sizeof(oneline) - 1] = 0;
		lineno++;
		trim(oneline);
		ptr = (char *) oneline;

		if (*ptr == '#' || *ptr == ';')
			continue;

		if (strlen(ptr) < 2)
			continue;

		name = strtok(ptr, "=");
		if (!name || !*name) {
			bb_error_msg(WARN_BAD_LINE, filename, lineno);
			continue;
		}

		trim(name);

		value = strtok(NULL, "\n\r");
		if (!value || !*value) {
			bb_error_msg(WARN_BAD_LINE, filename, lineno);
			continue;
		}

		while ((*value == ' ' || *value == '\t') && *value != 0)
			value++;
		strcpy(buffer, name);
		strcat(buffer, "=");
		strcat(buffer, value);
		sysctl_write_setting(buffer, output);
	}
	fclose(fp);
	return 0;
}						/* end sysctl_preload_file() */


/*
 *     Write a single sysctl setting
 */
int sysctl_write_setting(const char *setting, int output)
{
	int retval = 0;
	const char *name = setting;
	const char *value;
	const char *equals;
	char *tmpname, *outname, *cptr;
	int fd = -1;

	if (!name)			/* probably dont' want to display this  err */
		return 0;

	if (!(equals = strchr(setting, '='))) {
		bb_error_msg(ERR_NO_EQUALS, setting);
		return -1;
	}

	value = equals + sizeof(char);	/* point to the value in name=value */

	if (!*name || !*value || name == equals) {
		bb_error_msg(ERR_MALFORMED_SETTING, setting);
		return -2;
	}

	tmpname = bb_xasprintf("%s%.*s", PROC_PATH, (int)(equals - name), name);
	outname = bb_xstrdup(tmpname + strlen(PROC_PATH));

	while ((cptr = strchr(tmpname, '.')) != NULL)
		*cptr = '/';

	while ((cptr = strchr(outname, '/')) != NULL)
		*cptr = '.';

	if ((fd = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) {
		switch (errno) {
		case ENOENT:
			bb_error_msg(ERR_INVALID_KEY, outname);
			break;
		case EACCES:
			bb_perror_msg(ERR_PERMISSION_DENIED, outname);
			break;
		default:
			bb_error_msg(ERR_UNKNOWN_WRITING, errno, outname);
			break;
		}
		retval = -1;
	} else {
		dwrite_str(fd, value);
		close(fd);
		if (output) {
			dwrite_str(STDOUT_FILENO, outname);
			dwrite_str(STDOUT_FILENO, " = ");
		}
		dwrite_str(STDOUT_FILENO, value);
		dwrite_str(STDOUT_FILENO, "\n");
	}

	/* cleanup */
	free(tmpname);
	free(outname);
	return retval;
}						/* end sysctl_write_setting() */


/*
 *     Read a sysctl setting
 *
 */
int sysctl_read_setting(const char *setting, int output)
{
	int retval = 0;
	char *tmpname, *outname, *cptr;
	char inbuf[1025];
	const char *name = setting;
	FILE *fp;

	if (!setting || !*setting)
		bb_error_msg(ERR_INVALID_KEY, setting);

	tmpname = concat_path_file(PROC_PATH, name);
	outname = bb_xstrdup(tmpname + strlen(PROC_PATH));

	while ((cptr = strchr(tmpname, '.')) != NULL)
		*cptr = '/';
	while ((cptr = strchr(outname, '/')) != NULL)
		*cptr = '.';

	if ((fp = fopen(tmpname, "r")) == NULL) {
		switch (errno) {
		case ENOENT:
			bb_error_msg(ERR_INVALID_KEY, outname);
			break;
		case EACCES:
			bb_error_msg(ERR_PERMISSION_DENIED, outname);
			break;
		default:
			bb_error_msg(ERR_UNKNOWN_READING, errno, outname);
			break;
		}
		retval = -1;
	} else {
		while (fgets(inbuf, sizeof(inbuf) - 1, fp)) {
			if (output) {
				dwrite_str(STDOUT_FILENO, outname);
				dwrite_str(STDOUT_FILENO, " = ");
			}
			dwrite_str(STDOUT_FILENO, inbuf);
		}
		fclose(fp);
	}

	free(tmpname);
	free(outname);
	return retval;
}						/* end sysctl_read_setting() */



/*
 *     Display all the sysctl settings
 *
 */
int sysctl_display_all(const char *path, int output, int show_table)
{
	int retval = 0;
	int retval2;
	DIR *dp;
	struct dirent *de;
	char *tmpdir;
	struct stat ts;

	if (!(dp = bb_opendir(path))) {
		retval = -1;
	} else {
		while ((de = readdir(dp)) != NULL) {
			tmpdir = concat_subpath_file(path, de->d_name);
			if(tmpdir == NULL)
				continue;
			if ((retval2 = stat(tmpdir, &ts)) != 0)
				bb_perror_msg(tmpdir);
			else {
				if (S_ISDIR(ts.st_mode)) {
					sysctl_display_all(tmpdir, output, show_table);
				} else
					retval |=
						sysctl_read_setting(tmpdir + strlen(PROC_PATH),
											output);

			}
			free(tmpdir);
		}				/* end while */
		closedir(dp);
	}

	return retval;
}						/* end sysctl_display_all() */
