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

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

diff -u --recursive --new-file v1.3.28/linux/drivers/scsi/st.c linux/drivers/scsi/st.c
@@ -11,7 +11,7 @@
   Copyright 1992, 1993, 1994, 1995 Kai Makisara
 		 email Kai.Makisara@metla.fi
 
-  Last modified: Sun Sep 10 20:33:24 1995 by makisara@kai.makisara.fi
+  Last modified: Tue Sep 19 21:51:41 1995 by root@kai.makisara.fi
   Some small formal changes - aeb, 950809
 */
 #ifdef MODULE
@@ -74,14 +74,16 @@
 #define TAPE_NR(x) (MINOR(x) & 127)
 
 static int st_nbr_buffers;
-static int st_req_nbr_buffers;
 static ST_buffer **st_buffers;
 static int st_buffer_size = ST_BUFFER_SIZE;
 static int st_write_threshold = ST_WRITE_THRESHOLD;
 static int st_max_buffers = ST_MAX_BUFFERS;
 
+#define MAX_SCSI_TAPES 8
 Scsi_Tape * scsi_tapes = NULL;
 
+static ST_buffer *new_tape_buffer(int);
+
 static int st_init(void);
 static int st_attach(Scsi_Device *);
 static int st_detect(Scsi_Device *);
@@ -189,7 +191,7 @@
     if ((STp->buffer)->writing)
       SCpnt->request.rq_status = RQ_INACTIVE;
     else
-      SCpnt->request.rq_status = RQ_SCSI_BUSY;
+      SCpnt->request.rq_status = RQ_SCSI_DONE;
     if (!(STp->buffer)->writing || STp->write_pending)
       wake_up( &(STp->waiting) );
     STp->write_pending = 0;
@@ -257,13 +259,13 @@
   unsigned int flags;
 
   SCpnt = allocate_device(NULL, STp->device, 1);
+  SCpnt->request.rq_dev = devt;
   cmd[0] = SPACE;
   cmd[1] = ((SCpnt->lun << 5) & 0xe0) | 0x01; /* Space FileMarks */
   cmd[2] = cmd[3] = cmd[4] = 0xff;  /* -1 filemarks */
   cmd[5] = 0;
 
-  SCpnt->request.rq_status = RQ_ACTIVE;
-  SCpnt->request.rq_dev = devt;
+  SCpnt->request.rq_status = RQ_SCSI_BUSY;
   scsi_do_cmd(SCpnt,
 	      (void *) cmd, (void *) (STp->buffer)->b_data, 0,
 	      st_sleep_done, ST_TIMEOUT, MAX_RETRIES);
@@ -271,7 +273,7 @@
   /* need to do the check with interrupts off. -RAB */
   save_flags(flags);
   cli();
-  if (TAPE_NR(SCpnt->request.rq_dev) == dev)
+  if (SCpnt->request.rq_status != RQ_SCSI_DONE)
     sleep_on( &(STp->waiting) );
   restore_flags(flags);
   
@@ -336,7 +338,7 @@
     cmd[2] = blks >> 16;
     cmd[3] = blks >> 8;
     cmd[4] = blks;
