mountinfo_linux.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. package mountinfo
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "os"
  7. "strconv"
  8. "strings"
  9. )
  10. // GetMountsFromReader retrieves a list of mounts from the
  11. // reader provided, with an optional filter applied (use nil
  12. // for no filter). This can be useful in tests or benchmarks
  13. // that provide fake mountinfo data, or when a source other
  14. // than /proc/self/mountinfo needs to be read from.
  15. //
  16. // This function is Linux-specific.
  17. func GetMountsFromReader(r io.Reader, filter FilterFunc) ([]*Info, error) {
  18. s := bufio.NewScanner(r)
  19. out := []*Info{}
  20. for s.Scan() {
  21. var err error
  22. /*
  23. See http://man7.org/linux/man-pages/man5/proc.5.html
  24. 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
  25. (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
  26. (1) mount ID: unique identifier of the mount (may be reused after umount)
  27. (2) parent ID: ID of parent (or of self for the top of the mount tree)
  28. (3) major:minor: value of st_dev for files on filesystem
  29. (4) root: root of the mount within the filesystem
  30. (5) mount point: mount point relative to the process's root
  31. (6) mount options: per mount options
  32. (7) optional fields: zero or more fields of the form "tag[:value]"
  33. (8) separator: marks the end of the optional fields
  34. (9) filesystem type: name of filesystem of the form "type[.subtype]"
  35. (10) mount source: filesystem specific information or "none"
  36. (11) super options: per super block options
  37. In other words, we have:
  38. * 6 mandatory fields (1)..(6)
  39. * 0 or more optional fields (7)
  40. * a separator field (8)
  41. * 3 mandatory fields (9)..(11)
  42. */
  43. text := s.Text()
  44. fields := strings.Split(text, " ")
  45. numFields := len(fields)
  46. if numFields < 10 {
  47. // should be at least 10 fields
  48. return nil, fmt.Errorf("parsing '%s' failed: not enough fields (%d)", text, numFields)
  49. }
  50. // separator field
  51. sepIdx := numFields - 4
  52. // In Linux <= 3.9 mounting a cifs with spaces in a share
  53. // name (like "//srv/My Docs") _may_ end up having a space
  54. // in the last field of mountinfo (like "unc=//serv/My Docs").
  55. // Since kernel 3.10-rc1, cifs option "unc=" is ignored,
  56. // so spaces should not appear.
  57. //
  58. // Check for a separator, and work around the spaces bug
  59. for fields[sepIdx] != "-" {
  60. sepIdx--
  61. if sepIdx == 5 {
  62. return nil, fmt.Errorf("parsing '%s' failed: missing - separator", text)
  63. }
  64. }
  65. p := &Info{}
  66. p.Mountpoint, err = unescape(fields[4])
  67. if err != nil {
  68. return nil, fmt.Errorf("parsing '%s' failed: mount point: %w", fields[4], err)
  69. }
  70. p.FSType, err = unescape(fields[sepIdx+1])
  71. if err != nil {
  72. return nil, fmt.Errorf("parsing '%s' failed: fstype: %w", fields[sepIdx+1], err)
  73. }
  74. p.Source, err = unescape(fields[sepIdx+2])
  75. if err != nil {
  76. return nil, fmt.Errorf("parsing '%s' failed: source: %w", fields[sepIdx+2], err)
  77. }
  78. p.VFSOptions = fields[sepIdx+3]
  79. // ignore any numbers parsing errors, as there should not be any
  80. p.ID, _ = strconv.Atoi(fields[0])
  81. p.Parent, _ = strconv.Atoi(fields[1])
  82. mm := strings.SplitN(fields[2], ":", 3)
  83. if len(mm) != 2 {
  84. return nil, fmt.Errorf("parsing '%s' failed: unexpected major:minor pair %s", text, mm)
  85. }
  86. p.Major, _ = strconv.Atoi(mm[0])
  87. p.Minor, _ = strconv.Atoi(mm[1])
  88. p.Root, err = unescape(fields[3])
  89. if err != nil {
  90. return nil, fmt.Errorf("parsing '%s' failed: root: %w", fields[3], err)
  91. }
  92. p.Options = fields[5]
  93. // zero or more optional fields
  94. p.Optional = strings.Join(fields[6:sepIdx], " ")
  95. // Run the filter after parsing all fields.
  96. var skip, stop bool
  97. if filter != nil {
  98. skip, stop = filter(p)
  99. if skip {
  100. continue
  101. }
  102. }
  103. out = append(out, p)
  104. if stop {
  105. break
  106. }
  107. }
  108. if err := s.Err(); err != nil {
  109. return nil, err
  110. }
  111. return out, nil
  112. }
  113. func parseMountTable(filter FilterFunc) ([]*Info, error) {
  114. f, err := os.Open("/proc/self/mountinfo")
  115. if err != nil {
  116. return nil, err
  117. }
  118. defer f.Close()
  119. return GetMountsFromReader(f, filter)
  120. }
  121. // PidMountInfo retrieves the list of mounts from a given process' mount
  122. // namespace. Unless there is a need to get mounts from a mount namespace
  123. // different from that of a calling process, use GetMounts.
  124. //
  125. // This function is Linux-specific.
  126. //
  127. // Deprecated: this will be removed before v1; use GetMountsFromReader with
  128. // opened /proc/<pid>/mountinfo as an argument instead.
  129. func PidMountInfo(pid int) ([]*Info, error) {
  130. f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
  131. if err != nil {
  132. return nil, err
  133. }
  134. defer f.Close()
  135. return GetMountsFromReader(f, nil)
  136. }
  137. // A few specific characters in mountinfo path entries (root and mountpoint)
  138. // are escaped using a backslash followed by a character's ascii code in octal.
  139. //
  140. // space -- as \040
  141. // tab (aka \t) -- as \011
  142. // newline (aka \n) -- as \012
  143. // backslash (aka \\) -- as \134
  144. //
  145. // This function converts path from mountinfo back, i.e. it unescapes the above sequences.
  146. func unescape(path string) (string, error) {
  147. // try to avoid copying
  148. if strings.IndexByte(path, '\\') == -1 {
  149. return path, nil
  150. }
  151. // The following code is UTF-8 transparent as it only looks for some
  152. // specific characters (backslash and 0..7) with values < utf8.RuneSelf,
  153. // and everything else is passed through as is.
  154. buf := make([]byte, len(path))
  155. bufLen := 0
  156. for i := 0; i < len(path); i++ {
  157. if path[i] != '\\' {
  158. buf[bufLen] = path[i]
  159. bufLen++
  160. continue
  161. }
  162. s := path[i:]
  163. if len(s) < 4 {
  164. // too short
  165. return "", fmt.Errorf("bad escape sequence %q: too short", s)
  166. }
  167. c := s[1]
  168. switch c {
  169. case '0', '1', '2', '3', '4', '5', '6', '7':
  170. v := c - '0'
  171. for j := 2; j < 4; j++ { // one digit already; two more
  172. if s[j] < '0' || s[j] > '7' {
  173. return "", fmt.Errorf("bad escape sequence %q: not a digit", s[:3])
  174. }
  175. x := s[j] - '0'
  176. v = (v << 3) | x
  177. }
  178. if v > 255 {
  179. return "", fmt.Errorf("bad escape sequence %q: out of range" + s[:3])
  180. }
  181. buf[bufLen] = v
  182. bufLen++
  183. i += 3
  184. continue
  185. default:
  186. return "", fmt.Errorf("bad escape sequence %q: not a digit" + s[:3])
  187. }
  188. }
  189. return string(buf[:bufLen]), nil
  190. }