1 /* $NetBSD: rmtlib.c,v 1.19 2003/03/08 07:47:49 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 #include <sys/cdefs.h> 31 __RCSID("$NetBSD: rmtlib.c,v 1.19 2003/03/08 07:47:49 lukem 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 _rmt_open(const char *path, int oflag, int mode) 252 { 253 int i, rc; 254 char buffer[BUFMAGIC]; 255 char host[MAXHOSTLEN]; 256 char device[BUFMAGIC]; 257 char login[BUFMAGIC]; 258 char *sys, *dev, *user; 259 260 _DIAGASSERT(path != NULL); 261 262 sys = host; 263 dev = device; 264 user = login; 265 266 /* 267 * first, find an open pair of file descriptors 268 */ 269 270 for (i = 0; i < MAXUNIT; i++) 271 if (READ(i) == -1 && WRITE(i) == -1) 272 break; 273 274 if (i == MAXUNIT) { 275 errno = EMFILE; 276 return (-1); 277 } 278 279 /* 280 * pull apart system and device, and optional user 281 * don't munge original string 282 * if COMPAT is defined, also handle old (4.2) style person.site notation. 283 */ 284 285 while (*path != '@' 286 #ifdef COMPAT 287 && *path != '.' 288 #endif 289 && *path != ':') { 290 *sys++ = *path++; 291 } 292 *sys = '\0'; 293 path++; 294 295 if (*(path - 1) == '@') { 296 (void)strncpy(user, host, sizeof(login) - 1); 297 /* saw user part of user@host */ 298 sys = host; /* start over */ 299 while (*path != ':') { 300 *sys++ = *path++; 301 } 302 *sys = '\0'; 303 path++; 304 } 305 #ifdef COMPAT 306 else if (*(path - 1) == '.') { 307 while (*path != ':') { 308 *user++ = *path++; 309 } 310 *user = '\0'; 311 path++; 312 } 313 #endif 314 else 315 *user = '\0'; 316 317 while (*path) { 318 *dev++ = *path++; 319 } 320 *dev = '\0'; 321 322 #ifdef USE_REXEC 323 /* 324 * Execute the remote command using rexec 325 */ 326 READ(i) = WRITE(i) = _rmt_rexec(host, login); 327 if (READ(i) < 0) 328 return (-1); 329 #else 330 /* 331 * setup the pipes for the 'rsh' command and fork 332 */ 333 334 if (pipe(Ptc[i]) == -1 || pipe(Ctp[i]) == -1) 335 return (-1); 336 337 if ((rc = fork()) == -1) 338 return (-1); 339 340 if (rc == 0) { 341 char *rshpath, *rsh; 342 343 close(0); 344 dup(Ptc[i][0]); 345 close(Ptc[i][0]); close(Ptc[i][1]); 346 close(1); 347 dup(Ctp[i][1]); 348 close(Ctp[i][0]); close(Ctp[i][1]); 349 (void) setuid(getuid()); 350 (void) setgid(getgid()); 351 352 if ((rshpath = getenv("RCMD_CMD")) == NULL) 353 rshpath = _PATH_RSH; 354 if ((rsh = strrchr(rshpath, '/')) == NULL) 355 rsh = rshpath; 356 else 357 rsh++; 358 359 if (*login) { 360 execl(rshpath, rsh, host, "-l", login, 361 _PATH_RMT, NULL); 362 } else { 363 execl(rshpath, rsh, host, 364 _PATH_RMT, NULL); 365 } 366 367 /* 368 * bad problems if we get here 369 */ 370 371 perror("exec"); 372 exit(1); 373 } 374 375 close(Ptc[i][0]); close(Ctp[i][1]); 376 #endif 377 378 /* 379 * now attempt to open the tape device 380 */ 381 382 (void)snprintf(buffer, sizeof(buffer), "O%s\n%d\n", device, oflag); 383 if (command(i, buffer) == -1 || status(i) == -1) 384 return (-1); 385 386 return (i); 387 } 388 389 390 /* 391 * _rmt_close --- close a remote magtape unit and shut down 392 */ 393 static int 394 _rmt_close(int fildes) 395 { 396 int rc; 397 398 if (command(fildes, "C\n") != -1) { 399 rc = status(fildes); 400 401 rmtabort(fildes); 402 return (rc); 403 } 404 405 return (-1); 406 } 407 408 409 /* 410 * _rmt_read --- read a buffer from a remote tape 411 */ 412 static ssize_t 413 _rmt_read(int fildes, void *buf, size_t nbyte) 414 { 415 size_t rc, i; 416 char *p; 417 char buffer[BUFMAGIC]; 418 419 _DIAGASSERT(buf != NULL); 420 421 (void)snprintf(buffer, sizeof buffer, "R%lu\n", (u_long)nbyte); 422 if (command(fildes, buffer) == -1 || (rc = status(fildes)) == -1) 423 return (-1); 424 425 p = buf; 426 for (i = 0; i < rc; i += nbyte, p += nbyte) { 427 nbyte = read(READ(fildes), p, rc); 428 if (nbyte <= 0) { 429 rmtabort(fildes); 430 errno = EIO; 431 return (-1); 432 } 433 } 434 435 return (rc); 436 } 437 438 439 /* 440 * _rmt_write --- write a buffer to the remote tape 441 */ 442 static ssize_t 443 _rmt_write(int fildes, const void *buf, size_t nbyte) 444 { 445 char buffer[BUFMAGIC]; 446 void (*pstat)(int); 447 448 _DIAGASSERT(buf != NULL); 449 450 (void)snprintf(buffer, sizeof buffer, "W%lu\n", (u_long)nbyte); 451 if (command(fildes, buffer) == -1) 452 return (-1); 453 454 pstat = signal(SIGPIPE, SIG_IGN); 455 if (write(WRITE(fildes), buf, nbyte) == nbyte) { 456 signal(SIGPIPE, pstat); 457 return (status(fildes)); 458 } 459 460 signal(SIGPIPE, pstat); 461 rmtabort(fildes); 462 errno = EIO; 463 return (-1); 464 } 465 466 467 /* 468 * _rmt_lseek --- perform an imitation lseek operation remotely 469 */ 470 static off_t 471 _rmt_lseek(int fildes, off_t offset, int whence) 472 { 473 char buffer[BUFMAGIC]; 474 475 (void)snprintf(buffer, sizeof buffer, "L%lld\n%d\n", (long long)offset, 476 whence); 477 if (command(fildes, buffer) == -1) 478 return (-1); 479 480 return (status(fildes)); 481 } 482 483 484 /* 485 * _rmt_ioctl --- perform raw tape operations remotely 486 */ 487 #ifdef RMTIOCTL 488 static int 489 _rmt_ioctl(int fildes, unsigned long op, void *arg) 490 { 491 char c; 492 size_t rc, cnt; 493 char buffer[BUFMAGIC], *p; 494 495 _DIAGASSERT(arg != NULL); 496 497 /* 498 * MTIOCOP is the easy one. nothing is transfered in binary 499 */ 500 501 if (op == MTIOCTOP) { 502 (void)snprintf(buffer, sizeof buffer, "I%d\n%d\n", 503 ((struct mtop *)arg)->mt_op, 504 ((struct mtop *)arg)->mt_count); 505 if (command(fildes, buffer) == -1) 506 return (-1); 507 return (status(fildes)); 508 } 509 510 /* 511 * we can only handle 2 ops, if not the other one, punt 512 */ 513 514 if (op != MTIOCGET) { 515 errno = EINVAL; 516 return (-1); 517 } 518 519 /* 520 * grab the status and read it directly into the structure 521 * this assumes that the status buffer is (hopefully) not 522 * padded and that 2 shorts fit in a long without any word 523 * alignment problems, ie - the whole struct is contiguous 524 * NOTE - this is probably NOT a good assumption. 525 */ 526 527 if (command(fildes, "S") == -1 || (rc = status(fildes)) == -1) 528 return (-1); 529 530 p = arg; 531 for (; rc > 0; rc -= cnt, p += cnt) { 532 cnt = read(READ(fildes), p, rc); 533 if (cnt <= 0) { 534 rmtabort(fildes); 535 errno = EIO; 536 return (-1); 537 } 538 } 539 540 /* 541 * now we check for byte position. mt_type is a small integer field 542 * (normally) so we will check its magnitude. if it is larger than 543 * 256, we will assume that the bytes are swapped and go through 544 * and reverse all the bytes 545 */ 546 547 if (((struct mtget *) p)->mt_type < 256) 548 return (0); 549 550 for (cnt = 0; cnt < rc; cnt += 2) { 551 c = p[cnt]; 552 p[cnt] = p[cnt+1]; 553 p[cnt+1] = c; 554 } 555 556 return (0); 557 } 558 #endif /* RMTIOCTL */ 559 560 561 /* 562 * Added routines to replace open(), close(), lseek(), ioctl(), etc. 563 * The preprocessor can be used to remap these the rmtopen(), etc 564 * thus minimizing source changes: 565 * 566 * #ifdef <something> 567 * # define access rmtaccess 568 * # define close rmtclose 569 * # define creat rmtcreat 570 * # define dup rmtdup 571 * # define fcntl rmtfcntl 572 * # define fstat rmtfstat 573 * # define ioctl rmtioctl 574 * # define isatty rmtisatty 575 * # define lseek rmtlseek 576 * # define lstat rmtlstat 577 * # define open rmtopen 578 * # define read rmtread 579 * # define stat rmtstat 580 * # define write rmtwrite 581 * #endif 582 * 583 * -- Fred Fish 584 * 585 * ADR --- I set up a <rmt.h> include file for this 586 * 587 */ 588 589 /* 590 * Note that local vs remote file descriptors are distinquished 591 * by adding a bias to the remote descriptors. This is a quick 592 * and dirty trick that may not be portable to some systems. 593 */ 594 595 #define REM_BIAS 128 596 597 598 /* 599 * Test pathname to see if it is local or remote. A remote device 600 * is any string that contains ":/dev/". Returns 1 if remote, 601 * 0 otherwise. 602 */ 603 604 static int 605 remdev(const char *path) 606 { 607 608 _DIAGASSERT(path != NULL); 609 610 if ((path = strchr(path, ':')) != NULL) { 611 if (strncmp(path + 1, "/dev/", 5) == 0) { 612 return (1); 613 } 614 } 615 return (0); 616 } 617 618 619 /* 620 * Open a local or remote file. Looks just like open(2) to 621 * caller. 622 */ 623 int 624 rmtopen(const char *path, int oflag, ...) 625 { 626 mode_t mode; 627 int fd; 628 va_list ap; 629 va_start(ap, oflag); 630 631 mode = va_arg(ap, mode_t); 632 va_end(ap); 633 634 _DIAGASSERT(path != NULL); 635 636 if (remdev(path)) { 637 fd = _rmt_open(path, oflag, mode); 638 639 return ((fd == -1) ? -1 : (fd + REM_BIAS)); 640 } else { 641 return (open(path, oflag, mode)); 642 } 643 } 644 645 /* 646 * Test pathname for specified access. Looks just like access(2) 647 * to caller. 648 */ 649 650 int 651 rmtaccess(const char *path, int amode) 652 { 653 654 _DIAGASSERT(path != NULL); 655 656 if (remdev(path)) { 657 return (0); /* Let /etc/rmt find out */ 658 } else { 659 return (access(path, amode)); 660 } 661 } 662 663 664 /* 665 * Isrmt. Let a programmer know he has a remote device. 666 */ 667 int 668 isrmt(int fd) 669 { 670 671 return (fd >= REM_BIAS); 672 } 673 674 675 /* 676 * Read from stream. Looks just like read(2) to caller. 677 */ 678 ssize_t 679 rmtread(int fildes, void *buf, size_t nbyte) 680 { 681 682 _DIAGASSERT(buf != NULL); 683 684 if (isrmt(fildes)) { 685 return (_rmt_read(fildes - REM_BIAS, buf, nbyte)); 686 } else { 687 return (read(fildes, buf, nbyte)); 688 } 689 } 690 691 692 /* 693 * Write to stream. Looks just like write(2) to caller. 694 */ 695 ssize_t 696 rmtwrite(int fildes, const void *buf, size_t nbyte) 697 { 698 699 _DIAGASSERT(buf != NULL); 700 701 if (isrmt(fildes)) { 702 return (_rmt_write(fildes - REM_BIAS, buf, nbyte)); 703 } else { 704 return (write(fildes, buf, nbyte)); 705 } 706 } 707 708 /* 709 * Perform lseek on file. Looks just like lseek(2) to caller. 710 */ 711 off_t 712 rmtlseek(int fildes, off_t offset, int whence) 713 { 714 715 if (isrmt(fildes)) { 716 return (_rmt_lseek(fildes - REM_BIAS, offset, whence)); 717 } else { 718 return (lseek(fildes, offset, whence)); 719 } 720 } 721 722 723 /* 724 * Close a file. Looks just like close(2) to caller. 725 */ 726 int 727 rmtclose(int fildes) 728 { 729 730 if (isrmt(fildes)) { 731 return (_rmt_close(fildes - REM_BIAS)); 732 } else { 733 return (close(fildes)); 734 } 735 } 736 737 738 /* 739 * Do ioctl on file. Looks just like ioctl(2) to caller. 740 */ 741 int 742 rmtioctl(int fildes, unsigned long request, ...) 743 { 744 char *arg; 745 va_list ap; 746 va_start(ap, request); 747 748 arg = va_arg(ap, char *); 749 va_end(ap); 750 751 /* XXX: arg may be NULL ? */ 752 753 if (isrmt(fildes)) { 754 #ifdef RMTIOCTL 755 return (_rmt_ioctl(fildes - REM_BIAS, request, arg)); 756 #else 757 errno = EOPNOTSUPP; 758 return (-1); /* For now (fnf) */ 759 #endif 760 } else { 761 return (ioctl(fildes, request, arg)); 762 } 763 } 764 765 766 /* 767 * Duplicate an open file descriptor. Looks just like dup(2) 768 * to caller. 769 */ 770 int 771 rmtdup(int fildes) 772 { 773 774 if (isrmt(fildes)) { 775 errno = EOPNOTSUPP; 776 return (-1); /* For now (fnf) */ 777 } else { 778 return (dup(fildes)); 779 } 780 } 781 782 783 /* 784 * Get file status. Looks just like fstat(2) to caller. 785 */ 786 int 787 rmtfstat(int fildes, struct stat *buf) 788 { 789 790 _DIAGASSERT(buf != NULL); 791 792 if (isrmt(fildes)) { 793 errno = EOPNOTSUPP; 794 return (-1); /* For now (fnf) */ 795 } else { 796 return (fstat(fildes, buf)); 797 } 798 } 799 800 801 /* 802 * Get file status. Looks just like stat(2) to caller. 803 */ 804 int 805 rmtstat(const char *path, struct stat *buf) 806 { 807 808 _DIAGASSERT(path != NULL); 809 _DIAGASSERT(buf != NULL); 810 811 if (remdev(path)) { 812 errno = EOPNOTSUPP; 813 return (-1); /* For now (fnf) */ 814 } else { 815 return (stat(path, buf)); 816 } 817 } 818 819 820 /* 821 * Create a file from scratch. Looks just like creat(2) to the caller. 822 */ 823 int 824 rmtcreat(const char *path, mode_t mode) 825 { 826 827 _DIAGASSERT(path != NULL); 828 829 if (remdev(path)) { 830 return (rmtopen(path, 1 | O_CREAT, mode)); 831 } else { 832 return (creat(path, mode)); 833 } 834 } 835 836 837 /* 838 * Rmtfcntl. Do a remote fcntl operation. 839 */ 840 int 841 rmtfcntl(int fd, int cmd, ...) 842 { 843 void *arg; 844 va_list ap; 845 va_start(ap, cmd); 846 847 arg = va_arg(ap, void *); 848 va_end(ap); 849 850 /* XXX: arg may be NULL ? */ 851 852 if (isrmt(fd)) { 853 errno = EOPNOTSUPP; 854 return (-1); 855 } else { 856 return (fcntl(fd, cmd, arg)); 857 } 858 } 859 860 861 /* 862 * Rmtisatty. Do the isatty function. 863 */ 864 int 865 rmtisatty(int fd) 866 { 867 868 if (isrmt(fd)) 869 return (0); 870 else 871 return (isatty(fd)); 872 } 873 874 875 /* 876 * Get file status, even if symlink. Looks just like lstat(2) to caller. 877 */ 878 int 879 rmtlstat(const char *path, struct stat *buf) 880 { 881 882 _DIAGASSERT(path != NULL); 883 _DIAGASSERT(buf != NULL); 884 885 if (remdev(path)) { 886 errno = EOPNOTSUPP; 887 return (-1); /* For now (fnf) */ 888 } else { 889 return (lstat(path, buf)); 890 } 891 } 892