/*
 * swsuspctl.c
 *
 * Userspace interface to control software suspend features.
 *
 * Copyright (C) 2003 Bernard Blackham <b-swsusp@blackham.com.au>
 * Licensed under the GPLv2.
 *
 */

#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/reboot.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>


/* return values for swsusp() and also this program */
#define SWSUSP_SUCCESS            0
#define SWSUSP_ABORT_USER         11
#define SWSUSP_ABORT_NOSWAP       12
#define SWSUSP_ABORT_LITTLESWAP   13
#define SWSUSP_ABORT_FREEZE_FAIL  14


#define BMAGIC_SWSUSP	0xD000FCE2
#define SWSUSP_PROC     "/proc/sys/kernel/swsusp"
#define SWSUSP_FIFO     "/dev/swsuspctl"
#define PROG_NAME       "swsuspctl"
#define PROG_VERSION    "0.84"
#define SWSUSP_VERSION  VER_1_0_PRE13
#define SWSUSP_VERSION_NAME "1.0_pre13-2_4_21"

/* define some actions */
#define ACTION_STATUS    1
#define ACTION_PARAMS    2
#define ACTION_SAVE      3
#define ACTION_RESTORE   4
#define ACTION_GO        5
#define ACTION_DAEMONISE 6

/* define some swsusp versions we know about */
#define VER_UNKNOWN    -1
#define VER_1_0_ISH     0x1000
#define VER_1_0_PRE6    0x1006  /* we started here :) */
#define VER_1_0_PRE7    0x1007
#define VER_1_0_PRE8    0x1008
#define VER_1_0_PRE9    0x1009
#define VER_1_0_PRE10   0x1010
#define VER_1_0_PRE11   0x1011
#define VER_1_0_PRE12   0x1012
#define VER_1_0_PRE12A  0x1012
#define VER_1_0_PRE13   0x1013
#define VER_1_0         0x100F

/* some of the more obscurer parameters get obscurer characters      */
/* hopefully people wont find it triggers on random unicode chars :) */
#define OPT_ASYNC_READS  0xFF01
#define OPT_ASYNC_WRITES 0xFF02
#define OPT_PAGESET2     0xFF03
#define OPT_LOGALL       0xFF04
#define OPT_IMAGELIMIT   0xFF05
#define OPT_MAXASYNCIO   0xFF06

#define BAIL(s, x...) { fprintf(stderr, PROG_NAME": "s"\n", ## x); exit(1); }
#define WARN(l, s, x...) { if (verbosity>=l) fprintf(stderr, PROG_NAME": "s"\n", ## x); }

#define BSET(i, b, o) if(o)i|=(1<<b);else i&=~(1<<b)
#define BGET(i, b) (i&(1<<b))

int verbosity = 0;
int swsusp_version = VER_UNKNOWN;
int has_debug = 0;
char* swsusp_version_string = NULL;
char* called_name;
FILE* ctl_fifo;

typedef int status_t[6];
status_t curr_status, old_status;

int swsusp(void);

int intcmp(int* a, int* b, int sz) {
	/* returns 1 if a[]==b[] */
	int i;
	for (i=0;i<sz;i++)
		if (a[i]!=b[i]) return 0;
	return 1;
}

void sanity_checks() {
	FILE* pe;

	/* make sure our swsusp proc entry exists */
	pe = fopen(SWSUSP_PROC, "r");
	if (pe) {
		fclose(pe);
	} else {
		BAIL("Can not open "SWSUSP_PROC". "
				"Are you running a swsusp-enabled kernel?");
	}
}

void need_root() {
	/* make sure we are root */
	if (geteuid()!=0) BAIL("Sorry, you must be root to do that.");
}

#define MATCH(s,v) if (0==strcmp(ver_start, s)) { \
	swsusp_version = v; swsusp_version_string = strdup(ver_start); break; }
#define FUZZYMATCH(s, v) if (0==strncmp(ver_start, s, strlen(s))) { \
	swsusp_version = v; swsusp_version_string = strdup(ver_start); break; }

