You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

468 lines
10 KiB

  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. let lvm_exec = "/sbin/lvm";
  12. function lvm(cmd, ...args) {
  13. let lvm_json_cmds = [ "lvs", "pvs", "vgs" ];
  14. try {
  15. let json_param = "";
  16. if (cmd in lvm_json_cmds)
  17. json_param = "--reportformat json --units b ";
  18. let stdout = fs.popen(sprintf("LVM_SUPPRESS_FD_WARNINGS=1 %s %s %s%s", lvm_exec, cmd, json_param, join(" ", args)));
  19. let tmp;
  20. if (stdout) {
  21. tmp = stdout.read("all");
  22. let ret = {};
  23. ret.retval = stdout.close();
  24. if (json_param) {
  25. let data = json(tmp);
  26. if (data.report)
  27. ret.report = data.report[0];
  28. } else {
  29. ret.stdout = trim(tmp);
  30. }
  31. return ret;
  32. } else {
  33. printf("lvm cli command failed: %s\n", fs.error());
  34. }
  35. } catch(e) {
  36. printf("Failed to parse lvm cli output: %s\n%s\n", e, e.stacktrace[0].context);
  37. }
  38. return null;
  39. }
  40. function pvs() {
  41. let fstab = cursor.get_all('fstab');
  42. for (let k, section in fstab) {
  43. if (section['.type'] != 'uvol' || !section.vg_name)
  44. continue;
  45. return section.vg_name;
  46. }
  47. include("/usr/lib/uvol/blockdev_common.uc");
  48. let rootdev = blockdev_common.get_partition(blockdev_common.get_bootdev(), 0);
  49. if (!rootdev)
  50. return null;
  51. let tmp = lvm("pvs", "-o", "vg_name", "-S", sprintf("\"pv_name=~^/dev/%s.*\$\"", rootdev));
  52. if (tmp.report.pv[0])
  53. return tmp.report.pv[0].vg_name;
  54. else
  55. return null;
  56. }
  57. function vgs(vg_name) {
  58. let tmp = lvm("vgs", "-o", "vg_extent_size,vg_extent_count,vg_free_count", "-S", sprintf("\"vg_name=%s\"", vg_name));
  59. let ret = null;
  60. if (tmp && tmp.report.vg) {
  61. ret = tmp.report.vg;
  62. for (let r in ret) {
  63. r.vg_extent_size = +(rtrim(r.vg_extent_size, "B"));
  64. r.vg_extent_count = +r.vg_extent_count;
  65. r.vg_free_count = +r.vg_free_count;
  66. }
  67. }
  68. if (ret)
  69. return ret[0];
  70. else
  71. return null;
  72. }
  73. function lvs(vg_name, vol_name, extra_exp) {
  74. let ret = [];
  75. if (!vol_name)
  76. vol_name = ".*";
  77. let lvexpr = sprintf("\"lvname=~^[rw][owp]_%s\$ && vg_name=%s%s%s\"",
  78. vol_name, vg_name, extra_exp?" && ":"", extra_exp?extra_exp:"");
  79. let tmp = lvm("lvs", "-o", "lv_active,lv_name,lv_full_name,lv_size,lv_path,lv_dm_path", "-S", lvexpr);
  80. if (tmp && tmp.report.lv) {
  81. ret = tmp.report.lv;
  82. for (let r in ret) {
  83. r.lv_size = +(rtrim(r.lv_size, "B"));
  84. r.lv_active = (r.lv_active == "active");
  85. }
  86. }
  87. return ret;
  88. }
  89. function getdev(lv) {
  90. if (!lv)
  91. return null;
  92. for (let dms in fs.glob("/sys/devices/virtual/block/dm-*")) {
  93. let f = fs.open(sprintf("%s/dm/name", dms), "r");
  94. if (!f)
  95. continue;
  96. let dm_name = trim(f.read("all"));
  97. f.close();
  98. if ( split(lv.lv_dm_path, '/')[-1] == dm_name )
  99. return split(dms, '/')[-1]
  100. }
  101. return null;
  102. }
  103. function lvm_init(ctx) {
  104. cursor = ctx.cursor;
  105. fs = ctx.fs;
  106. if (type(fs.access) == "function" && !fs.access(lvm_exec, "x"))
  107. return false;
  108. vg_name = pvs();
  109. if (!vg_name)
  110. return false;
  111. vg = vgs(vg_name);
  112. uvol_uci_add = ctx.uci_add;
  113. uvol_uci_commit = ctx.uci_commit;
  114. uvol_uci_remove = ctx.uci_remove;
  115. uvol_uci_init = ctx.uci_init;
  116. return true;
  117. }
  118. function lvm_free() {
  119. if (!vg || !vg.vg_free_count || !vg.vg_extent_size)
  120. return 2;
  121. return sprintf("%d", vg.vg_free_count * vg.vg_extent_size);
  122. }
  123. function lvm_total() {
  124. if (!vg || !vg.vg_extent_count || !vg.vg_extent_size)
  125. return 2;
  126. return sprintf("%d", vg.vg_extent_count * vg.vg_extent_size);
  127. }
  128. function lvm_align() {
  129. if (!vg || !vg.vg_extent_size)
  130. return 2;
  131. return sprintf("%d", vg.vg_extent_size);
  132. }
  133. function lvm_list(vol_name) {
  134. let vols = [];
  135. if (!vg_name)
  136. return vols;
  137. let res = lvs(vg_name, vol_name);
  138. for (let lv in res) {
  139. let vol = {};
  140. if (substr(lv.lv_name, 3, 1) == ".")
  141. continue;
  142. vol.name = substr(lv.lv_name, 3);
  143. vol.mode = substr(lv.lv_name, 0, 2);
  144. if (!lv.lv_active) {
  145. if (vol.mode == "ro")
  146. vol.mode = "rd";
  147. if (vol.mode == "rw")
  148. vol.mode = "wd";
  149. }
  150. vol.size = lv.lv_size;
  151. push(vols, vol);
  152. }
  153. return vols;
  154. }
  155. function lvm_size(vol_name) {
  156. if (!vol_name || !vg_name)
  157. return 2;
  158. let res = lvs(vg_name, vol_name);
  159. if (!res[0])
  160. return 2;
  161. return sprintf("%d", res[0].lv_size);
  162. }
  163. function lvm_status(vol_name) {
  164. if (!vol_name || !vg_name)
  165. return 22;
  166. let res = lvs(vg_name, vol_name);
  167. if (!res[0])
  168. return 2;
  169. let mode = substr(res[0].lv_name, 0, 2);
  170. if ((mode != "ro" && mode != "rw") || !res[0].lv_active)
  171. return 1;
  172. return 0;
  173. }
  174. function lvm_device(vol_name) {
  175. if (!vol_name || !vg_name)
  176. return 22;
  177. let res = lvs(vg_name, vol_name);
  178. if (!res[0])
  179. return 2;
  180. let mode = substr(res[0].lv_name, 0, 2);
  181. if ((mode != "ro" && mode != "rw") || !res[0].lv_active)
  182. return 22;
  183. return getdev(res[0]);
  184. }
  185. function lvm_updown(vol_name, up) {
  186. if (!vol_name || !vg_name)
  187. return 22;
  188. let res = lvs(vg_name, vol_name);
  189. if (!res[0])
  190. return 2;
  191. let lv = res[0];
  192. if (!lv.lv_path)
  193. return 2;
  194. if (up && (wildcard(lv.lv_path, "/dev/*/wo_*") ||
  195. wildcard(lv.lv_path, "/dev/*/wp_*")))
  196. return 22;
  197. if (up)
  198. uvol_uci_commit(vol_name);
  199. if (lv.lv_active == up)
  200. return 0;
  201. if (!up) {
  202. let devname = getdev(lv);
  203. if (devname)
  204. system(sprintf("umount /dev/%s", devname));
  205. }
  206. let lvchange_r = lvm("lvchange", up?"-k":"-a", "n", lv.lv_full_name);
  207. if (up && lvchange_r.retval != 0)
  208. return lvchange_r.retval;
  209. lvchange_r = lvm("lvchange", up?"-a":"-k", "y", lv.lv_full_name);
  210. if (lvchange_r.retval != 0)
  211. return lvchange_r.retval;
  212. return 0
  213. }
  214. function lvm_up(vol_name) {
  215. return lvm_updown(vol_name, true);
  216. }
  217. function lvm_down(vol_name) {
  218. return lvm_updown(vol_name, false);
  219. }
  220. function lvm_create(vol_name, vol_size, vol_mode) {
  221. if (!vol_name || !vg_name)
  222. return 22;
  223. vol_size = +vol_size;
  224. if (vol_size <= 0)
  225. return 22;
  226. let res = lvs(vg_name, vol_name);
  227. if (res[0])
  228. return 17;
  229. let size_ext = vol_size / vg.vg_extent_size;
  230. if (vol_size % vg.vg_extent_size)
  231. ++size_ext;
  232. let lvmode, mode;
  233. if (vol_mode == "ro" || vol_mode == "wo") {
  234. lvmode = "r";
  235. mode = "wo";
  236. } else if (vol_mode == "rw") {
  237. lvmode = "rw";
  238. mode = "wp";
  239. } else {
  240. return 22;
  241. }
  242. 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);
  243. if (ret.retval != 0 || lvmode == "r")
  244. return ret.retval;
  245. let lv = lvs(vg_name, vol_name);
  246. if (!lv[0] || !lv[0].lv_full_name)
  247. return 22;
  248. lv = lv[0];
  249. let ret = lvm("lvchange", "-a", "y", lv.lv_full_name);
  250. if (ret.retval != 0)
  251. return ret.retval;
  252. let use_f2fs = (lv.lv_size > (100 * 1024 * 1024));
  253. if (use_f2fs) {
  254. let mkfs_ret = system(sprintf("/usr/sbin/mkfs.f2fs -f -l \"%s\" \"%s\"", vol_name, lv.lv_path));
  255. if (mkfs_ret != 0 && mkfs_ret != 134) {
  256. lvchange_r = lvm("lvchange", "-a", "n", lv.lv_full_name);
  257. if (lvchange_r.retval != 0)
  258. return lvchange_r.retval;
  259. return mkfs_ret;
  260. }
  261. } else {
  262. let mkfs_ret = system(sprintf("/usr/sbin/mke2fs -F -L \"%s\" \"%s\"", vol_name, lv.lv_path));
  263. if (mkfs_ret != 0) {
  264. lvchange_r = lvm("lvchange", "-a", "n", lv.lv_full_name);
  265. if (lvchange_r.retval != 0)
  266. return lvchange_r.retval;
  267. return mkfs_ret;
  268. }
  269. }
  270. uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), "rw");
  271. ret = lvm("lvchange", "-a", "n", lv.lv_full_name);
  272. if (ret.retval != 0)
  273. return ret.retval;
  274. ret = lvm("lvrename", vg_name, sprintf("wp_%s", vol_name), sprintf("rw_%s", vol_name));
  275. if (ret.retval != 0)
  276. return ret.retval;
  277. return 0;
  278. }
  279. function lvm_remove(vol_name) {
  280. if (!vol_name || !vg_name)
  281. return 22;
  282. let res = lvs(vg_name, vol_name);
  283. if (!res[0])
  284. return 2;
  285. if (res[0].lv_active)
  286. return 16;
  287. let ret = lvm("lvremove", "-y", res[0].lv_full_name);
  288. if (ret.retval != 0)
  289. return ret.retval;
  290. uvol_uci_remove(vol_name);
  291. uvol_uci_commit(vol_name);
  292. return 0;
  293. }
  294. function lvm_dd(in_fd, out_fd, vol_size) {
  295. let rem = vol_size;
  296. let buf;
  297. while ((buf = in_fd.read(vg.vg_extent_size)) && (rem > 0)) {
  298. rem -= length(buf);
  299. if (rem < 0) {
  300. buf = substr(buf, 0, rem);
  301. }
  302. out_fd.write(buf);
  303. }
  304. return rem;
  305. }
  306. function lvm_write(vol_name, vol_size) {
  307. if (!vol_name || !vg_name)
  308. return 22;
  309. let lv = lvs(vg_name, vol_name);
  310. if (!lv[0] || !lv[0].lv_full_name)
  311. return 2;
  312. lv = lv[0];
  313. vol_size = +vol_size;
  314. if (vol_size > lv.lv_size)
  315. return 27;
  316. if (wildcard(lv.lv_path, "/dev/*/wo_*")) {
  317. let ret = lvm("lvchange", "-p", "rw", lv.lv_full_name);
  318. if (ret.retval != 0)
  319. return ret.retval;
  320. let ret = lvm("lvchange", "-a", "y", lv.lv_full_name);
  321. if (ret.retval != 0)
  322. return ret.retval;
  323. let volfile = fs.open(lv.lv_path, "w");
  324. let ret = lvm_dd(fs.stdin, volfile, vol_size);
  325. volfile.close();
  326. if (ret < 0) {
  327. printf("more %d bytes data than given size!\n", -ret);
  328. }
  329. if (ret > 0) {
  330. printf("reading finished %d bytes before given size!\n", ret);
  331. }
  332. uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), "ro");
  333. let ret = lvm("lvchange", "-a", "n", lv.lv_full_name);
  334. if (ret.retval != 0)
  335. return ret.retval;
  336. let ret = lvm("lvchange", "-p", "r", lv.lv_full_name);
  337. if (ret.retval != 0)
  338. return ret.retval;
  339. let ret = lvm("lvrename", vg_name, sprintf("wo_%s", vol_name), sprintf("ro_%s", vol_name));
  340. if (ret.retval != 0)
  341. return ret.retval;
  342. } else {
  343. return 22;
  344. }
  345. return 0;
  346. }
  347. function lvm_detect() {
  348. let temp_up = [];
  349. let inactive_lv = lvs(vg_name, null, "lv_skip_activation!=0");
  350. for (let lv in inactive_lv) {
  351. lvm("lvchange", "-k", "n", lv.lv_full_name);
  352. lvm("lvchange", "-a", "y", lv.lv_full_name);
  353. push(temp_up, lv.lv_full_name);
  354. }
  355. sleep(1000);
  356. uvol_uci_init();
  357. for (let lv in lvs(vg_name)) {
  358. let vol_name = substr(lv.lv_name, 3);
  359. let vol_mode = substr(lv.lv_name, 0, 2);
  360. uvol_uci_add(vol_name, sprintf("/dev/%s", getdev(lv)), vol_mode);
  361. }
  362. uvol_uci_commit();
  363. for (let lv_full_name in temp_up) {
  364. lvm("lvchange", "-a", "n", lv_full_name);
  365. lvm("lvchange", "-k", "y", lv_full_name);
  366. }
  367. return 0;
  368. }
  369. function lvm_boot() {
  370. return 0;
  371. }
  372. backend.backend = "LVM";
  373. backend.priority = 50;
  374. backend.init = lvm_init;
  375. backend.boot = lvm_boot;
  376. backend.detect = lvm_detect;
  377. backend.free = lvm_free;
  378. backend.align = lvm_align;
  379. backend.total = lvm_total;
  380. backend.list = lvm_list;
  381. backend.size = lvm_size;
  382. backend.status = lvm_status;
  383. backend.device = lvm_device;
  384. backend.up = lvm_up;
  385. backend.down = lvm_down;
  386. backend.create = lvm_create;
  387. backend.remove = lvm_remove;
  388. backend.write = lvm_write;