/*
 * Copyright (C) 2002-2004 Sistina Software, Inc. All rights reserved.
 * Copyright (C) 2004 Red Hat, Inc. All rights reserved.
 *
 * This file is part of LVM2.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU General Public License v.2.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/* Routines dealing with the System LV */

#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/utsname.h>
#include <syslog.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <mntent.h>

#include "libdlm.h"
#include "log.h"
#include "list.h"
#include "locking.h"
#include "system-lv.h"
#include "clvmd-comms.h"
#ifdef HAVE_CCS
#include "ccs.h"
#endif

#define SYSTEM_LV_FILESYSTEM "ext2"
#define SYSTEM_LV_MOUNTPOINT "/tmp/.clvmd-XXXXXX"

extern char *config_filename(void);

static char system_lv_name[PATH_MAX] = { '\0' };
static char mount_point[PATH_MAX] = { '\0' };
static int mounted = 0;
static int mounted_rw = 0;
static int lockid;
static const char *lock_name = "CLVM_SYSTEM_LV";

/* Look in /proc/mounts or (as a last resort) /etc/mtab to
   see if the system-lv is mounted. If it is mounted and we
   think it's not then abort because we don't have the right
   lock status and we don't know what other processes are doing with it.

   Returns 1 for mounted, 0 for not mounted so it matches the condition
   of the "mounted" static variable above.
*/
static int is_really_mounted(void)
{
	FILE *mountfile;
	struct mntent *ment;

	mountfile = setmntent("/proc/mounts", "r");
	if (!mountfile) {
		mountfile = setmntent("/etc/mtab", "r");
		if (!mountfile) {
			log_error("Unable to open /proc/mounts or /etc/mtab");
			return -1;
		}
	}

	/* Look for system LV name in the file */
	do {
		ment = getmntent(mountfile);
		if (ment) {
			if (strcmp(ment->mnt_fsname, system_lv_name) == 0) {
				endmntent(mountfile);
				return 1;
			}
		}
	}
	while (ment);

	endmntent(mountfile);
	return 0;
}

/* Get the system LV name from the config file */
static int find_system_lv(void)
{
	if (system_lv_name[0] == '\0') {
#ifdef HAVE_CCS
		int error;
		ccs_node_t *ctree;

		/* Read the cluster config file */
		/* Open the config file */
		error = open_ccs_file(&ctree, "clvm.ccs");
		if (error) {
			perror("reading config file");
			return -1;
		}

		strcpy(system_lv_name, find_ccs_str(ctree,
						    "cluster/systemlv", '/',
						    "/dev/vg/system_lv"));

		/* Finished with config file */
		close_ccs_file(ctree);
#else
		if (getenv("CLVMD_SYSTEM_LV"))
			strcpy(system_lv_name, getenv("CLVMD_SYSTEM_LV"));
		else
			return -1;
#endif
	}

	/* See if it has been mounted outside our control */
	if (is_really_mounted() != mounted) {
		log_error
		    ("The system LV state has been mounted/umounted outside the control of clvmd\n"
		     "it cannot not be used for cluster communications until this is fixed.\n");
		return -1;
	}
	return 0;
}

/* No prizes */
int system_lv_umount(void)
{
	if (!mounted)
		return 0;

	if (umount(mount_point) < 0) {
		log_error("umount of system LV (%s) failed: %m\n",
			  system_lv_name);
		return -1;
	}

	sync_unlock(lock_name, lockid);
	mounted = 0;

	/* Remove the mount point */
	rmdir(mount_point);

	return 0;
}

