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