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 }