int system_lv_mount(int readwrite)
{
	int status;
	int saved_errno;
	int fd;

	if (find_system_lv()) {
		errno = EBUSY;
		return -1;
	}

	/* Is it already mounted suitably? */
	if (mounted) {
		if (!readwrite || (readwrite && mounted_rw)) {
			return 0;
		} else {
			/* Mounted RO and we need RW */
			if (system_lv_umount() < 0)
				return -1;
		}
	}

	/* Randomize the mount point */
	strcpy(mount_point, SYSTEM_LV_MOUNTPOINT);
	fd = mkstemp(mount_point);
	if (fd < 0) {
		log_error("mkstemp for system LV mount point failed: %m\n");
		return -1;
	}

	/* Race condition here but there's no mkstemp for directories */
	close(fd);
	unlink(mount_point);
	mkdir(mount_point, 0600);

	/* Make sure we have a system-lv lock */
	status =
	    sync_lock(lock_name, (readwrite) ? LKM_EXMODE : LKM_CRMODE, 0,
		      &lockid);
	if (status < 0)
		return -1;

	/* Mount it */
	if (mount(system_lv_name, mount_point, SYSTEM_LV_FILESYSTEM,
		  MS_MGC_VAL | MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_SYNCHRONOUS
		  | (readwrite ? 0 : MS_RDONLY), NULL) < 0) {
		/* mount(2) returns EINVAL if the volume has no FS on it. So, if we want to
		   write to it we try to make a filesystem in it and retry the mount */
		if (errno == EINVAL && readwrite) {
			char cmd[256];

			log_error("Attempting mkfs on system LV device %s\n",
				  system_lv_name);
			snprintf(cmd, sizeof(cmd), "/sbin/mkfs -t %s %s",
				 SYSTEM_LV_FILESYSTEM, system_lv_name);
			system(cmd);

			if (mount
			    (system_lv_name, mount_point, SYSTEM_LV_FILESYSTEM,
			     MS_MGC_VAL | MS_NOSUID | MS_NODEV | MS_NOEXEC |
			     MS_SYNCHRONOUS | (readwrite ? 0 : MS_RDONLY),
			     NULL) == 0)
				goto mounted;
		}

		saved_errno = errno;
		log_error("mount of system LV (%s, %s, %s) failed: %m\n",
			  system_lv_name, mount_point, SYSTEM_LV_FILESYSTEM);
		sync_unlock(lock_name, lockid);
		errno = saved_errno;
		return -1;
	}

      mounted:
/* Set the internal flags */
	mounted = 1;
	mounted_rw = readwrite;

	return 0;
}

/* Erase *all* files in the root directory of the system LV.
   This *MUST* be called with an appropriate lock held!
   The LV is left mounted RW because it is assumed that the
   caller wants to write something here after clearing some space */
int system_lv_eraseall(void)
{
	DIR *dir;
	struct dirent *ent;
	char fname[PATH_MAX];

	/* Must be mounted R/W */
	system_lv_mount(1);

	dir = opendir(mount_point);
	if (!dir)
		return -1;

	while ((ent = readdir(dir))) {
		struct stat st;
		snprintf(fname, sizeof(fname), "%s/%s", mount_point,
			 ent->d_name);

		if (stat(fname, &st)) {
			if (S_ISREG(st.st_mode))
				unlink(fname);
		}
	}
	closedir(dir);
	return 0;
}

/* This is a "high-level" routine - it mounts the system LV, writes
   the data into a file named after this node and then umounts the LV
   again */
int system_lv_write_data(char *data, ssize_t len)
{
	struct utsname nodeinfo;
	char fname[PATH_MAX];
	int outfile;
	ssize_t thiswrite;
	ssize_t written;

	if (system_lv_mount(1))
		return -1;

	/* Build the file name we are goingto use. */
	uname(&nodeinfo);
	snprintf(fname, sizeof(fname), "%s/%s", mount_point, nodeinfo.nodename);

	/* Open the file for output */
	outfile = open(fname, O_RDWR | O_CREAT | O_TRUNC, 0600);
	if (outfile < 0) {
		int saved_errno = errno;
		system_lv_umount();
		errno = saved_errno;
		return -1;
	}

	written = 0;
	do {
		thiswrite = write(outfile, data + written, len - written);
		if (thiswrite > 0)
			written += thiswrite;

	} while (written < len && thiswrite > 0);

	close(outfile);

	system_lv_umount();
	return (thiswrite < 0) ? -1 : 0;
}

/* This is a "high-level" routine - it mounts the system LV, reads
   the data from a named file and then umounts the LV
   again */
int system_lv_read_data(char *fname_base, char *data, ssize_t *len)
{
	char fname[PATH_MAX];
	int outfile;
	struct stat st;
	ssize_t filesize;
	ssize_t thisread;
	ssize_t readbytes;

	if (system_lv_mount(0))
		return -1;

	/* Build the file name we are going to use. */
	snprintf(fname, sizeof(fname), "%s/%s", mount_point, fname_base);

	/* Get the file size and stuff. Actually we only need the file size but
	   this will also check that the file exists */
	if (stat(fname, &st) < 0) {
		int saved_errno = errno;

		log_error("stat of file %s on system LV failed: %m\n", fname);
		system_lv_umount();
		errno = saved_errno;
		return -1;
	}
	filesize = st.st_size;

	outfile = open(fname, O_RDONLY);
	if (outfile < 0) {
		int saved_errno = errno;

		log_error("open of file %s on system LV failed: %m\n", fname);
		system_lv_umount();
		errno = saved_errno;
		return -1;
	}

	readbytes = 0;
	do {
		thisread =
		    read(outfile, data + readbytes, filesize - readbytes);
		if (thisread > 0)
			readbytes += thisread;

	} while (readbytes < filesize && thisread > 0);

	close(outfile);

	system_lv_umount();

	*len = readbytes;
	return (thisread < 0) ? -1 : 0;
}