/*
 * ziptool.c -- Linux tool for Iomega ZIP/JAZ drives, Version 1.1
 *
 * (c) 1996   ziptool  Grant R. Guenther (grant@torque.net),
 *                     based on work of Itai Nahshon
 * (c) 1996   jaztool  Bob Willmot (bwillmot@cnct.com)
 * (c) 1997   Mirko Kraft (Mirko.Kraft@ixos.de)
 * (cl) 1999  Aaron Segura <aaronsegura@netscape.net>
 *      - Mutilated Code to add -ld -m -u -ud options, and
 *        add jaz 2g Support.
 *	  09/24/99
 *
 * 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.
 *  
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * To obtain a copy of the GNU General Public License please write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
 * USA.
 *
 *
 * For further Information see the README file.
 */


#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <mntent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <linux/fs.h>
#include <linux/major.h>

#include <scsi/scsi.h>
#include <scsi/scsi_ioctl.h>


#ifndef MNTTYPE_AUTO
#define MNTTYPE_AUTO			"auto"
#endif
#ifndef IOMEGA_GET_PROT_MODE		/* patched kernels know this */
#define IOMEGA_GET_PROT_MODE		0x06
#define IOMEGA_SET_PROT_MODE		0x0c
#else
#define HAVE_KERNEL_PATCH		1
#endif

#define IOMEGA_MAX_PASSWORD_LENGTH	33

#define IOMEGA_GET_PROTECTION_MODE	IOMEGA_GET_PROT_MODE	/* see scsi.h    */
#define IOMEGA_SET_PROTECTION_MODE	IOMEGA_SET_PROT_MODE	/* see scsi.h    */

#define IOMEGA_PROTECTION_MODE_RW	0x00	/* no protection                 */
#define IOMEGA_PROTECTION_MODE_RO	0x02	/* write-protected               */
#define IOMEGA_PROTECTION_MODE_ROPW	0x03	/* password write-protected      */
#define IOMEGA_PROTECTION_MODE_PASS	0x05	/* password read/write-protected */

#define SPIN_STOP			0x01
#define SPIN_EJECT			0x02

#define MOUNT_DISK			0x01 // ams
#define UMOUNT_DISK			0x02 // ams
#define MOUNT_CMD			"/bin/mount"
#define UMOUNT_CMD			"/bin/umount"

/*
 * Model lists. Don't delete the NULL entries. You may add new entries here,
 * casing is not significant.
 */
static char	*jaz_models[] =
{
	"iomega  jaz 1gb",
	"iomega  jaz 2gb", //ams
	NULL
};

static char	*zip_models[] =
{
	"Iomega  ZIP 100",
	NULL
};


/*
 * Other stuff.
 */
static struct sdata
{
	int  inlen;
	int  outlen;
	char cmd[256];
}		scsi_cmd;

static char	**model_list;
static char	*progname;
static int	mounted = 0;

static int	jazip_check_dev (char*);
static int	jazip_ctrldoor (int, int);
static int	jazip_ctrlspin (int, int);
static int	jazip_display_mode (int);
static int	jazip_eject (int);
static int	jazip_get_mode (int);
static int	jazip_get_password (char*, char*, int);
static int	jazip_set_mode (int, int);
static void	jazip_usage (char*);
static int	jazip_ctrlmount (int, int, char *, char *);  // jazipfd, MOUNT/UMOUNT, dev, mntpoint

/*
 * Function  : jazip_ctrlmount
 *
 * Parameters: FD pointing to device, Operation, Device, Mountpoint
 *
 * Returns   : 0 on success, -1 on error
 *
 */

