|  | /* | 
|  | *      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 */ | 
|  | } |