/*
 * acpi_helper.cpp - acpi helper 
 *
 * Copyright (c) 2002 Paul Campbell <paul@taniwha.com>
 *
 *  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.
 */

//
//	README!!
//
//	This file contains code that is intended to be run setuid root
//	(only if the end user enables it themselves, it's not set that
//	way as part of a standard KDE build).
//
//	Because of this this code should be simple and easily visually
//	inspected for security holes and/or bugs - if you feel the need
//	to change this file please get someone else to review your work
//	(I'll happily do it for you - mail me at paul@taniwha.com, please
//	review mine!)
//
//	I recommend the following practices here - both for safety and
//	transparency:
//
//		- check all array references (snprintf/strncpy etc)
//
//		- avoid malloc/new calls and pointers  too if possible
//

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>

#define MAX_TOSHIBA_STRING 64

/* Write a value to /proc/acpi/sleep, where value may be between
   1 and 4 (whatever they mean). Does not return; calls exit(). */

void write_to_proc_sleep(int value)
{
	char tmp[256];
	int fd;

	/* Sanity check value */
	if ((value<1) || (value>4))
		exit(1);

	/* Convert value to string */
	snprintf(tmp,sizeof(tmp),"%d",value);
	tmp[sizeof(tmp)-1]=0;

	/* Broken imitation of typing sync <enter> sync <enter>
	   on the command line before shutting down the machine;
	   part of the lore of UNIX machines. */
	sync();
	sync();
	fd = open("/proc/acpi/sleep", O_RDWR);
	if (fd < 0)
		exit(1);
	write(fd, tmp, 1);
	close(fd);
	setuid(getuid());	// drop all priority asap
	exit(0);
}

/* Write string to new acpi power interface */
void write_to_power(const char * str)
{
	int fd;
	/* Broken imitation of typing sync <enter> sync <enter>
	   on the command line before shutting down the machine;
	   part of the lore of UNIX machines. */
	sync();
	sync();
	fd = open("/sys/power/state", O_RDWR);
	if (fd < 0)
		exit(1);
	write(fd, str, strlen(str));
	close(fd);
	setuid(getuid());	// drop all priority asap
	exit(0);
}

/* Run the program @param path, if it exists and seems safe to do so.
   Returns only if the program does not exist; if the program exists
   and is unsafe, exit; if the program exists and is safe, run it
   and never return. */
void run_program(const char *path, const int action)
{
	struct stat sb;
	int err;

	if (!path) exit(1); /* Bad pointer */
	if (path[0] != '/') exit(1); /* Not an absolute path */

	if ((err = stat(path, &sb)) != 0 || sb.st_mode&S_IWOTH) {
		if (err != 0) {
			fprintf(stderr, "Can't find %s\n", path);
			return;
		} else {
			fprintf(stderr, "%s is writeable by anyone - we don't trust it\n", path);
		}
		exit(1);
	}
	::setuid(::geteuid());					// otherwise bash will throw it away
	if (action == 1) {
		system("/usr/sbin/pmi action hibernate");
	} else if (action == 2) {
		system("/usr/sbin/pmi action sleep");
	} else {
		::execl(path, NULL);	// this is not KDE environment code 
	}
	exit(0);
}