jazip_ctrlmount ( int jazipfd, int oper, char *dev, char *mnt)
{
	int result;
	char cmd[255];
		
	bzero(cmd, 255);

	switch(oper)
	{
		case MOUNT_DISK:
			strncpy(cmd, MOUNT_CMD, strlen(MOUNT_CMD)+1);
			strncat(cmd, " -t ext2 ", 9 );
			strncat(cmd, dev, strlen(dev)+1);
			strncat(cmd, " ", 1);
			strncat(cmd, mnt, strlen(mnt)+1);
			break;
		
		case UMOUNT_DISK:	
			jazip_ctrldoor(jazipfd, SCSI_REMOVAL_ALLOW);
			strncpy(cmd, UMOUNT_CMD, strlen(UMOUNT_CMD)+1);
			strncat(cmd, " ", 1);
			strncat(cmd, dev, strnlen(dev));	
			break;
	}

	result = system(cmd);
	
	if ( result == 0)
		if ( oper == MOUNT_DISK)
			jazip_ctrldoor(jazipfd, SCSI_REMOVAL_PREVENT);

	return result;
}


/*
 * Function  : jazip_check_dev
 *
 * Parameters: dev   Name of device claiming to be a ZIP/JAZ drive.
 *
 * Returns   : Filepointer to ZIP/JAZ device on success, -1 on errors.
 */

static
int	jazip_check_dev (char *dev)
{
	FILE		*mtab;
	char		mounts[255];
	char		**model;
	char		scsi_signature[25];
	int		i, jazipfd;

/* 
 * Check to see if It is mounted via /etc/mtab
 *   (chop chop, hack hack )
 *
 */
	if (! (mtab = fopen("/etc/mtab", "r") ) )
	{
		perror("Opening /etc/mtab");
		exit(1);
	}

	while ( ! feof(mtab) )
	{
		fgets(mounts, 254, mtab);
		if( strstr(mounts, dev) )
		{
			mounted = 1;
		}
	}
	fclose(mtab);
	

/*
 * Finally open the device and read the model signature out of it.
 * Check if it is the one we expect.
 */
	if ((jazipfd = open (dev, O_RDONLY)) < 0) {
		(void) fprintf (stderr, "%s: can't open %s: %s.\n",
			progname, dev, strerror (errno));
		return (-1);
	}

	scsi_cmd.inlen  = 0;
	scsi_cmd.outlen = 40;
	scsi_cmd.cmd[0] = (char) INQUIRY;
	scsi_cmd.cmd[1] = (char) 0;
	scsi_cmd.cmd[2] = (char) 0;
	scsi_cmd.cmd[3] = (char) 0;
	scsi_cmd.cmd[4] = (char) 40;
	scsi_cmd.cmd[5] = (char) 0;

	if (ioctl (jazipfd, SCSI_IOCTL_SEND_COMMAND, (void*) &scsi_cmd)) {
		(void) fprintf (stderr, "%s: can't read scsi model signature "
			"from %s: %s.\n", progname, dev, strerror (errno));
		(void) close (jazipfd);
		return (-1);
	}

	for (i = 0; i < 24; i++)
		scsi_signature[i] = scsi_cmd.cmd[i + 8];
	scsi_signature[24] = 0;

	for (model = model_list; *model != NULL; ++model)
		if (!strncasecmp (scsi_signature, *model, strlen (*model)))
			return (jazipfd);

	(void) fprintf (stderr, "%s: model '%s' is not supported.\n",
		progname, scsi_signature);

	(void) close (jazipfd);
	return (-1);
}


/*
 * Function  : jazip_ctrldoor
 *
 * Parameters: jazipfd     File pointer of ZIP/JAZ drive.
 *             door_mode   SCSI_REMOVAL_ALLOW   - Unlock the device so
 *                                                that the medium can
 *                                                be removed.
 *                         SCSI_REMOVAL_PREVENT - Lock the device so
 *                                                that the medium can't
 *                                                be removed.
 *
 * Returns   : 0 on success, -1 on errors.
 */

