/dev/fd/N not synonymous with file descriptor N; it is on Linux

Houder houder@xs4all.nl
Sun Jan 27 18:39:00 GMT 2019


On Tue, 22 Jan 2019 11:39:28, Corinna Vinschen  wrote:
> 
> On Jan 22 11:20, Houder wrote:
> > On Tue, 22 Jan 2019 10:41:57, Corinna Vinschen  wrote:
> > > On Jan 22 10:25, Houder wrote:
> > [snip]
> >
> > > > Curious! It fails (for me) on W7 ...
> > >
> > > It works for me just as well on W7:
[snip]

> Maybe you should run the above shell session under strace and see if
> something unusual crops up.  BLODA?

NO BLODA.

Ok, for the record (as this is W7, i.e. pre-pre-W10 :-)

Using my original STC again: (source code included below)

 - create file (in /tmp) write-only, write "Hello, world!" to file, close fd
 - open file once more read-only
 - unlink file
 - open file, using /dev/fd/N, read-write <==== succeeds (and the handle shown by fcntl is read-write)
 - write "*****" to file (using the fd obtained in the previous line), lseek to begin of file
  - write fails w/ "Permission denied" <==== so ... the file cannot be written to?
 - read file (using the same fd)

Regards,
Henri

64-@@ uname -a
CYGWIN_NT-6.1 Seven 2.12.0s(0.333/5/3) 2019-01-21 10:25 x86_64 Cygwin

-----
>From the output of strace:

----- # write ... fails
   74   25044 [main] stc 1368 write: write(4, 0x100403117, 14)
   31   25075 [main] stc 1368 seterrno_from_nt_status: /ext/build/mknetrel/src/cygwin-snapshot-20190121-1/winsup/cygwin/fhandler.cc:294 status 0xC0000022 -> windows error 5
   29   25104 [main] stc 1368 geterrno_from_win_error: windows error 5 == errno 13
   27   25131 [main] stc 1368 write: -1 = write(4, 0x100403117, 14), errno 13

i.e. the STC fails in fhandler_base::raw_write() in winsup/cygwin/fhandler.cc,
right after NtWriteFile() ...

Btw, earlier on, when /tmp/stc.txt is unlinked, an "Sharing violation" occurs:
   32   21456 [main] stc 1368 unlink_nt: Trying to delete \??\E:\Cygwin64\tmp\stc.txt, isdir = 0
----- # Sharing violation? in unlink_nt() in winsup/cygwin/syscalls.cc, after the 3rd call to NtOpenfile()
   50   21506 [main] stc 1368 unlink_nt: Sharing violation when opening \??\E:\Cygwin64\tmp\stc.txt
  443   21949 [main] stc 1368 try_to_bin: \??\E:\Cygwin64\tmp\stc.txt, return bin_status 2 ### "has been moved"?
   35   21984 [main] stc 1368 unlink_nt: \??\E:\Cygwin64\tmp\stc.txt, return status = 0x0
   28   22012 [main] stc 1368 unlink: 0 = unlink(/tmp/stc.txt)

-----
Output of the STC:
64-@@ ./stc # source code included below ...
Invoked from: /usr/bin/bash (parent process)
flagsfl2 = 0x118000
devfile = /dev/fd/3
flagsfl3 = 0x118002
/dev/fd/3: Cannot write!, id = openfd3, errno = 13, errstr = Permission denied
buf = Hello, world!
64-@@
#  this is odd: open("/dev/fd/3", O_RDWR) succeeds ... write(4, ...) does not
#  fail w/ "Bad file descriptor", but w/ "Permission denied"!

-----
If the STC is put "on hold" right before termination (before closing
the file descriptors), the "bin" shows:

64-@@ pwd
/drv/e/$RECYCLE.BIN/S-1-5-21-91509220-1575020443-2714799223-1000
64-@@ ls -al
total 6
drwx------+ 1 Henri        None            0 Jan 27 17:27 .
drwxrwx---+ 1 Henri        None            0 Jan 27 08:47 ..
-rw-r-----  1 Unknown+User Unknown+Group  14 Jan 27 17:27 .???017d00000001f91f4e494283f3b8a953
-rwx------+ 1 Henri        None          129 Jan 27 08:33 desktop.ini

