/* * 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: /cvs/work/hardhat2_1_ar7/linux-2.4.17_mvl21/drivers/char/ftape/zftape/zftape-write.c,v $ * $Revision: 1.1.1.1 $ * $Date: 2003/06/23 22:18:26 $ * * This file contains the writing code * for the QIC-117 floppy-tape driver for Linux. */ #include #include #include #if LINUX_VERSION_CODE >= KERNEL_VER(2,1,6) #include #else #include #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. */ /* 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 *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 LINUX_VERSION_CODE > KERNEL_VER(2,1,3) if (copy_from_user(dst_buf + pos->seg_byte_pos, src_buf, *cnt) != 0) { TRACE_EXIT -EFAULT; } #else TRACE_CATCH(verify_area(VERIFY_READ, src_buf, *cnt),); memcpy_fromfs(dst_buf + pos->seg_byte_pos, src_buf, *cnt); #endif 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 neccessary */ 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 *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* 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 */ }