void find_swsusp_version() {
	/* FIXME - is there a better way? */
	FILE* dmesg = fopen("/var/log/dmesg", "r");
	char* s = malloc(1024);
	if (!dmesg) {
		WARN(1, "Can't open dmesg to find swsusp version! Results may be unpredictable!");
		return;
	}
	while (fgets(s, 1024, dmesg)) {
		if (strstr(s, "kswsuspd starting")) {
			char *ver_start, *ver_end;

			ver_start = strchr(s, ' ');
			if (!ver_start) continue;
			ver_start++; /* points to start of version number */

			ver_end = strchr(ver_start, ':');
			if (!ver_end) continue;
			*ver_end = '\0';

			WARN(3, "find_swsusp_version: maybe match \"%s\"", ver_start);

			/* now figure out what the version number is */
			MATCH("1.0_pre6-2_4_21_rc7", VER_1_0_PRE6);
			MATCH("1.0_pre7-2_4_21_rc7", VER_1_0_PRE7);
			MATCH("1.0_pre8-2_4_21_rc7", VER_1_0_PRE8);
			MATCH("1.0_pre9-2_4_21_rc7", VER_1_0_PRE9);
			MATCH("1.0_pre10-2_4_21_rc7", VER_1_0_PRE10);
			MATCH("1.0_pre11-2_4_21", VER_1_0_PRE11);
			MATCH("1.0_pre12-2_4_21", VER_1_0_PRE12);
			MATCH("1.0_pre12A-2_4_21", VER_1_0_PRE12);
			MATCH("1.0_pre13-2_4_21", VER_1_0_PRE13);
			FUZZYMATCH("1.0_", VER_1_0_ISH);
		}
	}
	if (feof(dmesg))
		WARN(2, "find_swsusp_version: no match")
	else
		WARN(2, "find_swsusp_version: 0x%x", swsusp_version);
	free(s);
	fclose(dmesg);
	if (swsusp_version == VER_UNKNOWN) swsusp_version_string = strdup("<unknown>");
}

void set_reboot(status_t status, int on) { BSET(status[1], 0, on); }
int  get_reboot(status_t status)  { return BGET(status[1], 0);     }

void set_progress(status_t status, int on) { BSET(status[1], 1, on); }
int  get_progress(status_t status)  { return BGET(status[1], 1); }

void set_pause(status_t status, int on) { BSET(status[1], 2, on); }
int  get_pause(status_t status)  { return BGET(status[1], 2); }

void set_slow(status_t status, int on) { BSET(status[1], 3, on); }
int  get_slow(status_t status)  { return BGET(status[1], 3); }

void set_beep(status_t status, int on) { BSET(status[1], 4, on); }
int  get_beep(status_t status)  { return BGET(status[1], 4); }

void set_async_reads(status_t status, int on) { BSET(status[1], 5, !on); }
int  get_async_reads(status_t status) { return !BGET(status[1], 5);     }

void set_async_writes(status_t status, int on) { BSET(status[1], 6, !on); }
int  get_async_writes(status_t status) { return !BGET(status[1], 6);     }

void set_pageset2(status_t status, int on) { BSET(status[1], 7, !on); }
int  get_pageset2(status_t status) { return !BGET(status[1], 7);     }

void set_logall(status_t status, int on) { BSET(status[1], 8, on); }
int  get_logall(status_t status)  { return BGET(status[1], 8); }

void set_compress(status_t status, int on) { BSET(status[1], 9, !on); }
int  get_compress(status_t status) { return !BGET(status[1], 9);     }

void set_imagelimit(status_t status, int sz) { status[4] = sz; }
int  get_imagelimit(status_t status) { return status[4]; }

void set_maxasyncio(status_t status, int max) { status[5] = max; }
int  get_maxasyncio(status_t status) { return status[5]; }

void set_debug(status_t status, int level) {
	/* precondition: level is {0..4} */
	status[2] = level?0xFFFF:0; /* set to 0 if level=0 */
	status[3] = (1<<level)-1;
}

int get_debug(status_t status) {
	/* picks the highest bit turned on from status[3] */
	int s = status[3], i=0;
	for(;s;i++,s>>=1);
	return i;
}

char* debug_words(int debug_level) {
	if (debug_level == 0) return "None";
	if (debug_level == 1) return "Low";
	if (debug_level == 2) return "Medium";
	if (debug_level == 3) return "High";
	if (debug_level == 4) return "Verbose";
	if (debug_level < 0) return "Unsure!";
	return "Seriously verbose";
}

void write_status(FILE* f, status_t status) {
	assert(f && status);
	if (has_debug) {
		if (fprintf(f, "%d %d %d %d %d %d\n", status[0], status[1], status[2],
					status[3], status[4], status[5])<=0)
			BAIL("error writing to file");
	} else {
		if (fprintf(f, "%d %d %d %d\n", status[0], status[1], status[4],
					status[5])<=0)
			BAIL("error writing to file");
	}
}

