github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libfuse/mounter.go (about)

     1  // Copyright 2016 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  //
     5  //go:build !windows
     6  // +build !windows
     7  
     8  package libfuse
     9  
    10  import (
    11  	"bytes"
    12  	"errors"
    13  	"fmt"
    14  	"os"
    15  	"os/exec"
    16  	"path"
    17  	"runtime"
    18  	"strings"
    19  
    20  	"bazil.org/fuse"
    21  	"github.com/keybase/client/go/kbconst"
    22  	"github.com/keybase/client/go/libkb"
    23  	"github.com/keybase/client/go/logger"
    24  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    25  )
    26  
    27  type mounter struct {
    28  	options StartOptions
    29  	c       *fuse.Conn
    30  	log     logger.Logger
    31  	runMode kbconst.RunMode
    32  }
    33  
    34  // fuseMount tries to mount the mountpoint.
    35  // On a force mount then unmount, re-mount if unsuccessful
    36  func (m *mounter) Mount() (err error) {
    37  	defer func() {
    38  		if err != nil {
    39  			msg := fmt.Sprintf("KBFS failed to FUSE mount at %s: %s", m.options.MountPoint, err)
    40  			fmt.Println(msg)
    41  			m.log.Warning(msg)
    42  		}
    43  	}()
    44  
    45  	m.c, err = fuseMountDir(m.options.MountPoint, m.options.PlatformParams)
    46  	// Exit if we were successful or we are not a force mounting on error.
    47  	// Otherwise, try unmounting and mounting again.
    48  	if err == nil || !m.options.ForceMount {
    49  		return err
    50  	}
    51  
    52  	// Mount failed, let's try to unmount and then try mounting again, even
    53  	// if unmounting errors here.
    54  	_ = m.Unmount()
    55  
    56  	// In case we are on darwin, ask the installer to reinstall the mount dir
    57  	// and try again as the last resort. This specifically fixes a situation
    58  	// where /keybase gets created and owned by root after Keybase app is
    59  	// started, and `kbfs` later fails to mount because of a permission error.
    60  	m.reinstallMountDirIfPossible()
    61  	m.c, err = fuseMountDir(m.options.MountPoint, m.options.PlatformParams)
    62  
    63  	return err
    64  }
    65  
    66  func fuseMountDir(dir string, platformParams PlatformParams) (*fuse.Conn, error) {
    67  	// Create mountdir directory on Linux.
    68  	switch libkb.RuntimeGroup() {
    69  	case keybase1.RuntimeGroup_LINUXLIKE:
    70  		// Inherit permissions from containing directory and umask.
    71  		err := os.MkdirAll(dir, os.ModeDir|os.ModePerm)
    72  		if err != nil {
    73  			return nil, err
    74  		}
    75  	default:
    76  	}
    77  
    78  	fi, err := os.Stat(dir)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	if !fi.IsDir() {
    83  		return nil, errors.New("mount point is not a directory")
    84  	}
    85  	options, err := getPlatformSpecificMountOptions(dir, platformParams)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	c, err := fuse.Mount(dir, options...)
    90  	if err != nil {
    91  		err = translatePlatformSpecificError(err, platformParams)
    92  		return nil, err
    93  	}
    94  	return c, nil
    95  }
    96  
    97  func isFusermountMountNotFoundError(output []byte, err error) bool {
    98  	if err == nil {
    99  		return false
   100  	}
   101  	return bytes.Contains(output, []byte("not found in /etc/mtab"))
   102  }
   103  
   104  func (m *mounter) Unmount() (err error) {
   105  	m.log.Info("Unmounting")
   106  	dir := m.options.MountPoint
   107  	// Try normal unmount
   108  	switch runtime.GOOS {
   109  	case "darwin": // no ios
   110  		_, err = exec.Command("/sbin/umount", dir).Output()
   111  	case "linux":
   112  		fusermountOutput, fusermountErr := exec.Command("fusermount", "-u", dir).CombinedOutput()
   113  		// Only clean up mountdir on a clean unmount.
   114  		if fusermountErr == nil {
   115  			m.log.Info("Successfully unmounted")
   116  			defer m.DeleteMountdirIfEmpty()
   117  		}
   118  		if fusermountErr != nil {
   119  			// Ignore errors where the mount was never mounted in the first place
   120  			if isFusermountMountNotFoundError(fusermountOutput, fusermountErr) {
   121  				m.log.Info("Ignoring mount-not-found fusermount error")
   122  			} else {
   123  				returnErr := fmt.Errorf("fusermount unmount resulted in unknown error: output=%v; err=%s", fusermountOutput, fusermountErr)
   124  				m.log.Warning(returnErr.Error())
   125  				err = returnErr
   126  			}
   127  		}
   128  	default:
   129  		err = fuse.Unmount(dir)
   130  	}
   131  	if err != nil && m.options.ForceMount {
   132  		// Unmount failed, so let's try and force it.
   133  		switch runtime.GOOS {
   134  		case "darwin": // no ios
   135  			_, err = exec.Command(
   136  				"/usr/sbin/diskutil", "unmountDisk", "force", dir).Output()
   137  		case "linux":
   138  			// Lazy unmount; will unmount when KBFS is no longer in use.
   139  			_, err = exec.Command("fusermount", "-u", "-z", dir).Output()
   140  		default:
   141  			err = errors.New("Forced unmount is not supported on this platform yet")
   142  		}
   143  	}
   144  	if execErr, ok := err.(*exec.ExitError); ok && execErr.Stderr != nil {
   145  		err = fmt.Errorf("%s (%s)", execErr, execErr.Stderr)
   146  	}
   147  	return
   148  }
   149  
   150  func (m *mounter) DeleteMountdirIfEmpty() {
   151  	m.log.Info("Deleting mountdir")
   152  	// os.Remove refuses to delete non-empty directories.
   153  	err := os.Remove(m.options.MountPoint)
   154  	if err != nil {
   155  		m.log.Errorf("Unable to delete mountdir: %s", err)
   156  	}
   157  }
   158  
   159  // volumeName returns the first word of the directory (base) name
   160  func volumeName(dir string) (string, error) { // nolint
   161  	volName := path.Base(dir)
   162  	if volName == "." || volName == "/" {
   163  		err := fmt.Errorf("Bad volume name: %v", volName)
   164  		return "", err
   165  	}
   166  	s := strings.Split(volName, " ")
   167  	if len(s) == 0 {
   168  		return "", fmt.Errorf("Bad volume name: %v", volName)
   169  	}
   170  	return s[0], nil
   171  }