Merge pull request #17303 from CarlosDerSeher/feature_bt_agent
[feed/packages.git] / utils / uvol / files / lvm.uc
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 // LVM2 backend for uvol
3 // (c) 2022 Daniel Golle <daniel@makrotopia.org>
4 //
5 // This plugin uses LVM2 as a storage backend for uvol.
6 //
7 // By default, volumes are allocated on the physical device used for booting,
8 // the LVM2 PV and VG are initialized auto-magically by the 'autopart' script.
9 // By setting the UCI option 'vg_name' in the 'uvol' section in /etc/config/fstab
10 // you may set an arbitrary LVM2 volume group to back uvol instad.
11
12 let lvm_exec = "/sbin/lvm";
13
14 function lvm(cmd, ...args) {
15 let lvm_json_cmds = [ "lvs", "pvs", "vgs" ];
16 try {
17 let json_param = "";
18 if (cmd in lvm_json_cmds)
19 json_param = "--reportformat json --units b ";
20 let stdout = fs.popen(sprintf("LVM_SUPPRESS_FD_WARNINGS=1 %s %s %s%s", lvm_exec, cmd, json_param, join(" ", args)));
21 let tmp;
22 if (stdout) {
23 tmp = stdout.read("all");
24 let ret = {};
25 ret.retval = stdout.close();
26 if (json_param) {
27 let data = json(tmp);
28 if (data.report)
29 ret.report = data.report[0];
30 } else {
31 ret.stdout = trim(tmp);
32 }
33 return ret;
34 } else {
35 printf("lvm cli command failed: %s\n", fs.error());
36 }
37 } catch(e) {
38 printf("Failed to parse lvm cli output: %s\n%s\n", e, e.stacktrace[0].context);
39 }
40 return null;
41 }
42
43 function pvs() {
44 let fstab = cursor.get_all('fstab');
45 for (let k, section in fstab) {
46 if (section['.type'] != 'uvol' || !section.vg_name)
47 continue;
48
49 return section.vg_name;
50 }
51 include("/usr/lib/uvol/blockdev_common.uc");
52 let rootdev = blockdev_common.get_partition(blockdev_common.get_bootdev(), 0);
53 if (!rootdev)
54 return null;
55
56 let tmp = lvm("pvs", "-o", "vg_name", "-S", sprintf("\"pv_name=~^/dev/%s.*\$\"", rootdev));
57 if (tmp.report.pv[0])
58 return tmp.report.pv[0].vg_name;
59 else
60 return null;
61 }
62
63 function vgs(vg_name) {
64 let tmp = lvm("vgs", "-o", "vg_extent_size,vg_extent_count,vg_free_count", "-S", sprintf("\"vg_name=%s\"", vg_name));
65 let ret = null;
66 if (tmp && tmp.report.vg) {
67 ret = tmp.report.vg;
68 for (let r in ret) {
69 r.vg_extent_size = +(rtrim(r.vg_extent_size, "B"));
70 r.vg_extent_count = +r.vg_extent_count;
71 r.vg_free_count = +r.vg_free_count;
72 }
73 }
74 if (ret)
75 return ret[0];
76 else
77 return null;
78 }
79
80 function lvs(vg_name, vol_name, extra_exp) {
81 let ret = [];
82 if (!vol_name)
83 vol_name = ".*";
84
85 let lvexpr = sprintf("\"lvname=~^[rw][owp]_%s\$ && vg_name=%s%s%s\"",
86 vol_name, vg_name, extra_exp?" && ":"", extra_exp?extra_exp:"");
87 let tmp = lvm("lvs", "-o", "lv_active,lv_name,lv_full_name,lv_size,lv_path,lv_dm_path", "-S", lvexpr);
88 if (tmp && tmp.report.lv) {
89 ret = tmp.report.lv;
90 for (let r in ret) {
91 r.lv_size = +(rtrim(r.lv_size, "B"));
92 r.lv_active = (r.lv_active == "active");
93 }
94 }
95 return ret;
96 }
97
98 function getdev(lv) {
99 if (!lv)
100 return null;
101
102 for (let dms in fs.glob("/sys/devices/virtual/block/dm-*")) {
103 let f = fs.open(sprintf("%s/dm/name", dms), "r");
104 if (!f)
105 continue;
106
107 let dm_name = trim(f.read("all"));
108 f.close();
109 if ( split(lv.lv_dm_path, '/')[-1] == dm_name )
110 return split(dms, '/')[-1]
111 }
112 return null;
113 }
114
115 function lvm_init(ctx) {
116 cursor = ctx.cursor;
117 fs = ctx.fs;
118 if (type(fs.access) == "function" && !fs.access(lvm_exec, "x"))
119 return false;
120
121 vg_name = pvs();
122 if (!vg_name)
123 return false;
124
125 vg = vgs(vg_name);
126 uvol_uci_add = ctx.uci_add;
127 uvol_uci_commit = ctx.uci_commit;
128 uvol_uci_remove = ctx.uci_remove;
129 uvol_uci_init = ctx.uci_init;
130 return true;
131 }
132
133 function lvm_free() {
134 if (!vg || !vg.vg_free_count || !vg.vg_extent_size)
135 return 2;
136
137 return sprintf("%d", vg.vg_free_count * vg.vg_extent_size);
138 }
139
140 function lvm_total() {
141 if (!vg || !vg.vg_extent_count || !vg.vg_extent_size)
142 return 2;
143
144 return sprintf("%d", vg.vg_extent_count * vg.vg_extent_size);
145 }
146
147 function lvm_align() {
148 if (!vg || !vg.vg_extent_size)
149 return 2;
150
151 return sprintf("%d", vg.vg_extent_size);
152 }
153
154 function lvm_list(vol_name) {
155 let vols = [];
156
157 if (!vg_name)
158 return vols;
159
160 let res = lvs(vg_name, vol_name);
161 for (let lv in res) {
162 let vol = {};
163 if (substr(lv.lv_name, 3, 1) == ".")
164 continue;
165
166 vol.name = substr(lv.lv_name, 3);
167 vol.mode = substr(lv.lv_name, 0, 2);
168 if (!lv.lv_active) {
169 if (vol.mode == "ro")
170 vol.mode = "rd";
171 if (vol.mode == "rw")
172 vol.mode = "wd";
173 }
174 vol.size = lv.lv_size;
175 push(vols, vol);
176 }
177
178 return vols;
179 }
180
181 function lvm_size(vol_name) {
182 if (!vol_name || !vg_name)
183 return 2;
184
185 let res = lvs(vg_name, vol_name);
186 if (!res[0])
187 return 2;
188
189 return sprintf("%d", res[0].lv_size);
190 }
191
192 function lvm_status(vol_name) {
193 if (!vol_name || !vg_name)
194 return 22;
195
196 let res = lvs(vg_name, vol_name);
197 if (!res[0])
198 return 2;
199
200 let mode = substr(res[0].lv_name, 0, 2);
201 if ((mode != "ro" && mode != "rw") || !res[0].lv_active)
202 return 1;
203
204 return 0;
205 }
206
207 function lvm_device(vol_name) {
208 if (!vol_name || !vg_name)
209 return 22;
210
211 let res = lvs(vg_name, vol_name);
212 if (!res[0])
213 return 2;
214
215 let mode = substr(res[0].lv_name, 0, 2);
216 if ((mode != "ro" && mode != "rw") || !res[0].lv_active)
217 return 22;
218
219 return getdev(res[0]);
220 }
221
222 function lvm_updown(vol_name, up) {
223 if (!vol_name || !vg_name)
224 return 22;
225
226 let res = lvs(vg_name, vol_name);
227 if (!res[0])
228 return 2;
229
230 let lv = res[0];
231 if (!lv.lv_path)
232 return 2;
233
234 if (up && (wildcard(lv.lv_path, "/dev/*/wo_*") ||
235 wildcard(lv.lv_path, "/dev/*/wp_*")))
236 return 22;
237
238 if (up)
239 uvol_uci_commit(vol_name);
240
241 if (lv.lv_active == up)
242 return 0;
243
244 if (!up) {
245 let devname = getdev(lv);
246 if (devname)
247 system(sprintf("umount /dev/%s", devname));
248 }
249
250 let lvchange_r = lvm("lvchange", up?"-k":"-a", "n", lv.lv_full_name);
251 if (up && lvchange_r.retval != 0)
252 return lvchange_r.retval;
253
254 lvchange_r = lvm("lvchange", up?"-a":"-k", "y", lv.lv_full_name);
255 if (lvchange_r.retval != 0)
256 return lvchange_r.retval;
257
258 return 0
259 }
260
261 function lvm_up(vol_name) {
262 return lvm_updown(vol_name, true);
263 }
264
265 function lvm_down(vol_name) {
266 return lvm_updown(vol_name, false);
267 }
268
269 function lvm_create(vol_name, vol_size, vol_mode) {
270 if (!vol_name || !vg_name)
271 return 22;
272
273 vol_size = +vol_size;
274 if (vol_size <= 0)
275 return 22;
276
277 let res = lvs(vg_name, vol_name);
278 if (res[0])
279 return 17;
280
281 let size_ext = vol_size / vg.vg_extent_size;
282 if (vol_size % vg.vg_extent_size)
283 ++size_ext;
284 let lvmode, mode;
285 if (vol_mode == "ro" || vol_mode == "wo") {
286 lvmode = "r";
287 mode = "wo";
288 } else if (vol_mode == "rw") {
289 lvmode = "rw";
290 mode = "wp";
291 } else {
292 return 22;
293 }
294
295 let ret = lvm("lvcreate", "-p", lvmode, "-a", "n", "-y", "-W", "n", "-Z", "n", "-n", sprintf("%s_%s", mode, vol_name), "-l", size_ext, vg_name);
296 if (ret.retval != 0 || lvmode == "r")
297 return ret.retval;
298
299 let lv = lvs(vg_name, vol_name);
300 if (!lv[0] || !lv[0].lv_full_name)
301 return 22;
302
303 lv = lv[0];
304 let ret = lvm("lvchange", "-a", "y", lv.lv_full_name);
305 if (ret.retval != 0)
306 return ret.retval;
307
308 let use_f2fs = (lv.lv_size > (100 * 1024 * 1024));
309 if (use_f2fs) {
310 let mkfs_ret = system(sprintf("/usr/sbin/mkfs.f2fs -f -l \"%s\" \"%s\"", vol_name, lv.lv_path));
311 if (mkfs_ret != 0 && mkfs_ret != 134) {
312 lvchange_r = lvm("lvchange", "-a", "n", lv.lv_full_name);
313 if (lvchange_r.retval != 0)
314 return lvchange_r.retval;
315 return mkfs_ret;
316 }
317 } else {
318 let mkfs_ret = system(sprintf("/usr/sbin/mke2fs -F -L \"%s\" \"%s\"", vol_name, lv.lv_path));
319 if (mkfs_ret != 0) {
320 lvchange_r = lvm("lvchange", "-a", "n", lv.lv_full_name);
321 if (lvchange_r.retval != 0)
322 return lvchange_r.retval;
323 return mkfs_ret;
324 }
325 }
326 uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), "rw");
327
328 ret = lvm("lvchange", "-a", "n", lv.lv_full_name);
329 if (ret.retval != 0)
330 return ret.retval;
331
332 ret = lvm("lvrename", vg_name, sprintf("wp_%s", vol_name), sprintf("rw_%s", vol_name));
333 if (ret.retval != 0)
334 return ret.retval;
335
336 return 0;
337 }
338
339 function lvm_remove(vol_name) {
340 if (!vol_name || !vg_name)
341 return 22;
342
343 let res = lvs(vg_name, vol_name);
344 if (!res[0])
345 return 2;
346
347 if (res[0].lv_active)
348 return 16;
349
350 let ret = lvm("lvremove", "-y", res[0].lv_full_name);
351 if (ret.retval != 0)
352 return ret.retval;
353
354 uvol_uci_remove(vol_name);
355 uvol_uci_commit(vol_name);
356 return 0;
357 }
358
359 function lvm_dd(in_fd, out_fd, vol_size) {
360 let rem = vol_size;
361 let buf;
362 while ((buf = in_fd.read(vg.vg_extent_size)) && (rem > 0)) {
363 rem -= length(buf);
364 if (rem < 0) {
365 buf = substr(buf, 0, rem);
366 }
367 out_fd.write(buf);
368 }
369 return rem;
370 }
371
372 function lvm_write(vol_name, vol_size) {
373 if (!vol_name || !vg_name)
374 return 22;
375
376 let lv = lvs(vg_name, vol_name);
377 if (!lv[0] || !lv[0].lv_full_name)
378 return 2;
379
380 lv = lv[0];
381 vol_size = +vol_size;
382 if (vol_size > lv.lv_size)
383 return 27;
384
385 if (wildcard(lv.lv_path, "/dev/*/wo_*")) {
386 let ret = lvm("lvchange", "-p", "rw", lv.lv_full_name);
387 if (ret.retval != 0)
388 return ret.retval;
389
390 let ret = lvm("lvchange", "-a", "y", lv.lv_full_name);
391 if (ret.retval != 0)
392 return ret.retval;
393
394 let volfile = fs.open(lv.lv_path, "w");
395 let ret = lvm_dd(fs.stdin, volfile, vol_size);
396 volfile.close();
397 if (ret < 0) {
398 printf("more %d bytes data than given size!\n", -ret);
399 }
400
401 if (ret > 0) {
402 printf("reading finished %d bytes before given size!\n", ret);
403 }
404
405 uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), "ro");
406
407 let ret = lvm("lvchange", "-a", "n", lv.lv_full_name);
408 if (ret.retval != 0)
409 return ret.retval;
410
411 let ret = lvm("lvchange", "-p", "r", lv.lv_full_name);
412 if (ret.retval != 0)
413 return ret.retval;
414
415 let ret = lvm("lvrename", vg_name, sprintf("wo_%s", vol_name), sprintf("ro_%s", vol_name));
416 if (ret.retval != 0)
417 return ret.retval;
418
419 } else {
420 return 22;
421 }
422 return 0;
423 }
424
425 function lvm_detect() {
426 let temp_up = [];
427 let inactive_lv = lvs(vg_name, null, "lv_skip_activation!=0");
428 for (let lv in inactive_lv) {
429 lvm("lvchange", "-k", "n", lv.lv_full_name);
430 lvm("lvchange", "-a", "y", lv.lv_full_name);
431 push(temp_up, lv.lv_full_name);
432 }
433 sleep(1000);
434 uvol_uci_init();
435 for (let lv in lvs(vg_name)) {
436 let vol_name = substr(lv.lv_name, 3);
437 let vol_mode = substr(lv.lv_name, 0, 2);
438 uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), vol_mode);
439 }
440 uvol_uci_commit();
441 for (let lv_full_name in temp_up) {
442 lvm("lvchange", "-a", "n", lv_full_name);
443 lvm("lvchange", "-k", "y", lv_full_name);
444 }
445 return 0;
446 }
447
448 function lvm_boot() {
449 return 0;
450 }
451
452 backend.backend = "LVM";
453 backend.priority = 50;
454 backend.init = lvm_init;
455 backend.boot = lvm_boot;
456 backend.detect = lvm_detect;
457 backend.free = lvm_free;
458 backend.align = lvm_align;
459 backend.total = lvm_total;
460 backend.list = lvm_list;
461 backend.size = lvm_size;
462 backend.status = lvm_status;
463 backend.device = lvm_device;
464 backend.up = lvm_up;
465 backend.down = lvm_down;
466 backend.create = lvm_create;
467 backend.remove = lvm_remove;
468 backend.write = lvm_write;