github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/install/install_nix.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  //go:build !windows
     5  // +build !windows
     6  
     7  package install
     8  
     9  import (
    10  	"io"
    11  	"os"
    12  	"path/filepath"
    13  	"strconv"
    14  
    15  	"github.com/keybase/client/go/lsof"
    16  )
    17  
    18  // maybeKernelOpenFiles returns true if the kernel might currently
    19  // have open files.  If it returns false, the mount is definitely not
    20  // in use.
    21  func maybeKernelOpenFiles(mountDir string, log Log) bool {
    22  	// This file name is copied from kbfs/libfs/constants.go because
    23  	// importing that package bloats the `keybase` service binary and
    24  	// causes compilation issues.
    25  	p := filepath.Join(mountDir, ".kbfs_open_file_count")
    26  	f, err := os.Open(p)
    27  	if err != nil {
    28  		log.Debug("Couldn't check for open files in %s: %+v", p, err)
    29  		return true
    30  	}
    31  	defer f.Close()
    32  
    33  	b, err := io.ReadAll(f)
    34  	if err != nil {
    35  		log.Debug("Couldn't read the open file count in %s: %+v", p, err)
    36  		return true
    37  	}
    38  
    39  	numOpenFiles, err := strconv.ParseInt(string(b), 10, 64)
    40  	if err != nil {
    41  		log.Debug("Couldn't parse the open file count (%s) in %s: %+v",
    42  			string(b), p, err)
    43  		return true
    44  	}
    45  
    46  	return numOpenFiles != 0
    47  }
    48  
    49  // IsInUse returns true if the mount is in use. This may be used by the updater
    50  // to determine if it's safe to apply an update and restart.
    51  func IsInUse(mountDir string, log Log) bool {
    52  	// Shortcut to avoid expensive lsof call if KBFS tells us that
    53  	// there are definitely no open files.
    54  	if !maybeKernelOpenFiles(mountDir, log) {
    55  		log.Debug("Definitely no open files; skipping lsof")
    56  		return false
    57  	}
    58  
    59  	// ignore error
    60  	lsofResults, _ := LsofMount(mountDir, log)
    61  	return len(lsofResults) > 0
    62  }
    63  
    64  // LsofMount does not return an error if it was unable to lsof
    65  // the mountpoint or the mountpoint does not exist.
    66  func LsofMount(mountDir string, log Log) ([]CommonLsofResult, error) {
    67  	log.Debug("Mount dir to lsof: %s", mountDir)
    68  	if mountDir == "" {
    69  		return nil, nil
    70  	}
    71  	if _, serr := os.Stat(mountDir); os.IsNotExist(serr) {
    72  		log.Debug("%s, mount dir lsof target, doesn't exist", mountDir)
    73  		return nil, nil
    74  	}
    75  
    76  	log.Debug("Checking mount (lsof)")
    77  	processes, err := lsof.MountPoint(mountDir)
    78  	if err != nil {
    79  		// If there is an error in lsof it's ok to continue
    80  		// An exit status of 1 means that the mount is not in use, and is
    81  		// not really an error.
    82  		log.Debug("Continuing despite error in lsof: %s", err)
    83  		return nil, nil
    84  	}
    85  	var ret []CommonLsofResult
    86  	for _, process := range processes {
    87  		ret = append(ret, CommonLsofResult{process.PID, process.Command})
    88  	}
    89  	return ret, nil
    90  }