1 /* $NetBSD: rmtlib.c,v 1.18 2001/11/05 15:10:25 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 (rexecserv == NULL) { 225 fprintf(stderr, "? exec/tcp: service not available."); 226 exit(1); 227 } 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 _rmt_open(const char *path, int oflag, int mode) 249 { 250 int i, rc; 251 char buffer[BUFMAGIC]; 252 char host[MAXHOSTLEN]; 253 char device[BUFMAGIC]; 254 char login[BUFMAGIC]; 255 char *sys, *dev, *user; 256 257 _DIAGASSERT(path != NULL); 258 259 sys = host; 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, host, sizeof(login) - 1); 294 /* saw user part of user@host */ 295 sys = host; /* 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(host, 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, host, "-l", login, 358 _PATH_RMT, NULL); 359 } else { 360 execl(rshpath, rsh, host, 361 _PATH_RMT, NULL); 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%lu\n", (u_long)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%lu\n", (u_long)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 712 if (isrmt(fildes)) { 713 return (_rmt_lseek(fildes - REM_BIAS, offset, whence)); 714 } else { 715 return (lseek(fildes, offset, whence)); 716 } 717 } 718 719 720 /* 721 * Close a file. Looks just like close(2) to caller. 722 */ 723 int 724 rmtclose(int fildes) 725 { 726 727 if (isrmt(fildes)) { 728 return (_rmt_close(fildes - REM_BIAS)); 729 } else { 730 return (close(fildes)); 731 } 732 } 733 734 735 /* 736 * Do ioctl on file. Looks just like ioctl(2) to caller. 737 */ 738 int 739 rmtioctl(int fildes, unsigned long request, ...) 740 { 741 char *arg; 742 va_list ap; 743 va_start(ap, request); 744 745 arg = va_arg(ap, char *); 746 va_end(ap); 747 748 /* XXX: arg may be NULL ? */ 749 750 if (isrmt(fildes)) { 751 #ifdef RMTIOCTL 752 return (_rmt_ioctl(fildes - REM_BIAS, request, arg)); 753 #else 754 errno = EOPNOTSUPP; 755 return (-1); /* For now (fnf) */ 756 #endif 757 } else { 758 return (ioctl(fildes, request, arg)); 759 } 760 } 761 762 763 /* 764 * Duplicate an open file descriptor. Looks just like dup(2) 765 * to caller. 766 */ 767 int 768 rmtdup(int fildes) 769 { 770 771 if (isrmt(fildes)) { 772 errno = EOPNOTSUPP; 773 return (-1); /* For now (fnf) */ 774 } else { 775 return (dup(fildes)); 776 } 777 } 778 779 780 /* 781 * Get file status. Looks just like fstat(2) to caller. 782 */ 783 int 784 rmtfstat(int fildes, struct stat *buf) 785 { 786 787 _DIAGASSERT(buf != NULL); 788 789 if (isrmt(fildes)) { 790 errno = EOPNOTSUPP; 791 return (-1); /* For now (fnf) */ 792 } else { 793 return (fstat(fildes, buf)); 794 } 795 } 796 797 798 /* 799 * Get file status. Looks just like stat(2) to caller. 800 */ 801 int 802 rmtstat(const char *path, struct stat *buf) 803 { 804 805 _DIAGASSERT(path != NULL); 806 _DIAGASSERT(buf != NULL); 807 808 if (remdev(path)) { 809 errno = EOPNOTSUPP; 810 return (-1); /* For now (fnf) */ 811 } else { 812 return (stat(path, buf)); 813 } 814 } 815 816 817 /* 818 * Create a file from scratch. Looks just like creat(2) to the caller. 819 */ 820 int 821 rmtcreat(const char *path, mode_t mode) 822 { 823 824 _DIAGASSERT(path != NULL); 825 826 if (remdev(path)) { 827 return (rmtopen(path, 1 | O_CREAT, mode)); 828 } else { 829 return (creat(path, mode)); 830 } 831 } 832 833 834 /* 835 * Rmtfcntl. Do a remote fcntl operation. 836 */ 837 int 838 rmtfcntl(int fd, int cmd, ...) 839 { 840 void *arg; 841 va_list ap; 842 va_start(ap, cmd); 843 844 arg = va_arg(ap, void *); 845 va_end(ap); 846 847 /* XXX: arg may be NULL ? */ 848 849 if (isrmt(fd)) { 850 errno = EOPNOTSUPP; 851 return (-1); 852 } else { 853 return (fcntl(fd, cmd, arg)); 854 } 855 } 856 857 858 /* 859 * Rmtisatty. Do the isatty function. 860 */ 861 int 862 rmtisatty(int fd) 863 { 864 865 if (isrmt(fd)) 866 return (0); 867 else 868 return (isatty(fd)); 869 } 870 871 872 /* 873 * Get file status, even if symlink. Looks just like lstat(2) to caller. 874 */ 875 int 876 rmtlstat(const char *path, struct stat *buf) 877 { 878 879 _DIAGASSERT(path != NULL); 880 _DIAGASSERT(buf != NULL); 881 882 if (remdev(path)) { 883 errno = EOPNOTSUPP; 884 return (-1); /* For now (fnf) */ 885 } else { 886 return (lstat(path, buf)); 887 } 888 } 889