zfs.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. // Package zfs provides wrappers around the ZFS command line tools.
  2. package zfs
  3. import (
  4. "errors"
  5. "fmt"
  6. "io"
  7. "strconv"
  8. "strings"
  9. )
  10. // ZFS dataset types, which can indicate if a dataset is a filesystem,
  11. // snapshot, or volume.
  12. const (
  13. DatasetFilesystem = "filesystem"
  14. DatasetSnapshot = "snapshot"
  15. DatasetVolume = "volume"
  16. )
  17. // Dataset is a ZFS dataset. A dataset could be a clone, filesystem, snapshot,
  18. // or volume. The Type struct member can be used to determine a dataset's type.
  19. //
  20. // The field definitions can be found in the ZFS manual:
  21. // http://www.freebsd.org/cgi/man.cgi?zfs(8).
  22. type Dataset struct {
  23. Name string
  24. Origin string
  25. Used uint64
  26. Avail uint64
  27. Mountpoint string
  28. Compression string
  29. Type string
  30. Written uint64
  31. Volsize uint64
  32. Usedbydataset uint64
  33. Logicalused uint64
  34. Quota uint64
  35. }
  36. // InodeType is the type of inode as reported by Diff
  37. type InodeType int
  38. // Types of Inodes
  39. const (
  40. _ = iota // 0 == unknown type
  41. BlockDevice InodeType = iota
  42. CharacterDevice
  43. Directory
  44. Door
  45. NamedPipe
  46. SymbolicLink
  47. EventPort
  48. Socket
  49. File
  50. )
  51. // ChangeType is the type of inode change as reported by Diff
  52. type ChangeType int
  53. // Types of Changes
  54. const (
  55. _ = iota // 0 == unknown type
  56. Removed ChangeType = iota
  57. Created
  58. Modified
  59. Renamed
  60. )
  61. // DestroyFlag is the options flag passed to Destroy
  62. type DestroyFlag int
  63. // Valid destroy options
  64. const (
  65. DestroyDefault DestroyFlag = 1 << iota
  66. DestroyRecursive = 1 << iota
  67. DestroyRecursiveClones = 1 << iota
  68. DestroyDeferDeletion = 1 << iota
  69. DestroyForceUmount = 1 << iota
  70. )
  71. // InodeChange represents a change as reported by Diff
  72. type InodeChange struct {
  73. Change ChangeType
  74. Type InodeType
  75. Path string
  76. NewPath string
  77. ReferenceCountChange int
  78. }
  79. // Logger can be used to log commands/actions
  80. type Logger interface {
  81. Log(cmd []string)
  82. }
  83. type defaultLogger struct{}
  84. func (*defaultLogger) Log(cmd []string) {
  85. return
  86. }
  87. var logger Logger = &defaultLogger{}
  88. // SetLogger set a log handler to log all commands including arguments before
  89. // they are executed
  90. func SetLogger(l Logger) {
  91. if l != nil {
  92. logger = l
  93. }
  94. }
  95. // zfs is a helper function to wrap typical calls to zfs.
  96. func zfs(arg ...string) ([][]string, error) {
  97. c := command{Command: "zfs"}
  98. return c.Run(arg...)
  99. }
  100. // Datasets returns a slice of ZFS datasets, regardless of type.
  101. // A filter argument may be passed to select a dataset with the matching name,
  102. // or empty string ("") may be used to select all datasets.
  103. func Datasets(filter string) ([]*Dataset, error) {
  104. return listByType("all", filter)
  105. }
  106. // Snapshots returns a slice of ZFS snapshots.
  107. // A filter argument may be passed to select a snapshot with the matching name,
  108. // or empty string ("") may be used to select all snapshots.
  109. func Snapshots(filter string) ([]*Dataset, error) {
  110. return listByType(DatasetSnapshot, filter)
  111. }
  112. // Filesystems returns a slice of ZFS filesystems.
  113. // A filter argument may be passed to select a filesystem with the matching name,
  114. // or empty string ("") may be used to select all filesystems.
  115. func Filesystems(filter string) ([]*Dataset, error) {
  116. return listByType(DatasetFilesystem, filter)
  117. }
  118. // Volumes returns a slice of ZFS volumes.
  119. // A filter argument may be passed to select a volume with the matching name,
  120. // or empty string ("") may be used to select all volumes.
  121. func Volumes(filter string) ([]*Dataset, error) {
  122. return listByType(DatasetVolume, filter)
  123. }
  124. // GetDataset retrieves a single ZFS dataset by name. This dataset could be
  125. // any valid ZFS dataset type, such as a clone, filesystem, snapshot, or volume.
  126. func GetDataset(name string) (*Dataset, error) {
  127. out, err := zfs("get", "-Hp", "all", name)
  128. if err != nil {
  129. return nil, err
  130. }
  131. ds := &Dataset{Name: name}
  132. for _, line := range out {
  133. if err := ds.parseLine(line); err != nil {
  134. return nil, err
  135. }
  136. }
  137. return ds, nil
  138. }
  139. // Clone clones a ZFS snapshot and returns a clone dataset.
  140. // An error will be returned if the input dataset is not of snapshot type.
  141. func (d *Dataset) Clone(dest string, properties map[string]string) (*Dataset, error) {
  142. if d.Type != DatasetSnapshot {
  143. return nil, errors.New("can only clone snapshots")
  144. }
  145. args := make([]string, 2, 4)
  146. args[0] = "clone"
  147. args[1] = "-p"
  148. if properties != nil {
  149. args = append(args, propsSlice(properties)...)
  150. }
  151. args = append(args, []string{d.Name, dest}...)
  152. _, err := zfs(args...)
  153. if err != nil {
  154. return nil, err
  155. }
  156. return GetDataset(dest)
  157. }
  158. // ReceiveSnapshot receives a ZFS stream from the input io.Reader, creates a
  159. // new snapshot with the specified name, and streams the input data into the
  160. // newly-created snapshot.
  161. func ReceiveSnapshot(input io.Reader, name string) (*Dataset, error) {
  162. c := command{Command: "zfs", Stdin: input}
  163. _, err := c.Run("receive", name)
  164. if err != nil {
  165. return nil, err
  166. }
  167. return GetDataset(name)
  168. }
  169. // SendSnapshot sends a ZFS stream of a snapshot to the input io.Writer.
  170. // An error will be returned if the input dataset is not of snapshot type.
  171. func (d *Dataset) SendSnapshot(output io.Writer) error {
  172. if d.Type != DatasetSnapshot {
  173. return errors.New("can only send snapshots")
  174. }
  175. c := command{Command: "zfs", Stdout: output}
  176. _, err := c.Run("send", d.Name)
  177. return err
  178. }
  179. // CreateVolume creates a new ZFS volume with the specified name, size, and
  180. // properties.
  181. // A full list of available ZFS properties may be found here:
  182. // https://www.freebsd.org/cgi/man.cgi?zfs(8).
  183. func CreateVolume(name string, size uint64, properties map[string]string) (*Dataset, error) {
  184. args := make([]string, 4, 5)
  185. args[0] = "create"
  186. args[1] = "-p"
  187. args[2] = "-V"
  188. args[3] = strconv.FormatUint(size, 10)
  189. if properties != nil {
  190. args = append(args, propsSlice(properties)...)
  191. }
  192. args = append(args, name)
  193. _, err := zfs(args...)
  194. if err != nil {
  195. return nil, err
  196. }
  197. return GetDataset(name)
  198. }
  199. // Destroy destroys a ZFS dataset. If the destroy bit flag is set, any
  200. // descendents of the dataset will be recursively destroyed, including snapshots.
  201. // If the deferred bit flag is set, the snapshot is marked for deferred
  202. // deletion.
  203. func (d *Dataset) Destroy(flags DestroyFlag) error {
  204. args := make([]string, 1, 3)
  205. args[0] = "destroy"
  206. if flags&DestroyRecursive != 0 {
  207. args = append(args, "-r")
  208. }
  209. if flags&DestroyRecursiveClones != 0 {
  210. args = append(args, "-R")
  211. }
  212. if flags&DestroyDeferDeletion != 0 {
  213. args = append(args, "-d")
  214. }
  215. if flags&DestroyForceUmount != 0 {
  216. args = append(args, "-f")
  217. }
  218. args = append(args, d.Name)
  219. _, err := zfs(args...)
  220. return err
  221. }
  222. // SetProperty sets a ZFS property on the receiving dataset.
  223. // A full list of available ZFS properties may be found here:
  224. // https://www.freebsd.org/cgi/man.cgi?zfs(8).
  225. func (d *Dataset) SetProperty(key, val string) error {
  226. prop := strings.Join([]string{key, val}, "=")
  227. _, err := zfs("set", prop, d.Name)
  228. return err
  229. }
  230. // GetProperty returns the current value of a ZFS property from the
  231. // receiving dataset.
  232. // A full list of available ZFS properties may be found here:
  233. // https://www.freebsd.org/cgi/man.cgi?zfs(8).
  234. func (d *Dataset) GetProperty(key string) (string, error) {
  235. out, err := zfs("get", key, d.Name)
  236. if err != nil {
  237. return "", err
  238. }
  239. return out[0][2], nil
  240. }
  241. // Snapshots returns a slice of all ZFS snapshots of a given dataset.
  242. func (d *Dataset) Snapshots() ([]*Dataset, error) {
  243. return Snapshots(d.Name)
  244. }
  245. // CreateFilesystem creates a new ZFS filesystem with the specified name and
  246. // properties.
  247. // A full list of available ZFS properties may be found here:
  248. // https://www.freebsd.org/cgi/man.cgi?zfs(8).
  249. func CreateFilesystem(name string, properties map[string]string) (*Dataset, error) {
  250. args := make([]string, 1, 4)
  251. args[0] = "create"
  252. if properties != nil {
  253. args = append(args, propsSlice(properties)...)
  254. }
  255. args = append(args, name)
  256. _, err := zfs(args...)
  257. if err != nil {
  258. return nil, err
  259. }
  260. return GetDataset(name)
  261. }
  262. // Snapshot creates a new ZFS snapshot of the receiving dataset, using the
  263. // specified name. Optionally, the snapshot can be taken recursively, creating
  264. // snapshots of all descendent filesystems in a single, atomic operation.
  265. func (d *Dataset) Snapshot(name string, recursive bool) (*Dataset, error) {
  266. args := make([]string, 1, 4)
  267. args[0] = "snapshot"
  268. if recursive {
  269. args = append(args, "-r")
  270. }
  271. snapName := fmt.Sprintf("%s@%s", d.Name, name)
  272. args = append(args, snapName)
  273. _, err := zfs(args...)
  274. if err != nil {
  275. return nil, err
  276. }
  277. return GetDataset(snapName)
  278. }
  279. // Rollback rolls back the receiving ZFS dataset to a previous snapshot.
  280. // Optionally, intermediate snapshots can be destroyed. A ZFS snapshot
  281. // rollback cannot be completed without this option, if more recent
  282. // snapshots exist.
  283. // An error will be returned if the input dataset is not of snapshot type.
  284. func (d *Dataset) Rollback(destroyMoreRecent bool) error {
  285. if d.Type != DatasetSnapshot {
  286. return errors.New("can only rollback snapshots")
  287. }
  288. args := make([]string, 1, 3)
  289. args[0] = "rollback"
  290. if destroyMoreRecent {
  291. args = append(args, "-r")
  292. }
  293. args = append(args, d.Name)
  294. _, err := zfs(args...)
  295. return err
  296. }
  297. // Children returns a slice of children of the receiving ZFS dataset.
  298. // A recursion depth may be specified, or a depth of 0 allows unlimited
  299. // recursion.
  300. func (d *Dataset) Children(depth uint64) ([]*Dataset, error) {
  301. args := []string{"get", "-t", "all", "-Hp", "all"}
  302. if depth > 0 {
  303. args = append(args, "-d")
  304. args = append(args, strconv.FormatUint(depth, 10))
  305. } else {
  306. args = append(args, "-r")
  307. }
  308. args = append(args, d.Name)
  309. out, err := zfs(args...)
  310. if err != nil {
  311. return nil, err
  312. }
  313. var datasets []*Dataset
  314. name := ""
  315. var ds *Dataset
  316. for _, line := range out {
  317. if name != line[0] {
  318. name = line[0]
  319. ds = &Dataset{Name: name}
  320. datasets = append(datasets, ds)
  321. }
  322. if err := ds.parseLine(line); err != nil {
  323. return nil, err
  324. }
  325. }
  326. return datasets[1:], nil
  327. }
  328. // Diff returns changes between a snapshot and the given ZFS dataset.
  329. // The snapshot name must include the filesystem part as it is possible to
  330. // compare clones with their origin snapshots.
  331. func (d *Dataset) Diff(snapshot string) ([]*InodeChange, error) {
  332. args := []string{"diff", "-FH", snapshot, d.Name}[:]
  333. out, err := zfs(args...)
  334. if err != nil {
  335. return nil, err
  336. }
  337. inodeChanges, err := parseInodeChanges(out)
  338. if err != nil {
  339. return nil, err
  340. }
  341. return inodeChanges, nil
  342. }