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