
/*
   raidlib.c : Multiple Devices tools for Linux
		Copyright (C) 1997, 1998, 1999
		Erik Troan <ewt@redhat.com>,
		Ingo Molnar <mingo@redhat.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, or (at your option)
   any later version.
   
   You should have received a copy of the GNU General Public License
   (for example /usr/src/linux/COPYING); if not, write to the Free
   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  
*/

#include "common.h"
#include "parser.h"
#include "raidlib.h"
#include "popt.h"
#include "version.h"

#include <asm/page.h>
#include <string.h>
#include <sys/sysmacros.h>

int fstat(int,struct stat *);
int stat(const char*,struct stat *);

struct md_version md_ver;

#define OLD_MDTOOLS ((md_ver.major == 0) && (md_ver.minor < 0.50))

static int save_errno = 0;

md_cfg_entry_t *cfg_head = NULL, *cfg = NULL;
int do_quiet_flag = 0;

int open_or_die (char *file)
{
	int fd;
  
	fd = open (file, O_RDWR);
	if (fd == -1) {
		perror (file);
		exit (EXIT_FAILURE);
	}

	return fd;
}

/*
 * converts a size in bytes to 'factor' metrics, which is a
 * kernel-internal way of dealing with chunk sizes and stuff.
 * It's the number of pages within the given number.
 */
static int s2f (int bytes)
{
	int factor, c, i;
	int kbytes;

	if (bytes % 1024) {
		printf("chunk_size must be an integral number of k\n");
		return 0;
	}
	kbytes = bytes >> 10;
	
	factor = kbytes >> (PAGE_SHIFT - 10);
	for (i = 1, c = 0; i < kbytes; i *= 2)
	if (factor & i) c++;
	
	if ((kbytes*MD_BLK_SIZ) % PAGE_SIZE || c != 1) {
		printf("Cannot handle %dk chunks. Defaulting to %dk\n",
				kbytes, 1 << (PAGE_SHIFT-10));
		return (0);
	}

	for (i = 0; !(factor & (1 << i)); i++);
	return i;
}

static int do_mdrun (int fd, char *dev, struct md_param *param) {
	int rc;
	int pers;

	/* Old versions of md (< 0.50) used this instead. Thank Ingo
	   for fixing it. */
	if (OLD_MDTOOLS) {
		pers = param->personality << MD_PERSONALITY_SHIFT;
		if ((param->personality == LINEAR || param->personality == STRIPED)
		&& param->chunk_size)
			pers |= s2f(param->chunk_size);
		rc = ioctl (fd, START_MD, (unsigned long)pers);
		return 0;
	}

	rc = ioctl (fd, RUN_ARRAY, (unsigned long)param);
	if (rc) {
		save_errno = errno;
		switch (save_errno) {
			case EBUSY:
				printf("%s: already running\n",dev);
				break;
			default:
				perror (dev);
		}
		errno = save_errno;
		return 1;
	}
	return 0;
}

static int do_mdstart (int fd, char *dev, dev_t rdev)
{
	int rc;

	rc = ioctl (fd, START_ARRAY, (unsigned long)rdev);

	if (rc) {
		save_errno = errno;
		switch (save_errno) {
			case EBUSY:
				printf("%s: already running\n",dev);
				break;
			default:
				perror (dev);
		}
		errno = save_errno;
		return 1;
	}
	return 0;
}

int do_raidstart_rw (int fd, char *dev)
{
	int func = RESTART_ARRAY_RW;
	int rc;
	struct stat s;

	fstat (fd, &s);
	
	if (major (s.st_rdev) != MD_MAJOR) {
		printf("%s: not an MD device!\n",dev);
		exit(EXIT_FAILURE);
	}

	if (OLD_MDTOOLS)
		exit(EXIT_FAILURE);

	rc = ioctl (fd, func, 0UL);
	if (rc) {
		save_errno = errno;
		switch (save_errno) {
			case EBUSY:
				printf(
					"%s: device already read-write!\n",dev);
				break;
			case ENXIO:
				printf("%s: not running!\n",dev);
				break;
			case EINVAL:
				printf("%s: old kernel?\n",dev);
			/* fall through */
			default:
				perror(dev);
		}
		errno = save_errno;
		return 1;
	}
	return 0;
}

int do_raidstop (int fd, char *dev, int ro)
{
	int func = ro ? STOP_ARRAY_RO : STOP_ARRAY;
	int rc;
	struct stat s;

	fstat (fd, &s);
	
	if (major (s.st_rdev) != MD_MAJOR) {
		printf("%s: not an MD device!\n",dev);
		exit(EXIT_FAILURE);
	}

	if (OLD_MDTOOLS)
	if (func == STOP_ARRAY) {
		rc = ioctl(fd, STOP_MD, 0UL);
			return 0;
	}

	rc = ioctl (fd, func, 0UL);
	if (rc) {
		save_errno = errno;
		switch (save_errno) {
			case ENXIO:
				printf("%s: already stopped\n",dev);
				break;
			case EINVAL:
				if (func == STOP_ARRAY_RO) {
					printf("%s: old kernel!\n",dev);
					break;
				}
			/* fall through */
			default:
				perror (dev);
		}
		errno = save_errno;
		return 1;
	}
	return 0;
}

