package ext import ( "os" "runtime" "syscall" "unsafe" ) // include/uapi/linux/mount.h /* * move_mount() flags. */ const ( MOVE_MOUNT_F_SYMLINKS = 1 << iota /* Follow symlinks on from path */ MOVE_MOUNT_F_AUTOMOUNTS /* Follow automounts on from path */ MOVE_MOUNT_F_EMPTY_PATH /* Empty from path permitted */ _ MOVE_MOUNT_T_SYMLINKS /* Follow symlinks on to path */ MOVE_MOUNT_T_AUTOMOUNTS /* Follow automounts on to path */ MOVE_MOUNT_T_EMPTY_PATH /* Empty to path permitted */ _ MOVE_MOUNT_SET_GROUP /* Set sharing group instead */ MOVE_MOUNT_BENEATH /* Mount beneath top mount */ ) /* * fsopen() flags. */ const ( FSOPEN_CLOEXEC = 1 << iota ) /* * fspick() flags. */ const ( FSPICK_CLOEXEC = 1 << iota FSPICK_SYMLINK_NOFOLLOW FSPICK_NO_AUTOMOUNT FSPICK_EMPTY_PATH ) /* * The type of fsconfig() call made. */ const ( FSCONFIG_SET_FLAG = iota /* Set parameter, supplying no value */ FSCONFIG_SET_STRING /* Set parameter, supplying a string value */ FSCONFIG_SET_BINARY /* Set parameter, supplying a binary blob value */ FSCONFIG_SET_PATH /* Set parameter, supplying an object by path */ FSCONFIG_SET_PATH_EMPTY /* Set parameter, supplying an object by (empty) path */ FSCONFIG_SET_FD /* Set parameter, supplying an object by fd */ FSCONFIG_CMD_CREATE /* Create new or reuse existing superblock */ FSCONFIG_CMD_RECONFIGURE /* Invoke superblock reconfiguration */ FSCONFIG_CMD_CREATE_EXCL /* Create new superblock, fail if reusing existing superblock */ ) /* * fsmount() flags. */ const ( FSMOUNT_CLOEXEC = 1 << iota ) /* * Mount attributes. */ const ( MOUNT_ATTR_RDONLY = 0x00000001 /* Mount read-only */ MOUNT_ATTR_NOSUID = 0x00000002 /* Ignore suid and sgid bits */ MOUNT_ATTR_NODEV = 0x00000004 /* Disallow access to device special files */ MOUNT_ATTR_NOEXEC = 0x00000008 /* Disallow program execution */ MOUNT_ATTR__ATIME = 0x00000070 /* Setting on how atime should be updated */ MOUNT_ATTR_RELATIME = 0x00000000 /* - Update atime relative to mtime/ctime. */ MOUNT_ATTR_NOATIME = 0x00000010 /* - Do not update access times. */ MOUNT_ATTR_STRICTATIME = 0x00000020 /* - Always perform atime updates */ MOUNT_ATTR_NODIRATIME = 0x00000080 /* Do not update directory access times */ MOUNT_ATTR_IDMAP = 0x00100000 /* Idmap mount to @userns_fd in struct mount_attr. */ MOUNT_ATTR_NOSYMFOLLOW = 0x00200000 /* Do not follow symlinks */ ) // FS provides low-level wrappers around the suite of file-descriptor-based // mount facilities in Linux. type FS struct { fd uintptr c runtime.Cleanup } // newFS allocates a new [FS] for the specified fd. func newFS(fd uintptr) *FS { fs := FS{fd: fd} fs.c = runtime.AddCleanup(&fs, func(fd uintptr) { _ = syscall.Close(int(fd)) }, fd) return &fs } // Close closes the underlying filesystem context. func (fs *FS) Close() error { if fs == nil { return syscall.EINVAL } err := syscall.Close(int(fs.fd)) fs.c.Stop() return err } // OpenFS creates a new filesystem context. func OpenFS(fsname string, flags int) (fs *FS, err error) { var s *byte s, err = syscall.BytePtrFromString(fsname) if err != nil { return } fd, _, errno := syscall.Syscall( SYS_FSOPEN, uintptr(unsafe.Pointer(s)), uintptr(flags|FSOPEN_CLOEXEC), 0, ) if errno != 0 { err = os.NewSyscallError("fsopen", errno) } else { fs = newFS(fd) } return } // PickFS selects filesystem for reconfiguration. func PickFS(dirfd int, pathname string, flags int) (fs *FS, err error) { var s *byte s, err = syscall.BytePtrFromString(pathname) if err != nil { return } fd, _, errno := syscall.Syscall( SYS_FSPICK, uintptr(dirfd), uintptr(unsafe.Pointer(s)), uintptr(flags|FSPICK_CLOEXEC), ) if errno != 0 { err = os.NewSyscallError("fspick", errno) } else { fs = newFS(fd) } return } // config configures new or existing filesystem context. func (fs *FS) config(cmd uint, key *byte, value unsafe.Pointer, aux int) (err error) { _, _, errno := syscall.Syscall6( SYS_FSCONFIG, fs.fd, uintptr(cmd), uintptr(unsafe.Pointer(key)), uintptr(value), uintptr(aux), 0, ) if errno != 0 { err = os.NewSyscallError("fsconfig", errno) } return } // SetFlag sets the flag parameter named by key. ([FSCONFIG_SET_FLAG]) func (fs *FS) SetFlag(key string) (err error) { var s *byte s, err = syscall.BytePtrFromString(key) if err != nil { return } return fs.config(FSCONFIG_SET_FLAG, s, nil, 0) } // SetString sets the string parameter named by key to the value specified by // value. ([FSCONFIG_SET_STRING]) func (fs *FS) SetString(key, value string) (err error) { var s0 *byte s0, err = syscall.BytePtrFromString(key) if err != nil { return } var s1 *byte s1, err = syscall.BytePtrFromString(value) if err != nil { return } return fs.config(FSCONFIG_SET_STRING, s0, unsafe.Pointer(s1), 0) } // mount instantiates mount object from filesystem context. func (fs *FS) mount(flags, attrFlags int) (fsfd int, err error) { r, _, errno := syscall.Syscall( SYS_FSMOUNT, fs.fd, uintptr(flags|FSMOUNT_CLOEXEC), uintptr(attrFlags), ) fsfd = int(r) if errno != 0 { err = os.NewSyscallError("fsmount", errno) } return } // MoveMount moves or attaches mount object to filesystem. func MoveMount( fromDirfd int, fromPathname string, toDirfd int, toPathname string, flags int, ) (err error) { var s0 *byte s0, err = syscall.BytePtrFromString(fromPathname) if err != nil { return } var s1 *byte s1, err = syscall.BytePtrFromString(toPathname) if err != nil { return } _, _, errno := syscall.Syscall6( SYS_MOVE_MOUNT, uintptr(fromDirfd), uintptr(unsafe.Pointer(s0)), uintptr(toDirfd), uintptr(unsafe.Pointer(s1)), uintptr(flags), 0, ) if errno != 0 { err = os.NewSyscallError("move_mount", errno) } return } // Mount attaches the underlying filesystem context to the specified pathname. func (fs *FS) Mount(pathname string, attrFlags int) error { if err := fs.config(FSCONFIG_CMD_CREATE_EXCL, nil, nil, 0); err != nil { return err } fd, err := fs.mount(0, attrFlags) if err != nil { return err } err = MoveMount( fd, "", -1, pathname, MOVE_MOUNT_F_EMPTY_PATH, ) closeErr := syscall.Close(fd) if err == nil { err = closeErr } return err }