1 /* $NetBSD: rmtlib.c,v 1.26 2012/03/21 10:10:37 matt Exp $ */
2
3 /*
4 * rmt --- remote tape emulator subroutines
5 *
6 * Originally written by Jeff Lee, modified some by Arnold Robbins
7 *
8 * WARNING: The man page rmt(8) for /etc/rmt documents the remote mag
9 * tape protocol which rdump and rrestore use. Unfortunately, the man
10 * page is *WRONG*. The author of the routines I'm including originally
11 * wrote his code just based on the man page, and it didn't work, so he
12 * went to the rdump source to figure out why. The only thing he had to
13 * change was to check for the 'F' return code in addition to the 'E',
14 * and to separate the various arguments with \n instead of a space. I
15 * personally don't think that this is much of a problem, but I wanted to
16 * point it out.
17 * -- Arnold Robbins
18 *
19 * Redone as a library that can replace open, read, write, etc, by
20 * Fred Fish, with some additional work by Arnold Robbins.
21 */
22
23 /*
24 * MAXUNIT --- Maximum number of remote tape file units
25 *
26 * READ --- Return the number of the read side file descriptor
27 * WRITE --- Return the number of the write side file descriptor
28 */
29
30 #include <sys/cdefs.h>
31 __RCSID("$NetBSD: rmtlib.c,v 1.26 2012/03/21 10:10:37 matt Exp $");
32
33 #define RMTIOCTL 1
34 /* #define USE_REXEC 1 */ /* rexec code courtesy of Dan Kegel, srs!dan */
35
36 #include <sys/types.h>
37 #include <sys/stat.h>
38
39 #ifdef RMTIOCTL
40 #include <sys/ioctl.h>
41 #include <sys/mtio.h>
42 #endif
43
44 #include <assert.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <signal.h>
48 #include <stdarg.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53 #include <err.h>
54
55 #ifdef USE_REXEC
56 #include <netdb.h>
57 #endif
58
59 #define __RMTLIB_PRIVATE
60 #include <rmt.h> /* get prototypes for remapped functions */
61
62 #include "pathnames.h"
63
64 static int _rmt_close(int);
65 static int _rmt_ioctl(int, unsigned long, void *);
66 static off_t _rmt_lseek(int, off_t, int);
67 static int _rmt_open(const char *, int, int);
68 static ssize_t _rmt_read(int, void *, size_t);
69 static ssize_t _rmt_write(int, const void *, size_t);
70 static int command(int, const char *);
71 static int remdev(const char *);
72 static void rmtabort(int);
73 static int status(int);
74
75
76 #define BUFMAGIC 64 /* a magic number for buffer sizes */
77 #define MAXUNIT 4
78
79 #define READ(fd) (Ctp[fd][0])
80 #define WRITE(fd) (Ptc[fd][1])
81
82 static int Ctp[MAXUNIT][2] = { {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1} };
83 static int Ptc[MAXUNIT][2] = { {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1} };
84
85
86 /*
87 * rmtabort --- close off a remote tape connection
88 */
89 static void
rmtabort(int fildes)90 rmtabort(int fildes)
91 {
92
93 close(READ(fildes));
94 close(WRITE(fildes));
95 READ(fildes) = -1;
96 WRITE(fildes) = -1;
97 }
98
99
100 /*
101 * command --- attempt to perform a remote tape command
102 */
103 static int
command(int fildes,const char * buf)104 command(int fildes, const char *buf)
105 {
106 size_t blen;
107 sig_t pstat;
108
109 _DIAGASSERT(buf != NULL);
110
111 /*
112 * save current pipe status and try to make the request
113 */
114
115 blen = strlen(buf);
116 pstat = signal(SIGPIPE, SIG_IGN);
117 if ((size_t)write(WRITE(fildes), buf, blen) == blen) {
118 signal(SIGPIPE, pstat);
119 return 0;
120 }
121
122 /*
123 * something went wrong. close down and go home
124 */
125
126 signal(SIGPIPE, pstat);
127 rmtabort(fildes);
128
129 errno = EIO;
130 return -1;
131 }
132
133
134 /*
135 * status --- retrieve the status from the pipe
136 */
137 static int
status(int fildes)138 status(int fildes)
139 {
140 int i;
141 char c, *cp;
142 char buffer[BUFMAGIC];
143
144 /*
145 * read the reply command line
146 */
147
148 for (i = 0, cp = buffer; i < BUFMAGIC; i++, cp++) {
149 if (read(READ(fildes), cp, 1) != 1) {
150 rmtabort(fildes);
151 errno = EIO;
152 return -1;
153 }
154 if (*cp == '\n') {
155 *cp = 0;
156 break;
157 }
158 }
159
160 if (i == BUFMAGIC) {
161 rmtabort(fildes);
162 errno = EIO;
163 return -1;
164 }
165
166 /*
167 * check the return status
168 */
169
170 for (cp = buffer; *cp; cp++)
171 if (*cp != ' ')
172 break;
173
174 if (*cp == 'E' || *cp == 'F') {
175 errno = atoi(cp + 1);
176 while (read(READ(fildes), &c, 1) == 1)
177 if (c == '\n')
178 break;
179
180 if (*cp == 'F')
181 rmtabort(fildes);
182
183 return -1;
184 }
185
186 /*
187 * check for mis-synced pipes
188 */
189
190 if (*cp != 'A') {
191 rmtabort(fildes);
192 errno = EIO;
193 return -1;
194 }
195
196 return atoi(cp + 1);
197 }
198
199
200 #ifdef USE_REXEC
201 /*
202 * _rmt_rexec
203 *
204 * execute /etc/rmt on a remote system using rexec().
205 * Return file descriptor of bidirectional socket for stdin and stdout
206 * If username is NULL, or an empty string, uses current username.
207 *
208 * ADR: By default, this code is not used, since it requires that
209 * the user have a .netrc file in his/her home directory, or that the
210 * application designer be willing to have rexec prompt for login and
211 * password info. This may be unacceptable, and .rhosts files for use
212 * with rsh are much more common on BSD systems.
213 */
214
215 static int _rmt_rexec(const char *, const char *);
216
217 static int
_rmt_rexec(const char * host,const char * user)218 _rmt_rexec(const char *host, const char *user)
219 {
220 struct servent *rexecserv;
221
222 _DIAGASSERT(host != NULL);
223 /* user may be NULL */
224
225 rexecserv = getservbyname("exec", "tcp");
226 if (rexecserv == NULL)
227 errx(1, "exec/tcp: service not available.");
228 if ((user != NULL) && *user == '\0')
229 user = NULL;
230 return rexec(&host, rexecserv->s_port, user, NULL,
231 "/etc/rmt", NULL);
232 }
233 #endif /* USE_REXEC */
234
235
236 /*
237 * _rmt_open --- open a magtape device on system specified, as given user
238 *
239 * file name has the form [user@]system:/dev/????
240 #ifdef COMPAT
241 * file name has the form system[.user]:/dev/????
242 #endif
243 */
244
245 #define MAXHOSTLEN 257 /* BSD allows very long host names... */
246
247 static int
248 /*ARGSUSED*/
_rmt_open(const char * path,int oflag,int mode)249 _rmt_open(const char *path, int oflag, int mode)
250 {
251 int i;
252 char buffer[BUFMAGIC];
253 char host[MAXHOSTLEN];
254 char device[BUFMAGIC];
255 char login[BUFMAGIC];
256 char *sys, *dev, *user;
257 const char *rshpath, *rsh;
258
259 _DIAGASSERT(path != NULL);
260
261 sys = host;
262 dev = device;
263 user = login;
264
265 /*
266 * first, find an open pair of file descriptors
267 */
268
269 for (i = 0; i < MAXUNIT; i++)
270 if (READ(i) == -1 && WRITE(i) == -1)
271 break;
272
273 if (i == MAXUNIT) {
274 errno = EMFILE;
275 return -1;
276 }
277
278 /*
279 * pull apart system and device, and optional user
280 * don't munge original string
281 * if COMPAT is defined, also handle old (4.2) style person.site notation.
282 */
283
284 while (*path != '@'
285 #ifdef COMPAT
286 && *path != '.'
287 #endif
288 && *path != ':') {
289 *sys++ = *path++;
290 }
291 *sys = '\0';
292 path++;
293
294 if (*(path - 1) == '@') {
295 (void)strncpy(user, host, sizeof(login) - 1);
296 /* saw user part of user@host */
297 sys = host; /* start over */
298 while (*path != ':') {
299 *sys++ = *path++;
300 }
301 *sys = '\0';
302 path++;
303 }
304 #ifdef COMPAT
305 else if (*(path - 1) == '.') {
306 while (*path != ':') {
307 *user++ = *path++;
308 }
309 *user = '\0';
310 path++;
311 }
312 #endif
313 else
314 *user = '\0';
315
316 while (*path) {
317 *dev++ = *path++;
318 }
319 *dev = '\0';
320
321 #ifdef USE_REXEC
322 /*
323 * Execute the remote command using rexec
324 */
325 READ(i) = WRITE(i) = _rmt_rexec(host, login);
326 if (READ(i) < 0)
327 return -1;
328 #else
329 /*
330 * setup the pipes for the 'rsh' command and fork
331 */
332
333 if (pipe(Ptc[i]) == -1 || pipe(Ctp[i]) == -1)
334 return -1;
335
336 switch (fork()) {
337 case -1:
338 return -1;
339
340 case 0:
341 close(0);
342 dup(Ptc[i][0]);
343 close(Ptc[i][0]); close(Ptc[i][1]);
344 close(1);
345 dup(Ctp[i][1]);
346 close(Ctp[i][0]); close(Ctp[i][1]);
347 (void) setuid(getuid());
348 (void) setgid(getgid());
349
350 if ((rshpath = getenv("RCMD_CMD")) == NULL)
351 rshpath = _PATH_RSH;
352 if ((rsh = strrchr(rshpath, '/')) == NULL)
353 rsh = rshpath;
354 else
355 rsh++;
356
357 if (*login) {
358 execl(rshpath, rsh, host, "-l", login, _PATH_RMT, NULL);
359 } else {
360 execl(rshpath, rsh, host, _PATH_RMT, NULL);
361 }
362
363 /*
364 * bad problems if we get here
365 */
366
367 err(1, "Cannnot exec %s", rshpath);
368 /*FALLTHROUGH*/
369 default:
370 break;
371 }
372
373 close(Ptc[i][0]); close(Ctp[i][1]);
374 #endif
375
376 /*
377 * now attempt to open the tape device
378 */
379
380 (void)snprintf(buffer, sizeof(buffer), "O%s\n%d\n", device, oflag);
381 if (command(i, buffer) == -1 || status(i) == -1)
382 return -1;
383
384 return i;
385 }
386
387
388 /*
389 * _rmt_close --- close a remote magtape unit and shut down
390 */
391 static int
_rmt_close(int fildes)392 _rmt_close(int fildes)
393 {
394 int rc;
395
396 if (command(fildes, "C\n") != -1) {
397 rc = status(fildes);
398
399 rmtabort(fildes);
400 return rc;
401 }
402
403 return -1;
404 }
405
406
407 /*
408 * _rmt_read --- read a buffer from a remote tape
409 */
410 static ssize_t
_rmt_read(int fildes,void * buf,size_t nbyte)411 _rmt_read(int fildes, void *buf, size_t nbyte)
412 {
413 size_t rc;
414 int rv;
415 ssize_t nread;
416 char *p;
417 char buffer[BUFMAGIC];
418
419 _DIAGASSERT(buf != NULL);
420
421 (void)snprintf(buffer, sizeof buffer, "R%zu\n", nbyte);
422 if (command(fildes, buffer) == -1 || (rv = status(fildes)) == -1)
423 return -1;
424
425 if (rv > (int)nbyte)
426 rv = (int)nbyte;
427
428 for (rc = rv, p = buf; rc > 0; rc -= nread, p += nread) {
429 if ((nread = read(READ(fildes), p, rc)) <= 0) {
430 rmtabort(fildes);
431 errno = EIO;
432 return -1;
433 }
434 }
435
436 return rv;
437 }
438
439
440 /*
441 * _rmt_write --- write a buffer to the remote tape
442 */
443 static ssize_t
_rmt_write(int fildes,const void * buf,size_t nbyte)444 _rmt_write(int fildes, const void *buf, size_t nbyte)
445 {
446 char buffer[BUFMAGIC];
447 sig_t pstat;
448
449 _DIAGASSERT(buf != NULL);
450
451 (void)snprintf(buffer, sizeof buffer, "W%zu\n", nbyte);
452 if (command(fildes, buffer) == -1)
453 return -1;
454
455 pstat = signal(SIGPIPE, SIG_IGN);
456 if ((size_t)write(WRITE(fildes), buf, nbyte) == nbyte) {
457 signal(SIGPIPE, pstat);
458 return status(fildes);
459 }
460
461 signal(SIGPIPE, pstat);
462 rmtabort(fildes);
463 errno = EIO;
464 return -1;
465 }
466
467
468 /*
469 * _rmt_lseek --- perform an imitation lseek operation remotely
470 */
471 static off_t
_rmt_lseek(int fildes,off_t offset,int whence)472 _rmt_lseek(int fildes, off_t offset, int whence)
473 {
474 char buffer[BUFMAGIC];
475
476 /*LONGLONG*/
477 (void)snprintf(buffer, sizeof buffer, "L%lld\n%d\n", (long long)offset,
478 whence);
479 if (command(fildes, buffer) == -1)
480 return -1;
481
482 return status(fildes);
483 }
484
485
486 /*
487 * _rmt_ioctl --- perform raw tape operations remotely
488 */
489 #ifdef RMTIOCTL
490 static int
_rmt_ioctl(int fildes,unsigned long op,void * arg)491 _rmt_ioctl(int fildes, unsigned long op, void *arg)
492 {
493 char c;
494 int rv;
495 size_t rc;
496 ssize_t cnt;
497 char buffer[BUFMAGIC], *p;
498 struct mtop *mtop = arg;
499
500 _DIAGASSERT(arg != NULL);
501
502 /*
503 * MTIOCOP is the easy one. nothing is transfered in binary
504 */
505
506 if (op == MTIOCTOP) {
507 (void)snprintf(buffer, sizeof buffer, "I%d\n%d\n",
508 mtop->mt_op, mtop->mt_count);
509 if (command(fildes, buffer) == -1)
510 return -1;
511 return status(fildes);
512 }
513
514 /*
515 * we can only handle 2 ops, if not the other one, punt
516 */
517
518 if (op != MTIOCGET) {
519 errno = EINVAL;
520 return -1;
521 }
522
523 /*
524 * grab the status and read it directly into the structure
525 * this assumes that the status buffer is (hopefully) not
526 * padded and that 2 shorts fit in a long without any word
527 * alignment problems, ie - the whole struct is contiguous
528 * NOTE - this is probably NOT a good assumption.
529 */
530
531 if (command(fildes, "S") == -1 || (rv = status(fildes)) == -1)
532 return -1;
533
534 memset(arg, 0, sizeof(struct mtget));
535 for (rc = rv, p = arg; rc > 0; rc -= cnt, p += cnt) {
536 if ((cnt = read(READ(fildes), p, rc)) <= 0) {
537 rmtabort(fildes);
538 errno = EIO;
539 return -1;
540 }
541 }
542
543 /*
544 * now we check for byte position. mt_type is a small integer field
545 * (normally) so we will check its magnitude. if it is larger than
546 * 256, we will assume that the bytes are swapped and go through
547 * and reverse all the bytes
548 */
549
550 if (((struct mtget *)(void *)p)->mt_type < 256)
551 return 0;
552
553 for (cnt = 0; cnt < rv; cnt += 2) {
554 c = p[cnt];
555 p[cnt] = p[cnt + 1];
556 p[cnt + 1] = c;
557 }
558
559 return 0;
560 }
561 #endif /* RMTIOCTL */
562
563
564 /*
565 * Added routines to replace open(), close(), lseek(), ioctl(), etc.
566 * The preprocessor can be used to remap these the rmtopen(), etc
567 * thus minimizing source changes:
568 *
569 * #ifdef <something>
570 * # define access rmtaccess
571 * # define close rmtclose
572 * # define creat rmtcreat
573 * # define dup rmtdup
574 * # define fcntl rmtfcntl
575 * # define fstat rmtfstat
576 * # define ioctl rmtioctl
577 * # define isatty rmtisatty
578 * # define lseek rmtlseek
579 * # define lstat rmtlstat
580 * # define open rmtopen
581 * # define read rmtread
582 * # define stat rmtstat
583 * # define write rmtwrite
584 * #endif
585 *
586 * -- Fred Fish
587 *
588 * ADR --- I set up a <rmt.h> include file for this
589 *
590 */
591
592 /*
593 * Note that local vs remote file descriptors are distinquished
594 * by adding a bias to the remote descriptors. This is a quick
595 * and dirty trick that may not be portable to some systems.
596 */
597
598 #define REM_BIAS 128
599
600
601 /*
602 * Test pathname to see if it is local or remote. A remote device
603 * is any string that contains ":/dev/". Returns 1 if remote,
604 * 0 otherwise.
605 */
606
607 static int
remdev(const char * path)608 remdev(const char *path)
609 {
610
611 _DIAGASSERT(path != NULL);
612
613 if ((path = strchr(path, ':')) != NULL) {
614 if (strncmp(path + 1, "/dev/", 5) == 0) {
615 return 1;
616 }
617 }
618 return 0;
619 }
620
621
622 /*
623 * Open a local or remote file. Looks just like open(2) to
624 * caller.
625 */
626 int
rmtopen(const char * path,int oflag,...)627 rmtopen(const char *path, int oflag, ...)
628 {
629 mode_t mode;
630 int fd;
631 va_list ap;
632 va_start(ap, oflag);
633
634 mode = va_arg(ap, mode_t);
635 va_end(ap);
636
637 _DIAGASSERT(path != NULL);
638
639 if (remdev(path)) {
640 fd = _rmt_open(path, oflag, (int)mode);
641
642 return (fd == -1) ? -1 : (fd + REM_BIAS);
643 } else {
644 return open(path, oflag, mode);
645 }
646 }
647
648 /*
649 * Test pathname for specified access. Looks just like access(2)
650 * to caller.
651 */
652
653 int
rmtaccess(const char * path,int amode)654 rmtaccess(const char *path, int amode)
655 {
656
657 _DIAGASSERT(path != NULL);
658
659 if (remdev(path)) {
660 return 0; /* Let /etc/rmt find out */
661 } else {
662 return access(path, amode);
663 }
664 }
665
666
667 /*
668 * Isrmt. Let a programmer know he has a remote device.
669 */
670 int
isrmt(int fd)671 isrmt(int fd)
672 {
673 int unbias = fd - REM_BIAS;
674
675 return (fd >= REM_BIAS) && unbias < MAXUNIT &&
676 (WRITE(unbias) != -1 || READ(unbias) != -1);
677 }
678
679
680 /*
681 * Read from stream. Looks just like read(2) to caller.
682 */
683 ssize_t
rmtread(int fildes,void * buf,size_t nbyte)684 rmtread(int fildes, void *buf, size_t nbyte)
685 {
686
687 _DIAGASSERT(buf != NULL);
688
689 if (isrmt(fildes)) {
690 return _rmt_read(fildes - REM_BIAS, buf, nbyte);
691 } else {
692 return read(fildes, buf, nbyte);
693 }
694 }
695
696
697 /*
698 * Write to stream. Looks just like write(2) to caller.
699 */
700 ssize_t
rmtwrite(int fildes,const void * buf,size_t nbyte)701 rmtwrite(int fildes, const void *buf, size_t nbyte)
702 {
703
704 _DIAGASSERT(buf != NULL);
705
706 if (isrmt(fildes)) {
707 return _rmt_write(fildes - REM_BIAS, buf, nbyte);
708 } else {
709 return write(fildes, buf, nbyte);
710 }
711 }
712
713 /*
714 * Perform lseek on file. Looks just like lseek(2) to caller.
715 */
716 off_t
rmtlseek(int fildes,off_t offset,int whence)717 rmtlseek(int fildes, off_t offset, int whence)
718 {
719
720 if (isrmt(fildes)) {
721 return _rmt_lseek(fildes - REM_BIAS, offset, whence);
722 } else {
723 return lseek(fildes, offset, whence);
724 }
725 }
726
727
728 /*
729 * Close a file. Looks just like close(2) to caller.
730 */
731 int
rmtclose(int fildes)732 rmtclose(int fildes)
733 {
734
735 if (isrmt(fildes)) {
736 return _rmt_close(fildes - REM_BIAS);
737 } else {
738 return close(fildes);
739 }
740 }
741
742
743 /*
744 * Do ioctl on file. Looks just like ioctl(2) to caller.
745 */
746 int
rmtioctl(int fildes,unsigned long request,...)747 rmtioctl(int fildes, unsigned long request, ...)
748 {
749 void *arg;
750 va_list ap;
751 va_start(ap, request);
752
753 arg = va_arg(ap, void *);
754 va_end(ap);
755
756 /* XXX: arg may be NULL ? */
757
758 if (isrmt(fildes)) {
759 #ifdef RMTIOCTL
760 return _rmt_ioctl(fildes - REM_BIAS, request, arg);
761 #else
762 errno = EOPNOTSUPP;
763 return -1; /* For now (fnf) */
764 #endif
765 } else {
766 return ioctl(fildes, request, arg);
767 }
768 }
769
770
771 /*
772 * Duplicate an open file descriptor. Looks just like dup(2)
773 * to caller.
774 */
775 int
rmtdup(int fildes)776 rmtdup(int fildes)
777 {
778
779 if (isrmt(fildes)) {
780 errno = EOPNOTSUPP;
781 return -1; /* For now (fnf) */
782 } else {
783 return dup(fildes);
784 }
785 }
786
787
788 /*
789 * Get file status. Looks just like fstat(2) to caller.
790 */
791 int
rmtfstat(int fildes,struct stat * buf)792 rmtfstat(int fildes, struct stat *buf)
793 {
794
795 _DIAGASSERT(buf != NULL);
796
797 if (isrmt(fildes)) {
798 errno = EOPNOTSUPP;
799 return -1; /* For now (fnf) */
800 } else {
801 return fstat(fildes, buf);
802 }
803 }
804
805
806 /*
807 * Get file status. Looks just like stat(2) to caller.
808 */
809 int
rmtstat(const char * path,struct stat * buf)810 rmtstat(const char *path, struct stat *buf)
811 {
812
813 _DIAGASSERT(path != NULL);
814 _DIAGASSERT(buf != NULL);
815
816 if (remdev(path)) {
817 errno = EOPNOTSUPP;
818 return -1; /* For now (fnf) */
819 } else {
820 return stat(path, buf);
821 }
822 }
823
824
825 /*
826 * Create a file from scratch. Looks just like creat(2) to the caller.
827 */
828 int
rmtcreat(const char * path,mode_t mode)829 rmtcreat(const char *path, mode_t mode)
830 {
831
832 _DIAGASSERT(path != NULL);
833
834 if (remdev(path)) {
835 return rmtopen(path, O_WRONLY | O_CREAT, mode);
836 } else {
837 return open(path, O_CREAT | O_TRUNC | O_WRONLY, mode);
838 }
839 }
840
841
842 /*
843 * Rmtfcntl. Do a remote fcntl operation.
844 */
845 int
rmtfcntl(int fd,int cmd,...)846 rmtfcntl(int fd, int cmd, ...)
847 {
848 void *arg;
849 va_list ap;
850 va_start(ap, cmd);
851
852 arg = va_arg(ap, void *);
853 va_end(ap);
854
855 /* XXX: arg may be NULL ? */
856
857 if (isrmt(fd)) {
858 errno = EOPNOTSUPP;
859 return -1;
860 } else {
861 return fcntl(fd, cmd, arg);
862 }
863 }
864
865
866 /*
867 * Rmtisatty. Do the isatty function.
868 */
869 int
rmtisatty(int fd)870 rmtisatty(int fd)
871 {
872
873 if (isrmt(fd))
874 return 0;
875 else
876 return isatty(fd);
877 }
878
879
880 /*
881 * Get file status, even if symlink. Looks just like lstat(2) to caller.
882 */
883 int
rmtlstat(const char * path,struct stat * buf)884 rmtlstat(const char *path, struct stat *buf)
885 {
886
887 _DIAGASSERT(path != NULL);
888 _DIAGASSERT(buf != NULL);
889
890 if (remdev(path)) {
891 errno = EOPNOTSUPP;
892 return -1; /* For now (fnf) */
893 } else {
894 return lstat(path, buf);
895 }
896 }
897