/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Multi-process streaming 4.3bsd /etc/rmt server. * Has three locks (for stdin, stdout, and the tape) * that are passed by signals and received by sigpause(). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include static sigset_t cmdmask, maskall, newmask; static sigset_t sendmask, tapemask; static struct mtop mtop; static struct mtget mtget; static jmp_buf sjbuf; #define RECV SIGIO #define TAPE SIGURG #define SEND SIGALRM #define ERROR SIGTERM #define OPEN SIGUSR1 #define CLOSE SIGUSR2 /* * Support for Version 1 of the extended RMT protocol: * Placing RMTIVERSION (-1) into the mt_op field of the ioctl ('I') * request will return the current version of the RMT protocol that * the server supports. For servers that don't support Version 1, * an error is returned and the client knows to only use Version 0 * (stock BSD) calls, which include mt_op values in the range of [0-7]. * * Note: The RMTIVERSION request must be made in order for the extended * protocol commands to be recognized. */ #define RMTIVERSION -1 #define RMT_VERSION 1 /* * These requests are made to the extended RMT protocol by specifying the * new 'i' command of RMT Protocol Version 1. They are intended to allow * an intelligent client to communicate with both BSD and Solaris RMT * servers heterogeneously. The 'i' command taks an mtop structure as * argument, exactly like the 'I' command does. */ #define RMTICACHE 0 #define RMTINOCACHE 1 #define RMTIRETEN 2 #define RMTIERASE 3 #define RMTIEOM 4 #define RMTINBSF 5 /* * These requests are made to the extended RMT protocol by specifying the * new 's' command of RMT Protocol Version 1. They are intended to allow * an intelligent client to obtain "mt status" information with both BSD * and Solaris RMT servers heterogeneously. They return the requested * piece of the mtget structure as an ascii integer. The request is made * by sending the required character immediately after the 's' character * without any trailing newline. A single ascii integer is returned, else * an error is returned. */ #define MTS_TYPE 'T' /* mtget.mt_type */ #define MTS_DSREG 'D' /* mtget.mt_dsreg */ #define MTS_ERREG 'E' /* mtget.mt_erreg */ #define MTS_RESID 'R' /* mtget.mt_resid */ #define MTS_FILENO 'F' /* mtget.mt_fileno */ #define MTS_BLKNO 'B' /* mtget.mt_blkno */ #define MTS_FLAGS 'f' /* mtget.mt_flags */ #define MTS_BF 'b' /* mtget.mt_bf */ #define MAXCHILD 1 static pid_t childpid[MAXCHILD]; static int children; static int tape = -1; static size_t maxrecsize = 0; static char *record; #define SSIZE 64 static char pos[SSIZE], op[SSIZE], mode[SSIZE], count[SSIZE]; static char device[MAXPATHLEN]; static FILE *debug; #define DEBUG(f) if (debug) (void) fprintf(debug, (f)) #define DEBUG1(f, a) if (debug) (void) fprintf(debug, (f), (a)) #define DEBUG2(f, a, b) if (debug) (void) fprintf(debug, (f), (a), (b)) #define DEBUG3(f, a, b, c) if (debug) \ (void) fprintf(debug, (f), (a), (b), (c)) static char key; #ifdef __STDC__ static void respond(offset_t, int); static void getstring(char *, size_t); static void checkbuf(size_t); #else static void respond(); static void getstring(); static void checkbuf(); #endif static void catch(int sig) { switch (sig) { default: return; case OPEN: key = 'O'; break; case CLOSE: key = 'C'; break; case ERROR: key = 'E'; break; } (void) sigprocmask(SIG_SETMASK, &maskall, (sigset_t *)0); longjmp(sjbuf, 1); } int main(int argc, char *argv[]) { struct sigaction sa; pid_t parent = getpid(), next = parent; int saverr; offset_t rval; ssize_t cc; size_t n, i; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); if (argc > 1) { if ((debug = fopen(argv[1], "w")) == NULL) exit(1); setbuf(debug, NULL); } (void) sigemptyset(&maskall); (void) sigaddset(&maskall, RECV); (void) sigaddset(&maskall, OPEN); (void) sigaddset(&maskall, CLOSE); (void) sigaddset(&maskall, ERROR); (void) sigaddset(&maskall, TAPE); (void) sigaddset(&maskall, SEND); tapemask = maskall; (void) sigdelset(&tapemask, TAPE); sendmask = maskall; (void) sigdelset(&sendmask, SEND); (void) sigemptyset(&cmdmask); (void) sigaddset(&cmdmask, TAPE); (void) sigaddset(&cmdmask, SEND); (void) sigemptyset(&sa.sa_mask); sa.sa_handler = catch; sa.sa_flags = SA_RESTART; (void) sigaction(RECV, &sa, (struct sigaction *)0); (void) sigaction(SEND, &sa, (struct sigaction *)0); (void) sigaction(TAPE, &sa, (struct sigaction *)0); (void) sigaction(OPEN, &sa, (struct sigaction *)0); (void) sigaction(CLOSE, &sa, (struct sigaction *)0); (void) sigaction(ERROR, &sa, (struct sigaction *)0); (void) sigprocmask(SIG_SETMASK, &maskall, (sigset_t *)0); (void) kill(parent, TAPE); (void) kill(parent, SEND); while (read(0, &key, 1) == 1) { switch (key) { case 'L': /* lseek */ getstring(count, sizeof (count)); getstring(pos, sizeof (pos)); DEBUG2("rmtd: L %s %s\n", count, pos); (void) kill(next, RECV); (void) sigsuspend(&tapemask); rval = llseek(tape, atoll(count), atoi(pos)); saverr = errno; (void) kill(next, TAPE); (void) sigsuspend(&sendmask); respond(rval, saverr); break; case 'I': /* ioctl */ case 'i': { /* extended version ioctl */ int bad = 0; getstring(op, sizeof (op)); getstring(count, sizeof (count)); DEBUG3("rmtd: %c %s %s\n", key, op, count); mtop.mt_op = atoi(op); mtop.mt_count = atoi(count); if (key == 'i') { /* * Map the supported compatibility defines * into real ioctl values. */ switch (mtop.mt_op) { case RMTICACHE: case RMTINOCACHE: /* not support on Sun */ bad = 1; break; case RMTIRETEN: mtop.mt_op = MTRETEN; break; case RMTIERASE: mtop.mt_op = MTERASE; break; case RMTIEOM: mtop.mt_op = MTEOM; break; case RMTINBSF: mtop.mt_op = MTNBSF; break; default: bad = 1; break; } } if (bad) { respond(-1LL, EINVAL); } else { (void) kill(next, RECV); (void) sigsuspend(&tapemask); if (mtop.mt_op == RMTIVERSION) { mtop.mt_count = RMT_VERSION; rval = (offset_t)mtop.mt_count; } else { rval = (offset_t)ioctl(tape, MTIOCTOP, (char *)&mtop); } saverr = errno; (void) kill(next, TAPE); (void) sigsuspend(&sendmask); respond(rval < 0 ? rval : (offset_t)mtop.mt_count, saverr); } break; } case 'S': /* status */ case 's': { /* extended status */ char skey; DEBUG1("rmtd: %c\n", key); if (key == 's') { if (read(0, &skey, 1) != 1) continue; } (void) kill(next, RECV); (void) sigsuspend(&tapemask); errno = 0; rval = (offset_t)ioctl(tape, MTIOCGET, (char *)&mtget); saverr = errno; (void) kill(next, TAPE); (void) sigsuspend(&sendmask); if (rval < 0) respond(rval, saverr); else { if (key == 's') { /* extended status */ DEBUG1("rmtd: s%c\n", key); switch (skey) { case MTS_TYPE: respond( (offset_t)mtget.mt_type, saverr); break; case MTS_DSREG: respond( (offset_t)mtget.mt_dsreg, saverr); break; case MTS_ERREG: respond( (offset_t)mtget.mt_erreg, saverr); break; case MTS_RESID: respond( (offset_t)mtget.mt_resid, saverr); break; case MTS_FILENO: respond( (offset_t)mtget.mt_fileno, saverr); break; case MTS_BLKNO: respond( (offset_t)mtget.mt_blkno, saverr); break; case MTS_FLAGS: respond( (offset_t)mtget.mt_flags, saverr); break; case MTS_BF: respond((offset_t)mtget.mt_bf, saverr); break; default: respond(-1LL, EINVAL); break; } } else { respond((offset_t)sizeof (mtget), saverr); (void) write(1, (char *)&mtget, sizeof (mtget)); } } break; } case 'W': getstring(count, sizeof (count)); n = (size_t)atol(count); checkbuf(n); DEBUG1("rmtd: W %s\n", count); #ifdef lint cc = 0; #endif for (i = 0; i < n; i += (size_t)cc) { cc = read(0, &record[i], n - i); if (cc <= 0) { DEBUG1(gettext("%s: premature eof\n"), "rmtd"); exit(2); } } (void) kill(next, RECV); (void) sigsuspend(&tapemask); rval = (offset_t)write(tape, record, n); saverr = errno; (void) kill(next, TAPE); (void) sigsuspend(&sendmask); respond(rval, saverr); break; case 'R': getstring(count, sizeof (count)); n = (size_t)atol(count); checkbuf(n); DEBUG1("rmtd: R %s\n", count); (void) kill(next, RECV); (void) sigsuspend(&tapemask); rval = (offset_t)read(tape, record, n); saverr = errno; (void) kill(next, TAPE); (void) sigsuspend(&sendmask); respond(rval, saverr); (void) write(1, record, (size_t)rval); break; default: DEBUG2(gettext("%s: garbage command '%c'\n"), "rmtd", key); /*FALLTHROUGH*/ case 'C': case 'O': /* rendezvous back into a single process */ if (setjmp(sjbuf) == 0 || getpid() != parent) { (void) sigsuspend(&tapemask); (void) sigsuspend(&sendmask); (void) kill(parent, key == 'O' ? OPEN : key == 'C' ? CLOSE : ERROR); (void) sigemptyset(&newmask); (void) sigsuspend(&newmask); } while (children > 0) { (void) kill(childpid[--children], SIGKILL); while (wait(NULL) != childpid[children]) ; } next = parent; if (key == 'C') { getstring(device, sizeof (device)); DEBUG1("rmtd: C %s\n", device); rval = (offset_t)close(tape); respond(rval, errno); (void) kill(parent, TAPE); (void) kill(parent, SEND); continue; } if (key != 'O') /* garbage command */ exit(3); (void) close(tape); getstring(device, sizeof (device)); getstring(mode, sizeof (mode)); DEBUG2("rmtd: O %s %s\n", device, mode); /* * Due to incompatibilities in the * assignment of mode bits between * BSD and System V, we strip all * but the read/write bits. However, * we also want to handle things larger * than 2GB, so we also force O_LARGEFILE. */ tape = open(device, O_LARGEFILE | (atoi(mode) & (O_RDONLY|O_WRONLY|O_RDWR))); respond((offset_t)tape, errno); if (tape >= 0) /* fork off */ while (children < MAXCHILD && (childpid[children] = fork()) > 0) next = childpid[children++]; if (next == parent) { (void) kill(parent, RECV); (void) kill(parent, TAPE); (void) kill(parent, SEND); } (void) sigsuspend(&cmdmask); continue; } (void) kill(next, SEND); (void) sigsuspend(&cmdmask); } (void) kill(next, RECV); return (0); } static void respond(offset_t rval, int Errno) { char resp[SSIZE]; char *errstr = strerror(Errno); if (rval < 0) { (void) snprintf(resp, SSIZE, "E%d\n%s\n", Errno, errstr); DEBUG2("rmtd: E %d (%s)\n", Errno, errstr); } else { (void) snprintf(resp, SSIZE, "A%lld\n", rval); DEBUG1("rmtd: A %lld\n", rval); } resp[SSIZE - 1] = '\0'; (void) write(1, resp, (int)strlen(resp)); } static void getstring(char *cp, size_t size) { char *limit = cp + size - 1; cp--; /* nullify first increment */ do { cp++; if (read(0, cp, 1) != 1) exit(0); } while ((*cp != '\n') && (cp < limit)); *cp = '\0'; } static void checkbuf(size_t size) { if (size <= maxrecsize) return; if (record != 0) free(record); if ((record = malloc(size)) == NULL) { DEBUG2(gettext("%s: cannot allocate %ld-byte buffer\n"), size, "rmtd"); exit(4); } maxrecsize = size; }