/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident	"$Header: sweepon.c 1.6 89/08/10 $"

/*
 * sweepon
 *
 * Utility for enabling or disabling patrol seeks 
 */

/* $Log:	sweepon.c,v $
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/buf.h>
#include <sys/time.h>
#define  INKERNEL
#include <sys/autoconf.h>
#undef	 INKERNEL
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <nlist.h>
#include <ctype.h>
#include <errno.h>

#define KMEM_FILENAME	"/dev/kmem"
#define DYNIX_FILENAME	"/unix"
#define IGNORE_FILENAME	"/etc/sweepon.ignore"

#define MAXDRIVES	100		/* max drives of any one type */

/*
 *  Global data
 */
int dotrace = 0;			/* debug flag */
int all = 0;				/* do all drives */
int waittime;				/* time to sleep between patrols */ 
int n_drives;				/* total configured drives */
struct dtypes {				/* array of types to patrol */
	char dname[32];
	int  dnumcfg;
	int  dnumalive;
	int  dignore[MAXDRIVES];
} dtypes[20] = {
	{ "zd" }			/* default type to patrol */
};
int ndtypes = 1;			/* number of dtypes to patrol */

extern int errno;
void read_ignorelist();


/*
 * sweepon [-d] {-a [disktype disktype...]|drive}
 * sweepoff [-d]] {-a [disktype disktype...]|drive}
 *
 * Patrol seeks have been suggested by Fujitsu as a means
 * of increasing drive longevity.  This program should be invoked 
 * to enable patrol seeks at system boottime.
 * This program enables or disables patrol seeks on one or more drives.
 * If the '-a' option is specified, all drives of the specified type are
 * enabled/disabled.  If no 'disktypes' are specified, it operates by
 * default on DCC drives, and since currently the zd driver is the only
 * driver able to support this functionality anyway, the ability to
 * specify 'disktypes' is undocumented.  If 'drive' is specified rather
 * than '-a', patrol seeks are enabled/disabled on the indicated drive.
 * 'drive' can be the drive without the full path (i.e. zd0, zd3...)
 * or can be a full path (i.e. /dev/rdsk/zd0).  The '-d' option enables
 * tracing.
 *
 * disktype	=	the root name of the drive types to patrol
 *			If unspecified, zd disks will be patroled by
 *			by default.
 *
 * Specific drives of a type which has seek enabled will not be enabled 
 * if placed in the /etc/sweepon.ignore file.
 */
main(argc, argv)
	int argc;
	char **argv;
{
	int i, j;
	int enable = 1;
	char name[64];
	char invoke_name[64];

	strcpy(invoke_name, *argv);
	if (argc == 1)
		goto usage;
	--argc;

	if (!strcmp(invoke_name, "sweepoff"))
		enable = 0;
	/*
	 * Parse -d options
	 */
	while ((*++argv)[0] == '-' && argc > 0) {
		--argc;
		if ((*argv)[1] == 'd' || (*argv)[2] == 'd')
			dotrace++;
		if ((*argv)[1] == 'a' || (*argv)[2] == 'a')
			all++;
		if (!dotrace && !all)
			goto usage;
	}
	--argv;
	
	if (!all) {
		/* name of disk is required */
		if (argc != 1)
			goto usage;
		strcpy(name, *++argv);
		--argc;
	}

	if (dotrace)
		printf("sweepon: %s seeks\n", 
			((enable) ? "enabling" : "disabling"));
	if (argc > 0)
		ndtypes = 0;
	while (argc--)
		strcpy(dtypes[ndtypes++].dname, *++argv);
#ifdef DEBUG
	{ int i;
	printf("ndtypes: ");
	for (i = 0; i < ndtypes; i++)
		printf("%s ", dtypes[i].dname);
	printf("\n");
	}
#endif /* DEBUG */
 
	if (all) {
		n_drives = getdrives();
		if (n_drives < 0)	/* error, message already printed */
			fatal_exit();
		if (n_drives == 0)	/* just no alive drives! */ 
			exit(0);
	}

	/*
	 * This routine needs to be called after getdrives()
	 * and after the ndtypes stuff is set up, since it
	 * uses the global data set up.
	 */
	if (enable && all)
		read_ignorelist();

	/*
	 * Now go through and enable/disable patrol seeks on the
	 * drive(s).
	 */
	if (all) {
		for (i = 0; i < ndtypes; i++) {
			for (j = 0; j < dtypes[i].dnumcfg; j++) {
				if (dtypes[i].dignore[j])
					continue;
				sprintf(name, "%s%d", 
					dtypes[i].dname, j);
				do_drive(name, enable);
			}
		}
	} else
		do_drive(name, enable);
	exit(0);
usage:
	fprintf(stderr, "usage: %s [-d] {-a|drive}\n", invoke_name);
	exit(1);
}


