snapsync 0.3.8 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +15 -2
- data/lib/snapsync/auto_sync.rb +75 -39
- data/lib/snapsync/btrfs.rb +123 -35
- data/lib/snapsync/btrfs_subvolume.rb +103 -0
- data/lib/snapsync/cleanup.rb +1 -0
- data/lib/snapsync/cli.rb +73 -42
- data/lib/snapsync/partitions_monitor.rb +74 -7
- data/lib/snapsync/remote_pathname.rb +330 -0
- data/lib/snapsync/snapper_config.rb +6 -0
- data/lib/snapsync/snapshot.rb +32 -3
- data/lib/snapsync/{local_sync.rb → snapshot_transfer.rb} +27 -28
- data/lib/snapsync/ssh_popen.rb +107 -0
- data/lib/snapsync/sync.rb +8 -4
- data/lib/snapsync/sync_all.rb +1 -1
- data/lib/snapsync/{local_target.rb → sync_target.rb} +14 -4
- data/lib/snapsync/test.rb +2 -9
- data/lib/snapsync/util.rb +18 -0
- data/lib/snapsync/version.rb +1 -1
- data/lib/snapsync.rb +31 -2
- data/snapsync.gemspec +13 -6
- data/snapsync.service +4 -1
- data/snapsync.timer +10 -0
- metadata +117 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c87326d0c598d929dd5d82c91fb635ef9e9542c6b55add928967f4979eb9d86f
|
4
|
+
data.tar.gz: ff4fc4dec816b21d5a1f9944f137b4ef43a48f5198e3b9b23951c7320d5f1777
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5483525d7f72e59e42f88c00c5ced3473c3d2bbd6682f390fc8a7b0fa68494f2197ebc654d47ef97821654844f9d26abf897919ab2b4cee108bd883e18efc82
|
7
|
+
data.tar.gz: 70f52a2ba5d403d23bc06a580a80bcb410ee5cecdb1e08d414463f03e1461b43d5970fb84a7acd8ea2baa6c5c43327e84597a6d1a6b8f0dda3975b68208887ca
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ snapper snapshot directory to a different location using btrfs send and
|
|
7
7
|
receive.
|
8
8
|
|
9
9
|
It can be used in two modes:
|
10
|
-
- in manual mode, you run snapsync
|
10
|
+
- in manual mode, you run snapsync
|
11
11
|
|
12
12
|
## Installation
|
13
13
|
|
@@ -15,7 +15,7 @@ You need to make sure that you've installed Ruby's bundler. On Ubuntu, run
|
|
15
15
|
$ apt install bundler
|
16
16
|
|
17
17
|
Then, the following will install snapsync in /opt/snapsync
|
18
|
-
|
18
|
+
|
19
19
|
$ wget https://raw.githubusercontent.com/doudou/snapsync/master/install.sh
|
20
20
|
$ sh install.sh
|
21
21
|
|
@@ -39,6 +39,10 @@ configurations, it will create /path/to/the/drive/snapsync/root and
|
|
39
39
|
/path/to/the/drive/snapsync/home). The 'default' synchronization policy is used
|
40
40
|
(see below for other options).
|
41
41
|
|
42
|
+
[EXPERIMENTAL] Snapsync targets can also be remote (ssh-reachable) filesystems by using the following scp-like target format:
|
43
|
+
|
44
|
+
$ snapsync init [user[:password]@]host:/path/to/drive/snapsync
|
45
|
+
|
42
46
|
If you use systemd, the background systemd job will from now on synchronize the
|
43
47
|
new target whenever it is present (i.e. as soon as it is plugged in). If you
|
44
48
|
don't, or if you decided to disable the service's auto-start, run (and keep on
|
@@ -64,6 +68,15 @@ Policies can be set at initialization time by passing additional arguments to
|
|
64
68
|
'snapsync init', or later with 'snapsync policy'. Run
|
65
69
|
'snapsync help init' and 'snapsync help policy' for more information.
|
66
70
|
|
71
|
+
E.g. If you want to keep the most recent 23 hourly, 6 daily, 3 weekly, 11
|
72
|
+
monthly, and 10 yearly snapshots:
|
73
|
+
|
74
|
+
$ snapsync policy /path/to/the/drive/snapsync/config_dir hour 23 day 6 week 3 month 11 year 10
|
75
|
+
|
76
|
+
If you only want the most 10 most recent day's snapshots:
|
77
|
+
|
78
|
+
$ snapsync policy /path/to/the/drive/snapsync/config_dir day 10
|
79
|
+
|
67
80
|
## Manual usage
|
68
81
|
|
69
82
|
If you prefer using snapsync manually, or use different automation that the one
|
data/lib/snapsync/auto_sync.rb
CHANGED
@@ -5,47 +5,89 @@ module Snapsync
|
|
5
5
|
# partition availability, and will run sync-all on each (declared) targets
|
6
6
|
# when they are available, optionally auto-mounting them
|
7
7
|
class AutoSync
|
8
|
-
AutoSyncTarget = Struct.new :partition_uuid, :
|
8
|
+
AutoSyncTarget = Struct.new :partition_uuid, :mountpoint, :relative, :automount, :name
|
9
9
|
|
10
10
|
attr_reader :config_dir
|
11
|
+
# @return [Hash<String,Array<AutoSyncTarget>>]
|
11
12
|
attr_reader :targets
|
12
13
|
attr_reader :partitions
|
13
14
|
|
14
15
|
DEFAULT_CONFIG_PATH = Pathname.new('/etc/snapsync.conf')
|
15
16
|
|
16
|
-
def
|
17
|
-
result = new
|
18
|
-
result.load_config
|
19
|
-
result
|
20
|
-
end
|
21
|
-
|
22
|
-
def initialize(config_dir = SnapperConfig.default_config_dir)
|
17
|
+
def initialize(config_dir = SnapperConfig.default_config_dir, snapsync_config_file: DEFAULT_CONFIG_PATH)
|
23
18
|
@config_dir = config_dir
|
24
19
|
@targets = Hash.new
|
25
20
|
@partitions = PartitionsMonitor.new
|
21
|
+
partitions.poll
|
22
|
+
|
23
|
+
if snapsync_config_file.exist?
|
24
|
+
load_config snapsync_config_file
|
25
|
+
end
|
26
26
|
end
|
27
27
|
|
28
|
-
def load_config(path = DEFAULT_CONFIG_PATH)
|
28
|
+
private def load_config(path = DEFAULT_CONFIG_PATH)
|
29
29
|
conf = YAML.load(path.read) || Array.new
|
30
|
+
parse_config_migrate_if_needed(path, conf)
|
31
|
+
end
|
32
|
+
|
33
|
+
private def parse_config_migrate_if_needed(path, conf)
|
34
|
+
migrated = false
|
35
|
+
if conf.is_a? Array
|
36
|
+
# Version 1
|
37
|
+
Snapsync.info "Migrating config from v1 to v2"
|
38
|
+
conf = config_migrate_v1_v2(conf)
|
39
|
+
migrated = true
|
40
|
+
elsif conf['version'] != 2
|
41
|
+
raise 'Unknown snapsync config version: %d ' % [conf.version]
|
42
|
+
end
|
30
43
|
parse_config(conf)
|
44
|
+
if migrated
|
45
|
+
write_config(path)
|
46
|
+
end
|
31
47
|
end
|
32
48
|
|
33
|
-
def
|
34
|
-
|
49
|
+
private def config_migrate_v1_v2(conf)
|
50
|
+
Snapsync.info "Migrating config from version 1 to version 2"
|
51
|
+
targets = conf.map do |target|
|
52
|
+
target['relative'] = target['path']
|
53
|
+
target.delete('path')
|
54
|
+
begin
|
55
|
+
target['mountpoint'] = partitions.mountpoint_of_uuid(target['partition_uuid'])
|
56
|
+
rescue
|
57
|
+
raise "Could not migrate config."
|
58
|
+
end
|
59
|
+
|
60
|
+
target
|
61
|
+
end
|
62
|
+
{
|
63
|
+
'version' => 2,
|
64
|
+
'targets' => targets
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
private def parse_config(conf)
|
69
|
+
conf['targets'].each do |hash|
|
35
70
|
target = AutoSyncTarget.new
|
36
71
|
hash.each { |k, v| target[k] = v }
|
37
|
-
target.
|
72
|
+
target.mountpoint = Snapsync::path(target.mountpoint)
|
73
|
+
target.relative = Snapsync::path(target.relative)
|
38
74
|
add(target)
|
39
75
|
end
|
40
76
|
end
|
41
77
|
|
42
78
|
def write_config(path)
|
43
|
-
data =
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
79
|
+
data = {
|
80
|
+
'version' => 2,
|
81
|
+
'targets' => each_autosync_target.map do |target|
|
82
|
+
target.to_h do |k,v|
|
83
|
+
if v.is_a? Snapsync::RemotePathname or v.is_a? Pathname
|
84
|
+
[k.to_s,v.to_s]
|
85
|
+
else
|
86
|
+
[k.to_s,v]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
}
|
49
91
|
File.open(path, 'w') do |io|
|
50
92
|
YAML.dump(data, io)
|
51
93
|
end
|
@@ -53,7 +95,7 @@ module Snapsync
|
|
53
95
|
|
54
96
|
# Enumerates the declared autosync targets
|
55
97
|
#
|
56
|
-
# @yieldparam [
|
98
|
+
# @yieldparam [AutoSyncTarget] target
|
57
99
|
# @return [void]
|
58
100
|
def each_autosync_target
|
59
101
|
return enum_for(__method__) if !block_given?
|
@@ -72,41 +114,34 @@ module Snapsync
|
|
72
114
|
# @return [void]
|
73
115
|
def each_available_autosync_target
|
74
116
|
return enum_for(__method__) if !block_given?
|
75
|
-
partitions.poll
|
76
|
-
|
77
|
-
partitions.known_partitions.each do |uuid, fs|
|
78
|
-
autosync_targets = targets[uuid]
|
79
|
-
next if autosync_targets.empty?
|
80
|
-
|
81
|
-
mp = fs['MountPoints'].first
|
82
|
-
if mp
|
83
|
-
mp = Pathname.new(mp[0..-2].pack("U*"))
|
84
|
-
end
|
85
117
|
|
118
|
+
each_autosync_target do |target|
|
119
|
+
# @type [RemotePathname]
|
86
120
|
begin
|
87
121
|
mounted = false
|
88
|
-
|
89
|
-
if
|
90
|
-
if
|
122
|
+
mountpoint = target.mountpoint
|
123
|
+
if not mountpoint.mountpoint?
|
124
|
+
if not target.automount
|
91
125
|
Snapsync.info "partition #{uuid} is present, but not mounted and automount is false. Ignoring"
|
92
126
|
next
|
93
127
|
end
|
94
128
|
|
95
129
|
Snapsync.info "partition #{uuid} is present, but not mounted, automounting"
|
96
130
|
begin
|
97
|
-
|
131
|
+
if mountpoint.is_a? RemotePathname
|
132
|
+
# TODO: automounting of remote paths
|
133
|
+
raise 'TODO'
|
134
|
+
else
|
135
|
+
fs.Mount([]).first
|
136
|
+
end
|
137
|
+
mounted = true
|
98
138
|
rescue Exception => e
|
99
139
|
Snapsync.warn "failed to mount, ignoring this target"
|
100
140
|
next
|
101
141
|
end
|
102
|
-
mp = Pathname.new(mp)
|
103
|
-
mounted = true
|
104
|
-
end
|
105
|
-
|
106
|
-
autosync_targets.each do |target|
|
107
|
-
yield(mp + target.path, target)
|
108
142
|
end
|
109
143
|
|
144
|
+
yield(target.mountpoint + target.relative, target)
|
110
145
|
ensure
|
111
146
|
if mounted
|
112
147
|
fs.Unmount([])
|
@@ -131,6 +166,7 @@ module Snapsync
|
|
131
166
|
end
|
132
167
|
end
|
133
168
|
|
169
|
+
# @param [AutoSyncTarget] target
|
134
170
|
def add(target)
|
135
171
|
targets[target.partition_uuid] ||= Array.new
|
136
172
|
targets[target.partition_uuid] << target
|
data/lib/snapsync/btrfs.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
1
|
module Snapsync
|
2
|
-
|
2
|
+
class Btrfs
|
3
|
+
class << self
|
4
|
+
# @return [Hash]
|
5
|
+
attr_accessor :_mountpointCache
|
6
|
+
end
|
7
|
+
self._mountpointCache = {}
|
8
|
+
|
3
9
|
class Error < RuntimeError
|
4
10
|
attr_reader :error_lines
|
5
11
|
def initialize(error_lines = Array.new)
|
6
12
|
@error_lines = error_lines
|
13
|
+
super error_lines
|
7
14
|
end
|
8
15
|
|
9
16
|
def pretty_print(pp)
|
@@ -20,49 +27,72 @@ module Snapsync
|
|
20
27
|
class UnexpectedBtrfsOutput < Error
|
21
28
|
end
|
22
29
|
|
23
|
-
|
30
|
+
include Comparable
|
31
|
+
def <=>(other)
|
32
|
+
mountpoint.to_s <=> other.mountpoint.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [AgnosticPath]
|
36
|
+
attr_reader :mountpoint
|
37
|
+
|
38
|
+
# @return [Array<SubvolumeMinimalInfo>]
|
39
|
+
attr_reader :subvolume_table
|
40
|
+
|
41
|
+
# @param [AgnosticPath] mountpoint
|
42
|
+
def initialize(mountpoint)
|
43
|
+
raise "Trying to create Btrfs wrapper on non-mountpoint #{mountpoint}" unless mountpoint.mountpoint?
|
44
|
+
|
45
|
+
Snapsync.debug "Creating Btrfs wrapper at #{mountpoint}"
|
46
|
+
@mountpoint = mountpoint
|
47
|
+
|
48
|
+
@subvolume_table = read_subvolume_table
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param [AgnosticPath] mountpoint
|
52
|
+
# @return [Btrfs]
|
53
|
+
def self.get(mountpoint)
|
54
|
+
mountpoint = mountpoint.findmnt
|
55
|
+
|
56
|
+
self._mountpointCache.fetch(mountpoint.to_s) do
|
57
|
+
btrfs = Btrfs.new mountpoint
|
58
|
+
self._mountpointCache[mountpoint.to_s] = btrfs
|
59
|
+
btrfs
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def btrfs_prog
|
24
64
|
ENV['BTRFS_PROG'] || 'btrfs'
|
25
65
|
end
|
26
66
|
|
27
67
|
# @api private
|
28
68
|
#
|
29
69
|
# A IO.popen-like API to btrfs subcommands
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
block_error, block_result = nil
|
34
|
-
IO.popen([btrfs_prog, *args, err: err_w, **options], mode) do |io|
|
35
|
-
err_w.close
|
70
|
+
# @yieldparam [IO] io
|
71
|
+
def popen(*args, mode: 'r', raise_on_error: true, **options)
|
72
|
+
Snapsync.debug "Btrfs(\"#{mountpoint}\").popen: #{args}"
|
36
73
|
|
74
|
+
if @mountpoint.is_a? RemotePathname
|
75
|
+
proc = SSHPopen.new(@mountpoint, [btrfs_prog, *args], **options)
|
76
|
+
return yield(proc)
|
77
|
+
else
|
78
|
+
# @type [IO,IO]
|
79
|
+
err_r, err_w = IO.pipe
|
37
80
|
begin
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
81
|
+
IO.popen([btrfs_prog, *args], err: err_w, mode: mode) do |io|
|
82
|
+
err_w.close
|
83
|
+
return yield(io)
|
84
|
+
end
|
85
|
+
if not $?.success?
|
86
|
+
raise Error.new, err_r.read.lines
|
87
|
+
end
|
88
|
+
ensure err_r.close
|
42
89
|
end
|
43
|
-
end
|
44
90
|
|
45
|
-
if $?.success? && !block_error
|
46
|
-
block_result
|
47
|
-
elsif raise_on_error
|
48
|
-
if block_error
|
49
|
-
raise Error.new, block_error.message
|
50
|
-
else
|
51
|
-
raise Error.new, "btrfs failed"
|
52
|
-
end
|
53
91
|
end
|
54
|
-
|
55
|
-
rescue Error => e
|
56
|
-
prefix = args.join(" ")
|
57
|
-
lines = err_r.readlines.map do |line|
|
58
|
-
"#{prefix}: #{line.chomp}"
|
59
|
-
end
|
60
|
-
raise Error.new(e.error_lines + lines), e.message, e.backtrace
|
61
|
-
|
62
|
-
ensure err_r.close
|
63
92
|
end
|
64
93
|
|
65
|
-
|
94
|
+
# @return [String]
|
95
|
+
def run(*args, **options)
|
66
96
|
popen(*args, **options) do |io|
|
67
97
|
io.read.encode('utf-8', undef: :replace, invalid: :replace)
|
68
98
|
end
|
@@ -72,12 +102,13 @@ module Snapsync
|
|
72
102
|
#
|
73
103
|
# @param [Pathname] path the subvolume path
|
74
104
|
# @return [Integer] the subvolume's generation
|
75
|
-
def
|
76
|
-
info =
|
105
|
+
def generation_of(path)
|
106
|
+
info = run('subvolume', 'show', path.to_s)
|
77
107
|
if info =~ /Generation[^:]*:\s+(\d+)/
|
78
108
|
Integer($1)
|
79
109
|
else
|
80
|
-
raise UnexpectedBtrfsOutput, "unexpected output for 'btrfs subvolume show',
|
110
|
+
raise UnexpectedBtrfsOutput, "unexpected output for 'btrfs subvolume show'," \
|
111
|
+
+" expected '#{info}' to contain a 'Generation:' line"
|
81
112
|
end
|
82
113
|
end
|
83
114
|
|
@@ -94,9 +125,66 @@ module Snapsync
|
|
94
125
|
#
|
95
126
|
# @overload find_new(subvolume_dir, last_gen)
|
96
127
|
# @return [#each] an enumeration of the lines of the find-new output
|
97
|
-
def
|
128
|
+
def find_new(subvolume_dir, last_gen, &block)
|
98
129
|
run('subvolume', 'find-new', subvolume_dir.to_s, last_gen.to_s).
|
99
130
|
each_line(&block)
|
100
131
|
end
|
132
|
+
|
133
|
+
# @return [Array<SubvolumeMinimalInfo>]
|
134
|
+
def read_subvolume_table
|
135
|
+
table = []
|
136
|
+
|
137
|
+
text = run('subvolume', 'list','-pcgquR', mountpoint.path_part)
|
138
|
+
text.each_line do |l|
|
139
|
+
item = SubvolumeMinimalInfo.new
|
140
|
+
l.gsub!('top level', 'top_level')
|
141
|
+
l = l.split
|
142
|
+
l.each_slice(2) do |kv|
|
143
|
+
k,v = kv
|
144
|
+
if v == '-'
|
145
|
+
v = nil
|
146
|
+
else
|
147
|
+
begin
|
148
|
+
v = Integer(v)
|
149
|
+
rescue
|
150
|
+
# ignore
|
151
|
+
end
|
152
|
+
end
|
153
|
+
item.instance_variable_set '@'+k, v
|
154
|
+
end
|
155
|
+
table.push item
|
156
|
+
end
|
157
|
+
|
158
|
+
table
|
159
|
+
end
|
160
|
+
|
161
|
+
# @param [AgnosticPath] subvolume_dir
|
162
|
+
# @return [Hash<String>]
|
163
|
+
def subvolume_show(subvolume_dir)
|
164
|
+
# @type [String]
|
165
|
+
info = run('subvolume', 'show', subvolume_dir.path_part)
|
166
|
+
|
167
|
+
data = {}
|
168
|
+
|
169
|
+
data['absolute_dir'] = info.lines[0].strip
|
170
|
+
|
171
|
+
lines = info.lines[1..-1]
|
172
|
+
lines.each_index do |i|
|
173
|
+
l = lines[i]
|
174
|
+
k, _, v = l.partition ':'
|
175
|
+
k = k.strip.downcase.gsub ' ', '_'
|
176
|
+
|
177
|
+
if k == 'snapshot(s)'
|
178
|
+
data['snapshots'] = lines[i+1..-1].map do |s|
|
179
|
+
s.strip
|
180
|
+
end
|
181
|
+
break
|
182
|
+
else
|
183
|
+
data[k] = v.strip
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
data
|
188
|
+
end
|
101
189
|
end
|
102
190
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Snapsync
|
2
|
+
# Output of `btrfs subvolume list`
|
3
|
+
class SubvolumeMinimalInfo
|
4
|
+
attr_reader :id
|
5
|
+
attr_reader :uuid
|
6
|
+
attr_reader :path
|
7
|
+
|
8
|
+
attr_reader :gen
|
9
|
+
attr_reader :cgen
|
10
|
+
|
11
|
+
attr_reader :parent
|
12
|
+
attr_reader :top_level
|
13
|
+
|
14
|
+
# @return [String,nil]
|
15
|
+
attr_reader :parent_uuid
|
16
|
+
# @return [String, nil]
|
17
|
+
attr_reader :received_uuid
|
18
|
+
end
|
19
|
+
|
20
|
+
class SubvolumeInfo
|
21
|
+
|
22
|
+
# @return [Btrfs]
|
23
|
+
attr_reader :btrfs
|
24
|
+
|
25
|
+
# @return [AgnosticPath]
|
26
|
+
attr_reader :subvolume_dir
|
27
|
+
|
28
|
+
# The absolute path in the btrfs filesystem
|
29
|
+
# @return [String]
|
30
|
+
attr_reader :absolute_dir
|
31
|
+
|
32
|
+
# @return [String]
|
33
|
+
attr_reader :name
|
34
|
+
|
35
|
+
# @return [String]
|
36
|
+
attr_reader :uuid
|
37
|
+
|
38
|
+
# Denotes a subvolume that's a direct parent in the snapshot's timeline.
|
39
|
+
# I.e. [parent -> self] difference possible
|
40
|
+
# @return [String]
|
41
|
+
attr_reader :parent_uuid
|
42
|
+
|
43
|
+
# Denotes the UUID of the subvolume sent by 'btrfs send'
|
44
|
+
# @return [String]
|
45
|
+
attr_reader :received_uuid
|
46
|
+
|
47
|
+
# @return [String]
|
48
|
+
attr_reader :creation_time
|
49
|
+
|
50
|
+
# @return [Integer]
|
51
|
+
attr_reader :subvolume_id
|
52
|
+
|
53
|
+
# @return [Integer]
|
54
|
+
attr_reader :generation
|
55
|
+
|
56
|
+
# @return [Integer]
|
57
|
+
attr_reader :gen_at_creation
|
58
|
+
|
59
|
+
# @return [Integer]
|
60
|
+
attr_reader :parent_id
|
61
|
+
|
62
|
+
# @return [Integer]
|
63
|
+
attr_reader :top_level_id
|
64
|
+
|
65
|
+
# @return [String]
|
66
|
+
attr_reader :flags
|
67
|
+
|
68
|
+
# A transaction id in the sending btrfs filesystem for the `btrfs send` action.
|
69
|
+
# Does not correspond to anything in subvolumes.
|
70
|
+
# @return [Integer]
|
71
|
+
attr_reader :send_transid
|
72
|
+
|
73
|
+
# @return [String]
|
74
|
+
attr_reader :send_time
|
75
|
+
|
76
|
+
|
77
|
+
# The transaction of id of the start of the receive. The next transaction_id holds actual data and changes.
|
78
|
+
# It is +1 of the subvolume's, created by btrfs receive, gen_at_creation
|
79
|
+
# @return [Integer]
|
80
|
+
attr_reader :receive_transid
|
81
|
+
|
82
|
+
# @return [String]
|
83
|
+
attr_reader :receive_time
|
84
|
+
|
85
|
+
# @return [Array<String>]
|
86
|
+
attr_reader :snapshots
|
87
|
+
|
88
|
+
# @param [AgnosticPath] subvolume_dir
|
89
|
+
def initialize(subvolume_dir)
|
90
|
+
@subvolume_dir = subvolume_dir
|
91
|
+
@btrfs = Btrfs.get(subvolume_dir)
|
92
|
+
|
93
|
+
integers = Set[:subvolume_id, :generation, :gen_at_creation, :parent_id, :top_level_id, :send_transid, :receive_transid]
|
94
|
+
btrfs.subvolume_show(subvolume_dir).each do |k, v|
|
95
|
+
if integers.include? k.to_sym
|
96
|
+
instance_variable_set '@'+k, Integer(v)
|
97
|
+
else
|
98
|
+
instance_variable_set '@'+k, v
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|