1/* $NetBSD: rmtlib.c,v 1.29 2024/03/22 19:36:56 andvar 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.29 2024/03/22 19:36:56 andvar 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#include <err.h> 54 55#ifdef USE_REXEC 56#include <netdb.h> 57#endif 58 59#define __RMTLIB_PRIVATE 60#include <rmt.h> /* get prototypes for remapped functions */ 61 62#include "pathnames.h" 63 64static int _rmt_close(int); 65static int _rmt_ioctl(int, unsigned long, void *); 66static off_t _rmt_lseek(int, off_t, int); 67static int _rmt_open(const char *, int, int); 68static ssize_t _rmt_read(int, void *, size_t); 69static ssize_t _rmt_write(int, const void *, size_t); 70static int command(int, const char *); 71static int remdev(const char *); 72static void rmtabort(int); 73static int status(int); 74 75 76#define BUFMAGIC 64 /* a magic number for buffer sizes */ 77#define MAXUNIT 4 78 79#define READ(fd) (Ctp[fd][0]) 80#define WRITE(fd) (Ptc[fd][1]) 81 82static int Ctp[MAXUNIT][2] = { {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1} }; 83static int Ptc[MAXUNIT][2] = { {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1} }; 84 85 86/* 87 * rmtabort --- close off a remote tape connection 88 */ 89static void 90rmtabort(int fildes) 91{ 92 93 close(READ(fildes)); 94 close(WRITE(fildes)); 95 READ(fildes) = -1; 96 WRITE(fildes) = -1; 97} 98 99 100/* 101 * command --- attempt to perform a remote tape command 102 */ 103static int 104command(int fildes, const char *buf) 105{ 106 size_t blen; 107 sig_t pstat; 108 109 _DIAGASSERT(buf != NULL); 110 111/* 112 * save current pipe status and try to make the request 113 */ 114 115 blen = strlen(buf); 116 pstat = signal(SIGPIPE, SIG_IGN); 117 if ((size_t)write(WRITE(fildes), buf, blen) == blen) { 118 signal(SIGPIPE, pstat); 119 return 0; 120 } 121 122/* 123 * something went wrong. close down and go home 124 */ 125 126 signal(SIGPIPE, pstat); 127 rmtabort(fildes); 128 129 errno = EIO; 130 return -1; 131} 132 133 134/* 135 * status --- retrieve the status from the pipe 136 */ 137static int 138status(int fildes) 139{ 140 int i; 141 char c, *cp; 142 char buffer[BUFMAGIC]; 143 144/* 145 * read the reply command line 146 */ 147 148 for (i = 0, cp = buffer; i < BUFMAGIC; i++, cp++) { 149 if (read(READ(fildes), cp, 1) != 1) { 150 rmtabort(fildes); 151 errno = EIO; 152 return -1; 153 } 154 if (*cp == '\n') { 155 *cp = 0; 156 break; 157 } 158 } 159 160 if (i == BUFMAGIC) { 161 rmtabort(fildes); 162 errno = EIO; 163 return -1; 164 } 165 166/* 167 * check the return status 168 */ 169 170 for (cp = buffer; *cp; cp++) 171 if (*cp != ' ') 172 break; 173 174 if (*cp == 'E' || *cp == 'F') { 175 errno = atoi(cp + 1); 176 while (read(READ(fildes), &c, 1) == 1) 177 if (c == '\n') 178 break; 179 180 if (*cp == 'F') 181 rmtabort(fildes); 182 183 return -1; 184 } 185 186/* 187 * check for mis-synced pipes 188 */ 189 190 if (*cp != 'A') { 191 rmtabort(fildes); 192 errno = EIO; 193 return -1; 194 } 195 196 return atoi(cp + 1); 197} 198 199 200#ifdef USE_REXEC 201/* 202 * _rmt_rexec 203 * 204 * execute /etc/rmt on a remote system using rexec(). 205 * Return file descriptor of bidirectional socket for stdin and stdout 206 * If username is NULL, or an empty string, uses current username. 207 * 208 * ADR: By default, this code is not used, since it requires that 209 * the user have a .netrc file in his/her home directory, or that the 210 * application designer be willing to have rexec prompt for login and 211 * password info. This may be unacceptable, and .rhosts files for use 212 * with rsh are much more common on BSD systems. 213 */ 214 215static int _rmt_rexec(const char *, const char *); 216 217static int 218_rmt_rexec(const char *host, const char *user) 219{ 220 struct servent *rexecserv; 221 222 _DIAGASSERT(host != NULL); 223 /* user may be NULL */ 224 225 rexecserv = getservbyname("exec", "tcp"); 226 if (rexecserv == NULL) 227 errx(1, "exec/tcp: service not available."); 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 247static int 248/*ARGSUSED*/ 249_rmt_open(const char *path, int oflag, int mode) 250{ 251 int i; 252 char buffer[2 * BUFMAGIC]; 253 char host[MAXHOSTLEN]; 254 char device[BUFMAGIC]; 255 char login[BUFMAGIC]; 256 char *sys, *dev, *user; 257 const char *rshpath, *rsh; 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)strlcpy(user, host, sizeof(login)); 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 switch (fork()) { 337 case -1: 338 return -1; 339 340 case 0: 341 close(0); 342 dup(Ptc[i][0]); 343 close(Ptc[i][0]); close(Ptc[i][1]); 344 close(1); 345 dup(Ctp[i][1]); 346 close(Ctp[i][0]); close(Ctp[i][1]); 347 (void) setuid(getuid()); 348 (void) setgid(getgid()); 349 350 if ((rshpath = getenv("RCMD_CMD")) == NULL) 351 rshpath = _PATH_RSH; 352 if ((rsh = strrchr(rshpath, '/')) == NULL) 353 rsh = rshpath; 354 else 355 rsh++; 356 357 if (*login) { 358 execl(rshpath, rsh, host, "-l", login, _PATH_RMT, NULL); 359 } else { 360 execl(rshpath, rsh, host, _PATH_RMT, NULL); 361 } 362 363/* 364 * bad problems if we get here 365 */ 366 367 err(1, "Cannot exec %s", rshpath); 368 /*FALLTHROUGH*/ 369 default: 370 break; 371 } 372 373 close(Ptc[i][0]); close(Ctp[i][1]); 374#endif 375 376/* 377 * now attempt to open the tape device 378 */ 379 380 (void)snprintf(buffer, sizeof(buffer), "O%s\n%d\n", device, oflag); 381 if (command(i, buffer) == -1 || status(i) == -1) 382 return -1; 383 384 return i; 385} 386 387 388/* 389 * _rmt_close --- close a remote magtape unit and shut down 390 */ 391static int 392_rmt_close(int fildes) 393{ 394 int rc; 395 396 if (command(fildes, "C\n") != -1) { 397 rc = status(fildes); 398 399 rmtabort(fildes); 400 return rc; 401 } 402 403 return -1; 404} 405 406 407/* 408 * _rmt_read --- read a buffer from a remote tape 409 */ 410static ssize_t 411_rmt_read(int fildes, void *buf, size_t nbyte) 412{ 413 size_t rc; 414 int rv; 415 ssize_t nread; 416 char *p; 417 char buffer[BUFMAGIC]; 418 419 _DIAGASSERT(buf != NULL); 420 421 (void)snprintf(buffer, sizeof buffer, "R%zu\n", nbyte); 422 if (command(fildes, buffer) == -1 || (rv = status(fildes)) == -1) 423 return -1; 424 425 if (rv > (int)nbyte) 426 rv = (int)nbyte; 427 428 for (rc = rv, p = buf; rc > 0; rc -= nread, p += nread) { 429 if ((nread = read(READ(fildes), p, rc)) <= 0) { 430 rmtabort(fildes); 431 errno = EIO; 432 return -1; 433 } 434 } 435 436 return rv; 437} 438 439 440/* 441 * _rmt_write --- write a buffer to the remote tape 442 */ 443static ssize_t 444_rmt_write(int fildes, const void *buf, size_t nbyte) 445{ 446 char buffer[BUFMAGIC]; 447 sig_t pstat; 448 449 _DIAGASSERT(buf != NULL); 450 451 (void)snprintf(buffer, sizeof buffer, "W%zu\n", nbyte); 452 if (command(fildes, buffer) == -1) 453 return -1; 454 455 pstat = signal(SIGPIPE, SIG_IGN); 456 if ((size_t)write(WRITE(fildes), buf, nbyte) == nbyte) { 457 signal(SIGPIPE, pstat); 458 return status(fildes); 459 } 460 461 signal(SIGPIPE, pstat); 462 rmtabort(fildes); 463 errno = EIO; 464 return -1; 465} 466 467 468/* 469 * _rmt_lseek --- perform an imitation lseek operation remotely 470 */ 471static off_t 472_rmt_lseek(int fildes, off_t offset, int whence) 473{ 474 char buffer[BUFMAGIC]; 475 476 /*LONGLONG*/ 477 (void)snprintf(buffer, sizeof buffer, "L%lld\n%d\n", (long long)offset, 478 whence); 479 if (command(fildes, buffer) == -1) 480 return -1; 481 482 return status(fildes); 483} 484 485 486/* 487 * _rmt_ioctl --- perform raw tape operations remotely 488 */ 489#ifdef RMTIOCTL 490static int 491_rmt_ioctl(int fildes, unsigned long op, void *arg) 492{ 493 char c; 494 int rv; 495 size_t rc; 496 ssize_t cnt; 497 char buffer[BUFMAGIC], *p; 498 struct mtop *mtop = arg; 499 500 _DIAGASSERT(arg != NULL); 501 502/* 503 * MTIOCOP is the easy one. nothing is transferred in binary 504 */ 505 506 if (op == MTIOCTOP) { 507 (void)snprintf(buffer, sizeof buffer, "I%d\n%d\n", 508 mtop->mt_op, mtop->mt_count); 509 if (command(fildes, buffer) == -1) 510 return -1; 511 return status(fildes); 512 } 513 514/* 515 * we can only handle 2 ops, if not the other one, punt 516 */ 517 518 if (op != MTIOCGET) { 519 errno = EINVAL; 520 return -1; 521 } 522 523/* 524 * grab the status and read it directly into the structure 525 * this assumes that the status buffer is (hopefully) not 526 * padded and that 2 shorts fit in a long without any word 527 * alignment problems, ie - the whole struct is contiguous 528 * NOTE - this is probably NOT a good assumption. 529 */ 530 531 if (command(fildes, "S") == -1 || (rv = status(fildes)) == -1) 532 return -1; 533 534 memset(arg, 0, sizeof(struct mtget)); 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 607static int 608remdev(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 */ 626int 627rmtopen(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 653int 654rmtaccess(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 */ 670int 671isrmt(int fd) 672{ 673 int unbias = fd - REM_BIAS; 674 675 return (fd >= REM_BIAS) && unbias < MAXUNIT && 676 (WRITE(unbias) != -1 || READ(unbias) != -1); 677} 678 679 680/* 681 * Read from stream. Looks just like read(2) to caller. 682 */ 683ssize_t 684rmtread(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 */ 700ssize_t 701rmtwrite(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 */ 716off_t 717rmtlseek(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 */ 731int 732rmtclose(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 */ 746int 747rmtioctl(int fildes, unsigned long request, ...) 748{ 749 void *arg; 750 va_list ap; 751 va_start(ap, request); 752 753 arg = va_arg(ap, void *); 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 */ 775int 776rmtdup(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 */ 791int 792rmtfstat(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 */ 809int 810rmtstat(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 */ 828int 829rmtcreat(const char *path, mode_t mode) 830{ 831 832 _DIAGASSERT(path != NULL); 833 834 if (remdev(path)) { 835 return rmtopen(path, O_WRONLY | O_CREAT, mode); 836 } else { 837 return open(path, O_CREAT | O_TRUNC | O_WRONLY, mode); 838 } 839} 840 841 842/* 843 * Rmtfcntl. Do a remote fcntl operation. 844 */ 845int 846rmtfcntl(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 */ 869int 870rmtisatty(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 */ 883int 884rmtlstat(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