Class: Snapsync::Btrfs

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/snapsync/btrfs.rb

Defined Under Namespace

Classes: Error, UnexpectedBtrfsOutput

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(mountpoint) ⇒ Btrfs

Returns a new instance of Btrfs.

Parameters:



42
43
44
45
46
47
48
49
# File 'lib/snapsync/btrfs.rb', line 42

def initialize(mountpoint)
    raise "Trying to create Btrfs wrapper on non-mountpoint #{mountpoint}" unless mountpoint.mountpoint?

    Snapsync.debug "Creating Btrfs wrapper at #{mountpoint}"
    @mountpoint = mountpoint

    @subvolume_table = read_subvolume_table
end

Class Attribute Details

._mountpointCacheHash

Returns:

  • (Hash)


5
6
7
# File 'lib/snapsync/btrfs.rb', line 5

def _mountpointCache
  @_mountpointCache
end

Instance Attribute Details

#mountpointAgnosticPath (readonly)

Returns:



36
37
38
# File 'lib/snapsync/btrfs.rb', line 36

def mountpoint
  @mountpoint
end

#subvolume_tableArray<SubvolumeMinimalInfo> (readonly)

Returns:



39
40
41
# File 'lib/snapsync/btrfs.rb', line 39

def subvolume_table
  @subvolume_table
end

Class Method Details

.get(mountpoint) ⇒ Btrfs

Parameters:

Returns:



53
54
55
56
57
58
59
60
61
# File 'lib/snapsync/btrfs.rb', line 53

def self.get(mountpoint)
    mountpoint = mountpoint.findmnt

    self._mountpointCache.fetch(mountpoint.to_s) do
        btrfs = Btrfs.new mountpoint
        self._mountpointCache[mountpoint.to_s] = btrfs
        btrfs
    end
end

Instance Method Details

#<=>(other) ⇒ Object



31
32
33
# File 'lib/snapsync/btrfs.rb', line 31

def <=>(other)
    mountpoint.to_s <=> other.mountpoint.to_s
end

#btrfs_progObject



63
64
65
# File 'lib/snapsync/btrfs.rb', line 63

def btrfs_prog
    ENV['BTRFS_PROG'] || 'btrfs'
end

#find_new(subvolume_dir, last_gen) {|a| ... } ⇒ Object #find_new(subvolume_dir, last_gen) ⇒ #each

Facade for ‘btrfs subvolume find-new’

It computes what changed between a reference generation of a subvolume, and that subvolume’s current state

Overloads:

  • #find_new(subvolume_dir, last_gen) {|a| ... } ⇒ Object

    Yield Parameters:

    • a (String)

      line of the find-new output

  • #find_new(subvolume_dir, last_gen) ⇒ #each

    Returns an enumeration of the lines of the find-new output.

    Returns:

    • (#each)

      an enumeration of the lines of the find-new output

Parameters:

  • subvolume_dir (String)

    the subvolume target of find-new

  • last_gen (Integer)

    the reference generation



128
129
130
131
# File 'lib/snapsync/btrfs.rb', line 128

def find_new(subvolume_dir, last_gen, &block)
    run('subvolume', 'find-new', subvolume_dir.to_s, last_gen.to_s).
        each_line(&block)
end

#generation_of(path) ⇒ Integer

Facade for finding the generation of a subvolume using ‘btrfs show’

Parameters:

  • path (Pathname)

    the subvolume path

Returns:

  • (Integer)

    the subvolume’s generation



105
106
107
108
109
110
111
112
113
# File 'lib/snapsync/btrfs.rb', line 105

def generation_of(path)
    info = run('subvolume', 'show', path.to_s)
    if info =~ /Generation[^:]*:\s+(\d+)/
        Integer($1)
    else
        raise UnexpectedBtrfsOutput, "unexpected output for 'btrfs subvolume show'," \
            +" expected '#{info}' to contain a 'Generation:' line"
    end
end

#popen(*args, mode: 'r', raise_on_error: true, **options) {|io| ... } ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

A IO.popen-like API to btrfs subcommands

Yield Parameters:

  • io (IO)


71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/snapsync/btrfs.rb', line 71

def popen(*args, mode: 'r', raise_on_error: true, **options)
    Snapsync.debug "Btrfs(\"#{mountpoint}\").popen: #{args}"

    if @mountpoint.is_a? RemotePathname
        proc = SSHPopen.new(@mountpoint, [btrfs_prog, *args], **options)
        return yield(proc)
    else
        # @type [IO,IO]
        err_r, err_w = IO.pipe
        begin
            IO.popen([btrfs_prog, *args], err: err_w, mode: mode) do |io|
                err_w.close
                return yield(io)
            end
            if not $?.success?
                raise Error.new, err_r.read.lines
            end
        ensure err_r.close
        end

    end
end

#read_subvolume_tableArray<SubvolumeMinimalInfo>

Returns:



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/snapsync/btrfs.rb', line 134

def read_subvolume_table
    table = []

    text = run('subvolume', 'list','-pcgquR', mountpoint.path_part)
    text.each_line do |l|
        item = SubvolumeMinimalInfo.new
        l.gsub!('top level', 'top_level')
        l = l.split
        l.each_slice(2) do |kv|
            k,v = kv
            if v == '-'
                v = nil
            else
                begin
                    v = Integer(v)
                rescue
                    # ignore
                end
            end
            item.instance_variable_set '@'+k, v
        end
        table.push item
    end

    table
end

#run(*args, **options) ⇒ String

Returns:

  • (String)


95
96
97
98
99
# File 'lib/snapsync/btrfs.rb', line 95

def run(*args, **options)
    popen(*args, **options) do |io|
        io.read.encode('utf-8', undef: :replace, invalid: :replace)
    end
end

#subvolume_show(subvolume_dir) ⇒ Hash<String>

Parameters:

Returns:

  • (Hash<String>)


163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/snapsync/btrfs.rb', line 163

def subvolume_show(subvolume_dir)
    # @type [String]
    info = run('subvolume', 'show', subvolume_dir.path_part)

    data = {}

    data['absolute_dir'] = info.lines[0].strip

    lines = info.lines[1..-1]
    lines.each_index do |i|
        l = lines[i]
        k, _, v = l.partition ':'
        k = k.strip.downcase.gsub ' ', '_'

        if k == 'snapshot(s)'
            data['snapshots'] = lines[i+1..-1].map do |s|
                s.strip
            end
            break
        else
            data[k] = v.strip
        end
    end

    data
end