Skip to content
This repository was archived by the owner on Sep 29, 2025. It is now read-only.

Commit e8b7265

Browse files
committed
Merge remote-tracking branch 'mwilck/master'
2 parents 2334a68 + d265426 commit e8b7265

File tree

2 files changed

+116
-28
lines changed

2 files changed

+116
-28
lines changed

README.md

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ GNU General Public License for more details.**
2323
* `--ignore-errors`: continue after errors in send/receive. May be useful for
2424
backing up corrupted file systems. Make sure to check results.
2525
* `--strategy`: either "parent", "snapshot", "chronological",
26-
or "generation" (default); see below.
26+
"generation" (default), or "bruteforce"; see below.
2727
* `--toplevel`: don't try to write the target toplevel subvolume, see below.
2828
* `--btrfs`: set full path to "btrfs" executable.
2929

@@ -88,13 +88,14 @@ determine reference subvolumes for each subvolume cloned.
8888

8989
### Child-parent relationship
9090

91-
Except for the "parent" strategy, the child-parent relationships ("C is a
92-
snapshot of P") in the target file system will be different from those in the
93-
source file system. If 3rd party tools rely on a certain parent-child
94-
relationship, only the "parent" strategy can be used. I tried this with
95-
**snapper**, and it seemed to work fine with a clone generated with the
96-
"generation" strategy; apparently it only relies on its own meta data, which
97-
is preserved in the cloning procedure.
91+
Except for the "parent" and "bruteforce" strategy, the child-parent
92+
relationships ("C is a snapshot of P") in the target file system will
93+
be different from those in the source file system. If 3rd party tools
94+
rely on a certain parent-child relationship, only the "parent"
95+
strategy can be used. I tried this with **snapper**, and it seemed to
96+
work fine with a clone generated with the "generation" strategy;
97+
apparently it only relies on its own meta data, which is preserved in
98+
the cloning procedure.
9899

99100
Moreover, file systems will not be cloned in the order of their creation, thus
100101
when a subvolumeis cloned, we can't be sure that its parent in the filesystem
@@ -134,14 +135,27 @@ where the current fs tree (the default subvolume) has been snapshotted
134135
several times in the past:
135136

136137
current ---------------------------------\
137-
| | | |
138-
snap4 snap3 snap2 snap1
138+
| | | |
139+
snap4 snap3 snap2 snap1
139140

140141
With "parent" strategy (which was Thomas' original proposal), we'd clone
141142
"current" first, and after that the snapshots one by one, using "current"
142143
both as "parent" and "clone source" (["-p" option to btrfs-send][2]) for every
143144
snapshot.
144145

146+
### "bruteforce" strategy
147+
148+
This strategy is similar to "parent". But it uses every "relative" of
149+
the subvolume to be cloned as clone source, rather than just the
150+
direct parent. The set of "relatives" contains all ancestors and all
151+
descendants of all ancestors. This may lead to a rather large set of
152+
clone sources, slowing down **btrfs send** operation.
153+
154+
Like "parent", this strategy preserves the child-parent
155+
relationships. As outlined above, that may be suboptimal for meta data
156+
cloning. But data cloning should be pretty good with this method, as
157+
every possible clone source is taken into account.
158+
145159
### "snapshot" strategy
146160

147161
Obviously, in the picture above, the similarity between snap1 and snap2 will

btrfs-clone

Lines changed: 92 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -349,17 +349,81 @@ class SvBaseDir(object):
349349
if not sv.ro and not opts.dry_run:
350350
prop_set_ro(newpath, False)
351351

352-
def parents_getter(subvols):
353-
lookup = { x.uuid: x for x in subvols }
354-
def _getter(x):
355-
while x.parent_uuid is not None:
356-
try:
357-
x = lookup[x.parent_uuid]
358-
except KeyError:
359-
return
352+
class SubvolSet(object):
353+
354+
def __init__(self, subvols):
355+
self.subvols = subvols
356+
self.lookup = { x.uuid: x for x in subvols }
357+
358+
def parents_getter(self):
359+
def _getter(x):
360+
while x.parent_uuid is not None:
361+
try:
362+
x = self.lookup[x.parent_uuid]
363+
except KeyError:
364+
return
365+
else:
366+
yield x
367+
return _getter
368+
369+
def get_parents(self, x):
370+
return self.parents_getter()(x)
371+
372+
def get_parent(self, x):
373+
if x.parent_uuid is not None and x.parent_uuid in self.lookup:
374+
return self.lookup[x.parent_uuid]
375+
return None
376+
377+
def siblings_getter(self):
378+
def _getter(x):
379+
return (y for y in self.subvols if x.parent_uuid == y.parent_uuid
380+
and y.uuid != x.uuid)
381+
return _getter
382+
383+
def get_siblings(self, x):
384+
return self.siblings_getter()(x)
385+
386+
def children_getter(self):
387+
def _getter(uuid):
388+
return (y for y in self.subvols if y.parent_uuid == uuid)
389+
return _getter
390+
391+
def get_children(self, x):
392+
return self.children_getter()(x)
393+
394+
def descendants_getter(self):
395+
def _getter(uuid):
396+
children = self.get_children(uuid)
397+
for c in children:
398+
yield c
399+
for x in (y for y in _getter(c.uuid)):
400+
yield x
401+
return _getter
402+
403+
def get_descendants(self, x):
404+
return self.descendants_getter()(x)
405+
406+
def relatives_getter(self):
407+
def _getter(sv):
408+
parents = list(self.get_parents(sv))
409+
if parents:
410+
grandpa = parents[-1]
360411
else:
412+
grandpa = sv
413+
if grandpa.parent_uuid is not None:
414+
# oldest ancestor is not in subvols
415+
uuid = grandpa.parent_uuid
416+
else:
417+
uuid = grandpa.uuid
418+
if uuid != sv.uuid:
419+
yield grandpa
420+
for x in (y for y in self.get_descendants(uuid)):
361421
yield x
362-
return _getter
422+
return _getter
423+
424+
def get_relatives(self, x):
425+
return self.relatives_getter()(x)
426+
363427

364428
class Strategy(object):
365429

@@ -379,7 +443,8 @@ class Strategy(object):
379443

380444
def prepare_subvols(self):
381445
self.subvols = get_subvols(self.old)
382-
self.get_parents = parents_getter(self.subvols)
446+
self.svset = SubvolSet(self.subvols)
447+
self.get_parents = self.svset.parents_getter()
383448
self.subvols.sort(key = self.sort_key)
384449

385450
atexit.register(set_all_ro, False, self.subvols, self.old)
@@ -427,6 +492,15 @@ class ParentStrategy(Strategy):
427492
def _done(self, sv):
428493
sv.set_ro(False, self.new)
429494

495+
class BruteStrategy(ParentStrategy):
496+
497+
def send_subvol(self, sv):
498+
relatives = (y for y in self.svset.get_relatives(sv) if y.ogen < sv.ogen)
499+
flags = self.build_flags(relatives, self.svset.get_parent(sv))
500+
do_send_recv(sv.get_path(self.old),
501+
os.path.dirname(sv.get_path(self.new)),
502+
flags)
503+
430504
class _FlatStrategy(Strategy):
431505

432506
def strategy(self):
@@ -690,14 +764,15 @@ class GenerationStrategy(_FlatStrategy):
690764
def _done(self, sv):
691765
self.done = [sv] + self.done
692766

767+
_strategies = {
768+
"parent": ParentStrategy,
769+
"snapshot": SnapStrategy,
770+
"chronological": ChronoStrategy,
771+
"generation": GenerationStrategy,
772+
"bruteforce": BruteStrategy,
773+
}
693774

694775
def get_strategy():
695-
_strategies = {
696-
"parent": ParentStrategy,
697-
"snapshot": SnapStrategy,
698-
"chronological": ChronoStrategy,
699-
"generation": GenerationStrategy,
700-
}
701776
return _strategies[opts.strategy]
702777

703778
def make_args():
@@ -707,8 +782,7 @@ def make_args():
707782
ps.add_argument("-f", "--force", action='store_true')
708783
ps.add_argument("-n", "--dry-run", action='store_true')
709784
ps.add_argument("-s", "--strategy", default="generation",
710-
choices=["parent", "snapshot", "chronological",
711-
"generation"])
785+
choices=_strategies.keys())
712786
ps.add_argument("--snap-base")
713787
ps.add_argument("--no-unshare", action='store_true')
714788
ps.add_argument("-t", "--toplevel", action='store_false',

0 commit comments

Comments
 (0)