PageRenderTime 1187ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://github.com/dotcloud/docker
Go | 360 lines | 290 code | 49 blank | 21 comment | 84 complexity | 368107dc82b1e8befa1386316e46b523 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-2-Clause, CC-BY-4.0, 0BSD, CC-BY-SA-4.0, GPL-2.0, BSD-3-Clause, MIT
  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 := 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[1:]}, " "),
  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 err = setUint(&ds.Referenced, line[9]); err != nil {
  98. return err
  99. }
  100. if runtime.GOOS == "solaris" {
  101. return nil
  102. }
  103. if err = setUint(&ds.Written, line[10]); err != nil {
  104. return err
  105. }
  106. if err = setUint(&ds.Logicalused, line[11]); err != nil {
  107. return err
  108. }
  109. if err = setUint(&ds.Usedbydataset, line[12]); err != nil {
  110. return err
  111. }
  112. return nil
  113. }
  114. /*
  115. * from zfs diff`s escape function:
  116. *
  117. * Prints a file name out a character at a time. If the character is
  118. * not in the range of what we consider "printable" ASCII, display it
  119. * as an escaped 3-digit octal value. ASCII values less than a space
  120. * are all control characters and we declare the upper end as the
  121. * DELete character. This also is the last 7-bit ASCII character.
  122. * We choose to treat all 8-bit ASCII as not printable for this
  123. * application.
  124. */
  125. func unescapeFilepath(path string) (string, error) {
  126. buf := make([]byte, 0, len(path))
  127. llen := len(path)
  128. for i := 0; i < llen; {
  129. if path[i] == '\\' {
  130. if llen < i+4 {
  131. return "", fmt.Errorf("Invalid octal code: too short")
  132. }
  133. octalCode := path[(i + 1):(i + 4)]
  134. val, err := strconv.ParseUint(octalCode, 8, 8)
  135. if err != nil {
  136. return "", fmt.Errorf("Invalid octal code: %v", err)
  137. }
  138. buf = append(buf, byte(val))
  139. i += 4
  140. } else {
  141. buf = append(buf, path[i])
  142. i++
  143. }
  144. }
  145. return string(buf), nil
  146. }
  147. var changeTypeMap = map[string]ChangeType{
  148. "-": Removed,
  149. "+": Created,
  150. "M": Modified,
  151. "R": Renamed,
  152. }
  153. var inodeTypeMap = map[string]InodeType{
  154. "B": BlockDevice,
  155. "C": CharacterDevice,
  156. "/": Directory,
  157. ">": Door,
  158. "|": NamedPipe,
  159. "@": SymbolicLink,
  160. "P": EventPort,
  161. "=": Socket,
  162. "F": File,
  163. }
  164. // matches (+1) or (-1)
  165. var referenceCountRegex = regexp.MustCompile("\\(([+-]\\d+?)\\)")
  166. func parseReferenceCount(field string) (int, error) {
  167. matches := referenceCountRegex.FindStringSubmatch(field)
  168. if matches == nil {
  169. return 0, fmt.Errorf("Regexp does not match")
  170. }
  171. return strconv.Atoi(matches[1])
  172. }
  173. func parseInodeChange(line []string) (*InodeChange, error) {
  174. llen := len(line)
  175. if llen < 1 {
  176. return nil, fmt.Errorf("Empty line passed")
  177. }
  178. changeType := changeTypeMap[line[0]]
  179. if changeType == 0 {
  180. return nil, fmt.Errorf("Unknown change type '%s'", line[0])
  181. }
  182. switch changeType {
  183. case Renamed:
  184. if llen != 4 {
  185. return nil, fmt.Errorf("Mismatching number of fields: expect 4, got: %d", llen)
  186. }
  187. case Modified:
  188. if llen != 4 && llen != 3 {
  189. return nil, fmt.Errorf("Mismatching number of fields: expect 3..4, got: %d", llen)
  190. }
  191. default:
  192. if llen != 3 {
  193. return nil, fmt.Errorf("Mismatching number of fields: expect 3, got: %d", llen)
  194. }
  195. }
  196. inodeType := inodeTypeMap[line[1]]
  197. if inodeType == 0 {
  198. return nil, fmt.Errorf("Unknown inode type '%s'", line[1])
  199. }
  200. path, err := unescapeFilepath(line[2])
  201. if err != nil {
  202. return nil, fmt.Errorf("Failed to parse filename: %v", err)
  203. }
  204. var newPath string
  205. var referenceCount int
  206. switch changeType {
  207. case Renamed:
  208. newPath, err = unescapeFilepath(line[3])
  209. if err != nil {
  210. return nil, fmt.Errorf("Failed to parse filename: %v", err)
  211. }
  212. case Modified:
  213. if llen == 4 {
  214. referenceCount, err = parseReferenceCount(line[3])
  215. if err != nil {
  216. return nil, fmt.Errorf("Failed to parse reference count: %v", err)
  217. }
  218. }
  219. default:
  220. newPath = ""
  221. }
  222. return &InodeChange{
  223. Change: changeType,
  224. Type: inodeType,
  225. Path: path,
  226. NewPath: newPath,
  227. ReferenceCountChange: referenceCount,
  228. }, nil
  229. }
  230. // example input
  231. //M / /testpool/bar/
  232. //+ F /testpool/bar/hello.txt
  233. //M / /testpool/bar/hello.txt (+1)
  234. //M / /testpool/bar/hello-hardlink
  235. func parseInodeChanges(lines [][]string) ([]*InodeChange, error) {
  236. changes := make([]*InodeChange, len(lines))
  237. for i, line := range lines {
  238. c, err := parseInodeChange(line)
  239. if err != nil {
  240. return nil, fmt.Errorf("Failed to parse line %d of zfs diff: %v, got: '%s'", i, err, line)
  241. }
  242. changes[i] = c
  243. }
  244. return changes, nil
  245. }
  246. func listByType(t, filter string) ([]*Dataset, error) {
  247. args := []string{"list", "-rHp", "-t", t, "-o", dsPropListOptions}
  248. if filter != "" {
  249. args = append(args, filter)
  250. }
  251. out, err := zfs(args...)
  252. if err != nil {
  253. return nil, err
  254. }
  255. var datasets []*Dataset
  256. name := ""
  257. var ds *Dataset
  258. for _, line := range out {
  259. if name != line[0] {
  260. name = line[0]
  261. ds = &Dataset{Name: name}
  262. datasets = append(datasets, ds)
  263. }
  264. if err := ds.parseLine(line); err != nil {
  265. return nil, err
  266. }
  267. }
  268. return datasets, nil
  269. }
  270. func propsSlice(properties map[string]string) []string {
  271. args := make([]string, 0, len(properties)*3)
  272. for k, v := range properties {
  273. args = append(args, "-o")
  274. args = append(args, fmt.Sprintf("%s=%s", k, v))
  275. }
  276. return args
  277. }
  278. func (z *Zpool) parseLine(line []string) error {
  279. prop := line[1]
  280. val := line[2]
  281. var err error
  282. switch prop {
  283. case "name":
  284. setString(&z.Name, val)
  285. case "health":
  286. setString(&z.Health, val)
  287. case "allocated":
  288. err = setUint(&z.Allocated, val)
  289. case "size":
  290. err = setUint(&z.Size, val)
  291. case "free":
  292. err = setUint(&z.Free, val)
  293. case "fragmentation":
  294. // Trim trailing "%" before parsing uint
  295. i := strings.Index(val, "%")
  296. if i < 0 {
  297. i = len(val)
  298. }
  299. err = setUint(&z.Fragmentation, val[:i])
  300. case "readonly":
  301. z.ReadOnly = val == "on"
  302. case "freeing":
  303. err = setUint(&z.Freeing, val)
  304. case "leaked":
  305. err = setUint(&z.Leaked, val)
  306. case "dedupratio":
  307. // Trim trailing "x" before parsing float64
  308. z.DedupRatio, err = strconv.ParseFloat(val[:len(val)-1], 64)
  309. }
  310. return err
  311. }