Line data Source code
1 : /*
2 : * Copyright (C) 2023 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 113 : import cockpit from "cockpit";
7 113 : import React from "react";
8 : import client from "../client.js";
9 :
10 : import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
11 :
12 : import {
13 : encode_filename,
14 : parse_options, unparse_options, extract_option, reload_systemd,
15 : set_crypto_auto_option, get_mount_points,
16 : BTRFS_TOOL_MOUNT_PATH
17 : } from "../utils.js";
18 : import { StorageButton } from "../storage-controls.jsx";
19 :
20 : import { mounting_dialog } from "./mounting-dialog.jsx";
21 : import { get_cryptobacking_noauto } from "./utils.jsx";
22 :
23 113 : const _ = cockpit.gettext;
24 :
25 108 : export function check_mismounted_fsys(backing_block, content_block, fstab_config, subvol) {
26 108 : if (client.in_anaconda_mode())
27 108 : return;
28 :
29 101 : const block_fsys = content_block && client.blocks_fsys[content_block.path];
30 108 : const [, dir, opts] = fstab_config;
31 :
32 56 : if (!(block_fsys || dir))
33 108 : return;
34 :
35 75 : function ignore_mount(m) {
36 : // We don't complain about the rootfs, it's probably
37 : // configured somewhere else, like in the bootloader.
38 75 : if (m == "/")
39 2 : return true;
40 :
41 : // This is the mount point used for monitoring btrfs filesystems.
42 75 : if (m.startsWith(BTRFS_TOOL_MOUNT_PATH))
43 2 : return true;
44 :
45 75 : return false;
46 75 : }
47 :
48 101 : const mounted_at = get_mount_points(client, block_fsys, subvol);
49 101 : const split_options = parse_options(opts);
50 101 : const opt_noauto = extract_option(split_options, "noauto");
51 101 : const opt_noauto_intent = extract_option(split_options, "x-cockpit-never-auto");
52 101 : const opt_systemd_automount = split_options.indexOf("x-systemd.automount") >= 0;
53 101 : const is_mounted = mounted_at.indexOf(dir) >= 0;
54 83 : const other_mounts = mounted_at.filter(m => m != dir && !ignore_mount(m));
55 101 : const crypto_backing_noauto = get_cryptobacking_noauto(client, backing_block);
56 :
57 101 : let type;
58 101 : if (dir) {
59 13 : if (!is_mounted && other_mounts.length > 0) {
60 13 : if (!opt_noauto)
61 13 : type = "change-mount-on-boot";
62 : else
63 13 : type = "mounted-no-config";
64 11 : } else if (crypto_backing_noauto && !opt_noauto)
65 18 : type = "locked-on-boot-mount";
66 51 : else if (!is_mounted && !opt_noauto)
67 43 : type = "mount-on-boot";
68 16 : else if (is_mounted && opt_noauto && !opt_noauto_intent && !opt_systemd_automount)
69 16 : type = "no-mount-on-boot";
70 18 : } else if (other_mounts.length > 0) {
71 18 : type = "mounted-no-config";
72 18 : }
73 :
74 101 : if (type)
75 49 : return { warning: "mismounted-fsys", type, other: other_mounts[0] };
76 108 : }
77 :
78 10 : export const MismountAlert = ({ warning, fstab_config, forced_options, backing_block, content_block, subvol }) => {
79 10 : if (!warning)
80 0 : return null;
81 :
82 10 : const { type, other } = warning;
83 10 : const [old_config, old_dir, old_opts, old_parents] = fstab_config;
84 10 : const split_options = parse_options(old_opts);
85 10 : extract_option(split_options, "noauto");
86 10 : const opt_ro = extract_option(split_options, "ro");
87 10 : const opt_nofail = extract_option(split_options, "nofail");
88 10 : const opt_netdev = extract_option(split_options, "_netdev");
89 10 : const split_options_for_fix_config = split_options.slice();
90 10 : if (forced_options)
91 1 : for (const opt of forced_options)
92 1 : extract_option(split_options, opt);
93 :
94 6 : function fix_config() {
95 6 : let opts = [];
96 6 : if (type == "mount-on-boot")
97 4 : opts.push("noauto");
98 2 : if (type == "locked-on-boot-mount") {
99 2 : opts.push("noauto");
100 2 : opts.push("x-cockpit-never-auto");
101 2 : }
102 6 : if (opt_ro)
103 0 : opts.push("ro");
104 6 : if (opt_nofail)
105 2 : opts.push("nofail");
106 6 : if (opt_netdev)
107 1 : opts.push("_netdev");
108 2 : if (subvol) {
109 2 : opts.push(`subvol=${subvol.pathname}`);
110 2 : }
111 :
112 : // Add the forced options, but only to new entries. We
113 : // don't want to modify existing entries beyond what we
114 : // say on the button.
115 2 : if (!old_config && forced_options)
116 1 : opts = opts.concat(forced_options);
117 :
118 6 : const new_opts = unparse_options(opts.concat(split_options_for_fix_config));
119 6 : let all_new_opts;
120 6 : if (new_opts && old_parents)
121 0 : all_new_opts = new_opts + "," + old_parents;
122 4 : else if (new_opts)
123 0 : all_new_opts = new_opts;
124 : else
125 0 : all_new_opts = old_parents;
126 :
127 6 : let new_dir = old_dir;
128 6 : if (type == "change-mount-on-boot" || type == "mounted-no-config")
129 4 : new_dir = other;
130 :
131 6 : const new_config = [
132 6 : "fstab", {
133 1 : fsname: old_config ? old_config[1].fsname : undefined,
134 6 : dir: { t: 'ay', v: encode_filename(new_dir) },
135 6 : type: { t: 'ay', v: encode_filename("auto") },
136 0 : opts: { t: 'ay', v: encode_filename(all_new_opts || "defaults") },
137 6 : freq: { t: 'i', v: 0 },
138 6 : passno: { t: 'i', v: 0 },
139 6 : "track-parents": { t: 'b', v: !old_config }
140 6 : }];
141 :
142 6 : function fixup_crypto_backing() {
143 6 : if (!backing_block)
144 6 : return;
145 6 : if (type == "no-mount-on-boot")
146 3 : return set_crypto_auto_option(backing_block, true);
147 6 : if (type == "locked-on-boot-mount")
148 2 : return set_crypto_auto_option(backing_block, false);
149 6 : }
150 :
151 6 : function fixup_fsys() {
152 6 : if (old_config)
153 1 : return backing_block.UpdateConfigurationItem(old_config, new_config, {}).then(reload_systemd);
154 : else
155 2 : return backing_block.AddConfigurationItem(new_config, {}).then(reload_systemd);
156 6 : }
157 :
158 6 : return fixup_fsys().then(fixup_crypto_backing);
159 6 : }
160 :
161 5 : function fix_mount() {
162 5 : const crypto_backing_crypto = client.blocks_crypto[backing_block.path];
163 :
164 5 : function do_mount() {
165 5 : if (!content_block)
166 0 : mounting_dialog(client, backing_block, "mount", forced_options);
167 : else
168 4 : return client.mount_at(content_block, old_dir);
169 5 : }
170 :
171 3 : function do_unmount(dir) {
172 3 : return client.unmount_at(dir)
173 3 : .then(() => {
174 3 : if (backing_block != content_block)
175 1 : return crypto_backing_crypto.Lock({});
176 3 : });
177 3 : }
178 :
179 5 : if (type == "change-mount-on-boot")
180 2 : return client.unmount_at(other).then(() => client.mount_at(content_block, old_dir));
181 5 : else if (type == "mount-on-boot")
182 3 : return do_mount();
183 3 : else if (type == "no-mount-on-boot")
184 3 : return do_unmount(old_dir);
185 3 : else if (type == "mounted-no-config")
186 0 : return do_unmount(other);
187 1 : else if (type == "locked-on-boot-mount") {
188 1 : if (backing_block != content_block)
189 1 : return set_crypto_auto_option(backing_block, true);
190 1 : }
191 5 : }
192 :
193 10 : let text;
194 10 : let fix_config_text;
195 10 : let fix_mount_text;
196 :
197 2 : if (type == "change-mount-on-boot") {
198 2 : text = cockpit.format(_("The filesystem is currently mounted on $0 but will be mounted on $1 on the next boot."), other, old_dir);
199 2 : fix_config_text = cockpit.format(_("Mount automatically on $0 on boot"), other);
200 2 : fix_mount_text = cockpit.format(_("Mount on $0 now"), old_dir);
201 2 : } else if (type == "mount-on-boot") {
202 6 : text = _("The filesystem is currently not mounted but will be mounted on the next boot.");
203 6 : fix_config_text = _("Do not mount automatically on boot");
204 6 : fix_mount_text = _("Mount now");
205 3 : } else if (type == "no-mount-on-boot") {
206 3 : text = _("The filesystem is currently mounted but will not be mounted after the next boot.");
207 3 : fix_config_text = _("Mount also automatically on boot");
208 3 : fix_mount_text = _("Unmount now");
209 2 : } else if (type == "mounted-no-config") {
210 6 : text = cockpit.format(_("The filesystem is currently mounted on $0 but will not be mounted after the next boot."), other);
211 6 : fix_config_text = cockpit.format(_("Mount automatically on $0 on boot"), other);
212 6 : fix_mount_text = _("Unmount now");
213 0 : } else if (type == "locked-on-boot-mount") {
214 2 : text = _("The filesystem is configured to be automatically mounted on boot but its encryption container will not be unlocked at that time.");
215 2 : fix_config_text = _("Do not mount automatically on boot");
216 2 : fix_mount_text = _("Unlock automatically on boot");
217 2 : }
218 :
219 10 : return (
220 10 : <Alert variant="warning" isInline
221 10 : title={_("Inconsistent filesystem mount")}>
222 10 : {text}
223 10 : <div className="storage-alert-actions">
224 10 : <StorageButton onClick={fix_config}>{fix_config_text}</StorageButton>
225 10 : { fix_mount_text && <StorageButton onClick={fix_mount}>{fix_mount_text}</StorageButton> }
226 10 : </div>
227 10 : </Alert>);
228 10 : };
|