void proc_read_status(status_t status) {
	FILE* f;
	assert(status);
	f = fopen(SWSUSP_PROC, "r");
	if (f) {
		switch (fscanf(f, "%d %d %d %d %d %d", &status[0], &status[1], &status[2], &status[3],
					&status[4], &status[5])) {
			case 4:
				status[4] = status[2]; /* max page size is in 4 for debug, else 2 */
				status[5] = status[3]; /* max async io ops in 5 for debug, else 3 */
				status[2] = 0;
				status[3] = 0;
				has_debug = 0;
				break;
			case 6:
				has_debug = 1;
				break;
			default:
				BAIL("error reading input");
		}
		fclose(f);
	} else
		BAIL("Could not open "SWSUSP_PROC" for reading.");
}

void proc_write_status(status_t status) {
	FILE* f;
	need_root();
	assert(status);
	f = fopen(SWSUSP_PROC, "w");
	if (f) {
		write_status(f, status);
		fclose(f);
		/* see if they want us to suspend */
		if (status[0]&1) swsusp();
	} else
		BAIL("Could not open "SWSUSP_PROC" for writing.");
}

void read_through_proc(FILE* f, status_t status) {
	char s[1024];
	FILE* proc;
	assert(f && status);
	if (fgets(s, 1024, f)) {
		if ((proc = fopen(SWSUSP_PROC, "w"))) {
			fprintf(proc, "%s", s);
			fclose(proc);
		}
	}
	/* and update our status to reflect proc */
	proc_read_status(status);
	if (status[0]&1) swsusp();
}

void print_status(status_t status) {
	assert(status);
	printf("kswsusp version %s\n", swsusp_version_string);
	printf("Status registers read "); write_status(stdout, status);
	printf(" Reboot after suspend: %s\n", get_reboot(status)?"Yes":"No");
	printf(" Show progress screen: %s\n", get_progress(status)?"Yes":"No");
	printf("  Pause between steps: %s\n", get_pause(status)?"Yes":"No");
	printf("   Asynchronous reads: %s\n", get_async_reads(status)?"Enabled":"Disabled");
	printf("  Asynchronous writes: %s\n", get_async_writes(status)?"Enabled":"Disabled");
	printf("      Write pageset 2: %s\n", get_pageset2(status)?"Yes":"No");
	printf("       Log everything: %s\n", get_logall(status)?"Yes":"No");
	printf("          Compression: %s\n", get_compress(status)?"Not disabled":"Disabled");

	printf("     Image size limit: ");
	if (get_imagelimit(status))
		printf("%d MB\n", get_imagelimit(status));
	else
		printf("unlimited\n");

	printf("    Max. async IO ops: %d\n", get_maxasyncio(status));

	printf(" Debug enabled kernel: %s\n", has_debug?"Yes":"No");

	if (has_debug) {
		printf("    Slow down suspend: %s\n", get_slow(status)?"Yes":"No");
		printf("  Beep between stages: %s\n", get_beep(status)?"Yes":"No");
		printf("      Debugging Level: %d (%s)\n", get_debug(status),
			debug_words(get_debug(status)));
	}
}

void show_version() {
	WARN(0, "userspace version "PROG_VERSION);
	WARN(0, "kswsusp version %s (detected as 0x%x)", swsusp_version_string,
			swsusp_version);
}

/* returns 0 if preparation failed, 1 otherwise */
int prepare_to_swsusp() {
	sync();
	return 1;
}

void console_log() {
	FILE* con = fopen("/dev/console", "w");
	fprintf(con, PROG_NAME" called. suspending with ");
	write_status(con, curr_status);
	fclose(con);
}

int swsusp() {
	need_root();
	console_log();
	WARN(1, "activating swsusp");
	switch (fork()) {
		case 0:
			reboot(BMAGIC_SWSUSP);
			break;
		case -1:
			BAIL("failed for fork: %s", strerror(errno));
			break;
		default:
			wait(0);
			sleep(1);
			proc_read_status(curr_status);
			if (curr_status[0] == 0) return SWSUSP_SUCCESS;
			if (curr_status[0] & 0x2) return SWSUSP_ABORT_USER;
			if (curr_status[0] & 0x4) return SWSUSP_ABORT_NOSWAP;
			if (curr_status[0] & 0x8) return SWSUSP_ABORT_LITTLESWAP;
			if (curr_status[0] & 0x10) return SWSUSP_ABORT_FREEZE_FAIL;
	}
	return -1;
}

