zfs.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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, snapshot, or volume.
  11. const (
  12. DatasetFilesystem = "filesystem"
  13. DatasetSnapshot = "snapshot"
  14. DatasetVolume = "volume"
  15. )
  16. // Dataset is a ZFS dataset. A dataset could be a clone, filesystem, snapshot, or volume.
  17. // The Type struct member can be used to determine a dataset's type.
  18. //
  19. // The field definitions can be found in the ZFS manual:
  20. // https://openzfs.github.io/openzfs-docs/man/7/zfsprops.7.html.
  21. type Dataset struct {
  22. Name string
  23. Origin string
  24. Used uint64
  25. Avail uint64
  26. Mountpoint string
  27. Compression string
  28. Type string
  29. Written uint64
  30. Volsize uint64
  31. Logicalused uint64
  32. Usedbydataset uint64
  33. Quota uint64
  34. Referenced 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([]string) {
  85. }
  86. var logger Logger = &defaultLogger{}
  87. // SetLogger set a log handler to log all commands including arguments before they are executed.
  88. func SetLogger(l Logger) {
  89. if l != nil {
  90. logger = l
  91. }
  92. }
  93. // zfs is a helper function to wrap typical calls to zfs that ignores stdout.
  94. func zfs(arg ...string) error {
  95. _, err := zfsOutput(arg...)
  96. return err
  97. }
  98. // zfs is a helper function to wrap typical calls to zfs.
  99. func zfsOutput(arg ...string) ([][]string, error) {
  100. c := command{Command: "zfs"}
  101. return c.Run(arg...)
  102. }
  103. // Datasets returns a slice of ZFS datasets, regardless of type.
  104. // A filter argument may be passed to select a dataset with the matching name, or empty string ("") may be used to select all datasets.
  105. func Datasets(filter string) ([]*Dataset, error) {
  106. return listByType("all", filter)
  107. }
  108. // Snapshots returns a slice of ZFS snapshots.
  109. // A filter argument may be passed to select a snapshot with the matching name, 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, 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, or empty string ("") may be used to select all volumes.
  120. func Volumes(filter string) ([]*Dataset, error) {
  121. return listByType(DatasetVolume, filter)
  122. }
  123. // GetDataset retrieves a single ZFS dataset by name.
  124. // This dataset could be any valid ZFS dataset type, such as a clone, filesystem, snapshot, or volume.
  125. func GetDataset(name string) (*Dataset, error) {
  126. out, err := zfsOutput("list", "-Hp", "-o", dsPropListOptions, name)
  127. if err != nil {
  128. return nil, err
  129. }
  130. ds := &Dataset{Name: name}
  131. for _, line := range out {
  132. if err := ds.parseLine(line); err != nil {
  133. return nil, err
  134. }
  135. }
  136. return ds, nil
  137. }
  138. // Clone clones a ZFS snapshot and returns a clone dataset.
  139. // An error will be returned if the input dataset is not of snapshot type.
  140. func (d *Dataset) Clone(dest string, properties map[string]string) (*Dataset, error) {
  141. if d.Type != DatasetSnapshot {
  142. return nil, errors.New("can only clone snapshots")
  143. }
  144. args := make([]string, 2, 4)
  145. args[0] = "clone"
  146. args[1] = "-p"
  147. if properties != nil {
  148. args = append(args, propsSlice(properties)...)
  149. }
  150. args = append(args, []string{d.Name, dest}...)
  151. if err := zfs(args...); err != nil {
  152. return nil, err
  153. }
  154. return GetDataset(dest)
  155. }
  156. // Unmount unmounts currently mounted ZFS file systems.
  157. func (d *Dataset) Unmount(force bool) (*Dataset, error) {
  158. if d.Type == DatasetSnapshot {
  159. return nil, errors.New("cannot unmount snapshots")
  160. }
  161. args := make([]string, 1, 3)
  162. args[0] = "umount"
  163. if force {
  164. args = append(args, "-f")
  165. }
  166. args = append(args, d.Name)
  167. if err := zfs(args...); err != nil {
  168. return nil, err
  169. }
  170. return GetDataset(d.Name)
  171. }
  172. // Mount mounts ZFS file systems.
  173. func (d *Dataset) Mount(overlay bool, options []string) (*Dataset, error) {
  174. if d.Type == DatasetSnapshot {
  175. return nil, errors.New("cannot mount snapshots")
  176. }
  177. args := make([]string, 1, 5)
  178. args[0] = "mount"
  179. if overlay {
  180. args = append(args, "-O")
  181. }
  182. if options != nil {
  183. args = append(args, "-o")
  184. args = append(args, strings.Join(options, ","))
  185. }
  186. args = append(args, d.Name)
  187. if err := zfs(args...); err != nil {
  188. return nil, err
  189. }
  190. return GetDataset(d.Name)
  191. }
  192. // ReceiveSnapshot receives a ZFS stream from the input io.Reader.
  193. // A new snapshot is created with the specified name, and streams the input data into the newly-created snapshot.
  194. func ReceiveSnapshot(input io.Reader, name string) (*Dataset, error) {
  195. c := command{Command: "zfs", Stdin: input}
  196. if _, err := c.Run("receive", name); err != nil {
  197. return nil, err
  198. }
  199. return GetDataset(name)
  200. }
  201. // SendSnapshot sends a ZFS stream of a snapshot to the input io.Writer.
  202. // An error will be returned if the input dataset is not of snapshot type.
  203. func (d *Dataset) SendSnapshot(output io.Writer) error {
  204. if d.Type != DatasetSnapshot {
  205. return errors.New("can only send snapshots")
  206. }
  207. c := command{Command: "zfs", Stdout: output}
  208. _, err := c.Run("send", d.Name)
  209. return err
  210. }
  211. // IncrementalSend sends a ZFS stream of a snapshot to the input io.Writer using the baseSnapshot as the starting point.
  212. // An error will be returned if the input dataset is not of snapshot type.
  213. func (d *Dataset) IncrementalSend(baseSnapshot *Dataset, output io.Writer) error {
  214. if d.Type != DatasetSnapshot || baseSnapshot.Type != DatasetSnapshot {
  215. return errors.New("can only send snapshots")
  216. }
  217. c := command{Command: "zfs", Stdout: output}
  218. _, err := c.Run("send", "-i", baseSnapshot.Name, d.Name)
  219. return err
  220. }
  221. // CreateVolume creates a new ZFS volume with the specified name, size, and properties.
  222. //
  223. // A full list of available ZFS properties may be found in the ZFS manual:
  224. // https://openzfs.github.io/openzfs-docs/man/7/zfsprops.7.html.
  225. func CreateVolume(name string, size uint64, properties map[string]string) (*Dataset, error) {
  226. args := make([]string, 4, 5)
  227. args[0] = "create"
  228. args[1] = "-p"
  229. args[2] = "-V"
  230. args[3] = strconv.FormatUint(size, 10)
  231. if properties != nil {
  232. args = append(args, propsSlice(properties)...)
  233. }
  234. args = append(args, name)
  235. if err := zfs(args...); err != nil {
  236. return nil, err
  237. }
  238. return GetDataset(name)
  239. }
  240. // Destroy destroys a ZFS dataset.
  241. // If the destroy bit flag is set, any descendents of the dataset will be recursively destroyed, including snapshots.
  242. // If the deferred bit flag is set, the snapshot is marked for deferred deletion.
  243. func (d *Dataset) Destroy(flags DestroyFlag) error {
  244. args := make([]string, 1, 3)
  245. args[0] = "destroy"
  246. if flags&DestroyRecursive != 0 {
  247. args = append(args, "-r")
  248. }
  249. if flags&DestroyRecursiveClones != 0 {
  250. args = append(args, "-R")
  251. }
  252. if flags&DestroyDeferDeletion != 0 {
  253. args = append(args, "-d")
  254. }
  255. if flags&DestroyForceUmount != 0 {
  256. args = append(args, "-f")
  257. }
  258. args = append(args, d.Name)
  259. err := zfs(args...)
  260. return err
  261. }
  262. // SetProperty sets a ZFS property on the receiving dataset.
  263. //
  264. // A full list of available ZFS properties may be found in the ZFS manual:
  265. // https://openzfs.github.io/openzfs-docs/man/7/zfsprops.7.html.
  266. func (d *Dataset) SetProperty(key, val string) error {
  267. prop := strings.Join([]string{key, val}, "=")
  268. err := zfs("set", prop, d.Name)
  269. return err
  270. }
  271. // GetProperty returns the current value of a ZFS property from the receiving dataset.
  272. //
  273. // A full list of available ZFS properties may be found in the ZFS manual:
  274. // https://openzfs.github.io/openzfs-docs/man/7/zfsprops.7.html.
  275. func (d *Dataset) GetProperty(key string) (string, error) {
  276. out, err := zfsOutput("get", "-H", key, d.Name)
  277. if err != nil {
  278. return "", err
  279. }
  280. return out[0][2], nil
  281. }
  282. // Rename renames a dataset.
  283. func (d *Dataset) Rename(name string, createParent, recursiveRenameSnapshots bool) (*Dataset, error) {
  284. args := make([]string, 3, 5)
  285. args[0] = "rename"
  286. args[1] = d.Name
  287. args[2] = name
  288. if createParent {
  289. args = append(args, "-p")
  290. }
  291. if recursiveRenameSnapshots {
  292. args = append(args, "-r")
  293. }
  294. if err := zfs(args...); err != nil {
  295. return d, err
  296. }
  297. return GetDataset(name)
  298. }
  299. // Snapshots returns a slice of all ZFS snapshots of a given dataset.
  300. func (d *Dataset) Snapshots() ([]*Dataset, error) {
  301. return Snapshots(d.Name)
  302. }
  303. // CreateFilesystem creates a new ZFS filesystem with the specified name and properties.
  304. //
  305. // A full list of available ZFS properties may be found in the ZFS manual:
  306. // https://openzfs.github.io/openzfs-docs/man/7/zfsprops.7.html.
  307. func CreateFilesystem(name string, properties map[string]string) (*Dataset, error) {
  308. args := make([]string, 1, 4)
  309. args[0] = "create"
  310. if properties != nil {
  311. args = append(args, propsSlice(properties)...)
  312. }
  313. args = append(args, name)
  314. if err := zfs(args...); 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 specified name.
  320. // Optionally, the snapshot can be taken recursively, creating snapshots of all descendent filesystems in a single, atomic operation.
  321. func (d *Dataset) Snapshot(name string, recursive bool) (*Dataset, error) {
  322. args := make([]string, 1, 4)
  323. args[0] = "snapshot"
  324. if recursive {
  325. args = append(args, "-r")
  326. }
  327. snapName := fmt.Sprintf("%s@%s", d.Name, name)
  328. args = append(args, snapName)
  329. if err := zfs(args...); err != nil {
  330. return nil, err
  331. }
  332. return GetDataset(snapName)
  333. }
  334. // Rollback rolls back the receiving ZFS dataset to a previous snapshot.
  335. // Optionally, intermediate snapshots can be destroyed.
  336. // A ZFS snapshot rollback cannot be completed without this option, if more recent snapshots exist.
  337. // An error will be returned if the input dataset is not of snapshot type.
  338. func (d *Dataset) Rollback(destroyMoreRecent bool) error {
  339. if d.Type != DatasetSnapshot {
  340. return errors.New("can only rollback snapshots")
  341. }
  342. args := make([]string, 1, 3)
  343. args[0] = "rollback"
  344. if destroyMoreRecent {
  345. args = append(args, "-r")
  346. }
  347. args = append(args, d.Name)
  348. err := zfs(args...)
  349. return err
  350. }
  351. // Children returns a slice of children of the receiving ZFS dataset.
  352. // A recursion depth may be specified, or a depth of 0 allows unlimited recursion.
  353. func (d *Dataset) Children(depth uint64) ([]*Dataset, error) {
  354. args := []string{"list"}
  355. if depth > 0 {
  356. args = append(args, "-d")
  357. args = append(args, strconv.FormatUint(depth, 10))
  358. } else {
  359. args = append(args, "-r")
  360. }
  361. args = append(args, "-t", "all", "-Hp", "-o", dsPropListOptions)
  362. args = append(args, d.Name)
  363. out, err := zfsOutput(args...)
  364. if err != nil {
  365. return nil, err
  366. }
  367. var datasets []*Dataset
  368. name := ""
  369. var ds *Dataset
  370. for _, line := range out {
  371. if name != line[0] {
  372. name = line[0]
  373. ds = &Dataset{Name: name}
  374. datasets = append(datasets, ds)
  375. }
  376. if err := ds.parseLine(line); err != nil {
  377. return nil, err
  378. }
  379. }
  380. return datasets[1:], nil
  381. }
  382. // Diff returns changes between a snapshot and the given ZFS dataset.
  383. // The snapshot name must include the filesystem part as it is possible to compare clones with their origin snapshots.
  384. func (d *Dataset) Diff(snapshot string) ([]*InodeChange, error) {
  385. args := []string{"diff", "-FH", snapshot, d.Name}
  386. out, err := zfsOutput(args...)
  387. if err != nil {
  388. return nil, err
  389. }
  390. inodeChanges, err := parseInodeChanges(out)
  391. if err != nil {
  392. return nil, err
  393. }
  394. return inodeChanges, nil
  395. }