如何在 FS_IOC_FIEMAP 中使用 ioctl

How to use ioctl with FS_IOC_FIEMAP

我的问题是处理稀疏文件读取并了解文件范围在哪里以围绕它执行一些逻辑。

因为没有直接的 API 调用来解决这些问题,我决定使用 ioctl api 来做到这一点。我从 cp 命令如何通过查看他们的代码处理复制稀疏文件的问题得到了这个想法,并最终看到了这个。

https://github.com/coreutils/coreutils/blob/df88fce71651afb2c3456967a142db0ae4bf9906/src/extent-scan.c#L112

因此,我尝试在用户 space 的示例程序 运行 中做同样的事情,但它出错了 "Invalid argument"。我不确定我遗漏了什么,或者用户 space 是否可以做到这一点。我在 ext4 文件系统上的 ubuntu 14.04 上 运行。这可能是设备驱动程序支持这些请求模式的问题吗?

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/fcntl.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <sys/ioctl.h>
    #include <linux/fs.h>
    #include "fiemap.h" //This is from https://github.com/coreutils/coreutils/blob/df88fce71651afb2c3456967a142db0ae4bf9906/src/fiemap.h

    int main(int argc, char* argv[]) {

        int input_fd;

        if(argc != 2){
            printf ("Usage: ioctl file1");
            return 1;
        }

        /* Create input file descriptor */
        input_fd = open (argv [1], O_RDWR);
        if (input_fd < 0) {
                perror ("open");
                return 2;
        }

        union { struct fiemap f; char c[4096]; } fiemap_buf;
        struct fiemap *fiemap = &fiemap_buf.f;
        int s = ioctl(input_fd, FS_IOC_FIEMAP, fiemap);

        if (s == 0) {
            printf("ioctl success\n");
        } else {
            printf("ioctl failure\n");
            char * errmsg = strerror(errno);
            printf("error: %d %s\n", errno, errmsg);
        }

        /* Close file descriptors */
        close (input_fd);

        return s;
    }

由于您在调用 ioctl() 之前未正确设置 fiemap_buf.f 参数,因此 EINVAL 可能来自 fiemap 无效内容而不是来自FS_IOC_FIEMAP 请求标识符支持自身。

例如,ioctl_fiemap()(来自内核)将评估 fiemap.fm_extent_count 以确定它是否大于 FIEMAP_MAX_EXTENTS 和 return -EINVAL 在这种情况下。由于没有对 fiemap 执行内存重置或参数化,这很可能是问题的根本原因。

请注意,根据您引用的 coreutils 代码,它在调用 ioctl() 之前执行 fiemap 的正确参数化:

  fiemap->fm_start = scan->scan_start;
  fiemap->fm_flags = scan->fm_flags;
  fiemap->fm_extent_count = count;
  fiemap->fm_length = FIEMAP_MAX_OFFSET - scan->scan_start;

请注意,不推荐使用 fiemap,因为您必须确保通过 FIEMAP_FLAG_SYNC,它有副作用。 lseek()、SEEK_DATA 和 SEEK_HOLE 接口是推荐的接口,但请注意,根据文件系统,将未写入的范围(分配的零)表示为空洞。