This file cannot be accessed (read) ... (can be read by STC, but NOT written to!).

64-@@ icacls .
. BUILTIN\Administrators:(OI)(CI)(F)
  NT AUTHORITY\SYSTEM:(OI)(CI)(F)
  Seven\Henri:(OI)(CI)(F)
  Mandatory Label\Low Mandatory Level:(OI)(CI)(IO)(NW)

Successfully processed 1 files; Failed processing 0 files
64-@@ getfacl .
# file: .
# owner: Henri
# group: None
user::rwx
group::---
group:SYSTEM:rwx        #effective:---
group:Administrators:rwx        #effective:---
mask::---
other::---
default:user::rwx
default:group::---
default:group:SYSTEM:rwx        #effective:---
default:group:Administrators:rwx        #effective:---
default:mask::---
default:other::---

-----
source code of STC:
// gcc -Wall -o stc stc.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h> // strerror()
#include <errno.h>
#include <fcntl.h>

// NOTE: my text uses "fd 2" for a file descriptor that equals 2; and fd2 for variable fd2

/*
 LPI, 5.11 The /dev/fd Directory (Linux Programming Interface, Michael Kerrisk)
 ...
FIXME: No, on Linux the file is "reopened": a new open file description is created!

Cygwin/ W7
file deleted: (fd2 is opened before file deletion)
 - file can be written to via fd3 iff fd2 has been opened read-write
file NOT deleted:
 - file can be written to via fd3, even if fd2 has been opened read-only
 */

/*
 - create file (in /tmp) write-only, write "Hello, world!" to file, close fd
 - open file once more read-only (or read-write)
 - unlink file
 - open file, using /dev/fd/N (or /proc/<pid>/fd/N), read-write
 - write "*****" to file (using the fd obtained in the previous line), lseek to begin of file
 - read file (using the same fd)
 */

void
errExit(const char *str)
{
    fprintf(stderr, "id = %s, errno = %d, errstr = %s\n", str, errno, strerror(errno));
    exit(EXIT_FAILURE);
}

