Check if a filedescriptor refers to a deleted file (in Bash)
To test whether a file descriptor refers to a regular file that has no remaining link in any directory on the filesystem, you could make a fstat()
system call on it and check the number of links (st_nlink
field) in the returned structure.
With zsh
, you could do it with its stat
builtin:
zmodload zsh/stat
fd=3
if
stat -s -H st -f $fd && # can be fstat'ed (is an opened fd)
[[ $st[mode] = -* ]] && # is a regular file
((st[nlink] == 0)) # has no link on the filesystem
then
print fd $fd is open on a regular file that has no link in the filessystem
fi
bash
(the GNU shell) has no equivalent, but if you are on a GNU system, you might have GNU stat
in which case you should be able to do something like:
fd=3
if [ "$(LC_ALL=C stat -c %F:%h - <&"$fd")" = 'regular file:0' ]; then
printf '%s\n' "fd $fd is open on a regular file that has no link in the filessystem"
fi
If your OS kernel is Linux, a more portable approach (for those OSes that don't have zsh
and where the core utilities are not from GNU), assuming the proc filesystem is mounted on /proc
could be to use ls
on /proc/self/fd/$fd
:
if
LC_ALL=C TZ=UTC0 ls -nLd /proc/self/fd/0 <&"$fd" |
LC_ALL=C awk -v ret=1 '
NF {if ($1 ~ /^-/ && $2 == 0) ret=0; exit}
END {exit(ret)}'
then
printf '%s\n' "fd $fd is open on a regular file that has no link in the filessystem"
fi
Here duplicating the fd on 0 like in the previous solution, so it works even if fd has the close-on-exec flag (assuming fd is not 0 in the first place, but fd 0 would normally not have the close-on-exec flag).
That kind of approach doesn't work with the fake filesystem that is Linux' procfs to check whether a fd open on /proc/<some-pid>/cmdline
refers to a live process:
$ zsh -c 'zmodload zsh/stat; (sleep 1; stat -f0 +nlink; cat) < /proc/$$/cmdline &'
$ 1
cat: -: No such process
See how fstat().st_nlink
returned 1 above (which would mean the file still had a link to a directory), while cat
's read()
on the fd returned an error. That's not usual filesystem semantic.
In any case, to check whether your parent is still running, you can call getppid()
which would return 1 or the pid of the child subreaper if the parent died. In zsh
, you'd use $sysparams[ppid]
(in the zsh/system
module).
$ sh -c 'zsh -c '\''zmodload zsh/system
print $PPID $sysparams[ppid]
sleep 2; print $PPID $sysparams[ppid]
'\'' & sleep 1'
14585 14585
$ 14585 1
In bash
, you could use ps -o ppid= -p "$BASHPID"
instead.
Another approach would be to create a pipe between parent and child and check with select
/poll
(or read -t0
in bash
) that it's still up.
Could be done by using a coproc
(only recently added to bash
) instead of &
.
background_with_pipe() {
coproc "$@" {PARENT_FD}<&0 <&3 3<&- >&4 4>&-
} 3<&0 4>&1
parent_gone() {
local ignore
read -t0 -u "$PARENT_FD" ignore
}
background_with_pipe eval '
parent_gone || echo parent still there
sleep 2
parent_gone && echo parent gone
'
sleep 1
exit
Which give:
$ bash ./that-script
parent still there
$ parent gone
Building up on your envisioned approach, and again assuming a Linux kernel with procfs
mounted on /proc
, you could also do:
exec {PARENT_CANARY}< /proc/self/cmdline; PARENT_PID=$BASHPID
parent_gone() {
! [[ /proc/$PARENT_PID/cmdline -ef /proc/self/fd/$PARENT_CANARY ]]
}
(
parent_gone || echo parent still there
sleep 2
parent_gone && echo parent gone
) &
sleep 1
Using [[ file1 -ef file2 ]]
that check whether the too files have the same dev and inode number (st_dev
and st_ino
returned by stat()
).
That seems to work with 5.6.0 but as we've seen above that /proc
doesn't honour the usual filesystem semantics, I can't guarantee it's race free (PID and inode number could possibly have been reused) or that it would work in future Linux versions.
Your original file exists completely unchanged.
Once a file has been opened by name, the file descriptor your process holds counts as a link to the file. The system does not release the file or its space until all links have been deleted: those can be any number of processes that have a file description open for it, plus any number of hard links.
You could stat the file at the time it was opened, and stat the current file by name. If they are different inodes or a different modification date, you have a deleted file and there is a new file. Or you might find you have a deleted file but no new one exists.