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";
9 :
10 : import { CardHeader, CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
11 : import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
12 : import { List, ListItem } from "@patternfly/react-core/dist/esm/components/List/index.js";
13 :
14 : import { VolumeIcon } from "../icons/gnome-icons.jsx";
15 : import {
16 : StorageCard, StorageDescription, PageTable,
17 : new_page, new_card, PAGE_CATEGORY_VIRTUAL,
18 : get_crossrefs
19 : } from "../pages.jsx";
20 : import { dialog_open, PassInput } from "../dialog.jsx";
21 : import { std_reply, with_stored_passphrase } from "./utils.jsx";
22 : import { get_stored_keydescs } from "./pool";
23 :
24 : import * as python from "python.js";
25 : import stratis3_start_pool_py from "./stratis3-start-pool.py";
26 :
27 113 : const _ = cockpit.gettext;
28 :
29 : /* Starting a pool has to deal with a number of possibilities:
30 :
31 : API REVISION:
32 :
33 : r6: Can only start V1 pools, takes unlock method as argument and
34 : passphrase in keyring.
35 :
36 : r8: Can start both V1 and V2 pools, takes passphrase via FD,
37 : unlock method determined by whether or not there is a
38 : FD.
39 :
40 : METADATA FORMAT:
41 :
42 : V1: Can have at most one passphrase and/or at most one clevis info,
43 : which are communicated in the StoppedPools manager property of both
44 : the r6 and r8 API.
45 :
46 : V2: Can have zero or more passphrases and clevis infos, a summary of which is
47 : communicated in the StoppedPools property.
48 : */
49 :
50 2 : function is_v1_pool(uuid) {
51 2 : if (client.stratis_interface_revision < 8)
52 0 : return true;
53 :
54 2 : const stopped_info = client.stratis_manager.StoppedPools[uuid];
55 2 : return !stopped_info.metadata_version.v[0] || stopped_info.metadata_version.v[1] == 1;
56 2 : }
57 :
58 2 : function stratis_r8_manager_start_pool(uuid, slot, passphrase) {
59 2 : const p = python.spawn(
60 2 : stratis3_start_pool_py,
61 0 : [uuid, slot, passphrase ? 0 : "-"],
62 2 : { superuser: "require" });
63 2 : if (passphrase)
64 0 : p.input(passphrase);
65 2 : return p;
66 2 : }
67 :
68 0 : function get_v1_keydesc(uuid) {
69 0 : const stopped_info = client.stratis_manager.StoppedPools[uuid];
70 0 : const kd = stopped_info.key_description;
71 0 : return kd.v[1].v[1];
72 0 : }
73 :
74 2 : async function manager_start_pool(uuid, unlock_method, passphrase) {
75 0 : if (client.stratis_interface_revision < 8) {
76 0 : const start = () => {
77 0 : return client.stratis_manager.StartPool(
78 0 : uuid, "uuid",
79 0 : unlock_method ? [true, unlock_method] : [false, ""])
80 0 : .then(std_reply);
81 0 : };
82 0 : if (passphrase) {
83 0 : const keydesc = get_v1_keydesc(uuid);
84 0 : return with_stored_passphrase(client, keydesc, passphrase, start);
85 0 : } else {
86 0 : return start();
87 0 : }
88 0 : } else {
89 0 : return await stratis_r8_manager_start_pool(uuid, unlock_method ? "any" : "-", passphrase);
90 2 : }
91 2 : }
92 :
93 2 : function start_pool(uuid, show_devs) {
94 2 : const stopped_info = client.stratis_manager.StoppedPools[uuid];
95 2 : const devs = client.stratis_stopped_pool_devnodes(uuid);
96 :
97 0 : async function prompt_for_passphrase() {
98 0 : if (client.stratis_interface_revision < 8) {
99 0 : const key_desc = get_v1_keydesc(uuid);
100 0 : const stored_keydescs = await get_stored_keydescs();
101 0 : if (stored_keydescs.includes(key_desc))
102 0 : return await client.stratis_manager.StartPool(uuid, "uuid", [true, "keyring"]).then(std_reply);
103 0 : }
104 :
105 0 : dialog_open({
106 0 : Title: _("Unlock encrypted Stratis pool"),
107 0 : Body: (show_devs &&
108 0 : <>
109 0 : <p>{_("Provide the passphrase for the pool on these block devices:")}</p>
110 0 : <List>{devs.map(d => <ListItem key={d}>{d}</ListItem>)}</List>
111 0 : <br />
112 0 : </>),
113 0 : Fields: [
114 0 : PassInput("passphrase", _("Passphrase"), { })
115 0 : ],
116 0 : Action: {
117 0 : Title: _("Unlock"),
118 0 : action: function(vals) {
119 0 : return manager_start_pool(uuid, "keyring", vals.passphrase);
120 0 : }
121 0 : }
122 0 : });
123 0 : }
124 :
125 2 : let have_passphrase;
126 2 : let have_clevis;
127 :
128 1 : if (is_v1_pool(uuid)) {
129 1 : const kd = stopped_info.key_description;
130 0 : have_passphrase = kd && kd.v[0] && kd.v[1].v[0];
131 1 : const ci = stopped_info.clevis_info;
132 0 : have_clevis = ci && ci.v[0] && ci.v[1].v[0];
133 0 : } else {
134 0 : const features = stopped_info.features.v[0] ? stopped_info.features.v[1] : { };
135 1 : const encrypted = features.encryption;
136 1 : have_passphrase = features.key_description_present;
137 1 : have_clevis = features.clevis_present;
138 :
139 : // stratisd 3.8.0 never sets the "key_description_present"
140 : // or "clevis_present" flags, so we have to try everything
141 : // when the pool is encrypted. (stratisd 3.8.1 and younger
142 : // will set at least one of them for encrypted pools.)
143 : //
144 0 : if (encrypted && !have_passphrase && !have_clevis)
145 0 : have_passphrase = have_clevis = true;
146 1 : }
147 :
148 2 : if (!have_passphrase && !have_clevis) {
149 : // Not an encrypted pool, just start it
150 2 : return manager_start_pool(uuid, null, null);
151 0 : } else if (have_passphrase && have_clevis) {
152 0 : return manager_start_pool(uuid, "clevis", null).catch(prompt_for_passphrase);
153 0 : } else if (!have_passphrase && have_clevis) {
154 0 : return manager_start_pool(uuid, "clevis", null);
155 0 : } else if (have_passphrase && !have_clevis) {
156 0 : return prompt_for_passphrase();
157 0 : }
158 2 : }
159 :
160 9 : export function make_stratis_stopped_pool_page(parent, uuid) {
161 9 : if (client.in_anaconda_mode())
162 9 : return;
163 :
164 9 : const pool_card = new_card({
165 9 : title: _("Stratis pool"),
166 9 : type_extra: _("stopped"),
167 9 : next: null,
168 9 : page_location: ["pool", uuid],
169 2 : page_name: client.stratis_manager.StoppedPools[uuid]?.name?.v || uuid,
170 9 : page_icon: VolumeIcon,
171 9 : page_category: PAGE_CATEGORY_VIRTUAL,
172 9 : component: StoppedStratisPoolCard,
173 9 : props: { uuid },
174 9 : actions: [
175 2 : { title: _("Start"), action: from_menu => start_pool(uuid, from_menu), spinner: true },
176 9 : ],
177 9 : });
178 :
179 9 : new_page(parent, pool_card);
180 9 : }
181 :
182 5 : const StoppedStratisPoolCard = ({ card, uuid }) => {
183 5 : return (
184 5 : <StorageCard card={card}>
185 5 : <CardBody>
186 5 : <DescriptionList className="pf-m-horizontal-on-sm">
187 5 : <StorageDescription title={_("UUID")} value={uuid} />
188 5 : </DescriptionList>
189 5 : </CardBody>
190 5 : <CardHeader><strong>{_("Block devices")}</strong></CardHeader>
191 5 : <PageTable
192 5 : emptyCaption={_("No block devices found")}
193 5 : aria-label={_("Stratis block devices")}
194 5 : crossrefs={get_crossrefs(uuid)} />
195 5 : </StorageCard>
196 : );
197 5 : };
|