/* ziptool.c -- ziptool for linux */

/*  (c) 1996   Grant R. Guenther,  based on work of Itai Nahshon  */

/* This program uses SCSI_IOCTL_SEND_COMMAND to deliver vendor specific 
   commands to an Iomega ZIP drive.  The program attempts to ensure that
   the SCSI device is actually a ZIP drive and that it is not currently
   mounted.  The checks are *not* foolproof - but only root can do these
   things anyway !

   Usage:  ziptool <dev> <command>

   where <dev> must be the full name of a scsi device, eg: /dev/sdc, note
   that you must not specify a partition on the device.  <command> can
   be one of:

	eject	ejects the disk in the drive
	ro	puts the disk into read-only mode, and ejects
	rw      puts the disk into read-write mode, and ejects  
	status  prints the read/write status of the disk

   If the disk is in a password protected mode, you will be prompted for
   a password when you attempt to change the mode.  This version of 
   ziptool does not officially support putting the disk into the password
   protected read-only mode, but the command 'PWRO' will do this if you
   are really sure that you want to.  REMEMBER: if you forget the password,
   you will not be able to put the disk back into read-write mode.

   NB:  The ZIP drive also supports a protection mode 5, under which the
   disk is neither readable nor writable until it is unlocked with a
   password.  This program cannot unlock a disk in mode 5 - as Linux is
   unable to open a disk that is not readable.  To support this feature
   would require patching the Linux kernel.  (And therefore, there is no
   command to put a disk into this mode, either.)

   Whenever you change the write-protection mode, ziptool ejects the disk.
   This is done to ensure that Linux will recheck the mode before it attempts
   to use the disk again.

*/

#include <stdio.h>
#include <mntent.h>

/* this is ugly and should not be here - but the SCSI include files are
   moving around ...
*/

#define SCSI_IOCTL_SEND_COMMAND 1

struct sdata {
	int  inlen;
	int  outlen;
	char cmd[256];
} scsi_cmd;

int is_mounted( char * fs )

/* -1 = error,  0 = not mounted,  1 = mounted */

{  struct mntent *mp;
   FILE *mtab;
   
   mtab = setmntent("/etc/mtab","r");
   if (!mtab) return -1;
   while (mp=getmntent(mtab)) 
      if (!strncmp(mp->mnt_fsname,fs,8)) break;
   endmntent(mtab);
   return (mp != NULL);
}

void error(char * msg);

int is_raw_scsi( char * fs )

{	return ((strlen(fs) == 8) && (strncmp("/dev/sd",fs,7) == 0));
}

int	is_zip( int fd )

{	char	id[25];
	int	i;

	scsi_cmd.inlen = 0;
	scsi_cmd.outlen = 40;
	scsi_cmd.cmd[0] = 0x12;		/* inquiry */
	scsi_cmd.cmd[1] = 0;
	scsi_cmd.cmd[2] = 0;
	scsi_cmd.cmd[3] = 0;
	scsi_cmd.cmd[4] = 40;
	scsi_cmd.cmd[5] = 0;

	if (ioctl(fd,SCSI_IOCTL_SEND_COMMAND,(void *)&scsi_cmd))
		error("inquiry ioctl error");

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

	return(!strncmp(id,"IOMEGA  ZIP 100",15));
}

void	motor( int fd, int mode )

{	scsi_cmd.inlen = 0;
	scsi_cmd.outlen = 0;
	scsi_cmd.cmd[0] = 0x1b;		/* start/stop */
	scsi_cmd.cmd[1] = 0;
	scsi_cmd.cmd[2] = 0;
	scsi_cmd.cmd[3] = 0;
	scsi_cmd.cmd[4] = mode;
	scsi_cmd.cmd[5] = 0;

	if (ioctl(fd,SCSI_IOCTL_SEND_COMMAND,(void *)&scsi_cmd))
		error("motor control ioctl error");
}

void	unlockdoor( int fd )