void create_fifo() {
	/* root is already guaranteed */
	unlink(SWSUSP_FIFO);
	if (mkfifo(SWSUSP_FIFO, 0200))
		BAIL("Could not create "SWSUSP_FIFO": %s", strerror(errno));
}

void reopen_fifo() {
	if (ctl_fifo) fclose(ctl_fifo);
	if (!(ctl_fifo = fopen(SWSUSP_FIFO, "r")))
		BAIL("Could not open "SWSUSP_FIFO": %s", strerror(errno));
}

void daemonize() {
	need_root();
	create_fifo();
	if (fork()) {
		/* we are parent. exit without touching buffers */
		_exit(0);
	}
	/* we are child. lets get cosy. */
	setsid();
	reopen_fifo();

	/* from here on, we're muted :( */
	if (!verbosity) {
		close(0);
		close(1);
		close(2);
	}

	while (1) {
		fd_set rfds;
		int ret;
		int fd = fileno(ctl_fifo);
		FD_ZERO(&rfds);
		FD_SET(fd, &rfds);

		ret = select(fd+1, &rfds, NULL, NULL, NULL);
		switch (ret) {
			case -1: /* this is bad :( */
				exit(-1);
			case 0: /* whaa? we said don't time out! */
				exit(-2);
			default:
				if (FD_ISSET(fd, &rfds)) {
					read_through_proc(ctl_fifo, curr_status);
					reopen_fifo();
				} else {
					/* something equally quirky has happened. move on with life. */
				}
		}
	}
	/* never returns */
}

void usage(int all) {
	fprintf(stderr, "Usage: %s [options]\n", called_name);
	fprintf(stderr, "Activates software suspend and controls parameters.\n");
	fprintf(stderr, " -g, --go             activate software suspend\n");
	fprintf(stderr, " -s, --status         prints the current status of swsusp\n");
	fprintf(stderr, " -z, --sane           set the swsusp parameters back to sensible values\n");
	fprintf(stderr, " -D, --daemonise      create /dev/swsuspctl and daemonize to answer its requests\n");
	fprintf(stderr, " -h, --help           gives a brief overview of usage\n");
	if (!all) {
	fprintf(stderr, " -H, --more-help      gives all the options\n");
	} else {
	fprintf(stderr, " -S, --save=[FILE]    save the current parameters to FILE\n");
	fprintf(stderr, "                      if FILE is not specified, writes to stdout\n");
	fprintf(stderr, " -R, --restore=[FILE] reads parameters from FILE\n");
	fprintf(stderr, "                      if FILE is not specified, reads from stdin\n");
	fprintf(stderr, " -f, --forget         only use these flags for this suspend\n");
	fprintf(stderr, " -v, --verbose        increase verbosity for this program\n");
	fprintf(stderr, "                      (not the suspend process itself - see --debug)\n");
	fprintf(stderr, " -V, --version        print swsusp version and kernel swsusp version\n");
	fprintf(stderr, " -r, --reboot=[0|1]   disables/enables reboot after suspend\n");
	fprintf(stderr, " -P, --progress=[0|1] disables/enables the progress display\n");
	fprintf(stderr, " -p, --pause=[0|1]    disables/enables pausing between each step (for debugging)\n");
	fprintf(stderr, " --async-writes=[0|1] disables/enables asynchronous reads on suspend\n");
	fprintf(stderr, " --async-reads=[0|1]  disables/enables asynchronous reads on resume\n");
	fprintf(stderr, " --log-all=[0|1]      logs absolutely everything to syslog\n");
	fprintf(stderr, " --pageset2=[0|1]     if 1, disables the second pageset\n");
	fprintf(stderr, " --image-limit=SIZE   specify the maximum image size that will be written in MB\n");
	fprintf(stderr, " --max-async-io=NUM   specify the maximum number of asynchronous I/O operations\n");
	fprintf(stderr, " -c, --compression=[0|1]\n");
	fprintf(stderr, "                      disables/enables compression of the suspend image\n");
	fprintf(stderr, "                      (requires CONFIG_SOFTWARE_SUSPEND_COMPRESSION=y in kernel)\n");

	if (has_debug) {
	fprintf(stderr, " -b, --beep=[0|1]     disables/enables beeps between each stage\n");
	fprintf(stderr, " -l, --slow=[0|1]     disables/enables delays between each stage\n");
	fprintf(stderr, " -d, --debug=LEVEL    controls the debugging output of swsusp\n");
	fprintf(stderr, "                      where LEVEL is 0..4 (none to highest)\n");
	fprintf(stderr, "                      (if debug is on, progress screen is turned off)\n");
	}
	}

	fprintf(stderr, "\nReport bugs to <b-swsusp@blackham.com.au>\n");
}