int
main(int argc, char **argv)
{
	int fd;
	int i;
	int toshibalcd_val = 0;
        bool useSysPower=false;

	fd = open("/sys/power/state", O_RDWR);
	if (fd >= 0)
		useSysPower=true;
        close(fd);

	::close(0);	// we're setuid - this is just in case
	for (i = 1; i < argc; i++)
	if (strcmp(argv[i], "--suspend") == 0 || strcmp(argv[i], "-suspend") == 0) {
		/* Returns only if suspend does not exist. */
		run_program("/usr/sbin/pmi", 2);
		/*
		if (useSysPower)
			write_to_power("mem");
		else
			write_to_proc_sleep(3);
		*/
		exit(0);
	} else
	if (strcmp(argv[i], "--standby") == 0 || strcmp(argv[i], "-standby") == 0) {
		if (useSysPower)
			write_to_power("standby");
		else
			write_to_proc_sleep(1);
		exit(0);
	} else
	if (strcmp(argv[i], "--standby2") == 0 || strcmp(argv[i], "-standby2") == 0) {
		write_to_proc_sleep(2);
		exit(0);
	} else
	if (strcmp(argv[i], "--hibernate") == 0 || strcmp(argv[i], "-hibernate") == 0) {
		run_program("/usr/sbin/pmi", 1);
		/*
		if (useSysPower)
			write_to_power("disk");
		else
			write_to_proc_sleep(4);
		*/
		exit(0);
	} else
	if (strcmp(argv[i], "--software-suspend") == 0 || strcmp(argv[i], "-software-suspend") == 0) {
		run_program("/usr/sbin/hibernate", 0);
		exit(0);
	} else
	if (strcmp(argv[i], "--throttling") == 0 || strcmp(argv[i], "-throttling") == 0) {
		int val;
		char tmp[256];

		i++;
		if (i >= argc) 
			break;
		if (strlen(argv[i]) > 50 || strchr(argv[i], '.'))
			break;
		snprintf(tmp, sizeof(tmp), "/proc/acpi/processor/%s/throttling", argv[i]);
		tmp[sizeof(tmp)-1] = 0;
		i++;
		if (i >= argc) 
			break;
		val= atoi(argv[i]);
		if (val < 0)
			break;
		sync();
		sync();
		fd = open(tmp, O_RDWR);
		if (fd < 0)
			exit(1);
		snprintf(tmp, sizeof(tmp), "%d", val);
		write(fd, tmp, strlen(tmp));
		close(fd);
        	setuid(getuid());	// drop all priority asap
		exit(0);
	} else
	if (strcmp(argv[i], "--performance") == 0 || strcmp(argv[i], "-performance") == 0) {
		int val;
		char tmp[256];

		i++;
		if (i >= argc) 
			break;
		if (strlen(argv[i]) > 50 || strchr(argv[i], '.'))
			break;
		snprintf(tmp, sizeof(tmp), "/proc/acpi/processor/%s/performance", argv[i]);
		tmp[sizeof(tmp)-1] = 0;
		i++;
		if (i >= argc) 
			break;
		val= atoi(argv[i]);
		if (val < 0)
			break;
		sync();
		sync();
		fd = open(tmp, O_RDWR);
		if (fd < 0)
			exit(1);
		snprintf(tmp, sizeof(tmp), "%d", val);
		write(fd, tmp, strlen(tmp));
		close(fd);
        	setuid(getuid());	// drop all priority asap
		exit(0);
	} else
	if (strcmp(argv[i], "--toshibalcd") == 0 || strcmp(argv[i], "-toshibalcd") == 0) {
		
		i++;
		if (i >= argc) 
			break;
		toshibalcd_val= atoi(argv[i]);
		if (toshibalcd_val < 0)
			toshibalcd_val = 0;
		if (toshibalcd_val > 7)
			toshibalcd_val = 7;
		fd = open("/proc/acpi/TOSHIBA1/lcd", O_RDWR);
		if (fd >= 0 ) {
		    char c;
		    
		    c = '0'+toshibalcd_val;
		    write(fd, &c, 1);
		    close(fd);
		} else {
		    fd = open("/proc/acpi/toshiba/lcd", O_RDWR);
		    if (fd >= 0) {
			char str[MAX_TOSHIBA_STRING];
			
			snprintf(str,sizeof(str),"brightness : %d",toshibalcd_val);
			str[sizeof(str)-1]=0;
			write(fd,str,strlen(str));	
			close(fd);
		    }
		}
        	setuid(getuid());	// drop all priority asap
		exit(0);
	} else
	// CPUFreq support
	if (strncmp(argv[i], "--cpufreq", 9) == 0 || strncmp(argv[i], "-cpufreq", 8) == 0) {
		if ((i+1) >= argc)
			break;
		if (strlen(argv[i+1]) > 50 || strchr(argv[i+1], '.'))
			break;
		int val;
		char tmp[256];
		// CPUFreq support for the interface of the 2.4 kernell (/proc/sys/cpu/N/)
		if (strcmp(argv[i], "--cpufreq-24") == 0 || strcmp(argv[i], "-cpufreq-24") == 0) {
			++i;
			snprintf(tmp, sizeof(tmp), "/proc/sys/cpu/%s/speed", argv[i]);
			tmp[sizeof(tmp)-1] = 0;
			++i;
			if (i >= argc)
				break;
			val = atoi(argv[i]);
			if (val < 0)
				break;
			fd = open(tmp, O_WRONLY);
			if (fd < 0)
				exit(1);
			snprintf(tmp, sizeof(tmp), "%d", val);
			write(fd, tmp, strlen(tmp));
		} else
		// CPUFreq support for the interface of the 2.5 kernel (/proc/cpufreq)
		if (strcmp(argv[i], "--cpufreq-25") == 0 || strcmp(argv[i], "-cpufreq-25") == 0) {
			++i;
			snprintf(tmp, sizeof(tmp), "%s", argv[i]);
			tmp[sizeof(tmp)-1] = 0;
			fd = open("/proc/cpufreq", O_WRONLY);
			if (fd < 0)
				exit(1);
			write(fd, tmp, strlen(tmp));
		} else
		// CPUFreq support fot the sysfs interface of the 2.5 kernel (/sys/devices/sys/cpuN/cpufreq/)
		if (strcmp(argv[i], "--cpufreq-sysfs") == 0 || strcmp(argv[i], "-cpufreq-sysfs") == 0) {
			++i;
			snprintf(tmp, sizeof(tmp), "/sys/devices/system/cpu/%s/cpufreq/scaling_governor", argv[i]);
			tmp[sizeof(tmp)-1] = 0;
			++i;
			if (i >= argc)
				break;
			fd = open(tmp, O_WRONLY);
			if (fd < 0)
				exit(1);
			if (strlen(argv[i]) > 50)
				break;
			snprintf(tmp, sizeof(tmp), "%s", argv[i]);
			tmp[sizeof(tmp)-1] = 0;
			write(fd, tmp, strlen(tmp));
		} else {
			break;
		}
		close(fd);
		setuid(getuid()); // drop all priority asap
		exit(0);
	} else {
usage:
        	setuid(getuid());	// drop all priority asap
		fprintf(stderr, "Usage: %s [--suspend] [--standby] [--hibernate][--software-suspend][--toshibalcd N][--performance CPU N][--throttling CPU N][--cpufreq-[24|25|sysfs]]\n", argv[0]);
		exit(1);
	}
	goto usage;
    
}

