2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1991-1998 University of Maryland at College Park
6 * Permission to use, copy, modify, distribute, and sell this software and its
7 * documentation for any purpose is hereby granted without fee, provided that
8 * the above copyright notice appear in all copies and that both that
9 * copyright notice and this permission notice appear in supporting
10 * documentation, and that the name of U.M. not be used in advertising or
11 * publicity pertaining to distribution of the software without specific,
12 * written prior permission. U.M. makes no representations about the
13 * suitability of this software for any purpose. It is provided "as is"
14 * without express or implied warranty.
16 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * Author: James da Silva, Systems Design and Analysis Group
24 * Computer Science Department
25 * University of Maryland at College Park
29 * $Id: tapeio.c,v 1.57 2006/07/06 15:04:18 martinea Exp $
31 * implements generic tape I/O functions
37 #include "fileheader.h"
44 #include "output-tape.h"
45 #include "output-null.h"
46 #include "output-rait.h"
47 #include "output-file.h"
49 static struct virtualtape {
51 int (*xxx_tape_access)(char *, int);
52 int (*xxx_tape_open)(char *, int, mode_t);
53 int (*xxx_tape_stat)(char *, struct stat *);
54 int (*xxx_tapefd_close)(int);
55 int (*xxx_tapefd_fsf)(int, off_t);
56 ssize_t (*xxx_tapefd_read)(int, void *, size_t);
57 int (*xxx_tapefd_rewind)(int);
58 void (*xxx_tapefd_resetofs)(int);
59 int (*xxx_tapefd_unload)(int);
60 int (*xxx_tapefd_status)(int, struct am_mt_status *);
61 int (*xxx_tapefd_weof)(int, off_t);
62 ssize_t (*xxx_tapefd_write)(int, const void *, size_t);
63 int (*xxx_tapefd_can_fork)(int);
65 /* note: "tape" has to be the first entry because it is the
66 ** default if no prefix match is found.
68 {"tape", tape_tape_access, tape_tape_open, tape_tape_stat,
69 tape_tapefd_close, tape_tapefd_fsf,
70 tape_tapefd_read, tape_tapefd_rewind, tape_tapefd_resetofs,
71 tape_tapefd_unload, tape_tapefd_status, tape_tapefd_weof,
72 tape_tapefd_write, tape_tapefd_can_fork },
73 {"null", null_tape_access, null_tape_open, null_tape_stat,
74 null_tapefd_close, null_tapefd_fsf,
75 null_tapefd_read, null_tapefd_rewind, null_tapefd_resetofs,
76 null_tapefd_unload, null_tapefd_status, null_tapefd_weof,
77 null_tapefd_write, null_tapefd_can_fork },
78 {"rait", rait_access, rait_tape_open, rait_stat,
79 rait_close, rait_tapefd_fsf,
80 rait_read, rait_tapefd_rewind, rait_tapefd_resetofs,
81 rait_tapefd_unload, rait_tapefd_status, rait_tapefd_weof,
82 rait_write, rait_tapefd_can_fork },
83 {"file", file_tape_access, file_tape_open, file_tape_stat,
84 file_tapefd_close, file_tapefd_fsf,
85 file_tapefd_read, file_tapefd_rewind, file_tapefd_resetofs,
86 file_tapefd_unload, file_tapefd_status, file_tapefd_weof,
87 file_tapefd_write, file_tapefd_can_fork },
88 {NULL, NULL, NULL, NULL,
95 static struct tape_info {
107 static struct tape_info **tape_info_p = &tape_info;
109 static size_t tape_info_count = 0;
111 static char *errstr = NULL;
113 static void tape_info_init(void *ptr);
114 static int name2slot(char *name, char **ntrans);
117 * Additional initialization function for tape_info table.
124 struct tape_info *t = ptr;
133 * Convert the "name" part of a device to a vtape slot.
145 if(0 != (pc = strchr(name, ':'))) {
146 len = (size_t)(pc - name);
147 for( i = 0 ; vtable[i].prefix && vtable[i].prefix[0]; i++ ) {
148 if(0 == strncmp(vtable[i].prefix, name , len)
149 && '\0' == vtable[i].prefix[len]) {
160 * Routines for parsing a device name.
164 * Initialize parsing. The text in the "dev" parameter will be altered,
165 * so a copy should be passed to us.
179 *dev_left = *dev_right = *dev_next = NULL; /* defensive coding */
182 * See if there is a '{' and find the matching '}'.
184 if((*dev_next = p = strchr(dev, '{')) != NULL) {
189 while((ch != '\0') && (ch != '{') && (ch != '}'))
193 * Did not find a matching '}'.
198 } else if(ch == '{') {
200 } else if(ch == '}') {
204 if(strchr(p, '{') != NULL || strchr(p, '}') != NULL) {
207 return -1; /* only one list allowed */
209 *dev_left = dev; /* text before the '{' */
210 **dev_next = '\0'; /* zap the '{' */
211 (*dev_next)++; /* point to the first name */
212 p[-1] = '\0'; /* zap the '}' */
213 *dev_right = p; /* text after the '}' */
216 * Arrange to return just one name.
219 *dev_left = *dev_right = "";
225 * Return the next device name. A dynamic area is returned that the
226 * caller is responsible for freeing.
240 p = next = *dev_next; /* remember the start point */
244 while((ch != '\0') && (ch != '{') && (ch != '}') && (ch != ','))
248 * Found the end of a name.
252 return NULL; /* end of the list */
254 p--; /* point to the null byte */
256 } else if(ch == '{') {
258 } else if(ch == '}') {
262 } while(depth != 0 || ch != ',');
264 p[-1] = '\0'; /* zap the ',' */
266 *dev_next = p; /* set up for the next call */
267 return vstralloc(dev_left, next, dev_right, NULL);
271 * The following functions get/set fields in the tape_info structure.
272 * To allow them to be called (e.g. set) from lower level open functions
273 * started by tape_open, we check and allocate the tape_info structure
274 * here as well as in tape_open.
281 amtable_alloc((void **)tape_info_p,
287 if(tape_info[fd].master_fd != -1)
288 return tapefd_getinfo_host(tape_info[fd].master_fd);
289 return tape_info[fd].host;
297 amtable_alloc((void **)tape_info_p,
303 amfree(tape_info[fd].host);
305 tape_info[fd].host = stralloc(v);
313 amtable_alloc((void **)tape_info_p,
319 if(tape_info[fd].master_fd != -1)
320 return tapefd_getinfo_disk(tape_info[fd].master_fd);
321 return tape_info[fd].disk;
329 amtable_alloc((void **)tape_info_p,
335 amfree(tape_info[fd].disk);
337 tape_info[fd].disk = stralloc(v);
342 tapefd_getinfo_level(
345 amtable_alloc((void **)tape_info_p,
351 if(tape_info[fd].master_fd != -1)
352 return tapefd_getinfo_level(tape_info[fd].master_fd);
353 return tape_info[fd].level;
357 tapefd_setinfo_level(
361 amtable_alloc((void **)tape_info_p,
367 tape_info[fd].level = v;
371 tapefd_getinfo_datestamp(
374 amtable_alloc((void **)tape_info_p,
380 return tape_info[fd].datestamp;
384 tapefd_setinfo_datestamp(
388 amtable_alloc((void **)tape_info_p,
394 tape_info[fd].datestamp = newstralloc(tape_info[fd].datestamp, v);
398 tapefd_getinfo_length(
401 amtable_alloc((void **)tape_info_p,
407 return tape_info[fd].length;
411 tapefd_setinfo_length(
415 amtable_alloc((void **)tape_info_p,
421 tape_info[fd].length = v;
425 tapefd_getinfo_tapetype(
428 amtable_alloc((void **)tape_info_p,
434 return tape_info[fd].tapetype;
438 tapefd_setinfo_tapetype(
442 amtable_alloc((void **)tape_info_p,
448 tape_info[fd].tapetype = newstralloc(tape_info[fd].tapetype, v);
452 tapefd_getinfo_fake_label(
455 amtable_alloc((void **)tape_info_p,
461 return tape_info[fd].fake_label;
465 tapefd_setinfo_fake_label(
469 amtable_alloc((void **)tape_info_p,
475 tape_info[fd].fake_label = v;
479 tapefd_getinfo_ioctl_fork(
482 amtable_alloc((void **)tape_info_p,
488 return tape_info[fd].ioctl_fork;
492 tapefd_setinfo_ioctl_fork(
496 amtable_alloc((void **)tape_info_p,
502 tape_info[fd].ioctl_fork = v;
506 tapefd_set_master_fd(
510 amtable_alloc((void **)tape_info_p,
516 tape_info[fd].master_fd = master_fd;
521 * The normal tape operation functions.
532 vslot = name2slot(filename, &tname);
533 return vtable[vslot].xxx_tape_access(tname, mode);
544 vslot = name2slot(filename, &tname);
545 return vtable[vslot].xxx_tape_stat(tname, buf);
560 mask = (mode_t)va_arg(ap, int);
563 vslot = name2slot(filename, &tname);
564 if((fd = vtable[vslot].xxx_tape_open(tname, mode, mask)) >= 0) {
565 amtable_alloc((void **)tape_info_p,
572 * It is possible to recurse in the above open call and come
573 * back here twice for the same file descriptor. Set the vtape
574 * index only if it is not already set, i.e. the first call wins.
576 if(tape_info[fd].vtape_index < 0) {
577 tape_info[fd].vtape_index = vslot;
590 if ((fd < 0) || ((size_t)fd >= tape_info_count)
591 || ((vslot = tape_info[fd].vtape_index) < 0)) {
596 vslot = tape_info[fd].vtape_index;
597 if((res = vtable[vslot].xxx_tapefd_close(fd)) == 0) {
598 amfree(tape_info[fd].host);
599 amfree(tape_info[fd].disk);
600 amfree(tape_info[fd].datestamp);
601 amfree(tape_info[fd].tapetype);
602 memset(tape_info + fd, 0, SIZEOF(*tape_info));
603 tape_info_init((void *)(tape_info + fd));
614 if ((fd < 0) || ((size_t)fd >= tape_info_count)
615 || (tape_info[fd].vtape_index < 0)) {
620 vslot = tape_info[fd].vtape_index;
621 return vtable[vslot].xxx_tapefd_can_fork(fd);
631 if ((fd < 0) || ((size_t)fd >= tape_info_count)
632 || (tape_info[fd].vtape_index < 0)) {
637 vslot = tape_info[fd].vtape_index;
638 return vtable[vslot].xxx_tapefd_fsf(fd, count);
647 if ((fd < 0) || ((size_t)fd >= tape_info_count)
648 || (tape_info[fd].vtape_index < 0)) {
653 vslot = tape_info[fd].vtape_index;
654 return vtable[vslot].xxx_tapefd_rewind(fd);
663 if ((fd < 0) || ((size_t)fd >= tape_info_count)
664 || (tape_info[fd].vtape_index < 0)) {
665 errno = EBADF; /* not that it matters */
669 vslot = tape_info[fd].vtape_index;
670 vtable[vslot].xxx_tapefd_resetofs(fd);
679 if ((fd < 0) || ((size_t)fd >= tape_info_count)
680 || (tape_info[fd].vtape_index < 0)) {
685 vslot = tape_info[fd].vtape_index;
686 return vtable[vslot].xxx_tapefd_unload(fd);
692 struct am_mt_status *stat)
696 if ((fd < 0) || ((size_t)fd >= tape_info_count)
697 || (tape_info[fd].vtape_index < 0)) {
702 vslot = tape_info[fd].vtape_index;
703 return vtable[vslot].xxx_tapefd_status(fd, stat);
713 if ((fd < 0) || ((size_t)fd >= tape_info_count)
714 || (tape_info[fd].vtape_index < 0)) {
719 vslot = tape_info[fd].vtape_index;
720 return vtable[vslot].xxx_tapefd_weof(fd, count);
732 if ((fd < 0) || ((size_t)fd >= tape_info_count)
733 || (tape_info[fd].vtape_index < 0)) {
738 vslot = tape_info[fd].vtape_index;
739 return vtable[vslot].xxx_tapefd_read(fd, buffer, count);
750 if ((fd < 0) || ((size_t)fd >= tape_info_count)
751 || (tape_info[fd].vtape_index < 0)) {
756 vslot = tape_info[fd].vtape_index;
757 return vtable[vslot].xxx_tapefd_write(fd, buffer, count);
767 if((fd = tape_open(devname, O_RDONLY)) < 0) {
768 r = errstr = newvstralloc(errstr,
769 "tape_rewind: tape open: ",
774 } else if(tapefd_rewind(fd) == -1) {
775 r = errstr = newvstralloc(errstr,
776 "tape_rewind: rewinding tape: ",
795 if((fd = tape_open(devname, O_RDONLY)) < 0) {
796 r = errstr = newvstralloc(errstr,
797 "tape_unload: tape open: ",
802 } else if(tapefd_unload(fd) == -1) {
803 r = errstr = newvstralloc(errstr,
804 "tape_unload: unloading tape: ",
822 char count_str[NUM_STR_SIZE];
825 if((fd = tape_open(devname, O_RDONLY)) < 0) {
826 r = errstr = newvstralloc(errstr,
827 "tape_fsf: tape open: ",
832 } else if(tapefd_fsf(fd, count) == -1) {
833 snprintf(count_str, SIZEOF(count_str), OFF_T_FMT,
834 (OFF_T_FMT_TYPE)count);
835 r = errstr = newvstralloc(errstr,
838 "file", (count == 1) ? "" : "s",
849 /* Reads the tape label, like you expect. If failure, returns an error
850 string. If the tape might not be an Amanda tape, the returned
851 string will start with NOT_AMANDA_TAPE_MSG. */
867 buflen = getconf_readblocksize() * 1024;
868 buffer = alloc(buflen + 1);
870 if(tapefd_getinfo_fake_label(fd)) {
871 *datestamp = stralloc("X");
872 *label = stralloc(FAKE_LABEL);
873 } else if(tapefd_rewind(fd) == -1) {
874 r = stralloc2("rewinding tape: ", strerror(errno));
875 } else if((rc = tapefd_read(fd, buffer, buflen)) == -1) {
876 r = vstralloc(NOT_AMANDA_TAPE_MSG, " (",
877 strerror(errno), ")", NULL);
878 } else if (rc == 0) {
879 r = stralloc2(NOT_AMANDA_TAPE_MSG, " (Read 0 bytes)");
881 /* make sure buffer is null-terminated */
884 parse_file_header(buffer, &file, (size_t)rc);
885 if(file.type != F_TAPESTART) {
886 r = stralloc(NOT_AMANDA_TAPE_MSG);
888 *datestamp = stralloc(file.datestamp);
889 *label = stralloc(file.name);
894 errstr = newvstralloc(errstr, r, NULL);
907 if((fd = tape_open(devname, O_RDONLY)) < 0) {
908 r = vstralloc("tape_rdlabel: tape open: ",
914 r = tapefd_rdlabel(fd, datestamp, label);
920 errstr = newvstralloc(errstr, r, NULL);
936 if(tapefd_rewind(fd) == -1) {
937 r = errstr = newstralloc2(errstr, "rewinding tape: ", strerror(errno));
940 file.type = F_TAPESTART;
941 strncpy(file.datestamp, datestamp, SIZEOF(file.datestamp) - 1);
942 file.datestamp[SIZEOF(file.datestamp) - 1] = '\0';
943 strncpy(file.name, label, SIZEOF(file.name) - 1);
944 file.name[SIZEOF(file.name) - 1] = '\0';
945 buffer = alloc(size);
946 file.blocksize = size;
947 build_header(buffer, &file, size);
948 tapefd_setinfo_host(fd, NULL);
949 tapefd_setinfo_disk(fd, label);
950 tapefd_setinfo_level(fd, -1);
951 if((rc = tapefd_write(fd, buffer, size)) != (ssize_t)size) {
952 r = errstr = newstralloc2(errstr,
954 (rc != -1) ? "short write"
972 if((fd = tape_open(devname, O_WRONLY)) < 0) {
973 r = errstr = newstralloc2(errstr,
975 (errno == EACCES) ? "tape is write-protected"
977 } else if(tapefd_wrlabel(fd, datestamp, label, size) != NULL) {
998 file.type = F_TAPEEND;
999 strncpy(file.datestamp, datestamp, SIZEOF(file.datestamp) - 1);
1000 file.datestamp[SIZEOF(file.datestamp) - 1] = '\0';
1001 buffer = alloc(size);
1002 file.blocksize = size;
1003 build_header(buffer, &file, size);
1004 tapefd_setinfo_host(fd, NULL);
1005 tapefd_setinfo_disk(fd, "TAPEEND");
1006 tapefd_setinfo_level(fd, -1);
1008 if((rc = tapefd_write(fd, buffer, size)) != (ssize_t)size) {
1009 r = errstr = newstralloc2(errstr, "writing endmark: ",
1010 (rc != -1) ? "short write" : strerror(errno));
1026 if((fd = tape_open(devname, O_WRONLY)) < 0) {
1027 r = errstr = newstralloc2(errstr,
1028 "writing endmark: ",
1029 (errno == EACCES) ? "tape is write-protected"
1031 } else if(tapefd_wrendmark(fd, datestamp, size) != NULL) {
1047 /* first, make sure the file exists and the permissions are right */
1049 if(tape_access(devname, R_OK|W_OK) == -1) {
1050 r = errstr = newstralloc(errstr, strerror(errno));
1051 } else if((fd = tape_open(devname, O_WRONLY)) < 0) {
1052 r = errstr = newstralloc(errstr,
1053 (errno == EACCES) ? "tape write-protected"
1065 * The following test program may be used to exercise I/O patterns through
1066 * the tapeio interface. Commands may either be on the command line or
1067 * read from stdin (e.g. for a test suite).
1073 /* If the C library does not define random(), try to use rand() by
1074 defining USE_RAND, but then make sure you are not using hardware
1075 compression, because the low-order bits of rand() may not be that
1077 #define random() rand()
1078 #define srandom(seed) srand(seed)
1086 fprintf(stderr, " ?|help\n");
1087 fprintf(stderr, " open [\"file\"|$TAPE [\"mode\":O_RDONLY]]\n");
1088 fprintf(stderr, " read [\"records\":\"all\"]\n");
1089 fprintf(stderr, " write [\"records\":1] [\"file#\":\"+\"] [\"record#\":\"+\"] [\"host\"] [\"disk\"] [\"level\"]\n");
1090 fprintf(stderr, " eof|weof [\"count\":1]\n");
1091 fprintf(stderr, " fsf [\"count\":1]\n");
1092 fprintf(stderr, " rewind\n");
1093 fprintf(stderr, " unload\n");
1099 fprintf(stderr, "usage: %s [-c cmd [args] [%% cmd [args] ...]]\n", pgm);
1103 #define TEST_BLOCKSIZE (32 * 1024)
1105 #define MAX_TOKENS 10
1109 static char *token_area[MAX_TOKENS + 1];
1110 static char **token;
1111 static int token_count;
1114 static off_t current_file = (off_t)0;
1115 static off_t current_record = (off_t)0;
1117 static int have_length = 0;
1118 static int length = (off_t)0;
1120 static int show_timestamp = 0;
1122 char write_buf[TEST_BLOCKSIZE];
1131 || (token_count >= 2 && strcmp(token[1], "$TAPE") == 0)) {
1132 if((file = getenv("TAPE")) == NULL) {
1133 fprintf(stderr, "tape_open: no file name and $TAPE not set\n");
1139 if(token_count > 2) {
1140 mode = atoi(token[2]);
1145 fprintf(stderr, "tapefd_open(\"%s\", %d): ", file, mode);
1146 if((fd = tape_open(file, mode, 0644)) < 0) {
1149 fprintf(stderr, "%d (OK)\n", fd);
1151 tapefd_setinfo_length(fd, length);
1161 fprintf(stderr, "tapefd_close(): ");
1162 if((result = tapefd_close(fd)) < 0) {
1165 fprintf(stderr, "%d (OK)\n", result);
1173 off_t count = (off_t)0;
1175 char buf[SIZEOF(write_buf)];
1182 if(token_count > 1 && strcmp(token[1], "all") != 0) {
1183 count = OFF_T_ATOI(token[1]);
1188 for(i = 0; (! have_count) || (i < count); i++) {
1189 fprintf(stderr, "tapefd_read(" OFF_T_FMT "): ", (OFF_T_FMT_TYPE)i);
1190 if((result = tapefd_read(fd, buf, SIZEOF(buf))) < 0) {
1193 } else if(result == 0) {
1194 fprintf(stderr, SSIZE_T_FMT" (EOF)\n", result);
1196 * If we were not given a count, EOF breaks the loop, otherwise
1197 * we keep trying (to test read after EOF handling).
1203 if(result == (ssize_t)sizeof(buf)) {
1210 * If the amount read is really short, we may refer to junk
1211 * when displaying the record data, but things are pretty
1212 * well screwed up at this point anyway so it is not worth
1213 * the effort to deal with.
1216 SSIZE_T_FMT " (%s): file %d: record %d",
1221 if(show_timestamp) {
1223 tm = localtime(&then);
1225 ": %04d/%02d/%02d %02d:%02d:%02d\n",
1233 fputc('\n', stderr);
1249 if(token_count > 1) {
1250 count = OFF_T_ATOI(token[1]);
1255 if(token_count > 2 && strcmp(token[2], "+") != 0) {
1256 current_file = OFF_T_ATOI(token[2]);
1259 if(token_count > 3 && strcmp(token[3], "+") != 0) {
1260 current_record = OFF_T_ATOI(token[3]);
1263 if(token_count > 4 && token[4][0] != '\0') {
1264 tapefd_setinfo_host(fd, token[4]);
1267 if(token_count > 5 && token[5][0] != '\0') {
1268 tapefd_setinfo_disk(fd, token[5]);
1271 if(token_count > 6 && token[6][0] != '\0') {
1272 tapefd_setinfo_level(fd, atoi(token[6]));
1275 p = (off_t *)write_buf;
1278 tm = localtime(&now);
1279 for(i = 0; i < count; i++, (current_record += (off_t)1)) {
1280 p[0] = current_file;
1281 p[1] = current_record;
1282 fprintf(stderr, "tapefd_write(" OFF_T_FMT "): ", i);
1283 if((result = tapefd_write(fd, write_buf, SIZEOF(write_buf))) < 0) {
1287 if(result == (ssize_t)sizeof(write_buf)) {
1293 "%d (%s): file " OFF_T_FMT ": record " OFF_T_FMT,
1298 if(show_timestamp) {
1300 ": %04d/%02d/%02d %02d:%02d:%02d\n",
1308 fputc('\n', stderr);
1319 if(token_count > 1) {
1320 count = OFF_T_ATOI(token[1]);
1325 fprintf(stderr, "tapefd_fsf(" OFF_T_FMT "): ", (OFF_T_FMT_TYPE)count);
1326 if((result = tapefd_fsf(fd, count)) < 0) {
1329 fprintf(stderr, "%d (OK)\n", result);
1330 current_file += count;
1331 current_record = (off_t)0;
1341 if(token_count > 1) {
1342 count = OFF_T_ATOI(token[1]);
1347 fprintf(stderr, "tapefd_weof(" OFF_T_FMT "): ", count);
1348 if((result = tapefd_weof(fd, count)) < 0) {
1351 fprintf(stderr, "%d (OK)\n", result);
1352 current_file += count;
1353 current_record = (off_t)0;
1362 fprintf(stderr, "tapefd_rewind(): ");
1363 if((result = tapefd_rewind(fd)) < 0) {
1366 fprintf(stderr, "%d (OK)\n", result);
1367 current_file = (off_t)0;
1368 current_record = (off_t)0;
1377 fprintf(stderr, "tapefd_unload(): ");
1378 if((result = tapefd_unload(fd)) < 0) {
1381 fprintf(stderr, "%d (OK)\n", result);
1382 current_file = (off_t)-1;
1383 current_record = (off_t)-1;
1392 { "?", 0, do_help },
1393 { "help", 0, do_help },
1394 { "eof", 0, do_weof },
1395 { "weof", 0, do_weof },
1396 { "fsf", 0, do_fsf },
1397 { "rewind", 0, do_rewind },
1398 { "offline", 0, do_unload },
1399 { "open", 0, do_open },
1400 { "close", 0, do_close },
1401 { "read", 0, do_read },
1402 { "write", 0, do_write },
1419 /* Don't die when child closes pipe */
1420 signal(SIGPIPE, SIG_IGN);
1422 if((pgm = strrchr(argv[0], '/')) != NULL) {
1429 * Compute the minimum abbreviation for each command.
1431 for(i = 0; cmd[i].name; i++) {
1432 cmd[i].min_chars = 1;
1434 for(j = 0; cmd[j].name; j++) {
1438 if(0 == strncmp(cmd[i].name, cmd[j].name, cmd[i].min_chars)) {
1442 if(0 == cmd[j].name) {
1450 * Process the command line flags.
1452 while((ch = getopt(argc, argv, "hcl:t")) != EOF) {
1459 length = OFF_T_ATOI(optarg);
1462 switch(optarg[j-1] ) {
1464 case 'b': length /= (off_t)2; break;
1465 case 'M': length *= (off_t)1024; break;
1466 default: length /= (off_t)1024; break;
1469 length /= (off_t)1024;
1483 * Initialize the write buffer.
1487 for(j = 0; j < (int)SIZEOF(write_buf); j++) {
1488 write_buf[j] = (char)random();
1494 token = token_area + 1;
1495 token_area[0] = ""; /* if cmdline */
1498 for(token_count = 1;
1499 token_count < (int)(SIZEOF(token_area) / SIZEOF(token_area[0]))
1501 token_count++, optind++) {
1502 if(strcmp(argv[optind], "%") == 0) {
1506 token_area[token_count] = argv[optind];
1509 if(token_count == 0 && optind >= argc) {
1513 if((line = areads(0)) == NULL) {
1516 if((s = strchr(line, '#')) != NULL) {
1519 s = line + strlen(line) - 1;
1520 while(s >= line && isspace(*s)) {
1523 token_count = split(line,
1525 SIZEOF(token_area) / SIZEOF(token_area[0]),
1531 * Truncate tokens at first comment indicator, then test for
1534 for(i = 0; i < token_count; i++) {
1535 if(token[i][0] == '#') {
1540 if(token_count <= 0) {
1541 continue; /* blank/comment input line */
1545 * Find the command to run, the do it.
1547 j = strlen(token[0]);
1548 for(i = 0; cmd[i].name; i++) {
1549 if(strncmp(cmd[i].name, token[0], j) == 0
1550 && j >= cmd[i].min_chars) {
1554 if(cmd[i].name == NULL) {
1555 fprintf(stderr, "%s: unknown command: %s\n", pgm, token[0]);