/*
 * do_drive
 * 	Enable or disable patrol seeks on the drive specified by
 *	'name' according to the flag 'enable'
 */
do_drive(name, enable)
	char *name;
	int enable;
{
	char unitname[256];
	int fd;
	int iocmd;

	if (name[0] == '/')	/* full path */
		strcpy(unitname, name);
	else
		(void)sprintf(unitname, "/dev/diag/rdsk/%s", name);
	fd = open(unitname, O_RDONLY);
	if (fd < 0 && name[0] != '/') {
		/* try raw device */
		(void)sprintf(unitname, "/dev/rdsk/%s", name);
		fd = open(unitname, O_RDONLY);
	}
	if (fd < 0) {
		if (dotrace) {
			fprintf (stderr, "sweepon: %s: ",  name);
			perror ("open");
		}
		return;
	}
	iocmd = (enable) ? RIOPATROL : RIONPATROL;
	if (ioctl(fd, iocmd, (char *)NULL) < 0) {
		fprintf(stderr, "sweepon: can't %s patrol ",
			((enable) ? "enable" : "disable"));
		fprintf(stderr, "seek on %s\n",  name);
		perror("ioctl error");
		close(fd);
		return;
	}
	if (dotrace)
		printf ("sweepon: patrol seek %s on %s\n",
			((enable) ? "enabled" : "disabled"),
			name);
	close(fd);
	return;
}

/*
 * read_ignorelist()
 *
 * This routine reads the /etc/sweepon.ignore file and causes
 * the indicated drives to be skipped when enabling patrol seeks. 
 */
void
read_ignorelist()
{
	FILE *fp = fopen (IGNORE_FILENAME, "r");
	char buf[256];
	int i, j, num, len;

	if (fp == NULL)
		/* no ignore file is not an error */
		return;

	while (fgets(buf, sizeof(buf), fp)) {
		/* Get rid of newline */
		buf[strlen(buf)-1] = '\0';
		if (!buf[0] || buf[0] == '#') 
			/* skip blank lines or comments */
			continue;
		for (i = 0; i < ndtypes; i++) {
			len = strlen(dtypes[i].dname);
			if (!strncmp(buf, dtypes[i].dname, len)
			    && isdigit(buf[len]))
				/* match! */
				break;
		}
		if (i == ndtypes)		/* no match */
			continue;
		
		/*
		 * We've found the right drive type.
		 * Set the specified drive to be ignored.
		 */
		num = atoi(&buf[len]);
		if (num < MAXDRIVES)
			dtypes[i].dignore[num] = 1;
		if (dotrace)
			printf("sweepon: ignoring %s%d\n",
				dtypes[i].dname, num);
	}
	return;
}

/*
 * getdrives
 *
 * Build the drive table.  We look at the 'driver' structure 
 * in /dev/kmem to determine how many of that type are configured, 
 * reserving a spot for each.
 */
getdrives()
{
	int kmem_fd;
	int result;

	kmem_fd = open (KMEM_FILENAME, O_RDONLY);
	if (kmem_fd < 0) {
		perror ("sweepon: open kmem");
		return -1;
	}
	result = getdrives_guts (kmem_fd);
	(void) close (kmem_fd);
	return result;
}

