utils.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. package zfs
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "os/exec"
  8. "regexp"
  9. "runtime"
  10. "strconv"
  11. "strings"
  12. "github.com/google/uuid"
  13. )
  14. type command struct {
  15. Command string
  16. Stdin io.Reader
  17. Stdout io.Writer
  18. }
  19. func (c *command) Run(arg ...string) ([][]string, error) {
  20. cmd := exec.Command(c.Command, arg...)
  21. var stdout, stderr bytes.Buffer
  22. if c.Stdout == nil {
  23. cmd.Stdout = &stdout
  24. } else {
  25. cmd.Stdout = c.Stdout
  26. }
  27. if c.Stdin != nil {
  28. cmd.Stdin = c.Stdin
  29. }
  30. cmd.Stderr = &stderr
  31. id := uuid.New().String()
  32. joinedArgs := cmd.Path
  33. if len(cmd.Args) > 1 {
  34. joinedArgs = strings.Join(append([]string{cmd.Path}, cmd.Args[1:]...), " ")
  35. }
  36. logger.Log([]string{"ID:" + id, "START", joinedArgs})
  37. if err := cmd.Run(); err != nil {
  38. return nil, &Error{
  39. Err: err,
  40. Debug: joinedArgs,
  41. Stderr: stderr.String(),
  42. }
  43. }
  44. logger.Log([]string{"ID:" + id, "FINISH"})
  45. // assume if you passed in something for stdout, that you know what to do with it
  46. if c.Stdout != nil {
  47. return nil, nil
  48. }
  49. lines := strings.Split(stdout.String(), "\n")
  50. // last line is always blank
  51. lines = lines[0 : len(lines)-1]
  52. output := make([][]string, len(lines))
  53. for i, l := range lines {
  54. output[i] = strings.Split(l, "\t")
  55. }
  56. return output, nil
  57. }
  58. func setString(field *string, value string) {
  59. v := ""
  60. if value != "-" {
  61. v = value
  62. }
  63. *field = v
  64. }
  65. func setUint(field *uint64, value string) error {
  66. var v uint64
  67. if value != "-" {
  68. var err error
  69. v, err = strconv.ParseUint(value, 10, 64)
  70. if err != nil {
  71. return err
  72. }
  73. }
  74. *field = v
  75. return nil
  76. }
  77. func (d *Dataset) parseLine(line []string) error {
  78. var err error
  79. if len(line) != len(dsPropList) {
  80. return errors.New("output does not match what is expected on this platform")
  81. }
  82. setString(&d.Name, line[0])
  83. setString(&d.Origin, line[1])
  84. if err = setUint(&d.Used, line[2]); err != nil {
  85. return err
  86. }
  87. if err = setUint(&d.Avail, line[3]); err != nil {
  88. return err
  89. }
  90. setString(&d.Mountpoint, line[4])
  91. setString(&d.Compression, line[5])
  92. setString(&d.Type, line[6])
  93. if err = setUint(&d.Volsize, line[7]); err != nil {
  94. return err
  95. }
  96. if err = setUint(&d.Quota, line[8]); err != nil {
  97. return err
  98. }
  99. if err = setUint(&d.Referenced, line[9]); err != nil {
  100. return err
  101. }
  102. if runtime.GOOS == "solaris" {
  103. return nil
  104. }
  105. if err = setUint(&d.Written, line[10]); err != nil {
  106. return err
  107. }
  108. if err = setUint(&d.Logicalused, line[11]); err != nil {
  109. return err
  110. }
  111. return setUint(&d.Usedbydataset, line[12])
  112. }
  113. /*
  114. * from zfs diff`s escape function:
  115. *
  116. * Prints a file name out a character at a time. If the character is
  117. * not in the range of what we consider "printable" ASCII, display it
  118. * as an escaped 3-digit octal value. ASCII values less than a space
  119. * are all control characters and we declare the upper end as the
  120. * DELete character. This also is the last 7-bit ASCII character.
  121. * We choose to treat all 8-bit ASCII as not printable for this
  122. * application.
  123. */
  124. func unescapeFilepath(path string) (string, error) {
  125. buf := make([]byte, 0, len(path))
  126. llen := len(path)
  127. for i := 0; i < llen; {
  128. if path[i] == '\\' {
  129. if llen < i+4 {
  130. return "", fmt.Errorf("invalid octal code: too short")
  131. }
  132. octalCode := path[(i + 1):(i + 4)]
  133. val, err := strconv.ParseUint(octalCode, 8, 8)
  134. if err != nil {
  135. return "", fmt.Errorf("invalid octal code: %w", err)
  136. }
  137. buf = append(buf, byte(val))
  138. i += 4
  139. } else {
  140. buf = append(buf, path[i])
  141. i++
  142. }
  143. }
  144. return string(buf), nil
  145. }
  146. var changeTypeMap = map[string]ChangeType{
  147. "-": Removed,
  148. "+": Created,
  149. "M": Modified,
  150. "R": Renamed,
  151. }
  152. var inodeTypeMap = map[string]InodeType{
  153. "B": BlockDevice,
  154. "C": CharacterDevice,
  155. "/": Directory,
  156. ">": Door,
  157. "|": NamedPipe,
  158. "@": SymbolicLink,
  159. "P": EventPort,
  160. "=": Socket,
  161. "F": File,
  162. }
  163. // matches (+1) or (-1).
  164. var referenceCountRegex = regexp.MustCompile(`\(([+-]\d+?)\)`)
  165. func parseReferenceCount(field string) (int, error) {
  166. matches := referenceCountRegex.FindStringSubmatch(field)
  167. if matches == nil {
  168. return 0, fmt.Errorf("regexp does not match")
  169. }
  170. return strconv.Atoi(matches[1])
  171. }
  172. func parseInodeChange(line []string) (*InodeChange, error) {
  173. llen := len(line) // nolint:ifshort // llen *is* actually used
  174. if llen < 1 {
  175. return nil, fmt.Errorf("empty line passed")
  176. }
  177. changeType := changeTypeMap[line[0]]
  178. if changeType == 0 {
  179. return nil, fmt.Errorf("unknown change type '%s'", line[0])
  180. }
  181. switch changeType {
  182. case Renamed:
  183. if llen != 4 {
  184. return nil, fmt.Errorf("mismatching number of fields: expect 4, got: %d", llen)
  185. }
  186. case Modified:
  187. if llen != 4 && llen != 3 {
  188. return nil, fmt.Errorf("mismatching number of fields: expect 3..4, got: %d", llen)
  189. }
  190. default:
  191. if llen != 3 {
  192. return nil, fmt.Errorf("mismatching number of fields: expect 3, got: %d", llen)
  193. }
  194. }
  195. inodeType := inodeTypeMap[line[1]]
  196. if inodeType == 0 {
  197. return nil, fmt.Errorf("unknown inode type '%s'", line[1])
  198. }
  199. path, err := unescapeFilepath(line[2])
  200. if err != nil {
  201. return nil, fmt.Errorf("failed to parse filename: %w", err)
  202. }
  203. var newPath string
  204. var referenceCount int
  205. switch changeType {
  206. case Renamed:
  207. newPath, err = unescapeFilepath(line[3])
  208. if err != nil {
  209. return nil, fmt.Errorf("failed to parse filename: %w", err)
  210. }
  211. case Modified:
  212. if llen == 4 {
  213. referenceCount, err = parseReferenceCount(line[3])
  214. if err != nil {
  215. return nil, fmt.Errorf("failed to parse reference count: %w", err)
  216. }
  217. }
  218. default:
  219. newPath = ""
  220. }
  221. return &InodeChange{
  222. Change: changeType,
  223. Type: inodeType,
  224. Path: path,
  225. NewPath: newPath,
  226. ReferenceCountChange: referenceCount,
  227. }, nil
  228. }
  229. // example input for parseInodeChanges
  230. // M / /testpool/bar/
  231. // + F /testpool/bar/hello.txt
  232. // M / /testpool/bar/hello.txt (+1)
  233. // M / /testpool/bar/hello-hardlink
  234. func parseInodeChanges(lines [][]string) ([]*InodeChange, error) {
  235. changes := make([]*InodeChange, len(lines))
  236. for i, line := range lines {
  237. c, err := parseInodeChange(line)
  238. if err != nil {
  239. return nil, fmt.Errorf("failed to parse line %d of zfs diff: %w, got: '%s'", i, err, line)
  240. }
  241. changes[i] = c
  242. }
  243. return changes, nil
  244. }
  245. func listByType(t, filter string) ([]*Dataset, error) {
  246. args := []string{"list", "-rHp", "-t", t, "-o", dsPropListOptions}
  247. if filter != "" {
  248. args = append(args, filter)
  249. }
  250. out, err := zfsOutput(args...)
  251. if err != nil {
  252. return nil, err
  253. }
  254. var datasets []*Dataset
  255. name := ""
  256. var ds *Dataset
  257. for _, line := range out {
  258. if name != line[0] {
  259. name = line[0]
  260. ds = &Dataset{Name: name}
  261. datasets = append(datasets, ds)
  262. }
  263. if err := ds.parseLine(line); err != nil {
  264. return nil, err
  265. }
  266. }
  267. return datasets, nil
  268. }
  269. func propsSlice(properties map[string]string) []string {
  270. args := make([]string, 0, len(properties)*3)
  271. for k, v := range properties {
  272. args = append(args, "-o")
  273. args = append(args, fmt.Sprintf("%s=%s", k, v))
  274. }
  275. return args
  276. }
  277. func (z *Zpool) parseLine(line []string) error {
  278. prop := line[1]
  279. val := line[2]
  280. var err error
  281. switch prop {
  282. case "name":
  283. setString(&z.Name, val)
  284. case "health":
  285. setString(&z.Health, val)
  286. case "allocated":
  287. err = setUint(&z.Allocated, val)
  288. case "size":
  289. err = setUint(&z.Size, val)
  290. case "free":
  291. err = setUint(&z.Free, val)
  292. case "fragmentation":
  293. // Trim trailing "%" before parsing uint
  294. i := strings.Index(val, "%")
  295. if i < 0 {
  296. i = len(val)
  297. }
  298. err = setUint(&z.Fragmentation, val[:i])
  299. case "readonly":
  300. z.ReadOnly = val == "on"
  301. case "freeing":
  302. err = setUint(&z.Freeing, val)
  303. case "leaked":
  304. err = setUint(&z.Leaked, val)
  305. case "dedupratio":
  306. // Trim trailing "x" before parsing float64
  307. z.DedupRatio, err = strconv.ParseFloat(val[:len(val)-1], 64)
  308. }
  309. return err
  310. }