static
int	jazip_ctrldoor (int jazipfd, int door_mode)
{
	switch (door_mode) {
	case SCSI_REMOVAL_ALLOW:
	case SCSI_REMOVAL_PREVENT:
		scsi_cmd.inlen  = 0;
		scsi_cmd.outlen = 0;
		scsi_cmd.cmd[0] = (char) ALLOW_MEDIUM_REMOVAL;
		scsi_cmd.cmd[1] = (char) 0;
		scsi_cmd.cmd[2] = (char) 0;
		scsi_cmd.cmd[3] = (char) 0;
		scsi_cmd.cmd[4] = (char) door_mode;
		scsi_cmd.cmd[5] = (char) 0;

		return (ioctl (jazipfd, SCSI_IOCTL_SEND_COMMAND,
			(void*) &scsi_cmd));
	}

	return (-1);
}


/*
 * Function  : jazip_ctrlspin
 *
 * Parameters: jazipfd     File pointer of ZIP/JAZ drive.
 *             spin_mode   SPIN_EJECT - eject an inserted medium (why is
 *                                      this controlled via START_STOP
 *                                      anyway?)
 *                         SPIN_STOP  - stop the ZIP/JAZ drive.
 *
 * Returns   : 0 on success, -1 on errors.
 */

static
int	jazip_ctrlspin (int jazipfd, int spin_mode)
{
	switch (spin_mode) {
	case SPIN_EJECT:
	case SPIN_STOP:
		scsi_cmd.inlen  = 0;
		scsi_cmd.outlen = 0;
		scsi_cmd.cmd[0] = (char) START_STOP;
		scsi_cmd.cmd[1] = (char) 0;
		scsi_cmd.cmd[2] = (char) 0;
		scsi_cmd.cmd[3] = (char) 0;
		scsi_cmd.cmd[4] = (char) spin_mode;
		scsi_cmd.cmd[5] = (char) 0;

		return (ioctl (jazipfd, SCSI_IOCTL_SEND_COMMAND,
			(void*) &scsi_cmd));
	}

	return (-1);
}


/*
 * Function  : jazip_display_mode
 *
 * Parameters: jazipfd     File pointer of ZIP/JAZ drive.
 *
 * Returns   : 0 success, -1 on errors.
 */

static
int	jazip_display_mode (int jazipfd)
{
	int	mode;


	if ((mode = jazip_get_mode (jazipfd)) < 0) {
		(void) fprintf (stderr, "%s: reading current protection mode "
			"failed.\n", progname);
		return (-1);
	}

/*
 * Iomega offers more modes than we can handle (e.g. this infamous read
 * and write password protected mode). So if we have an unsupported
 * mode just it's decimal number is reported.
 */
	switch (mode) {
	case IOMEGA_PROTECTION_MODE_RW:
		(void) printf ("%s: medium is not protected.\n", progname);
		break;
	case IOMEGA_PROTECTION_MODE_RO:
		(void) printf ("%s: medium is write-protected.\n", progname);
		break;
	case IOMEGA_PROTECTION_MODE_ROPW:
		(void) printf ("%s: medium is password write-protected.\n",
			progname);
		break;
	default:
		(void) fprintf (stderr, "%s: current protection mode %d is "
			"unsupported.\n", progname, mode);
		break;
	}

	return (0);
}


/*
 * Function  : jazip_eject
 *
 * Parameters: jazipfd     File pointer of ZIP/JAZ drive.
 *
 * Returns   : 0 on success, -1 on errors.
 */

static
int	jazip_eject (int jazipfd)
{
	if (jazip_ctrldoor (jazipfd, SCSI_REMOVAL_ALLOW)) {
		(void) fprintf (stderr, "%s: can't unlock drive door.\n",
			progname);
		return (-1);
	}

	if (jazip_ctrlspin (jazipfd, SPIN_STOP)) {
		(void) fprintf (stderr, "%s: can't spin down drive.\n",
			progname);
		return (-1);
	}

	if (jazip_ctrlspin (jazipfd, SPIN_EJECT)) {
		(void) fprintf (stderr, "%s: can't eject medium.\n",
			progname);
		return (-1);
	}

	return (0);
}


/*
 * Function  : jazip_get_mode
 *
 * Parameters: jazipfd     File pointer of ZIP/JAZ drive.
 *
 * Returns   : Protection mode on success, -1 on errors.
 */

