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