int do_raidhotadd (int md_fd, char * disk_name, char * md_name)
{
	int rc;
	struct stat s;

	if (OLD_MDTOOLS) {
		printf("kernel does not support hot-add!\n");
		exit(EXIT_FAILURE);
	}
	
	fstat (md_fd, &s);
	if (major (s.st_rdev) != MD_MAJOR) {
		printf("%s: not an MD device!\n",md_name);
		exit(EXIT_FAILURE);
	}

	stat (disk_name, &s);

	rc = ioctl (md_fd, HOT_ADD_DISK, (unsigned long)s.st_rdev);
	if (rc) {
		save_errno = errno;
  		printf("%s: can not hot-add disk: ", md_name);
		switch (save_errno) {
			case ENXIO:
				printf("disk does not exist!\n");
				break;
			case EBUSY:
				printf("disk busy!\n");
				break;
			case ENOSPC:
				printf("too small disk!\n");
				break;
			case ENODEV:
				printf("array not running!\n");
				break;
			case EINVAL:
				printf("invalid argument.\n");
				break;
			default:
				perror (md_name);
		}
		errno = save_errno;
		return 1;
	}
	return 0;
}

int do_raidhotremove (int md_fd, char * disk_name, char * md_name)
{
	int rc;
	struct stat s;

	if (OLD_MDTOOLS) {
		printf("kernel does not support hot-remove!\n");
		exit(EXIT_FAILURE);
	}
	
	fstat (md_fd, &s);
	if (major (s.st_rdev) != MD_MAJOR) {
		printf("%s: not an MD device!\n",md_name);
		exit(EXIT_FAILURE);
	}

	stat (disk_name, &s);

	rc = ioctl (md_fd, HOT_REMOVE_DISK, (unsigned long)s.st_rdev);
	if (rc) {
		save_errno = errno;
  		printf("%s: can not hot-remove disk: ", md_name);
		switch (save_errno) {
			case ENXIO:
				printf("disk not in array!\n");
				break;
			case EBUSY:
				printf("disk busy!\n");
				break;
			case ENOSPC:
				printf("too small disk!\n");
				break;
			case ENODEV:
				printf("array not running!\n");
				break;
			case EINVAL:
				printf("invalid argument.\n");
				break;
			default:
				perror (md_name);
		}
		errno = save_errno;
		return 1;
	}
	return 0;
}

int do_raidsetfaulty (int md_fd, char * disk_name, char * md_name)
{
	int rc;
	struct stat s;

	if (OLD_MDTOOLS) {
		printf("kernel does not support hot-add!\n");
		exit(EXIT_FAILURE);
	}

	fstat (md_fd, &s);
	if (major (s.st_rdev) != MD_MAJOR) {
		printf("%s: not an MD device!\n",md_name);
		exit(EXIT_FAILURE);
	}

	stat (disk_name, &s);

	rc = ioctl (md_fd, SET_DISK_FAULTY, (unsigned long)s.st_rdev);
	if (rc) {
		save_errno = errno;
		printf("%s: can not set disk faulty: ", md_name);
		switch (save_errno) {
			case ENXIO:
				printf("disk does not exist!\n");
				break;
			case EBUSY:
				printf("disk busy!\n");
				break;
			case ENOSPC:
				printf("too small disk!\n");
				break;
			case ENODEV:
				printf("array not running!\n");
				break;
			case EINVAL:
				printf("invalid argument.\n");
				break;
			default:
				perror (md_name);
		}
		errno = save_errno;
		return 1;
	}
	return 0;
}

int handleOneConfig(enum raidFunc func, md_cfg_entry_t * cfg) {
	int rc = 0;
	int fd = -1;
	struct md_param param;

	switch (func) {
		case raidstart:
		{
			struct stat s;

			stat (cfg->device_name[0], &s);

			fd = open_or_die(cfg->md_name);
			if (do_mdstart (fd, cfg->md_name, s.st_rdev)) rc++;
			break;
		}

		case raidrun:
			memset(&param,0,sizeof(param));
			switch (cfg->array.param.level) {
				case -3: param.personality = LVM; break;
				case -2: param.personality = TRANSLUCENT; break;
				case -1: param.personality = LINEAR; break;
				case 0:  param.personality = RAID0; break;
				case 1:  param.personality = RAID1; break;
				case 4:
				case 5:  param.personality = RAID5; break;
				default: exit (EXIT_FAILURE);
			}

			param.chunk_size = cfg->array.param.chunk_size;

			fd = open_or_die(cfg->md_name);
			if (do_mdrun (fd, cfg->md_name, &param)) rc++;
			break;

		case raidstop:
			return 1;

		case raidstop_ro:
			return 1;

		case raidhotremove:
			return 1;

		case raidhotadd:
			return 1;

		case raidsetfaulty:
			return 1;
	}
	close (fd);

	return rc;
}

int prepare_raidlib (void)
{
	if (getMdVersion(&md_ver)) {
		printf("cannot determine md version: %s\n",
			strerror(errno));
		return EXIT_FAILURE;
	}
	return 0;
}

