// SPDX-License-Identifier: GPL-2.0-only /* SCSI Tape Driver for Linux version 1.1 and newer. See the accompanying file Documentation/scsi/st.rst for more information.
History: Rewritten from Dwayne Forsyth's SCSI tape driver by Kai Makisara. Contribution and ideas from several people including (in alphabetical order) Klaus Ehrenfried, Eugene Exarevsky, Eric Lee Green, Wolfgang Denk, Steve Hirsch, Andreas Koppenh"ofer, Michael Leodolter, Eyal Lebedinsky, Michael Schaefer, J"org Weule, and Eric Youngdale.
Copyright 1992 - 2016 Kai Makisara email Kai.Makisara@kolumbus.fi
Some small formal changes - aeb, 950809
Last modified: 18-JAN-1998 Richard Gooch <rgooch@atnf.csiro.au> Devfs support
*/
/* The driver prints some debugging information on the console if DEBUG
is defined and non-zero. */ #define DEBUG 1 #define NO_DEBUG 0
#define ST_DEB_MSG KERN_NOTICE #if DEBUG /* The message level for the debug messages is currently set to KERN_NOTICE so that people can easily see the messages. Later when the debugging messages
in the drivers are more widely classified, this may be changed to KERN_DEBUG. */ #define DEB(a) a #define DEBC(a) if (debugging) { a ; } #else #define DEB(a) #define DEBC(a) #endif
/* Set 'perm' (4th argument) to 0 to disable module_param's definition * of sysfs parameters (which module_param doesn't yet support). * Sysfs parameters defined explicitly later.
*/
module_param_named(buffer_kbs, buffer_kbs, int, 0);
MODULE_PARM_DESC(buffer_kbs, "Default driver buffer size for fixed block mode (KB; 32)");
module_param_named(max_sg_segs, max_sg_segs, int, 0);
MODULE_PARM_DESC(max_sg_segs, "Maximum number of scatter/gather segments to use (256)");
module_param_named(try_direct_io, try_direct_io, int, 0);
MODULE_PARM_DESC(try_direct_io, "Try direct I/O between user buffer and tape drive (1)");
module_param_named(debug_flag, debug_flag, int, 0);
MODULE_PARM_DESC(debug_flag, "Enable DEBUG, same as setting debugging=1");
/* Extra parameters for testing */
module_param_named(try_rdio, try_rdio, int, 0);
MODULE_PARM_DESC(try_rdio, "Try direct read i/o when possible");
module_param_named(try_wdio, try_wdio, int, 0);
MODULE_PARM_DESC(try_wdio, "Try direct write i/o when possible");
/* Restrict the number of modes so that names for all are assigned */ #if ST_NBR_MODES > 16 #error"Maximum number of modes is 16" #endif /* Bit reversed order to get same names for same minors with all
mode counts */ staticconstchar *st_formats[] = { "", "r", "k", "s", "l", "t", "o", "u", "m", "v", "p", "x", "a", "y", "q", "z"};
/* The default definitions have been moved to st_options.h */
/* The buffer size should fit into the 24 bits for length in the
6-byte SCSI read and write commands. */ #if ST_FIXED_BUFFER_SIZE >= (2 << 24 - 1) #error"Buffer size should not exceed (2 << 24 - 1) bytes!" #endif
staticint debugging = DEBUG;
/* Setting these non-zero may risk recognizing resets */ #define MAX_RETRIES 0 #define MAX_WRITE_RETRIES 0 #define MAX_READY_RETRIES 0
/* If the device signature is on the list of incompatible drives, the
function returns a pointer to the name of the correct driver (if known) */ staticchar * st_incompatible(struct scsi_device* SDp)
{ struct st_reject_data *rp;
for (rp=&(reject_list[0]); rp->vendor != NULL; rp++) if (!strncmp(rp->vendor, SDp->vendor, strlen(rp->vendor)) &&
!strncmp(rp->model, SDp->model, strlen(rp->model)) &&
!strncmp(rp->rev, SDp->rev, strlen(rp->rev))) { if (rp->driver_hint) return rp->driver_hint; else return"unknown";
} return NULL;
}
/* Do the scsi command. Waits until command performed if do_wait is true. Otherwise write_behind_check() is used to check that the command
has finished. */ staticstruct st_request *
st_do_scsi(struct st_request * SRpnt, struct scsi_tape * STp, unsignedchar *cmd, int bytes, int direction, int timeout, int retries, int do_wait)
{ struct completion *waiting; struct rq_map_data *mdata = &STp->buffer->map_data; int ret;
/* if async, make sure there's no command outstanding */ if (!do_wait && ((STp->buffer)->last_SRpnt)) {
st_printk(KERN_ERR, STp, "Async command already active.\n"); if (signal_pending(current))
(STp->buffer)->syscall_result = (-EINTR); else
(STp->buffer)->syscall_result = (-EBUSY); return NULL;
}
if (!SRpnt) {
SRpnt = st_allocate_request(STp); if (!SRpnt) return NULL;
}
/* If async IO, set last_SRpnt. This ptr tells write_behind_check
which IO is outstanding. It's nulled out when the IO completes. */ if (!do_wait)
(STp->buffer)->last_SRpnt = SRpnt;
ret = st_scsi_execute(SRpnt, cmd, direction, NULL, bytes, timeout,
retries); if (ret) { /* could not allocate the buffer or request was too large */
(STp->buffer)->syscall_result = (-EBUSY);
(STp->buffer)->last_SRpnt = NULL;
} elseif (do_wait) {
wait_for_completion(waiting);
SRpnt->waiting = NULL;
(STp->buffer)->syscall_result = st_chk_result(STp, SRpnt);
}
return SRpnt;
}
/* Handle the write-behind checking (waits for completion). Returns -ENOSPC if write has been correct but EOM early warning reached, -EIO if write ended in error or zero if write successful. Asynchronous writes are used only in
variable block mode. */ staticint write_behind_check(struct scsi_tape * STp)
{ int retval = 0; struct st_buffer *STbuffer; struct st_partstat *STps; struct st_cmdstatus *cmdstatp; struct st_request *SRpnt;
STbuffer = STp->buffer; if (!STbuffer->writing) return 0;
DEB( if (STp->write_pending)
STp->nbr_waits++; else
STp->nbr_finished++;
) /* end DEB */
cmdstatp = &STbuffer->cmdstat; if (STbuffer->syscall_result) {
retval = -EIO; if (cmdstatp->have_sense && !cmdstatp->deferred &&
(cmdstatp->flags & SENSE_EOM) &&
(cmdstatp->sense_hdr.sense_key == NO_SENSE ||
cmdstatp->sense_hdr.sense_key == RECOVERED_ERROR)) { /* EOM at write-behind, has all data been written? */ if (!cmdstatp->remainder_valid ||
cmdstatp->uremainder64 == 0)
retval = -ENOSPC;
} if (retval == -EIO)
STps->drv_block = -1;
}
STbuffer->writing = 0;
DEB(if (debugging && retval)
st_printk(ST_DEB_MSG, STp, "Async write error %x, return value %d.\n",
STbuffer->cmdstat.midlevel_result, retval);) /* end DEB */
return retval;
}
/* Step over EOF if it has been inadvertently crossed (ioctl not used because
it messes up the block number). */ staticint cross_eof(struct scsi_tape * STp, int forward)
{ struct st_request *SRpnt; unsignedchar cmd[MAX_COMMAND_SIZE];
if (cmdstatp->have_sense && !cmdstatp->deferred &&
(cmdstatp->flags & SENSE_EOM) &&
(cmdstatp->sense_hdr.sense_key == NO_SENSE ||
cmdstatp->sense_hdr.sense_key == RECOVERED_ERROR) &&
(!cmdstatp->remainder_valid ||
cmdstatp->uremainder64 == 0)) { /* All written at EOM early warning */
STp->dirty = 0;
(STp->buffer)->buffer_bytes = 0; if (STps->drv_block >= 0)
STps->drv_block += blks;
result = (-ENOSPC);
} else {
st_printk(KERN_ERR, STp, "Error on flush.\n");
STps->drv_block = (-1);
result = (-EIO);
}
} else { if (STps->drv_block >= 0)
STps->drv_block += blks;
STp->dirty = 0;
(STp->buffer)->buffer_bytes = 0;
}
st_release_request(SRpnt);
SRpnt = NULL;
} return result;
}
/* Flush the tape buffer. The tape will be positioned correctly unless
seek_next is true. */ staticint flush_buffer(struct scsi_tape *STp, int seek_next)
{ int backspace, result; struct st_partstat *STps;
if (STp->ready != ST_READY) return 0;
/* * If there was a bus reset, block further access * to this device.
*/ if (STp->pos_unknown) return (-EIO);
/* Set the internal state after reset */ staticvoid reset_state(struct scsi_tape *STp)
{ int i; struct st_partstat *STps;
STp->pos_unknown = 0; for (i = 0; i < ST_NBR_PARTITIONS; i++) {
STps = &(STp->ps[i]);
STps->rw = ST_IDLE;
STps->eof = ST_NOEOF;
STps->at_sm = 0;
STps->last_block_valid = 0;
STps->drv_block = -1;
STps->drv_file = -1;
} if (STp->can_partitions) {
STp->partition = find_partition(STp); if (STp->partition < 0)
STp->partition = 0;
}
}
/* Test if the drive is ready. Returns either one of the codes below or a negative system
error code. */ #define CHKRES_READY 0 #define CHKRES_NEW_SESSION 1 #define CHKRES_NOT_READY 2 #define CHKRES_NO_TAPE 3
if (STp->can_partitions && STp->nbr_partitions < 1) { /* This code is reached when the device is opened for the first time after the driver has been initialized with tape in the drive and the
partition support has been enabled. */
DEBC_printk(STp, "Updating partition number in status.\n"); if ((STp->partition = find_partition(STp)) < 0) {
retval = STp->partition; goto err_out;
}
STp->new_partition = STp->partition;
STp->nbr_partitions = 1; /* This guess will be updated when necessary */
}
if (new_session) { /* Change the drive parameters for the new mode */
STp->density_changed = STp->blksize_changed = 0;
STp->compression_changed = 0; if (!(STm->defaults_for_writes) &&
(retval = set_mode_densblk(STp, STm)) < 0) goto err_out;
if (STp->default_drvbuffer != 0xff) { if (st_int_ioctl(STp, MTSETDRVBUFFER, STp->default_drvbuffer))
st_printk(KERN_WARNING, STp, "Can't set default drive " "buffering to %d.\n",
STp->default_drvbuffer);
}
}
return CHKRES_READY;
err_out: return retval;
}
/* Open the device. Needs to take the BKL only because of incrementing the SCSI host
module count. */ staticint st_open(struct inode *inode, struct file *filp)
{ int i, retval = (-EIO); int resumed = 0; struct scsi_tape *STp; struct st_partstat *STps; int dev = TAPE_NR(inode);
/* * We really want to do nonseekable_open(inode, filp); here, but some * versions of tar incorrectly call lseek on tapes and bail out if that * fails. So we disallow pread() and pwrite(), but permit lseeks.
*/
filp->f_mode &= ~(FMODE_PREAD | FMODE_PWRITE);
if (!(STp = scsi_tape_get(dev))) { return -ENXIO;
}
filp->private_data = STp;
spin_lock(&st_use_lock); if (STp->in_use) {
spin_unlock(&st_use_lock);
DEBC_printk(STp, "Device already in use.\n");
scsi_tape_put(STp); return (-EBUSY);
}
/* See that we have at least a one page buffer available */ if (!enlarge_buffer(STp->buffer, PAGE_SIZE)) {
st_printk(KERN_WARNING, STp, "Can't allocate one page tape buffer.\n");
retval = (-EOVERFLOW); goto err_out;
}
/* Flush the tape buffer before close */ staticint st_flush(struct file *filp, fl_owner_t id)
{ int result = 0, result2; unsignedchar cmd[MAX_COMMAND_SIZE]; struct st_request *SRpnt; struct scsi_tape *STp = filp->private_data; struct st_modedef *STm = &(STp->modes[STp->current_mode]); struct st_partstat *STps = &(STp->ps[STp->partition]);
if (file_count(filp) > 1) return 0;
if (STps->rw == ST_WRITING && !STp->pos_unknown) {
result = st_flush_write_buffer(STp); if (result != 0 && result != (-ENOSPC)) goto out;
}
if (STp->can_partitions &&
(result2 = switch_partition(STp)) < 0) {
DEBC_printk(STp, "switch_partition at close failed.\n"); if (result == 0)
result = result2; goto out;
}
DEBC( if (STp->nbr_requests)
st_printk(KERN_DEBUG, STp, "Number of r/w requests %d, dio used in %d, " "pages %d.\n", STp->nbr_requests, STp->nbr_dio,
STp->nbr_pages));
out: if (STp->rew_at_close) {
result2 = st_int_ioctl(STp, MTREW, 1); if (result == 0)
result = result2;
} return result;
}
/* Close the device and release it. BKL is not needed: this is the only thread
accessing this tape. */ staticint st_release(struct inode *inode, struct file *filp)
{ struct scsi_tape *STp = filp->private_data;
if (STp->door_locked == ST_LOCKED_AUTO)
do_door_lock(STp, 0);
/* The checks common to both reading and writing */ static ssize_t rw_checks(struct scsi_tape *STp, struct file *filp, size_t count)
{
ssize_t retval = 0;
/* * If we are in the middle of error recovery, don't let anyone * else try and use this device. Also, if error recovery fails, it * may try and take the device offline, in which case all further * access to the device is prohibited.
*/ if (!scsi_block_when_processing_errors(STp->device)) {
retval = (-ENXIO); goto out;
}
if (STp->ready != ST_READY) { if (STp->ready == ST_NO_TAPE)
retval = (-ENOMEDIUM); else
retval = (-EIO); goto out;
}
if (! STp->modes[STp->current_mode].defined) {
retval = (-ENXIO); goto out;
}
/* * If there was a bus reset, block further access * to this device.
*/ if (STp->pos_unknown) {
retval = (-EIO); goto out;
}
if (count == 0) goto out;
DEB( if (!STp->in_use) {
st_printk(ST_DEB_MSG, STp, "Incorrect device.\n");
retval = (-EIO); goto out;
} ) /* end DEB */
if (STp->can_partitions &&
(retval = switch_partition(STp)) < 0) goto out;
staticint setup_buffering(struct scsi_tape *STp, constchar __user *buf,
size_t count, int is_read)
{ int i, bufsize, retval = 0; struct st_buffer *STbp = STp->buffer;
if (is_read)
i = STp->try_dio_now && try_rdio; else
i = STp->try_dio_now && try_wdio;
if (i && ((unsignedlong)buf & queue_dma_alignment(
STp->device->request_queue)) == 0) {
i = sgl_map_user_pages(STbp, STbp->use_sg, (unsignedlong)buf,
count, (is_read ? READ : WRITE)); if (i > 0) {
STbp->do_dio = i;
STbp->buffer_bytes = 0; /* can be used as transfer counter */
} else
STbp->do_dio = 0; /* fall back to buffering with any error */
STbp->sg_segs = STbp->do_dio;
DEB( if (STbp->do_dio) {
STp->nbr_dio++;
STp->nbr_pages += STbp->do_dio;
}
)
} else
STbp->do_dio = 0;
DEB( STp->nbr_requests++; )
if (!STbp->do_dio) { if (STp->block_size)
bufsize = STp->block_size > st_fixed_buffer_size ?
STp->block_size : st_fixed_buffer_size; else {
bufsize = count; /* Make sure that data from previous user is not leaked even if
HBA does not return correct residual */ if (is_read && STp->sili && !STbp->cleared)
clear_buffer(STbp);
}
/* Can be called more than once after each setup_buffer() */ staticvoid release_buffering(struct scsi_tape *STp, int is_read)
{ struct st_buffer *STbp;
/* Write must be integral number of blocks */ if (STp->block_size != 0 && (count % STp->block_size) != 0) {
st_printk(KERN_WARNING, STp, "Write not multiple of tape block size.\n");
retval = (-EINVAL); goto out;
}
if (STbp->syscall_result != 0) { struct st_cmdstatus *cmdstatp = &STp->buffer->cmdstat;
DEBC_printk(STp, "Error on write:\n"); if (cmdstatp->have_sense && (cmdstatp->flags & SENSE_EOM)) {
scode = cmdstatp->sense_hdr.sense_key; if (cmdstatp->remainder_valid)
undone = (int)cmdstatp->uremainder64; elseif (STp->block_size == 0 &&
scode == VOLUME_OVERFLOW)
undone = transfer; else
undone = 0; if (STp->block_size != 0)
undone *= STp->block_size; if (undone <= do_count) { /* Only data from this write is not written */
count += undone;
b_point -= undone;
do_count -= undone; if (STp->block_size)
blks = (transfer - undone) / STp->block_size;
STps->eof = ST_EOM_OK; /* Continue in fixed block mode if all written in this request but still something left to write (retval left to zero)
*/ if (STp->block_size == 0 ||
undone > 0 || count == 0)
retval = (-ENOSPC); /* EOM within current request */
DEBC_printk(STp, "EOM with %d " "bytes unwritten.\n",
(int)count);
} else { /* EOT within data buffered earlier (possible only
in fixed block mode without direct i/o) */ if (!retry_eot && !cmdstatp->deferred &&
(scode == NO_SENSE || scode == RECOVERED_ERROR)) {
move_buffer_data(STp->buffer, transfer - undone);
retry_eot = 1; if (STps->drv_block >= 0) {
STps->drv_block += (transfer - undone) /
STp->block_size;
}
STps->eof = ST_EOM_OK;
DEBC_printk(STp, "Retry " "write of %d " "bytes at EOM.\n",
STp->buffer->buffer_bytes); goto retry_write;
} else { /* Either error within data buffered by driver or
failed retry */
count -= do_count;
blks = do_count = 0;
STps->eof = ST_EOM_ERROR;
STps->drv_block = (-1); /* Too cautious? */
retval = (-EIO); /* EOM for old data */
DEBC_printk(STp, "EOM with " "lost data.\n");
}
}
} else {
count += do_count;
STps->drv_block = (-1); /* Too cautious? */
retval = STbp->syscall_result;
}
}
if (STps->drv_block >= 0) { if (STp->block_size == 0)
STps->drv_block += (do_count > 0); else
STps->drv_block += blks;
}
STbp->buffer_bytes = 0;
STp->dirty = 0;
if (retval || retry_eot) { if (count < total)
retval = total - count; goto out;
}
}
if (STps->eof == ST_EOD_1)
STps->eof = ST_EOM_OK; elseif (STps->eof != ST_EOM_OK)
STps->eof = ST_NOEOF;
retval = total - count;
out: if (SRpnt != NULL)
st_release_request(SRpnt);
release_buffering(STp, 0);
mutex_unlock(&STp->lock);
return retval;
}
/* Read data from the tape. Returns zero in the normal case, one if the eof status has changed, and the negative error code in case of a fatal error. Otherwise updates the buffer and the eof state.
Does release user buffer mapping if it is set.
*/ staticlong read_tape(struct scsi_tape *STp, long count, struct st_request ** aSRpnt)
{ int transfer, blks, bytes; unsignedchar cmd[MAX_COMMAND_SIZE]; struct st_request *SRpnt; struct st_modedef *STm; struct st_partstat *STps; struct st_buffer *STbp; int retval = 0;
if (cmdstatp->sense_hdr.sense_key == BLANK_CHECK)
cmdstatp->flags &= 0xcf; /* No need for EOM in this case */
if (cmdstatp->flags != 0) { /* EOF, EOM, or ILI */ /* Compute the residual count */ if (cmdstatp->remainder_valid)
transfer = (int)cmdstatp->uremainder64; else
transfer = 0; if (cmdstatp->sense_hdr.sense_key == MEDIUM_ERROR) { if (STp->block_size == 0)
transfer = bytes; /* Some drives set ILI with MEDIUM ERROR */
cmdstatp->flags &= ~SENSE_ILI;
}
if (cmdstatp->flags & SENSE_ILI) { /* ILI */ if (STp->block_size == 0 &&
transfer < 0) {
st_printk(KERN_NOTICE, STp, "Failed to read %d " "byte block with %d " "byte transfer.\n",
bytes - transfer,
bytes); if (STps->drv_block >= 0)
STps->drv_block += 1;
STbp->buffer_bytes = 0; return (-ENOMEM);
} elseif (STp->block_size == 0) {
STbp->buffer_bytes = bytes - transfer;
} else {
st_release_request(SRpnt);
SRpnt = *aSRpnt = NULL; if (transfer == blks) { /* We did not get anything, error */
st_printk(KERN_NOTICE, STp, "Incorrect " "block size.\n"); if (STps->drv_block >= 0)
STps->drv_block += blks - transfer + 1;
st_int_ioctl(STp, MTBSR, 1); return (-EIO);
} /* We have some data, deliver it */
STbp->buffer_bytes = (blks - transfer) *
STp->block_size;
DEBC_printk(STp, "ILI but " "enough data " "received %ld " "%d.\n", count,
STbp->buffer_bytes); if (STps->drv_block >= 0)
STps->drv_block += 1; if (st_int_ioctl(STp, MTBSR, 1)) return (-EIO);
}
} elseif (cmdstatp->flags & SENSE_FMK) { /* FM overrides EOM */ if (STps->eof != ST_FM_HIT)
STps->eof = ST_FM_HIT; else
STps->eof = ST_EOD_2; if (STp->block_size == 0)
STbp->buffer_bytes = 0; else
STbp->buffer_bytes =
bytes - transfer * STp->block_size;
DEBC_printk(STp, "EOF detected (%d " "bytes read).\n",
STbp->buffer_bytes);
} elseif (cmdstatp->flags & SENSE_EOM) { if (STps->eof == ST_FM)
STps->eof = ST_EOD_1; else
STps->eof = ST_EOM_OK; if (STp->block_size == 0)
STbp->buffer_bytes = bytes - transfer; else
STbp->buffer_bytes =
bytes - transfer * STp->block_size;
DEBC_printk(STp, "EOM detected (%d " "bytes read).\n",
STbp->buffer_bytes);
}
} /* end of EOF, EOM, ILI test */ else { /* nonzero sense key */
DEBC_printk(STp, "Tape error while reading.\n");
STps->drv_block = (-1); if (STps->eof == ST_FM &&
cmdstatp->sense_hdr.sense_key == BLANK_CHECK) {
DEBC_printk(STp, "Zero returned for " "first BLANK CHECK " "after EOF.\n");
STps->eof = ST_EOD_2; /* First BLANK_CHECK after FM */
} else/* Some other extended sense code */
retval = (-EIO);
}
if (STbp->buffer_bytes < 0) /* Caused by bogus sense data */
STbp->buffer_bytes = 0;
} /* End of extended sense test */ else { /* Non-extended sense */
retval = STbp->syscall_result;
}
} /* End of error handling */ else { /* Read successful */
STbp->buffer_bytes = bytes; if (STp->sili) /* In fixed block mode residual is always zero here */
STbp->buffer_bytes -= STp->buffer->cmdstat.residual;
}
if (STps->drv_block >= 0) { if (STp->block_size == 0)
STps->drv_block++; else
STps->drv_block += STbp->buffer_bytes / STp->block_size;
} return retval;
}
if (do_dio) { /* Check the buffer writability before any tape movement. Don't alter
buffer data. */ if (copy_from_user(&i, buf, 1) != 0 ||
copy_to_user(buf, &i, 1) != 0 ||
copy_from_user(&i, buf + count - 1, 1) != 0 ||
copy_to_user(buf + count - 1, &i, 1) != 0) {
retval = (-EFAULT); goto out;
}
}
STps->rw = ST_READING;
/* Loop until enough data in buffer or a special condition found */ for (total = 0, special = 0; total < count && !special;) {
/* Get new data if the buffer is empty */ if (STbp->buffer_bytes == 0) {
special = read_tape(STp, count - total, &SRpnt); if (special < 0) { /* No need to continue read */
retval = special; goto out;
}
}
/* Move the data from driver buffer to user buffer */ if (STbp->buffer_bytes > 0) {
DEB( if (debugging && STps->eof != ST_NOEOF)
st_printk(ST_DEB_MSG, STp, "EOF up (%d). Left %d, needed %d.\n",
STps->eof, STbp->buffer_bytes,
(int)(count - total));
) /* end DEB */
transfer = STbp->buffer_bytes < count - total ?
STbp->buffer_bytes : count - total; if (!do_dio) {
i = from_buffer(STbp, buf, transfer); if (i) {
retval = i; goto out;
}
}
buf += transfer;
total += transfer;
}
if (STp->block_size == 0) break; /* Read only one variable length block */
} /* for (total = 0, special = 0;
total < count && !special; ) */
/* Change the eof state if no data from tape or buffer */ if (total == 0) { if (STps->eof == ST_FM_HIT) {
STps->eof = ST_FM;
STps->drv_block = 0; if (STps->drv_file >= 0)
STps->drv_file++;
} elseif (STps->eof == ST_EOD_1) {
STps->eof = ST_EOD_2;
STps->drv_block = 0; if (STps->drv_file >= 0)
STps->drv_file++;
} elseif (STps->eof == ST_EOD_2)
STps->eof = ST_EOD;
} elseif (STps->eof == ST_FM)
STps->eof = ST_NOEOF;
retval = total;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.