Re: [PATCH v3 00/12] man2: document "new" mount API

Askar Safin <safinaskar-yBVup5oSL51Wk0Htik3J/[email protected]>
Newsgroups gmane.linux.kernel.api,gmane.linux.kernel.autofs,gmane.linux.file-systems,gmane.linux.kernel,gmane.linux.man
Message-ID <[email protected]>
I just sent to fsdevel fix for that RESOLVE_NO_XDEV bug.

Aleksa Sarai <cyphar-gVpy/LI/[email protected]>:
> No, LOOKUP_AUTOMOUNT affects all components. I double-checked this with
> Christian.

No. I just tested this. See tests (and miniconfig) in the end of this message.

statx always follows automounts in non-final components no matter what.
I tested this. And it follows automounts in final component depending on
AT_NO_AUTOMOUNT. I tested this too. Also, absolutely all other syscalls always
follow automounts in non-final components no matter what. With sole exception
for openat2 with RESOLVE_NO_XDEV. I didn't test this, but I conclude this
by reading code.

First of all, LOOKUP_PARENT's doc in kernel currently is wrong:
https://elixir.bootlin.com/linux/v6.17-rc1/source/include/linux/namei.h#L31

We see there:
#define LOOKUP_PARENT    BIT(10)    /* Looking up final parent in path */

This is not true. LOOKUP_PARENT means that we are resolving any non-final
component. LOOKUP_PARENT is set when we enter link_path_walk, which
is used for resolving everything except for final component.
And LOOKUP_PARENT is cleared when we leave link_path_walk.

