/*
 * zgetdump - Tool for copying and converting System z dumps
 *
 * Main functions
 *
 * Copyright IBM Corp. 2001, 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 <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/fs.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mtio.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include "lib/zt_common.h"
#include "opts.h"
#include "zgetdump.h"
#include "dt.h"
#include "dfi.h"
#include "dfo.h"
#include "output.h"
#include "zfuse.h"

/*
 * Globals
 */
struct zgetdump_globals g;

/*
 * Signal handler for exiting zgetdump (the atexit handler will do the work)
 */
static void sig_exit(int sig)
{
	(void) sig;

	STDERR("\n"); /* E.g. to get newline after '^C' */
	ERR_EXIT("Got signal %i, exiting...", sig);
}

/*
 * Install signal handler
 */
static void sig_handler_init(void)
{
	struct sigaction sigact = { 0 };

	/* Ignore signals SIGUSR1 and SIGUSR2 */
	if (sigemptyset(&sigact.sa_mask) < 0)
		goto fail;
	sigact.sa_handler = SIG_IGN;
	if (sigaction(SIGUSR1, &sigact, NULL) < 0)
		goto fail;
	if (sigaction(SIGUSR2, &sigact, NULL) < 0)
		goto fail;

	/* Exit on SIGINT, SIGTERM, SIGHUP, ... */
	if (sigemptyset(&sigact.sa_mask) < 0)
		goto fail;
	sigact.sa_handler = sig_exit;
	if (sigaction(SIGINT, &sigact, NULL) < 0)
		goto fail;
	if (sigaction(SIGTERM, &sigact, NULL) < 0)
		goto fail;
	if (sigaction(SIGHUP, &sigact, NULL) < 0)
		goto fail;
	if (sigaction(SIGQUIT, &sigact, NULL) < 0)
		goto fail;
	if (sigaction(SIGALRM, &sigact, NULL) < 0)
		goto fail;
	if (sigaction(SIGPIPE, &sigact, NULL) < 0)
		goto fail;
	return;
fail:
	ERR_EXIT_ERRNO("Could not initialize signal handler");
}

/*
 * Check if dump contains kdump dump and production system dump
 */
static void kdump_select_check(void)
{
	static char *msg =
		"The dump contains \"kdump\" and \"production system\"\n"
		"          Access \"production system\" with \"-s prod\"\n"
		"          Access \"kdump\" with \"-s kdump\"\n"
		"          Access the complete dump with \"-s all\"\n"
		"          Send both dumps to your service organization";

	if (g.opts.select_specified)
		return;
	if (!dfi_kdump_base())
		return;
	ERR_EXIT("%s", msg);
}

static int dfi_init_error(int rc)
{
	if (rc == -ENOKEY) {
		if (g.opts.key_path)
			ERR("Cannot read key: '%s'", g.opts.key_path);
		else
			ERR("No key for dump decryption provided.");
		opts_print_usage(g.opts.prog_name);
	} else {
		STDERR("Dump cannot be processed (is not complete)\n");
	}
	return 1;
}

/*
 * Run "--umount" action
 */
static int do_umount(void)
{
	zfuse_umount();
	return 0;
}

/*
 * Run "--device" action
 */
static int do_device_info(void)
{
	dt_init();
	dt_info_print();
	return 0;
}

/*
 * Run "--info" action
 */
static int do_dump_info(void)
{
	int rc;

	rc = dfi_init();
	if (rc != 0) {
		dfi_info_print();
		STDERR("\n");
		return dfi_init_error(rc);
	}
	kdump_select_check();
	dfi_info_print();
	dfi_exit();
	return 0;
}

/*
 * Run "--mount" action
 */
static int do_mount(void)
{
	int rc;

	rc = dfi_init();
	if (rc != 0)
		return dfi_init_error(rc);
	dfo_init();
	kdump_select_check();
	rc = zfuse_mount_dump();
	dfi_exit();
	return rc;
}

static FILE *open_file_for_writing(const char *output)
{
	FILE *stream;
	int fd;

	if (output) {
		fd = open(output,
			  O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC,
			  0600);
		if (fd == -1)
			ERR_EXIT_ERRNO("Could not open '%s' exclusively", output);
	} else {
		fd = dup(STDOUT_FILENO);
		if (fd == -1)
			ERR_EXIT_ERRNO("Could not dup() stdout");
	}

	stream = fdopen(fd, "w");
	if (!stream)
		ERR_EXIT_ERRNO("Could not fdopen()");
	fd = -1;

	return stream;
}

/*
 * Run "copy to output" action
 */
static int do_copy(const char *output)
{
	FILE *stream;
	int rc;

	rc = dfi_init();
	if (rc != 0)
		return dfi_init_error(rc);
	dfo_init();
	kdump_select_check();
	stream = open_file_for_writing(output);
	rc = write_dump(stream);
	fclose(stream);
	dfi_exit();
	return rc;
}

/*
 * The zgetdump main function
 */
int main(int argc, char *argv[])
{
	sig_handler_init();
	opts_parse(argc, argv, &g.opts);

	if (dfo_set(g.opts.fmt) != 0)
		ERR_EXIT("Invalid target format \"%s\" specified", g.opts.fmt);

	switch (g.opts.action) {
	case ZG_ACTION_COPY:
		return do_copy(g.opts.output_path);
	case ZG_ACTION_DUMP_INFO:
		return do_dump_info();
	case ZG_ACTION_DEVICE_INFO:
		return do_device_info();
	case ZG_ACTION_MOUNT:
		return do_mount();
	case ZG_ACTION_UMOUNT:
		return do_umount();
	}
	ABORT("Invalid action: %i", g.opts.action);
}
