我正在尝试读取文件的类型/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);
}
原来这
getdirents
告诉你的是实话!如果我们进入一个无根 podman 容器并运行
mount
,我们会看到这/dev/null
实际上是一个绑定安装(-v ...
这里只是为了让我可以从容器内部访问您的示例代码):如果我们卸载该绑定安装,我们会看到什么?让我们来了解一下:
首先,我们需要容器 pid:
这样,我们就可以
nsenter
输入关联的 mount 和 pid 命名空间:最后我们可以卸载
/dev/null
绑定挂载:现在,我们看到:
令人惊讶的是,这是一个文件!
调用
getdirents
是从 读取目录条目/dev
,这意味着它不知道绑定安装...因此您会看到d_type
底层条目的 。