diff --git a/connection.go b/connection.go index ba65e67..12522c0 100644 --- a/connection.go +++ b/connection.go @@ -61,6 +61,7 @@ const maxReadahead = 1 << 20 type Connection struct { cfg MountConfig debugLogger *log.Logger + infoLogger *log.Logger errorLogger *log.Logger wireLogger io.Writer @@ -109,12 +110,14 @@ func GetWirelog(ctx context.Context) *WireLogRecord { func newConnection( cfg MountConfig, debugLogger *log.Logger, + infoLogger *log.Logger, errorLogger *log.Logger, wireLogger io.Writer, dev *os.File) (*Connection, error) { c := &Connection{ cfg: cfg, debugLogger: debugLogger, + infoLogger: infoLogger, errorLogger: errorLogger, wireLogger: wireLogger, dev: dev, diff --git a/log_util.go b/log_util.go new file mode 100644 index 0000000..b65ac4f --- /dev/null +++ b/log_util.go @@ -0,0 +1,30 @@ +// Copyright 2026 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fuse + +import ( + "log" +) + +// firstLogger returns the first non-nil logger from the provided list. +// If all are nil, it returns no-op logger +func FirstLogger(loggers ...*log.Logger) *log.Logger { + for _, l := range loggers { + if l != nil { + return l + } + } + return nil +} diff --git a/mount.go b/mount.go index 23db0a8..205568f 100644 --- a/mount.go +++ b/mount.go @@ -54,16 +54,17 @@ func Mount( } // Begin the mounting process, which will continue in the background. - if config.DebugLogger != nil { - config.DebugLogger.Println("Beginning the mounting kickoff process") + infoDebugFallbackLogger := FirstLogger(config.InfoLogger, config.DebugLogger) + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Beginning the mounting kickoff process") } ready := make(chan error, 1) dev, err := mount(dir, config, ready) if err != nil { return nil, fmt.Errorf("mount: %v", err) } - if config.DebugLogger != nil { - config.DebugLogger.Println("Completed the mounting kickoff process") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Completed the mounting kickoff process") } // Choose a parent context for ops. @@ -72,21 +73,22 @@ func Mount( cfgCopy.OpContext = context.Background() } - if config.DebugLogger != nil { - config.DebugLogger.Println("Creating a connection object") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Creating a connection object") } // Create a Connection object wrapping the device. connection, err := newConnection( cfgCopy, config.DebugLogger, + config.InfoLogger, config.ErrorLogger, config.WireLogger, dev) if err != nil { return nil, fmt.Errorf("newConnection: %v", err) } - if config.DebugLogger != nil { - config.DebugLogger.Println("Successfully created the connection") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Successfully created the connection") } // Serve the connection in the background. When done, set the join status. @@ -96,8 +98,8 @@ func Mount( close(mfs.joinStatusAvailable) }() - if config.DebugLogger != nil { - config.DebugLogger.Println("Waiting for mounting process to complete") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Waiting for mounting process to complete") } // Wait for the mount process to complete. @@ -128,9 +130,10 @@ func checkMountPoint(dir string) error { return nil } -func fusermount(binary string, argv []string, additionalEnv []string, wait bool, debugLogger *log.Logger) (*os.File, error) { - if debugLogger != nil { - debugLogger.Println("Creating a socket pair") +func fusermount(binary string, argv []string, additionalEnv []string, wait bool, debugLogger *log.Logger, infoLogger *log.Logger) (*os.File, error) { + infoDebugFallbackLogger := FirstLogger(infoLogger, debugLogger) + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Creating a socket pair") } // Create a socket pair. fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) @@ -138,8 +141,8 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool, return nil, fmt.Errorf("Socketpair: %v", err) } - if debugLogger != nil { - debugLogger.Println("Creating files to wrap the sockets") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Creating files to wrap the sockets") } // Wrap the sockets into os.File objects that we will pass off to fusermount. writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes") @@ -148,8 +151,8 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool, readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads") defer readFile.Close() - if debugLogger != nil { - debugLogger.Println("Starting fusermount/os mount") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Starting fusermount/os mount") } // Start fusermount/mount_macfuse/mount_osxfuse. cmd := exec.Command(binary, argv...) @@ -168,8 +171,8 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool, return nil, fmt.Errorf("running %v: %v", binary, err) } - if debugLogger != nil { - debugLogger.Println("Wrapping socket pair in a connection") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Wrapping socket pair in a connection") } // Wrap the socket file in a connection. c, err := net.FileConn(readFile) @@ -178,8 +181,8 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool, } defer c.Close() - if debugLogger != nil { - debugLogger.Println("Checking that we have a unix domain socket") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Checking that we have a unix domain socket") } // We expect to have a Unix domain socket. uc, ok := c.(*net.UnixConn) @@ -187,8 +190,8 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool, return nil, fmt.Errorf("Expected UnixConn, got %T", c) } - if debugLogger != nil { - debugLogger.Println("Read a message from socket") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Read a message from socket") } // Read a message. buf := make([]byte, 32) // expect 1 byte @@ -211,8 +214,8 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool, scm := scms[0] - if debugLogger != nil { - debugLogger.Println("Successfully read the socket message.") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Successfully read the socket message.") } // Pull out the FD returned by fusermount @@ -225,8 +228,8 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool, return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds) } - if debugLogger != nil { - debugLogger.Println("Converting FD into os.File") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Converting FD into os.File") } // Turn the FD into an os.File. return os.NewFile(uintptr(gotFds[0]), "/dev/fuse"), nil diff --git a/mount_config.go b/mount_config.go index f95895a..4e98747 100644 --- a/mount_config.go +++ b/mount_config.go @@ -48,6 +48,10 @@ type MountConfig struct { // performed. DebugLogger *log.Logger + // A logger to use for logging info information. If nil, no info logging is + // performed. + InfoLogger *log.Logger + // A logger to use for logging fuse wire requests. If nil, no wire logging is // performed. WireLogger io.Writer diff --git a/mount_darwin.go b/mount_darwin.go index 6964120..33a6319 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -209,7 +209,7 @@ func callMountCommFD( env = append(env, "_FUSE_COMMVERS=2") argv = append(argv, dir) - return fusermount(bin, argv, env, false, cfg.DebugLogger) + return fusermount(bin, argv, env, false, cfg.DebugLogger, cfg.InfoLogger) } // Begin the process of mounting at the given directory, returning a connection @@ -298,9 +298,11 @@ func startFuseTServer(binary string, argv []string, additionalEnv []string, wait bool, debugLogger *log.Logger, + infoLogger *log.Logger, ready chan<- error) (*os.File, error) { - if debugLogger != nil { - debugLogger.Println("Creating a socket pair") + infoDebugFallbackLogger := FirstLogger(infoLogger, debugLogger) + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Creating a socket pair") } var err error @@ -319,12 +321,12 @@ func startFuseTServer(binary string, argv []string, syscall.CloseOnExec(int(local.Fd())) syscall.CloseOnExec(int(local_mon.Fd())) - if debugLogger != nil { - debugLogger.Println("Creating files to wrap the sockets") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Creating files to wrap the sockets") } - if debugLogger != nil { - debugLogger.Println("Starting fusermount/os mount") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Starting fusermount/os mount") } // Start fusermount/mount_macfuse/mount_osxfuse. cmd := exec.Command(binary, argv...) @@ -346,16 +348,16 @@ func startFuseTServer(binary string, argv []string, return nil, fmt.Errorf("running %v: %v", binary, err) } - if debugLogger != nil { - debugLogger.Println("Wrapping socket pair in a connection") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Wrapping socket pair in a connection") } - if debugLogger != nil { - debugLogger.Println("Checking that we have a unix domain socket") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Checking that we have a unix domain socket") } - if debugLogger != nil { - debugLogger.Println("Read a message from socket") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Read a message from socket") } go func() { @@ -373,8 +375,8 @@ func startFuseTServer(binary string, argv []string, close(ready) }() - if debugLogger != nil { - debugLogger.Println("Successfully read the socket message.") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Successfully read the socket message.") } return local, nil @@ -406,7 +408,7 @@ func mountFuset( env = append(env, "_FUSE_COMMVERS=2") argv = append(argv, dir) - return startFuseTServer(fuseTBin, argv, env, false, cfg.DebugLogger, ready) + return startFuseTServer(fuseTBin, argv, env, false, cfg.DebugLogger, cfg.InfoLogger, ready) } func mount( diff --git a/mount_linux.go b/mount_linux.go index 38e19c5..7ad99f5 100644 --- a/mount_linux.go +++ b/mount_linux.go @@ -55,8 +55,9 @@ var mountflagopts = map[string]func(uintptr) uintptr{ var errFallback = errors.New("sentinel: fallback to fusermount(1)") func directmount(dir string, cfg *MountConfig) (*os.File, error) { - if cfg.DebugLogger != nil { - cfg.DebugLogger.Println("Preparing for direct mounting") + infoDebugFallbackLogger := FirstLogger(cfg.InfoLogger, cfg.DebugLogger) + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Preparing for direct mounting") } // We use syscall.Open + os.NewFile instead of os.OpenFile so that the file // is opened in blocking mode. When opened in non-blocking mode, the Go @@ -67,8 +68,8 @@ func directmount(dir string, cfg *MountConfig) (*os.File, error) { } dev := os.NewFile(uintptr(fd), "/dev/fuse") - if cfg.DebugLogger != nil { - cfg.DebugLogger.Println("Successfully opened the /dev/fuse in blocking mode") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Successfully opened the /dev/fuse in blocking mode") } // As per libfuse/fusermount.c:847: https://bit.ly/2SgtWYM#L847 data := fmt.Sprintf("fd=%d,rootmode=40000,user_id=%d,group_id=%d", @@ -93,8 +94,8 @@ func directmount(dir string, cfg *MountConfig) (*os.File, error) { delete(opts, "subtype") data += "," + mapToOptionsString(opts) - if cfg.DebugLogger != nil { - cfg.DebugLogger.Println("Starting the unix mounting") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Starting the unix mounting") } if err := unix.Mount( fsname, // source @@ -123,8 +124,10 @@ func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) { // On linux, mounting is never delayed. ready <- nil - if cfg.DebugLogger != nil { - cfg.DebugLogger.Println("Parsing fuse file descriptor") + infoDebugFallbackLogger := FirstLogger(cfg.InfoLogger, cfg.DebugLogger) + + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Parsing fuse file descriptor") } // If the mountpoint is /dev/fd/N, assume that the file descriptor N is an // already open FUSE channel. Parse it, cast it to an fd, and don't do any @@ -138,8 +141,8 @@ func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) { // have the CAP_SYS_ADMIN capability. dev, err := directmount(dir, cfg) if err == errFallback { - if cfg.DebugLogger != nil { - cfg.DebugLogger.Println("Directmount failed. Trying fallback.") + if infoDebugFallbackLogger != nil { + infoDebugFallbackLogger.Println("Directmount failed. Trying fallback.") } fusermountPath, err := findFusermount() if err != nil { @@ -150,7 +153,7 @@ func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) { "--", dir, } - dev, err := fusermount(fusermountPath, argv, []string{}, true, cfg.DebugLogger) + dev, err := fusermount(fusermountPath, argv, []string{}, true, cfg.DebugLogger, cfg.InfoLogger) if err == nil { return dev, nil }