/*
 * getdrives_guts
 *
 * Does the actual work of reading the /dev/kmem data structures
 * and getting the info
 */
getdrives_guts(kmem_fd)
	int kmem_fd;
{
	static struct nlist nl[] = {
#define	X_DRIVERS	0
		{ "drivers" },
		0
	};
	long k_driver;
	struct driver_module driver_module;
	struct driver_module *driver = &driver_module;
	char name[32];
	int drives_found = 0;
	int i, k, j;

	if (nlist(DYNIX_FILENAME, nl) < 0) {
		fprintf(stderr, "sweepon: nlist error on %s: ", DYNIX_FILENAME);
		perror("");
		return -1;
	}
	if (nl[X_DRIVERS].n_value == 0) {
		fprintf (stderr, "sweepon: 'drivers' not found!\n"); 
		return 0;
	}
	k_driver = nl[X_DRIVERS].n_value;
#ifdef DEBUG
	printf("looking for %d types in 'drivers'\n", ndtypes);
#endif /* DEBUG */
	for (k = 0; k < ndtypes; k++) {
		driver->numdev = 1;
		driver->name = name;
		name[0] = '\0';
		for (j = 0; driver->numdev 
			    && strcmp(dtypes[k].dname, driver->name); j++) {
			/*
			 * Search through the driver array in kmem
			 * looking for the entry with the name that
			 * matches our disk type.
			 */
			if (lseek (kmem_fd, 
			    (off_t)k_driver+(j*sizeof(struct driver_module)), 
			     0) < 0) {
				perror("sweepon: error seeking to drivers array in kmem");
				fprintf(stderr, "sweepon: %s symbols may be inconsistent with /dev/kmem\n",
					DYNIX_FILENAME);
				return -1;
			}
			if (read (kmem_fd, (char *) driver, 
				  sizeof(struct driver_module)) < 0) {
				perror("sweepon: error reading driver entry from kmem");
				fprintf(stderr, "sweepon: %s symbols may be inconsistent with /dev/kmem\n",
					DYNIX_FILENAME);
				return -1;
			}
			if (lseek(kmem_fd, (off_t)driver->name, 0) < 0) {
				perror("sweepon: error seeking to driver name in kmem");
				fprintf(stderr, "sweepon: %s symbols may be inconsistent with /dev/kmem\n",
					DYNIX_FILENAME);
				return(-1);
			}
			if (read(kmem_fd, name, 32) < 0) {
				perror("sweepon: error reading driver name in kmem");
				fprintf(stderr, "sweepon: %s symbols may be inconsistent with /dev/kmem\n",
					DYNIX_FILENAME);
				return(-1);
			}
			driver->name = name;
		}
		if (!driver->numdev) {
			/*
			 * Didn't find a match -- no drives configured
			 */
			fprintf(stderr, 
				"sweepon: no devices of type %s configured\n",
				dtypes[k].dname);
			continue;
		}
		if (dotrace)
			printf("sweepon: %d devices of type %s, %d alive\n", 
				driver->numdev, dtypes[k].dname, driver->nalive);
		/*
		 * Get the number of configured, alive drives..
		 */
		dtypes[k].dnumcfg = driver->numdev;
		dtypes[k].dnumalive = driver->nalive;
		for (i = 0; i < driver->numdev; i++)
			/* zero ignore array */
			dtypes[k].dignore[i] = 0;
		drives_found += driver->nalive;
	}
	return drives_found;
}

/*
 *  fatal_exit
 */
fatal_exit()
{
	fprintf (stderr, "SWEEPON: FAILED TO ENABLE PATROL SEEKS.\n"); 
	fprintf (stderr, "Please attempt to resolve the problem.\n");
	fprintf (stderr, "Contact Sequent Customer Service for assistance.\n");
	exit(2);
}
