1 /* $NetBSD: rmtlib.c,v 1.21 2006/03/19 23:05:50 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.21 2006/03/19 23:05:50 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 < rv; 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