static
int	jazip_get_mode (int jazipfd)
{
	scsi_cmd.inlen  = 0;
	scsi_cmd.outlen = 256;
	scsi_cmd.cmd[0] = (char) IOMEGA_GET_PROTECTION_MODE;
	scsi_cmd.cmd[1] = (char) 0;
	scsi_cmd.cmd[2] = (char) 2;
	scsi_cmd.cmd[3] = (char) 0;
	scsi_cmd.cmd[4] = (char) 128;
	scsi_cmd.cmd[5] = (char) 0;

	if (ioctl (jazipfd, SCSI_IOCTL_SEND_COMMAND, (void*) &scsi_cmd))
		return (-1);

	return ((int) (scsi_cmd.cmd[21] & 0x0f));
}


/*
 * Function  : jazip_get_password
 *
 * Parameters: prompt   Prompt to be displayed.
 *             buf      Buffer to copy password into.
 *             maxlen   Maximum number of characters to read.
 *
 * Returns   : Password length on success, -1 on errors.
 */

static
int	jazip_get_password (char *prompt, char *buf, int maxlen)
{
	int	len;

/*
 * Print prompt and read input. If the input is not ended by a new line
 * character '\n', then all remaining characters until '\n' must be
 * read from stdin.
 */
	(void) printf (prompt);
	(void) fflush (stdout);

	*buf = '\0';
	if (fgets (buf, maxlen, stdin) == NULL)
		return (-1);

	if ((len = strlen (buf)) > 0) {
		if (*(buf + len - 1) == '\n') {
			*(buf + len - 1) = '\0';
			--len;
		} else if (len == maxlen - 1)
			while (fgetc (stdin) != (int) '\n');
	}

	return (len);
}


/*
 * Function  : jazip_set_mode
 *
 * Parameters: jazipfd     File pointer of ZIP/JAZ drive.
 *             mode        IOMEGA_PROTECTION_MODE_RO   - Set normal
 *                                                       write-protected
 *                                                       mode.
 *                         IOMEGA_PROTECTION_MODE_ROPW - Set password
 *                                                       write-protected
 *                                                       mode.
 *                         IOMEGA_PROTECTION_MODE_RW   - Remove any
 *                                                       protection.
 *
 * Returns   : Protection mode on success, -1 on errors.
 */

