utils.go 6.6 KB

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