int processbool(char* arg, char* option) {
	if (arg) {
		if (arg[1] || arg[0] < '0' || arg[0] > '1') 
			BAIL("Invalid parameter to --%s (%s)", option, arg)
		else 
			return (arg[0]-'0');
	}
	return 1;
}

int processint(char* arg, char* option) {
	if (arg) {
		char* end;
		int ret = strtol(arg, &end, 10);
		if (*end != '\0')
			BAIL("Invalid parameter to --%s (%s)", option, arg)
		else
			return ret;
	}
	return 0;
}

int main(int argc, char**argv) {
	int c;
	int forget = 0;
	int action = 0;
	int paramchanged = 0;
	int ret = 0;
	char* savefile = NULL;
	FILE* f;
	static struct option long_opts[] = {
		{"save", 2, 0, 'S'},
		{"restore", 2, 0, 'R'}, 
		{"go", 0, 0, 'g'},
		{"sane", 0, 0, 'z'},
		{"status", 0, 0, 's'},
		{"daemonise", 0, 0, 'D'},
		{"reboot", 2, 0, 'r'},
		{"compression", 2, 0, 'c'},
		{"progress", 2, 0, 'P'},
		{"pause", 2, 0, 'p'},
		{"beep", 2, 0, 'b'},
		{"slow", 2, 0, 'l'},
		{"debug", 1, 0, 'd'},
		{"async-writes", 2, 0, OPT_ASYNC_WRITES},
		{"async-reads", 2, 0, OPT_ASYNC_READS},
		{"pageset2", 2, 0, OPT_PAGESET2},
		{"log-all", 2, 0, OPT_LOGALL},
		{"image-limit", 1, 0, OPT_IMAGELIMIT},
		{"max-async-io", 1, 0, OPT_MAXASYNCIO},
		{"forget", 0, 0, 'f'},
		{"help", 0, 0, 'h'},
		{"more-help", 0, 0, 'H'},
		{"verbose", 0, 0, 'v'},
		{"version", 0, 0, 'V'},
		{0, 0, 0, 0},
	};
	static char short_opts[] = "S::R::gzsDr::c::P::p::b::l::d:fhHvV";

	called_name = argv[0];
	sanity_checks();
	find_swsusp_version();
	if (swsusp_version != SWSUSP_VERSION) {
		fprintf(stderr, "WARNING: This swsusp program was requires "SWSUSP_VERSION_NAME" be compiled into the\n");
		fprintf(stderr, "kernel. You appear to have version %s.\n", swsusp_version_string);
		fprintf(stderr, "Results may be unpredictable. You should download the correct version for your\n");
		fprintf(stderr, "kernel.\n\n");
	}
	proc_read_status(curr_status);
	memcpy(old_status, curr_status, sizeof(status_t));

	while (1) {
		int optindex = 0;
		if ((c = getopt_long(argc, argv, short_opts, long_opts, &optindex))==-1)
			break;
		switch(c) {
			case 's': 
				if (action)
					BAIL("Incompatible actions specified!")
				else action = ACTION_STATUS;
				break;
			case 'S':
				if (action)
					BAIL("Incompatible actions specified!")
				else action = ACTION_SAVE;
				savefile = optarg;
				break;
			case 'R':
				if (action)
					BAIL("Incompatible actions specified!")
				else action = ACTION_RESTORE;
				savefile = optarg;
				break;
			case 'g':
				if (action)
					BAIL("Incompatible actions specified!")
				else action = ACTION_GO;
				break;
			case 'z':
				paramchanged = 1;
				curr_status[0] = curr_status[2] = curr_status[3] = curr_status[4] = 0;
				curr_status[1] = 2;
				curr_status[5] = 64;
				break;
			case 'D':
				if (action)
					BAIL("Incompatible actions specified!")
				else action = ACTION_DAEMONISE;
				break;
			case 'r':
				paramchanged = 1;
				set_reboot(curr_status, processbool(optarg, "reboot"));
				break;
			case 'c':
				paramchanged=1;
				set_compress(curr_status, processbool(optarg, "compression"));
				break;
			case 'P':
				paramchanged=1;
				set_progress(curr_status, processbool(optarg, "progress"));
				break;
			case 'p':
				paramchanged=1;
				set_pause(curr_status, processbool(optarg, "pause"));
				break;
			case 'b':
				paramchanged=1;
				set_beep(curr_status, processbool(optarg, "beep"));
				break;
			case 'l':
				paramchanged=1;
				set_slow(curr_status, processbool(optarg, "slow"));
				break;
			case 'd':
				paramchanged=1;
				if (!optarg || optarg[1] || optarg[0] < '0' || optarg[0] > '4')
					BAIL("Invalid debug level specified (%s)", optarg);
				set_debug(curr_status, optarg[0]-'0'); /* FIXME */
				/* turn progress off if debug on, and vice versa */
				set_progress(curr_status, (optarg[0]-'0')?0:1);
				break;
			case OPT_ASYNC_WRITES:
				paramchanged=1;
				set_async_writes(curr_status, processbool(optarg, "async-writes"));
				break;
			case OPT_ASYNC_READS:
				paramchanged=1;
				set_async_reads(curr_status, processbool(optarg, "async-reads"));
				break;
			case OPT_LOGALL:
				paramchanged=1;
				set_logall(curr_status, processbool(optarg, "log-all"));
				break;
			case OPT_PAGESET2:
				paramchanged=1;
				set_pageset2(curr_status, processbool(optarg, "pageset2"));
				break;
			case OPT_IMAGELIMIT:
				paramchanged=1;
				set_imagelimit(curr_status, processint(optarg, "image-limit"));
				break;
			case OPT_MAXASYNCIO:
				paramchanged=1;
				set_maxasyncio(curr_status, processint(optarg, "max-async-io"));
				break;
			case 'f': forget = 1; break;
			case 'h': usage(0); exit(1); break;
			case 'H': usage(1); exit(1); break;
			case 'v': verbosity++; break;
			case 'V': show_version(); exit(1);
			case 0:
			case '?': 
			default:  /* invalid long options. getopt writes the error for us */
				fprintf(stderr, "Try `%s --help' for more information.\n", called_name);
				exit(1);
		}
	}

	if (optind < argc) {
		usage(0);
		WARN(0, "Excess parameters!");
		exit(1);
	}

	if (!paramchanged && !action) {
		usage(0);
		WARN(0, "No action specified (pass -g to activate)");
		exit(1);
	}

	/* changes to the settings required from params if anything changed */
	/* if (paramchanged && !intcmp(old_status, curr_status, 5)) { */
	if (paramchanged) {
		proc_write_status(curr_status);
		if (!action) action = ACTION_PARAMS;
	}

	switch (action) {
		case ACTION_STATUS:
			print_status(curr_status);
			break;
		case ACTION_PARAMS:
			WARN(0, "Settings changed.");
			break;
		case ACTION_GO:
			if (prepare_to_swsusp())
				ret = swsusp();
			else
				BAIL("Couldn't prepare to swsusp!");
			WARN(1, "Reverting settings");
			if (forget) proc_write_status(old_status); /* put back old status */
			switch (ret) {
				case SWSUSP_SUCCESS:
					break;
				case SWSUSP_ABORT_USER:
					WARN(0, "Suspend aborted by user!");
					break;
				case SWSUSP_ABORT_NOSWAP:
					WARN(0, "Suspend aborted - no swap available! (need to mkswap & swapon?)");
					break;
				case SWSUSP_ABORT_LITTLESWAP:
					WARN(0, "Suspend aborted - not enough free swap available.");
					break;
				case SWSUSP_ABORT_FREEZE_FAIL:
					WARN(0, "Suspend aborted - failed to freeze all processes.");
					break;
			}
			exit(ret);
		case ACTION_SAVE:
			f = savefile?fopen(savefile, "w"):stdout;
			if (f) {
				write_status(f, curr_status);
				fclose(f);
			} else BAIL("Could not open file for writing (%s)", savefile);
			break;
		case ACTION_RESTORE:
			f = savefile?fopen(savefile, "r"):stdin;
			if (f) {
				read_through_proc(f, curr_status);
				fclose(f);
			} else BAIL("Could not open file for reading (%s)", savefile);
			break;
		case ACTION_DAEMONISE:
			daemonize();
			break;
		default:
			if (!paramchanged) usage(0); /* this really should never happen anyway */
			exit(1);
	}

	return 0;
}

/* vi:set ts=4: */
