zfs.go 12 KB

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