nix.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. //go:build !windows && !darwin
  2. package internal
  3. import (
  4. "bufio"
  5. "bytes"
  6. "math"
  7. "net/url"
  8. "os"
  9. "sync/atomic"
  10. "golang.org/x/sys/unix"
  11. )
  12. // The mounts could change anytime, but probably won't all too often. So we
  13. // read them each time Mount() is called, but remember the count, so we can
  14. // at least prepare a buffer of that size. This must only be accessed atomicly.
  15. var lastMountCount int32
  16. // Mounts reads /proc/mounts and returns all mounts found, excluding
  17. // everything on the devices sysfs, rootfs, cgroup and /dev/ paths.
  18. func Mounts() ([]string, error) {
  19. handle, err := os.Open("/proc/mounts")
  20. if err != nil {
  21. return nil, err
  22. }
  23. defer handle.Close()
  24. scanner := bufio.NewScanner(handle)
  25. // I just assume that entries won't be much longer than 200, since my
  26. // own /proc/mounts was max 157 characters. Worst case, we'll probably
  27. // expand to 400. Either way, this should handle most cases efficiently.
  28. buffer := make([]byte, 0, 200)
  29. scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
  30. if atEOF && len(data) == 0 {
  31. return 0, nil, nil
  32. }
  33. if i := bytes.IndexByte(data, '\n'); i >= 0 {
  34. if firstSpace := bytes.IndexByte(data, ' '); firstSpace >= 0 {
  35. // Some device won't contain a trash either way or might be
  36. // dangerous to interact with.
  37. device := string(data[:firstSpace])
  38. switch device {
  39. case "rootfs", "sysfs", "cgroup", "cgroup2":
  40. // Skip line
  41. return i + 1, nil, nil
  42. }
  43. if nextSpace := bytes.IndexByte(data[firstSpace+1:], ' '); nextSpace >= 0 {
  44. path := data[firstSpace+1 : firstSpace+1+nextSpace]
  45. // Devices don't usually contain files and /sys should
  46. // be off-limits anyway.
  47. if bytes.HasPrefix(path, []byte("/dev/")) || bytes.HasPrefix(path, []byte("/sys/")) {
  48. return i + 1, nil, nil
  49. }
  50. return i + 1, path, nil
  51. }
  52. }
  53. return i + 1, nil, nil
  54. }
  55. // If we're at EOF, we have a final, non-terminated line. Return it.
  56. if atEOF {
  57. return len(data), data, nil
  58. }
  59. // Request more data.
  60. return 0, nil, nil
  61. })
  62. scanner.Buffer(buffer, math.MaxInt)
  63. mounts := make([]string, 0, atomic.LoadInt32(&lastMountCount))
  64. for scanner.Scan() {
  65. mounts = append(mounts, scanner.Text())
  66. }
  67. atomic.StoreInt32(&lastMountCount, int32(len(mounts)))
  68. return mounts, nil
  69. }
  70. // RemoveAllIfExists tries to remove the given path. The path can either be a
  71. // directory or a file. This function catches certain errors, so you don't have
  72. // to.
  73. func RemoveAllIfExists(path string) error {
  74. RETRY:
  75. if err := os.RemoveAll(path); err != nil {
  76. pathErr, ok := err.(*os.PathError)
  77. if ok {
  78. switch pathErr.Err {
  79. case unix.EINTR:
  80. // A non-error basically which tells you to try again.
  81. // Use goto in order to prevent growing stack.
  82. goto RETRY
  83. case unix.ENOENT:
  84. // Does not exist.
  85. return nil
  86. case unix.ENOTDIR:
  87. // Not a directory or file. This happens on WSL when
  88. // attempting to delete in /mnt/wslg/versions.txt, which is
  89. // weird considering that os.Remove (used internally) can
  90. // delete files.
  91. return nil
  92. case unix.EROFS:
  93. // Occurs if the filesystem is read-only.
  94. return nil
  95. }
  96. }
  97. return err
  98. }
  99. return nil
  100. }
  101. // EscapeUrl escapes the path according to the FreeDesktop Trash specification.
  102. // Which basically just refers to "RFC 2396, section 2".
  103. func EscapeUrl(path string) string {
  104. u := &url.URL{Path: path}
  105. return u.EscapedPath()
  106. }
  107. // FileExists omits the parts to make this usable cross-platform and
  108. // therefore saves a minimal amount of CPU cycles and some allocations.
  109. func FileExists(path string) (bool, error) {
  110. var (
  111. stat unix.Stat_t
  112. err error
  113. )
  114. RETRY:
  115. for {
  116. err = unix.Stat(path, &stat)
  117. switch err {
  118. case nil:
  119. // No issue, exists
  120. return true, nil
  121. case unix.EINTR:
  122. // A non-error basically which tells you to try again.
  123. continue RETRY
  124. case unix.ENOENT:
  125. // Doesn't exist
  126. return false, nil
  127. default:
  128. // Unexpected error
  129. return false, err
  130. }
  131. }
  132. }