int
main(int argc, char *argv[])
{
#if 1
    {
        char tmp0[30] = { 0 }; char tmp1[30] = { 0 };
        if (snprintf(tmp0, sizeof(tmp0), "/proc/%u/exe", (unsigned int)getppid() ) == -1 ) errExit("sprintf-tmp0");
        //printf("tmp0 = %s\n", tmp0);
        if (strlen(tmp0) == sizeof(tmp0) ) errExit("strlen-tmp0");
        if (readlink(tmp0, tmp1, sizeof(tmp1) - 1) ==  -1) errExit("readlink-tmp1");
        //printf("tmp1 = %s\n", tmp1);
        if (strlen(tmp1) == sizeof(tmp1) ) errExit("strlen-tmp1");
        fprintf(stderr, "Invoked from: %s (parent process)\n", tmp1);
    }
#endif

    int fd1, fd2, fd3;

    errno = 0;

    // kludge (ignore error if file does not exist) ...
    if (unlink("/tmp/stc.txt") == -1) {
        if (errno != 2) errExit("unlink"); else errno = 0;
    }

    // create a tmpfile in the same way that bash would do ...
    fd1 = open("/tmp/stc.txt", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, 0600);
    if (fd1 == -1)
        errExit("openfd1");

    if (write(fd1, "Hello, world!\n", 14) == -1) errExit("writefd1");

    // close fd1 like bash would do ...
    if (close(fd1) == -1) errExit("closefd1");

    // also open this tmpfile for reading like bash would do ...
    fd2 = open("/tmp/stc.txt", O_RDONLY);
    //fd2 = open("/tmp/stc.txt", O_RDWR);
    if (fd2 == -1)
        errExit("openfd2");

#if 1
    int flagsfl2;
    if ( (flagsfl2 = fcntl(fd2, F_GETFL)) == -1) errExit("fcntlfd1-getfl2");
    fprintf(stderr, "flagsfl2 = 0x%x\n", flagsfl2);
    // Linux: /usr/include/bits/fcntl-linux.h
    // Cygwin: /usr/include/sys/_default_fcntl.h
#endif

    // delete the tmpfile like bash would do ...
    if (unlink("/tmp/stc.txt") == -1) errExit("unlink");

    // kludge: compose a string (using fd2) representing "the device file"
    // in /dev/fd (a symlnk to /proc/self/fd) for file descriptor fd2 which
    // is still open ...
    char devfile[16] = "/dev/fd/";       devfile[8] =  fd2 + 0x30; devfile[9] =  '\0';
    //char devfile[16] = "/proc/self/fd/"; devfile[14] = fd2 + 0x30; devfile[15] = '\0';

    fprintf(stderr, "devfile = %s\n", devfile);

    // open this device file; it succeeds on Linux, but fails on Cygwin (that
    // is, it failed before 20190107?)
    //fd3 = open(devfile, O_RDONLY); // this failed before 20190107?
    fd3 = open(devfile, O_RDWR); // odd: succeeds on W7 (fd2 is read-only!)

    if (fd3 == -1) { // does not happen beyond 20190106
        const char *id = "openfd3";
#if 1
	fprintf(stderr, "%s: Cannot open!, id = %s, errno = %d, errstr = %s\n",
            devfile, id, errno, strerror(errno));
        /* previously, (before 20190107?)
           it was not even possible to open the file, using either "/dev/fd/N"
           or "/proc/self/fd/N" if the target had been unlink'ed ... Now it is
           possible to open the file as indicated, but it can only be read. On
           Linux however, it is even possible to open it "read-write" ...
           (where fd3 will point to a brand-new open file handle)
         */
        errno = 0;
        char buf[16] = { 0 };
        if (read(fd2, buf, sizeof(buf) ) == -1) errExit("readfd2");
        fprintf(stderr, "buf = %s", buf);
        if (close(fd2) == -1) fprintf(stderr, "closefd2 failed\n");
        exit(EXIT_FAILURE);
#else
        errExit(id);
#endif
    }

#if 0
    {
        char buf[16] = { 0 };
        if (read(fd2, buf, sizeof(buf) ) == -1) errExit("readfd2");
        fprintf(stderr, "buf = %s", buf);
    }
#endif

#if 1
    int flagsfl3;
    if ( (flagsfl3 = fcntl(fd3, F_GETFL)) == -1) errExit("fcntlfd3-getfl3");
    fprintf(stderr, "flagsfl3 = 0x%x\n", flagsfl3);
    // Linux: /usr/include/bits/fcntl-linux.h
    // Cygwin: /usr/include/sys/_default_fcntl.h
#endif

// option: write using fd3
#if 1
    // failure unless fd2 has been opened read-write (and file has been deleted)
    // W7: fd3 will be a duplicate of fd2 in that case ...
    if (write(fd3, "*****\n", 14) == -1) {
        const char *id = "openfd3";
  #if 1
        fprintf(stderr, "%s: Cannot write!, id = %s, errno = %d, errstr = %s\n",
        devfile, id, errno, strerror(errno));
        errno = 0;
  #else
        errExit(id);
  #endif
    }

    // required because of write() -- reset file offset
    if (lseek(fd3, 0, SEEK_SET) == -1) errExit("lseekfd3");

#endif // end - option: write using fd3

    char buf[16] = { 0 };
    if (read(fd3, buf, sizeof(buf) ) == -1) errExit("readfd3");
    fprintf(stderr, "buf = %s", buf);
//sleep(30);
    if (close(fd3) == -1) fprintf(stderr, "closefd3 failed\n");
    if (close(fd2) == -1) fprintf(stderr, "closefd2 failed\n");
}

//=====


--
Problem reports:       http://cygwin.com/problems.html
FAQ:                   http://cygwin.com/faq/
Documentation:         http://cygwin.com/docs.html
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple



More information about the Cygwin mailing list