4

My problem is to deal with sparse file reads and understand where the extents of the file are to perform some logic around it.

Since, there is no direct API call to figure these stuff out, I decided to use ioctl api to do this. I got the idea from how cp command deals with problems of copying over sparse files by going through their code and ended up seeing this.

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

So, I tried to do the same thing in my sample program running in user space and it errors out with "Invalid argument". I am not sure what I am missing or if this is even possible from userspace. I am running on ubuntu 14.04 on an ext4 file system. Could this be a problem with device driver supporting these request modes underneath?

    #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;
    }
Aila
  • 319
  • 1
  • 4
  • 11

2 Answers2

3

As you're not properly setting the fiemap_buf.f parameters before invoking ioctl(), it is likely that the EINVAL is coming from the fiemap invalid contents than from the FS_IOC_FIEMAP request identifier support itself.

For instance, the ioctl_fiemap() (from kernel) will evaluate the fiemap.fm_extent_count in order to determine if it is greater than FIEMAP_MAX_EXTENTS and return -EINVAL in that case. Since no memory reset nor parameterization is being performed on fiemap, this is very likely the root cause of the problem.

Note that from the coreutils code you referenced, it performs the correct parameterization of fiemap before calling ioctl():

  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;
pah
  • 4,700
  • 6
  • 28
  • 37
1

Note fiemap is not recommended as you have to be sure to pass FIEMAP_FLAG_SYNC which has side effects. The lseek(), SEEK_DATA and SEEK_HOLE interface is the recommended one, though note that will, depending on file system, represent unwritten extents (allocated zeros) as holes.

pixelbeat
  • 30,615
  • 9
  • 51
  • 60
  • Thanks for the suggestion. We did try SEEK_DATA and SEEK_HOLE with lseek, but it looks like it is supported only from a higher linux kernel version for xfs file system than the one we are on. So, we had to resort to the ioctl way. I am kind of new to this low level programming, could you advice on what could be the side effects with the FIEMAP_FLAG_SYNC flag? – Aila Jul 31 '16 at 01:26
  • syncing can have large performance implications and should be avoided where possible – pixelbeat Jul 31 '16 at 22:51