patch-2.1.66 linux/drivers/char/ftape/zftape/zftape-ctl.c

Next file: linux/drivers/char/ftape/zftape/zftape-ctl.h
Previous file: linux/drivers/char/ftape/zftape/zftape-buffers.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.65/linux/drivers/char/ftape/zftape/zftape-ctl.c linux/drivers/char/ftape/zftape/zftape-ctl.c
@@ -0,0 +1,1502 @@
+/* 
+ *      Copyright (C) 1996, 1997 Claus-Justus Heine
+
+ 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-ctl.c,v $
+ * $Revision: 1.2.6.2 $
+ * $Date: 1997/11/14 18:07:33 $
+ *
+ *      This file contains the non-read/write zftape functions
+ *      for the QIC-40/80/3010/3020 floppy-tape driver for Linux.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#define __NO_VERSION__
+#include <linux/module.h>
+#ifdef CONFIG_KERNELD
+#include <linux/kerneld.h>
+#endif
+#include <linux/fcntl.h>
+
+#include <linux/zftape.h>
+
+#if LINUX_VERSION_CODE >= KERNEL_VER(2,1,6)
+#include <asm/uaccess.h>
+#else
+#include <asm/segment.h>
+#endif
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-vtbl.h"
+
+/*      Global vars.
+ */
+int zft_write_protected = 0; /* this is when cartridge rdonly or O_RDONLY */
+int zft_header_read     = 0;
+int zft_offline         = 0;
+unsigned int zft_unit   = 0;
+int zft_resid           = 0;
+int zft_mt_compression  = 0;
+
+/*      Local vars.
+ */
+static int going_offline   = 0;
+
+typedef int (mt_fun)(int *argptr);
+typedef int (*mt_funp)(int *argptr);
+typedef struct
+{
+	mt_funp function;
+	unsigned offline         : 1; /* op permitted if offline or no_tape */
+	unsigned write_protected : 1; /* op permitted if write-protected    */
+	unsigned not_formatted   : 1; /* op permitted if tape not formatted */
+	unsigned raw_mode        : 1; /* op permitted if zft_mode == 0    */
+	unsigned need_idle_state : 1; /* need to call def_idle_state        */
+	char     *name;
+} fun_entry;
+
+static mt_fun mt_dummy, mt_reset, mt_fsr, mt_bsr, mt_rew, mt_offl, mt_nop,
+	mt_weof, mt_erase, mt_ras2, mt_setblk, mt_setdensity,
+	mt_seek, mt_tell, mt_reten, mt_eom, mt_fsf, mt_bsf,
+	mt_fsfm, mt_bsfm, mt_setdrvbuffer, mt_compression;
+
+static fun_entry mt_funs[]=
+{ 
+	{mt_reset       , 1, 1, 1, 1, 0, "MT_RESET" }, /*  0 */
+	{mt_fsf         , 0, 1, 0, 0, 1, "MT_FSF"   },
+	{mt_bsf         , 0, 1, 0, 0, 1, "MT_BSF"   },
+	{mt_fsr         , 0, 1, 0, 1, 1, "MT_FSR"   },
+	{mt_bsr         , 0, 1, 0, 1, 1, "MT_BSR"   },
+	{mt_weof        , 0, 0, 0, 0, 0, "MT_WEOF"  }, /*  5 */
+	{mt_rew         , 0, 1, 1, 1, 0, "MT_REW"   },
+	{mt_offl        , 0, 1, 1, 1, 0, "MT_OFFL"  },
+	{mt_nop         , 1, 1, 1, 1, 0, "MT_NOP"   },
+	{mt_reten       , 0, 1, 1, 1, 0, "MT_RETEN" },
+	{mt_bsfm        , 0, 1, 0, 0, 1, "MT_BSFM"  }, /* 10 */
+	{mt_fsfm        , 0, 1, 0, 0, 1, "MT_FSFM"  },
+	{mt_eom         , 0, 1, 0, 0, 1, "MT_EOM"   },
+	{mt_erase       , 0, 0, 0, 1, 0, "MT_ERASE" },
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_RAS1"  },
+	{mt_ras2        , 0, 0, 0, 1, 0, "MT_RAS2"  },
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_RAS3"  },
+	{mt_dummy       , 1, 1, 1, 1, 0, "UNKNOWN"  },
+	{mt_dummy       , 1, 1, 1, 1, 0, "UNKNOWN"  },
+	{mt_dummy       , 1, 1, 1, 1, 0, "UNKNOWN"  },
+	{mt_setblk      , 1, 1, 1, 1, 1, "MT_SETBLK"}, /* 20 */
+	{mt_setdensity  , 1, 1, 1, 1, 0, "MT_SETDENSITY"},
+	{mt_seek        , 0, 1, 0, 1, 1, "MT_SEEK"  },
+	{mt_dummy       , 0, 1, 0, 1, 1, "MT_TELL"  }, /* wr-only ?! */
+	{mt_setdrvbuffer, 1, 1, 1, 1, 0, "MT_SETDRVBUFFER" },
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_FSS"   }, /* 25 */
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_BSS"   },
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_WSM"   },
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_LOCK"  },
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_UNLOCK"},
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_LOAD"  }, /* 30 */
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_UNLOAD"},
+	{mt_compression , 1, 1, 1, 0, 1, "MT_COMPRESSION"},
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_SETPART"},
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_MKPART"}
+};  
+
+#define NR_MT_CMDS NR_ITEMS(mt_funs)
+
+void zft_reset_position(zft_position *pos)
+{
+	TRACE_FUN(ft_t_flow);
+
+	pos->seg_byte_pos =
+		pos->volume_pos = 0;
+	if (zft_header_read) {
+		/* need to keep track of the volume table and
+		 * compression map. We therefor simply
+		 * position at the beginning of the first
+		 * volume. This covers old ftape archives as
+		 * well has various flavours of the
+		 * compression map segments. The worst case is
+		 * that the compression map shows up as a
+		 * additional volume in front of all others.
+		 */
+		pos->seg_pos  = zft_find_volume(0)->start_seg;
+		pos->tape_pos = zft_calc_tape_pos(pos->seg_pos);
+	} else {
+		pos->tape_pos =  0;
+		pos->seg_pos  = -1;
+	}
+	zft_just_before_eof =  0;
+	zft_deblock_segment = -1;
+	zft_io_state        = zft_idle;
+	zft_zap_read_buffers();
+	zft_prevent_flush();
+	/*  unlock the compresison module if it is loaded.
+	 *  The zero arg means not to try to load the module.
+	 */
+	if (zft_cmpr_lock(0) == 0) {
+		(*zft_cmpr_ops->reset)(); /* unlock */
+	}
+	TRACE_EXIT;
+}
+
+static void zft_init_driver(void)
+{
+	TRACE_FUN(ft_t_flow);
+
+	zft_resid =
+		zft_header_read          =
+		zft_old_ftape            =
+		zft_offline              =
+		zft_write_protected      =
+		going_offline            =
+		zft_mt_compression       =
+		zft_header_changed       =
+		zft_volume_table_changed =
+		zft_written_segments     = 0;
+	zft_blk_sz = CONFIG_ZFT_DFLT_BLK_SZ;
+	zft_reset_position(&zft_pos); /* does most of the stuff */
+	ftape_zap_read_buffers();
+	ftape_set_state(idle);
+	TRACE_EXIT;
+}
+
+int zft_def_idle_state(void)
+{ 
+	int result = 0;
+	TRACE_FUN(ft_t_flow);
+	
+	if (!zft_header_read) {
+		result = zft_read_header_segments();
+	} else if ((result = zft_flush_buffers()) >= 0 && zft_qic_mode) {
+		/*  don't move past eof
+		 */
+		(void)zft_close_volume(&zft_pos);
+	}
+	if (ftape_abort_operation() < 0) {
+		TRACE(ft_t_warn, "ftape_abort_operation() failed");
+		result = -EIO;
+	}
+	/* clear remaining read buffers */
+	zft_zap_read_buffers();
+	zft_io_state = zft_idle;
+	TRACE_EXIT result;
+}
+
+/*****************************************************************************
+ *                                                                           *
+ *  functions for the MTIOCTOP commands                                      *
+ *                                                                           *
+ *****************************************************************************/
+
+static int mt_dummy(int *dummy)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE_EXIT -ENOSYS;
+}
+
+static int mt_reset(int *dummy)
+{        
+	TRACE_FUN(ft_t_flow);
+	
+	(void)ftape_seek_to_bot();
+	TRACE_CATCH(ftape_reset_drive(),
+		    zft_init_driver(); zft_uninit_mem(); zft_offline = 1);
+	/*  fake a re-open of the device. This will set all flage and 
+	 *  allocate buffers as appropriate. The new tape condition will
+	 *  force the open routine to do anything we need.
+	 */
+	TRACE_CATCH(_zft_open(-1 /* fake reopen */, 0 /* dummy */),);
+	TRACE_EXIT 0;
+}
+
+static int mt_fsf(int *arg)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+
+	result = zft_skip_volumes(*arg, &zft_pos);
+	zft_just_before_eof = 0;
+	TRACE_EXIT result;
+}
+
+static int mt_bsf(int *arg)
+{
+	int result = 0;
+	TRACE_FUN(ft_t_flow);
+	
+	if (*arg != 0) {
+		result = zft_skip_volumes(-*arg + 1, &zft_pos);
+	}
+	TRACE_EXIT result;
+}
+
+static int seek_block(__s64 data_offset,
+		      __s64 block_increment,
+		      zft_position *pos)
+{ 
+	int result      = 0;
+	__s64 new_block_pos;
+	__s64 vol_block_count;
+	const zft_volinfo *volume;
+	int exceed;
+	TRACE_FUN(ft_t_flow);
+	
+	volume = zft_find_volume(pos->seg_pos);
+	if (volume->start_seg == 0 || volume->end_seg == 0) {
+		TRACE_EXIT -EIO;
+	}
+	new_block_pos   = (zft_div_blksz(data_offset, volume->blk_sz)
+			   + block_increment);
+	vol_block_count = zft_div_blksz(volume->size, volume->blk_sz);
+	if (new_block_pos < 0) {
+		TRACE(ft_t_noise,
+		      "new_block_pos " LL_X " < 0", LL(new_block_pos));
+		zft_resid     = (int)new_block_pos;
+		new_block_pos = 0;
+		exceed = 1;
+	} else if (new_block_pos > vol_block_count) {
+		TRACE(ft_t_noise,
+		      "new_block_pos " LL_X " exceeds size of volume " LL_X,
+		      LL(new_block_pos), LL(vol_block_count));
+		zft_resid     = (int)(vol_block_count - new_block_pos);
+		new_block_pos = vol_block_count;
+		exceed = 1;
+	} else {
+		exceed = 0;
+	}
+	if (zft_use_compression && volume->use_compression) {
+		TRACE_CATCH(zft_cmpr_lock(1 /* try to load */),);
+		result = (*zft_cmpr_ops->seek)(new_block_pos, pos, volume,
+					       zft_deblock_buf);
+		pos->tape_pos  = zft_calc_tape_pos(pos->seg_pos);
+		pos->tape_pos += pos->seg_byte_pos;
+	} else {
+		pos->volume_pos = zft_mul_blksz(new_block_pos, volume->blk_sz);
+		pos->tape_pos   = zft_calc_tape_pos(volume->start_seg);
+		pos->tape_pos  += pos->volume_pos;
+		pos->seg_pos    = zft_calc_seg_byte_coord(&pos->seg_byte_pos,
+							  pos->tape_pos);
+	}
+	zft_just_before_eof = volume->size == pos->volume_pos;
+	if (zft_just_before_eof) {
+		/* why this? because zft_file_no checks agains start
+		 * and end segment of a volume. We do not want to
+		 * advance to the next volume with this function.
+		 */
+		TRACE(ft_t_noise, "set zft_just_before_eof");
+		zft_position_before_eof(pos, volume);
+	}
+	TRACE(ft_t_noise, "\n"
+	      KERN_INFO "new_seg_pos : %d\n"
+	      KERN_INFO "new_tape_pos: " LL_X "\n"
+	      KERN_INFO "vol_size    : " LL_X "\n"
+	      KERN_INFO "seg_byte_pos: %d\n"
+	      KERN_INFO "blk_sz  : %d", 
+	      pos->seg_pos, LL(pos->tape_pos),
+	      LL(volume->size), pos->seg_byte_pos,
+	      volume->blk_sz);
+	if (!exceed) {
+		zft_resid = new_block_pos - zft_div_blksz(pos->volume_pos,
+							  volume->blk_sz);
+	}
+	if (zft_resid < 0) {
+		zft_resid = -zft_resid;
+	}
+	TRACE_EXIT ((exceed || zft_resid != 0) && result >= 0) ? -EINVAL : result;
+}     
+
+static int mt_fsr(int *arg)
+{ 
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	result = seek_block(zft_pos.volume_pos,  *arg, &zft_pos);
+	TRACE_EXIT result;
+}
+
+static int mt_bsr(int *arg)
+{   
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	result = seek_block(zft_pos.volume_pos, -*arg, &zft_pos);
+	TRACE_EXIT result;
+}
+
+static int mt_weof(int *arg)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE_CATCH(zft_flush_buffers(),);
+	result = zft_weof(*arg, &zft_pos);
+	TRACE_EXIT result;
+}
+
+static int mt_rew(int *dummy)
+{          
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	if(zft_header_read) {
+		(void)zft_def_idle_state();
+	}
+	result = ftape_seek_to_bot();
+	zft_reset_position(&zft_pos);
+	TRACE_EXIT result;
+}
+
+static int mt_offl(int *dummy)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	going_offline= 1;
+	result = mt_rew(NULL);
+	TRACE_EXIT result;
+}
+
+static int mt_nop(int *dummy)
+{
+	TRACE_FUN(ft_t_flow);
+	/*  should we set tape status?
+	 */
+	if (!zft_offline) { /* offline includes no_tape */
+		(void)zft_def_idle_state();
+	}
+	TRACE_EXIT 0; 
+}
+
+static int mt_reten(int *dummy)
+{  
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	if(zft_header_read) {
+		(void)zft_def_idle_state();
+	}
+	result = ftape_seek_to_eot();
+	if (result >= 0) {
+		result = ftape_seek_to_bot();
+	}
+	TRACE_EXIT(result);
+}
+
+static int fsfbsfm(int arg, zft_position *pos)
+{ 
+	const zft_volinfo *vtbl;
+	__s64 block_pos;
+	TRACE_FUN(ft_t_flow);
+	
+	/* What to do? This should seek to the next file-mark and
+	 * position BEFORE. That is, a next write would just extend
+	 * the current file.  Well. Let's just seek to the end of the
+	 * current file, if count == 1.  If count > 1, then do a
+	 * "mt_fsf(count - 1)", and then seek to the end of that file.
+	 * If count == 0, do nothing
+	 */
+	if (arg == 0) {
+		TRACE_EXIT 0;
+	}
+	zft_just_before_eof = 0;
+	TRACE_CATCH(zft_skip_volumes(arg < 0 ? arg : arg-1, pos),
+		    if (arg > 0) {
+			    zft_resid ++; 
+		    });
+	vtbl      = zft_find_volume(pos->seg_pos);
+	block_pos = zft_div_blksz(vtbl->size, vtbl->blk_sz);
+	(void)seek_block(0, block_pos, pos);
+	if (pos->volume_pos != vtbl->size) {
+		zft_just_before_eof = 0;
+		zft_resid = 1;
+		/* we didn't managed to go there */
+		TRACE_ABORT(-EIO, ft_t_err, 
+			    "wanted file position " LL_X ", arrived at " LL_X, 
+			    LL(vtbl->size), LL(pos->volume_pos));
+	}
+	zft_just_before_eof = 1;
+	TRACE_EXIT 0; 
+}
+
+static int mt_bsfm(int *arg)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	result = fsfbsfm(-*arg, &zft_pos);
+	TRACE_EXIT result;
+}
+
+static int mt_fsfm(int *arg)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	result = fsfbsfm(*arg, &zft_pos);
+	TRACE_EXIT result;
+}
+
+static int mt_eom(int *dummy)
+{              
+	TRACE_FUN(ft_t_flow);
+	
+	zft_skip_to_eom(&zft_pos);
+	TRACE_EXIT 0;
+}
+
+static int mt_erase(int *dummy)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	result = zft_erase();
+	TRACE_EXIT result;
+}
+
+static int mt_ras2(int *dummy)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	result = -ENOSYS;
+	TRACE_EXIT result;
+} 
+
+/*  Sets the new blocksize in BYTES
+ *
+ */
+static int mt_setblk(int *new_size)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	if((unsigned int)(*new_size) > ZFT_MAX_BLK_SZ) {
+		TRACE_ABORT(-EINVAL, ft_t_info,
+			    "desired blk_sz (%d) should be <= %d bytes",
+			    *new_size, ZFT_MAX_BLK_SZ);
+	}
+	if ((*new_size & (FT_SECTOR_SIZE-1)) != 0) {
+		TRACE_ABORT(-EINVAL, ft_t_info,
+			"desired blk_sz (%d) must be a multiple of %d bytes",
+			    *new_size, FT_SECTOR_SIZE);
+	}
+	if (*new_size == 0) {
+		if (zft_use_compression) {
+			TRACE_ABORT(-EINVAL, ft_t_info,
+				    "Variable block size not yet "
+				    "supported with compression");
+		}
+		*new_size = 1;
+	}
+	zft_blk_sz = *new_size;
+	TRACE_EXIT 0;
+} 
+
+static int mt_setdensity(int *arg)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	SET_TRACE_LEVEL(*arg);
+	TRACE(TRACE_LEVEL, "tracing set to %d", TRACE_LEVEL);
+	if ((int)TRACE_LEVEL != *arg) {
+		TRACE_EXIT -EINVAL;
+	}
+	TRACE_EXIT 0;
+}          
+
+static int mt_seek(int *new_block_pos)
+{ 
+	int result= 0;        
+	TRACE_FUN(ft_t_any);
+	
+	result = seek_block(0, (__s64)*new_block_pos, &zft_pos);
+	TRACE_EXIT result;
+}
+
+/*  OK, this is totally different from SCSI, but the worst thing that can 
+ *  happen is that there is not enough defragmentated memory that can be 
+ *  allocated. Also, there is a hardwired limit of 16 dma buffers in the 
+ *  stock ftape module. This shouldn't bring the system down.
+ *
+ * NOTE: the argument specifies the total number of dma buffers to use.
+ *       The driver needs at least 3 buffers to function at all.
+ * 
+ */
+static int mt_setdrvbuffer(int *cnt)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (*cnt < 3) {
+		TRACE_EXIT -EINVAL;
+	}
+	TRACE_CATCH(ftape_set_nr_buffers(*cnt),);
+	TRACE_EXIT 0;
+}
+/* return the block position from start of volume 
+ */
+static int mt_tell(int *arg)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	*arg   = zft_div_blksz(zft_pos.volume_pos,
+			       zft_find_volume(zft_pos.seg_pos)->blk_sz);
+	TRACE_EXIT 0;
+}
+
+static int mt_compression(int *arg)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	/*  Ok. We could also check whether compression is available at
+	 *  all by trying to load the compression module.  We could
+	 *  also check for a block size of 1 byte which is illegal
+	 *  with compression.  Instead of doing it here we rely on
+	 *  zftape_write() to do the proper checks.
+	 */
+	if ((unsigned int)*arg > 1) {
+		TRACE_EXIT -EINVAL;
+	}
+	if (*arg != 0 && zft_blk_sz == 1) { /* variable block size */
+		TRACE_ABORT(-EINVAL, ft_t_info,
+			    "Compression not yet supported "
+			    "with variable block size");
+	}
+	zft_mt_compression  = *arg;
+	if ((zft_unit & ZFT_ZIP_MODE) == 0) {
+		zft_use_compression = zft_mt_compression;
+	}
+	TRACE_EXIT 0;
+}
+
+/*  check whether write access is allowed. Write access is denied when
+ *  + zft_write_protected == 1 -- this accounts for either hard write 
+ *                                protection of the cartridge or for 
+ *                                O_RDONLY access mode of the tape device
+ *  + zft_offline == 1         -- this meany that there is either no tape 
+ *                                or that the MTOFFLINE ioctl has been 
+ *                                previously issued (`soft eject')
+ *  + ft_formatted == 0        -- this means that the cartridge is not
+ *                                formatted
+ *  Then we distinuguish two cases. When zft_qic_mode is TRUE, then we try
+ *  to emulate a `traditional' (aka SCSI like) UN*X tape device. Therefore we
+ *  deny writes when
+ *  + zft_qic_mode ==1 && 
+ *       (!zft_tape_at_lbot() &&   -- tape no at logical BOT
+ *        !(zft_tape_at_eom() ||   -- tape not at logical EOM (or EOD)
+ *          (zft_tape_at_eom() &&
+ *           zft_old_ftape())))    -- we can't add new volume to tapes 
+ *                                    written by old ftape because ftape
+ *                                    don't use the volume table
+ *
+ *  when the drive is in true raw mode (aka /dev/rawft0) then we don't 
+ *  care about LBOT and EOM conditions. This device is intended for a 
+ *  user level program that wants to truly implement the QIC-80 compliance
+ *  at the logical data layout level of the cartridge, i.e. implement all
+ *  that volume table and volume directory stuff etc.<
+ */
+int zft_check_write_access(zft_position *pos)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (zft_offline) { /* offline includes no_tape */
+		TRACE_ABORT(-ENXIO,
+			    ft_t_info, "tape is offline or no cartridge");
+	}
+	if (!ft_formatted) {
+		TRACE_ABORT(-EACCES, ft_t_info, "tape is not formatted");
+	} 
+	if (zft_write_protected) {
+		TRACE_ABORT(-EACCES, ft_t_info, "cartridge write protected");
+	} 
+	if (zft_qic_mode) {
+		/*  check BOT condition */
+		if (!zft_tape_at_lbot(pos)) {
+			/*  protect cartridges written by old ftape if
+			 *  not at BOT because they use the vtbl
+			 *  segment for storing data
+			 */
+			if (zft_old_ftape) {
+				TRACE_ABORT(-EACCES, ft_t_warn, 
+      "Cannot write to cartridges written by old ftape when not at BOT");
+			}
+			/*  not at BOT, but allow writes at EOD, of course
+			 */
+			if (!zft_tape_at_eod(pos)) {
+				TRACE_ABORT(-EACCES, ft_t_info,
+					    "tape not at BOT and not at EOD");
+			}
+		}
+		/*  fine. Now the tape is either at BOT or at EOD. */
+	}
+	/* or in raw mode in which case we don't care about BOT and EOD */
+	TRACE_EXIT 0;
+}
+
+/*  decide when we should lock the module in memory, even when calling
+ *  the release routine. This really is necessary for use with
+ *  kerneld.
+ *
+ *  NOTE: we MUST NOT use zft_write_protected, because this includes
+ *  the file access mode as well which has no meaning with our 
+ *  asynchronous update scheme.
+ *
+ *  Ugly, ugly. We need to look the module if we changed the block size.
+ *  How sad! Need persistent modules storage!
+ *
+ *  NOTE: I don't want to lock the module if the number of dma buffers 
+ *  has been changed. It's enough! Stop the story! Give me persisitent
+ *  module storage! Do it!
+ */
+int zft_dirty(void)
+{
+	if (!ft_formatted || zft_offline) { 
+		/* cannot be dirty if not formatted or offline */
+		return 0;
+	}
+	if (zft_blk_sz != CONFIG_ZFT_DFLT_BLK_SZ) {
+		/* blocksize changed, must lock */
+		return 1;
+	}
+	if (zft_mt_compression != 0) {
+		/* compression mode with /dev/qft, must lock */
+		return 1;
+	}
+	if (!zft_header_read) {
+		/* tape is logical at BOT, no lock */
+		return 0;
+	}
+	if (!zft_tape_at_lbot(&zft_pos)) {
+		/* somewhere inside a volume, lock tape */
+		return 1;
+	}
+	if (zft_volume_table_changed || zft_header_changed) {
+		/* header segments dirty if tape not write protected */
+		return !(ft_write_protected || zft_old_ftape);
+	}
+	return 0;
+}
+
+/*      OPEN routine called by kernel-interface code
+ *
+ *      NOTE: this is also called by mt_reset() with dev_minor == -1
+ *            to fake a reopen after a reset.
+ */
+int _zft_open(unsigned int dev_minor, unsigned int access_mode)
+{
+	static unsigned int tape_unit;
+	static unsigned int file_access_mode;
+	int result;
+	TRACE_FUN(ft_t_flow);
+
+	if ((int)dev_minor == -1) {
+		/* fake reopen */
+		zft_unit    = tape_unit;
+		access_mode = file_access_mode;
+		zft_init_driver(); /* reset all static data to defaults */
+	} else {
+		tape_unit        = dev_minor;
+		file_access_mode = access_mode;
+		if ((result = ftape_enable(FTAPE_SEL(dev_minor))) < 0) {
+			TRACE_ABORT(-ENXIO, ft_t_err,
+				    "ftape_enable failed: %d", result);
+		}
+		if (ft_new_tape || ft_no_tape || !ft_formatted ||
+		    (FTAPE_SEL(zft_unit) != FTAPE_SEL(dev_minor)) ||
+		    (zft_unit & ZFT_RAW_MODE) != (dev_minor & ZFT_RAW_MODE)) {
+			/* reset all static data to defaults,
+			 */
+			zft_init_driver(); 
+		}
+		zft_unit = dev_minor;
+	}
+	zft_set_flags(zft_unit); /* decode the minor bits */
+	if (zft_blk_sz == 1 && zft_use_compression) {
+		ftape_disable(); /* resets ft_no_tape */
+		TRACE_ABORT(-ENODEV, ft_t_warn, "Variable block size not yet "
+			    "supported with compression");
+	}
+	/*  no need for most of the buffers when no tape or not
+	 *  formatted.  for the read/write operations, it is the
+	 *  regardless whether there is no tape, a not-formatted tape
+	 *  or the whether the driver is soft offline.  
+	 *  Nevertheless we allow some ioctls with non-formatted tapes, 
+	 *  like rewind and reset.
+	 */
+	if (ft_no_tape || !ft_formatted) {
+		zft_uninit_mem();
+	}
+	if (ft_no_tape) {
+		zft_offline = 1; /* so we need not test two variables */
+	}
+	if ((access_mode == O_WRONLY || access_mode == O_RDWR) &&
+	    (ft_write_protected || ft_no_tape)) {
+		ftape_disable(); /* resets ft_no_tape */
+		TRACE_ABORT(ft_no_tape ? -ENXIO : -EROFS,
+			    ft_t_warn, "wrong access mode %s cartridge",
+			    ft_no_tape ? "without a" : "with write protected");
+	}
+	zft_write_protected = (access_mode == O_RDONLY || 
+			       ft_write_protected != 0);
+	if (zft_write_protected) {
+		TRACE(ft_t_noise,
+		      "read only access mode: %d, "
+		      "drive write protected: %d", 
+		      access_mode == O_RDONLY,
+		      ft_write_protected != 0);
+	}
+	if (!zft_offline) {
+		TRACE_CATCH(zft_vmalloc_once(&zft_deblock_buf,FT_SEGMENT_SIZE),
+			    ftape_disable());
+	}
+	/* zft_seg_pos should be greater than the vtbl segpos but not
+	 * if in compatability mode and only after we read in the
+	 * header segments
+	 *
+	 * might also be a problem if the user makes a backup with a
+	 * *qft* device and rewinds it with a raw device.
+	 */
+	if (zft_qic_mode         &&
+	    !zft_old_ftape       &&
+	    zft_pos.seg_pos >= 0 &&
+	    zft_header_read      && 
+	    zft_pos.seg_pos <= ft_first_data_segment) {
+		TRACE(ft_t_noise, "you probably mixed up the zftape devices!");
+		zft_reset_position(&zft_pos); 
+	}
+	TRACE_EXIT 0;
+}
+
+/*      RELEASE routine called by kernel-interface code
+ */
+int _zft_close(void)
+{
+	int result = 0;
+	TRACE_FUN(ft_t_flow);
+	
+	if (zft_offline) {
+		/* call the hardware release routine. Puts the drive offline */
+		ftape_disable();
+		TRACE_EXIT 0;
+	}
+	if (!(ft_write_protected || zft_old_ftape)) {
+		result = zft_flush_buffers();
+		TRACE(ft_t_noise, "writing file mark at current position");
+		if (zft_qic_mode && zft_close_volume(&zft_pos) == 0) {
+			zft_move_past_eof(&zft_pos);
+		}
+		if ((zft_tape_at_lbot(&zft_pos) ||
+		     !(zft_unit & FTAPE_NO_REWIND))) {
+			if (result >= 0) {
+				result = zft_update_header_segments();
+			} else {
+				TRACE(ft_t_err,
+				"Error: unable to update header segments");
+			}
+		}
+	}
+	ftape_abort_operation();
+	if (!(zft_unit & FTAPE_NO_REWIND)) {
+		TRACE(ft_t_noise, "rewinding tape");
+		if (ftape_seek_to_bot() < 0 && result >= 0) {
+			result = -EIO; /* keep old value */
+		}
+		zft_reset_position(&zft_pos);
+	} 
+	zft_zap_read_buffers();
+	/*  now free up memory as much as possible. We don't destroy
+	 *  the deblock buffer if it containes a valid segment.
+	 */
+	if (zft_deblock_segment == -1) {
+		zft_vfree(&zft_deblock_buf, FT_SEGMENT_SIZE); 
+	}
+	/* high level driver status, forces creation of a new volume
+	 * when calling ftape_write again and not zft_just_before_eof
+	 */
+	zft_io_state = zft_idle;  
+	if (going_offline) {
+		zft_init_driver();
+		zft_uninit_mem();
+		going_offline = 0;
+		zft_offline   = 1;
+	} else if (zft_dirty()) {
+		TRACE(ft_t_noise, "Keeping module locked in memory because:\n"
+		      KERN_INFO "header segments need updating: %s\n"
+		      KERN_INFO "tape not at BOT              : %s",
+		      (zft_volume_table_changed || zft_header_changed) 
+		      ? "yes" : "no",
+		      zft_tape_at_lbot(&zft_pos) ? "no" : "yes");
+	} else if (zft_cmpr_lock(0 /* don't load */) == 0) {
+		(*zft_cmpr_ops->reset)(); /* unlock it again */
+	}
+	zft_memory_stats();
+	/* call the hardware release routine. Puts the drive offline */
+	ftape_disable();
+	TRACE_EXIT result;
+}
+
+/*
+ *  the wrapper function around the wrapper MTIOCTOP ioctl
+ */
+static int mtioctop(struct mtop *mtop, int arg_size)
+{
+	int result = 0;
+	fun_entry *mt_fun_entry;
+	TRACE_FUN(ft_t_flow);
+	
+	if (arg_size != sizeof(struct mtop) || mtop->mt_op >= NR_MT_CMDS) {
+		TRACE_EXIT -EINVAL;
+	}
+	TRACE(ft_t_noise, "calling MTIOCTOP command: %s",
+	      mt_funs[mtop->mt_op].name);
+	mt_fun_entry= &mt_funs[mtop->mt_op];
+	zft_resid = mtop->mt_count;
+	if (!mt_fun_entry->offline && zft_offline) {
+		if (ft_no_tape) {
+			TRACE_ABORT(-ENXIO, ft_t_info, "no tape present");
+		} else {
+			TRACE_ABORT(-ENXIO, ft_t_info, "drive is offline");
+		}
+	}
+	if (!mt_fun_entry->not_formatted && !ft_formatted) {
+		TRACE_ABORT(-EACCES, ft_t_info, "tape is not formatted");
+	}
+	if (!mt_fun_entry->write_protected) {
+		TRACE_CATCH(zft_check_write_access(&zft_pos),);
+	}
+	if (mt_fun_entry->need_idle_state && !(zft_offline || !ft_formatted)) {
+		TRACE_CATCH(zft_def_idle_state(),);
+	}
+	if (!zft_qic_mode && !mt_fun_entry->raw_mode) {
+		TRACE_ABORT(-EACCES, ft_t_info, 
+"Drive needs to be in QIC-80 compatibility mode for this command");
+	}
+	result = (mt_fun_entry->function)(&mtop->mt_count);
+	if (zft_tape_at_lbot(&zft_pos)) {
+		TRACE_CATCH(zft_update_header_segments(),);
+	}
+	if (result >= 0) {
+		zft_resid = 0;
+	}
+	TRACE_EXIT result;
+}
+
+/*
+ *  standard MTIOCGET ioctl
+ */
+static int mtiocget(struct mtget *mtget, int arg_size)
+{
+	const zft_volinfo *volume;
+	__s64 max_tape_pos;
+	TRACE_FUN(ft_t_flow);
+	
+	if (arg_size != sizeof(struct mtget)) {
+		TRACE_ABORT(-EINVAL, ft_t_info, "bad argument size: %d",
+			    arg_size);
+	}
+	mtget->mt_type  = ft_drive_type.vendor_id + 0x800000;
+	mtget->mt_dsreg = ft_last_status.space;
+	mtget->mt_erreg = ft_last_error.space; /* error register */
+	mtget->mt_resid = zft_resid; /* residuum of writes, reads and
+				      * MTIOCTOP commands 
+				      */
+	if (!zft_offline) { /* neither no_tape nor soft offline */
+		mtget->mt_gstat = GMT_ONLINE(~0UL);
+		/* should rather return the status of the cartridge
+		 * than the access mode of the file, therefor use
+		 * ft_write_protected, not zft_write_protected 
+		 */
+		if (ft_write_protected) {
+			mtget->mt_gstat |= GMT_WR_PROT(~0UL);
+		}
+		if(zft_header_read) { /* this catches non-formatted */
+			volume = zft_find_volume(zft_pos.seg_pos);
+			mtget->mt_fileno = volume->count;
+			max_tape_pos = zft_capacity - zft_blk_sz;
+			if (zft_use_compression) {
+				max_tape_pos -= ZFT_CMPR_OVERHEAD;
+			}
+			if (zft_tape_at_eod(&zft_pos)) {
+				mtget->mt_gstat |= GMT_EOD(~0UL);
+			}
+			if (zft_pos.tape_pos > max_tape_pos) {
+				mtget->mt_gstat |= GMT_EOT(~0UL);
+			}
+			mtget->mt_blkno = zft_div_blksz(zft_pos.volume_pos,
+							volume->blk_sz);
+			if (zft_just_before_eof) {
+				mtget->mt_gstat |= GMT_EOF(~0UL);
+			}
+			if (zft_tape_at_lbot(&zft_pos)) {
+				mtget->mt_gstat |= GMT_BOT(~0UL);
+			}
+		} else {
+			mtget->mt_fileno = mtget->mt_blkno = -1;
+			if (mtget->mt_dsreg & QIC_STATUS_AT_BOT) {
+				mtget->mt_gstat |= GMT_BOT(~0UL);
+			}
+		}
+	} else {
+		if (ft_no_tape) {
+			mtget->mt_gstat = GMT_DR_OPEN(~0UL);
+		} else {
+			mtget->mt_gstat = 0UL;
+		}
+ 		mtget->mt_fileno = mtget->mt_blkno = -1;
+	}
+	TRACE_EXIT 0;
+}
+
+#ifdef MTIOCRDFTSEG
+/*
+ *  Read a floppy tape segment. This is useful for manipulating the
+ *  volume table, and read the old header segment before re-formatting
+ *  the cartridge.
+ */
+static int mtiocrdftseg(struct mtftseg * mtftseg, int arg_size)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCRDFTSEG");
+	if (zft_qic_mode) {
+		TRACE_ABORT(-EACCES, ft_t_info,
+			    "driver needs to be in raw mode for this ioctl");
+	} 
+	if (arg_size != sizeof(struct mtftseg)) {
+		TRACE_ABORT(-EINVAL, ft_t_info, "bad argument size: %d",
+			    arg_size);
+	}
+	if (zft_offline) {
+		TRACE_EXIT -ENXIO;
+	}
+	if (mtftseg->mt_mode != FT_RD_SINGLE &&
+	    mtftseg->mt_mode != FT_RD_AHEAD) {
+		TRACE_ABORT(-EINVAL, ft_t_info, "invalid read mode");
+	}
+	if (!ft_formatted) {
+		TRACE_EXIT -EACCES; /* -ENXIO ? */
+
+	}
+	if (!zft_header_read) {
+		TRACE_CATCH(zft_def_idle_state(),);
+	}
+	if (mtftseg->mt_segno > ft_last_data_segment) {
+		TRACE_ABORT(-EINVAL, ft_t_info, "segment number is too large");
+	}
+	mtftseg->mt_result = ftape_read_segment(mtftseg->mt_segno,
+						zft_deblock_buf,
+						mtftseg->mt_mode);
+	if (mtftseg->mt_result < 0) {
+		/*  a negativ result is not an ioctl error. if
+		 *  the user wants to read damaged tapes,
+		 *  it's up to her/him
+		 */
+		TRACE_EXIT 0;
+	}
+#if LINUX_VERSION_CODE > KERNEL_VER(2,1,3)
+	if (copy_to_user(mtftseg->mt_data,
+			 zft_deblock_buf,
+			 mtftseg->mt_result) != 0) {
+		TRACE_EXIT -EFAULT;
+	}
+#else
+	TRACE_CATCH(verify_area(VERIFY_WRITE, mtftseg->mt_data,
+				mtftseg->mt_result),);
+	memcpy_tofs(mtftseg->mt_data, zft_deblock_buf, 
+		    mtftseg->mt_result);
+#endif
+	TRACE_EXIT 0;
+}
+#endif
+
+#ifdef MTIOCWRFTSEG
+/*
+ *  write a floppy tape segment. This version features writing of
+ *  deleted address marks, and gracefully ignores the (software)
+ *  ft_formatted flag to support writing of header segments after
+ *  formatting.
+ */
+static int mtiocwrftseg(struct mtftseg * mtftseg, int arg_size)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCWRFTSEG");
+	if (zft_write_protected || zft_qic_mode) {
+		TRACE_EXIT -EACCES;
+	} 
+	if (arg_size != sizeof(struct mtftseg)) {
+		TRACE_ABORT(-EINVAL, ft_t_info, "bad argument size: %d",
+			    arg_size);
+	}
+	if (zft_offline) {
+		TRACE_EXIT -ENXIO;
+	}
+	if (mtftseg->mt_mode != FT_WR_ASYNC   && 
+	    mtftseg->mt_mode != FT_WR_MULTI   &&
+	    mtftseg->mt_mode != FT_WR_SINGLE  &&
+	    mtftseg->mt_mode != FT_WR_DELETE) {
+		TRACE_ABORT(-EINVAL, ft_t_info, "invalid write mode");
+	}
+	/*
+	 *  We don't check for ft_formatted, because this gives
+	 *  only the software status of the driver.
+	 *
+	 *  We assume that the user knows what it is
+	 *  doing. And rely on the low level stuff to fail
+	 *  when the tape isn't formatted. We only make sure
+	 *  that The header segment buffer is allocated,
+	 *  because it holds the bad sector map.
+	 */
+	if (zft_hseg_buf == NULL) {
+		TRACE_EXIT -ENXIO;
+	}
+	if (mtftseg->mt_mode != FT_WR_DELETE) {
+#if LINUX_VERSION_CODE > KERNEL_VER(2,1,3)
+		if (copy_from_user(zft_deblock_buf, 
+				   mtftseg->mt_data,
+				   FT_SEGMENT_SIZE) != 0) {
+			TRACE_EXIT -EFAULT;
+		}
+#else
+		TRACE_CATCH(verify_area(VERIFY_READ, 
+					mtftseg->mt_data, 
+					FT_SEGMENT_SIZE),);
+		memcpy_fromfs(zft_deblock_buf, mtftseg->mt_data,
+			      FT_SEGMENT_SIZE);
+#endif
+	}
+	mtftseg->mt_result = ftape_write_segment(mtftseg->mt_segno, 
+						 zft_deblock_buf,
+						 mtftseg->mt_mode);
+	if (mtftseg->mt_result >= 0 && mtftseg->mt_mode == FT_WR_SINGLE) {
+		/*  
+		 *  a negativ result is not an ioctl error. if
+		 *  the user wants to write damaged tapes,
+		 *  it's up to her/him
+		 */
+		if ((result = ftape_loop_until_writes_done()) < 0) {
+			mtftseg->mt_result = result;
+		}
+	}
+	TRACE_EXIT 0;
+}
+#endif
+  
+#ifdef MTIOCVOLINFO
+/*
+ *  get information about volume positioned at.
+ */
+static int mtiocvolinfo(struct mtvolinfo *volinfo, int arg_size)
+{
+	const zft_volinfo *volume;
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCVOLINFO");
+	if (arg_size != sizeof(struct mtvolinfo)) {
+		TRACE_ABORT(-EINVAL,
+			    ft_t_info, "bad argument size: %d", arg_size);
+	}
+	if (zft_offline) {
+		TRACE_EXIT -ENXIO;
+	}
+	if (!ft_formatted) {
+		TRACE_EXIT -EACCES;
+	}
+	TRACE_CATCH(zft_def_idle_state(),);
+	volume = zft_find_volume(zft_pos.seg_pos);
+	volinfo->mt_volno   = volume->count;
+	volinfo->mt_blksz   = volume->blk_sz == 1 ? 0 : volume->blk_sz;
+	volinfo->mt_size    = volume->size >> 10;
+	volinfo->mt_rawsize = ((zft_calc_tape_pos(volume->end_seg + 1) >> 10) -
+			       (zft_calc_tape_pos(volume->start_seg) >> 10));
+	volinfo->mt_cmpr    = volume->use_compression;
+	TRACE_EXIT 0;
+}
+#endif
+
+#ifdef ZFT_OBSOLETE  
+static int mtioc_zftape_getblksz(struct mtblksz *blksz, int arg_size)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_noise, "\n"
+	      KERN_INFO "Mag tape ioctl command: MTIOC_ZTAPE_GETBLKSZ\n"
+	      KERN_INFO "This ioctl is here merely for compatibility.\n"
+	      KERN_INFO "Please use MTIOCVOLINFO instead");
+	if (arg_size != sizeof(struct mtblksz)) {
+		TRACE_ABORT(-EINVAL,
+			    ft_t_info, "bad argument size: %d", arg_size);
+	}
+	if (zft_offline) {
+		TRACE_EXIT -ENXIO;
+	}
+	if (!ft_formatted) {
+		TRACE_EXIT -EACCES;
+	}
+	TRACE_CATCH(zft_def_idle_state(),);
+	blksz->mt_blksz = zft_find_volume(zft_pos.seg_pos)->blk_sz;
+	TRACE_EXIT 0;
+}
+#endif
+
+#ifdef MTIOCGETSIZE
+/*
+ *  get the capacity of the tape cartridge.
+ */
+static int mtiocgetsize(struct mttapesize *size, int arg_size)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_noise, "Mag tape ioctl command: MTIOC_ZFTAPE_GETSIZE");
+	if (arg_size != sizeof(struct mttapesize)) {
+		TRACE_ABORT(-EINVAL,
+			    ft_t_info, "bad argument size: %d", arg_size);
+	}
+	if (zft_offline) {
+		TRACE_EXIT -ENXIO;
+	}
+	if (!ft_formatted) {
+		TRACE_EXIT -EACCES;
+	}
+	TRACE_CATCH(zft_def_idle_state(),);
+	size->mt_capacity = (unsigned int)(zft_capacity>>10);
+	size->mt_used     = (unsigned int)(zft_get_eom_pos()>>10);
+	TRACE_EXIT 0;
+}
+#endif
+
+static int mtiocpos(struct mtpos *mtpos, int arg_size)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCPOS");
+	if (arg_size != sizeof(struct mtpos)) {
+		TRACE_ABORT(-EINVAL,
+			    ft_t_info, "bad argument size: %d", arg_size);
+	}
+	result = mt_tell((int *)&mtpos->mt_blkno);
+	TRACE_EXIT result;
+}
+
+#ifdef MTIOCFTFORMAT
+/*
+ * formatting of floppy tape cartridges. This is intended to be used
+ * together with the MTIOCFTCMD ioctl and the new mmap feature 
+ */
+
+/* 
+ *  This function uses ftape_decode_header_segment() to inform the low
+ *  level ftape module about the new parameters.
+ *
+ *  It erases the hseg_buf. The calling process must specify all
+ *  parameters to assure proper operation.
+ *
+ *  return values: -EINVAL - wrong argument size
+ *                 -EINVAL - if ftape_decode_header_segment() failed.
+ */
+static int set_format_parms(struct ftfmtparms *p, __u8 *hseg_buf)
+{
+	ft_trace_t old_level = TRACE_LEVEL;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_noise, "MTIOCFTFORMAT operation FTFMT_SETPARMS");
+	memset(hseg_buf, 0, FT_SEGMENT_SIZE);
+	PUT4(hseg_buf, FT_SIGNATURE, FT_HSEG_MAGIC);
+
+	/*  fill in user specified parameters
+	 */
+	hseg_buf[FT_FMT_CODE] = (__u8)p->ft_fmtcode;
+	PUT2(hseg_buf, FT_SPT, p->ft_spt);
+	hseg_buf[FT_TPC]      = (__u8)p->ft_tpc;
+	hseg_buf[FT_FHM]      = (__u8)p->ft_fhm;
+	hseg_buf[FT_FTM]      = (__u8)p->ft_ftm;
+
+	/*  fill in sane defaults to make ftape happy.
+	 */ 
+	hseg_buf[FT_FSM] = (__u8)128; /* 128 is hard wired all over ftape */
+	if (p->ft_fmtcode == fmt_big) {
+		PUT4(hseg_buf, FT_6_HSEG_1,   0);
+		PUT4(hseg_buf, FT_6_HSEG_2,   1);
+		PUT4(hseg_buf, FT_6_FRST_SEG, 2);
+		PUT4(hseg_buf, FT_6_LAST_SEG, p->ft_spt * p->ft_tpc - 1);
+	} else {
+		PUT2(hseg_buf, FT_HSEG_1,    0);
+		PUT2(hseg_buf, FT_HSEG_2,    1);
+		PUT2(hseg_buf, FT_FRST_SEG,  2);
+		PUT2(hseg_buf, FT_LAST_SEG, p->ft_spt * p->ft_tpc - 1);
+	}
+
+	/*  Synchronize with the low level module. This is particularly
+	 *  needed for unformatted cartridges as the QIC std was previously 
+	 *  unknown BUT is needed to set data rate and to calculate timeouts.
+	 */
+	TRACE_CATCH(ftape_calibrate_data_rate(p->ft_qicstd&QIC_TAPE_STD_MASK),
+		    _res = -EINVAL);
+
+	/*  The following will also recalcualte the timeouts for the tape
+	 *  length and QIC std we want to format to.
+	 *  abort with -EINVAL rather than -EIO
+	 */
+	SET_TRACE_LEVEL(ft_t_warn);
+	TRACE_CATCH(ftape_decode_header_segment(hseg_buf),
+		    SET_TRACE_LEVEL(old_level); _res = -EINVAL);
+	SET_TRACE_LEVEL(old_level);
+	TRACE_EXIT 0;
+}
+
+/*
+ *  Return the internal SOFTWARE status of the kernel driver. This does
+ *  NOT query the tape drive about its status.
+ */
+static int get_format_parms(struct ftfmtparms *p, __u8 *hseg_buffer)
+{
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_noise, "MTIOCFTFORMAT operation FTFMT_GETPARMS");
+	p->ft_qicstd  = ft_qic_std;
+	p->ft_fmtcode = ft_format_code;
+	p->ft_fhm     = hseg_buffer[FT_FHM];
+	p->ft_ftm     = hseg_buffer[FT_FTM];
+	p->ft_spt     = ft_segments_per_track;
+	p->ft_tpc     = ft_tracks_per_tape;
+	TRACE_EXIT 0;
+}
+
+static int mtiocftformat(struct mtftformat *mtftformat, int arg_size)
+{
+	int result;
+	union fmt_arg *arg = &mtftformat->fmt_arg;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCFTFORMAT");
+	if (zft_offline) {
+		if (ft_no_tape) {
+			TRACE_ABORT(-ENXIO, ft_t_info, "no tape present");
+		} else {
+			TRACE_ABORT(-ENXIO, ft_t_info, "drive is offline");
+		}
+	}
+	if (zft_qic_mode) {
+		TRACE_ABORT(-EACCES, ft_t_info,
+			    "driver needs to be in raw mode for this ioctl");
+	} 
+	if (zft_hseg_buf == NULL) {
+		TRACE_CATCH(zft_vcalloc_once(&zft_hseg_buf, FT_SEGMENT_SIZE),);
+	}
+	zft_header_read = 0;
+	switch(mtftformat->fmt_op) {
+	case FTFMT_SET_PARMS:
+		TRACE_CATCH(set_format_parms(&arg->fmt_parms, zft_hseg_buf),);
+		TRACE_EXIT 0;
+	case FTFMT_GET_PARMS:
+		TRACE_CATCH(get_format_parms(&arg->fmt_parms, zft_hseg_buf),);
+		TRACE_EXIT 0;
+	case FTFMT_FORMAT_TRACK:
+		if ((ft_formatted && zft_check_write_access(&zft_pos) < 0) ||
+		    (!ft_formatted && zft_write_protected)) {
+			TRACE_ABORT(-EACCES, ft_t_info, "Write access denied");
+		}
+		TRACE_CATCH(ftape_format_track(arg->fmt_track.ft_track,
+					       arg->fmt_track.ft_gap3),);
+		TRACE_EXIT 0;
+	case FTFMT_STATUS:
+		TRACE_CATCH(ftape_format_status(&arg->fmt_status.ft_segment),);
+		TRACE_EXIT 0;
+	case FTFMT_VERIFY:
+		TRACE_CATCH(ftape_verify_segment(arg->fmt_verify.ft_segment,
+				(SectorMap *)&arg->fmt_verify.ft_bsm),);
+		TRACE_EXIT 0;
+	default:
+		TRACE_ABORT(-EINVAL, ft_t_err, "Invalid format operation");
+	}
+	TRACE_EXIT result;
+}
+#endif
+
+#ifdef MTIOCFTCMD
+/*
+ *  send a QIC-117 command to the drive, with optional timeouts,
+ *  parameter and result bits. This is intended to be used together
+ *  with the formatting ioctl.
+ */
+static int mtiocftcmd(struct mtftcmd *ftcmd, int arg_size)
+{
+	int i;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCFTCMD");
+	if (!suser()) {
+		TRACE_ABORT(-EPERM, ft_t_info,
+			    "only the superuser may send raw qic-117 commands");
+	}
+	if (zft_qic_mode) {
+		TRACE_ABORT(-EACCES, ft_t_info,
+			    "driver needs to be in raw mode for this ioctl");
+	} 
+	if (arg_size != sizeof(struct mtftcmd)) {
+		TRACE_ABORT(-EINVAL,
+			    ft_t_info, "bad argument size: %d", arg_size);
+	}
+	if (ftcmd->ft_wait_before) {
+		TRACE_CATCH(ftape_ready_wait(ftcmd->ft_wait_before,
+					     &ftcmd->ft_status),);
+	}
+	if (ftcmd->ft_status & QIC_STATUS_ERROR)
+		goto ftmtcmd_error;
+	if (ftcmd->ft_result_bits != 0) {
+		TRACE_CATCH(ftape_report_operation(&ftcmd->ft_result,
+						   ftcmd->ft_cmd,
+						   ftcmd->ft_result_bits),);
+	} else {
+		TRACE_CATCH(ftape_command(ftcmd->ft_cmd),);
+		if (ftcmd->ft_status & QIC_STATUS_ERROR)
+			goto ftmtcmd_error;
+		for (i = 0; i < ftcmd->ft_parm_cnt; i++) {
+			TRACE_CATCH(ftape_parameter(ftcmd->ft_parms[i]&0x0f),);
+			if (ftcmd->ft_status & QIC_STATUS_ERROR)
+				goto ftmtcmd_error;
+		}
+	}
+	if (ftcmd->ft_wait_after != 0) {
+		TRACE_CATCH(ftape_ready_wait(ftcmd->ft_wait_after,
+					     &ftcmd->ft_status),);
+	}
+ftmtcmd_error:	       
+	if (ftcmd->ft_status & QIC_STATUS_ERROR) {
+		TRACE(ft_t_noise, "error status set");
+		TRACE_CATCH(ftape_report_error(&ftcmd->ft_error,
+					       &ftcmd->ft_cmd, 1),);
+	}
+	TRACE_EXIT 0; /* this is not an i/o error */
+}
+#endif
+
+/*  IOCTL routine called by kernel-interface code
+ */
+int _zft_ioctl(unsigned int command, void * arg)
+{
+	int result;
+	union { struct mtop       mtop;
+		struct mtget      mtget;
+		struct mtpos      mtpos;
+#ifdef MTIOCRDFTSEG
+		struct mtftseg    mtftseg;
+#endif
+#ifdef MTIOCVOLINFO
+		struct mtvolinfo  mtvolinfo;
+#endif
+#ifdef MTIOCGETSIZE
+		struct mttapesize mttapesize;
+#endif
+#ifdef MTIOCFTFORMAT
+		struct mtftformat mtftformat;
+#endif
+#ifdef ZFT_OBSOLETE
+		struct mtblksz mtblksz;
+#endif
+#ifdef MTIOCFTCMD
+		struct mtftcmd mtftcmd;
+#endif
+	} krnl_arg;
+	int arg_size = _IOC_SIZE(command);
+	int dir = _IOC_DIR(command);
+	TRACE_FUN(ft_t_flow);
+
+	/* This check will only catch arguments that are too large !
+	 */
+	if (dir & (_IOC_READ | _IOC_WRITE) && arg_size > sizeof(krnl_arg)) {
+		TRACE_ABORT(-EINVAL,
+			    ft_t_info, "bad argument size: %d", arg_size);
+	}
+	if (dir & _IOC_WRITE) {
+#if LINUX_VERSION_CODE > KERNEL_VER(2,1,3)
+		if (copy_from_user(&krnl_arg, arg, arg_size) != 0) {
+			TRACE_EXIT -EFAULT;
+		}
+#else
+		TRACE_CATCH(verify_area(VERIFY_READ, arg, arg_size),);
+		memcpy_fromfs(&krnl_arg, arg, arg_size);
+#endif
+	}
+	TRACE(ft_t_flow, "called with ioctl command: 0x%08x", command);
+	switch (command) {
+	case MTIOCTOP:
+		result = mtioctop(&krnl_arg.mtop, arg_size);
+		break;
+	case MTIOCGET:
+		result = mtiocget(&krnl_arg.mtget, arg_size);
+		break;
+	case MTIOCPOS:
+		result = mtiocpos(&krnl_arg.mtpos, arg_size);
+		break;
+#ifdef MTIOCVOLINFO
+	case MTIOCVOLINFO:
+		result = mtiocvolinfo(&krnl_arg.mtvolinfo, arg_size);
+		break;
+#endif
+#ifdef ZFT_OBSOLETE
+	case MTIOC_ZFTAPE_GETBLKSZ:
+		result = mtioc_zftape_getblksz(&krnl_arg.mtblksz, arg_size);
+		break;
+#endif
+#ifdef MTIOCRDFTSEG
+	case MTIOCRDFTSEG: /* read a segment via ioctl */
+		result = mtiocrdftseg(&krnl_arg.mtftseg, arg_size);
+		break;
+#endif
+#ifdef MTIOCWRFTSEG
+	case MTIOCWRFTSEG: /* write a segment via ioctl */
+		result = mtiocwrftseg(&krnl_arg.mtftseg, arg_size);
+		break;
+#endif
+#ifdef MTIOCGETSIZE
+	case MTIOCGETSIZE:
+		result = mtiocgetsize(&krnl_arg.mttapesize, arg_size);
+		break;
+#endif
+#ifdef MTIOCFTFORMAT
+	case MTIOCFTFORMAT:
+		result = mtiocftformat(&krnl_arg.mtftformat, arg_size);
+		break;
+#endif
+#ifdef MTIOCFTCMD
+	case MTIOCFTCMD:
+		result = mtiocftcmd(&krnl_arg.mtftcmd, arg_size);
+		break;
+#endif
+	default:
+		result = -EINVAL;
+		break;
+	}
+	if ((result >= 0) && (dir & _IOC_READ)) {
+#if LINUX_VERSION_CODE > KERNEL_VER(2,1,3)
+		if (copy_to_user(arg, &krnl_arg, arg_size) != 0) {
+			TRACE_EXIT -EFAULT;
+		}
+#else
+		TRACE_CATCH(verify_area(VERIFY_WRITE, arg, arg_size),);
+		memcpy_tofs(arg, &krnl_arg, arg_size);
+#endif
+	}
+	TRACE_EXIT result;
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov