sculpt: make storage target configurable

This patch lays the selection of the used storage target into the hands
of the config/manager file. By default, Sculpt selects the target by its
built-in heuristics, probing for a Sculpt partition. However, by
specifying a <target> node, one can explicitly select a storage target.

E.g., for using the 2nd partition of the SATA disk connected to port 1
of the AHCI controller, one can now specify:

  <target driver="ahci" port="1" partition="2"/>

For selecting the ram_fs as target:

  <target driver="ram_fs"/>

The latter case is particularly useful for custom Sculpt scenarios
deployed entirely from RAM. For such scenarios, add two lines to
your .sculpt file:

  ram_fs:  depot
  manager: use_ram_fs

The first line configures the ram_fs such that the depot is mounted
as a tar archive. The second line configures the sculpt manager to
select the ram_fs as storage target. You can find this feature
exemplified in default-linux.sculpt scenario.

  build/x86_64$ make run/sculpt_test KERNEL=linux BOARD=linux

It is worth noting that the configuration can be changed at runtime.
This allows for switching between different storage targets on the fly.

Issue #5166
This commit is contained in:
Norman Feske 2024-03-28 12:34:57 +01:00 committed by Christian Helmuth
parent 508e0bdfbf
commit 4a1a29b3d0
9 changed files with 133 additions and 73 deletions

View File

@ -2,6 +2,7 @@
drivers: linux
deploy: example
ram_fs: depot
manager: use_ram_fs
# selection of launcher-menu entries
launcher: sticks_blue_backdrop nano3d system_shell

View File

@ -0,0 +1,3 @@
<config>
<target driver="ram_fs"/>
</config>

View File

