blob: 94327b8c97b948abd8babc6e53f898ba8ec96568 [file] [log] [blame]
/*
* Copyright (C) 1996, 1997 Claus 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-write.c,v $
* $Revision: 1.3 $
* $Date: 1997/11/06 00:50:29 $
*
* This file contains the writing code
* for the QIC-117 floppy-tape driver for Linux.
*/
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/zftape.h>
#include <asm/uaccess.h>
#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.
*/
/* Local vars.
*/
static int last_write_failed;
static int need_flush;
void zft_prevent_flush(void)
{
need_flush = 0;
}
static int zft_write_header_segments(__u8* buffer)
{
int header_1_ok = 0;
int header_2_ok = 0;
unsigned int time_stamp;
TRACE_FUN(ft_t_noise);
TRACE_CATCH(ftape_abort_operation(),);
ftape_seek_to_bot(); /* prevents extra rewind */
if (GET4(buffer, 0) != FT_HSEG_MAGIC) {
TRACE_ABORT(-EIO, ft_t_err,
"wrong header signature found, aborting");
}
/* Be optimistic: */
PUT4(buffer, FT_SEG_CNT,
zft_written_segments + GET4(buffer, FT_SEG_CNT) + 2);
if ((time_stamp = zft_get_time()) != 0) {
PUT4(buffer, FT_WR_DATE, time_stamp);
if (zft_label_changed) {
PUT4(buffer, FT_LABEL_DATE, time_stamp);
}
}
TRACE(ft_t_noise,
"writing first header segment %d", ft_header_segment_1);
header_1_ok = zft_verify_write_segments(ft_header_segment_1,
buffer, FT_SEGMENT_SIZE,
zft_deblock_buf) >= 0;
TRACE(ft_t_noise,
"writing second header segment %d", ft_header_segment_2);
header_2_ok = zft_verify_write_segments(ft_header_segment_2,
buffer, FT_SEGMENT_SIZE,
zft_deblock_buf) >= 0;
if (!header_1_ok) {
TRACE(ft_t_warn, "Warning: "
"update of first header segment failed");
}
if (!header_2_ok) {
TRACE(ft_t_warn, "Warning: "
"update of second header segment failed");
}
if (!header_1_ok && !header_2_ok) {
TRACE_ABORT(-EIO, ft_t_err, "Error: "
"update of both header segments failed.");
}
TRACE_EXIT 0;
}
int zft_update_header_segments(void)
{
TRACE_FUN(ft_t_noise);
/* must NOT use zft_write_protected, as it also includes the
* file access mode. But we also want to update when soft
* write protection is enabled (O_RDONLY)
*/
if (ft_write_protected || zft_old_ftape) {
TRACE_ABORT(0, ft_t_noise, "Tape set read-only: no update");
}
if (!zft_header_read) {
TRACE_ABORT(0, ft_t_noise, "Nothing to update");
}
if (!zft_header_changed) {
zft_header_changed = zft_written_segments > 0;
}
if (!zft_header_changed && !zft_volume_table_changed) {
TRACE_ABORT(0, ft_t_noise, "Nothing to update");
}
TRACE(ft_t_noise, "Updating header segments");
if (ftape_get_status()->fti_state == writing) {
TRACE_CATCH(ftape_loop_until_writes_done(),);
}
TRACE_CATCH(ftape_abort_operation(),);
zft_deblock_segment = -1; /* invalidate the cache */
if (zft_header_changed) {
TRACE_CATCH(zft_write_header_segments(zft_hseg_buf),);
}
if (zft_volume_table_changed) {
TRACE_CATCH(zft_update_volume_table(ft_first_data_segment),);
}
zft_header_changed =
zft_volume_table_changed =
zft_label_changed =
zft_written_segments = 0;
TRACE_CATCH(ftape_abort_operation(),);
ftape_seek_to_bot();
TRACE_EXIT 0;
}
static int read_merge_buffer(int seg_pos, __u8 *buffer, int offset, int seg_sz)
{
int result = 0;
const ft_trace_t old_tracing = TRACE_LEVEL;
TRACE_FUN(ft_t_flow);
if (zft_qic_mode) {
/* writing in the middle of a volume is NOT allowed
*
*/
TRACE(ft_t_noise, "No need to read a segment");
memset(buffer + offset, 0, seg_sz - offset);
TRACE_EXIT 0;
}
TRACE(ft_t_any, "waiting");
ftape_start_writing(FT_WR_MULTI);
TRACE_CATCH(ftape_loop_until_writes_done(),);
TRACE(ft_t_noise, "trying to read segment %d from offset %d",
seg_pos, offset);
SET_TRACE_LEVEL(ft_t_bug);
result = zft_fetch_segment_fraction(seg_pos, buffer,
FT_RD_SINGLE,
offset, seg_sz - offset);
SET_TRACE_LEVEL(old_tracing);
if (result != (seg_sz - offset)) {
TRACE(ft_t_noise, "Ignore error: read_segment() result: %d",
result);
memset(buffer + offset, 0, seg_sz - offset);
}
TRACE_EXIT 0;
}
/* flush the write buffer to tape and write an eof-marker at the
* current position if not in raw mode. This function always
* positions the tape before the eof-marker. _ftape_close() should
* then advance to the next segment.
*
* the parameter "finish_volume" describes whether to position before
* or after the possibly created file-mark. We always position after
* the file-mark when called from ftape_close() and a flush was needed
* (that is ftape_write() was the last tape operation before calling
* ftape_flush) But we always position before the file-mark when this
* function get's called from outside ftape_close()
*/
int zft_flush_buffers(void)
{
int result;
int data_remaining;
int this_segs_size;
TRACE_FUN(ft_t_flow);
TRACE(ft_t_data_flow,
"entered, ftape_state = %d", ftape_get_status()->fti_state);
if (ftape_get_status()->fti_state != writing && !need_flush) {
TRACE_ABORT(0, ft_t_noise, "no need for flush");
}
zft_io_state = zft_idle; /* triggers some initializations for the
* read and write routines
*/
if (last_write_failed) {
ftape_abort_operation();
TRACE_EXIT -EIO;
}
TRACE(ft_t_noise, "flushing write buffers");
this_segs_size = zft_get_seg_sz(zft_pos.seg_pos);
if (this_segs_size == zft_pos.seg_byte_pos) {
zft_pos.seg_pos ++;
data_remaining = zft_pos.seg_byte_pos = 0;
} else {
data_remaining = zft_pos.seg_byte_pos;
}
/* If there is any data not written to tape yet, append zero's
* up to the end of the sector (if using compression) or merge
* it with the data existing on the tape Then write the
* segment(s) to tape.
*/
TRACE(ft_t_noise, "Position:\n"
KERN_INFO "seg_pos : %d\n"
KERN_INFO "byte pos : %d\n"
KERN_INFO "remaining: %d",
zft_pos.seg_pos, zft_pos.seg_byte_pos, data_remaining);
if (data_remaining > 0) {
do {
this_segs_size = zft_get_seg_sz(zft_pos.seg_pos);
if (this_segs_size > data_remaining) {
TRACE_CATCH(read_merge_buffer(zft_pos.seg_pos,
zft_deblock_buf,
data_remaining,
this_segs_size),
last_write_failed = 1);
}
result = ftape_write_segment(zft_pos.seg_pos,
zft_deblock_buf,
FT_WR_MULTI);
if (result != this_segs_size) {
TRACE(ft_t_err, "flush buffers failed");
zft_pos.tape_pos -= zft_pos.seg_byte_pos;
zft_pos.seg_byte_pos = 0;
last_write_failed = 1;
TRACE_EXIT result;
}
zft_written_segments ++;
TRACE(ft_t_data_flow,
"flush, moved out buffer: %d", result);
/* need next segment for more data (empty segments?)
*/
if (result < data_remaining) {
if (result > 0) {
/* move remainder to buffer beginning
*/
memmove(zft_deblock_buf,
zft_deblock_buf + result,
FT_SEGMENT_SIZE - result);
}
}
data_remaining -= result;
zft_pos.seg_pos ++;
} while (data_remaining > 0);
TRACE(ft_t_any, "result: %d", result);
zft_deblock_segment = --zft_pos.seg_pos;
if (data_remaining == 0) { /* first byte next segment */
zft_pos.seg_byte_pos = this_segs_size;
} else { /* after data previous segment, data_remaining < 0 */
zft_pos.seg_byte_pos = data_remaining + result;
}
} else {
TRACE(ft_t_noise, "zft_deblock_buf empty");
zft_pos.seg_pos --;
zft_pos.seg_byte_pos = zft_get_seg_sz (zft_pos.seg_pos);
ftape_start_writing(FT_WR_MULTI);
}
TRACE(ft_t_any, "waiting");
if ((result = ftape_loop_until_writes_done()) < 0) {
/* that's really bad. What to to with zft_tape_pos?
*/
TRACE(ft_t_err, "flush buffers failed");
}
TRACE(ft_t_any, "zft_seg_pos: %d, zft_seg_byte_pos: %d",
zft_pos.seg_pos, zft_pos.seg_byte_pos);
last_write_failed =
need_flush = 0;
TRACE_EXIT result;
}
/* return-value: the number of bytes removed from the user-buffer
*
* out:
* int *write_cnt: how much actually has been moved to the
* zft_deblock_buf
* int req_len : MUST NOT BE CHANGED, except at EOT, in
* which case it may be adjusted
* in :
* char *buff : the user buffer
* int buf_pos_write : copy of buf_len_wr int
* this_segs_size : the size in bytes of the actual segment
* char
* *zft_deblock_buf : zft_deblock_buf
*/
static int zft_simple_write(int *cnt,
__u8 *dst_buf, const int seg_sz,
const __u8 __user *src_buf, const int req_len,
const zft_position *pos,const zft_volinfo *volume)
{
int space_left;
TRACE_FUN(ft_t_flow);
/* volume->size holds the tape capacity while volume is open */
if (pos->tape_pos + volume->blk_sz > volume->size) {
TRACE_EXIT -ENOSPC;
}
/* remaining space in this segment, NOT zft_deblock_buf
*/
space_left = seg_sz - pos->seg_byte_pos;
*cnt = req_len < space_left ? req_len : space_left;
if (copy_from_user(dst_buf + pos->seg_byte_pos, src_buf, *cnt) != 0) {
TRACE_EXIT -EFAULT;
}
TRACE_EXIT *cnt;
}
static int check_write_access(int req_len,
const zft_volinfo **volume,
zft_position *pos,
const unsigned int blk_sz)
{
int result;
TRACE_FUN(ft_t_flow);
if ((req_len % zft_blk_sz) != 0) {
TRACE_ABORT(-EINVAL, ft_t_info,
"write-count %d must be multiple of block-size %d",
req_len, blk_sz);
}
if (zft_io_state == zft_writing) {
/* all other error conditions have been checked earlier
*/
TRACE_EXIT 0;
}
zft_io_state = zft_idle;
TRACE_CATCH(zft_check_write_access(pos),);
/* If we haven't read the header segment yet, do it now.
* This will verify the configuration, get the bad sector
* table and read the volume table segment
*/
if (!zft_header_read) {
TRACE_CATCH(zft_read_header_segments(),);
}
/* fine. Now the tape is either at BOT or at EOD,
* Write start of volume now
*/
TRACE_CATCH(zft_open_volume(pos, blk_sz, zft_use_compression),);
*volume = zft_find_volume(pos->seg_pos);
DUMP_VOLINFO(ft_t_noise, "", *volume);
zft_just_before_eof = 0;
/* now merge with old data if necessary */
if (!zft_qic_mode && pos->seg_byte_pos != 0){
result = zft_fetch_segment(pos->seg_pos,
zft_deblock_buf,
FT_RD_SINGLE);
if (result < 0) {
if (result == -EINTR || result == -ENOSPC) {
TRACE_EXIT result;
}
TRACE(ft_t_noise,
"ftape_read_segment() result: %d. "
"This might be normal when using "
"a newly\nformatted tape", result);
memset(zft_deblock_buf, '\0', pos->seg_byte_pos);
}
}
zft_io_state = zft_writing;
TRACE_EXIT 0;
}
static int fill_deblock_buf(__u8 *dst_buf, const int seg_sz,
zft_position *pos, const zft_volinfo *volume,
const char __user *usr_buf, const int req_len)
{
int cnt = 0;
int result = 0;
TRACE_FUN(ft_t_flow);
if (seg_sz == 0) {
TRACE_ABORT(0, ft_t_data_flow, "empty segment");
}
TRACE(ft_t_data_flow, "\n"
KERN_INFO "remaining req_len: %d\n"
KERN_INFO " buf_pos: %d",
req_len, pos->seg_byte_pos);
/* zft_deblock_buf will not contain a valid segment any longer */
zft_deblock_segment = -1;
if (zft_use_compression) {
TRACE_CATCH(zft_cmpr_lock(1 /* try to load */),);
TRACE_CATCH(result= (*zft_cmpr_ops->write)(&cnt,
dst_buf, seg_sz,
usr_buf, req_len,
pos, volume),);
} else {
TRACE_CATCH(result= zft_simple_write(&cnt,
dst_buf, seg_sz,
usr_buf, req_len,
pos, volume),);
}
pos->volume_pos += result;
pos->seg_byte_pos += cnt;
pos->tape_pos += cnt;
TRACE(ft_t_data_flow, "\n"
KERN_INFO "removed from user-buffer : %d bytes.\n"
KERN_INFO "copied to zft_deblock_buf: %d bytes.\n"
KERN_INFO "zft_tape_pos : " LL_X " bytes.",
result, cnt, LL(pos->tape_pos));
TRACE_EXIT result;
}
/* called by the kernel-interface routine "zft_write()"
*/
int _zft_write(const char __user *buff, int req_len)
{
int result = 0;
int written = 0;
int write_cnt;
int seg_sz;
static const zft_volinfo *volume = NULL;
TRACE_FUN(ft_t_flow);
zft_resid = req_len;
last_write_failed = 1; /* reset to 0 when successful */
/* check if write is allowed
*/
TRACE_CATCH(check_write_access(req_len, &volume,&zft_pos,zft_blk_sz),);
while (req_len > 0) {
/* Allow us to escape from this loop with a signal !
*/
FT_SIGNAL_EXIT(_DONT_BLOCK);
seg_sz = zft_get_seg_sz(zft_pos.seg_pos);
if ((write_cnt = fill_deblock_buf(zft_deblock_buf,
seg_sz,
&zft_pos,
volume,
buff,
req_len)) < 0) {
zft_resid -= written;
if (write_cnt == -ENOSPC) {
/* leave the remainder to flush_buffers()
*/
TRACE(ft_t_info, "No space left on device");
last_write_failed = 0;
if (!need_flush) {
need_flush = written > 0;
}
TRACE_EXIT written > 0 ? written : -ENOSPC;
} else {
TRACE_EXIT result;
}
}
if (zft_pos.seg_byte_pos == seg_sz) {
TRACE_CATCH(ftape_write_segment(zft_pos.seg_pos,
zft_deblock_buf,
FT_WR_ASYNC),
zft_resid -= written);
zft_written_segments ++;
zft_pos.seg_byte_pos = 0;
zft_deblock_segment = zft_pos.seg_pos;
++zft_pos.seg_pos;
}
written += write_cnt;
buff += write_cnt;
req_len -= write_cnt;
} /* while (req_len > 0) */
TRACE(ft_t_data_flow, "remaining in blocking buffer: %d",
zft_pos.seg_byte_pos);
TRACE(ft_t_data_flow, "just written bytes: %d", written);
last_write_failed = 0;
zft_resid -= written;
need_flush = need_flush || written > 0;
TRACE_EXIT written; /* bytes written */
}