static
int	jazip_set_mode (int jazipfd, int mode)
{
	char	password[IOMEGA_MAX_PASSWORD_LENGTH + 1];
	int	i, len, oldmode;


/*
 * Get the current mode.
 */
	switch (oldmode = jazip_get_mode (jazipfd)) {
/*
 * The medium is currently password write-protected. Further actions
 * depend from the new mode.
 */
	case IOMEGA_PROTECTION_MODE_ROPW:
		switch (mode) {
/*
 * A big TODO here:
 * The new mode is also password write-protected. This is equivalent
 * to a password change.
 *
 * AT THE MOMENT I DON'T KNOW HOW A PASSWORD CHANGE IS DONE, I.E. IF
 * THERE IS A SEPERATE SCSI COMMAND OR IF IT IS DONE BY SWITCHING
 * MODES FROM P/W WRITE-PROTECTED TO NOT OR WRITE PROTECTED AND THEN
 * BACK TO P/W WRITE-PROTECTED. SO THIS FEATURE IS CURRENTLY NOT
 * SUPPORTED.
 */
		case IOMEGA_PROTECTION_MODE_ROPW:
			(void) fprintf (stderr, "%s: sorry. changing password is "
				"currently not supported.\n", progname);
			return (-1);
/*
 * The new mode is normal write-protected or unprotected. Prompt for
 * the password and then add it to the SCSI command.
 */
		case IOMEGA_PROTECTION_MODE_RO:
		case IOMEGA_PROTECTION_MODE_RW:
			if ((len = jazip_get_password ("Password: ", password,
			IOMEGA_MAX_PASSWORD_LENGTH + 1)) < 0) {
				(void) fprintf (stderr, "%s: reading password failed.\n",
					progname);
				return (-1);
			}

			for (i = 0; i < len; i++)
				scsi_cmd.cmd[6 + i] = password[i];
			break;
/*
 * An unsupported new mode was specified.
 */
		default:
			(void) fprintf (stderr, "%s: sorry. setting protection to "
				"mode %d is not supported.\n", progname, mode);
		
			return (-1);
		}

		break;
/*
 * The medium is currently normal write-protected or unprotected.
 * Further actions depend from the new mode.
 */
	case IOMEGA_PROTECTION_MODE_RO:
	case IOMEGA_PROTECTION_MODE_RW:
		switch (mode) {
/*
 * The new mode is password write-protected. Prompt for a password and
 * then add it to the SCSI command.
 */
		case IOMEGA_PROTECTION_MODE_ROPW:
			if ((len = jazip_get_password ("Password: ", password,
			IOMEGA_MAX_PASSWORD_LENGTH + 1)) < 0) {
				(void) fprintf (stderr, "%s: reading password failed.\n",
					progname);
				return (-1);
			}

			for (i = 0; i < len; i++)
				scsi_cmd.cmd[6 + i] = password[i];
			break;
/*
 * The new mode is normal write-protected or unprotected. Nothing needs
 * to be done here.
 */
		case IOMEGA_PROTECTION_MODE_RO:
		case IOMEGA_PROTECTION_MODE_RW:
			len = 0;
			break;
/*
 * An unsupported new mode was specified.
 */
		default:
			(void) fprintf (stderr, "%s: sorry. setting protection to "
				"mode %d is not supported.\n", progname, mode);
			return (-1);
		}

		break;
/*
 * Oops - something went wrong when getting the current protection mode.
 */
	case -1:
		(void) fprintf (stderr, "%s: reading current protection mode "
			"failed.\n", progname);
		return (-1);
/*
 * The current protection mode of the medium is one that is not (yet)
 * supported by this tool.
 */
	default:
		(void) fprintf (stderr, "%s: current protection mode %d is "
			"unsupported.\n", progname, oldmode);
		return (-1);
	}

/*
 * Set the new mode.
 */
	scsi_cmd.inlen  = len;
	scsi_cmd.outlen = 0;
	scsi_cmd.cmd[0] = (char) IOMEGA_SET_PROTECTION_MODE;
	scsi_cmd.cmd[1] = (char) mode;
	scsi_cmd.cmd[2] = (char) 0;
	scsi_cmd.cmd[3] = (char) 0;
	scsi_cmd.cmd[4] = (char) len;
	scsi_cmd.cmd[5] = (char) 0;

	if (ioctl (jazipfd, SCSI_IOCTL_SEND_COMMAND, (void*) &scsi_cmd)) {
		(void) fprintf (stderr, "%s: can't set protection mode to %d.\n",
			progname, mode);
		return (-1);
	}

/*
 * Verify the new mode. If we found an unpatched kernel, we must eject the
 * medium afterwards to ensure the kernel recognizes the new protection mode.
 */
	(void) jazip_display_mode (jazipfd);

#ifndef HAVE_KERNEL_PATCH
	if (jazip_eject (jazipfd)) {
		(void) fprintf (stderr, "%s: please eject medium manually.\n",
			progname);
		return (-1);
	}
#endif
	return (0);
}


/*
 * Function  : jazip_usage
 *
 * Parameters: progname   Program name to be displayed.
 *
 * Returns   : Nothing.
 */

static
void 	jazip_usage (char* progname)
{
	(void) fprintf (stderr,
		"usage: %s -e  <dev>   eject disk\n"
		"       %s -s  <dev>   report protection status\n"
		"       %s -ro <dev>   make disk read only\n"
		"       %s -rp <dev>   make disk read only with password\n"
		"       %s -rw <dev>   make disk full accessible\n"
		"       %s -ld <dev>   lock the door on the drive (must be mounted)\n"
		"       %s -ud <dev>   unlock the door on the drive ^^^^^^^^^^^^^\n"
		"       %s -m  <dev> <dir>\n"
		"                           mounts the given <dev>4 on <dir> and locks the door\n"
		"       %s -u  <dev>   unmounts the given <dev>4\n\n",
		progname, progname, progname, progname, progname, progname, progname, progname, progname);
}