{	scsi_cmd.inlen = 0;
	scsi_cmd.outlen = 0;
	scsi_cmd.cmd[0] = 0x1e;		/* prevent/allow media removal */
	scsi_cmd.cmd[1] = 0;
	scsi_cmd.cmd[2] = 0;
	scsi_cmd.cmd[3] = 0;
	scsi_cmd.cmd[4] = 0;
	scsi_cmd.cmd[5] = 0;

	if (ioctl(fd,SCSI_IOCTL_SEND_COMMAND,(void *)&scsi_cmd))
		error("door unlock ioctl error");
}

void eject ( int fd )

{	unlockdoor(fd);
	motor(fd,1);
	motor(fd,2);
}

int	get_prot_mode( int fd )

{      	scsi_cmd.inlen = 0;
	scsi_cmd.outlen = 256;
	scsi_cmd.cmd[0] = 6;		/* Iomega non-sense command */
	scsi_cmd.cmd[1] = 0;
	scsi_cmd.cmd[2] = 2;
	scsi_cmd.cmd[3] = 0;
	scsi_cmd.cmd[4] = 128;
	scsi_cmd.cmd[5] = 0;

	if (ioctl(fd,SCSI_IOCTL_SEND_COMMAND,(void *)&scsi_cmd))
		error("non-sense ioctl error");

	return (scsi_cmd.cmd[21] & 0x0f);	/* protection code */
}

void  dostatus( int fd, char * dev )   /* explain protection codes */

{	int	s;

	s = get_prot_mode(fd);

	if (s == 0) printf("ziptool: %s is not write-protected\n",dev);
	if (s == 2) printf("ziptool: %s is write-protected\n",dev);
	if (s == 3) printf("ziptool: %s is password write-protected\n",dev);
}

void  pmode ( int fd, int mode, char * dev)

{	int	i, oldmode, len;
	char	pw[34];

	pw[0]=0;
	oldmode = get_prot_mode(fd);
	if ((1 & mode) || (1 & oldmode)) {
	  printf("Password: ");
	  fgets(pw,33,stdin);
	  len = strlen(pw);
	  if ((len > 0) && (pw[len-1] == '\n')) pw[len-1] = 0;
	}
	len = strlen(pw);

	scsi_cmd.inlen = len;
	scsi_cmd.outlen = 0;
	scsi_cmd.cmd[0] = 0x0c;
	scsi_cmd.cmd[1] = mode;
	scsi_cmd.cmd[2] = 0;
	scsi_cmd.cmd[3] = 0;
	scsi_cmd.cmd[4] = len;
	scsi_cmd.cmd[5] = 0;

	for(i=0;i<len;i++) scsi_cmd.cmd[6+i] = pw[i];

	if (ioctl(fd,SCSI_IOCTL_SEND_COMMAND,(void *)&scsi_cmd))
		error("set protection mode ioctl error");

	dostatus(fd,dev);

	eject(fd);	/* so linux can reset the wp mode */
}

void error(char * msg)

{ 	printf("ziptool: %s\n",msg);
	exit(1);
}

main(int argc, char **argv)

{  	int rs;
	int zfd;

	if (argc != 3) {
	     	printf("usage: ziptool /dev/sd? eject|ro|rw|status\n");
		exit(1);
	}
	if (!is_raw_scsi(argv[1])) error("not a raw scsi device");
	rs = is_mounted(argv[1]);
	if (rs == -1) error("unable to access /etc/mtab");
	if (rs == 1) error("device is mounted");

	zfd = open(argv[1],0);
	if (zfd < 0) error("unable to open device");

	if (!is_zip(zfd)) error("device is not an IOMEGA ZIP drive");

	if (!strcmp(argv[2],"eject")) eject(zfd);
	else if (!strcmp(argv[2],"ro")) pmode(zfd,2,argv[1]);
	else if (!strcmp(argv[2],"rw")) pmode(zfd,0,argv[1]);
	else if (!strcmp(argv[2],"PWRO")) pmode(zfd,3,argv[1]);
	else if (!strcmp(argv[2],"status")) dostatus(zfd,argv[1]);
        else error("unknown command");

	close(zfd);
}
