Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand Down
30 changes: 30 additions & 0 deletions log_util.go
Original file line number Diff line number Diff line change
@@ -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
}
57 changes: 30 additions & 27 deletions mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -128,18 +130,19 @@ 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)
if err != nil {
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")
Expand All @@ -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...)
Expand All @@ -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)
Expand All @@ -178,17 +181,17 @@ 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)
if !ok {
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
Expand All @@ -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
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions mount_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 18 additions & 16 deletions mount_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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...)
Expand All @@ -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() {
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand Down
25 changes: 14 additions & 11 deletions mount_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down