/* $NetBSD: rmtlib.c,v 1.21 2006/03/19 23:05:50 christos Exp $ */ /* * rmt --- remote tape emulator subroutines * * Originally written by Jeff Lee, modified some by Arnold Robbins * * WARNING: The man page rmt(8) for /etc/rmt documents the remote mag * tape protocol which rdump and rrestore use. Unfortunately, the man * page is *WRONG*. The author of the routines I'm including originally * wrote his code just based on the man page, and it didn't work, so he * went to the rdump source to figure out why. The only thing he had to * change was to check for the 'F' return code in addition to the 'E', * and to separate the various arguments with \n instead of a space. I * personally don't think that this is much of a problem, but I wanted to * point it out. * -- Arnold Robbins * * Redone as a library that can replace open, read, write, etc, by * Fred Fish, with some additional work by Arnold Robbins. */ /* * MAXUNIT --- Maximum number of remote tape file units * * READ --- Return the number of the read side file descriptor * WRITE --- Return the number of the write side file descriptor */ #include __RCSID("$NetBSD: rmtlib.c,v 1.21 2006/03/19 23:05:50 christos Exp $"); #define RMTIOCTL 1 /* #define USE_REXEC 1 */ /* rexec code courtesy of Dan Kegel, srs!dan */ #include #include #ifdef RMTIOCTL #include #include #endif #include #include #include #include #include #include #include #include #include #ifdef USE_REXEC #include #endif #define __RMTLIB_PRIVATE #include /* get prototypes for remapped functions */ #include "pathnames.h" static int _rmt_close(int); static int _rmt_ioctl(int, unsigned long, void *); static off_t _rmt_lseek(int, off_t, int); static int _rmt_open(const char *, int, int); static ssize_t _rmt_read(int, void *, size_t); static ssize_t _rmt_write(int, const void *, size_t); static int command(int, char *); static int remdev(const char *); static void rmtabort(int); static int status(int); int isrmt(int); #define BUFMAGIC 64 /* a magic number for buffer sizes */ #define MAXUNIT 4 #define READ(fd) (Ctp[fd][0]) #define WRITE(fd) (Ptc[fd][1]) static int Ctp[MAXUNIT][2] = { {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1} }; static int Ptc[MAXUNIT][2] = { {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1} }; /* * rmtabort --- close off a remote tape connection */ static void rmtabort(int fildes) { close(READ(fildes)); close(WRITE(fildes)); READ(fildes) = -1; WRITE(fildes) = -1; } /* * command --- attempt to perform a remote tape command */ static int command(int fildes, char *buf) { size_t blen; void (*pstat)(int); _DIAGASSERT(buf != NULL); /* * save current pipe status and try to make the request */ blen = strlen(buf); pstat = signal(SIGPIPE, SIG_IGN); if (write(WRITE(fildes), buf, blen) == blen) { signal(SIGPIPE, pstat); return (0); } /* * something went wrong. close down and go home */ signal(SIGPIPE, pstat); rmtabort(fildes); errno = EIO; return (-1); } /* * status --- retrieve the status from the pipe */ static int status(int fildes) { int i; char c, *cp; char buffer[BUFMAGIC]; /* * read the reply command line */ for (i = 0, cp = buffer; i < BUFMAGIC; i++, cp++) { if (read(READ(fildes), cp, 1) != 1) { rmtabort(fildes); errno = EIO; return (-1); } if (*cp == '\n') { *cp = 0; break; } } if (i == BUFMAGIC) { rmtabort(fildes); errno = EIO; return (-1); } /* * check the return status */ for (cp = buffer; *cp; cp++) if (*cp != ' ') break; if (*cp == 'E' || *cp == 'F') { errno = atoi(cp + 1); while (read(READ(fildes), &c, 1) == 1) if (c == '\n') break; if (*cp == 'F') rmtabort(fildes); return (-1); } /* * check for mis-synced pipes */ if (*cp != 'A') { rmtabort(fildes); errno = EIO; return (-1); } return (atoi(cp + 1)); } #ifdef USE_REXEC /* * _rmt_rexec * * execute /etc/rmt on a remote system using rexec(). * Return file descriptor of bidirectional socket for stdin and stdout * If username is NULL, or an empty string, uses current username. * * ADR: By default, this code is not used, since it requires that * the user have a .netrc file in his/her home directory, or that the * application designer be willing to have rexec prompt for login and * password info. This may be unacceptable, and .rhosts files for use * with rsh are much more common on BSD systems. */ static int _rmt_rexec(const char *, const char *); static int _rmt_rexec(const char *host, const char *user) { struct servent *rexecserv; _DIAGASSERT(host != NULL); /* user may be NULL */ rexecserv = getservbyname("exec", "tcp"); if (rexecserv == NULL) { fprintf(stderr, "? exec/tcp: service not available."); exit(1); } if ((user != NULL) && *user == '\0') user = NULL; return (rexec(&host, rexecserv->s_port, user, NULL, "/etc/rmt", NULL)); } #endif /* USE_REXEC */ /* * _rmt_open --- open a magtape device on system specified, as given user * * file name has the form [user@]system:/dev/???? #ifdef COMPAT * file name has the form system[.user]:/dev/???? #endif */ #define MAXHOSTLEN 257 /* BSD allows very long host names... */ static int /*ARGSUSED*/ _rmt_open(const char *path, int oflag, int mode) { int i, rc; char buffer[BUFMAGIC]; char host[MAXHOSTLEN]; char device[BUFMAGIC]; char login[BUFMAGIC]; char *sys, *dev, *user; _DIAGASSERT(path != NULL); sys = host; dev = device; user = login; /* * first, find an open pair of file descriptors */ for (i = 0; i < MAXUNIT; i++) if (READ(i) == -1 && WRITE(i) == -1) break; if (i == MAXUNIT) { errno = EMFILE; return (-1); } /* * pull apart system and device, and optional user * don't munge original string * if COMPAT is defined, also handle old (4.2) style person.site notation. */ while (*path != '@' #ifdef COMPAT && *path != '.' #endif && *path != ':') { *sys++ = *path++; } *sys = '\0'; path++; if (*(path - 1) == '@') { (void)strncpy(user, host, sizeof(login) - 1); /* saw user part of user@host */ sys = host; /* start over */ while (*path != ':') { *sys++ = *path++; } *sys = '\0'; path++; } #ifdef COMPAT else if (*(path - 1) == '.') { while (*path != ':') { *user++ = *path++; } *user = '\0'; path++; } #endif else *user = '\0'; while (*path) { *dev++ = *path++; } *dev = '\0'; #ifdef USE_REXEC /* * Execute the remote command using rexec */ READ(i) = WRITE(i) = _rmt_rexec(host, login); if (READ(i) < 0) return (-1); #else /* * setup the pipes for the 'rsh' command and fork */ if (pipe(Ptc[i]) == -1 || pipe(Ctp[i]) == -1) return (-1); if ((rc = fork()) == -1) return (-1); if (rc == 0) { char *rshpath, *rsh; close(0); dup(Ptc[i][0]); close(Ptc[i][0]); close(Ptc[i][1]); close(1); dup(Ctp[i][1]); close(Ctp[i][0]); close(Ctp[i][1]); (void) setuid(getuid()); (void) setgid(getgid()); if ((rshpath = getenv("RCMD_CMD")) == NULL) rshpath = _PATH_RSH; if ((rsh = strrchr(rshpath, '/')) == NULL) rsh = rshpath; else rsh++; if (*login) { execl(rshpath, rsh, host, "-l", login, _PATH_RMT, NULL); } else { execl(rshpath, rsh, host, _PATH_RMT, NULL); } /* * bad problems if we get here */ perror("exec"); exit(1); } close(Ptc[i][0]); close(Ctp[i][1]); #endif /* * now attempt to open the tape device */ (void)snprintf(buffer, sizeof(buffer), "O%s\n%d\n", device, oflag); if (command(i, buffer) == -1 || status(i) == -1) return (-1); return (i); } /* * _rmt_close --- close a remote magtape unit and shut down */ static int _rmt_close(int fildes) { int rc; if (command(fildes, "C\n") != -1) { rc = status(fildes); rmtabort(fildes); return (rc); } return (-1); } /* * _rmt_read --- read a buffer from a remote tape */ static ssize_t _rmt_read(int fildes, void *buf, size_t nbyte) { size_t rc; int rv; ssize_t nread; char *p; char buffer[BUFMAGIC]; _DIAGASSERT(buf != NULL); (void)snprintf(buffer, sizeof buffer, "R%lu\n", (u_long)nbyte); if (command(fildes, buffer) == -1 || (rv = status(fildes)) == -1) return (-1); if (rv > nbyte) rv = nbyte; for (rc = rv, p = buf; rc > 0; rc -= nread, p += nread) { if ((nread = read(READ(fildes), p, rc)) <= 0) { rmtabort(fildes); errno = EIO; return (-1); } } return rv; } /* * _rmt_write --- write a buffer to the remote tape */ static ssize_t _rmt_write(int fildes, const void *buf, size_t nbyte) { char buffer[BUFMAGIC]; void (*pstat)(int); _DIAGASSERT(buf != NULL); (void)snprintf(buffer, sizeof buffer, "W%lu\n", (u_long)nbyte); if (command(fildes, buffer) == -1) return (-1); pstat = signal(SIGPIPE, SIG_IGN); if (write(WRITE(fildes), buf, nbyte) == nbyte) { signal(SIGPIPE, pstat); return (status(fildes)); } signal(SIGPIPE, pstat); rmtabort(fildes); errno = EIO; return (-1); } /* * _rmt_lseek --- perform an imitation lseek operation remotely */ static off_t _rmt_lseek(int fildes, off_t offset, int whence) { char buffer[BUFMAGIC]; /*LONGLONG*/ (void)snprintf(buffer, sizeof buffer, "L%lld\n%d\n", (long long)offset, whence); if (command(fildes, buffer) == -1) return (-1); return (status(fildes)); } /* * _rmt_ioctl --- perform raw tape operations remotely */ #ifdef RMTIOCTL static int _rmt_ioctl(int fildes, unsigned long op, void *arg) { char c; int rv; size_t rc; ssize_t cnt; char buffer[BUFMAGIC], *p; _DIAGASSERT(arg != NULL); /* * MTIOCOP is the easy one. nothing is transfered in binary */ if (op == MTIOCTOP) { (void)snprintf(buffer, sizeof buffer, "I%d\n%d\n", ((struct mtop *)arg)->mt_op, ((struct mtop *)arg)->mt_count); if (command(fildes, buffer) == -1) return (-1); return (status(fildes)); } /* * we can only handle 2 ops, if not the other one, punt */ if (op != MTIOCGET) { errno = EINVAL; return (-1); } /* * grab the status and read it directly into the structure * this assumes that the status buffer is (hopefully) not * padded and that 2 shorts fit in a long without any word * alignment problems, ie - the whole struct is contiguous * NOTE - this is probably NOT a good assumption. */ if (command(fildes, "S") == -1 || (rv = status(fildes)) == -1) return (-1); for (rc = rv, p = arg; rc > 0; rc -= cnt, p += cnt) { if ((cnt = read(READ(fildes), p, rc)) <= 0) { rmtabort(fildes); errno = EIO; return (-1); } } /* * now we check for byte position. mt_type is a small integer field * (normally) so we will check its magnitude. if it is larger than * 256, we will assume that the bytes are swapped and go through * and reverse all the bytes */ if (((struct mtget *)(void *)p)->mt_type < 256) return (0); for (cnt = 0; cnt < rv; cnt += 2) { c = p[cnt]; p[cnt] = p[cnt+1]; p[cnt+1] = c; } return (0); } #endif /* RMTIOCTL */ /* * Added routines to replace open(), close(), lseek(), ioctl(), etc. * The preprocessor can be used to remap these the rmtopen(), etc * thus minimizing source changes: * * #ifdef * # define access rmtaccess * # define close rmtclose * # define creat rmtcreat * # define dup rmtdup * # define fcntl rmtfcntl * # define fstat rmtfstat * # define ioctl rmtioctl * # define isatty rmtisatty * # define lseek rmtlseek * # define lstat rmtlstat * # define open rmtopen * # define read rmtread * # define stat rmtstat * # define write rmtwrite * #endif * * -- Fred Fish * * ADR --- I set up a include file for this * */ /* * Note that local vs remote file descriptors are distinquished * by adding a bias to the remote descriptors. This is a quick * and dirty trick that may not be portable to some systems. */ #define REM_BIAS 128 /* * Test pathname to see if it is local or remote. A remote device * is any string that contains ":/dev/". Returns 1 if remote, * 0 otherwise. */ static int remdev(const char *path) { _DIAGASSERT(path != NULL); if ((path = strchr(path, ':')) != NULL) { if (strncmp(path + 1, "/dev/", 5) == 0) { return (1); } } return (0); } /* * Open a local or remote file. Looks just like open(2) to * caller. */ int rmtopen(const char *path, int oflag, ...) { mode_t mode; int fd; va_list ap; va_start(ap, oflag); mode = va_arg(ap, mode_t); va_end(ap); _DIAGASSERT(path != NULL); if (remdev(path)) { fd = _rmt_open(path, oflag, (int)mode); return ((fd == -1) ? -1 : (fd + REM_BIAS)); } else { return (open(path, oflag, mode)); } } /* * Test pathname for specified access. Looks just like access(2) * to caller. */ int rmtaccess(const char *path, int amode) { _DIAGASSERT(path != NULL); if (remdev(path)) { return (0); /* Let /etc/rmt find out */ } else { return (access(path, amode)); } } /* * Isrmt. Let a programmer know he has a remote device. */ int isrmt(int fd) { return (fd >= REM_BIAS); } /* * Read from stream. Looks just like read(2) to caller. */ ssize_t rmtread(int fildes, void *buf, size_t nbyte) { _DIAGASSERT(buf != NULL); if (isrmt(fildes)) { return (_rmt_read(fildes - REM_BIAS, buf, nbyte)); } else { return (read(fildes, buf, nbyte)); } } /* * Write to stream. Looks just like write(2) to caller. */ ssize_t rmtwrite(int fildes, const void *buf, size_t nbyte) { _DIAGASSERT(buf != NULL); if (isrmt(fildes)) { return (_rmt_write(fildes - REM_BIAS, buf, nbyte)); } else { return (write(fildes, buf, nbyte)); } } /* * Perform lseek on file. Looks just like lseek(2) to caller. */ off_t rmtlseek(int fildes, off_t offset, int whence) { if (isrmt(fildes)) { return (_rmt_lseek(fildes - REM_BIAS, offset, whence)); } else { return (lseek(fildes, offset, whence)); } } /* * Close a file. Looks just like close(2) to caller. */ int rmtclose(int fildes) { if (isrmt(fildes)) { return (_rmt_close(fildes - REM_BIAS)); } else { return (close(fildes)); } } /* * Do ioctl on file. Looks just like ioctl(2) to caller. */ int rmtioctl(int fildes, unsigned long request, ...) { char *arg; va_list ap; va_start(ap, request); arg = va_arg(ap, char *); va_end(ap); /* XXX: arg may be NULL ? */ if (isrmt(fildes)) { #ifdef RMTIOCTL return (_rmt_ioctl(fildes - REM_BIAS, request, arg)); #else errno = EOPNOTSUPP; return (-1); /* For now (fnf) */ #endif } else { return (ioctl(fildes, request, arg)); } } /* * Duplicate an open file descriptor. Looks just like dup(2) * to caller. */ int rmtdup(int fildes) { if (isrmt(fildes)) { errno = EOPNOTSUPP; return (-1); /* For now (fnf) */ } else { return (dup(fildes)); } } /* * Get file status. Looks just like fstat(2) to caller. */ int rmtfstat(int fildes, struct stat *buf) { _DIAGASSERT(buf != NULL); if (isrmt(fildes)) { errno = EOPNOTSUPP; return (-1); /* For now (fnf) */ } else { return (fstat(fildes, buf)); } } /* * Get file status. Looks just like stat(2) to caller. */ int rmtstat(const char *path, struct stat *buf) { _DIAGASSERT(path != NULL); _DIAGASSERT(buf != NULL); if (remdev(path)) { errno = EOPNOTSUPP; return (-1); /* For now (fnf) */ } else { return (stat(path, buf)); } } /* * Create a file from scratch. Looks just like creat(2) to the caller. */ int rmtcreat(const char *path, mode_t mode) { _DIAGASSERT(path != NULL); if (remdev(path)) { return (rmtopen(path, 1 | O_CREAT, mode)); } else { return (creat(path, mode)); } } /* * Rmtfcntl. Do a remote fcntl operation. */ int rmtfcntl(int fd, int cmd, ...) { void *arg; va_list ap; va_start(ap, cmd); arg = va_arg(ap, void *); va_end(ap); /* XXX: arg may be NULL ? */ if (isrmt(fd)) { errno = EOPNOTSUPP; return (-1); } else { return (fcntl(fd, cmd, arg)); } } /* * Rmtisatty. Do the isatty function. */ int rmtisatty(int fd) { if (isrmt(fd)) return (0); else return (isatty(fd)); } /* * Get file status, even if symlink. Looks just like lstat(2) to caller. */ int rmtlstat(const char *path, struct stat *buf) { _DIAGASSERT(path != NULL); _DIAGASSERT(buf != NULL); if (remdev(path)) { errno = EOPNOTSUPP; return (-1); /* For now (fnf) */ } else { return (lstat(path, buf)); } }