diff options
Diffstat (limited to 'drivers/char/ftape/zftape/zftape-write.c')
-rw-r--r-- | drivers/char/ftape/zftape/zftape-write.c | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/drivers/char/ftape/zftape/zftape-write.c b/drivers/char/ftape/zftape/zftape-write.c new file mode 100644 index 00000000000..94327b8c97b --- /dev/null +++ b/drivers/char/ftape/zftape/zftape-write.c @@ -0,0 +1,483 @@ +/* + * 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 */ +} |