Broken symlinks and a mysterious (deleted)
Asked Answered
O

2

6

I've been doing stuff with the proc filesystem on linux, and I've come across some behavior I'd like to have clarified.

Each process in /proc has a symlink to it's executable file, /proc/{pid}/exe. If a process continues to run after it's executable has been deleted, reading this symlink will return the path to the executable, with (deleted) appended to the end.

Running this command you may even see a few on your system:

grep '(deleted)' <(for dir in $(ls /proc | grep -E '^[0-9]+'); do echo "$dir $(readlink /proc/$dir/exe)"; done)

I tried recreating this behavior with some simple bash commands:

>>> echo "temporary file" >> tmpfile.test
>>> ln -s tmpfile.test tmpfile.link
>>> rm tmpfile.test
>>> readlink tmpfile.link
tmpfile.test

There is no (deleted) appended to the name! Trying a cat tmpfile.link confirms that the link is broken (cat: tmpfile.link: No such file or directory).

However, the other day this same test did result in a (deleted) being appended to the output of readlink. What gives?

Here is what I would like to know:

  • Is there a sequences of events that guarantees (deleted) will be appended to the name?
  • Why does /proc/{pid}/exe show (deleted) for removed executables?
  • How can I get the name of an executable through /proc/{pid}/exe without any appended (deleted) and guarantee that the original executable wasn't just named some_executable (deleted)?
Osmen answered 4/6, 2014 at 14:4 Comment(0)
D
5

It is not readlink, but Linux changes the symlink to point to <filename> (deleted), i.e., (deleted) gets appended to the target of the link.

Doxology answered 4/6, 2014 at 14:16 Comment(1)
Not quite the answer I'm looking for, but it set me off in the right direction.Osmen
R
5

FWIW, the special <filename> (deleted) behavior is implemented in the Linux kernel function d_path() here: https://elixir.bootlin.com/linux/v4.1.13/source/fs/dcache.c#L3080.

The source comments (in the snippet below) suggest the special behavior only applies to names (paths) generated on-the-fly for some 'synthetic filesystems' (e.g. procfs) and 'psuedo inodes'.

/**
 * d_path - return the path of a dentry
 * @path: path to report
 * @buf: buffer to return value in
 * @buflen: buffer length
 *
 * Convert a dentry into an ASCII path name. If the entry has been deleted
 * the string " (deleted)" is appended. Note that this is ambiguous.
 *
 * Returns a pointer into the buffer or an error code if the path was
 * too long. Note: Callers should use the returned pointer, not the passed
 * in buffer, to use the name! The implementation often starts at an offset
 * into the buffer, and may leave 0 bytes at the start.
 *
 * "buflen" should be positive.
 */
char *d_path(const struct path *path, char *buf, int buflen)
{
    char *res = buf + buflen;
    struct path root;
    int error;

    /*
     * We have various synthetic filesystems that never get mounted.  On
     * these filesystems dentries are never used for lookup purposes, and
     * thus don't need to be hashed.  They also don't need a name until a
     * user wants to identify the object in /proc/pid/fd/.  The little hack
     * below allows us to generate a name for these objects on demand:
     *
     * Some pseudo inodes are mountable.  When they are mounted
     * path->dentry == path->mnt->mnt_root.  In that case don't call d_dname
     * and instead have d_path return the mounted path.
     */
    if (path->dentry->d_op && path->dentry->d_op->d_dname &&
        (!IS_ROOT(path->dentry) || path->dentry != path->mnt->mnt_root))
        return path->dentry->d_op->d_dname(path->dentry, buf, buflen);

    rcu_read_lock();
    get_fs_root_rcu(current->fs, &root);
    error = path_with_deleted(path, &root, &res, &buflen);
    rcu_read_unlock();

    if (error < 0)
        res = ERR_PTR(error);
    return res;
}
Ripping answered 25/9, 2019 at 19:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.