/* $Id: posix-tape.c,v 1.7 2006/09/30 12:35:01 fredette Exp $ */ /* host/posix/posix-tape.c - implementation of tapes on a POSIX system: */ /* * Copyright (c) 2003 Matt Fredette * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Matt Fredette. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include _TME_RCSID("$Id: posix-tape.c,v 1.7 2006/09/30 12:35:01 fredette Exp $"); /* includes: */ #include #include #include #include #include #include #include #ifdef HAVE_STDARG_H #include #else /* HAVE_STDARG_H */ #include #endif /* HAVE_STDARG_H */ /* macros: */ /* tape flags: */ #define TME_POSIX_TAPE_FLAG_RO TME_BIT(0) #define TME_POSIX_TAPE_FLAG_DIRTY TME_BIT(1) /* the size of the control callout ring: */ #define TME_POSIX_TAPE_CONTROL_RING_SIZE (16) /* the callout flags: */ #define TME_POSIX_TAPE_CALLOUT_CHECK (0) #define TME_POSIX_TAPE_CALLOUT_RUNNING TME_BIT(0) #define TME_POSIX_TAPE_CALLOUTS_MASK (-2) #define TME_POSIX_TAPE_CALLOUT_CONTROL TME_BIT(1) /* types: */ /* a posix tape control: */ struct tme_posix_tape_control { /* the control: */ unsigned int tme_posix_tape_control_which; }; /* a posix tape segment: */ struct tme_posix_tape_segment { /* tape segments are kept on a doubly linked list: */ struct tme_posix_tape_segment *tme_posix_tape_segment_next; struct tme_posix_tape_segment *tme_posix_tape_segment_prev; /* the filename of this tape segment: */ char *tme_posix_tape_segment_filename; /* the file descriptor of this tape segment: */ int tme_posix_tape_segment_fd; /* this is nonzero iff this tape segment is a real tape: */ int tme_posix_tape_segment_real_tape; }; /* a posix tape: */ struct tme_posix_tape { /* backpointer to our element: */ struct tme_element *tme_posix_tape_element; /* our mutex: */ tme_mutex_t tme_posix_tape_mutex; /* our flags: */ int tme_posix_tape_flags; /* the tape segments: */ struct tme_posix_tape_segment *tme_posix_tape_segments; /* the connection: */ struct tme_tape_connection *tme_posix_tape_connection; /* the callout flags: */ int tme_posix_tape_callout_flags; /* the control ring buffer: */ struct tme_posix_tape_control tme_posix_tape_controls[TME_POSIX_TAPE_CONTROL_RING_SIZE]; unsigned int tme_posix_tape_control_head; unsigned int tme_posix_tape_control_tail; /* the current tape segment: */ struct tme_posix_tape_segment *tme_posix_tape_segment_current; /* the block sizes: */ unsigned long tme_posix_tape_block_size_min; unsigned long tme_posix_tape_block_size_max; unsigned long tme_posix_tape_block_size_fixed; /* the tape buffer: */ unsigned long tme_posix_tape_buffer_size; tme_uint8_t *tme_posix_tape_buffer_data; unsigned long tme_posix_tape_buffer_flags; unsigned long tme_posix_tape_buffer_count_xfer; }; /* this allocates the next control in the ring buffer: */ struct tme_posix_tape_control * _tme_posix_tape_control_new(struct tme_posix_tape *posix_tape) { int old_head; struct tme_posix_tape_control *control; /* abort if the ring buffer overflows: */ old_head = posix_tape->tme_posix_tape_control_head; posix_tape->tme_posix_tape_control_head = ((old_head + 1) & (TME_POSIX_TAPE_CONTROL_RING_SIZE - 1)); if ((posix_tape->tme_posix_tape_control_head == posix_tape->tme_posix_tape_control_tail) && (posix_tape->tme_posix_tape_connection != NULL)) { abort(); } /* return the control: */ control = &posix_tape->tme_posix_tape_controls[old_head]; return (control); } /* the posix tape callout function. it must be called with the mutex locked: */ static void _tme_posix_tape_callout(struct tme_posix_tape *posix_tape, int new_callouts) { struct tme_tape_connection *conn_tape; unsigned int old_tail; struct tme_posix_tape_control *control; int callouts, later_callouts; int rc; /* add in any new callouts: */ posix_tape->tme_posix_tape_callout_flags |= new_callouts; /* if this function is already running in another thread, simply return now. the other thread will do our work: */ if (posix_tape->tme_posix_tape_callout_flags & TME_POSIX_TAPE_CALLOUT_RUNNING) { return; } /* callouts are now running: */ posix_tape->tme_posix_tape_callout_flags |= TME_POSIX_TAPE_CALLOUT_RUNNING; /* assume that we won't need any later callouts: */ later_callouts = 0; /* loop while callouts are needed: */ for (; ((callouts = posix_tape->tme_posix_tape_callout_flags) & TME_POSIX_TAPE_CALLOUTS_MASK); ) { /* clear the needed callouts: */ posix_tape->tme_posix_tape_callout_flags = (callouts & ~TME_POSIX_TAPE_CALLOUTS_MASK); callouts &= TME_POSIX_TAPE_CALLOUTS_MASK; /* get the tape connection: */ conn_tape = posix_tape->tme_posix_tape_connection; /* if we need to call out a control: */ if (callouts & TME_POSIX_TAPE_CALLOUT_CONTROL) { /* there must be a control to call out: */ old_tail = posix_tape->tme_posix_tape_control_tail; assert (old_tail != posix_tape->tme_posix_tape_control_head); control = &posix_tape->tme_posix_tape_controls[old_tail]; /* unlock the mutex: */ tme_mutex_unlock(&posix_tape->tme_posix_tape_mutex); /* do the callout: */ rc = TME_OK; if (conn_tape != NULL) { switch (control->tme_posix_tape_control_which) { case TME_TAPE_CONTROL_LOAD: case TME_TAPE_CONTROL_UNLOAD: rc = ((*conn_tape->tme_tape_connection_control) (conn_tape, control->tme_posix_tape_control_which)); break; default: rc = TME_OK; assert(FALSE); } } /* lock the mutex: */ tme_mutex_lock(&posix_tape->tme_posix_tape_mutex); /* if the callout was unsuccessful, remember that at some later time this callout should be attempted again: */ if (rc != TME_OK) { later_callouts |= TME_POSIX_TAPE_CALLOUT_CONTROL; } /* otherwise, this callout was successful: */ else { /* advance the tail pointer: */ posix_tape->tme_posix_tape_control_tail = ((old_tail + 1) & (TME_POSIX_TAPE_CONTROL_RING_SIZE - 1)); /* if there are more controls to callout, call them out now: */ if (posix_tape->tme_posix_tape_control_tail != posix_tape->tme_posix_tape_control_head) { posix_tape->tme_posix_tape_callout_flags |= TME_POSIX_TAPE_CALLOUT_CONTROL; } } } } /* put in any later callouts, and clear that callouts are running: */ posix_tape->tme_posix_tape_callout_flags = later_callouts; } /* this closes and frees all tape segments: */ static void _tme_posix_tape_segments_close(struct tme_posix_tape *posix_tape) { struct tme_posix_tape_segment *segment; /* while we have segments: */ for (; posix_tape->tme_posix_tape_segments != NULL; ) { /* get the next segment and remove it from the list: */ segment = posix_tape->tme_posix_tape_segments; posix_tape->tme_posix_tape_segments = segment->tme_posix_tape_segment_next; /* if the segment file descriptor is open, close it: */ if (segment->tme_posix_tape_segment_fd >= 0) { close(segment->tme_posix_tape_segment_fd); } /* free the segment filename: */ tme_free(segment->tme_posix_tape_segment_filename); /* free the segment itself: */ tme_free(segment); } /* there is no current segment: */ posix_tape->tme_posix_tape_segment_current = NULL; } /* this unloads a tape: */ static int _tme_posix_tape_unload(struct tme_posix_tape *posix_tape) { /* close and free all tape segments: */ _tme_posix_tape_segments_close(posix_tape); return (TME_OK); } /* this opens a tape segment and makes it the current segment: */ static int _tme_posix_tape_segment_open(struct tme_posix_tape *posix_tape, struct tme_posix_tape_segment *segment) { /* there is no current segment: */ posix_tape->tme_posix_tape_segment_current = NULL; /* open the segment: */ segment->tme_posix_tape_segment_fd = open(segment->tme_posix_tape_segment_filename, ((posix_tape->tme_posix_tape_flags & TME_POSIX_TAPE_FLAG_RO) ? O_RDONLY : O_RDWR)); /* if the open failed: */ if (segment->tme_posix_tape_segment_fd < 0) { return (errno); } /* set the current segment: */ posix_tape->tme_posix_tape_segment_current = segment; return (TME_OK); } /* this rewinds a tape: */ static int _tme_posix_tape_rewind(struct tme_posix_tape *posix_tape) { struct tme_posix_tape_segment *segment; int rc; /* assume this will succeed: */ rc = TME_OK; /* if there is a current segment and it's not the first segment: */ segment = posix_tape->tme_posix_tape_segment_current; if (segment != NULL && segment != posix_tape->tme_posix_tape_segments) { /* this must be a normal file: */ assert (!segment->tme_posix_tape_segment_real_tape); /* if the segment is open, close it: */ if (segment->tme_posix_tape_segment_fd >= 0) { close(segment->tme_posix_tape_segment_fd); segment->tme_posix_tape_segment_fd = -1; } } /* get the first segment: */ segment = posix_tape->tme_posix_tape_segments; /* if this is a real tape: */ if (segment->tme_posix_tape_segment_real_tape) { /* the tape must be open: */ assert (segment->tme_posix_tape_segment_fd >= 0); /* XXX TBD */ abort(); } /* otherwise, this is a normal file: */ else { /* if the file isn't open, open it, else seek it to the beginning: */ if (segment->tme_posix_tape_segment_fd < 0) { rc = _tme_posix_tape_segment_open(posix_tape, segment); } else { if (lseek(segment->tme_posix_tape_segment_fd, 0, SEEK_SET) != 0) { rc = errno; } } } return (rc); } /* this skips file marks: */ static int _tme_posix_tape_mark_skip(struct tme_posix_tape *posix_tape, unsigned int count, int forward) { struct tme_posix_tape_segment *segment; int rc; segment = posix_tape->tme_posix_tape_segment_current; /* if we are at end-of-media, or if we're skipping no marks, do nothing: */ if (segment == NULL || count == 0) { return (TME_OK); } /* this segment must be a normal file: */ assert (!segment->tme_posix_tape_segment_real_tape); /* if this segment is open, close it: */ if (segment->tme_posix_tape_segment_fd >= 0) { close(segment->tme_posix_tape_segment_fd); segment->tme_posix_tape_segment_fd = -1; } /* skip the marks: */ for (; segment != NULL && count-- > 0; ) { segment = (forward ? segment->tme_posix_tape_segment_next : segment->tme_posix_tape_segment_prev); } /* for now, if we are skipping in reverse we must have a segment - what do we do otherwise? */ assert (forward || segment != NULL); /* clear the active segment pointer: */ posix_tape->tme_posix_tape_segment_current = NULL; /* if we have a segment, open it: */ if (segment != NULL) { rc = _tme_posix_tape_segment_open(posix_tape, segment); assert (rc == TME_OK); } /* if we are skipping in reverse, we must leave the tape on the beginning of media side of the file mark: */ if (!forward) { rc = (lseek(segment->tme_posix_tape_segment_fd, 0, SEEK_END) < 0); assert (rc == 0); } return (TME_OK); } /* this finishes a transfer. it must be called with the mutex locked: */ static int _tme_posix_tape_xfer1(struct tme_posix_tape *posix_tape, int *_flags, unsigned long *_count_xfer, unsigned long *_count_bytes, int xfer_read) { struct tme_posix_tape_segment *segment; unsigned long count_xfer, count_bytes_user, count_bytes_tape; unsigned long xfer_factor, block_size; long rc; /* assume that we will return no flags: */ *_flags = 0; /* get the transfer count: */ count_xfer = posix_tape->tme_posix_tape_buffer_count_xfer; /* get any forced block size: */ block_size = posix_tape->tme_posix_tape_block_size_min; if (block_size != posix_tape->tme_posix_tape_block_size_min) { block_size = 0; } /* if the request is for one or more blocks at a fixed block size: */ if (posix_tape->tme_posix_tape_buffer_flags & TME_TAPE_FLAG_FIXED) { /* the device must be in fixed block size mode: */ xfer_factor = posix_tape->tme_posix_tape_block_size_fixed; if (xfer_factor == 0) { assert (block_size != 0); xfer_factor = block_size; } else { assert (block_size == 0 || block_size == xfer_factor); block_size = xfer_factor; } } /* otherwise, the request is for one block at a variable block size: */ else { /* the device might be forcing a block size: */ xfer_factor = 1; if (block_size == 0) { block_size = count_xfer; } } /* calculate the byte count for the user: */ count_bytes_user = count_xfer * xfer_factor; /* calculate the byte count for the tape. this is the byte count for the user rounded up to the block size: */ count_bytes_tape = count_bytes_user % block_size; count_bytes_tape = (count_bytes_user + (count_bytes_tape ? (block_size - count_bytes_tape) : 0)); /* get the current segment: */ segment = posix_tape->tme_posix_tape_segment_current; /* if this is a read: */ if (xfer_read) { /* if we are out of segments: */ if (segment == NULL) { /* act like we read zero bytes: */ rc = 0; } /* otherwise, do the read: */ else { /* do the read: */ rc = read(segment->tme_posix_tape_segment_fd, posix_tape->tme_posix_tape_buffer_data, count_bytes_user); /* if this segment is not a real tape, and we're expected to transfer more bytes than the user requested, seek over the extra bytes, leaving the medium "positioned after the block": */ if (!segment->tme_posix_tape_segment_real_tape && count_bytes_tape > count_bytes_user) { lseek(segment->tme_posix_tape_segment_fd, (count_bytes_tape - count_bytes_user), SEEK_CUR); } } } /* otherwise, this is a write: */ else { /* we must have a segment and it must be a real tape: */ assert (segment != NULL && segment->tme_posix_tape_segment_real_tape); /* do the write: */ rc = write(segment->tme_posix_tape_segment_fd, posix_tape->tme_posix_tape_buffer_data, count_bytes_user); } /* if the transfer got an error: */ if (rc < 0) { /* return the error: */ /* XXX is this sufficient? */ *_count_bytes = 0; *_count_xfer = 0; return (errno); } /* otherwise, we transferred some or none of the data: */ else { /* return the final byte count: */ *_count_bytes = rc; /* if the request was for one or more blocks at a fixed block size: */ if (posix_tape->tme_posix_tape_buffer_flags & TME_TAPE_FLAG_FIXED) { /* return the number of blocks successfully transferred: */ *_count_xfer = (rc / xfer_factor); } /* otherwise, the request is for one block at a variable block size: */ else { /* return the actual size of the block transferred: */ /* if the user asked for what we thought was a whole block, but we transferred less, how much we did transfer is the actual block size: */ if (count_bytes_user == block_size && (unsigned long) rc < block_size) { *_count_xfer = rc; } /* otherwise, if this segment is a real tape, we don't know how long the block really was. for now, we just return what we are using as the block size: */ /* XXX it may be possible to do an ioctl to get the true residual: */ else if (segment->tme_posix_tape_segment_real_tape) { *_count_xfer = block_size; } /* otherwise, this segment isn't a real tape, so we can return what we are using as the block size: */ else { *_count_xfer = block_size; } } /* if we didn't read as much from the tape as we were supposed to: */ if ((unsigned long) rc < count_bytes_tape) { /* if we read an exact multiple of the block size (including zero blocks), the read was short because of a file mark. return the mark (EOF) condition: */ if ((rc % block_size) == 0) { /* return the mark condition: */ *_flags |= TME_TAPE_FLAG_MARK; /* if this segment was not a real tape device, skip the file mark - i.e., move to the next segment: */ if (segment != NULL && !segment->tme_posix_tape_segment_real_tape) { rc = _tme_posix_tape_mark_skip(posix_tape, 1, TRUE); assert (rc == TME_OK); } } /* otherwise, the read was short because we read a partial block. return the incorrect length indication (ILI) condition: */ else { *_flags |= TME_TAPE_FLAG_ILI; } } } return (TME_OK); } /* this starts a transfer. it must be called with the mutex locked: */ static int _tme_posix_tape_xfer0(struct tme_posix_tape *posix_tape, int flags, unsigned long count_xfer, unsigned long *_count_bytes) { unsigned long xfer_factor, count_bytes; int old_flags; unsigned long old_count_xfer, old_count_bytes; int rc; /* if the buffer is dirty: */ if (posix_tape->tme_posix_tape_flags & TME_POSIX_TAPE_FLAG_DIRTY) { /* write out the buffer: */ rc = _tme_posix_tape_xfer1(posix_tape, &old_flags, &old_count_xfer, &old_count_bytes, FALSE); assert (rc == TME_OK); /* this buffer is no longer dirty: */ posix_tape->tme_posix_tape_flags &= ~TME_POSIX_TAPE_FLAG_DIRTY; } /* set the parameters of this transfer: */ posix_tape->tme_posix_tape_buffer_flags = flags; posix_tape->tme_posix_tape_buffer_count_xfer = count_xfer; /* if the request is for one or more blocks at a fixed block size: */ if (posix_tape->tme_posix_tape_buffer_flags & TME_TAPE_FLAG_FIXED) { /* it is an error if the device isn't in fixed block size mode: */ xfer_factor = posix_tape->tme_posix_tape_block_size_fixed; if (xfer_factor == 0) { xfer_factor = posix_tape->tme_posix_tape_block_size_min; if (xfer_factor != posix_tape->tme_posix_tape_block_size_max) { return (EINVAL); } } } /* otherwise, the request is for one block at a variable block size: */ else { xfer_factor = 1; } /* calculate the byte count: */ count_bytes = count_xfer * xfer_factor; /* if the buffer isn't big enough: */ if (posix_tape->tme_posix_tape_buffer_size < count_bytes) { /* resize the buffer: */ posix_tape->tme_posix_tape_buffer_size = count_bytes; posix_tape->tme_posix_tape_buffer_data = tme_renew(tme_uint8_t, posix_tape->tme_posix_tape_buffer_data, posix_tape->tme_posix_tape_buffer_size); } /* done: */ *_count_bytes = count_bytes; return (TME_OK); } /* this releases the current buffer: */ static int _tme_posix_tape_release(struct tme_tape_connection *conn_tape, int *_flags, unsigned long *_count_xfer) { struct tme_posix_tape *posix_tape; unsigned long count_bytes; int rc; /* recover our data structure: */ posix_tape = (struct tme_posix_tape *) conn_tape->tme_tape_connection.tme_connection_element->tme_element_private; /* assume that this call will succeed: */ rc = TME_OK; /* lock the mutex: */ tme_mutex_lock(&posix_tape->tme_posix_tape_mutex); /* if the buffer is dirty: */ rc = TME_OK; if (posix_tape->tme_posix_tape_flags & TME_POSIX_TAPE_FLAG_DIRTY) { /* write out the buffer: */ rc = _tme_posix_tape_xfer1(posix_tape, _flags, _count_xfer, &count_bytes, FALSE); /* this buffer is no longer dirty: */ posix_tape->tme_posix_tape_flags &= ~TME_POSIX_TAPE_FLAG_DIRTY; } /* unlock the mutex: */ tme_mutex_unlock(&posix_tape->tme_posix_tape_mutex); return (rc); } /* this returns a read buffer: */ static int _tme_posix_tape_read(struct tme_tape_connection *conn_tape, int *_flags, unsigned long *_count_xfer, unsigned long *_count_bytes, const tme_uint8_t **_buffer) { struct tme_posix_tape *posix_tape; int rc; /* recover our data structure: */ posix_tape = (struct tme_posix_tape *) conn_tape->tme_tape_connection.tme_connection_element->tme_element_private; /* lock the mutex: */ tme_mutex_lock(&posix_tape->tme_posix_tape_mutex); /* start the transfer: */ rc = _tme_posix_tape_xfer0(posix_tape, *_flags, *_count_xfer, _count_bytes); *_buffer = posix_tape->tme_posix_tape_buffer_data; /* if the start succeeded, finish the transfer: */ if (rc == TME_OK) { rc = _tme_posix_tape_xfer1(posix_tape, _flags, _count_xfer, _count_bytes, TRUE); } /* unlock the mutex: */ tme_mutex_unlock(&posix_tape->tme_posix_tape_mutex); return (rc); } /* this returns a write buffer: */ static int _tme_posix_tape_write(struct tme_tape_connection *conn_tape, int flags, unsigned long count_xfer, unsigned long *_count_bytes, tme_uint8_t **_buffer) { struct tme_posix_tape *posix_tape; int rc; /* recover our data structure: */ posix_tape = (struct tme_posix_tape *) conn_tape->tme_tape_connection.tme_connection_element->tme_element_private; /* lock the mutex: */ tme_mutex_lock(&posix_tape->tme_posix_tape_mutex); /* start the transfer: */ rc = _tme_posix_tape_xfer0(posix_tape, flags, count_xfer, _count_bytes); *_buffer = posix_tape->tme_posix_tape_buffer_data; /* unlock the mutex: */ tme_mutex_unlock(&posix_tape->tme_posix_tape_mutex); return (rc); } /* the tape control handler: */ #ifdef HAVE_STDARG_H static int _tme_posix_tape_control(struct tme_tape_connection *conn_tape, unsigned int control, ...) #else /* HAVE_STDARG_H */ static int _tme_posix_tape_control(conn_tape, control, va_alist) struct tme_tape_connection *conn_tape; unsigned int control; va_dcl #endif /* HAVE_STDARG_H */ { struct tme_posix_tape *posix_tape; unsigned long *sizes; const unsigned long *csizes; unsigned int count; va_list control_args; int *_flags; int rc; /* recover our data structure: */ posix_tape = (struct tme_posix_tape *) conn_tape->tme_tape_connection.tme_connection_element->tme_element_private; /* lock the mutex: */ tme_mutex_lock(&posix_tape->tme_posix_tape_mutex); /* start the variable arguments: */ #ifdef HAVE_STDARG_H va_start(control_args, control); #else /* HAVE_STDARG_H */ va_start(control_args); #endif /* HAVE_STDARG_H */ /* dispatch on the sequence type: */ switch (control) { case TME_TAPE_CONTROL_LOAD: _flags = va_arg(control_args, int *); *_flags = (posix_tape->tme_posix_tape_segments != NULL); rc = TME_OK; break; case TME_TAPE_CONTROL_UNLOAD: rc = _tme_posix_tape_unload(posix_tape); break; case TME_TAPE_CONTROL_MARK_WRITE: abort(); case TME_TAPE_CONTROL_DENSITY_GET: abort(); case TME_TAPE_CONTROL_DENSITY_SET: abort(); case TME_TAPE_CONTROL_BLOCK_SIZE_GET: sizes = va_arg(control_args, unsigned long *); sizes[0] = posix_tape->tme_posix_tape_block_size_min; sizes[1] = posix_tape->tme_posix_tape_block_size_max; sizes[2] = posix_tape->tme_posix_tape_block_size_fixed; rc = TME_OK; break; case TME_TAPE_CONTROL_BLOCK_SIZE_SET: csizes = va_arg(control_args, const unsigned long *); /* the minimum block size must be less than or equal to the maximum block size: */ if (csizes[0] > csizes[1]) { return (EINVAL); } /* if we aren't given a fixed block size: */ if (csizes[2] == 0) { /* if the minimum and maximum block sizes are the same, we are in fixed block size mode: */ if (csizes[0] == csizes[1]) { posix_tape->tme_posix_tape_block_size_fixed = csizes[0]; } /* otherwise, we are not in fixed block size mode: */ else { posix_tape->tme_posix_tape_block_size_fixed = 0; } } /* otherwise, we are given a fixed block size. it must be within the minimum and maximum block sizes: */ else { if (csizes[2] < csizes[0] || csizes[2] > csizes[1]) { return (EINVAL); } posix_tape->tme_posix_tape_block_size_fixed = csizes[2]; } /* set the minimum and maximum block sizes: */ posix_tape->tme_posix_tape_block_size_min = csizes[0]; posix_tape->tme_posix_tape_block_size_max = csizes[1]; rc = TME_OK; break; case TME_TAPE_CONTROL_MARK_SKIPF: case TME_TAPE_CONTROL_MARK_SKIPR: count = va_arg(control_args, unsigned int); rc = _tme_posix_tape_mark_skip(posix_tape, count, (control == TME_TAPE_CONTROL_MARK_SKIPF)); break; case TME_TAPE_CONTROL_REWIND: rc = _tme_posix_tape_rewind(posix_tape); break; default: abort(); } /* end the variable arguments: */ va_end(control_args); /* unlock the mutex: */ tme_mutex_unlock(&posix_tape->tme_posix_tape_mutex); return (rc); } /* our internal command function: */ static int __tme_posix_tape_command(struct tme_posix_tape *posix_tape, const char * const * args, char **_output) { struct tme_posix_tape_segment *segment, **_prev; struct tme_posix_tape_control *control; struct stat statbuf; int flags; int arg_i; int usage; int rc; int new_callouts; /* assume we won't need any new callouts: */ new_callouts = 0; /* check the command: */ usage = FALSE; arg_i = 1; /* the "load" command: */ if (TME_ARG_IS(args[arg_i], "load")) { arg_i++; /* if a tape is currently loaded, it must be unloaded first: */ if (posix_tape->tme_posix_tape_segments != NULL) { tme_output_append_error(_output, _("%s: tape already loaded; must unload first"), args[0]); return (EBUSY); } /* check for flags, which must all come before the tape segment filenames: */ flags = 0; for (;;) { /* the "read-only" flag: */ if (TME_ARG_IS(args[arg_i], "read-only")) { flags |= TME_POSIX_TAPE_FLAG_RO; arg_i++; } else { break; } } /* all of the remaining arguments must be tape segment filenames. assume that all segments open successfully: */ rc = TME_OK; /* open the tape segments: */ segment = NULL; posix_tape->tme_posix_tape_segments = NULL; for (_prev = &posix_tape->tme_posix_tape_segments; ; _prev = &segment->tme_posix_tape_segment_next) { /* stop if we're out of filenames: */ if (args[arg_i] == NULL) { break; } /* allocate a new tape segment and link it in: */ segment = tme_new0(struct tme_posix_tape_segment, 1); segment->tme_posix_tape_segment_filename = tme_strdup(args[arg_i]); segment->tme_posix_tape_segment_next = NULL; segment->tme_posix_tape_segment_prev = *_prev; *_prev = segment; /* open the segment file: */ segment->tme_posix_tape_segment_fd = open(segment->tme_posix_tape_segment_filename, O_RDONLY); if (segment->tme_posix_tape_segment_fd < 0) { rc = errno; break; } /* stat the segment file: */ if (fstat(segment->tme_posix_tape_segment_fd, &statbuf) < 0) { rc = errno; break; } /* if this is a block device: */ if (S_ISBLK(statbuf.st_mode)) { tme_output_append_error(_output, _("cannot use a block device: %s"), args[arg_i]); rc = EINVAL; break; } /* if this is a character device: */ else if (S_ISCHR(statbuf.st_mode)) { /* if this is not the only segment: */ if (posix_tape->tme_posix_tape_segments != segment || args[arg_i + 1] != NULL) { tme_output_append_error(_output, _("a real device must be the only tape segment: ")); rc = EINVAL; break; } /* this is a real tape: */ segment->tme_posix_tape_segment_real_tape = TRUE; } /* otherwise, this is a regular file: */ else { /* this tape must be read-only: */ flags |= TME_POSIX_TAPE_FLAG_RO; } /* if this is not the first segment, close the file: */ if (posix_tape->tme_posix_tape_segments != segment) { close(segment->tme_posix_tape_segment_fd); segment->tme_posix_tape_segment_fd = -1; } /* advance to the next segment filename: */ arg_i++; } /* if we got an error: */ if (rc != TME_OK) { /* append any segment filename to the error: */ if (segment != NULL) { tme_output_append_error(_output, "%s", segment->tme_posix_tape_segment_filename); } /* close and free the segments: */ _tme_posix_tape_segments_close(posix_tape); } /* otherwise, if we opened no segments: */ else if (posix_tape->tme_posix_tape_segments == NULL) { tme_output_append_error(_output, "%s %s load [read-only] { %s | %s [ .. %s ] }", _("usage:"), args[0], _("DEVICE"), _("FILENAME"), _("FILENAME")); rc = EINVAL; } /* otherwise, a tape has now been loaded: */ else { /* a tape has now been loaded: */ posix_tape->tme_posix_tape_flags = flags; /* the first segment is still open. make it the current segment: */ posix_tape->tme_posix_tape_segment_current = posix_tape->tme_posix_tape_segments; /* call out a LOAD control: */ control = _tme_posix_tape_control_new(posix_tape); control->tme_posix_tape_control_which = TME_TAPE_CONTROL_LOAD; new_callouts |= TME_POSIX_TAPE_CALLOUT_CONTROL; } } /* the "unload" command: */ else if (TME_ARG_IS(args[arg_i], "unload")) { /* if no tape is currently loaded: */ if (posix_tape->tme_posix_tape_segments == NULL) { tme_output_append_error(_output, _("%s: no tape loaded"), args[0]); return (ENXIO); } /* we must have no arguments: */ if (args[arg_i + 1] != NULL) { tme_output_append_error(_output, "%s %s unload", _("usage:"), args[0]); return (EINVAL); } /* unload the tape: */ _tme_posix_tape_unload(posix_tape); /* call out an UNLOAD control: */ control = _tme_posix_tape_control_new(posix_tape); control->tme_posix_tape_control_which = TME_TAPE_CONTROL_UNLOAD; new_callouts |= TME_POSIX_TAPE_CALLOUT_CONTROL; rc = TME_OK; } /* any other command: */ else { if (args[arg_i] != NULL) { tme_output_append_error(_output, "%s '%s', ", _("unknown command"), args[1]); } tme_output_append_error(_output, _("available %s commands: %s"), args[0], "load unload"); return (EINVAL); } /* make any new callouts: */ _tme_posix_tape_callout(posix_tape, new_callouts); return (rc); } /* our command function: */ static int _tme_posix_tape_command(struct tme_element *element, const char * const * args, char **_output) { struct tme_posix_tape *posix_tape; int rc; /* recover our data structure: */ posix_tape = (struct tme_posix_tape *) element->tme_element_private; /* lock the mutex: */ tme_mutex_lock(&posix_tape->tme_posix_tape_mutex); /* call the internal command function: */ rc = __tme_posix_tape_command(posix_tape, args, _output); /* unlock the mutex: */ tme_mutex_unlock(&posix_tape->tme_posix_tape_mutex); return (rc); } /* this breaks a posix tape connection: */ static int _tme_posix_tape_connection_break(struct tme_connection *conn, unsigned int state) { abort(); } /* this makes a posix tape connection: */ static int _tme_posix_tape_connection_make(struct tme_connection *conn, unsigned int state) { struct tme_posix_tape *posix_tape; struct tme_tape_connection *conn_tape; /* recover our data structure: */ posix_tape = (struct tme_posix_tape *) conn->tme_connection_element->tme_element_private; /* both sides must be tape connections: */ assert(conn->tme_connection_type == TME_CONNECTION_TAPE); assert(conn->tme_connection_other->tme_connection_type == TME_CONNECTION_TAPE); /* we're always set up to answer calls across the connection, so we only have to do work when the connection has gone full, namely taking the other side of the connection: */ if (state == TME_CONNECTION_FULL) { /* lock the mutex: */ tme_mutex_lock(&posix_tape->tme_posix_tape_mutex); /* save this connection to our list of connections: */ conn_tape = (struct tme_tape_connection *) conn->tme_connection_other; posix_tape->tme_posix_tape_connection = conn_tape; /* unlock the mutex: */ tme_mutex_unlock(&posix_tape->tme_posix_tape_mutex); } return (TME_OK); } /* this makes a new connection side for a posix tape: */ static int _tme_posix_tape_connections_new(struct tme_element *element, const char * const *args, struct tme_connection **_conns, char **_output) { struct tme_posix_tape *posix_tape; struct tme_tape_connection *conn_tape; struct tme_connection *conn; /* recover our data structure: */ posix_tape = (struct tme_posix_tape *) element->tme_element_private; /* if we already have a connection, there's nothing to do: */ if (posix_tape->tme_posix_tape_connection != NULL) { return (TME_OK); } /* create our side of a tape connection: */ conn_tape = tme_new0(struct tme_tape_connection, 1); conn = &conn_tape->tme_tape_connection; /* fill in the generic connection: */ conn->tme_connection_next = *_conns; conn->tme_connection_type = TME_CONNECTION_TAPE; conn->tme_connection_score = tme_tape_connection_score; conn->tme_connection_make = _tme_posix_tape_connection_make; conn->tme_connection_break = _tme_posix_tape_connection_break; /* fill in the tape connection: */ conn_tape->tme_tape_connection_read = _tme_posix_tape_read; conn_tape->tme_tape_connection_write = _tme_posix_tape_write; conn_tape->tme_tape_connection_release = _tme_posix_tape_release; conn_tape->tme_tape_connection_control = _tme_posix_tape_control; /* return the connection side possibility: */ *_conns = conn; return (TME_OK); } /* the new posix tape function: */ TME_ELEMENT_SUB_NEW_DECL(tme_host_posix,tape) { struct tme_posix_tape *posix_tape; int flags; int arg_i; int usage; /* check our arguments: */ flags = 0; arg_i = 1; usage = FALSE; /* loop reading our arguments: */ for (;;) { if (0) { } /* if we've run out of arguments: */ else if (args[arg_i + 0] == NULL) { break; } /* this is a bad argument: */ else { tme_output_append_error(_output, "%s %s", args[arg_i], _("unexpected")); usage = TRUE; break; } } if (usage) { tme_output_append_error(_output, "%s %s", _("usage:"), args[0]); return (EINVAL); } /* start the tape structure: */ posix_tape = tme_new0(struct tme_posix_tape, 1); posix_tape->tme_posix_tape_element = element; tme_mutex_init(&posix_tape->tme_posix_tape_mutex); posix_tape->tme_posix_tape_flags = flags; posix_tape->tme_posix_tape_segments = NULL; posix_tape->tme_posix_tape_segment_current = NULL; /* XXX these are fake values for now: */ posix_tape->tme_posix_tape_block_size_min = 512; posix_tape->tme_posix_tape_block_size_max = 32768; posix_tape->tme_posix_tape_block_size_fixed = 0; /* start the tape buffer. the 16384 isn't magic, it's just a starting point: */ posix_tape->tme_posix_tape_buffer_size = 16384; posix_tape->tme_posix_tape_buffer_data = tme_new(tme_uint8_t, posix_tape->tme_posix_tape_buffer_size); /* fill the element: */ element->tme_element_private = posix_tape; element->tme_element_connections_new = _tme_posix_tape_connections_new; element->tme_element_command = _tme_posix_tape_command; return (TME_OK); }