patch-2.4.15 linux/drivers/scsi/st.c

Next file: linux/drivers/scsi/st.h
Previous file: linux/drivers/scsi/sd.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.14/linux/drivers/scsi/st.c linux/drivers/scsi/st.c
@@ -12,7 +12,7 @@
    Copyright 1992 - 2001 Kai Makisara
    email Kai.Makisara@metla.fi
 
-   Last modified: Wed Oct  3 22:17:59 2001 by makisara@kai.makisara.local
+   Last modified: Sat Nov  3 19:30:55 2001 by makisara@kai.makisara.local
    Some small formal changes - aeb, 950809
 
    Last modified: 18-JAN-1998 Richard Gooch <rgooch@atnf.csiro.au> Devfs support
@@ -21,7 +21,7 @@
    error handling will be discarded.
  */
 
-static char *verstr = "20011003";
+static char *verstr = "20011103";
 
 #include <linux/module.h>
 
@@ -276,6 +276,17 @@
                                driver_byte(result) & DRIVER_MASK, host_byte(result));
 	}
 
+	if (STp->cln_mode >= EXTENDED_SENSE_START) {
+		if (STp->cln_sense_value)
+			STp->cleaning_req |= ((SRpnt->sr_sense_buffer[STp->cln_mode] &
+					       STp->cln_sense_mask) == STp->cln_sense_value);
+		else
+			STp->cleaning_req |= ((SRpnt->sr_sense_buffer[STp->cln_mode] &
+					       STp->cln_sense_mask) != 0);
+	}
+	if (sense[12] == 0 && sense[13] == 0x17) /* ASC and ASCQ => cleaning requested */
+		STp->cleaning_req = 1;
+
 	if ((sense[0] & 0x70) == 0x70 &&
 	    scode == RECOVERED_ERROR
 #if ST_RECOVERED_WRITE_FATAL
@@ -414,15 +425,6 @@
 	(STp->buffer)->syscall_result = st_chk_result(STp, (STp->buffer)->last_SRpnt);
 	scsi_release_request((STp->buffer)->last_SRpnt);
 
-	if (STbuffer->writing < STbuffer->buffer_bytes)
-#if 0
-		memcpy(STbuffer->b_data,
-		       STbuffer->b_data + STbuffer->writing,
-		       STbuffer->buffer_bytes - STbuffer->writing);
-#else
-		printk(KERN_WARNING
-                       "st: write_behind_check: something left in buffer!\n");
-#endif
 	STbuffer->buffer_bytes -= STbuffer->writing;
 	STps = &(STp->ps[STp->partition]);
 	if (STps->drv_block >= 0) {
@@ -636,47 +638,27 @@
 	return 0;
 }
 
-
-/* Open the device. Needs to be called with BKL only because of incrementing the SCSI host
-   module count. */
-static int st_open(struct inode *inode, struct file *filp)
-{
-	unsigned short st_flags;
-	int i, need_dma_buffer, new_session = FALSE;
-	int retval;
-	unsigned char cmd[MAX_COMMAND_SIZE];
+/* See if the drive is ready and gather information about the tape. Return values:
+   < 0   negative error code from errno.h
+   0     drive ready
+   1     drive not ready (possibly no tape)
+*/
+#define CHKRES_READY      0
+#define CHKRES_NOT_READY  1
+
+static int check_tape(Scsi_Tape *STp, struct file *filp)
+{
+	int i, retval, new_session = FALSE;
+	unsigned char cmd[MAX_COMMAND_SIZE], saved_cleaning;
+	unsigned short st_flags = filp->f_flags;
 	Scsi_Request *SRpnt;
-	Scsi_Tape *STp;
 	ST_mode *STm;
 	ST_partstat *STps;
-	int dev = TAPE_NR(inode->i_rdev);
+	int dev = TAPE_NR(STp->devt);
+	struct inode *inode = filp->f_dentry->d_inode;
 	int mode = TAPE_MODE(inode->i_rdev);
-	unsigned long flags;
-
-	write_lock_irqsave(&st_dev_arr_lock, flags);
-	STp = scsi_tapes[dev];
-	if (dev >= st_template.dev_max || STp == NULL) {
-		write_unlock_irqrestore(&st_dev_arr_lock, flags);
-		return (-ENXIO);
-	}
-
-	if (STp->in_use) {
-		write_unlock_irqrestore(&st_dev_arr_lock, flags);
-		DEB( printk(ST_DEB_MSG "st%d: Device already in use.\n", dev); )
-		return (-EBUSY);
-	}
-	STp->in_use = 1;
-	write_unlock_irqrestore(&st_dev_arr_lock, flags);
-	STp->rew_at_close = STp->autorew_dev = (MINOR(inode->i_rdev) & 0x80) == 0;
-
-	if (STp->device->host->hostt->module)
-		__MOD_INC_USE_COUNT(STp->device->host->hostt->module);
-	STp->device->access_count++;
 
-	if (!scsi_block_when_processing_errors(STp->device)) {
-		retval = (-ENXIO);
-		goto err_out;
-	}
+	STp->ready = ST_READY;
 
 	if (mode != STp->current_mode) {
                 DEBC(printk(ST_DEB_MSG "st%d: Mode change from %d to %d.\n",
@@ -686,54 +668,11 @@
 	}
 	STm = &(STp->modes[STp->current_mode]);
 
-	/* Allocate a buffer for this user */
-	need_dma_buffer = STp->restr_dma;
-	write_lock_irqsave(&st_dev_arr_lock, flags);
-	for (i = 0; i < st_nbr_buffers; i++)
-		if (!st_buffers[i]->in_use &&
-		    (!need_dma_buffer || st_buffers[i]->dma)) {
-			STp->buffer = st_buffers[i];
-			(STp->buffer)->in_use = 1;
-			break;
-		}
-	write_unlock_irqrestore(&st_dev_arr_lock, flags);
-	if (i >= st_nbr_buffers) {
-		STp->buffer = new_tape_buffer(FALSE, need_dma_buffer, TRUE);
-		if (STp->buffer == NULL) {
-			printk(KERN_WARNING "st%d: Can't allocate tape buffer.\n", dev);
-			retval = (-EBUSY);
-			goto err_out;
-		}
-	}
-
-	(STp->buffer)->writing = 0;
-	(STp->buffer)->syscall_result = 0;
-	(STp->buffer)->use_sg = STp->device->host->sg_tablesize;
-
-	/* Compute the usable buffer size for this SCSI adapter */
-	if (!(STp->buffer)->use_sg)
-		(STp->buffer)->buffer_size = (STp->buffer)->sg[0].length;
-	else {
-		for (i = 0, (STp->buffer)->buffer_size = 0; i < (STp->buffer)->use_sg &&
-		     i < (STp->buffer)->sg_segs; i++)
-			(STp->buffer)->buffer_size += (STp->buffer)->sg[i].length;
-	}
-
-	st_flags = filp->f_flags;
-	STp->write_prot = ((st_flags & O_ACCMODE) == O_RDONLY);
-
-	STp->dirty = 0;
-	for (i = 0; i < ST_NBR_PARTITIONS; i++) {
-		STps = &(STp->ps[i]);
-		STps->rw = ST_IDLE;
-	}
-	STp->ready = ST_READY;
-	STp->recover_count = 0;
-	DEB( STp->nbr_waits = STp->nbr_finished = 0; )
-
 	memset((void *) &cmd[0], 0, MAX_COMMAND_SIZE);
 	cmd[0] = TEST_UNIT_READY;
 
+	saved_cleaning = STp->cleaning_req;
+	STp->cleaning_req = 0;
 	SRpnt = st_do_scsi(NULL, STp, cmd, 0, SCSI_DATA_NONE, STp->long_timeout,
 			   MAX_READY_RETRIES, TRUE);
 	if (!SRpnt) {
@@ -742,7 +681,7 @@
 	}
 
 	if ((SRpnt->sr_sense_buffer[0] & 0x70) == 0x70 &&
-	    (SRpnt->sr_sense_buffer[2] & 0x0f) == UNIT_ATTENTION) {	/* New media? */
+	    (SRpnt->sr_sense_buffer[2] & 0x0f) == UNIT_ATTENTION) { /* New media? */
 
 		/* Flush the queued UNIT ATTENTION sense data */
 		for (i=0; i < 10; i++) {
@@ -771,6 +710,8 @@
 		}
 		new_session = TRUE;
 	}
+	else
+		STp->cleaning_req |= saved_cleaning;
 
 	if ((STp->buffer)->syscall_result != 0) {
 		if ((STp->device)->scsi_level >= SCSI_2 &&
@@ -788,7 +729,7 @@
 		STp->ps[0].drv_file = STp->ps[0].drv_block = (-1);
 		STp->partition = STp->new_partition = 0;
 		STp->door_locked = ST_UNLOCKED;
-		return 0;
+		return CHKRES_NOT_READY;
 	}
 
 	if (STp->omit_blklims)
@@ -869,7 +810,8 @@
 
                 DEBC(printk(ST_DEB_MSG "st%d: Write protected\n", dev));
 
-		if ((st_flags & O_ACCMODE) == O_WRONLY || (st_flags & O_ACCMODE) == O_RDWR) {
+		if ((st_flags & O_ACCMODE) == O_WRONLY ||
+		    (st_flags & O_ACCMODE) == O_RDWR) {
 			retval = (-EROFS);
 			goto err_out;
 		}
@@ -904,6 +846,95 @@
 		}
 	}
 
+	return CHKRES_READY;
+
+ err_out:
+	return retval;
+}
+
+
+/* Open the device. Needs to be called with BKL only because of incrementing the SCSI host
+   module count. */
+static int st_open(struct inode *inode, struct file *filp)
+{
+	int i, need_dma_buffer;
+	int retval = (-EIO);
+	Scsi_Tape *STp;
+	ST_partstat *STps;
+	int dev = TAPE_NR(inode->i_rdev);
+	unsigned long flags;
+
+	write_lock_irqsave(&st_dev_arr_lock, flags);
+	STp = scsi_tapes[dev];
+	if (dev >= st_template.dev_max || STp == NULL) {
+		write_unlock_irqrestore(&st_dev_arr_lock, flags);
+		return (-ENXIO);
+	}
+
+	if (STp->in_use) {
+		write_unlock_irqrestore(&st_dev_arr_lock, flags);
+		DEB( printk(ST_DEB_MSG "st%d: Device already in use.\n", dev); )
+		return (-EBUSY);
+	}
+	STp->in_use = 1;
+	write_unlock_irqrestore(&st_dev_arr_lock, flags);
+	STp->rew_at_close = STp->autorew_dev = (MINOR(inode->i_rdev) & 0x80) == 0;
+
+	if (STp->device->host->hostt->module)
+		__MOD_INC_USE_COUNT(STp->device->host->hostt->module);
+	STp->device->access_count++;
+
+	if (!scsi_block_when_processing_errors(STp->device)) {
+		retval = (-ENXIO);
+		goto err_out;
+	}
+
+	/* Allocate a buffer for this user */
+	need_dma_buffer = STp->restr_dma;
+	write_lock_irqsave(&st_dev_arr_lock, flags);
+	for (i = 0; i < st_nbr_buffers; i++)
+		if (!st_buffers[i]->in_use &&
+		    (!need_dma_buffer || st_buffers[i]->dma)) {
+			STp->buffer = st_buffers[i];
+			(STp->buffer)->in_use = 1;
+			break;
+		}
+	write_unlock_irqrestore(&st_dev_arr_lock, flags);
+	if (i >= st_nbr_buffers) {
+		STp->buffer = new_tape_buffer(FALSE, need_dma_buffer, TRUE);
+		if (STp->buffer == NULL) {
+			printk(KERN_WARNING "st%d: Can't allocate tape buffer.\n", dev);
+			retval = (-EBUSY);
+			goto err_out;
+		}
+	}
+
+	(STp->buffer)->writing = 0;
+	(STp->buffer)->syscall_result = 0;
+	(STp->buffer)->use_sg = STp->device->host->sg_tablesize;
+
+	/* Compute the usable buffer size for this SCSI adapter */
+	if (!(STp->buffer)->use_sg)
+		(STp->buffer)->buffer_size = (STp->buffer)->sg[0].length;
+	else {
+		for (i = 0, (STp->buffer)->buffer_size = 0; i < (STp->buffer)->use_sg &&
+		     i < (STp->buffer)->sg_segs; i++)
+			(STp->buffer)->buffer_size += (STp->buffer)->sg[i].length;
+	}
+
+	STp->write_prot = ((filp->f_flags & O_ACCMODE) == O_RDONLY);
+
+	STp->dirty = 0;
+	for (i = 0; i < ST_NBR_PARTITIONS; i++) {
+		STps = &(STp->ps[i]);
+		STps->rw = ST_IDLE;
+	}
+	STp->recover_count = 0;
+	DEB( STp->nbr_waits = STp->nbr_finished = 0; )
+
+	retval = check_tape(STp, filp);
+	if (retval < 0)
+		goto err_out;
 	return 0;
 
  err_out:
@@ -1819,7 +1850,7 @@
 	       dev, STm->defaults_for_writes, STp->omit_blklims, STp->can_partitions,
 	       STp->scsi2_logical);
 	printk(KERN_INFO
-	       "st%d:    sysv: %d\n", dev, STm->sysv);
+	       "st%d:    sysv: %d nowait: %d\n", dev, STm->sysv, STp->immediate);
         DEB(printk(KERN_INFO
                    "st%d:    debugging: %d\n",
                    dev, debugging);)
@@ -1856,6 +1887,7 @@
 		if ((STp->device)->scsi_level >= SCSI_2)
 			STp->can_partitions = (options & MT_ST_CAN_PARTITIONS) != 0;
 		STp->scsi2_logical = (options & MT_ST_SCSI2LOGICAL) != 0;
+		STp->immediate = (options & MT_ST_NOWAIT) != 0;
 		STm->sysv = (options & MT_ST_SYSV) != 0;
 		DEB( debugging = (options & MT_ST_DEBUGGING) != 0; )
 		st_log_options(STp, STm, dev);
@@ -1884,6 +1916,8 @@
 			STp->can_partitions = value;
 		if ((options & MT_ST_SCSI2LOGICAL) != 0)
 			STp->scsi2_logical = value;
+		if ((options & MT_ST_NOWAIT) != 0)
+			STp->immediate = value;
 		if ((options & MT_ST_SYSV) != 0)
 			STm->sysv = value;
                 DEB(
@@ -1922,6 +1956,17 @@
 			printk(KERN_INFO "st%d: Normal timeout set to %d seconds.\n",
                                dev, value);
 		}
+	} else if (code == MT_ST_SET_CLN) {
+		value = (options & ~MT_ST_OPTIONS) & 0xff;
+		if (value != 0 &&
+		    value < EXTENDED_SENSE_START && value >= SCSI_SENSE_BUFFERSIZE)
+			return (-EINVAL);
+		STp->cln_mode = value;
+		STp->cln_sense_mask = (options >> 8) & 0xff;
+		STp->cln_sense_value = (options >> 16) & 0xff;
+		printk(KERN_INFO
+		       "st%d: Cleaning request mode %d, mask %02x, value %02x\n",
+		       dev, value, STp->cln_sense_mask, STp->cln_sense_value);
 	} else if (code == MT_ST_DEF_OPTIONS) {
 		code = (options & ~MT_ST_CLEAR_DEFAULT);
 		value = (options & MT_ST_CLEAR_DEFAULT);
@@ -2099,6 +2144,78 @@
 	STp->compression_changed = TRUE;
 	return 0;
 }
+
+
+/* Process the load and unload commands (does unload if the load code is zero) */
+static int do_load_unload(Scsi_Tape *STp, struct file *filp, int load_code)
+{
+	int retval = (-EIO), timeout;
+	DEB(int dev = TAPE_NR(STp->devt);)
+	unsigned char cmd[MAX_COMMAND_SIZE];
+	ST_partstat *STps;
+	Scsi_Request *SRpnt;
+
+	if (STp->ready != ST_READY && !load_code) {
+		if (STp->ready == ST_NO_TAPE)
+			return (-ENOMEDIUM);
+		else
+			return (-EIO);
+	}
+
+	memset(cmd, 0, MAX_COMMAND_SIZE);
+	cmd[0] = START_STOP;
+	if (load_code)
+		cmd[4] |= 1;
+	/*
+	 * If arg >= 1 && arg <= 6 Enhanced load/unload in HP C1553A
+	 */
+	if (load_code >= 1 + MT_ST_HPLOADER_OFFSET
+	    && load_code <= 6 + MT_ST_HPLOADER_OFFSET) {
+		DEBC(printk(ST_DEB_MSG "st%d: Enhanced %sload slot %2ld.\n",
+			    dev, (cmd[4]) ? "" : "un",
+			    load_code - MT_ST_HPLOADER_OFFSET));
+		cmd[3] = load_code - MT_ST_HPLOADER_OFFSET; /* MediaID field of C1553A */
+	}
+	if (STp->immediate) {
+		cmd[1] = 1;	/* Don't wait for completion */
+		timeout = STp->timeout;
+	}
+	else
+		timeout = STp->long_timeout;
+
+	DEBC(
+		if (!load_code)
+		printk(ST_DEB_MSG "st%d: Unloading tape.\n", dev);
+		else
+		printk(ST_DEB_MSG "st%d: Loading tape.\n", dev);
+		);
+
+	SRpnt = st_do_scsi(NULL, STp, cmd, 0, SCSI_DATA_NONE,
+			   timeout, MAX_RETRIES, TRUE);
+	if (!SRpnt)
+		return (STp->buffer)->syscall_result;
+
+	retval = (STp->buffer)->syscall_result;
+	scsi_release_request(SRpnt);
+
+	if (!retval) {	/* SCSI command successful */
+
+		if (!load_code)
+			STp->rew_at_close = 0;
+		else
+			STp->rew_at_close = STp->autorew_dev;
+
+		retval = check_tape(STp, filp);
+		if (retval > 0)
+			retval = 0;
+	}
+	else {
+		STps = &(STp->ps[STp->partition]);
+		STps->drv_file = STps->drv_block = (-1);
+	}
+
+	return retval;
+}
 
 
 /* Internal ioctl function */
@@ -2106,7 +2223,7 @@
 {
 	int timeout;
 	long ltmp;
-	int i, ioctl_result;
+	int ioctl_result;
 	int chg_eof = TRUE;
 	unsigned char cmd[MAX_COMMAND_SIZE];
 	Scsi_Request *SRpnt;
@@ -2115,7 +2232,7 @@
 	int datalen = 0, direction = SCSI_DATA_NONE;
 	int dev = TAPE_NR(STp->devt);
 
-	if (STp->ready != ST_READY && cmd_in != MTLOAD) {
+	if (STp->ready != ST_READY) {
 		if (STp->ready == ST_NO_TAPE)
 			return (-ENOMEDIUM);
 		else
@@ -2254,42 +2371,11 @@
 		break;
 	case MTREW:
 		cmd[0] = REZERO_UNIT;
-#if ST_NOWAIT
-		cmd[1] = 1;	/* Don't wait for completion */
-		timeout = STp->timeout;
-#endif
-                DEBC(printk(ST_DEB_MSG "st%d: Rewinding tape.\n", dev));
-		fileno = blkno = at_sm = 0;
-		break;
-	case MTOFFL:
-	case MTLOAD:
-	case MTUNLOAD:
-		cmd[0] = START_STOP;
-		if (cmd_in == MTLOAD)
-			cmd[4] |= 1;
-		/*
-		 * If arg >= 1 && arg <= 6 Enhanced load/unload in HP C1553A
-		 */
-		if (cmd_in != MTOFFL &&
-		    arg >= 1 + MT_ST_HPLOADER_OFFSET
-		    && arg <= 6 + MT_ST_HPLOADER_OFFSET) {
-                        DEBC(printk(ST_DEB_MSG "st%d: Enhanced %sload slot %2ld.\n",
-                                    dev, (cmd[4]) ? "" : "un",
-                                    arg - MT_ST_HPLOADER_OFFSET));
-			cmd[3] = arg - MT_ST_HPLOADER_OFFSET; /* MediaID field of C1553A */
+		if (STp->immediate) {
+			cmd[1] = 1;	/* Don't wait for completion */
+			timeout = STp->timeout;
 		}
-#if ST_NOWAIT
-		cmd[1] = 1;	/* Don't wait for completion */
-		timeout = STp->timeout;
-#else
-		timeout = STp->long_timeout;
-#endif
-                DEBC(
-			if (cmd_in != MTLOAD)
-				printk(ST_DEB_MSG "st%d: Unloading tape.\n", dev);
-			else
-				printk(ST_DEB_MSG "st%d: Loading tape.\n", dev);
-		)
+                DEBC(printk(ST_DEB_MSG "st%d: Rewinding tape.\n", dev));
 		fileno = blkno = at_sm = 0;
 		break;
 	case MTNOP:
@@ -2298,10 +2384,10 @@
 		break;
 	case MTRETEN:
 		cmd[0] = START_STOP;
-#if ST_NOWAIT
-		cmd[1] = 1;	/* Don't wait for completion */
-		timeout = STp->timeout;
-#endif
+		if (STp->immediate) {
+			cmd[1] = 1;	/* Don't wait for completion */
+			timeout = STp->timeout;
+		}
 		cmd[4] = 3;
                 DEBC(printk(ST_DEB_MSG "st%d: Retensioning tape.\n", dev));
 		fileno = blkno = at_sm = 0;
@@ -2331,12 +2417,13 @@
 			return (-EACCES);
 		cmd[0] = ERASE;
 		cmd[1] = 1;	/* To the end of tape */
-#if ST_NOWAIT
-		cmd[1] |= 2;	/* Don't wait for completion */
-		timeout = STp->timeout;
-#else
-		timeout = STp->long_timeout * 8;
-#endif
+		if (STp->immediate) {
+			cmd[1] |= 2;	/* Don't wait for completion */
+			timeout = STp->timeout;
+		}
+		else
+			timeout = STp->long_timeout * 8;
+
                 DEBC(printk(ST_DEB_MSG "st%d: Erasing tape.\n", dev));
 		fileno = blkno = at_sm = 0;
 		break;
@@ -2462,17 +2549,6 @@
 		else if (chg_eof)
 			STps->eof = ST_NOEOF;
 
-
-		if (cmd_in == MTOFFL || cmd_in == MTUNLOAD)
-			STp->rew_at_close = 0;
-		else if (cmd_in == MTLOAD) {
-			STp->rew_at_close = STp->autorew_dev;
-			for (i = 0; i < ST_NBR_PARTITIONS; i++) {
-				STp->ps[i].rw = ST_IDLE;
-				STp->ps[i].last_block_valid = FALSE;
-			}
-			STp->partition = 0;
-		}
 	} else { /* SCSI command was not completely successful. Don't return
                     from this block without releasing the SCSI command block! */
 
@@ -2692,10 +2768,10 @@
                                     dev, STp->partition, partition));
 		}
 	}
-#if ST_NOWAIT
-	scmd[1] |= 1;		/* Don't wait for completion */
-	timeout = STp->timeout;
-#endif
+	if (STp->immediate) {
+		scmd[1] |= 1;		/* Don't wait for completion */
+		timeout = STp->timeout;
+	}
 
 	SRpnt = st_do_scsi(NULL, STp, scmd, 0, SCSI_DATA_NONE,
 			   timeout, MAX_READY_RETRIES, TRUE);
@@ -3073,6 +3149,16 @@
 			goto out;
 		}
 
+		if (mtc.mt_op == MTUNLOAD || mtc.mt_op == MTOFFL) {
+			retval = do_load_unload(STp, file, 0);
+			goto out;
+		}
+
+		if (mtc.mt_op == MTLOAD) {
+			retval = do_load_unload(STp, file, max(1, mtc.mt_count));
+			goto out;
+		}
+
 		if (STp->can_partitions && STp->ready == ST_READY &&
 		    (i = update_partition(STp)) < 0) {
 			retval = i;
@@ -3155,6 +3241,8 @@
                     (STm->do_buffer_writes && STp->block_size != 0) ||
 		    STp->drv_buffer != 0)
 			mt_status.mt_gstat |= GMT_IM_REP_EN(0xffffffff);
+		if (STp->cleaning_req)
+			mt_status.mt_gstat |= GMT_CLN(0xffffffff);
 
 		i = copy_to_user((char *) arg, (char *) &(mt_status),
 				 sizeof(struct mtget));
@@ -3642,6 +3730,7 @@
 	tpnt->two_fm = ST_TWO_FM;
 	tpnt->fast_mteom = ST_FAST_MTEOM;
 	tpnt->scsi2_logical = ST_SCSI2LOGICAL;
+	tpnt->immediate = ST_NOWAIT;
 	tpnt->write_threshold = st_write_threshold;
 	tpnt->default_drvbuffer = 0xff;		/* No forced buffering */
 	tpnt->partition = 0;

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)