/*
 * Function  : main
 *
 * Parameters: argc   Number of command line parameters.
 *             argv   Array of pointers to command line parameters
 *                    as strings.
 *
 * Returns   : Exit status of program.
 */

int	main (int argc, char *argv[])
{
	char	partition[10];
	char	*ptr;
	int	result = 1;
	int	jazipfd;

/*
 * How were we called? Currently "ziptool" and "jaztool" are supported.
 * The name determines what type of device we are looking for.
 */
	if ((ptr = strrchr (argv[0], '/')) != NULL)
		progname = ptr + 1;
	else
		progname = argv[0];

	if (!strcmp (progname, "jaztool"))
		model_list = jaz_models;
	else if (!strcmp (progname, "ziptool"))
		model_list = zip_models;
	else {
		jazip_usage ("ziptool");
		jazip_usage ("jaztool");
		exit (1);
	}

/*
 * Check parameters and call appropriate functions. Supported are:
 *  -e    Eject medium.
 *  -s    Get protection status of currently inserted medium.
 *  -ro   Set normal write-protected mode.
 *  -rp   Set password write-protected mode.
 *  -rw   Remove any protection.
 *  -ld   Lock the Door on a mounted Disk
 *  -ud   Unlock the Door ....
 *  -m    Mount and automatically Lock the Disk  Takes extra cmdline params
 *  -u    Umount the disk. Takes extra cmdlink parameters
 */
	if (argc >= 3) 
	{
		if ((jazipfd = jazip_check_dev (argv[2])) >= 0) 
		{
			if (!strcmp (argv[1], "-e"))
				if( ! mounted )
					result = jazip_eject (jazipfd);
				else 
					printf("Device is mounted.\n");

			else if (!strcmp (argv[1], "-s"))
				result = jazip_display_mode (jazipfd);
		
			else if ( !strcmp (argv[1], "-ro"))
				if ( ! mounted )
					result = jazip_set_mode (jazipfd, IOMEGA_PROTECTION_MODE_RO);
				else
					printf("Device is mounted.\n");

			else if ( !strcmp (argv[1], "-rp"))
				if ( ! mounted )
					result = jazip_set_mode (jazipfd, IOMEGA_PROTECTION_MODE_ROPW);
				else
					printf("Device is mounted.\n");

			else if ( !strcmp (argv[1], "-rw")) 
				if ( ! mounted)
					result = jazip_set_mode (jazipfd, IOMEGA_PROTECTION_MODE_RW);
				else
					printf("Device is mounted.\n");

			else if (!strcmp (argv[1], "-ld"))
				if ( ! mounted)
					printf("Device isn't mounted.\n");
				else
					result = jazip_ctrldoor (jazipfd, SCSI_REMOVAL_PREVENT);

			else if (!strcmp (argv[1], "-ud"))
				if ( ! mounted)
					printf("Device isn't Locked.\n");
				else
					result = jazip_ctrldoor (jazipfd, SCSI_REMOVAL_ALLOW);

			else if ( (!strcmp (argv[1], "-m")) && (argc == 4) )
			{
				strncpy(partition, argv[2], 8);
				strncat(partition, "4", 1);
				printf("Mounting %s on %s\n", partition, argv[3]);	
				result = jazip_ctrlmount (jazipfd, MOUNT_DISK, partition, argv[3]);
			}

			else if ( (!strcmp (argv[1], "-u")) && (argc == 3) )
			{	
				strncpy(partition, argv[2], strlen(argv[2]));
				strncat(partition, "4", 1);
				result = jazip_ctrlmount (jazipfd, UMOUNT_DISK, partition, NULL);
			}
			else
				jazip_usage (progname);

			(void) close (jazipfd);
		}
	} else
		jazip_usage (progname);

	exit (abs (result));
}
