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.20.4.7.4.4.2.9 2003/11/28 12:34:52 martinea Exp $
31 * implements generic tape I/O functions
38 #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) P((char *, int));
52 int (*xxx_tape_open) ();
53 int (*xxx_tape_stat) P((char *, struct stat *));
54 int (*xxx_tapefd_close) P((int));
55 int (*xxx_tapefd_fsf) P((int, int));
56 ssize_t (*xxx_tapefd_read) P((int, void *, size_t));
57 int (*xxx_tapefd_rewind) P((int));
58 void (*xxx_tapefd_resetofs) P((int));
59 int (*xxx_tapefd_unload) P((int));
60 int (*xxx_tapefd_status) P((int, struct am_mt_status *));
61 int (*xxx_tapefd_weof) P((int, int));
62 ssize_t (*xxx_tapefd_write) P((int, const void *, size_t));
63 int (*xxx_tapefd_can_fork) P((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 },
91 static struct tape_info {
103 static int tape_info_count = 0;
105 static char *errstr = NULL;
108 * Additional initialization function for tape_info table.
115 struct tape_info *t = ptr;
124 * Convert the "name" part of a device to a vtape slot.
128 name2slot(name, ntrans)
135 if(0 != (pc = strchr(name, ':'))) {
137 for( i = 0 ; vtable[i].prefix && vtable[i].prefix[0]; i++ ) {
138 if(0 == strncmp(vtable[i].prefix, name , len)
139 && '\0' == vtable[i].prefix[len]) {
150 * Routines for parsing a device name.
154 * Initialize parsing. The text in the "dev" parameter will be altered,
155 * so a copy should be passed to us.
159 tapeio_init_devname(char * dev,
167 *dev_left = *dev_right = *dev_next = NULL; /* defensive coding */
170 * See if there is a '{' and find the matching '}'.
172 if((*dev_next = p = strchr(dev, '{')) != NULL) {
176 while((ch = *p++) != '\0' && ch != '{' && ch != '}') {}
179 * Did not find a matching '}'.
184 } else if(ch == '{') {
186 } else if(ch == '}') {
190 if(strchr(p, '{') != NULL || strchr(p, '}') != NULL) {
193 return -1; /* only one list allowed */
195 *dev_left = dev; /* text before the '{' */
196 **dev_next = '\0'; /* zap the '{' */
197 (*dev_next)++; /* point to the first name */
198 p[-1] = '\0'; /* zap the '}' */
199 *dev_right = p; /* text after the '}' */
202 * Arrange to return just one name.
205 *dev_left = *dev_right = "";
211 * Return the next device name. A dynamic area is returned that the
212 * caller is responsible for freeing.
216 tapeio_next_devname(char * dev_left,
224 p = next = *dev_next; /* remember the start point */
227 while((ch = *p++) != '\0' && ch != '{' && ch != '}' && ch != ',') {}
230 * Found the end of a name.
234 return NULL; /* end of the list */
236 p--; /* point to the null byte */
238 } else if(ch == '{') {
240 } else if(ch == '}') {
244 } while(depth != 0 || ch != ',');
246 p[-1] = '\0'; /* zap the ',' */
248 *dev_next = p; /* set up for the next call */
249 return vstralloc(dev_left, next, dev_right, NULL);
253 * The following functions get/set fields in the tape_info structure.
254 * To allow them to be called (e.g. set) from lower level open functions
255 * started by tape_open, we check and allocate the tape_info structure
256 * here as well as in tape_open.
260 tapefd_getinfo_host(fd)
263 amtable_alloc((void **)&tape_info,
269 if(tape_info[fd].master_fd != -1)
270 return tapefd_getinfo_host(tape_info[fd].master_fd);
271 return tape_info[fd].host;
275 tapefd_setinfo_host(fd, v)
279 amtable_alloc((void **)&tape_info,
285 amfree(tape_info[fd].host);
287 tape_info[fd].host = stralloc(v);
292 tapefd_getinfo_disk(fd)
295 amtable_alloc((void **)&tape_info,
301 if(tape_info[fd].master_fd != -1)
302 return tapefd_getinfo_disk(tape_info[fd].master_fd);
303 return tape_info[fd].disk;
307 tapefd_setinfo_disk(fd, v)
311 amtable_alloc((void **)&tape_info,
317 amfree(tape_info[fd].disk);
319 tape_info[fd].disk = stralloc(v);
324 tapefd_getinfo_level(fd)
327 amtable_alloc((void **)&tape_info,
333 if(tape_info[fd].master_fd != -1)
334 return tapefd_getinfo_level(tape_info[fd].master_fd);
335 return tape_info[fd].level;
339 tapefd_setinfo_level(fd, v)
343 amtable_alloc((void **)&tape_info,
349 tape_info[fd].level = v;
353 tapefd_getinfo_datestamp(fd)
356 amtable_alloc((void **)&tape_info,
362 return tape_info[fd].datestamp;
366 tapefd_setinfo_datestamp(fd, v)
370 amtable_alloc((void **)&tape_info,
376 tape_info[fd].datestamp = newstralloc(tape_info[fd].datestamp, v);
380 tapefd_getinfo_length(fd)
383 amtable_alloc((void **)&tape_info,
389 return tape_info[fd].length;
393 tapefd_setinfo_length(fd, v)
397 amtable_alloc((void **)&tape_info,
403 tape_info[fd].length = v;
407 tapefd_getinfo_tapetype(fd)
410 amtable_alloc((void **)&tape_info,
416 return tape_info[fd].tapetype;
420 tapefd_setinfo_tapetype(fd, v)
424 amtable_alloc((void **)&tape_info,
430 tape_info[fd].tapetype = newstralloc(tape_info[fd].tapetype, v);
434 tapefd_getinfo_fake_label(fd)
437 amtable_alloc((void **)&tape_info,
443 return tape_info[fd].fake_label;
447 tapefd_setinfo_fake_label(fd, v)
451 amtable_alloc((void **)&tape_info,
457 tape_info[fd].fake_label = v;
461 tapefd_getinfo_ioctl_fork(fd)
464 amtable_alloc((void **)&tape_info,
470 return tape_info[fd].ioctl_fork;
474 tapefd_setinfo_ioctl_fork(fd, v)
478 amtable_alloc((void **)&tape_info,
484 tape_info[fd].ioctl_fork = v;
488 tapefd_set_master_fd(fd, master_fd)
492 amtable_alloc((void **)&tape_info,
498 tape_info[fd].master_fd = master_fd;
503 * The normal tape operation functions.
507 tape_access(filename, mode)
514 vslot = name2slot(filename, &tname);
515 return vtable[vslot].xxx_tape_access(tname, mode);
519 tape_stat(filename, buf)
526 vslot = name2slot(filename, &tname);
527 return vtable[vslot].xxx_tape_stat(tname, buf);
531 tape_open(filename, mode, mask)
540 vslot = name2slot(filename, &tname);
541 if((fd = vtable[vslot].xxx_tape_open(tname, mode, mask)) >= 0) {
542 amtable_alloc((void **)&tape_info,
549 * It is possible to recurse in the above open call and come
550 * back here twice for the same file descriptor. Set the vtape
551 * index only if it is not already set, i.e. the first call wins.
553 if(tape_info[fd].vtape_index < 0) {
554 tape_info[fd].vtape_index = vslot;
567 || fd >= tape_info_count
568 || (vslot = tape_info[fd].vtape_index) < 0) {
572 if((res = vtable[vslot].xxx_tapefd_close(fd)) == 0) {
573 amfree(tape_info[fd].host);
574 amfree(tape_info[fd].disk);
575 amfree(tape_info[fd].datestamp);
576 amfree(tape_info[fd].tapetype);
577 memset(tape_info + fd, 0, sizeof(*tape_info));
578 tape_info_init((void *)(tape_info + fd));
590 || fd >= tape_info_count
591 || (vslot = tape_info[fd].vtape_index) < 0) {
595 res = vtable[vslot].xxx_tapefd_can_fork(fd);
601 tapefd_fsf(fd, count)
608 || fd >= tape_info_count
609 || (vslot = tape_info[fd].vtape_index) < 0) {
613 return vtable[vslot].xxx_tapefd_fsf(fd, count);
623 || fd >= tape_info_count
624 || (vslot = tape_info[fd].vtape_index) < 0) {
628 return vtable[vslot].xxx_tapefd_rewind(fd);
638 || fd >= tape_info_count
639 || (vslot = tape_info[fd].vtape_index) < 0) {
640 errno = EBADF; /* not that it matters */
643 vtable[vslot].xxx_tapefd_resetofs(fd);
653 || fd >= tape_info_count
654 || (vslot = tape_info[fd].vtape_index) < 0) {
658 return vtable[vslot].xxx_tapefd_unload(fd);
662 tapefd_status(fd, stat)
664 struct am_mt_status *stat;
669 || fd >= tape_info_count
670 || (vslot = tape_info[fd].vtape_index) < 0) {
674 return vtable[vslot].xxx_tapefd_status(fd, stat);
678 tapefd_weof(fd, count)
685 || fd >= tape_info_count
686 || (vslot = tape_info[fd].vtape_index) < 0) {
690 return vtable[vslot].xxx_tapefd_weof(fd, count);
694 tapefd_read(fd, buffer, count)
702 || fd >= tape_info_count
703 || (vslot = tape_info[fd].vtape_index) < 0) {
707 return vtable[vslot].xxx_tapefd_read(fd, buffer, count);
711 tapefd_write(fd, buffer, count)
719 || fd >= tape_info_count
720 || (vslot = tape_info[fd].vtape_index) < 0) {
724 return vtable[vslot].xxx_tapefd_write(fd, buffer, count);
734 if((fd = tape_open(devname, O_RDONLY)) < 0) {
735 r = errstr = newvstralloc(errstr,
736 "tape_rewind: tape open: ",
741 } else if(tapefd_rewind(fd) == -1) {
742 r = errstr = newvstralloc(errstr,
743 "tape_rewind: rewinding tape: ",
762 if((fd = tape_open(devname, O_RDONLY)) < 0) {
763 r = errstr = newvstralloc(errstr,
764 "tape_unload: tape open: ",
769 } else if(tapefd_unload(fd) == -1) {
770 r = errstr = newvstralloc(errstr,
771 "tape_unload: unloading tape: ",
784 tape_fsf(devname, count)
789 char count_str[NUM_STR_SIZE];
792 if((fd = tape_open(devname, O_RDONLY)) < 0) {
793 r = errstr = newvstralloc(errstr,
794 "tape_fsf: tape open: ",
799 } else if(tapefd_fsf(fd, count) == -1) {
800 ap_snprintf(count_str, sizeof(count_str), "%d", count);
801 r = errstr = newvstralloc(errstr,
804 "file", (count == 1) ? "" : "s",
816 tapefd_rdlabel(fd, datestamp, label)
829 buflen = MAX_TAPE_BLOCK_BYTES;
830 buffer = alloc(buflen + 1);
832 if(tapefd_getinfo_fake_label(fd)) {
833 *datestamp = stralloc("X");
834 *label = stralloc(FAKE_LABEL);
835 } else if(tapefd_rewind(fd) == -1) {
836 r = errstr = newstralloc2(errstr, "rewinding tape: ", strerror(errno));
837 } else if((rc = tapefd_read(fd, buffer, buflen)) == -1) {
838 r = errstr = newstralloc2(errstr, "reading label: ", strerror(errno));
841 /* make sure buffer is null-terminated */
844 parse_file_header(buffer, &file, rc);
845 if(file.type != F_TAPESTART) {
846 r = errstr = newstralloc(errstr, "not an amanda tape");
848 *datestamp = stralloc(file.datestamp);
849 *label = stralloc(file.name);
857 tape_rdlabel(devname, datestamp, label)
865 if((fd = tape_open(devname, O_RDONLY)) < 0) {
866 r = errstr = newvstralloc(errstr,
867 "tape_rdlabel: tape open: ",
872 } else if(tapefd_rdlabel(fd, datestamp, label) != NULL) {
882 tapefd_wrlabel(fd, datestamp, label, size)
893 if(tapefd_rewind(fd) == -1) {
894 r = errstr = newstralloc2(errstr, "rewinding tape: ", strerror(errno));
897 file.type = F_TAPESTART;
898 strncpy(file.datestamp, datestamp, sizeof(file.datestamp) - 1);
899 file.datestamp[sizeof(file.datestamp) - 1] = '\0';
900 strncpy(file.name, label, sizeof(file.name) - 1);
901 file.name[sizeof(file.name) - 1] = '\0';
902 buffer = alloc(size);
903 file.blocksize = size;
904 build_header(buffer, &file, size);
905 tapefd_setinfo_host(fd, NULL);
906 tapefd_setinfo_disk(fd, label);
907 tapefd_setinfo_level(fd, -1);
908 if((rc = tapefd_write(fd, buffer, size)) != size) {
909 r = errstr = newstralloc2(errstr,
911 (rc != -1) ? "short write"
920 tape_wrlabel(devname, datestamp, label, size)
929 if((fd = tape_open(devname, O_WRONLY)) < 0) {
930 r = errstr = newstralloc2(errstr,
932 (errno == EACCES) ? "tape is write-protected"
934 } else if(tapefd_wrlabel(fd, datestamp, label, size) != NULL) {
944 tapefd_wrendmark(fd, datestamp, size)
955 file.type = F_TAPEEND;
956 strncpy(file.datestamp, datestamp, sizeof(file.datestamp) - 1);
957 file.datestamp[sizeof(file.datestamp) - 1] = '\0';
958 buffer = alloc(size);
959 file.blocksize = size;
960 build_header(buffer, &file, size);
961 tapefd_setinfo_host(fd, NULL);
962 tapefd_setinfo_disk(fd, "TAPEEND");
963 tapefd_setinfo_level(fd, -1);
965 if((rc = tapefd_write(fd, buffer, size)) != size) {
966 r = errstr = newstralloc2(errstr, "writing endmark: ",
967 (rc != -1) ? "short write" : strerror(errno));
975 tape_wrendmark(devname, datestamp, size)
983 if((fd = tape_open(devname, O_WRONLY)) < 0) {
984 r = errstr = newstralloc2(errstr,
986 (errno == EACCES) ? "tape is write-protected"
988 } else if(tapefd_wrendmark(fd, datestamp, size) != NULL) {
998 tape_writable(devname)
1004 /* first, make sure the file exists and the permissions are right */
1006 if(tape_access(devname, R_OK|W_OK) == -1) {
1007 r = errstr = newstralloc(errstr, strerror(errno));
1008 } else if((fd = tape_open(devname, O_WRONLY)) < 0) {
1009 r = errstr = newstralloc(errstr,
1010 (errno == EACCES) ? "tape write-protected"
1022 * The following test program may be used to exercise I/O patterns through
1023 * the tapeio interface. Commands may either be on the command line or
1024 * read from stdin (e.g. for a test suite).
1030 /* If the C library does not define random(), try to use rand() by
1031 defining USE_RAND, but then make sure you are not using hardware
1032 compression, because the low-order bits of rand() may not be that
1034 #define random() rand()
1035 #define srandom(seed) srand(seed)
1043 fprintf(stderr, " ?|help\n");
1044 fprintf(stderr, " open [\"file\"|$TAPE [\"mode\":O_RDONLY]]\n");
1045 fprintf(stderr, " read [\"records\":\"all\"]\n");
1046 fprintf(stderr, " write [\"records\":1] [\"file#\":\"+\"] [\"record#\":\"+\"] [\"host\"] [\"disk\"] [\"level\"]\n");
1047 fprintf(stderr, " eof|weof [\"count\":1]\n");
1048 fprintf(stderr, " fsf [\"count\":1]\n");
1049 fprintf(stderr, " rewind\n");
1050 fprintf(stderr, " unload\n");
1056 fprintf(stderr, "usage: %s [-c cmd [args] [%% cmd [args] ...]]\n", pgm);
1060 #define TEST_BLOCKSIZE (32 * 1024)
1062 #define MAX_TOKENS 10
1066 static char *token_area[MAX_TOKENS + 1];
1067 static char **token;
1068 static int token_count;
1071 static int current_file = 0;
1072 static int current_record = 0;
1074 static int have_length = 0;
1075 static int length = 0;
1077 static int show_timestamp = 0;
1079 char write_buf[TEST_BLOCKSIZE];
1088 || (token_count >= 2 && strcmp(token[1], "$TAPE") == 0)) {
1089 if((file = getenv("TAPE")) == NULL) {
1090 fprintf(stderr, "tape_open: no file name and $TAPE not set\n");
1096 if(token_count > 2) {
1097 mode = atoi(token[2]);
1102 fprintf(stderr, "tapefd_open(\"%s\", %d): ", file, mode);
1103 if((fd = tape_open(file, mode, 0644)) < 0) {
1106 fprintf(stderr, "%d (OK)\n", fd);
1108 tapefd_setinfo_length(fd, length);
1118 fprintf(stderr, "tapefd_close(): ");
1119 if((result = tapefd_close(fd)) < 0) {
1122 fprintf(stderr, "%d (OK)\n", result);
1132 char buf[sizeof(write_buf)];
1139 if(token_count > 1 && strcmp(token[1], "all") != 0) {
1140 count = atoi(token[1]);
1145 for(i = 0; (! have_count) || (i < count); i++) {
1146 fprintf(stderr, "tapefd_read(%d): ", i);
1147 if((result = tapefd_read(fd, buf, sizeof(buf))) < 0) {
1150 } else if(result == 0) {
1151 fprintf(stderr, "%d (EOF)\n", result);
1153 * If we were not given a count, EOF breaks the loop, otherwise
1154 * we keep trying (to test read after EOF handling).
1160 if(result == sizeof(buf)) {
1167 * If the amount read is really short, we may refer to junk
1168 * when displaying the record data, but things are pretty
1169 * well screwed up at this point anyway so it is not worth
1170 * the effort to deal with.
1173 "%d (%s): file %d: record %d",
1178 if(show_timestamp) {
1180 tm = localtime(&then);
1182 ": %04d/%02d/%02d %02d:%02d:%02d\n",
1190 fputc('\n', stderr);
1206 if(token_count > 1) {
1207 count = atoi(token[1]);
1212 if(token_count > 2 && strcmp(token[2], "+") != 0) {
1213 current_file = atoi(token[2]);
1216 if(token_count > 3 && strcmp(token[3], "+") != 0) {
1217 current_record = atoi(token[3]);
1220 if(token_count > 4 && token[4][0] != '\0') {
1221 tapefd_setinfo_host(fd, token[4]);
1224 if(token_count > 5 && token[5][0] != '\0') {
1225 tapefd_setinfo_disk(fd, token[5]);
1228 if(token_count > 6 && token[6][0] != '\0') {
1229 tapefd_setinfo_level(fd, atoi(token[6]));
1232 p = (int *)write_buf;
1235 tm = localtime(&now);
1236 for(i = 0; i < count; i++, current_record++) {
1237 p[0] = current_file;
1238 p[1] = current_record;
1239 fprintf(stderr, "tapefd_write(%d): ", i);
1240 if((result = tapefd_write(fd, write_buf, sizeof(write_buf))) < 0) {
1244 if(result == sizeof(write_buf)) {
1250 "%d (%s): file %d: record %d",
1255 if(show_timestamp) {
1257 ": %04d/%02d/%02d %02d:%02d:%02d\n",
1265 fputc('\n', stderr);
1276 if(token_count > 1) {
1277 count = atoi(token[1]);
1282 fprintf(stderr, "tapefd_fsf(%d): ", count);
1283 if((result = tapefd_fsf(fd, count)) < 0) {
1286 fprintf(stderr, "%d (OK)\n", result);
1287 current_file += count;
1298 if(token_count > 1) {
1299 count = atoi(token[1]);
1304 fprintf(stderr, "tapefd_weof(%d): ", count);
1305 if((result = tapefd_weof(fd, count)) < 0) {
1308 fprintf(stderr, "%d (OK)\n", result);
1309 current_file += count;
1319 fprintf(stderr, "tapefd_rewind(): ");
1320 if((result = tapefd_rewind(fd)) < 0) {
1323 fprintf(stderr, "%d (OK)\n", result);
1334 fprintf(stderr, "tapefd_unload(): ");
1335 if((result = tapefd_unload(fd)) < 0) {
1338 fprintf(stderr, "%d (OK)\n", result);
1340 current_record = -1;
1349 { "?", 0, do_help },
1350 { "help", 0, do_help },
1351 { "eof", 0, do_weof },
1352 { "weof", 0, do_weof },
1353 { "fsf", 0, do_fsf },
1354 { "rewind", 0, do_rewind },
1355 { "offline", 0, do_unload },
1356 { "open", 0, do_open },
1357 { "close", 0, do_close },
1358 { "read", 0, do_read },
1359 { "write", 0, do_write },
1376 if((pgm = strrchr(argv[0], '/')) != NULL) {
1383 * Compute the minimum abbreviation for each command.
1385 for(i = 0; cmd[i].name; i++) {
1386 cmd[i].min_chars = 1;
1388 for(j = 0; cmd[j].name; j++) {
1392 if(0 == strncmp(cmd[i].name, cmd[j].name, cmd[i].min_chars)) {
1396 if(0 == cmd[j].name) {
1404 * Process the command line flags.
1406 while((ch = getopt(argc, argv, "hcl:t")) != EOF) {
1413 length = atoi(optarg);
1416 switch(optarg[j-1] ) {
1418 case 'b': length /= 2; break;
1419 case 'M': length *= 1024; break;
1420 default: length /= 1024; break;
1437 * Initialize the write buffer.
1441 for(j = 0; j < sizeof(write_buf); j++) {
1442 write_buf[j] = (char)random();
1448 token = token_area + 1;
1449 token_area[0] = ""; /* if cmdline */
1452 for(token_count = 1;
1453 token_count < (sizeof(token_area) / sizeof(token_area[0]))
1455 token_count++, optind++) {
1456 if(strcmp(argv[optind], "%") == 0) {
1460 token_area[token_count] = argv[optind];
1463 if(token_count == 0 && optind >= argc) {
1467 if((line = areads(0)) == NULL) {
1470 if((s = strchr(line, '#')) != NULL) {
1473 s = line + strlen(line) - 1;
1474 while(s >= line && isspace(*s)) {
1477 token_count = split(line,
1479 sizeof(token_area) / sizeof(token_area[0]),
1485 * Truncate tokens at first comment indicator, then test for
1488 for(i = 0; i < token_count; i++) {
1489 if(token[i][0] == '#') {
1494 if(token_count <= 0) {
1495 continue; /* blank/comment input line */
1499 * Find the command to run, the do it.
1501 j = strlen(token[0]);
1502 for(i = 0; cmd[i].name; i++) {
1503 if(strncmp(cmd[i].name, token[0], j) == 0
1504 && j >= cmd[i].min_chars) {
1508 if(cmd[i].name == NULL) {
1509 fprintf(stderr, "%s: unknown command: %s\n", pgm, token[0]);