home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume7 / remtape / rmtlib.c < prev    next >
Encoding:
C/C++ Source or Header  |  1986-12-16  |  13.9 KB  |  863 lines

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