2015-07-24 08:45:16 +03:00
|
|
|
package fuse
|
2015-07-24 08:39:04 +03:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2023-07-03 23:03:22 +03:00
|
|
|
"log"
|
2015-07-24 08:39:04 +03:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"syscall"
|
2015-07-24 08:48:23 +03:00
|
|
|
|
|
|
|
"github.com/jacobsa/fuse/internal/buffer"
|
2023-07-03 23:11:17 +03:00
|
|
|
"github.com/jacobsa/fuse/internal/fusekernel"
|
2015-07-24 08:39:04 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
var errNoAvail = errors.New("no available fuse devices")
|
2016-09-25 19:07:29 +03:00
|
|
|
var errNotLoaded = errors.New("osxfuse is not loaded")
|
2015-07-24 08:39:04 +03:00
|
|
|
|
2016-09-25 19:07:29 +03:00
|
|
|
// errOSXFUSENotFound is returned from Mount when the OSXFUSE installation is
|
2016-10-16 10:56:34 +03:00
|
|
|
// not detected. Make sure OSXFUSE is installed.
|
2016-09-25 19:07:29 +03:00
|
|
|
var errOSXFUSENotFound = errors.New("cannot locate OSXFUSE")
|
|
|
|
|
2016-10-16 10:56:44 +03:00
|
|
|
// osxfuseInstallation describes the paths used by an installed OSXFUSE
|
|
|
|
// version.
|
|
|
|
type osxfuseInstallation struct {
|
2016-10-16 10:56:40 +03:00
|
|
|
// Prefix for the device file. At mount time, an incrementing number is
|
|
|
|
// suffixed until a free FUSE device is found.
|
|
|
|
DevicePrefix string
|
|
|
|
|
|
|
|
// Path of the load helper, used to load the kernel extension if no device
|
|
|
|
// files are found.
|
|
|
|
Load string
|
|
|
|
|
|
|
|
// Path of the mount helper, used for the actual mount operation.
|
|
|
|
Mount string
|
|
|
|
|
|
|
|
// Environment variable used to pass the path to the executable calling the
|
|
|
|
// mount helper.
|
|
|
|
DaemonVar string
|
2021-08-18 09:55:49 +03:00
|
|
|
|
|
|
|
// Environment variable used to pass the "called by library" flag.
|
|
|
|
LibVar string
|
2021-10-19 19:50:09 +03:00
|
|
|
|
|
|
|
// Open device manually (false) or receive the FD through a UNIX socket,
|
|
|
|
// like with fusermount (true)
|
|
|
|
UseCommFD bool
|
2016-10-16 10:56:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2016-10-16 10:56:44 +03:00
|
|
|
osxfuseInstallations = []osxfuseInstallation{
|
2021-08-18 09:55:49 +03:00
|
|
|
// v4
|
|
|
|
{
|
|
|
|
DevicePrefix: "/dev/macfuse",
|
|
|
|
Load: "/Library/Filesystems/macfuse.fs/Contents/Resources/load_macfuse",
|
|
|
|
Mount: "/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse",
|
|
|
|
DaemonVar: "_FUSE_DAEMON_PATH",
|
|
|
|
LibVar: "_FUSE_CALL_BY_LIB",
|
2021-10-19 19:50:09 +03:00
|
|
|
UseCommFD: true,
|
2021-08-18 09:55:49 +03:00
|
|
|
},
|
|
|
|
|
2016-10-16 10:56:40 +03:00
|
|
|
// v3
|
|
|
|
{
|
|
|
|
DevicePrefix: "/dev/osxfuse",
|
|
|
|
Load: "/Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse",
|
|
|
|
Mount: "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse",
|
|
|
|
DaemonVar: "MOUNT_OSXFUSE_DAEMON_PATH",
|
2021-08-18 09:55:49 +03:00
|
|
|
LibVar: "MOUNT_OSXFUSE_CALL_BY_LIB",
|
2016-10-16 10:56:40 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
// v2
|
|
|
|
{
|
|
|
|
DevicePrefix: "/dev/osxfuse",
|
|
|
|
Load: "/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs",
|
|
|
|
Mount: "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs",
|
|
|
|
DaemonVar: "MOUNT_FUSEFS_DAEMON_PATH",
|
2021-08-18 09:55:49 +03:00
|
|
|
LibVar: "MOUNT_FUSEFS_CALL_BY_LIB",
|
2016-10-16 10:56:40 +03:00
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2023-07-03 23:03:22 +03:00
|
|
|
const FUSET_SRV_PATH = "/usr/local/bin/go-nfsv4"
|
|
|
|
|
2016-09-25 19:07:29 +03:00
|
|
|
func loadOSXFUSE(bin string) error {
|
|
|
|
cmd := exec.Command(bin)
|
2015-07-24 08:39:04 +03:00
|
|
|
cmd.Dir = "/"
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
err := cmd.Run()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-09-25 19:07:29 +03:00
|
|
|
func openOSXFUSEDev(devPrefix string) (dev *os.File, err error) {
|
2015-07-24 08:48:17 +03:00
|
|
|
// Try each device name.
|
2015-07-24 08:39:04 +03:00
|
|
|
for i := uint64(0); ; i++ {
|
2016-09-25 19:07:29 +03:00
|
|
|
path := devPrefix + strconv.FormatUint(i, 10)
|
2015-07-24 08:48:17 +03:00
|
|
|
dev, err = os.OpenFile(path, os.O_RDWR, 0000)
|
2015-07-24 08:39:04 +03:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
if i == 0 {
|
2015-07-24 08:48:17 +03:00
|
|
|
// Not even the first device was found. Fuse must not be loaded.
|
2020-01-28 12:10:08 +03:00
|
|
|
return nil, errNotLoaded
|
2015-07-24 08:39:04 +03:00
|
|
|
}
|
|
|
|
|
2015-07-24 08:48:17 +03:00
|
|
|
// Otherwise we've run out of kernel-provided devices
|
2020-01-28 12:10:08 +03:00
|
|
|
return nil, errNoAvail
|
2015-07-24 08:39:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY {
|
2015-07-24 08:48:17 +03:00
|
|
|
// This device is in use; try the next one.
|
2015-07-24 08:39:04 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-01-28 12:10:08 +03:00
|
|
|
return dev, nil
|
2015-07-24 08:39:04 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-19 19:50:09 +03:00
|
|
|
func convertMountArgs(daemonVar string, libVar string,
|
|
|
|
cfg *MountConfig) ([]string, []string, error) {
|
2015-07-24 08:39:04 +03:00
|
|
|
|
2015-07-24 08:51:18 +03:00
|
|
|
// The mount helper doesn't understand any escaping.
|
2015-07-24 08:54:02 +03:00
|
|
|
for k, v := range cfg.toMap() {
|
2015-07-24 08:39:04 +03:00
|
|
|
if strings.Contains(k, ",") || strings.Contains(v, ",") {
|
2021-10-19 19:50:09 +03:00
|
|
|
return nil, nil, fmt.Errorf(
|
2015-07-24 08:51:18 +03:00
|
|
|
"mount options cannot contain commas on darwin: %q=%q",
|
|
|
|
k,
|
|
|
|
v)
|
2015-07-24 08:39:04 +03:00
|
|
|
}
|
|
|
|
}
|
2015-07-24 08:51:18 +03:00
|
|
|
|
2021-11-08 17:02:43 +03:00
|
|
|
env := []string{libVar + "="}
|
2021-10-19 19:50:09 +03:00
|
|
|
if daemonVar != "" {
|
|
|
|
env = append(env, daemonVar+"="+os.Args[0])
|
|
|
|
}
|
|
|
|
argv := []string{
|
2015-07-24 08:54:02 +03:00
|
|
|
"-o", cfg.toOptionsString(),
|
2015-07-24 08:39:04 +03:00
|
|
|
// Tell osxfuse-kext how large our buffer is. It must split
|
|
|
|
// writes larger than this into multiple writes.
|
|
|
|
//
|
|
|
|
// OSXFUSE seems to ignore InitResponse.MaxWrite, and uses
|
|
|
|
// this instead.
|
2021-11-08 17:02:43 +03:00
|
|
|
"-o", "iosize=" + strconv.FormatUint(buffer.MaxWriteSize, 10),
|
2021-10-19 19:50:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return argv, env, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func callMount(
|
|
|
|
bin string,
|
|
|
|
daemonVar string,
|
|
|
|
libVar string,
|
|
|
|
dir string,
|
|
|
|
cfg *MountConfig,
|
|
|
|
dev *os.File,
|
|
|
|
ready chan<- error) error {
|
|
|
|
|
|
|
|
argv, env, err := convertMountArgs(daemonVar, libVar, cfg)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call the mount helper, passing in the device file and saving output into a
|
|
|
|
// buffer.
|
|
|
|
argv = append(argv,
|
2015-07-24 08:39:04 +03:00
|
|
|
// refers to fd passed in cmd.ExtraFiles
|
|
|
|
"3",
|
|
|
|
dir,
|
|
|
|
)
|
2021-10-19 19:50:09 +03:00
|
|
|
cmd := exec.Command(bin, argv...)
|
2015-07-24 08:51:18 +03:00
|
|
|
cmd.ExtraFiles = []*os.File{dev}
|
2021-10-19 19:50:09 +03:00
|
|
|
cmd.Env = env
|
2015-07-24 08:51:18 +03:00
|
|
|
|
2015-07-24 08:39:04 +03:00
|
|
|
var buf bytes.Buffer
|
|
|
|
cmd.Stdout = &buf
|
|
|
|
cmd.Stderr = &buf
|
|
|
|
|
2020-01-28 12:10:08 +03:00
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
return err
|
2015-07-24 08:39:04 +03:00
|
|
|
}
|
2015-07-24 08:51:18 +03:00
|
|
|
|
|
|
|
// In the background, wait for the command to complete.
|
2015-07-24 08:39:04 +03:00
|
|
|
go func() {
|
|
|
|
err := cmd.Wait()
|
|
|
|
if err != nil {
|
|
|
|
if buf.Len() > 0 {
|
|
|
|
output := buf.Bytes()
|
|
|
|
output = bytes.TrimRight(output, "\n")
|
2015-07-24 08:51:18 +03:00
|
|
|
err = fmt.Errorf("%v: %s", err, output)
|
2015-07-24 08:39:04 +03:00
|
|
|
}
|
|
|
|
}
|
2015-07-24 08:51:18 +03:00
|
|
|
|
|
|
|
ready <- err
|
2015-07-24 08:39:04 +03:00
|
|
|
}()
|
2015-07-24 08:51:18 +03:00
|
|
|
|
2020-01-28 12:10:08 +03:00
|
|
|
return nil
|
2015-07-24 08:39:04 +03:00
|
|
|
}
|
|
|
|
|
2021-10-19 19:50:09 +03:00
|
|
|
func callMountCommFD(
|
|
|
|
bin string,
|
|
|
|
daemonVar string,
|
|
|
|
libVar string,
|
|
|
|
dir string,
|
|
|
|
cfg *MountConfig) (*os.File, error) {
|
|
|
|
|
|
|
|
argv, env, err := convertMountArgs(daemonVar, libVar, cfg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
env = append(env, "_FUSE_COMMVERS=2")
|
|
|
|
argv = append(argv, dir)
|
|
|
|
|
2022-09-06 09:54:02 +03:00
|
|
|
return fusermount(bin, argv, env, false, cfg.DebugLogger)
|
2021-10-19 19:50:09 +03:00
|
|
|
}
|
|
|
|
|
2015-07-24 08:45:16 +03:00
|
|
|
// Begin the process of mounting at the given directory, returning a connection
|
|
|
|
// to the kernel. Mounting continues in the background, and is complete when an
|
|
|
|
// error is written to the supplied channel. The file system may need to
|
|
|
|
// service the connection in order for mounting to complete.
|
2023-07-03 23:03:22 +03:00
|
|
|
func mountOsxFuse(
|
2015-07-24 08:45:16 +03:00
|
|
|
dir string,
|
2015-07-24 08:54:02 +03:00
|
|
|
cfg *MountConfig,
|
2015-07-24 08:45:16 +03:00
|
|
|
ready chan<- error) (dev *os.File, err error) {
|
2016-10-16 10:56:34 +03:00
|
|
|
// Find the version of osxfuse installed on this machine.
|
2016-10-16 10:56:44 +03:00
|
|
|
for _, loc := range osxfuseInstallations {
|
2016-09-25 19:07:29 +03:00
|
|
|
if _, err := os.Stat(loc.Mount); os.IsNotExist(err) {
|
|
|
|
// try the other locations
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-10-19 19:50:09 +03:00
|
|
|
if loc.UseCommFD {
|
|
|
|
// Call the mount binary with the device.
|
|
|
|
ready <- nil
|
|
|
|
dev, err = callMountCommFD(loc.Mount, loc.DaemonVar, loc.LibVar, dir, cfg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("callMount: %v", err)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-09-25 19:07:29 +03:00
|
|
|
// Open the device.
|
|
|
|
dev, err = openOSXFUSEDev(loc.DevicePrefix)
|
|
|
|
|
|
|
|
// Special case: we may need to explicitly load osxfuse. Load it, then
|
|
|
|
// try again.
|
|
|
|
if err == errNotLoaded {
|
|
|
|
err = loadOSXFUSE(loc.Load)
|
|
|
|
if err != nil {
|
2020-01-28 12:10:08 +03:00
|
|
|
return nil, fmt.Errorf("loadOSXFUSE: %v", err)
|
2016-09-25 19:07:29 +03:00
|
|
|
}
|
2015-07-24 08:45:16 +03:00
|
|
|
|
2016-09-25 19:07:29 +03:00
|
|
|
dev, err = openOSXFUSEDev(loc.DevicePrefix)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Propagate errors.
|
2015-07-24 08:39:04 +03:00
|
|
|
if err != nil {
|
2020-01-28 12:10:08 +03:00
|
|
|
return nil, fmt.Errorf("openOSXFUSEDev: %v", err)
|
2015-07-24 08:39:04 +03:00
|
|
|
}
|
2015-07-24 08:45:16 +03:00
|
|
|
|
2016-09-25 19:07:29 +03:00
|
|
|
// Call the mount binary with the device.
|
2021-08-18 09:55:49 +03:00
|
|
|
if err := callMount(loc.Mount, loc.DaemonVar, loc.LibVar, dir, cfg, dev, ready); err != nil {
|
2016-09-25 19:07:29 +03:00
|
|
|
dev.Close()
|
2020-01-28 12:10:08 +03:00
|
|
|
return nil, fmt.Errorf("callMount: %v", err)
|
2016-09-25 19:07:29 +03:00
|
|
|
}
|
2015-07-24 08:45:16 +03:00
|
|
|
|
2020-01-28 12:10:08 +03:00
|
|
|
return dev, nil
|
2015-07-24 08:39:04 +03:00
|
|
|
}
|
2015-07-24 08:45:16 +03:00
|
|
|
|
2020-01-28 12:10:08 +03:00
|
|
|
return nil, errOSXFUSENotFound
|
2015-07-24 08:39:04 +03:00
|
|
|
}
|
2023-07-03 23:03:22 +03:00
|
|
|
|
|
|
|
func fusetBinary() (string, error) {
|
|
|
|
srv_path := os.Getenv("FUSE_NFSSRV_PATH")
|
|
|
|
if srv_path == "" {
|
|
|
|
srv_path = FUSET_SRV_PATH
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := os.Stat(srv_path); err == nil {
|
|
|
|
return srv_path, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", fmt.Errorf("FUSE-T not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
func unixgramSocketpair() (l, r *os.File, err error) {
|
|
|
|
fd, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, os.NewSyscallError("socketpair",
|
|
|
|
err.(syscall.Errno))
|
|
|
|
}
|
|
|
|
l = os.NewFile(uintptr(fd[0]), fmt.Sprintf("socketpair-half%d", fd[0]))
|
|
|
|
r = os.NewFile(uintptr(fd[1]), fmt.Sprintf("socketpair-half%d", fd[1]))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var local, local_mon, remote, remote_mon *os.File
|
|
|
|
|
|
|
|
func startFuseTServer(binary string, argv []string,
|
|
|
|
additionalEnv []string,
|
|
|
|
wait bool,
|
|
|
|
debugLogger *log.Logger,
|
|
|
|
ready chan<- error) (*os.File, error) {
|
|
|
|
if debugLogger != nil {
|
|
|
|
debugLogger.Println("Creating a socket pair")
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
local, remote, err = unixgramSocketpair()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer remote.Close()
|
|
|
|
|
|
|
|
local_mon, remote_mon, err = unixgramSocketpair()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer remote_mon.Close()
|
|
|
|
|
|
|
|
syscall.CloseOnExec(int(local.Fd()))
|
|
|
|
syscall.CloseOnExec(int(local_mon.Fd()))
|
|
|
|
|
|
|
|
if debugLogger != nil {
|
|
|
|
debugLogger.Println("Creating files to wrap the sockets")
|
|
|
|
}
|
|
|
|
|
|
|
|
if debugLogger != nil {
|
|
|
|
debugLogger.Println("Starting fusermount/os mount")
|
|
|
|
}
|
|
|
|
// Start fusermount/mount_macfuse/mount_osxfuse.
|
|
|
|
cmd := exec.Command(binary, argv...)
|
|
|
|
cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3")
|
|
|
|
cmd.Env = append(cmd.Env, "_FUSE_MONFD=4")
|
|
|
|
cmd.Env = append(cmd.Env, additionalEnv...)
|
|
|
|
cmd.ExtraFiles = []*os.File{remote, remote_mon}
|
2023-07-18 15:49:40 +03:00
|
|
|
cmd.Stderr = nil
|
|
|
|
cmd.Stdout = nil
|
|
|
|
// daemonize
|
|
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
|
|
Setsid: true,
|
|
|
|
}
|
2023-07-03 23:03:22 +03:00
|
|
|
|
|
|
|
// Run the command.
|
|
|
|
err = cmd.Start()
|
|
|
|
cmd.Process.Release()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("running %v: %v", binary, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if debugLogger != nil {
|
|
|
|
debugLogger.Println("Wrapping socket pair in a connection")
|
|
|
|
}
|
|
|
|
|
|
|
|
if debugLogger != nil {
|
|
|
|
debugLogger.Println("Checking that we have a unix domain socket")
|
|
|
|
}
|
|
|
|
|
|
|
|
if debugLogger != nil {
|
|
|
|
debugLogger.Println("Read a message from socket")
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
if _, err = local_mon.Write([]byte("mount")); err != nil {
|
|
|
|
err = fmt.Errorf("fuse-t failed: %v", err)
|
|
|
|
} else {
|
|
|
|
reply := make([]byte, 4)
|
|
|
|
if _, err = local_mon.Read(reply); err != nil {
|
|
|
|
fmt.Printf("mount read %v\n", err)
|
|
|
|
err = fmt.Errorf("fuse-t failed: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ready <- err
|
|
|
|
close(ready)
|
|
|
|
}()
|
|
|
|
|
|
|
|
if debugLogger != nil {
|
|
|
|
debugLogger.Println("Successfully read the socket message.")
|
|
|
|
}
|
|
|
|
|
|
|
|
return local, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func mountFuset(
|
|
|
|
dir string,
|
|
|
|
cfg *MountConfig,
|
|
|
|
ready chan<- error) (dev *os.File, err error) {
|
2023-12-24 15:06:42 +03:00
|
|
|
fuseTBin, err := fusetBinary()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-07-03 23:03:22 +03:00
|
|
|
|
2023-07-03 23:11:17 +03:00
|
|
|
fusekernel.IsPlatformFuseT = true
|
2023-07-03 23:03:22 +03:00
|
|
|
env := []string{}
|
|
|
|
argv := []string{
|
|
|
|
fmt.Sprintf("--rwsize=%d", buffer.MaxWriteSize),
|
|
|
|
}
|
|
|
|
|
|
|
|
if cfg.VolumeName != "" {
|
|
|
|
argv = append(argv, "--volname")
|
|
|
|
argv = append(argv, cfg.VolumeName)
|
|
|
|
}
|
|
|
|
if cfg.ReadOnly {
|
|
|
|
argv = append(argv, "-r")
|
|
|
|
}
|
|
|
|
|
|
|
|
env = append(env, "_FUSE_COMMVERS=2")
|
|
|
|
argv = append(argv, dir)
|
|
|
|
|
2023-12-24 15:06:42 +03:00
|
|
|
return startFuseTServer(fuseTBin, argv, env, false, cfg.DebugLogger, ready)
|
2023-07-03 23:03:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func mount(
|
|
|
|
dir string,
|
|
|
|
cfg *MountConfig,
|
|
|
|
ready chan<- error) (dev *os.File, err error) {
|
|
|
|
|
2023-07-03 23:11:17 +03:00
|
|
|
fusekernel.IsPlatformFuseT = false
|
2023-12-24 15:06:42 +03:00
|
|
|
switch cfg.FuseImpl {
|
|
|
|
case FUSEImplMacFUSE:
|
|
|
|
dev, err = mountOsxFuse(dir, cfg, ready)
|
|
|
|
case FUSEImplFuseT:
|
|
|
|
fallthrough
|
|
|
|
default:
|
|
|
|
dev, err = mountFuset(dir, cfg, ready)
|
2023-07-03 23:03:22 +03:00
|
|
|
}
|
2023-12-24 15:06:42 +03:00
|
|
|
return
|
2023-07-03 23:03:22 +03:00
|
|
|
}
|