Skip to content
API Documentation
BastionGuard™
Subvolume.vala
Go to the documentation of this file.
1/*
2 * Subvolume.vala
3 *
4 * Copyright 2012-2018 Tony George <teejeetech@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 *
21 *
22 */
23
24using TeeJee.Logging;
29using TeeJee.System;
30using TeeJee.Misc;
31
32public class Subvolume : GLib.Object{
33
34 public string device_uuid;
35 public string name = "";
36 public string path = "";
37 public long id = -1;
38 public int64 total_bytes = 0;
39 public int64 unshared_bytes = 0;
40
41 public string mount_path = "";
42
43 //parent
45
46 public Subvolume(string name, string path, string parent_dev_uuid, SnapshotRepo? parent_repo){
47
48 this.name = name;
49 this.path = path;
50 this.device_uuid = parent_dev_uuid;
51 this.repo = parent_repo;
52
53 if (repo != null){
54 this.mount_path = repo.mount_paths[name];
55 }
56 }
57
58 public string total_formatted{
59 owned get{
61 }
62 }
63
64 public string unshared_formatted{
65 owned get{
67 }
68 }
69
70 public Device? get_device(){
71
73 }
74
75 public bool exists_on_disk{
76 get {
77 return dir_exists(path);
78 }
79 }
80
82 get {
83 return (repo == null);
84 }
85 }
86
87 public static Gee.HashMap<string, Subvolume> detect_subvolumes_for_system_by_path(
88 string system_path, SnapshotRepo? repo, Gtk.Window? parent_window){
89
90 var map = new Gee.HashMap<string, Subvolume>();
91
92 log_debug("Searching subvolume for system at path: %s".printf(system_path));
93
94 var fstab = FsTabEntry.read_file(path_combine(system_path, "/etc/fstab"));
95 var crypttab = CryptTabEntry.read_file(path_combine(system_path, "/etc/crypttab"));
96
97 foreach(var item in fstab){
98
99 if (!item.is_for_system_directory()){ continue; }
100
101 if (item.subvolume_name().length > 0){
102
103 var dev = item.resolve_device(crypttab, parent_window);
104 var dev_name = (dev == null) ? "" : dev.device;
105 var dev_uuid = (dev == null) ? "" : dev.uuid;
106
107 log_debug("Found subvolume: %s, on device: %s".printf(item.subvolume_name(), dev_name));
108
109 var subvol = new Subvolume(item.subvolume_name(), item.mount_point, dev_uuid, repo);
110 map.set(subvol.name, subvol);
111 }
112 }
113
114 return map;
115 }
116
117 public void print_info(){
118
119 log_debug("name=%s, uuid=%s, id=%ld, path=%s".printf(name, device_uuid, id, path));
120 }
121
122 // actions ----------------------------------
123
124 public bool remove(){
125
127 if (name == "@"){
128 path = path_combine(App.mount_point_app + "/backup", "@");
129 }
130 else if (name == "@home"){
131 path = path_combine(App.mount_point_app + "/backup-home", "@home");
132 }
133 }
134
135 string cmd = "";
136 string std_out, std_err, subpath;
137 int ret_val;
138
139 if (!dir_exists(path)){ return true; } // ok, item does not exist
140
141 log_msg("%s: %s (Id:%ld)".printf(_("Deleting subvolume"), name, id));
142
143 // always remove subvolumes recursively, this does not affect performance
144 string options = App.use_option_raw ? "--commit-after" : "";
145
147 options += " --recursive";
148 }
149
150 subpath = path_combine(path, name);
151 if (dir_exists(subpath)) { // possible nested subvolume present, verify before deleting
152 int rc_show = exec_sync("btrfs subvolume show '%s'".printf(subpath), out std_out, out std_err);
153 if (rc_show == 0) {
154 // confirmed nested btrfs subvolume, delete it first
155 cmd = "btrfs subvolume delete %s '%s'".printf(options, subpath);
156 log_debug("Deleting nested subvolume in snapshot");
157 log_debug(cmd);
158 ret_val = exec_sync(cmd, out std_out, out std_err);
159 if (ret_val != 0){
160 log_error(std_err);
161 log_error(_("Failed to delete snapshot nested subvolume") + ": '%s'".printf(subpath));
162 return false;
163 }
164 } else {
165 // path exists but is not a btrfs subvolume, skip nested removal
166 log_debug("Nested path exists but is not a btrfs subvolume, skipping: %s".printf(subpath));
167 }
168 }
169
170 cmd = "btrfs subvolume delete %s '%s'".printf(options, path);
171 log_debug(cmd);
172 ret_val = exec_sync(cmd, out std_out, out std_err);
173 if (ret_val != 0){
174 log_error(std_err);
175 log_error(_("Failed to delete snapshot subvolume") + ": '%s'".printf(path));
176 return false;
177 }
178
179 log_msg("%s: %s (Id:%ld)\n".printf(_("Deleted subvolume"), name, id));
180
182 if ((id > 0) && (repo != null)){
183
184 log_debug("Waiting on btrfs to finish deleting...");
185 while (exec_sync("btrfs subvolume sync %s".printf(mount_path), out std_out, out std_err) != 0) {
186 log_debug("Still waiting for btrfs to finish deleting... %s".printf(std_err));
187 sleep(1000);
188 }
189
190 log_debug("Rescanning quotas...");
191 while (exec_sync("btrfs quota rescan %s".printf(mount_path), out std_out, out std_err) != 0) {
192 log_debug("Still rescanning quotas... %s".printf(std_err));
193 sleep(1000);
194 }
195
196 // Check if qgroup still exists before trying to destroy it.
197 // The qgroup may have been auto-destroyed when the subvolume was deleted.
198 string qgroup_id = "0/%ld".printf(id);
199 cmd = "btrfs qgroup show -f '%s'".printf(repo.mount_paths[name]);
200 log_debug(cmd);
201 ret_val = exec_sync(cmd, out std_out, out std_err);
202
203 bool qgroup_exists = (ret_val == 0) && (std_out.contains(qgroup_id));
204
205 if (!qgroup_exists) {
206 log_debug("Qgroup %s already removed (auto-destroyed with subvolume)".printf(qgroup_id));
207 } else {
208 log_msg("%s: %s".printf(_("Destroying qgroup"), qgroup_id));
209
210 cmd = "btrfs qgroup destroy %s '%s'".printf(qgroup_id, repo.mount_paths[name]);
211 log_debug(cmd);
212 ret_val = exec_sync(cmd, out std_out, out std_err);
213 if (ret_val != 0){
214 // Qgroup may have been destroyed between our check and destroy attempt
215 log_debug("Qgroup %s could not be destroyed (likely already removed): %s".printf(qgroup_id, std_err));
216 } else {
217 log_msg("%s: %s\n".printf(_("Destroyed qgroup"), qgroup_id));
218 }
219 }
220
221 log_debug("Rescanning quotas (post)...");
222 while (exec_sync("btrfs quota rescan %s".printf(mount_path), out std_out, out std_err) != 0) {
223 log_debug("Still rescanning quotas (post)... %s".printf(std_err));
224 sleep(1000);
225 }
226
227 log_debug("Final sync (post)...");
228 while (exec_sync("btrfs subvolume sync %s".printf(mount_path), out std_out, out std_err) != 0) {
229 log_debug("Still syncing (post)... %s".printf(std_err));
230 sleep(1000);
231 }
232
233 }
234 }
235
236 return true;
237 }
238
239 public bool restore(){
240
241 if (is_system_subvolume) { return false; }
242
243 // restore snapshot subvolume by creating new subvolume snapshots ----------------------
244
245 string src_path = path;
246 string dst_path = path_combine(mount_path, name);
247
248 if (!dir_exists(src_path)){
249 log_error("%s: %s".printf(_("Not Found"), src_path));
250 return false;
251 }
252
253 if (dir_exists(dst_path)){
254 log_error("%s: %s".printf(_("Subvolume exists at destination"), dst_path));
255 return false;
256 }
257
258 string cmd = "btrfs subvolume snapshot '%s' '%s'".printf(src_path, dst_path);
259 log_debug(cmd);
260
261 string std_out, std_err;
262 int status = exec_sync(cmd, out std_out, out std_err);
263
264 if (status != 0){
265 log_error(std_err);
266 log_error(_("btrfs returned an error") + ": %d".printf(status));
267 log_error(_("Failed to restore system subvolume") + ": %s".printf(name));
268 return false;
269 }
270
271 log_msg(_("Restored system subvolume") + ": %s".printf(name));
272
273 return true;
274 }
275}
Main App
#define _(s)
static ? Device get_device_by_uuid(string uuid)
Definition Device.vala:1081
Definition Main.vala:39
bool btrfs_qgroups_enabled
Definition Main.vala:182
string mount_point_app
Definition Main.vala:82
static bool btrfs_version__can_recursive_delete
Definition Main.vala:55
Gee.HashMap< string, string > mount_paths
string mount_path
bool restore()
string unshared_formatted
string total_formatted
void print_info()
Subvolume(string name, string path, string parent_dev_uuid, SnapshotRepo? parent_repo)
string path
bool exists_on_disk
bool is_system_subvolume
string device_uuid
Device? get_device()
SnapshotRepo? repo
static Gee.HashMap< string, Subvolume > detect_subvolumes_for_system_by_path(string system_path, SnapshotRepo? repo, Gtk.Window? parent_window)
int64 total_bytes
int64 unshared_bytes
string name
string path_combine(string path1, string path2)
string format_file_size(uint64 size, bool binary_units=false, string unit="", bool show_units=true, int decimals=1)
bool dir_exists(string dir_path)
void log_debug(string message)
void log_error(string message, bool highlight=false, bool is_warning=false)
void log_msg(string message, bool highlight=false)
int exec_sync(string cmd, out string? std_out=null, out string? std_err=null)
void sleep(int milliseconds)