/*
 * ipl_tools - Linux for System z reipl and shutdown tools
 *
 * Command: chshut
 *
 * Copyright IBM Corp. 2008, 2017
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */

#include "ipl_tools.h"

static const char *const usage_chshut =
"Usage: %s TRIGGER ACTION [COMMAND] [OPTIONS]\n"
"\n"
"Change the shutdown actions for Linux on System z.\n"
"\n"
"TRIGGER specifies when the action is performed:\n"
"  halt      System has been shut down (e.g. shutdown -h -H now)\n"
"  poff      System has been shut down for power off (e.g. shutdown -h -P now)\n"
"  reboot    System has been shut down for reboot (e.g. shutdown -r)\n"
"  Note: Depending on the distribution, \"halt\" might be mapped to \"poff\".\n"
"\n"
"ACTION specifies the action to be performed:\n"
"  ipl       IPL with previous settings\n"
"  reipl     IPL with re-IPL settings (see chreipl)\n"
"  stop      Stop all CPUs\n"
"  vmcmd     Run z/VM CP command defined by COMMAND\n"
"\n"
"COMMAND defines the z/VM CP command to issue.\n"
"\n"
"OPTIONS:\n"
"  -h, --help        Print this help, then exit\n"
"  -v, --version     Print version information, then exit\n";

static void __noreturn print_usage_chshut_exit(void)
{
	printf(usage_chshut, g.prog_name);
	exit(0);
}

static void parse_chshut_options(int argc, char *argv[])
{
	int opt, idx;
	const struct option long_opts[] = {
		{ "help",	 no_argument,		NULL, 'h' },
		{ "version",	 no_argument,		NULL, 'v' },
		{ NULL,		 0,			NULL,  0  }
	};
	while ((opt = getopt_long(argc, argv, "hv", long_opts, &idx)) != -1) {
		switch (opt) {
		case 'h':
			print_usage_chshut_exit();
		case 'v':
			print_version_exit();
		default:
			print_help_hint_exit();
		}
	}
	if (!is_root())
		ERR_EXIT("You must be root to perform this operation");
}

static struct shutdown_trigger *shutdown_trigger_get(const char *trigger)
{
	int i;

	for (i = 0; shutdown_trigger_vec[i]; i++) {
		if (strcmp(trigger, shutdown_trigger_vec[i]->name) != 0)
			continue;
		if (shutdown_trigger_vec[i]->available)
			return shutdown_trigger_vec[i];
		ERR_EXIT("Shutdown trigger \"%s\" is not available on "
			 "your system", trigger);
	}
	ERR_EXIT("Unknown shutdown trigger \"%s\" specified", trigger);
}

static struct shutdown_action *shutdown_action_get(const char *action)
{
	int i;

	for (i = 0; shutdown_action_vec[i]; i++) {
		if (strcmp(action, shutdown_action_vec[i]->name) == 0)
			return shutdown_action_vec[i];
	}
	ERR_EXIT("Unknown shutdown action \"%s\" specified", action);
}

/*
 * Multiple CP commands can be specified via "vmcmd XY1 vmcmd XY2 ..."
 */
static void vmcmd_set(struct shutdown_trigger *st, int argc, char *argv[])
{
	char vmcmd[1024], path[PATH_MAX];
	int first = 1, i;
	int vmcmd_length = 0;

	if (is_lpar())
		ERR_EXIT("vmcmd works only under z/VM");
	memset(vmcmd, 0, sizeof(vmcmd));
	for (i = 2; i < argc; i++) {
		if (strcmp(argv[i], "vmcmd") != 0)
			ERR_EXIT("Invalid vmcmd command specification");
		if (i == argc - 1)
			ERR_EXIT("vmcmd needs an additional argument");
		if (!first) {
			strcat(vmcmd, "\n");
			vmcmd_length++;
		} else {
			first = 0;
		}
		vmcmd_length += strlen(argv[i + 1]);
		if (vmcmd_length >= 127)
			ERR_EXIT("The vmcmd command must not exceed 127 "
				 "characters");

		strcat(vmcmd, argv[i + 1]);
		i++;
	}

	sprintf(path, "vmcmd/%s", st->name_sysfs);
	write_str(vmcmd, path);
}

void cmd_chshut(int argc, char *argv[])
{
	struct shutdown_trigger *st;
	struct shutdown_action *sa;
	char path[PATH_MAX];

	parse_chshut_options(argc, argv);

	if (argc < 2) {
		ERR("No trigger specified");
		print_help_hint_exit();
	}
	shutdown_init();
	st = shutdown_trigger_get(argv[1]);
	if (st == &shutdown_trigger_panic ||
	    st == &shutdown_trigger_restart)
		ERR_EXIT("Please use \"service dumpconf\" for "
			 "configuring the %s trigger",
			 st->name);
	if (argc < 3) {
		ERR("No action specified");
		print_help_hint_exit();
	}
	sa = shutdown_action_get(argv[2]);

	if (sa == &shutdown_action_vmcmd) {
		vmcmd_set(st, argc, argv);
	} else if (argc != 3) {
		ERR("Too many parameters specified");
		print_help_hint_exit();
	}
	sprintf(path, "shutdown_actions/%s", st->name_sysfs);
	if (write_str_errno(argv[2], path))
		ERR_EXIT_ERRNO("Could not set \"%s\"", path);
}
