Line data Source code
1 : /*
2 : * Copyright (C) 2016 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 113 : import React from "react";
7 : import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
8 :
9 113 : import cockpit from "cockpit";
10 : import client from "../client.js";
11 :
12 : import {
13 : block_name, decode_filename,
14 : parse_options, extract_option,
15 : get_fstab_config_with_client,
16 : find_children_for_mount_point,
17 : get_mount_points,
18 : } from "../utils.js";
19 : import { parse_subvol_from_options } from "../btrfs/utils.jsx";
20 : import { StorageLink } from "../storage-controls.jsx";
21 :
22 : import { mounting_dialog } from "./mounting-dialog.jsx";
23 :
24 113 : const _ = cockpit.gettext;
25 :
26 : /* is mounted with an entry in fstab */
27 108 : export function is_mounted(client, block, subvol) {
28 108 : const block_fsys = client.blocks_fsys[block.path];
29 108 : const mounted_at = get_mount_points(client, block_fsys, subvol);
30 108 : const [, dir] = get_fstab_config(block, false, subvol);
31 106 : if (dir) {
32 106 : return mounted_at.indexOf(dir) >= 0;
33 106 : } else
34 108 : return null;
35 108 : }
36 :
37 112 : export function get_fstab_config(block, also_child_config, subvol) {
38 112 : return get_fstab_config_with_client(client, block, also_child_config, subvol);
39 112 : }
40 :
41 3 : function nice_block_name(block) {
42 1 : return block_name(client.blocks[block.CryptoBackingDevice] || block);
43 3 : }
44 :
45 48 : function find_blocks_for_mount_point(client, mount_point, self_block, self_subvol) {
46 7 : function same_btrfs_volume(a, b) {
47 7 : return (client.blocks_fsys_btrfs[a.path] &&
48 7 : client.blocks_fsys_btrfs[b.path] &&
49 7 : client.blocks_fsys_btrfs[a.path].data.uuid == client.blocks_fsys_btrfs[b.path].data.uuid);
50 7 : }
51 :
52 7 : function same_btrfs_subvol(a, b) {
53 7 : return (a && b &&
54 6 : ((a.pathname && a.pathname == b.pathname) ||
55 0 : (a.id && a.id == b.id)));
56 7 : }
57 :
58 17 : function is_self(b, subvol) {
59 17 : if (client.blocks_fsys_btrfs[self_block.path])
60 0 : return same_btrfs_volume(b, self_block) && same_btrfs_subvol(subvol, self_subvol);
61 : else
62 4 : return self_block && (b == self_block || client.blocks[b.CryptoBackingDevice] == self_block);
63 17 : }
64 :
65 3 : function fmt_block_and_subvol(block, subvol) {
66 3 : if (subvol)
67 2 : return cockpit.format(_("btrfs subvolume $0 of $1"),
68 0 : (subvol.pathname == "/" || subvol.id == 5) ? "top-level" : subvol.pathname || subvol.id,
69 0 : block.IdLabel || block.IdUUID);
70 : else
71 1 : return nice_block_name(block);
72 3 : }
73 :
74 48 : const blocks = [];
75 48 : const seen_uuids = {};
76 :
77 48 : for (const p in client.blocks) {
78 48 : const b = client.blocks[p];
79 48 : for (const c of b.Configuration) {
80 48 : if (c[0] == "fstab") {
81 48 : let dir = decode_filename(c[1].dir.v);
82 48 : if (dir[0] != "/")
83 0 : dir = "/" + dir;
84 48 : const subvol = parse_subvol_from_options(decode_filename(c[1].opts.v));
85 3 : if (dir == mount_point && !is_self(b, subvol)) {
86 3 : if (!seen_uuids[b.IdUUID]) {
87 3 : seen_uuids[b.IdUUID] = true;
88 3 : blocks.push(fmt_block_and_subvol(b, subvol));
89 3 : }
90 3 : }
91 48 : }
92 48 : }
93 48 : }
94 :
95 48 : return blocks;
96 48 : }
97 :
98 52 : export function is_valid_mount_point(client, block, val, will_not_mount, allow_empty, subvol) {
99 13 : if (val === "") {
100 3 : if (!will_not_mount && !allow_empty)
101 3 : return _("Mount point cannot be empty");
102 12 : return null;
103 12 : }
104 :
105 48 : const other_blocks = find_blocks_for_mount_point(client, val, block, subvol);
106 48 : if (other_blocks.length > 0)
107 3 : return cockpit.format(_("Mount point is already used for $0"), other_blocks.join(", "));
108 :
109 38 : if (!will_not_mount) {
110 38 : const children = find_children_for_mount_point(client, val, block, subvol);
111 38 : if (Object.keys(children).length > 0)
112 2 : return <>
113 2 : {_("Filesystems are already mounted below this mountpoint.")}
114 2 : {Object.keys(children).map(m => <div key={m}>
115 2 : {cockpit.format("• $0 on $1", nice_block_name(children[m]),
116 2 : client.strip_mount_point_prefix(m))}
117 2 : </div>)}
118 2 : {_("Please unmount them first.")}
119 2 : </>;
120 38 : }
121 52 : }
122 :
123 101 : export function get_cryptobacking_noauto(client, block) {
124 24 : const crypto_backing = block.IdUsage == "crypto" ? block : client.blocks[block.CryptoBackingDevice];
125 101 : if (!crypto_backing)
126 101 : return false;
127 :
128 15 : const crypto_config = crypto_backing.Configuration.find(c => c[0] == "crypttab");
129 25 : if (!crypto_config)
130 25 : return false;
131 :
132 24 : const crypto_options = decode_filename(crypto_config[1].options.v).split(",")
133 15 : .map(o => o.trim());
134 24 : return crypto_options.indexOf("noauto") >= 0;
135 101 : }
136 :
137 6 : export function edit_mount_point(block, forced_options, subvol) {
138 6 : mounting_dialog(client, block, "update", forced_options, subvol);
139 6 : }
140 :
141 48 : export const MountPoint = ({ fstab_config, forced_options, backing_block, content_block, subvol }) => {
142 47 : const is_filesystem_mounted = content_block && is_mounted(client, content_block, subvol);
143 48 : const [, old_dir, old_opts] = fstab_config;
144 48 : const split_options = parse_options(old_opts);
145 48 : extract_option(split_options, "noauto");
146 48 : const opt_ro = extract_option(split_options, "ro");
147 48 : const opt_never_auto = extract_option(split_options, "x-cockpit-never-auto");
148 48 : const opt_nofail = extract_option(split_options, "nofail");
149 48 : const opt_netdev = extract_option(split_options, "_netdev");
150 48 : if (forced_options)
151 14 : for (const opt of forced_options)
152 14 : extract_option(split_options, opt);
153 :
154 48 : let mount_point_text = null;
155 39 : if (old_dir) {
156 39 : mount_point_text = client.strip_mount_point_prefix(old_dir);
157 39 : let opt_texts = [];
158 39 : if (opt_ro)
159 3 : opt_texts.push(_("read only"));
160 39 : if (opt_never_auto)
161 10 : opt_texts.push(_("never mount at boot"));
162 38 : else if (opt_netdev)
163 5 : opt_texts.push(_("after network"));
164 36 : else if (opt_nofail)
165 7 : opt_texts.push(_("ignore failure"));
166 : else
167 12 : opt_texts.push(_("stop boot on failure"));
168 39 : opt_texts = opt_texts.concat(split_options);
169 36 : if (opt_texts.length && !client.in_anaconda_mode()) {
170 36 : mount_point_text = cockpit.format("$0 ($1)", mount_point_text, opt_texts.join(", "));
171 36 : }
172 39 : }
173 :
174 48 : let extra_text = null;
175 5 : if (client.in_anaconda_mode()) {
176 5 : if (!old_dir)
177 2 : extra_text = _("The filesystem has no assigned mount point.");
178 2 : } else {
179 34 : if (!is_filesystem_mounted) {
180 34 : if (!old_dir)
181 8 : extra_text = _("The filesystem has no permanent mount point.");
182 : else
183 20 : extra_text = _("The filesystem is not mounted.");
184 8 : } else if (backing_block != content_block) {
185 12 : if (!opt_never_auto)
186 11 : extra_text = _("The filesystem will be unlocked and mounted on the next boot. This might require inputting a passphrase.");
187 12 : }
188 45 : }
189 :
190 22 : if (!mount_point_text) {
191 22 : mount_point_text = extra_text;
192 22 : extra_text = null;
193 22 : }
194 :
195 48 : if (extra_text)
196 24 : extra_text = <><br />{extra_text}</>;
197 :
198 48 : return (
199 48 : <>
200 48 : <Flex>
201 48 : { mount_point_text &&
202 48 : <FlexItem>{ mount_point_text }</FlexItem>
203 : }
204 48 : <FlexItem>
205 0 : <StorageLink onClick={() => edit_mount_point(content_block || backing_block,
206 4 : forced_options, subvol)}>
207 48 : {_("edit")}
208 48 : </StorageLink>
209 48 : </FlexItem>
210 48 : </Flex>
211 48 : { extra_text }
212 48 : </>);
213 48 : };
214 :
215 108 : export const mount_point_text = (mount_point, mounted) => {
216 108 : let mp_text;
217 106 : if (mount_point) {
218 106 : mp_text = client.strip_mount_point_prefix(mount_point);
219 106 : if (mp_text == false)
220 11 : return null;
221 56 : if (!mounted && !client.in_anaconda_mode())
222 51 : mp_text = mp_text + " " + _("(not mounted)");
223 106 : } else {
224 108 : if (client.in_anaconda_mode())
225 11 : mp_text = _("(no assigned mount point)");
226 : else
227 101 : mp_text = _("(not mounted)");
228 108 : }
229 108 : return mp_text;
230 108 : };
|