github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/shell/paths_unix.go (about)

     1  //go:build !windows && !plan9
     2  // +build !windows,!plan9
     3  
     4  package shell
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"syscall"
    11  
    12  	"github.com/markusbkk/elvish/pkg/diag"
    13  	"github.com/markusbkk/elvish/pkg/env"
    14  	"github.com/markusbkk/elvish/pkg/fsutil"
    15  )
    16  
    17  func newRCPath() (string, error) {
    18  	return xdgHomePath(env.XDG_CONFIG_HOME, ".config", "elvish/rc.elv")
    19  }
    20  
    21  const elvishLib = "elvish/lib"
    22  
    23  func newLibPaths() ([]string, error) {
    24  	var paths []string
    25  	libConfig, errConfig := xdgHomePath(env.XDG_CONFIG_HOME, ".config", elvishLib)
    26  	if errConfig == nil {
    27  		paths = append(paths, libConfig)
    28  	}
    29  	libData, errData := xdgHomePath(env.XDG_DATA_HOME, ".local/share", elvishLib)
    30  	if errData == nil {
    31  		paths = append(paths, libData)
    32  	}
    33  
    34  	libSystem := os.Getenv(env.XDG_DATA_DIRS)
    35  	if libSystem == "" {
    36  		libSystem = "/usr/local/share:/usr/share"
    37  	}
    38  	for _, p := range filepath.SplitList(libSystem) {
    39  		paths = append(paths, filepath.Join(p, elvishLib))
    40  	}
    41  
    42  	return paths, diag.Errors(errConfig, errData)
    43  }
    44  
    45  func newDBPath() (string, error) {
    46  	return xdgHomePath(env.XDG_STATE_HOME, ".local/state", "elvish/db.bolt")
    47  }
    48  
    49  func xdgHomePath(envName, fallback, suffix string) (string, error) {
    50  	dir := os.Getenv(envName)
    51  	if dir == "" {
    52  		home, err := fsutil.GetHome("")
    53  		if err != nil {
    54  			return "", fmt.Errorf("resolve ~/%s/%s: %w", fallback, suffix, err)
    55  		}
    56  		dir = filepath.Join(home, fallback)
    57  	}
    58  	return filepath.Join(dir, suffix), nil
    59  }
    60  
    61  // Returns a "run directory" for storing ephemeral files, which is guaranteed
    62  // to be only accessible to the current user.
    63  //
    64  // The path of the run directory is either $XDG_RUNTIME_DIR/elvish or
    65  // $tmpdir/elvish-$uid (where $tmpdir is the system temporary directory). The
    66  // former is used if the XDG_RUNTIME_DIR environment variable exists and the
    67  // latter directory does not exist.
    68  func secureRunDir() (string, error) {
    69  	runDirs := runDirCandidates()
    70  	for _, runDir := range runDirs {
    71  		if checkExclusiveAccess(runDir) {
    72  			return runDir, nil
    73  		}
    74  	}
    75  	runDir := runDirs[0]
    76  	err := os.MkdirAll(runDir, 0700)
    77  	if err != nil {
    78  		return "", fmt.Errorf("mkdir: %v", err)
    79  	}
    80  	if !checkExclusiveAccess(runDir) {
    81  		return "", fmt.Errorf("cannot create %v as a secure run directory", runDir)
    82  	}
    83  	return runDir, nil
    84  }
    85  
    86  // Returns one or more candidates for the run directory, in descending order of
    87  // preference.
    88  func runDirCandidates() []string {
    89  	tmpDirPath := filepath.Join(os.TempDir(), fmt.Sprintf("elvish-%d", os.Getuid()))
    90  	if os.Getenv(env.XDG_RUNTIME_DIR) != "" {
    91  		xdgDirPath := filepath.Join(os.Getenv(env.XDG_RUNTIME_DIR), "elvish")
    92  		return []string{xdgDirPath, tmpDirPath}
    93  	}
    94  	return []string{tmpDirPath}
    95  }
    96  
    97  func checkExclusiveAccess(runDir string) bool {
    98  	info, err := os.Stat(runDir)
    99  	if err != nil {
   100  		return false
   101  	}
   102  	stat := info.Sys().(*syscall.Stat_t)
   103  	return info.IsDir() && int(stat.Uid) == os.Getuid() && stat.Mode&077 == 0
   104  }