snapsync 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|