Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 16 Feb 2015 00:50:54 +0000
From:      bugzilla-noreply@freebsd.org
To:        freebsd-bugs@FreeBSD.org
Subject:   [Bug 197695] Add facility to retrieve a canonical path for a currently open file descriptor
Message-ID:  <bug-197695-8@https.bugs.freebsd.org/bugzilla/>

next in thread | raw e-mail | index | archive | help
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=197695

            Bug ID: 197695
           Summary: Add facility to retrieve a canonical path for a
                    currently open file descriptor
           Product: Base System
           Version: 11.0-CURRENT
          Hardware: Any
                OS: Any
            Status: New
          Severity: Affects Many People
          Priority: ---
         Component: kern
          Assignee: freebsd-bugs@FreeBSD.org
          Reporter: s_bugzilla@nedprod.com

I recently discovered that FreeBSD is the only one of the major operating
systems which does not provide a method of retrieving a current canonical path
for a currently open file descriptor. I think this should be fixed, as without
this facility writing code which implements race-free file entry unlinking is
extremely tough - you simply cannot avoid accidentally deleting the wrong file
if another process changes out the filing system underneath you, and you cannot
use the trick of openat() + statat() + unlinkat() on the current path of a file
descriptor to ensure you are deleting the correct file (of course, it would be
super great if POSIX allowed one to delete and rename files via open file
descriptor like Windows does and then life would be much easier writing race
free filing system code. But I digress).

How other OSs implement path reading:

* Windows: NtQueryObject(hFile, ObjectNameInformation, nameFull.Buffer,
sizeof(nameFull.Buffer), &returnedLength). This returns the NT kernel path for
an open file handle. With a bit of work, this can be converted into a DOS style
path. On Windows, the NT kernel path used to open a handle is retained per
handle, so hard links for the same file don't confound. Also, NT usefully
supplies a boolean which indicates if the file is deleted or not.

* Linux: readlink("/proc/self/fd/NNN", buffer, sizeof(buffer)) returning the
length of the buffer filled. Linux usefully prepends (older kernels) or appends
(newer kernels) the string "(deleted)" if the file is deleted. Unfortunately
hard links can confound on Linux, so the path returned may be very different to
the one you opened. You basically get back _some_ path referring to that inode,
whichever the kernel found first in its caches.

* Mac OS X: fnctl(fd, F_GETPATH, buffer). No size of the buffer is supplied
which I think was a real oversight. No return of how much of the buffer was
filled either. Also, if the file is deleted you just get back the last known
good path, and I don't know if this API is confounded by hard links.


How FreeBSD might implement this:

1. /proc is deprecated on FreeBSD, so Linux's approach is out. I dislike the OS
X API as OS X did it, but if one added a size_t* bufsize and let one query the
buffer size by passing a null buffer it would look a lot better. Some method of
indicating if the file is deleted (e.g. a null string return) even better
again.

2. As an alternative to implementing our own F_GETPATH which doesn't match OS
X's API, the following code could work:

        size_t len;
        int mib[4]={CTL_KERN, KERN_PROC, KERN_PROC_FILEDESC, getpid()};
        BOOST_AFIO_ERRHOS(sysctl(mib, 4, NULL, &len, NULL, 0));
        std::vector<char> buffer(len*2);
        BOOST_AFIO_ERRHOS(sysctl(mib, 4, buffer.data(), &len, NULL, 0));
        for(char *p=buffer.data(); p<buffer.data()+len;)
        {
          struct kinfo_file *kif=(struct kinfo_file *) p;
          if(kif->kf_fd==fd)
          {
            lock_guard<pathlock_t> g(pathlock);
            _path=path::string_type(kif->kf_path);
            return _path;
          }
          p+=kif->kf_structsize;
        }

Right now FreeBSD returns path information per fd via KERN_PROC_FILEDESC for
just about every type of file descriptor *except* regular files. If you use
procstat you'll see this for yourself - regular files always get a null path.

Ideally speaking the kernel would track the path used to open each fd as it
changed over time, this would prevent hard link confounding. However, I can see
that would require filing system support. One alternative could be to return a
null terminated sequence of path fragments within its mounted filing system,
one per hard link, but again I can see filing system support might be needed.

Looking at kernel source code, ZFS provides a ZFS_IOC_OBJ_TO_PATH ioctl which
will return you a path from a supplied ZFS object, however I note that it
returns exactly one path, so I assume that ZFS objects are one per hard link.
UFS appears to provide a SAVENAME facility in ufs_lookup_ino(), so in theory
there it's easy. I didn't look into the other filing systems, but it doesn't
look like implementing this would be hard for someone familiar with the FreeBSD
kernel.

And procstat and lsof would now return more useful information, also a win.

Niall

-- 
You are receiving this mail because:
You are the assignee for the bug.



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?bug-197695-8>