Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
mvollmer committed Sep 22, 2023
1 parent 98a2e09 commit 7592ccb
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 40 deletions.
84 changes: 84 additions & 0 deletions doc/anaconda.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
Cockpit Storage in Anaconda Mode
================================

Anaconda (the OS Installer) can open the Cockpit "storaged" page for
advanced setup of the target storage devices. When this is done,
storaged is in a special "Anaconda mode" and behaves significantly
different.

In essence, the storaged page restricts itself to working with the
target environment. It will hide the real root filesystem (on the USB
stick that the Live environment was booted from), but let the user
create a "fake" root filesystem on some block device.

Entering Anaconda mode
----------------------

The "storaged" page is put into Anaconda mode by storing a
"cockpit_anaconda" item in its `window.localStorage`. The value
should be a JSON encoded object, the details of which are explained
below.

Since both Anaconda and the storaged page are served from the same
origin, Anaconda can just execute something like this:

```
window.localStorage.setItem("cockpit_anaconda",
JSON.stringify({
"mount_point_prefix": "/sysroot",
"ignore_devices": [ "/dev/sr0", "/dev/loop0" ]
}));
window.open("/cockpit/@localhost/storage/index.html", "storage-tab");
```

Ignoring storage devices
------------------------

Anaconda needs to tell the storaged page which devices can not be used
to install the OS on. This is done with the "ignore_devices" entry,
which is a array of strings.

```
{
"ignore_devices": [ "/dev/sda" ]
}
```

Entries in that array can refer to block devices, LVM2 volume groups
(/dev/vgroup-name/), and Stratis pools (/dev/stratis/pool-name/).

Mount point prefix
------------------

The storaged page can be put into a kind of "chroot" environment by
giving it a mount point prefix like so:

```
{
"mount_point_prefix": "/sysroot"
}
```

This works at the UI level: filesystems that have mount points outside
of "/sysroot" are hidden from the user, and when letting the user work
with mount points below "/sysroot", the "/sysroot" prefix is
omitted. So when the user says to create a filesystem on "/var", they
are actually creating one on "/sysroot/var".

However, Cockpit (via UDisks2) will still write the new mount point
configuration into the real /etc/fstab (_not_ /sysroot/etc/fstab).

In addition to that, Cockpit will also store the mount points in the
`"cockpit_mount_points"` item in `window.localStorage`, as a JSON
encoded object.

This is a simple map from block device to mount point, like

```
{
"/dev/vda1": "/boot",
"/dev/vda2": "/"
}
```

The mount points do not include the mount point prefix.
66 changes: 66 additions & 0 deletions pkg/storaged/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ function update_indices() {
client.update = () => {
update_indices();
client.path_warnings = find_warnings(client);
client.export_mount_point_mapping();
client.dispatchEvent("changed");
};

Expand Down Expand Up @@ -628,6 +629,15 @@ function init_model(callback) {
}
}

try {
client.anaconda = JSON.parse(window.localStorage.getItem("cockpit_anaconda"));
} catch {
console.warn("Can't parse cockpit_anaconda configuration as JSON");
client.anaconda = null;
}

console.log("ANACONDA", client.anaconda);