@ -255,6 +255,8 @@ struct Sculpt::Main : Input_event_handler,
void _handle_config(Xml_node const &config)
{
_handle_storage_devices();
_verbose_modem = config.attribute_value("verbose_modem", false);
}
@ -303,20 +305,22 @@ struct Sculpt::Main : Input_event_handler,
void _handle_storage_devices()
{
Storage_target const orig_sculpt_partition = _storage._sculpt_partition;
Storage_target const orig_target = _storage._selected_target;
bool total_progress = false;
for (bool progress = true; progress; total_progress |= progress) {
progress = false;
_drivers.with_storage_devices([&] (Drivers::Storage_devices const &devices) {
progress = _storage.update(devices.usb, devices.ahci,
devices.nvme, devices.mmc).progress; });
_config.with_xml([&] (Xml_node const &config) {
progress = _storage.update(config,
devices.usb, devices.ahci,
devices.nvme, devices.mmc).progress; }); });
/* update USB policies for storage devices */
_drivers.update_usb();
}
if (orig_sculpt_partition != _storage._sculpt_partition)
if (orig_target != _storage._selected_target)
_restart_from_storage_target();
if (total_progress) {
@ -410,7 +414,7 @@ struct Sculpt::Main : Input_event_handler,
*/
bool _update_running() const
{
return _storage._sculpt_partition.valid()
return _storage._selected_target.valid()
&& !_prepare_in_progress()
&& _network.ready()
&& _deploy.update_needed();
@ -750,7 +754,7 @@ struct Sculpt::Main : Input_event_handler,
Conditional_widget<Storage_widget> _storage_widget {
Conditional_widget<Storage_widget>::Attr { .centered = true },
Id { "storage dialog" }, _storage._storage_devices, _storage._sculpt_partition };
Id { "storage dialog" }, _storage._storage_devices, _storage._selected_target };
/*
* Network section
@ -797,7 +801,7 @@ struct Sculpt::Main : Input_event_handler,
Conditional_widget<Graph>
_graph { Id { "graph" },
_runtime_state, _cached_runtime_config, _storage._storage_devices,
_storage._sculpt_partition, _storage._ram_fs_state,
_storage._selected_target, _storage._ram_fs_state,
_popup.state, _deploy._children };
Conditional_widget<Network_widget>
@ -897,28 +901,28 @@ struct Sculpt::Main : Input_event_handler,
_software_title_bar.view_status(s, _software_status_message()); });
s.widget(_software_tabs_widget, _software_title_bar.selected(),
_storage._sculpt_partition, _presets, _software_status_available());
_storage._selected_target, _presets, _software_status_available());
s.widget(_graph, _software_title_bar.selected()
&& _software_tabs_widget.hosted.runtime_selected());
s.widget(_software_presets_widget, _software_title_bar.selected()
&& _software_tabs_widget.hosted.presets_selected()
&& _storage._sculpt_partition.valid(),
&& _storage._selected_target.valid(),
_presets);
s.widget(_software_options_widget, _software_title_bar.selected()
&& _software_tabs_widget.hosted.options_selected()
&& _storage._sculpt_partition.valid());
&& _storage._selected_target.valid());
s.widget(_software_add_widget, _software_title_bar.selected()
&& _software_tabs_widget.hosted.add_selected()
&& _storage._sculpt_partition.valid());
&& _storage._selected_target.valid());
_image_index_rom.with_xml([&] (Xml_node const &image_index) {
s.widget(_software_update_widget, _software_title_bar.selected()
&& _software_tabs_widget.hosted.update_selected()
&& _storage._sculpt_partition.valid(),
&& _storage._selected_target.valid(),
image_index);
});
@ -966,7 +970,7 @@ struct Sculpt::Main : Input_event_handler,
void _handle_runtime_state(Xml_node const &);
Runtime_state _runtime_state { _heap, _storage._sculpt_partition };
Runtime_state _runtime_state { _heap, _storage._selected_target };
Managed_config<Main> _runtime_config {
_env, "config", "runtime", *this, &Main::_handle_runtime };
@ -1314,9 +1318,15 @@ struct Sculpt::Main : Input_event_handler,
void use(Storage_target const &target) override
{
_storage._sculpt_partition = target;
Storage_target const orig_target = _storage._selected_target;
_storage._selected_target = target;
_software_update_widget.hosted.reset();
_download_queue.reset();
if (orig_target != _storage._selected_target)
_restart_from_storage_target();
generate_runtime_config();
}
@ -2382,7 +2392,7 @@ void Sculpt::Main::_generate_runtime_config(Xml_generator &xml) const
/*
* Load configuration and update depot config on the sculpt partition
*/
if (_storage._sculpt_partition.valid() && _prepare_in_progress())
if (_storage._selected_target.valid() && _prepare_in_progress())
xml.node("start", [&] {
gen_prepare_start_content(xml, _prepare_version); });
@ -2390,7 +2400,7 @@ void Sculpt::Main::_generate_runtime_config(Xml_generator &xml) const
* Spawn chroot instances for accessing '/depot' and '/public'. The
* chroot instances implicitly refer to the 'default_fs_rw'.
*/
if (_storage._sculpt_partition.valid()) {
if (_storage._selected_target.valid()) {
auto chroot = [&] (Start_name const &name, Path const &path, Writeable w) {
xml.node("start", [&] {
@ -2405,7 +2415,7 @@ void Sculpt::Main::_generate_runtime_config(Xml_generator &xml) const
}
/* execute file operations */
if (_storage._sculpt_partition.valid())
if (_storage._selected_target.valid())
if (_file_operation_queue.any_operation_in_progress())
xml.node("start", [&] {
gen_fs_tool_start_content(xml, _fs_tool_version,
@ -2417,7 +2427,7 @@ void Sculpt::Main::_generate_runtime_config(Xml_generator &xml) const
xml.node("start", [&] {
gen_update_start_content(xml); });
if (_storage._sculpt_partition.valid() && !_prepare_in_progress()) {
if (_storage._selected_target.valid() && !_prepare_in_progress()) {
xml.node("start", [&] {
gen_launcher_query_start_content(xml); });

View File

@ -98,7 +98,7 @@ void Graph::_view_selected_node_content(Scope<Depgraph, Frame, Vbox> &s,
}
if (name == "ram_fs")
s.widget(_ram_fs_widget, _sculpt_partition, _ram_fs_state);
s.widget(_ram_fs_widget, _selected_target, _ram_fs_state);
String<100> const
ram (Capacity{info.assigned_ram - info.avail_ram}, " / ",
@ -130,7 +130,7 @@ void Graph::_view_selected_node_content(Scope<Depgraph, Frame, Vbox> &s,
void Graph::view(Scope<Depgraph> &s) const
{
if (Feature::PRESENT_PLUS_MENU && _sculpt_partition.valid())
if (Feature::PRESENT_PLUS_MENU && _selected_target.valid())
s.widget(_plus, _popup_state == Popup::VISIBLE);
/* parent roles */
@ -185,7 +185,7 @@ void Graph::view(Scope<Depgraph> &s) const
Dialog::Id primary_dep = Id { component.primary_dependency };
if (primary_dep.value == "default_fs_rw")
primary_dep = Dialog::Id { _sculpt_partition.fs() };
primary_dep = Dialog::Id { _selected_target.fs() };
/* primary dependency is another component */
_runtime_config.with_graph_id(primary_dep,
@ -222,7 +222,7 @@ void Graph::view(Scope<Depgraph> &s) const
return;
if (dep_name == "default_fs_rw")
dep_name = _sculpt_partition.fs();
dep_name = _selected_target.fs();
Dialog::Id dep_id { dep_name };
@ -264,7 +264,7 @@ void Graph::click(Clicked_at const &at, Action &action)
action.open_popup_dialog(popup_anchor(at._location));
});
_ram_fs_widget .propagate(at, _sculpt_partition, action);
_ram_fs_widget .propagate(at, _selected_target, action);
_ahci_devices_widget.propagate(at, action);
_nvme_devices_widget.propagate(at, action);
_mmc_devices_widget .propagate(at, action);

View File

@ -40,7 +40,7 @@ struct Sculpt::Graph : Widget<Depgraph>
Runtime_state &_runtime_state;
Runtime_config const &_runtime_config;
Storage_devices const &_storage_devices;
Storage_target const &_sculpt_partition;
Storage_target const &_selected_target;
Ram_fs_state const &_ram_fs_state;
Popup::State const &_popup_state;
Depot_deploy::Children const &_deploy_children;
@ -56,19 +56,19 @@ struct Sculpt::Graph : Widget<Depgraph>
Hosted<Depgraph, Frame, Vbox, Frame, Ahci_devices_widget>
_ahci_devices_widget { Id { "ahci_devices" },
_storage_devices, _sculpt_partition };
_storage_devices, _selected_target };
Hosted<Depgraph, Frame, Vbox, Frame, Nvme_devices_widget>
_nvme_devices_widget { Id { "nvme_devices" },
_storage_devices, _sculpt_partition };
_storage_devices, _selected_target };
Hosted<Depgraph, Frame, Vbox, Frame, Mmc_devices_widget>
_mmc_devices_widget { Id { "mmc_devices" },
_storage_devices, _sculpt_partition };
_storage_devices, _selected_target };
Hosted<Depgraph, Frame, Vbox, Frame, Usb_devices_widget>
_usb_devices_widget { Id { "usb_devices" },
_storage_devices, _sculpt_partition };
_storage_devices, _selected_target };
bool _storage_selected = false;
@ -79,13 +79,13 @@ struct Sculpt::Graph : Widget<Depgraph>
Graph(Runtime_state &runtime_state,
Runtime_config const &runtime_config,
Storage_devices const &storage_devices,
Storage_target const &sculpt_partition,
Storage_target const &selected_target,
Ram_fs_state const &ram_fs_state,
Popup::State const &popup_state,
Depot_deploy::Children const &deploy_children)
:
_runtime_state(runtime_state), _runtime_config(runtime_config),
_storage_devices(storage_devices), _sculpt_partition(sculpt_partition),
_storage_devices(storage_devices), _selected_target(selected_target),
_ram_fs_state(ram_fs_state), _popup_state(popup_state),
_deploy_children(deploy_children)
{ }

View File

@ -119,6 +119,13 @@ struct Sculpt::Main : Input_event_handler,
void _handle_gui_mode();
Rom_handler<Main> _config { _env, "config", *this, &Main::_handle_config };
void _handle_config(Xml_node const &)
{
_handle_storage_devices();
}
Screensaver _screensaver { _env, *this };
/**
@ -249,20 +256,22 @@ struct Sculpt::Main : Input_event_handler,
void _handle_storage_devices()
{
Storage_target const orig_sculpt_partition = _storage._sculpt_partition;
Storage_target const orig_target = _storage._selected_target;
bool total_progress = false;
for (bool progress = true; progress; total_progress |= progress) {
progress = false;
_drivers.with_storage_devices([&] (Drivers::Storage_devices const &devices) {
progress = _storage.update(devices.usb, devices.ahci,
devices.nvme, devices.mmc).progress; });
_config.with_xml([&] (Xml_node const &config) {
progress = _storage.update(config,
devices.usb, devices.ahci,
devices.nvme, devices.mmc).progress; }); });
/* update USB policies for storage devices */
_drivers.update_usb();
}
if (orig_sculpt_partition != _storage._sculpt_partition)
if (orig_target != _storage._selected_target)
_restart_from_storage_target();
if (total_progress) {
@ -381,7 +390,7 @@ struct Sculpt::Main : Input_event_handler,
*/
bool _update_running() const
{
return _storage._sculpt_partition.valid()
return _storage._selected_target.valid()
&& !_prepare_in_progress()
&& _network.ready()
&& _deploy.update_needed();
@ -603,7 +612,7 @@ struct Sculpt::Main : Input_event_handler,
bool system_available() const override
{
return _storage._sculpt_partition.valid() && !_prepare_in_progress();
return _storage._selected_target.valid() && !_prepare_in_progress();
}
struct Diag_dialog : Top_level_dialog
@ -669,7 +678,7 @@ struct Sculpt::Main : Input_event_handler,
void _handle_runtime_state(Xml_node const &);
Runtime_state _runtime_state { _heap, _storage._sculpt_partition };
Runtime_state _runtime_state { _heap, _storage._selected_target };
Managed_config<Main> _runtime_config {
_env, "config", "runtime", *this, &Main::_handle_runtime };
@ -801,10 +810,15 @@ struct Sculpt::Main : Input_event_handler,
void use(Storage_target const &target) override
{
_storage._sculpt_partition = target;
Storage_target const orig_target = _storage._selected_target;
_storage._selected_target = target;
_system_dialog.reset_update_widget();
_download_queue.reset();
if (orig_target != _storage._selected_target)
_restart_from_storage_target();
/* hide system panel button and system dialog when "un-using" */
_panel_dialog.refresh();
_system_dialog.refresh();
@ -1394,7 +1408,7 @@ struct Sculpt::Main : Input_event_handler,
Popup _popup { };
Graph _graph { _runtime_state, _cached_runtime_config, _storage._storage_devices,
_storage._sculpt_partition, _storage._ram_fs_state,
_storage._selected_target, _storage._ram_fs_state,
_popup.state, _deploy._children };
struct Graph_dialog : Dialog::Top_level_dialog
@ -2049,7 +2063,7 @@ void Sculpt::Main::_generate_runtime_config(Xml_generator &xml) const
/*
* Load configuration and update depot config on the sculpt partition
*/
if (_storage._sculpt_partition.valid() && _prepare_in_progress())
if (_storage._selected_target.valid() && _prepare_in_progress())
xml.node("start", [&] {
gen_prepare_start_content(xml, _prepare_version); });
@ -2061,7 +2075,7 @@ void Sculpt::Main::_generate_runtime_config(Xml_generator &xml) const
* Spawn chroot instances for accessing '/depot' and '/public'. The
* chroot instances implicitly refer to the 'default_fs_rw'.
*/
if (_storage._sculpt_partition.valid()) {
if (_storage._selected_target.valid()) {
auto chroot = [&] (Start_name const &name, Path const &path, Writeable w) {
xml.node("start", [&] {
@ -2076,7 +2090,7 @@ void Sculpt::Main::_generate_runtime_config(Xml_generator &xml) const
}
/* execute file operations */
if (_storage._sculpt_partition.valid())
if (_storage._selected_target.valid())
if (_file_operation_queue.any_operation_in_progress())
xml.node("start", [&] {
gen_fs_tool_start_content(xml, _fs_tool_version,
@ -2088,7 +2102,7 @@ void Sculpt::Main::_generate_runtime_config(Xml_generator &xml) const
xml.node("start", [&] {
gen_update_start_content(xml); });
if (_storage._sculpt_partition.valid() && !_prepare_in_progress()) {
if (_storage._selected_target.valid() && !_prepare_in_progress()) {
xml.node("start", [&] {
gen_launcher_query_start_content(xml); });

View File

@ -28,6 +28,15 @@ struct Sculpt::Storage_target
Storage_device::Port port;
Partition::Number partition;
static Storage_target from_xml(Xml_node const &target)
{
return {
.driver = target.attribute_value("driver", Storage_device::Driver()),
.port = target.attribute_value("port", Storage_device::Port()),
.partition = target.attribute_value("partition", Partition::Number())
};
}
bool operator == (Storage_target const &other) const
{
return (driver == other.driver)

View File

@ -17,7 +17,8 @@
using namespace Sculpt;
Progress Storage::update(Xml_node const &usb, Xml_node const &ahci,
Progress Storage::update(Xml_node const &config,
Xml_node const &usb, Xml_node const &ahci,
Xml_node const &nvme, Xml_node const &mmc)
{
bool progress = false;
@ -36,49 +37,67 @@ Progress Storage::update(Xml_node const &usb, Xml_node const &ahci,
_storage_devices.usb_storage_devices.for_each([&] (Usb_storage_device &dev) {
dev.process_report(); });
if (!_sculpt_partition.valid()) {
Storage_target const orig_selected_target = _selected_target;
Storage_target const orig_configured_target = _configured_target;
config.with_sub_node("target",
[&] (Xml_node const &target) {
Storage_target const configured_target = Storage_target::from_xml(target);
if (configured_target != _configured_target) {
_configured_target = configured_target;
_malconfiguration = false; } },
[&] { _configured_target = { }; });
if (orig_configured_target != _configured_target)
_selected_target = { };
if (!_selected_target.valid()) {
bool const all_devices_enumerated = !usb .has_type("empty")
&& !ahci.has_type("empty")
&& !nvme.has_type("empty")
&& !mmc .has_type("empty");
if (all_devices_enumerated) {
Storage_target const default_target =
_discovery_state.detect_default_target(_storage_devices);
if (default_target.valid()) {
_sculpt_partition = default_target;
progress |= true;
}
if (_configured_target.valid())
_selected_target = _malconfiguration ? Storage_target { } : _configured_target;
else
_selected_target = _discovery_state.detect_default_target(_storage_devices);
}
}
/*
* Detect the removal of a USB stick that is currently in "use". Reset
* the '_sculpt_partition' to enable the selection of another storage
* the '_selected_target' to enable the selection of another storage
* target to use.
*/
else if (_sculpt_partition.valid()) {
else if (_selected_target.valid()) {
bool sculpt_partition_exists = false;
bool selected_target_exists = false;
if (_sculpt_partition.ram_fs())
sculpt_partition_exists = true;
if (_selected_target.ram_fs())
selected_target_exists = true;
_storage_devices.for_each([&] (Storage_device const &device) {
device.for_each_partition([&] (Partition const &partition) {
if (device.driver == _sculpt_partition.driver
&& partition.number == _sculpt_partition.partition)
sculpt_partition_exists = true; }); });
if (device.driver == _selected_target.driver
&& partition.number == _selected_target.partition)
selected_target_exists = true; }); });
if (!sculpt_partition_exists) {
warning("sculpt partition unexpectedly vanished");
_sculpt_partition = Storage_target { };
progress |= true;
if (!selected_target_exists) {
if (_configured_target.valid()) {
warning("configured storage target does not exist");
_malconfiguration = true;
} else {
warning("selected storage target unexpectedly vanished");
}
_selected_target = { };
}
}
progress |= (orig_selected_target != _selected_target);
return { progress };
}
@ -90,11 +109,11 @@ void Storage::gen_runtime_start_nodes(Xml_generator &xml) const
auto contains_used_fs = [&] (Storage_device const &device)
{
if (!_sculpt_partition.valid())
if (!_selected_target.valid())
return false;
return (device.port == _sculpt_partition.port)
&& (device.driver == _sculpt_partition.driver);
return (device.port == _selected_target.port)
&& (device.driver == _selected_target.driver);
};
_storage_devices.usb_storage_devices.for_each([&] (Usb_storage_device const &device) {
@ -135,7 +154,7 @@ void Storage::gen_runtime_start_nodes(Xml_generator &xml) const
gen_resize2fs_start_content(xml, target); }); }
if (partition.file_system.type != File_system::UNKNOWN) {
if (partition.file_system.inspected || target == _sculpt_partition)
if (partition.file_system.inspected || target == _selected_target)
xml.node("start", [&] {
gen_fs_start_content(xml, target, partition.file_system.type); });
@ -144,7 +163,7 @@ void Storage::gen_runtime_start_nodes(Xml_generator &xml) const
* to as "default_fs_rw" without the need to know the name of the
* underlying storage target.
*/
if (target == _sculpt_partition)
if (target == _selected_target)
gen_named_node(xml, "alias", "default_fs_rw", [&] {
xml.attribute("child", target.fs()); });
}
@ -163,7 +182,7 @@ void Storage::gen_runtime_start_nodes(Xml_generator &xml) const
}); /* for each device */
if (_sculpt_partition.ram_fs())
if (_selected_target.ram_fs())
gen_named_node(xml, "alias", "default_fs_rw", [&] {
xml.attribute("child", "ram_fs"); });
}

View File

@ -31,13 +31,17 @@ struct Sculpt::Storage : Noncopyable
Ram_fs_state _ram_fs_state;
Storage_target _sculpt_partition { };
Storage_target _configured_target { },
_selected_target { };
bool _malconfiguration = false;
Discovery_state _discovery_state { };
Inspect_view_version _inspect_view_version { 0 };
Progress update(Xml_node const &usb_devices, Xml_node const &ahci_ports,
Progress update(Xml_node const &config,
Xml_node const &usb_devices, Xml_node const &ahci_ports,
Xml_node const &nvme_namespaces, Xml_node const &mmc_devices);
/*