paths_unix.go 2.7 KB

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