rmtlib.c revision 1.20 1 /* $NetBSD: rmtlib.c,v 1.20 2005/12/05 02:04:16 christos 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.20 2005/12/05 02:04:16 christos 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
54 #ifdef USE_REXEC
55 #include <netdb.h>
56 #endif
57
58 #define __RMTLIB_PRIVATE
59 #include <rmt.h> /* get prototypes for remapped functions */
60
61 #include "pathnames.h"
62
63 static int _rmt_close(int);
64 static int _rmt_ioctl(int, unsigned long, void *);
65 static off_t _rmt_lseek(int, off_t, int);
66 static int _rmt_open(const char *, int, int);
67 static ssize_t _rmt_read(int, void *, size_t);
68 static ssize_t _rmt_write(int, const void *, size_t);
69 static int command(int, char *);
70 static int remdev(const char *);
71 static void rmtabort(int);
72 static int status(int);
73
74 int isrmt(int);
75
76
77 #define BUFMAGIC 64 /* a magic number for buffer sizes */
78 #define MAXUNIT 4
79
80 #define READ(fd) (Ctp[fd][0])
81 #define WRITE(fd) (Ptc[fd][1])
82
83 static int Ctp[MAXUNIT][2] = { {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1} };
84 static int Ptc[MAXUNIT][2] = { {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1} };
85
86
87 /*
88 * rmtabort --- close off a remote tape connection
89 */
90 static void
91 rmtabort(int fildes)
92 {
93
94 close(READ(fildes));
95 close(WRITE(fildes));
96 READ(fildes) = -1;
97 WRITE(fildes) = -1;
98 }
99
100
101 /*
102 * command --- attempt to perform a remote tape command
103 */
104 static int
105 command(int fildes, char *buf)
106 {
107 size_t blen;
108 void (*pstat)(int);
109
110 _DIAGASSERT(buf != NULL);
111
112 /*
113 * save current pipe status and try to make the request
114 */
115
116 blen = strlen(buf);
117 pstat = signal(SIGPIPE, SIG_IGN);
118 if (write(WRITE(fildes), buf, blen) == blen) {
119 signal(SIGPIPE, pstat);
120 return (0);
121 }
122
123 /*
124 * something went wrong. close down and go home
125 */
126
127 signal(SIGPIPE, pstat);
128 rmtabort(fildes);
129
130 errno = EIO;
131 return (-1);
132 }
133
134
135 /*
136 * status --- retrieve the status from the pipe
137 */
138 static int
139 status(int fildes)
140 {
141 int i;
142 char c, *cp;
143 char buffer[BUFMAGIC];
144
145 /*
146 * read the reply command line
147 */
148
149 for (i = 0, cp = buffer; i < BUFMAGIC; i++, cp++) {
150 if (read(READ(fildes), cp, 1) != 1) {
151 rmtabort(fildes);
152 errno = EIO;
153 return (-1);
154 }
155 if (*cp == '\n') {
156 *cp = 0;
157 break;
158 }
159 }
160
161 if (i == BUFMAGIC) {
162 rmtabort(fildes);
163 errno = EIO;
164 return (-1);
165 }
166
167 /*
168 * check the return status
169 */
170
171 for (cp = buffer; *cp; cp++)
172 if (*cp != ' ')
173 break;
174
175 if (*cp == 'E' || *cp == 'F') {
176 errno = atoi(cp + 1);
177 while (read(READ(fildes), &c, 1) == 1)
178 if (c == '\n')
179 break;
180
181 if (*cp == 'F')
182 rmtabort(fildes);
183
184 return (-1);
185 }
186
187 /*
188 * check for mis-synced pipes
189 */
190
191 if (*cp != 'A') {
192 rmtabort(fildes);
193 errno = EIO;
194 return (-1);
195 }
196
197 return (atoi(cp + 1));
198 }
199
200
201 #ifdef USE_REXEC
202 /*
203 * _rmt_rexec
204 *
205 * execute /etc/rmt on a remote system using rexec().
206 * Return file descriptor of bidirectional socket for stdin and stdout
207 * If username is NULL, or an empty string, uses current username.
208 *
209 * ADR: By default, this code is not used, since it requires that
210 * the user have a .netrc file in his/her home directory, or that the
211 * application designer be willing to have rexec prompt for login and
212 * password info. This may be unacceptable, and .rhosts files for use
213 * with rsh are much more common on BSD systems.
214 */
215
216 static int _rmt_rexec(const char *, const char *);
217
218 static int
219 _rmt_rexec(const char *host, const char *user)
220 {
221 struct servent *rexecserv;
222
223 _DIAGASSERT(host != NULL);
224 /* user may be NULL */
225
226 rexecserv = getservbyname("exec", "tcp");
227 if (rexecserv == NULL) {
228 fprintf(stderr, "? exec/tcp: service not available.");
229 exit(1);
230 }
231 if ((user != NULL) && *user == '\0')
232 user = NULL;
233 return (rexec(&host, rexecserv->s_port, user, NULL,
234 "/etc/rmt", NULL));
235 }
236 #endif /* USE_REXEC */
237
238
239 /*
240 * _rmt_open --- open a magtape device on system specified, as given user
241 *
242 * file name has the form [user@]system:/dev/????
243 #ifdef COMPAT
244 * file name has the form system[.user]:/dev/????
245 #endif
246 */
247
248 #define MAXHOSTLEN 257 /* BSD allows very long host names... */
249
250 static int
251 /*ARGSUSED*/
252 _rmt_open(const char *path, int oflag, int mode)
253 {
254 int i, rc;
255 char buffer[BUFMAGIC];
256 char host[MAXHOSTLEN];
257 char device[BUFMAGIC];
258 char login[BUFMAGIC];
259 char *sys, *dev, *user;
260
261 _DIAGASSERT(path != NULL);
262
263 sys = host;
264 dev = device;
265 user = login;
266
267 /*
268 * first, find an open pair of file descriptors
269 */
270
271 for (i = 0; i < MAXUNIT; i++)
272 if (READ(i) == -1 && WRITE(i) == -1)
273 break;
274
275 if (i == MAXUNIT) {
276 errno = EMFILE;
277 return (-1);
278 }
279
280 /*
281 * pull apart system and device, and optional user
282 * don't munge original string
283 * if COMPAT is defined, also handle old (4.2) style person.site notation.
284 */
285
286 while (*path != '@'
287 #ifdef COMPAT
288 && *path != '.'
289 #endif
290 && *path != ':') {
291 *sys++ = *path++;
292 }
293 *sys = '\0';
294 path++;
295
296 if (*(path - 1) == '@') {
297 (void)strncpy(user, host, sizeof(login) - 1);
298 /* saw user part of user@host */
299 sys = host; /* start over */
300 while (*path != ':') {
301 *sys++ = *path++;
302 }
303 *sys = '\0';
304 path++;
305 }
306 #ifdef COMPAT
307 else if (*(path - 1) == '.') {
308 while (*path != ':') {
309 *user++ = *path++;
310 }
311 *user = '\0';
312 path++;
313 }
314 #endif
315 else
316 *user = '\0';
317
318 while (*path) {
319 *dev++ = *path++;
320 }
321 *dev = '\0';
322
323 #ifdef USE_REXEC
324 /*
325 * Execute the remote command using rexec
326 */
327 READ(i) = WRITE(i) = _rmt_rexec(host, login);
328 if (READ(i) < 0)
329 return (-1);
330 #else
331 /*
332 * setup the pipes for the 'rsh' command and fork
333 */
334
335 if (pipe(Ptc[i]) == -1 || pipe(Ctp[i]) == -1)
336 return (-1);
337
338 if ((rc = fork()) == -1)
339 return (-1);
340
341 if (rc == 0) {
342 char *rshpath, *rsh;
343
344 close(0);
345 dup(Ptc[i][0]);
346 close(Ptc[i][0]); close(Ptc[i][1]);
347 close(1);
348 dup(Ctp[i][1]);
349 close(Ctp[i][0]); close(Ctp[i][1]);
350 (void) setuid(getuid());
351 (void) setgid(getgid());
352
353 if ((rshpath = getenv("RCMD_CMD")) == NULL)
354 rshpath = _PATH_RSH;
355 if ((rsh = strrchr(rshpath, '/')) == NULL)
356 rsh = rshpath;
357 else
358 rsh++;
359
360 if (*login) {
361 execl(rshpath, rsh, host, "-l", login,
362 _PATH_RMT, NULL);
363 } else {
364 execl(rshpath, rsh, host,
365 _PATH_RMT, NULL);
366 }
367
368 /*
369 * bad problems if we get here
370 */
371
372 perror("exec");
373 exit(1);
374 }
375
376 close(Ptc[i][0]); close(Ctp[i][1]);
377 #endif
378
379 /*
380 * now attempt to open the tape device
381 */
382
383 (void)snprintf(buffer, sizeof(buffer), "O%s\n%d\n", device, oflag);
384 if (command(i, buffer) == -1 || status(i) == -1)
385 return (-1);
386
387 return (i);
388 }
389
390
391 /*
392 * _rmt_close --- close a remote magtape unit and shut down
393 */
394 static int
395 _rmt_close(int fildes)
396 {
397 int rc;
398
399 if (command(fildes, "C\n") != -1) {
400 rc = status(fildes);
401
402 rmtabort(fildes);
403 return (rc);
404 }
405
406 return (-1);
407 }
408
409
410 /*
411 * _rmt_read --- read a buffer from a remote tape
412 */
413 static ssize_t
414 _rmt_read(int fildes, void *buf, size_t nbyte)
415 {
416 size_t rc;
417 int rv;
418 ssize_t nread;
419 char *p;
420 char buffer[BUFMAGIC];
421
422 _DIAGASSERT(buf != NULL);
423
424 (void)snprintf(buffer, sizeof buffer, "R%lu\n", (u_long)nbyte);
425 if (command(fildes, buffer) == -1 || (rv = status(fildes)) == -1)
426 return (-1);
427
428 if (rv > nbyte)
429 rv = nbyte;
430
431 for (rc = rv, p = buf; rc > 0; rc -= nread, p += nread) {
432 if ((nread = read(READ(fildes), p, rc)) <= 0) {
433 rmtabort(fildes);
434 errno = EIO;
435 return (-1);
436 }
437 }
438
439 return rv;
440 }
441
442
443 /*
444 * _rmt_write --- write a buffer to the remote tape
445 */
446 static ssize_t
447 _rmt_write(int fildes, const void *buf, size_t nbyte)
448 {
449 char buffer[BUFMAGIC];
450 void (*pstat)(int);
451
452 _DIAGASSERT(buf != NULL);
453
454 (void)snprintf(buffer, sizeof buffer, "W%lu\n", (u_long)nbyte);
455 if (command(fildes, buffer) == -1)
456 return (-1);
457
458 pstat = signal(SIGPIPE, SIG_IGN);
459 if (write(WRITE(fildes), buf, nbyte) == nbyte) {
460 signal(SIGPIPE, pstat);
461 return (status(fildes));
462 }
463
464 signal(SIGPIPE, pstat);
465 rmtabort(fildes);
466 errno = EIO;
467 return (-1);
468 }
469
470
471 /*
472 * _rmt_lseek --- perform an imitation lseek operation remotely
473 */
474 static off_t
475 _rmt_lseek(int fildes, off_t offset, int whence)
476 {
477 char buffer[BUFMAGIC];
478
479 /*LONGLONG*/
480 (void)snprintf(buffer, sizeof buffer, "L%lld\n%d\n", (long long)offset,
481 whence);
482 if (command(fildes, buffer) == -1)
483 return (-1);
484
485 return (status(fildes));
486 }
487
488
489 /*
490 * _rmt_ioctl --- perform raw tape operations remotely
491 */
492 #ifdef RMTIOCTL
493 static int
494 _rmt_ioctl(int fildes, unsigned long op, void *arg)
495 {
496 char c;
497 int rv;
498 size_t rc;
499 ssize_t cnt;
500 char buffer[BUFMAGIC], *p;
501
502 _DIAGASSERT(arg != NULL);
503
504 /*
505 * MTIOCOP is the easy one. nothing is transfered in binary
506 */
507
508 if (op == MTIOCTOP) {
509 (void)snprintf(buffer, sizeof buffer, "I%d\n%d\n",
510 ((struct mtop *)arg)->mt_op,
511 ((struct mtop *)arg)->mt_count);
512 if (command(fildes, buffer) == -1)
513 return (-1);
514 return (status(fildes));
515 }
516
517 /*
518 * we can only handle 2 ops, if not the other one, punt
519 */
520
521 if (op != MTIOCGET) {
522 errno = EINVAL;
523 return (-1);
524 }
525
526 /*
527 * grab the status and read it directly into the structure
528 * this assumes that the status buffer is (hopefully) not
529 * padded and that 2 shorts fit in a long without any word
530 * alignment problems, ie - the whole struct is contiguous
531 * NOTE - this is probably NOT a good assumption.
532 */
533
534 if (command(fildes, "S") == -1 || (rv = status(fildes)) == -1)
535 return (-1);
536
537 for (rc = rv, p = arg; rc > 0; rc -= cnt, p += cnt) {
538 if ((cnt = read(READ(fildes), p, rc)) <= 0) {
539 rmtabort(fildes);
540 errno = EIO;
541 return (-1);
542 }
543 }
544
545 /*
546 * now we check for byte position. mt_type is a small integer field
547 * (normally) so we will check its magnitude. if it is larger than
548 * 256, we will assume that the bytes are swapped and go through
549 * and reverse all the bytes
550 */
551
552 if (((struct mtget *)(void *)p)->mt_type < 256)
553 return (0);
554
555 for (cnt = 0; cnt < rc; cnt += 2) {
556 c = p[cnt];
557 p[cnt] = p[cnt+1];
558 p[cnt+1] = c;
559 }
560
561 return (0);
562 }
563 #endif /* RMTIOCTL */
564
565
566 /*
567 * Added routines to replace open(), close(), lseek(), ioctl(), etc.
568 * The preprocessor can be used to remap these the rmtopen(), etc
569 * thus minimizing source changes:
570 *
571 * #ifdef <something>
572 * # define access rmtaccess
573 * # define close rmtclose
574 * # define creat rmtcreat
575 * # define dup rmtdup
576 * # define fcntl rmtfcntl
577 * # define fstat rmtfstat
578 * # define ioctl rmtioctl
579 * # define isatty rmtisatty
580 * # define lseek rmtlseek
581 * # define lstat rmtlstat
582 * # define open rmtopen
583 * # define read rmtread
584 * # define stat rmtstat
585 * # define write rmtwrite
586 * #endif
587 *
588 * -- Fred Fish
589 *
590 * ADR --- I set up a <rmt.h> include file for this
591 *
592 */
593
594 /*
595 * Note that local vs remote file descriptors are distinquished
596 * by adding a bias to the remote descriptors. This is a quick
597 * and dirty trick that may not be portable to some systems.
598 */
599
600 #define REM_BIAS 128
601
602
603 /*
604 * Test pathname to see if it is local or remote. A remote device
605 * is any string that contains ":/dev/". Returns 1 if remote,
606 * 0 otherwise.
607 */
608
609 static int
610 remdev(const char *path)
611 {
612
613 _DIAGASSERT(path != NULL);
614
615 if ((path = strchr(path, ':')) != NULL) {
616 if (strncmp(path + 1, "/dev/", 5) == 0) {
617 return (1);
618 }
619 }
620 return (0);
621 }
622
623
624 /*
625 * Open a local or remote file. Looks just like open(2) to
626 * caller.
627 */
628 int
629 rmtopen(const char *path, int oflag, ...)
630 {
631 mode_t mode;
632 int fd;
633 va_list ap;
634 va_start(ap, oflag);
635
636 mode = va_arg(ap, mode_t);
637 va_end(ap);
638
639 _DIAGASSERT(path != NULL);
640
641 if (remdev(path)) {
642 fd = _rmt_open(path, oflag, (int)mode);
643
644 return ((fd == -1) ? -1 : (fd + REM_BIAS));
645 } else {
646 return (open(path, oflag, mode));
647 }
648 }
649
650 /*
651 * Test pathname for specified access. Looks just like access(2)
652 * to caller.
653 */
654
655 int
656 rmtaccess(const char *path, int amode)
657 {
658
659 _DIAGASSERT(path != NULL);
660
661 if (remdev(path)) {
662 return (0); /* Let /etc/rmt find out */
663 } else {
664 return (access(path, amode));
665 }
666 }
667
668
669 /*
670 * Isrmt. Let a programmer know he has a remote device.
671 */
672 int
673 isrmt(int fd)
674 {
675
676 return (fd >= REM_BIAS);
677 }
678
679
680 /*
681 * Read from stream. Looks just like read(2) to caller.
682 */
683 ssize_t
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
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
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
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
747 rmtioctl(int fildes, unsigned long request, ...)
748 {
749 char *arg;
750 va_list ap;
751 va_start(ap, request);
752
753 arg = va_arg(ap, char *);
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
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
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
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
829 rmtcreat(const char *path, mode_t mode)
830 {
831
832 _DIAGASSERT(path != NULL);
833
834 if (remdev(path)) {
835 return (rmtopen(path, 1 | O_CREAT, mode));
836 } else {
837 return (creat(path, mode));
838 }
839 }
840
841
842 /*
843 * Rmtfcntl. Do a remote fcntl operation.
844 */
845 int
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
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
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