nix-rust / nix

Rust friendly bindings to *nix APIs
MIT License
2.63k stars 660 forks source link

Does nix support mount for MacOS? #1192

Open pwang7 opened 4 years ago

pwang7 commented 4 years ago

I'm trying nix to make some syscalls, instead of using libc. But it seems nix has no mount for MacOS, am I right?

asomers commented 4 years ago

Yep. Though it probably wouldn't be hard to extend it to work on OSX, too.

pwang7 commented 4 years ago

@asomers could you please advise me how to extend mount to work on OSX?

asomers commented 4 years ago

Uh, not really. I don't have a Mac and I haven't used one since 10.3. You'll have to figure out how mount works on that platform.

pwang7 commented 4 years ago

Let me explain what I found first.

It seems mount(2) differs a lot w.r.t. Linux and OSX. Linux: int mount(const char *source, const char *target, const char *filesystemtype, unsigned long mountflags, const void *data); 5 input arguments; OSX: int mount(const char *type, const char *dir, int flags, void *data); 4 input arguments.

The trick part is the last input argument of mount(2), void *data, which has different meaning w.r.t. Linux and OSX. According to Linux man page, the data argument is a c-string of comma-separated options understood by this filesystem. Whereas, under OSX, the data argument is a pointer to a structure that contains the type specific arguments to mount, and the format for these argument structures is different w.r.t. each filesystem.

Another thing is the mount flags are different w.r.t. Linux and OSX.

I used libc::mount() under OSX to successfully mount a Filesystem in User Space (FUSE), which requires a struct as following:

struct fuse_mount_args {
    char     mntpath[MAXPATHLEN];        // path to the mount point
    char     fsname[MAXPATHLEN];         // file system description string
    char     fstypename[MFSTYPENAMELEN]; // file system type name
    char     volname[MAXPATHLEN];        // volume name
    uint64_t altflags;                   // see mount-time flags below
    uint32_t blocksize;                  // fictitious block size of our "storage"
    uint32_t daemon_timeout;             // timeout in seconds for upcalls to daemon
    uint32_t fsid;                       // optional custom value for part of fsid[0]
    uint32_t fssubtype;                  // file system sub type id
    uint32_t iosize;                     // maximum size for reading or writing
    uint32_t random;                     // random "secret" from device
    uint32_t rdev;                       // dev_t for the /dev/osxfuse{n} in question
};

Whereas it seems the struct to mount Mac HFS under OSX is something else, which I haven't tried yet:

struct hfs_mount_args {
    char     *fspec;                  // The device to mount
    uid_t     hfs_uid;
    gid_t     hfs_gid;
    mode_t    hfs_mask;
    u_int32_t hfs_encoding;
    struct    timezone hfs_timezone;
    int       flags;
    int       journal_tbuffer_size;
    int       journal_flags;
    int       journal_disable;
};

Although libc::mount should work under OSX, I found it's very hard to debug (no much documentation) and unsafe. It seems lib::mount is a thin wrapper to syscalls. Another painful thing is when doing Rust FFI with C, handling char str[MAX_LENGTH] is less inconvenient than char *str.

I wish I could contribute to nix about mount under OSX, but I'm new to nix. That's why I ask for your advise @asomers.

Thanks!

asomers commented 4 years ago

Yes, your analysis is correct. libc only provides a very thin wrapper around native C library calls (which are themselves usually thin wrappers around syscalls). The mount function is very different between operating systems, and the C structures can be awkward. The good news is that you don't have to worry about cross-platform compatibility. Nix should expose mount in the way that makes the most sense for each operating system. For OSX (and FreeBSD, which has the same interface), I suggest something like this:

trait MountData {
    fn fstype() -> &'static OsStr;
    fn as_data(&self) -> *const c_void;
}
libc_bitflags!(
    pub struct MntFlags: c_int {
        MNT_RDONLY;
        ...
    }
);
fn mount<MD: MountData, P: ?Sized + NixPath>(dir: &P, flags: MntFlags, data: &MD) -> Result<()> {
    ...
}

Does that sound good?

pwang7 commented 4 years ago

Cool, your design looks good to me.

One more thing is the mount flags are also different w.r.t. Linux and OSX (or BSD). Linux mount flags:

#define MS_RDONLY    1  /* Mount read-only */
#define MS_NOSUID    2  /* Ignore suid and sgid bits */
...

BSD mount flags:

#define MNT_RDONLY  0x00000001  /* read only filesystem */
#define MNT_SYNCHRONOUS 0x00000002  /* file system written synchronously */
...

It seems libc doesn't define all of MNT_* flags, should the MNT_* flags be defined in nix? Or do they have to be defined in libc first?

BTW, I checked the nix mount.rs file, is it the right place to start with?

asomers commented 4 years ago

Those flags should all be defined in libc. If they aren't there already, then you'll have to submit a PR to libc to add them. And yes, mount.rs is the right place.

pwang7 commented 4 years ago

OK, I'll deal with libc first, thanks Alan!

pwang7 commented 4 years ago

Just to follow up, I've added macOS mount flags to libc, https://github.com/rust-lang/libc/pull/1690