/*
* Copyright 2023-2024, ttcoder
* All rights reserved. Distributed under the terms of the MIT license.
*/
// libc.lib.so
#include <stdio.h>
// haiku.lib.so
#include <storage/Entry.h>
#include <storage/Path.h>
#include <support/StringList.h>
// this
#include "_Ge_InitPilot.h"
/******/
extern Genode::Env & HogEnv();
/**************/
static int debug = 2; // set _2_ to list children, set _3_ to dump their config XML
///FIXME-2: listen to init reports:
// use Attached_rom_dataspace... with "polling" in MsgRcv() for now ? and use sigh(andler) later, but in a separate thread of course (separate Genode native signals)
/*
test/init/main.cc:
Attached_rom_dataspace _init_state { _env, "state" };
Then look for attribute "exited=.." (with a status code) :
<child name="/apps/Pulse" binary="/apps/Pulse" version="1" exited="0">
*/
///ToDo-2: nano3d etc are NOT displayed in Deskbar : handle display (and quitting) of Genode apps (nano3d, vnc_server, ftpd...), hybrid-style, by creating special menu items from the Dynit report ...?
struct hog::Pilot::Dynit_Child
{
public: // Code
/*
Dynit_Child()
: argStrings()
,overrideConfigXml()
{
}
*/
public: // Data
BStringList argStrings;
BString overrideConfigXml;
int32 ramQuotaMb;
//BStringList environs...
//int launch_iteration; // aka "version"
};
//#pragma mark - base class
hog::Pilot::Pilot()
: childrenParams()
{
}
hog::Pilot::~Pilot()
{
for( auto it = childrenParams.begin(); it != childrenParams.end(); it++ )
delete it->second;
;
childrenParams.clear();
}
bool hog::Pilot::Toggle( const BStringList & arg_list )
{
if( debug )
printf( "Pilot.Toggle <%s>\n", arg_list.First().String() );
if( arg_list.CountStrings() <= 0 || arg_list.First().IsEmpty() )
return false;//B_BAD_DATA;
//if( access(argv[0], 0777) == failure )
// return false;//B_NO_SUCH_FILE;
// T0 : this.state
auto existing = childrenParams.find( arg_list.First()/**/ );
if( existing == childrenParams.end() )
{
// insert
childrenParams[ arg_list.First()/**/ ] = new Dynit_Child { arg_list, "", 0 };
}
else
{
// remove
delete existing->second;
childrenParams.erase( existing );
}
// T+ : sync Init from this.state
genAndSendConf();
return true;///
}
bool hog::Pilot::Toggle_Xml( BString exe_path, BString xml_config, int32 ram_quota_MB )
{
if( debug >= 1 )
printf( "Pilot.Toggle_Xml <%s> %d MB [%s]\n", exe_path.String(), ram_quota_MB, xml_config.String() );
if( exe_path.IsEmpty() )
return false;
auto existing = childrenParams.find( exe_path );
if( existing == childrenParams.end() )
{
BStringList arg_list;
arg_list.Add( exe_path );
childrenParams[ exe_path ] = new Dynit_Child { arg_list, xml_config, ram_quota_MB };
}
else
{
delete existing->second;
childrenParams.erase( existing );
}
genAndSendConf();
return true;
}
//#pragma mark - subclass for non-packaged (Be or Ge) apps
hog::InitPilot::InitPilot()
: Pilot()
//done, setting 128 KB now... Resolved? //FIXME-2: pass a value to fourth arg (instead of letting it default to default val), pass a 'big' value:
,dynTx( HogEnv(), "config", "dynit_config", 128*1024 ) // using "dynit_config" instead of "config" for clarity, so set "label=dynit_config":
{
#if 0
use an ::Expanding_reporter !
..
Genode::Reporter::Xml_generator xml( dyn, [&] ()
{
xml.attribute( "start", "top" );
etc
});
*/
#endif
}
BString hog::InitPilot::genHeader()
{
BString s;
s
<< "<config verbose=\"yes\" prio_levels=\"4\">\n"
// (see: genode.org/documentation/genode-foundations/23.05/system_configuration/The_init_component.html#State_reporting )
///FIXME-2: reverted to "NO" requested, otherwise I get errors: "[dynit] Error: state report exceeds maximum size" .. maybe here too it needs an "expanding reporter" ?
<< " <report requested=\"no\"/> <!-- report app exits/crashes to dynit_report_rom -->\n"
<< " <!-- These should be understood as 'surroundings-provide', or 'siblings-provide'... -->\n"
<< " <parent-provides>\n"
<< " <service name=\"PD\"/>\n"
<< " <service name=\"RM\"/>\n" // necessary for lxip.lib.so
<< " <service name=\"Nic\"/>\n" // necessary for lxip.lib.so
<< " <service name=\"CPU\"/>\n"
<< " <service name=\"LOG\"/>\n"
<< " <service name=\"ROM\"/>\n"
<< " <service name=\"Timer\"/>\n"
<< " <service name=\"Rtc\"/>\n"
<< " <service name=\"TRACE\"/> <!-- for 'top' -->\n"
<< " <service name=\"Report\"/>\n" // for GeDepot to report "installation"
<< " <service name=\"Block\"/>\n" // for part_block (launched by mount_daemon/registrar)
<< " <service name=\"File_system\"/>\n"
<< " <service name=\"Hai_Broker\"/>\n"
<< " <service name=\"Gui\"/>\n"
<< " <service name=\"Audio_out\"/>\n"
<< " <service name=\"Play\"/>\n"
<< " </parent-provides>\n"
<< " <default-route>\n"
// Careful with "blanket" routing, must not mess up basic ROM access
// otherwise that prevents routing to even "ld.lib.so", the very
// first needed library, which triggers a NULL-ptr crash in dyn/init's spawn...
// [init -> dyn] Error: binary is not an ELF
// no RM attachment (READ pf_addr=0x0 pf_ip=0x0 from pager_object: pd='init -> dyn -> top' thread='top')
// Anyway here in the "leaf" configuration node, we just forward to (parent) dyn/init,
// and *it* will take care to route e.g. "libc.lib.so" to normal ROM routing,
// route e.g. "Mandelbrot" app routing to fs_rom, and route everything else as usual.
<< " <any-service> <parent/> </any-service>\n"
<< " </default-route>\n"
// << " <default caps=\"300\"/>\n" // AutoCast needs more than 300...:
<< " <default caps=\"500\"/>\n"
;
return s;
}
void hog::InitPilot::genAndSendConf()
{
// T-
dynTx.enabled( true );
dynTx.clear();
BString s;
// header
s += genHeader();
// children
for( auto it = childrenParams.begin(); it != childrenParams.end(); it++ )
{
const auto item = it->second;
s += genOneChildConf( item->argStrings, item->overrideConfigXml, item->ramQuotaMb );
if( debug >= 2 )
printf(" gen child <%s>\n", it->first.String() );
}
// footer
s += "</config>\n";
if( debug >= 3 )
printf( "InitPilot.genConf:\n%s\n", s.String() );
// T+ : Tx to Genode 'init'
dynTx.report( s, s.Length() );
}
BString hog::InitPilot::genOneChildConf( const BStringList & arg_list, const BString & xml_conf, int ram_quota_megs )
{
// Handle *one* sub-process' configuration.
///ToDo-2: stripping /boot prefix: does this make sense ? The reasoning is, fs_rom forwards the path as-is to $vfs_server_name (e.g. vfs_ram), so e.g. it requests "/boot/bin/top" to it, WITHOUT STRIPPING the "/boot" prefix... So we need to remove the prefix here (?), this gets us going with the "nano3d" and "top" examples, even WITH the /boot prefix.
BString stripped_arg_zero;
stripped_arg_zero = arg_list.StringAt( 0 );
if( stripped_arg_zero.StartsWith("/boot") )
{
stripped_arg_zero.RemoveFirst( "/boot" );
}
else
printf( "*** warning: initpilot.genOneChild(): argv does not start with /boot prefix: <%s>\n", stripped_arg_zero.String() );
const int ver = 1;//xx delete 'version' stuff? We only handle quit-then-restart, not in-situ restarting, so no use for this it seems
int priority = -1;
int ram_megs = ram_quota_megs > 0 ? ram_quota_megs : 128; // default to 128 MB RAM quota
BString s;
s
<< " <start name=\"" << "dyn/" << BPath(stripped_arg_zero).Leaf() << "\" version=\"" << ver << "\" priority=\"" << priority << "\" >\n" // -1 or -2 priority helps much for "gears OpenGL", but also with MMD..?
// eg "dyn/Pulse" instead of "/system/apps/Pulse" for log readability
<< " <resource name=\"RAM\" quantum=\"" << ram_megs << "M\"/>\n"
<< " <binary name=\"" << stripped_arg_zero << "\"/>\n"
;
///later: get rid of the second clause? or still needed for running nano3d directly (sans depot_deploy) ?
if( stripped_arg_zero.StartsWith("/apps/genode") || stripped_arg_zero.StartsWith("/depot") )
{
// Genode apps (nano3d etc) should route "Gui" to WM:
// re-label Gui (to use MOTIF window manager Gui instead of raw nitpicker Gui)
s
<< " <route>\n"
<< " <service name=\"Gui\"> <parent label=\"ge_wm_gui\"/> </service>\n" // dynit parent knows how to route "ge_wm_gui"
;
///hack: support PartitionsSetupGPT's use of mkntfs (it should look for Blocks in part_block, not call up to ahci_drv):
if( stripped_arg_zero.EndsWith("/mkntfs") )
{
s << " <any-service> <child name=\"dyn/part_block\"/> <parent/> </any-service>\n" ;
}
else
{
s << " <any-service> <parent/> </any-service>\n" ;
}
s
<< " </route>\n"
;
///ToDo: the label "ge_wm_gui" ends up in the window title bar, in stead of eg "nano3d" title, how come ?
}
if( xml_conf.Length() )
{
s += xml_conf;
s += "\n";
// Context: there are Genode apps like e.g.
// - gpt_write
// - hog_ftpd
// that require a config-style param instead of
// argv/argc-style params, and yet they cannot be launched in the "depot_deploy" init
// as neither their binary nor their runtime file are hosted in the filesystem, so
// the runtime/config must be passed inline, and the binary loaded with
// "start name=foo" instead of "start pkg=foo".
// For those, one may call this gen() to spawn them in this init (instead of
// the depot_deploy init).
}
else // default to a "Be-like" <config> node:
{
s
<< " <config ld_verbose=\"yes\">\n"
<< " <libc socket=\"/socket\" rtc=\"/dev/rtc\" stdin=\"/dev/null\" stdout=\"/dev/log\" stderr=\"/dev/log\">\n"
<< " <pthread placement=\"all-cpus\" verbose=\"no\" />\n"
<< " </libc>\n"
<< " <env key=\"_\" value=\"" << arg_list.First() << "\"/> \n" // e.g. "/boot/apps/Pulse"
;
for( int i = 0 ; i < arg_list.CountStrings(); i++ )
{
// e.g. "/boot/apps/Pulse"
s << " <arg value=\"" << arg_list.StringAt(i) << "\"/>\n" ;
}
s
<< " <vfs>\n"
<< " <dir name=\"dev\">\n"
<< " <rtc/>\n"
<< " <log/>\n"
<< " <null/>\n"
<< " <fs label=\"downstream\"/>\n" // access "/dev/bro2app" (broker-to-app traffic)
#if 0 // enable this if using vfs_oss.lib.so OSS audio instead of 'native' audio output:
<< " <oss name=\"dsp\"/>/n"
#endif
<< " </dir>\n"
<< " <dir name=\"boot\">\n"
<< " <fs label=\"part_boot\"/>\n" // access "/boot"
<< " </dir>\n"
// << " <dir name=\"boot2\">\n"
// << " <fs label=\"part_boot2\"/>\n" // access "/boot2"
// << " </dir>\n"
;
if( stripped_arg_zero.FindFirst("FtpPositive") >= 0 ) ///ToDo-2: hackish...
{
s
<< " <dir name=\"socket\">\n"
<< " <lwip dhcp=\"yes\"/> <!-- Get an IP assigned by nic_router -->\n"
<< " </dir>\n"
;
}
s
<< " </vfs>\n"
<< " </config>\n"
;
}
s
<< " </start>\n"
;
return s;
}
//#pragma mark - subclass for depot-packaged (Genode) apps
#include "_Ge_DeployPilot.h"
hog::DeployPilot::DeployPilot()
: Pilot()
,depotDeployTx( HogEnv(), "config", "depotdeploy.config" ) // using "depotdeploy.config" instead of "config" for clarity, so set "label=depotdeploy.config":
{
}
void hog::DeployPilot::genAndSendConf()
{
// T-
//printf(" depotdeployt-report-Tx\n");///we may remain blocked on this call if the target component fails to start (for lack of RAM quota etc)
depotDeployTx.enabled( true );
depotDeployTx.clear();
BString s;
// header
s
<< " <config arch=\"x86_64\" prio_levels=\"4\">\n"
<< " <report state=\"yes\"/>\n" ///
<< " <static>\n"
<< " <parent-provides>\n"
<< " <service name=\"ROM\"/>\n"
<< " <service name=\"Report\"/>\n" // for clipboard etc
<< " <service name=\"CPU\"/>\n"
<< " <service name=\"PD\"/>\n"
<< " <service name=\"RM\"/>\n" // for mesa_gpu-cpu
<< " <service name=\"LOG\"/>\n"
<< " <service name=\"Timer\"/>\n"
<< " <service name=\"Gui\"/>\n"
<< " <service name=\"Rtc\"/>\n" // for Falkon (https certificates)
<< " <service name=\"Nic\"/>\n" // for Falkon etc
<< " <service name=\"Audio_out\"/>\n" // for media etc
<< " <service name=\"Audio_in\"/>\n"
<< " <service name=\"Play\"/>\n" // for media etc
<< " <service name=\"Record\"/>\n"
<< " <service name=\"File_system\"/>\n"
<< " <service name=\"Capture\"/>\n" // for VNC server
<< " <service name=\"Event\"/>\n" // for VNC server
<< " </parent-provides>\n"
<< " </static>\n"
<< " <common_routes>\n"
// <!-- the exe and libs (except ldso) are *not* in this hand-crafted section here, they -->
// <!-- appear later in the depot_deploy crafted section, with full paths, retrieved via cached_fs_rom -->
<< " <service name=\"ROM\" label_last=\"ld.lib.so\"> <parent/> </service>\n"
<< " <service name=\"ROM\" label_last=\"init\"> <parent/> </service>\n"
//Wrong routing! This gets routed to cached_fs_rom (??), and makes it crash on "invalid file handle":
// << " <service name=\"ROM\" label_last=\"clipboard\"> <parent/> </service>\n"
// << " <service name=\"Report\" label_last=\"clipboard\"> <parent/> </service>\n"
<< " <service name=\"Report\" label_last=\"shape\"> <parent/> </service>\n"
<< " <service name=\"CPU\"> <parent/> </service>\n"
<< " <service name=\"PD\"> <parent/> </service>\n"
<< " <service name=\"RM\"> <parent/> </service>\n" // for mesa_gpu-cpu
<< " <service name=\"LOG\"> <parent/> </service>\n"
<< " <service name=\"Timer\"> <parent/> </service>\n"
<< " <service name=\"Gui\"> <parent/> </service>\n"
<< " <service name=\"Rtc\"> <parent/> </service>\n" // for Falkon (https certificates)
<< " <service name=\"Nic\"> <parent/> </service>\n" // for Falkon etc
<< " <service name=\"Audio_out\"> <parent/> </service>\n" // for media etc
<< " <service name=\"Audio_in\"> <parent/> </service>\n"
<< " <service name=\"Play\"> <parent/> </service>\n" // for media etc
<< " <service name=\"Record\"> <parent/> </service>\n"
///later: ram_fs for Falkon etc: this should not be in "common" routes, should either be user-configurable (à la Sculpt), or have a "sub runtime" of some sort
///later: Capture should not be in "common" routes (thus enabled for Falkon et al), just hardcode it for vnc_server...
<< " <service name=\"File_system\" label=\"config\"> <parent label=\"ram_fs\"/> </service> <!-- For Falkon --> \n"
<< " <service name=\"File_system\" label=\"downloads\"> <parent label=\"ram_fs\"/> </service> <!-- For Falkon --> \n"
<< " <service name=\"File_system\" label=\"rw\"> <parent label=\"ram_fs\"/> </service> <!-- For qt5_TextEdit --> \n"
<< " <service name=\"Capture\"> <parent/> </service>\n" // for VNC server
<< " <service name=\"Event\"> <parent/> </service>\n" // for VNC server
<< " </common_routes>\n"
;
// child #1 (hardcoded)
//
// Mesa is required for Qt apps, and aliased either to software pipe (mesa_gpu-softpipe.lib.so) or Intel GPU pipe (mesa_gpu_drv.lib.so). We use the software pipe for now.
// -> first child (required by qt5 apps, so might as well enable it always)
// (will only work if downloaded from GeDepot though)
//
BString hardcoded_child_mesa;
if( false==childrenParams.empty() )
{
const char mesa_pkg[] = "genodelabs/pkg/mesa_gpu-cpu/2023-04-25";
hardcoded_child_mesa
<< " <start name=\"" << "depot:" << "mesa_gpu-cpu" << "\" pkg=\"" << mesa_pkg << "\" priority=\"" << 0 << "\" />\n"
;
}
s += hardcoded_child_mesa;
// children #2..n (dynamic)
//
for( auto it = childrenParams.begin(); it != childrenParams.end(); it++ )
{
///later: <ramQuotaMb> is currently *not* passed to gen(). The pkg runtime does specify a RAM quota... But it also provides for overriding(?) that quota, and we could take advantage of that by applying the client-provided <ramQuotaMb> ?
BString child = genOneChildConf( it->second->argStrings, it->second->overrideConfigXml );
s += child;
if( debug >= 2 )
printf(" gen child <%s>\n", it->first.String() );
}
// footer
s << "</config>\n";
if( debug >= 3 )
printf( "DepotDeploy.genDeployConf:\n%s\n", s.String() );
// T+ : Tx to 'init'
depotDeployTx.report( s, s.Length() );
}
BString hog::DeployPilot::genOneChildConf( const BStringList & arg_list, const BString & xml_conf )
{
BString s;
int priority = -2; // We need at least -1 (-2 is better) : think e.g. CPU-hungry GL-Gears in DeployPilot, versus AC in InitPilot...
BString stripped_arg0;
stripped_arg0 = arg_list.StringAt( 0 ); // e.g. "/boot/depot/genodelabs/pkg/nano3d/2023-11-24/runtime"
if( stripped_arg0.StartsWith("/boot/depot/") )
stripped_arg0.RemoveFirst( "/boot/depot/" ); // e.g. "genodelabs/pkg/nano3d/2023-11-24/runtime"
else
printf( "*** warning: deploypilot.genOneChildConf(): argv does not start with /boot/depot prefix: <%s>\n", stripped_arg0.String() );
if( stripped_arg0.EndsWith("/runtime") )
stripped_arg0.RemoveLast( "/runtime" ); // e.g. "genodelabs/pkg/nano3d/2023-11-24"
else
printf("*** warning: deploy: argument is not a 'runtime': <%s>\n", stripped_arg0.String());
BString appname;
{
BStringList tokens;
stripped_arg0.Split( "/", true, tokens );
appname = tokens.StringAt( 2 ); // e.g. genodelabs/pkg/[ nano3d ]/2023-11-24
}
s
<< " <start name=\"" << "depot:" << appname << "\" pkg=\"" << stripped_arg0 << "\" priority=\"" << priority << "\" >\n" // -1 or -2 priority helps much for "gears OpenGL", but not only...
///later: make this <route> conditional, with "if runtime explicitely requires this...".. Of course not on 'xml_conf', which holds the <config> node instead of the <route> node, but with a similar param ?
<< " <route>\n"
<< " <service name=\"ROM\" label_last=\"mesa_gpu_drv.lib.so\"> <any-child/> </service>\n"
<< " </route>\n"
<< " </start>\n"
;
return s;
}