Now let's look here:
https://elixir.bootlin.com/linux/v6.17-rc1/source/fs/namei.c#L1447

	if (!(lookup_flags & (LOOKUP_PARENT | LOOKUP_DIRECTORY |
			   LOOKUP_OPEN | LOOKUP_CREATE | LOOKUP_AUTOMOUNT)) &&

We never return -EISDIR in this "if" if we are in non-final component
thanks to LOOKUP_PARENT here. We fall to finish_automount instead.

Again: if this is non-final component, then LOOKUP_PARENT is set, and thus
LOOKUP_AUTOMOUNT is ignored. If this is final component, then LOOKUP_AUTOMOUNT
may affect things.

Code below tests that:
- statx always follows non-final automounts
- statx follow final automounts depending on options

The code doesn't test other syscalls, they can be added if needed.

The code was tested in Qemu on Linux 6.17-rc1.

I'm not trying to insult you in any way.

Again: thank you a lot for your work! For openat2 and for these mans.

Askar Safin

====

miniconfig:

CONFIG_64BIT=y

CONFIG_EXPERT=y

CONFIG_PRINTK=y
CONFIG_PRINTK_TIME=y

CONFIG_TTY=y
CONFIG_VT=y
CONFIG_VT_CONSOLE=y
CONFIG_FRAMEBUFFER_CONSOLE=y

CONFIG_PROC_FS=y
CONFIG_DEVTMPFS=y
CONFIG_SYSFS=y
CONFIG_TMPFS=y
CONFIG_DEBUG_FS=y
CONFIG_USER_EVENTS=y
CONFIG_FTRACE=y
CONFIG_MULTIUSER=y
CONFIG_NAMESPACES=y
CONFIG_USER_NS=y
CONFIG_PID_NS=y


CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y

CONFIG_BLK_DEV_INITRD=y
CONFIG_RD_GZIP=y

CONFIG_BINFMT_ELF=y
CONFIG_BINFMT_SCRIPT=y

CONFIG_TRACEFS_AUTOMOUNT_DEPRECATED=y

CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y

====

/*
Author: Askar Safin
Public domain

Make sure your kernel is compiled with CONFIG_TRACEFS_AUTOMOUNT_DEPRECATED=y

If all tests pass, the program
should print "All tests passed".
Any other output means that something gone wrong.

This program requires root in initial user namespace
*/

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <linux/openat2.h>

#define MY_ASSERT(cond) do { \
    if (!(cond)) { \
        fprintf (stderr, "%s: assertion failed\n", #cond); \
        exit (1); \
    } \
} while (0)

bool
tracing_mounted (void)
{
    struct statx tracing;
    if (statx (AT_FDCWD, "/tmp/debugfs/tracing", AT_NO_AUTOMOUNT, 0, &tracing) != 0)
        {
            perror ("statx tracing");
            exit (1);
        }
    if (!(tracing.stx_attributes_mask & STATX_ATTR_MOUNT_ROOT))
        {
            fprintf (stderr, "???\n");
            exit (1);
        }
    return tracing.stx_attributes & STATX_ATTR_MOUNT_ROOT;
}

void
mount_debugfs (void)
{
    if (mount (NULL, "/tmp/debugfs", "debugfs", 0, NULL) != 0)
        {
            perror ("mount debugfs");
            exit (1);
        }
    MY_ASSERT (!tracing_mounted ());
}

void
umount_debugfs (void)
{
    umount ("/tmp/debugfs/tracing"); // Ignore errors
    if (umount ("/tmp/debugfs") != 0)
        {
            perror ("umount debugfs");
            exit (1);
        }
}

int
main (void)
{
    // Init
    {
        if (chdir ("/") != 0)
            {
                perror ("chdir /");
                exit (1);
            }
        if (unshare (CLONE_NEWNS) != 0)
            {
                perror ("unshare");
                exit (1);
            }
        if (mount (NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0)
            {
                perror ("mount(NULL, /, NULL, MS_REC | MS_PRIVATE, NULL)");
                exit (1);
            }
        if (mount (NULL, "/tmp", "tmpfs", 0, NULL) != 0)
            {
                perror ("mount tmpfs");
                exit (1);
            }
    }
    if (mkdir ("/tmp/debugfs", 0777) != 0)
        {
            perror ("mkdir(/tmp/debugfs)");
            exit (1);
        }

    // statx always follows automounts in non-final components. With AT_NO_AUTOMOUNT and without AT_NO_AUTOMOUNT
    {
        mount_debugfs();
        {
            struct statx readme;
            if (statx (AT_FDCWD, "/tmp/debugfs/tracing/README", 0, 0, &readme) != 0)
                {
                    perror ("statx");
                    exit (1);
                }
        }
        MY_ASSERT (tracing_mounted ());
        umount_debugfs();

        mount_debugfs();
        {
            struct statx readme;
            if (statx (AT_FDCWD, "/tmp/debugfs/tracing/README", AT_NO_AUTOMOUNT, 0, &readme) != 0)
                {
                    perror ("statx");
                    exit (1);
                }
        }
        MY_ASSERT (tracing_mounted ());
        umount_debugfs();
    }

    // statx follows automounts in final components if AT_NO_AUTOMOUNT is not specified
    {
        mount_debugfs();
        {
            struct statx tracing;
            if (statx (AT_FDCWD, "/tmp/debugfs/tracing", 0, 0, &tracing) != 0)
                {
                    perror ("statx");
                    exit (1);
                }
            if (!(tracing.stx_attributes_mask & STATX_ATTR_MOUNT_ROOT))
                {
                    fprintf (stderr, "???\n");
                    exit (1);
                }

            // Checking that this is new mount, not automount point itself
            MY_ASSERT (tracing.stx_attributes & STATX_ATTR_MOUNT_ROOT);
        }
        MY_ASSERT (tracing_mounted ());
        umount_debugfs ();

        mount_debugfs();
        {
            struct statx tracing;
            if (statx (AT_FDCWD, "/tmp/debugfs/tracing", AT_NO_AUTOMOUNT, 0, &tracing) != 0)
                {
                    perror ("statx");
                    exit (1);
                }
            if (!(tracing.stx_attributes_mask & STATX_ATTR_MOUNT_ROOT))
                {
                    fprintf (stderr, "???\n");
                    exit (1);
                }

            MY_ASSERT (!(tracing.stx_attributes & STATX_ATTR_MOUNT_ROOT));
        }
        MY_ASSERT (!tracing_mounted ());
        umount_debugfs ();
    }

    printf ("All tests passed\n");
    exit (0);
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.