filesystemsfuse

How to tell fuse3 that you need a device and a mount point in command-line arguments


I'm using fuse3 to write a file system driver that should parse all the fuse options and then take two arguments, a device path (or file containing a file system image) and a mount-point.

Is there a convenient way to use fuse's command-line parsing to extract the device from the command line? I've taken to manipulating the arguments before I hand them to fuse like this:

    struct fuse_args args;
    const char *device = NULL;

    memset(&args, 0, sizeof(args));
    for (int i = 0; i < argc; ++i)
        if (i > 0 && i == argc - 2 && *argv[i] != '-')
            image = argv[i];
        else
            fuse_opt_add_arg(&args, argv[i]);

    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
        return 1;

    if (!device) {
        usage(argv[0]);
        fuse_opt_add_arg(&args, "--help");
    }

    fuse_main(args.argc, args.argv, &oper, nullptr);

But this is totally gross and doesn't quite work if the user specified only one argument, unless that argument also happens to be a valid mountpoint, because fuse seems to check the mountpoint viability before printing the help.

Surely this must be a common thing to want to do, so I'm wondering what the correct idiom is for such a file system.


Solution

  • so for this, you will have to restrict the user on the order of the cmd arguments(all fuse/mount options come before the device path and mount point). To make it simple, ensure that the path to the device and mount point are provided last:

    So in your main function, this statement will check that there is a correct number of arguments and that the mount point and device do not start with hyphens as with options.

        if ((argc < 3) || (argv[argc-2][0] == '-') || (argv[argc-1][0] == '-'))
        {
            fprintf(stderr, "usage:  ./myfuse [FUSE and mount options] devicepath mountPoint\n");
            abort();
        }
    

    Then extract the device path and store it in a struct:

    struct my_state *my_data;
    my_data = malloc(sizeof(struct my_state));
    if (my_data == NULL) {
        perror("main calloc");
        abort();
    }
    
    my_data->devicepath = realpath(argv[argc - 2], NULL);
        argv[argc-2] = argv[argc-1];
        argv[argc-1] = NULL;
        argc--;
    

    Notice that I remove the device paths from argv and decrement argc by 1 before passing them to the fuse_main function Then while calling the fuse_main function make sure to pass the my_data struct:

    fuse_main(argc, argv, &my_fuse_operations, my_data);
    

    Here is the definition of the my_state struct which you can put in a header file:

    struct my_state{
        char *devicepath;
    };
    

    You should also add this definition in the header file below the struct definition:

    #define BB_DATA ((struct my_state *) fuse_get_context()->private_data)
    

    And also in your init function call fuse_get_context and return BB_DATA:

    void *my_init()
    {
        fuse_get_context();
        return BB_DATA;
    }
    

    The return value will be passed in the private_data field of fuse_context to all file operations and as a parameter to the destroy() method. The fuse_context is set up before this function is called, and fuse_get_context()->private_data returns the user_data passed to fuse_main().