pull_time().then(() => {
read_os_release().then(os_release => {
client.os_release = os_release;
Expand Down Expand Up @@ -1371,4 +1381,60 @@ client.get_config = (name, def) => {
}
};

client.in_anaconda_mode = () => !!client.anaconda;

client.strip_mount_point_prefix = (dir) => {
const mpp = client.anaconda?.mount_point_prefix;

if (dir && mpp) {
if (dir.indexOf(mpp) != 0)
return false;

dir = dir.substr(mpp.length);
if (dir == "")
dir = "/";
}

return dir;
}

Check notice

Code scanning / CodeQL

Semicolon insertion Note

Avoid automated semicolon insertion (93% of all statements in
the enclosing script
have an explicit semicolon).

client.add_mount_point_prefix = (dir) => {
const mpp = client.anaconda?.mount_point_prefix;
if (mpp) {
if (dir == "/")
dir = mpp;
else
dir = mpp + dir;
}
return dir;
}

Check notice

Code scanning / CodeQL

Semicolon insertion Note

Avoid automated semicolon insertion (93% of all statements in
the enclosing script
have an explicit semicolon).

client.should_ignore_device = (devname) => {
return client.anaconda?.ignore_devices && client.anaconda.ignore_devices.indexOf(devname) != -1;
};

client.should_ignore_block = (block) => {
return client.should_ignore_device(utils.decode_filename(block.PreferredDevice));
};

client.export_mount_point_mapping = () => {
console.log("EXPORT");

const mpm = { };
for (const p in client.blocks) {
const b = client.blocks[p];
for (const c of b.Configuration) {
if (c[0] == "fstab") {
const dir = client.strip_mount_point_prefix(utils.decode_filename(c[1].dir.v));
if (dir) {
console.log("MPM", utils.decode_filename(b.PreferredDevice), dir);
mpm[utils.decode_filename(b.PreferredDevice)] = dir;
}
}
}
}

window.localStorage.setItem("cockpit_mount_points", JSON.stringify(mpm));
};

export default client;
16 changes: 14 additions & 2 deletions pkg/storaged/content-views.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ function block_description(client, block, options) {
type = cockpit.format(C_("storage-id-desc", "$0 filesystem"), block.IdType);
if (client.fsys_sizes.data[mount_point])
size = client.fsys_sizes.data[mount_point];
used_for = mount_point;
used_for = client.strip_mount_point_prefix(mount_point);
} else if (block.IdUsage == "raid") {
if (block_pvol && client.vgroups[block_pvol.VolumeGroup]) {
const vgroup = client.vgroups[block_pvol.VolumeGroup];
Expand Down Expand Up @@ -559,6 +559,18 @@ function append_row(client, rows, level, key, name, desc, tabs, job_object, opti
if (info)
info = <>{"\n"}{info}</>;

let location;
if (desc.used_for === false) {
// XXX - urks
location = _("(Not part of target)");
menu = null;
tabs.actions = null;
tabs.renderers = [];
} else if (desc.link)
location = <Button isInline variant="link" onClick={() => cockpit.location.go(desc.link)}>{desc.used_for}</Button>;
else
location = desc.used_for;

const cols = [
{
title: (
Expand All @@ -568,7 +580,7 @@ function append_row(client, rows, level, key, name, desc, tabs, job_object, opti
</span>)
},
{ title: desc.type },
{ title: desc.link ? <Button isInline variant="link" onClick={() => cockpit.location.go(desc.link)}>{desc.used_for}</Button> : desc.used_for },
{ title: location },
{
title: desc.size.length
? <StorageUsageBar stats={desc.size} critical={desc.critical_size || 0.95} block={name} />
Expand Down
11 changes: 9 additions & 2 deletions pkg/storaged/dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1069,7 +1069,8 @@ export const BlockingMessage = (usage) => {
pvol: _("physical volume of LVM2 volume group"),
mdraid: _("member of RAID device"),
vdo: _("backing device for VDO device"),
"stratis-pool-member": _("member of Stratis pool")
"stratis-pool-member": _("member of Stratis pool"),
mounted: _("Filesystem outside the target"),
};

const rows = [];
Expand Down Expand Up @@ -1151,9 +1152,15 @@ export const TeardownMessage = (usage) => {
const name = (fsys
? fsys.Devnode
: block_name(client.blocks[use.block.CryptoBackingDevice] || use.block));
let location = use.location;
if (use.usage == "mounted") {
location = client.strip_mount_point_prefix(location);
if (location === false)
location = _("(Not part of target)");
}
rows.push({
columns: [name,
use.location || "-",
location || "-",
use.actions.length ? use.actions.join(", ") : "-",
{
title: <UsersPopover users={use.users || []} />,
Expand Down
3 changes: 2 additions & 1 deletion pkg/storaged/drives-panel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ export class DrivesPanel extends React.Component {
const drives = drive_rows(client);

return (
<SidePanel id="drives"
<SidePanel client={client}
id="drives"
className="storage-drives-list"
title={_("Drives")}
empty_text={_("No drives attached")}
Expand Down
29 changes: 20 additions & 9 deletions pkg/storaged/format-dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,20 @@ export function extract_option(split, opt) {
export function initial_tab_options(client, block, for_fstab) {
const options = { };

utils.get_parent_blocks(client, block.path).forEach(p => {
// "nofail" is the default for new filesystems with Cockpit so
// that a failure to mount one of them will not prevent
// Cockpit from starting. This allows people to debug and fix
// these failures with Cockpit itself.
//
// "nofail" is the default for new filesystems with Cockpit so
// that a failure to mount one of them will not prevent
// Cockpit from starting. This allows people to debug and fix
// these failures with Cockpit itself.
//
// In Anaconda mode however, we don't make "nofail" the
// default since people will be creating the core filesystems
// like "/", "/var", etc.

if (!client.in_anaconda_mode())
options.nofail = true;

utils.get_parent_blocks(client, block.path).forEach(p => {

if (utils.is_netdev(client, p)) {
options._netdev = true;
}
Expand Down Expand Up @@ -160,10 +166,10 @@ export function format_dialog(client, path, start, size, enable_dos_extended) {
return false;
})
.then(version => {
format_dialog_internal(client, path, start, size, enable_dos_extended, version);
return format_dialog_internal(client, path, start, size, enable_dos_extended, version);
});
} else {
format_dialog_internal(client, path, start, size, enable_dos_extended);
return format_dialog_internal(client, path, start, size, enable_dos_extended);
}
}

Expand Down Expand Up @@ -260,6 +266,10 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
if (old_opts == undefined)
old_opts = initial_mount_options(client, block);

old_dir = client.strip_mount_point_prefix(old_dir);
if (old_dir === false)
return Promise.reject(_("This device can not be used for the installation target."));

const split_options = parse_options(old_opts);
extract_option(split_options, "noauto");
const opt_ro = extract_option(split_options, "ro");
Expand Down Expand Up @@ -298,7 +308,7 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
value: old_dir || "",
validate: (val, values, variant) => {
if (variant !== "nomount")
return is_valid_mount_point(client, block, val);
return is_valid_mount_point(client, block, client.add_mount_point_prefix(val));
}
}),
SelectOne("type", _("Type"),
Expand Down Expand Up @@ -492,6 +502,7 @@ function format_dialog_internal(client, path, start, size, enable_dos_extended,
if (mount_point != "") {
if (mount_point[0] != "/")
mount_point = "/" + mount_point;
mount_point = client.add_mount_point_prefix(mount_point);

config_items.push(["fstab", {
dir: { t: 'ay', v: utils.encode_filename(mount_point) },
Expand Down
23 changes: 17 additions & 6 deletions pkg/storaged/fsys-panel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export class FilesystemsPanel extends React.Component {
function is_mount(path) {
const block = client.blocks[path];

if (client.should_ignore_block(block))
return false;

// Stratis filesystems are handled separately
if (client.blocks_stratis_fsys[path])
return false;
Expand All @@ -72,14 +75,18 @@ export class FilesystemsPanel extends React.Component {

function make_mount(path) {
const block = client.blocks[path];
const [, mount_point] = get_fstab_config(block, true);
let [, mount_point] = get_fstab_config(block, true);
const fsys_size = client.fsys_sizes.data[mount_point];
const backing_block = client.blocks[block.CryptoBackingDevice] || block;
const block_lvm2 = client.blocks_lvm2[backing_block.path];
const lvol = block_lvm2 && client.lvols[block_lvm2.LogicalVolume];
const vgroup = lvol && client.vgroups[lvol.VolumeGroup];
let name = null;

mount_point = client.strip_mount_point_prefix(mount_point);
if (mount_point === false)
return null;

if (vgroup)
name = vgroup.Name + "/" + lvol.Name;

Expand All @@ -106,7 +113,7 @@ export class FilesystemsPanel extends React.Component {
}

const mounts = Object.keys(client.blocks).filter(is_mount)
.map(make_mount);
.map(make_mount).filter(m => m != null);

function has_filesystems(path) {
return client.stratis_pool_filesystems[path].length > 0;
Expand All @@ -132,8 +139,11 @@ export class FilesystemsPanel extends React.Component {
let mount = "-";
if (block) {
const [, mp] = get_fstab_config(block, true);
if (mp)
mount = mp;
if (mp) {
mount = client.strip_mount_point_prefix(mp);
if (mount === false)
return null;
}
}
return {
props: { path, client, key: fs.path },
Expand All @@ -152,11 +162,11 @@ export class FilesystemsPanel extends React.Component {
}
]
};
});
}).filter(m => m != null);
}

const pools = Object.keys(client.stratis_pools).filter(has_filesystems)
.map(make_pool);
.map(make_pool);

function onRowClick(event, row) {
if (!event || event.button !== 0)
Expand All @@ -177,6 +187,7 @@ export class FilesystemsPanel extends React.Component {
sortBy={{ index: 0, direction: SortByDirection.asc }}
aria-label={_("Filesystems")}
onRowClick={onRowClick}
emptyCaption={_("No filesystems")}
columns={[
{ title: _("Source"), sortable: true },
{ title: _("Type"), sortable: true },
Expand Down
Loading

0 comments on commit 7592ccb

Please sign in to comment.