How to retrieve the filename of a FileHandle?

If I get a file handle with let file = FileHandle(forReadingAtPath: "/path/to/file.json"), is there a way to retrieve the filename “/path/to/file.json” back from the file instance?

There’s no portable way to do this, but file provides a fileDescriptor which can be mapped to a filename on macOS using fcntl with F_GETPATH:

var buffer = [CChar](repeating: 0, count: 100)
_ = fcntl(file.fileDescriptor, F_GETPATH, &buffer)
print(String(cString: buffer))

In general you can’t do that. For one thing there’s no guarantee that there still is a path to it. (The file could have been unlinked after it was opened.) Also, which path should be returned if the file is hardlinked to multiple names? Due to these issues the POSIX standard provides no mechanism for this.

Darwin does have the nonstandard F_GETPATH option to fcntl() but it’s not safe to depend on it. It does seem to track files being renamed, but it returns stale information when the file is unlinked.

Example:

import Darwin

var fd = open("/tmp/foo", O_RDWR|O_CREAT, 438)
var buffer = [CChar](repeating: 0, count: Int(MAXPATHLEN))
_ = fcntl(fd, F_GETPATH, &buffer)
print("The path is", String(cString: buffer))

rename("/tmp/foo","/tmp/bar")
_ = fcntl(fd, F_GETPATH, &buffer)
print("Now the path is", String(cString: buffer))

unlink("/tmp/bar")
_ = fcntl(fd, F_GETPATH, &buffer)
print("And now that the file has been deleted, fcntl() still says it's at", String(cString: buffer))

close(fd)

Output:

The path is /private/tmp/foo
Now the path is /private/tmp/bar
And now that the file has been deleted, fcntl() still says it's at /private/tmp/bar

On Linux you can call readlink() on /proc/self/fd/N where N is the number of the file descriptor. It tracks file renaming and when a file is unlinked it appends " (deleted)" to the path (at least on the kernel version I tested).

Some other operating systems have their own mechanisms to do this but not all of them do. Reliability and behavior in edge cases varies.