-
Notifications
You must be signed in to change notification settings - Fork 2
/
restorablefs.py
145 lines (112 loc) · 4.32 KB
/
restorablefs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
'''
Created on Oct 28, 2011
@author: user
'''
import os
import uuid
from utils import shell_exec, cd, get_logger
class IRestorableFS(object):
"""
File system with commit and roolback
Interface for the fylesystems with rollback and commit
"""
save_changes = False
def __enter__(self):
"""for construction 'with :'"""
pass
def __exit__(self, type, value, traceback):
if type != None:
self.umount()
def mount(self, mpoint):
"""mount fs"""
return self
def umount(self):
"""umount fs"""
pass
def commit(self):
"""commit changes on fs"""
pass
class BtrFS(IRestorableFS):
"""Btrfs based implemantation of RestorableFS"""
def __init__(self, device=None, image_file=None):
self.save_changes = False
self.device = device
self.image_file = image_file
self.snapname = None
self.snappath = None
self.subvolid = None
assert self.image_file is not None or self.device is not None
assert self.image_file is None or self.device is None
def subvolumes(self):
res = shell_exec('btrfs subvolume list "{0}"'.format(self.mpoint))
for line in res.split('\n'):
items = line.split(" ")
# subvolid subvolpath
yield items[1], items[6]
def create_snapshot(self):
self.snapname = str(uuid.uuid1())
self.snappath = os.path.join(self.mpoint, 'snapshots', self.snapname)
shell_exec('btrfs subvolume snapshot "{0}" "{1}"'.format(
self.mpoint, self.mpoint))
for subvolid, subvolume in self.subvolumes():
if subvolume.endswith(self.snapname):
break
assert subvolume.endswith(self.snapname)
self.subvolid = subvolid
def remove_all_snapshot(self):
for subvolid, subvolume in self.subvolumes():
shell_exec('btrfs subvolume delete "{0}"'.format(
os.path.join(self.mpoint, subvolume)))
def mount(self, mpoint):
"""
mounts device than creates subvolume on this device umounts device
and mounts snapshot in folder "mpoint"
"""
if self.image_file is not None:
if not os.path.exists(self.image_file):
raise ValueError("FS file not found: {0!r}".format(
self.image_file))
losetup = shell_exec('losetup -f --show "{0}"'.format(
self.image_file))
self.device = losetup.split("\n")[0]
self.mpoint = mpoint
shell_exec('mount "{0}" "{1}"'.format(self.device, self.mpoint))
self.create_snapshot()
shell_exec('umount "{0}"'.format(self.mpoint))
shell_exec('mount -o "subvolid={0}" "{1}" "{2}"'.format(
self.subvolid,
self.device,
self.mpoint))
return self
def umount(self):
"""
umounts fs
if method commit() was executed else makes working suvolume default
(saves changes on disk), else deletes working subvolume (restores
its state)
"""
if self.save_changes:
tdir = self.mpoint
else:
tdir = "/"
with cd(tdir):
if self.save_changes:
shell_exec('btrfs subvolume set-default "{0}" "{1}"'.format(\
self.snap,
self.mpoint))
self.save_changes = False
else:
shell_exec('umount "{0}"'.format(self.mpoint))
shell_exec('mount "{0}" "{1}"'.format(self.device, self.mpoint))
os.chdir(self.mpoint)
shell_exec('btrfs subvolume delete "{0}"'.format(self.snap))
os.chdir("/")
shell_exec('umount "{0}"'.format(self.mpoint))
if self.image_file is not None:
shell_exec('losetup -d "{0}"'.format(self.device))
def commit(self):
"""
if this function used, changes on umount will be saved
"""
self.save_changes = True
RestorableFS = BtrFS