-    SCpnt->request.rq_status = RQ_ACTIVE;
+    SCpnt->request.rq_status = RQ_SCSI_BUSY;
     SCpnt->request.rq_dev = devt;
     scsi_do_cmd (SCpnt,
 		 (void *) cmd, (STp->buffer)->b_data, transfer,
@@ -345,7 +347,7 @@
     /* this must be done with interrupts off */
     save_flags (flags);
     cli();
-    if (TAPE_NR(SCpnt->request.rq_dev) == dev)
+    if (SCpnt->request.rq_status != RQ_SCSI_DONE)
       sleep_on( &(STp->waiting) );
     restore_flags(flags);
  
@@ -388,7 +390,6 @@
   STp = &(scsi_tapes[dev]);
   STbuffer = STp->buffer;
 
-
   /*
    * If there was a bus reset, block further access
    * to this device.
@@ -455,10 +456,14 @@
       if (!st_buffers[i]->in_use)
 	break;
     if (i >= st_nbr_buffers) {
-      printk("st%d: No free buffers.\n", dev);
-      return (-EBUSY);
+      STp->buffer = new_tape_buffer(FALSE);
+      if (STp->buffer == NULL) {
+	printk("st%d: No free buffers.\n", dev);
+	return (-EBUSY);
+      }
     }
-    STp->buffer = st_buffers[i];
+    else
+      STp->buffer = st_buffers[i];
     (STp->buffer)->in_use = 1;
     (STp->buffer)->writing = 0;
     STp->in_use = 1;
@@ -479,6 +484,7 @@
 #endif
 
     SCpnt = allocate_device(NULL, STp->device, 1);
+    SCpnt->request.rq_dev = devt;
     if (!SCpnt) {
       printk("st%d: Tape request not allocated", dev);
       return (-EBUSY);
@@ -487,8 +493,7 @@
     memset ((void *) &cmd[0], 0, 10);
     cmd[0] = TEST_UNIT_READY;
     cmd[1] = (SCpnt->lun << 5) & 0xe0;
-    SCpnt->request.rq_status = RQ_ACTIVE;
-    SCpnt->request.rq_dev = devt;
+    SCpnt->request.rq_status = RQ_SCSI_BUSY;
     scsi_do_cmd(SCpnt,
 		(void *) cmd, (void *) (STp->buffer)->b_data,
 		0, st_sleep_done, ST_LONG_TIMEOUT,
@@ -497,7 +502,7 @@
     /* this must be done with interrupts off */
     save_flags (processor_flags);
     cli();
-    if (TAPE_NR(SCpnt->request.rq_dev) == dev)
+    if (SCpnt->request.rq_status != RQ_SCSI_DONE)
       sleep_on( &(STp->waiting) );
     restore_flags(processor_flags);
 
@@ -507,8 +512,7 @@
       memset ((void *) &cmd[0], 0, 10);
       cmd[0] = TEST_UNIT_READY;
       cmd[1] = (SCpnt->lun << 5) & 0xe0;
-      SCpnt->request.rq_status = RQ_ACTIVE;
-      SCpnt->request.rq_dev = devt;
+      SCpnt->request.rq_status = RQ_SCSI_BUSY;
       scsi_do_cmd(SCpnt,
 		  (void *) cmd, (void *) (STp->buffer)->b_data,
 		  0, st_sleep_done, ST_LONG_TIMEOUT,
@@ -517,7 +521,7 @@
       /* this must be done with interrupts off */
       save_flags (processor_flags);
       cli();
-      if (TAPE_NR(SCpnt->request.rq_dev) == dev)
+      if (SCpnt->request.rq_status != RQ_SCSI_DONE)
 	sleep_on( &(STp->waiting) );
       restore_flags(processor_flags);
 
@@ -551,8 +555,7 @@
     memset ((void *) &cmd[0], 0, 10);
     cmd[0] = READ_BLOCK_LIMITS;
     cmd[1] = (SCpnt->lun << 5) & 0xe0;
-    SCpnt->request.rq_status = RQ_ACTIVE;
-    SCpnt->request.rq_dev = devt;
+    SCpnt->request.rq_status = RQ_SCSI_BUSY;
     scsi_do_cmd(SCpnt,
 		(void *) cmd, (void *) (STp->buffer)->b_data,
 		6, st_sleep_done, ST_TIMEOUT, MAX_READY_RETRIES);
@@ -560,7 +563,7 @@
     /* this must be done with interrupts off */
     save_flags (processor_flags);
     cli();
-    if (TAPE_NR(SCpnt->request.rq_dev) == dev)
+    if (SCpnt->request.rq_status != RQ_SCSI_DONE)
       sleep_on( &(STp->waiting) );
     restore_flags(processor_flags);
 
@@ -587,8 +590,7 @@
     cmd[0] = MODE_SENSE;
     cmd[1] = (SCpnt->lun << 5) & 0xe0;
     cmd[4] = 12;
-    SCpnt->request.rq_status = RQ_ACTIVE;
-    SCpnt->request.rq_dev = devt;
+    SCpnt->request.rq_status = RQ_SCSI_BUSY;
     scsi_do_cmd(SCpnt,
 		(void *) cmd, (void *) (STp->buffer)->b_data,
 		12, st_sleep_done, ST_TIMEOUT, MAX_READY_RETRIES);
@@ -596,7 +598,7 @@
     /* this must be done with interrupts off */
     save_flags (processor_flags);
     cli();
-    if (TAPE_NR(SCpnt->request.rq_dev) == dev)
+    if (SCpnt->request.rq_status != RQ_SCSI_DONE)
       sleep_on( &(STp->waiting) );
     restore_flags(processor_flags);
 
@@ -714,13 +716,13 @@
 
       if (result == 0 || result == (-ENOSPC)) {
 	SCpnt = allocate_device(NULL, STp->device, 1);
+	SCpnt->request.rq_dev = devt;
 
 	memset(cmd, 0, 10);
 	cmd[0] = WRITE_FILEMARKS;
 	cmd[1] = (SCpnt->lun << 5) & 0xe0;
 	cmd[4] = 1 + STp->two_fm;
-	SCpnt->request.rq_status = RQ_ACTIVE;
-	SCpnt->request.rq_dev = devt;
+	SCpnt->request.rq_status = RQ_SCSI_BUSY;
 	scsi_do_cmd( SCpnt,
 		    (void *) cmd, (void *) (STp->buffer)->b_data,
 		    0, st_sleep_done, ST_TIMEOUT, MAX_WRITE_RETRIES);
@@ -728,7 +730,7 @@
 	/* this must be done with interrupts off */
 	save_flags (flags);
 	cli();
-	if (TAPE_NR(SCpnt->request.rq_dev) == dev)
+	if (SCpnt->request.rq_status != RQ_SCSI_DONE)
 	  sleep_on( &(STp->waiting) );
 	restore_flags(flags);
 
@@ -867,6 +869,7 @@
       write_threshold--;
 
     SCpnt = allocate_device(NULL, STp->device, 1);
+    SCpnt->request.rq_dev = devt;
 
     total = count;
 
@@ -902,8 +905,7 @@
       cmd[2] = blks >> 16;
       cmd[3] = blks >> 8;
       cmd[4] = blks;
-      SCpnt->request.rq_status = RQ_ACTIVE;
-      SCpnt->request.rq_dev = devt;
+      SCpnt->request.rq_status = RQ_SCSI_BUSY;
       scsi_do_cmd (SCpnt,
 		   (void *) cmd, (STp->buffer)->b_data, transfer,
 		   st_sleep_done, ST_TIMEOUT, MAX_WRITE_RETRIES);
@@ -911,7 +913,7 @@
       /* this must be done with interrupts off */
       save_flags (flags);
       cli();
-      if (TAPE_NR(SCpnt->request.rq_dev) == dev)
+      if (SCpnt->request.rq_status != RQ_SCSI_DONE)
 	sleep_on( &(STp->waiting) );
       restore_flags(flags);
 
@@ -1018,8 +1020,7 @@
       cmd[2] = blks >> 16;
       cmd[3] = blks >> 8;
       cmd[4] = blks;
-      SCpnt->request.rq_status = RQ_ACTIVE;
-      SCpnt->request.rq_dev = devt;
+      SCpnt->request.rq_status = RQ_SCSI_BUSY;
       STp->write_pending = 1;
       scsi_do_cmd (SCpnt,
 		   (void *) cmd, (STp->buffer)->b_data,
@@ -1090,6 +1091,7 @@
     STp->rw = ST_READING;
 
     SCpnt = allocate_device(NULL, STp->device, 1);
+    SCpnt->request.rq_dev = devt;
 
     for (total = 0; total < count; ) {
 
@@ -1118,8 +1120,7 @@
 	cmd[3] = blks >> 8;
 	cmd[4] = blks;
 
-	SCpnt->request.rq_status = RQ_ACTIVE;
-	SCpnt->request.rq_dev = devt;
+	SCpnt->request.rq_status = RQ_SCSI_BUSY;
 	scsi_do_cmd (SCpnt,
 		     (void *) cmd, (STp->buffer)->b_data,
 		     bytes, st_sleep_done, ST_TIMEOUT, MAX_RETRIES);
@@ -1127,7 +1128,7 @@
 	/* this must be done with interrupts off */
 	save_flags (flags);
 	cli();
-	if (TAPE_NR(SCpnt->request.rq_dev) == dev)
+	if (SCpnt->request.rq_status != RQ_SCSI_DONE)
 	  sleep_on( &(STp->waiting) );
 	restore_flags(flags);
 
@@ -1687,9 +1688,9 @@
      }
 
    SCpnt = allocate_device(NULL, STp->device, 1);
-   cmd[1] |= (SCpnt->lun << 5) & 0xe0;
-   SCpnt->request.rq_status = RQ_ACTIVE;
    SCpnt->request.rq_dev = devt;
+   cmd[1] |= (SCpnt->lun << 5) & 0xe0;
+   SCpnt->request.rq_status = RQ_SCSI_BUSY;
    scsi_do_cmd(SCpnt,
 	       (void *) cmd, (void *) (STp->buffer)->b_data, datalen,
 	       st_sleep_done, timeout, MAX_RETRIES);
@@ -1697,7 +1698,7 @@
    /* this must be done with interrupts off */
    save_flags (flags);
    cli();
-   if (TAPE_NR(SCpnt->request.rq_dev) == dev)
+   if (SCpnt->request.rq_status != RQ_SCSI_DONE)
      sleep_on( &(STp->waiting) );
    restore_flags(flags);
 
@@ -1860,30 +1861,38 @@
      if (i)
 	return i;
 
-     /*
-      * If there was a bus reset, block further access
-      * to this device.  If the user wants to rewind the tape,
-      * then reset the flag and allow access again.
-      */
-     if( STp->device->was_reset) 
-       {
-	 if(mtc.mt_op != MTREW && 
-	    mtc.mt_op != MTOFFL &&
-	    mtc.mt_op != MTRESET && 
-	    mtc.mt_op != MTRETEN && 
-	    mtc.mt_op != MTEOM)
-	   return (-EIO);
-	 STp->device->was_reset = 0;
-       }
-
      memcpy_fromfs((char *) &mtc, (char *)arg, sizeof(struct mtop));
 
-     i = flush_buffer(inode, file, mtc.mt_op == MTSEEK ||
-		      mtc.mt_op == MTREW || mtc.mt_op == MTOFFL ||
-		      mtc.mt_op == MTRETEN || mtc.mt_op == MTEOM ||
-		      mtc.mt_op == MTLOCK || mtc.mt_op == MTLOAD);
-     if (i < 0)
-       return i;
+     if (!(STp->device)->was_reset) {
+       i = flush_buffer(inode, file, mtc.mt_op == MTSEEK ||
+			mtc.mt_op == MTREW || mtc.mt_op == MTOFFL ||
+			mtc.mt_op == MTRETEN || mtc.mt_op == MTEOM ||
+			mtc.mt_op == MTLOCK || mtc.mt_op == MTLOAD);
+       if (i < 0)
+	 return i;
+     }
+     else {
+       /*
+	* If there was a bus reset, block further access
+	* to this device.  If the user wants to rewind the tape,
+	* then reset the flag and allow access again.
+	*/
+       if(mtc.mt_op != MTREW && 
+	  mtc.mt_op != MTOFFL &&
+	  mtc.mt_op != MTRETEN && 
+	  mtc.mt_op != MTERASE &&
+	  mtc.mt_op != MTSEEK &&
+	  mtc.mt_op != MTEOM)
+	 return (-EIO);
+       STp->device->was_reset = 0;
+       if (STp->door_locked != ST_UNLOCKED) {
+	 if (st_int_ioctl(inode, file, MTLOCK, 0)) {
+	   printk("st%d: Could not relock door after bus reset.\n", dev);
+	   STp->door_locked = ST_UNLOCKED;
+	 }
+       }
+     }
+
      if (mtc.mt_op != MTNOP && mtc.mt_op != MTSETBLK &&
 	 mtc.mt_op != MTSETDENSITY && mtc.mt_op != MTWSM &&
 	 mtc.mt_op != MTSETDRVBUFFER)
@@ -1970,6 +1979,7 @@
        return i;
 
      SCpnt = allocate_device(NULL, STp->device, 1);
+     SCpnt->request.rq_dev = devt;
 
      memset (scmd, 0, 10);
      if ((STp->device)->scsi_level < SCSI_2) {
@@ -1980,8 +1990,7 @@
        scmd[0] = READ_POSITION;
        scmd[1] = 1;
      }
-     SCpnt->request.rq_status = RQ_ACTIVE;
-     SCpnt->request.rq_dev = devt;
+     SCpnt->request.rq_status = RQ_SCSI_BUSY;
      scmd[1] |= (SCpnt->lun << 5) & 0xe0;
      scsi_do_cmd(SCpnt,
 		 (void *) scmd, (void *) (STp->buffer)->b_data,
@@ -1990,7 +1999,7 @@
      /* this must be done with interrupts off */
      save_flags (flags);
      cli();
-     if (TAPE_NR(SCpnt->request.rq_dev) == dev)
+     if (SCpnt->request.rq_status != RQ_SCSI_DONE)
        sleep_on( &(STp->waiting) );
      restore_flags(flags);
 
@@ -2026,6 +2035,44 @@
 }
 
 
+/* Try to allocate a new tape buffer */
+	static ST_buffer *
+new_tape_buffer( int from_initialization )
+{
+  int priority;
+  ST_buffer *tb;
+
+  if (st_nbr_buffers >= st_template.dev_max)
+    return NULL;  /* Should never happen */
+
+  if (from_initialization)
+    priority = GFP_ATOMIC;
+  else
+    priority = GFP_KERNEL;
+  tb = (ST_buffer *)scsi_init_malloc(sizeof(ST_buffer), priority);
+  if (tb) {
+    tb->b_data = (unsigned char *)scsi_init_malloc(st_buffer_size,
+						   priority | GFP_DMA);
+    if (!tb->b_data) {
+      scsi_init_free((char *)tb, sizeof(ST_buffer));
+      tb = NULL;
+    }
+  }
+  if (!tb) {
+    printk("st: Can't allocate new tape buffer (nbr %d).\n", st_nbr_buffers);
+    return NULL;
+  }
+
+#if DEBUG
+  printk("st: Allocated tape buffer %d.\n", st_nbr_buffers);
+#endif
+  tb->in_use = 0;
+  tb->writing = 0;
+  st_buffers[st_nbr_buffers++] = tb;
+  return tb;
+}
+
+
 /* Set the boot options. Syntax: st=xxx,yyy
    where xxx is buffer size in 512 byte blocks and yyy is write threshold
    in 512 byte blocks. */
@@ -2120,6 +2167,9 @@
 {
   int i;
   Scsi_Tape * STp;
+#if !ST_RUNTIME_BUFFERS
+  int target_nbr;
+#endif
 
   if (st_template.dev_noticed == 0) return 0;
 
@@ -2132,15 +2182,17 @@
   }
 
   if (scsi_tapes) return 0;
-  scsi_tapes = (Scsi_Tape *) scsi_init_malloc(
-  		(st_template.dev_noticed + ST_EXTRA_DEVS) * 
-					      sizeof(Scsi_Tape), GFP_ATOMIC);
+  st_template.dev_max = st_template.dev_noticed + ST_EXTRA_DEVS;
+  if (st_template.dev_max < MAX_SCSI_TAPES)
+    st_template.dev_max = MAX_SCSI_TAPES;
+  scsi_tapes =
+    (Scsi_Tape *) scsi_init_malloc(st_template.dev_max * sizeof(Scsi_Tape),
+				   GFP_ATOMIC);
   if (scsi_tapes == NULL) {
     printk("Unable to allocate descriptors for SCSI tapes.\n");
     unregister_chrdev(SCSI_TAPE_MAJOR, "st");
     return 1;
   }
-  st_template.dev_max = st_template.dev_noticed + ST_EXTRA_DEVS;
 
 #if DEBUG
   printk("st: Buffer size %d bytes, write threshold %d bytes.\n",
@@ -2168,62 +2220,49 @@
     STp->drv_block = 0;
     STp->moves_after_eof = 1;
     STp->at_sm = 0;
-    STp->mt_status = (struct mtget *) scsi_init_malloc(sizeof(struct mtget), GFP_ATOMIC);
+    STp->mt_status = (struct mtget *) scsi_init_malloc(sizeof(struct mtget),
+						       GFP_ATOMIC);
     /* Initialize status */
     memset((void *) scsi_tapes[i].mt_status, 0, sizeof(struct mtget));
   }
 
   /* Allocate the buffers */
-  st_nbr_buffers = st_template.dev_noticed;
-  if (st_nbr_buffers < ST_EXTRA_DEVS)
-    st_nbr_buffers = ST_EXTRA_DEVS;
-  if (st_nbr_buffers > st_max_buffers)
-    st_nbr_buffers = st_max_buffers;
-  st_req_nbr_buffers = st_nbr_buffers;
-  st_buffers = (ST_buffer **) scsi_init_malloc(st_nbr_buffers * 
-					       sizeof(ST_buffer *),
-					       GFP_ATOMIC);
+  st_buffers =
+    (ST_buffer **) scsi_init_malloc(st_template.dev_max * sizeof(ST_buffer *),
+				    GFP_ATOMIC);
   if (st_buffers == NULL) {
     printk("Unable to allocate tape buffer pointers.\n");
     unregister_chrdev(SCSI_TAPE_MAJOR, "st");
     scsi_init_free((char *) scsi_tapes,
-		   (st_template.dev_noticed + ST_EXTRA_DEVS) 
-		   * sizeof(Scsi_Tape));
+		   st_template.dev_max * sizeof(Scsi_Tape));
     return 1;
   }
 
-  /* FIXME - if we are hitting this because we are loading a tape module
-  as a loadable driver, we should not use kmalloc - it will allocate
-  a 64Kb region in order to buffer about 32Kb.  Try using 31 blocks
-  instead. */
-  
-  for (i=0; i < st_nbr_buffers; i++) {
-    st_buffers[i] = (ST_buffer *) scsi_init_malloc(sizeof(ST_buffer) - 
-						   1 + st_buffer_size,
-						   GFP_ATOMIC | GFP_DMA);
-    if (st_buffers[i] == NULL) {
-      printk("Not enough memory for buffer %d.\n", i);
+#if ST_RUNTIME_BUFFERS
+  st_nbr_buffers = 0;
+#else
+  target_nbr = st_template.dev_noticed;
+  if (target_nbr < ST_EXTRA_DEVS)
+    target_nbr = ST_EXTRA_DEVS;
+  if (target_nbr > st_max_buffers)
+    target_nbr = st_max_buffers;
+
+  for (i=st_nbr_buffers=0; i < target_nbr; i++) {
+    if (!new_tape_buffer(TRUE)) {
       if (i == 0) {
+	printk("Can't continue without at least one tape buffer.\n");
 	unregister_chrdev(SCSI_TAPE_MAJOR, "st");
-	scsi_init_free((char *) scsi_tapes,
-		       (st_template.dev_noticed + ST_EXTRA_DEVS) 
-		       * sizeof(Scsi_Tape));
 	scsi_init_free((char *) st_buffers,
-		       st_nbr_buffers * sizeof(ST_buffer *));
+		       st_template.dev_max * sizeof(ST_buffer *));
+	scsi_init_free((char *) scsi_tapes,
+		       st_template.dev_max * sizeof(Scsi_Tape));
 	return 1;
       }
-      st_nbr_buffers = i;
       printk("Number of tape buffers adjusted.\n");
       break;
     }
-    else {
-#if DEBUG
-/*    printk("st: Buffer address: %p\n", st_buffers[i]); */
-#endif
-      st_buffers[i]->in_use = 0;
-      st_buffers[i]->writing = 0;
-    }
   }
+#endif
   return 0;
 }
 
@@ -2265,18 +2304,17 @@
   st_registered--;
   if(scsi_tapes != NULL) {
     scsi_init_free((char *) scsi_tapes,
-		   (st_template.dev_noticed + ST_EXTRA_DEVS) 
-		   * sizeof(Scsi_Tape));
+		   st_template.dev_max * sizeof(Scsi_Tape));
 
     if (st_buffers != NULL) {
       for (i=0; i < st_nbr_buffers; i++)
 	if (st_buffers[i] != NULL) {
-	  scsi_init_free((char *) st_buffers[i],
-			 sizeof(ST_buffer) - 1 + st_buffer_size);
+	  scsi_init_free((char *) st_buffers[i]->b_data, st_buffer_size);
+	  scsi_init_free((char *) st_buffers[i], sizeof(ST_buffer));
 	}
       
       scsi_init_free((char *) st_buffers,
-		     st_req_nbr_buffers * sizeof(ST_buffer *));
+		     st_template.dev_max * sizeof(ST_buffer *));
     }
   }
   st_template.dev_max = 0;

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this