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