getdents() 系统调用似乎在容器内返回不同的结果

getdents() 系统调用似乎在容器内返回不同的结果

我正在尝试读取文件的类型/dev/null。如果我使用stat()它,它会正确报告它是一个字符设备。

如果我使用getdents(),它还会报告它是一个字符设备 - 除非我在容器中运行它,在这种情况下它会说它是一个常规文件!

为什么在容器中运行它会产生不同的结果?

使用该镜像在最新版本的 docker 和 podman 上进行了测试,给出了相同的结果ubuntu:22.04

下面是复制代码 - 该stat()方法始终有效,但getdents在容器内运行时会导致断言失败。另外值得注意的是,代码并不总是被复制 - 在某些系统/容器上它似乎仍然可以正常工作。

(在linux 6.8.2-arch2-1和podman 5.0.0上测试)

#include <assert.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/syscall.h>

#define BUF_SIZE 1024

struct linux_dirent {
    long           d_ino;
    off_t          d_off;
    unsigned short d_reclen;
    char           d_name[];
};

int main() {
    // stat approach

    struct stat st;
    stat("/dev/null", &st);

    printf("stat type: %d\n", st.st_mode & S_IFMT);

    assert((st.st_mode & S_IFMT) == S_IFCHR);

    // getdirents approach

    int fd, nread;
    char buf[BUF_SIZE];
    struct linux_dirent *d;
    int bpos;
    char d_type;

    fd = open("/dev", O_RDONLY | O_DIRECTORY);

    for (;;) {
        nread = syscall(SYS_getdents, fd, buf, BUF_SIZE);

        for (bpos = 0; bpos < nread;) {
            d = (struct linux_dirent *)(buf + bpos);
            if (strcmp(d->d_name, "null") == 0) {
                d_type = *(buf + bpos + d->d_reclen - 1);
                printf("getdents type: %d\n", d_type);
                assert(d_type == DT_CHR);
                exit(EXIT_SUCCESS);
            }
            bpos += d->d_reclen;
        }
    }
    close(fd);

    exit(EXIT_SUCCESS);
}

答案1

事实证明,这getdirents就是告诉你的真相!

如果我们进入一个无根 podman 容器并运行mount,我们会看到这/dev/null实际上是一个绑定挂载(-v ...这里只是为了让我可以从容器内部访问您的示例代码):

$ podman run -it --rm  -v $PWD:/src:z fedora:39
[root@00af7efc8781 /]# mount |grep /dev/null
devtmpfs on /dev/null type devtmpfs (rw,nosuid,noexec,seclabel,size=4096k,nr_inodes=8186582,mode=755,inode64)

如果我们卸载该绑定安装,我们会看到什么?让我们来了解一下:

  • 首先,我们需要容器 pid:

    $ podman container inspect -l | jq .[0].State.Pid
    50502
    
  • 这样,我们就可以nsenter输入关联的 mount 和 pid 命名空间:

    $ sudo nsenter -t 50502 -m -p
    
  • 最后我们可以卸载/dev/null绑定挂载:

    [root@fizzgig /]# umount /dev/null
    

现在,我们看到:

[root@fizzgig /]# ls -l /dev/null
-rwx------. 1 21937 21937 0 Apr  2 20:03 /dev/null

令人惊讶的是,这是一个文件!


调用getdirents是从 读取目录条目/dev,这意味着它不知道绑定安装...因此您会看到d_type底层条目的 。

相关内容