PageRenderTime 40ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/src/github.com/mistifyio/go-zfs/utils.go

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