Site logo
Stories around the Genode Operating System RSS feed
Norman Feske avatar

Goa - sticking together a little Unix (part 2)


Let us transcend the simplistic bash scenario created in part 1 and enter the stage of an interactive system.

Some reorg is in order

The scenario we built in the first part was quite small. For such small scenarios, defining the <config> node right in the runtime file is quite handy. Once the subsystem becomes bigger, however, its better to move the <config> into a dedicated ROM module. Let us create a new directory named raw/ inside the project directory, and move the <config> node from the runtime file to a new file raw/unix.config. Goa will pick up all files contained in the raw/ directory and supply them as ROM modules to the Genode scenario.

Since there is no longer a <config> provided in the runtime file, we tell the runtime to use the "unix.config" as configuration by changing the <runtime> node as follows:

 <runtime ram="100M" caps="5000" binary="init" config="unix.config">

Since unix.config is expected to be present as a ROM module, we have to declare via a <rom> node in the runtime file.

For reference, the pkg/unix/runtime file should now look as follows:

 <runtime ram="100M" caps="5000" binary="init" config="unix.config">

   <content>
     <rom label="init"/>
     <rom label="bash.tar"/>
     <rom label="vfs"/>
     <rom label="vfs.lib.so"/>
     <rom label="fs_rom"/>
     <rom label="libc.lib.so"/>
     <rom label="libm.lib.so"/>
     <rom label="posix.lib.so"/>
     <rom label="unix.config"/>
   </content>

 </runtime>

The raw/unix.config file:

 <config verbose="yes">

   <parent-provides>
     <service name="ROM"/>
     <service name="LOG"/>
     <service name="RM"/>
     <service name="CPU"/>
     <service name="PD"/>
     <service name="Timer"/>
   </parent-provides>

   <start name="vfs" caps="100">
     <resource name="RAM" quantum="10M"/>
     <provides> <service name="File_system"/> </provides>
     <config>
       <vfs> <tar name="bash.tar"/> </vfs>
       <default-policy root="/" />
     </config>
     <route> <any-service> <parent/> </any-service> </route>
   </start>

   <start name="vfs_rom" caps="100">
     <resource name="RAM" quantum="10M"/>
     <binary name="fs_rom"/>
     <provides> <service name="ROM"/> </provides>
     <config/>
     <route>
       <service name="File_system"> <child name="vfs"/> </service>
       <any-service> <parent/> </any-service>
     </route>
   </start>

   <start name="/bin/bash" caps="1000">
     <resource name="RAM" quantum="10M" />
     <config>
       <libc stdin="/dev/null" stdout="/dev/log" stderr="/dev/log"
             rtc="/dev/null"/>
       <vfs>
         <dir name="dev"> <null/> <log/> </dir>
       </vfs>
       <arg value="bash"/>
       <arg value="-c"/>
       <arg value="echo files at /dev: /dev/*"/>
     </config>
     <route>
       <service name="ROM" label_last="/bin/bash">
         <child name="vfs_rom"/> </service>
       <any-service> <parent/> </any-service>
     </route>
   </start>

 </config>

This reorganization has two advantages. First, we save one indentation level for the <config> node. Second, by separating the unix.config from the runtime in the form of a dedicated ROM module, we can later reuse the same ROM module for other runtime files. It is always good to have reusable building blocks.

Let's give the new version a try by issuing goa run. The output should look familiar to us.

GUI stack

Goa supports interactive system scenarios by looking at the requirements stated in the runtime file. Right now, the runtime file merely states the amount of RAM and caps as a requirement. We can add the presence of a GUI service as an additional requirement by adding the following node inside the <runtime> node:

 <requires>
   <gui/>
   <timer/>
 </requires>

When Goa processes the goa run command, it evaluates this information. The <gui> node tells Goa that the scenario will request a session to a GUI server. When running the scenario on Linux, Goa will automatically integrate the components needed for such a GUI server. This includes a pseudo graphics driver, a pseudo input driver, and the nitpicker GUI server.

Let's try goa run after having added the <requires> definition to our runtime. Goa responds with the following message:

 [unix] Error: runtime requires <gui/>,
               which is not mentioned in <parent-provides>

It points out the fact that the runtime file pretends to require a <gui> service but according to init configuration in unix.config no such service is actually obtained from he parent. So either the <requires> definition is superfluous or the init configuration is wrong or incomplete. So satisfy this sanity check, let's add the following line to the

<parent-provides> declarations in the raw/unix.config file.

 <parent-provides>
   ...
   <service name="Gui"/>
 </parent-provides>

Upon the next goa run, we can see that Goa automatically downloads the basic components of the GUI stack. Not only that. When starting the scenario, a new window with a greenish background pops up. When hovering the mouse over the window, you can see a small mouse pointer. If you are curious how the system the GUI stack is assembled in detail, please have a look at var/run/config. But from the perspective of our Unix scenario, these exact details are not of interest. The only important point is that our scenario is now officially able to request a "Gui" and a "Timer" service from the underlying system.

With these preconditions in place, we can start a graphical terminal in our unix.config by adding the following <start> node:

 <start name="terminal" caps="110">
   <resource name="RAM" quantum="10M"/>
   <provides> <service name="Terminal"/> </provides>
   <route>
     <service name="ROM" label="config">
       <parent label="terminal.config"/> </service>
     <any-service> <parent/> </any-service>
   </route>
 </start>

The "terminal" uses a GUI service to create a graphical terminal and provides the textual input and output in the form of a "Terminal" service. In the routing rules of the terminal, you can see that the terminal's configuration is fetched from a dedicated ROM module called "terminal.config". We have no such ROM module defined yet. However, let's still give it try:

 [init -> unix] Error: terminal: environment ROM session denied (label="terminal" ...)
 ...

That's not surprising as we have not added terminal to our archives nor have we stated the <rom> modules in the runtime file's <content>. Let's do this now. While we are at it, let's also add a <rom> node for the "terminal.config" ROM.

The following line must be added to pkg/unix/archives

 genodelabs/src/terminal

The following two lines must be added to the runtime file's <content>:

 <content>
   ...
   <rom label="terminal"/>
   <rom label="terminal.config"/>
 </content>

When trying goa run again, we see that we exchanged the previous errors with a new one. Let's call it progress:

 [unix] Error: Unable to find content ROM module 'terminal.config'.
  You either need to add it to the 'raw/' directory 
  or add the corresponding dependency to the 'archives' file.

The error is easy to explain. We have configured the "terminal" start node to fetch its configuration from a ROM called terminal.config but have not defined the ROM module so far. Let's add a new file raw/terminal.config with an empty <config> node:

 <config/>

With the file added, our next call of goa run is answered as follows.

 [init -> unix -> terminal] Error: Uncaught exception of type
                            'Genode::Xml_node::Nonexistent_sub_node'
 [init -> unix -> terminal] Warning: abort called - thread: ep

Well, the terminal seems underwhelmed by us serving an empty <config/> as configuration. It is time to become more specific. Let's change the content of the raw/terminal.config to something meaningful:

 <config>
   <vfs>
     <rom name="VeraMono.ttf"/>
     <dir name="fonts">
       <dir name="monospace">
         <ttf name="regular" path="/VeraMono.ttf" size_px="16"/>
       </dir>
     </dir>
   </vfs>
 </config>

Wait a minute. How is this a terminal configuration?

The terminal expects its font to be found at its local VFS at /fonts/monospace. The font has the form of a pseudo file system that provides the pixel data of the glyphs along with the font meta data as a bunch of pseudo files. So here, we mount a TrueType font with the ttf file-system driver at /fonts/monospace. The font file is specified as path attribute, which refers to "/VeraMono.ttf". This file, in turn, is backed by a <rom> session that requests the ROM module named "VeraMono.ttf".

With this configuration in place, the next attempt of goa run yields a quite predictable result:

 [init -> unix -> terminal] Error: could not open ROM session for "VeraMono.ttf"
 [init -> unix -> terminal] Error: failed to create <rom> VFS node
 [init -> unix -> terminal] Error:     name="VeraMono.ttf"
 [init -> unix -> terminal] Error: ROM-session creation failed (...)
 [init -> unix -> terminal] Error: could not open ROM session for "vfs_ttf.lib.so"
 [init -> unix -> terminal] Error: failed to create <ttf> VFS node
 [init -> unix -> terminal] Error:     name="regular"
 [init -> unix -> terminal] Error:     path="/VeraMono.ttf"
 [init -> unix -> terminal] Error:     size_px="16"

The terminal configuration refers to two ROM modules that we haven't yet included into the scenario. The "VeraMono.ttf" is the TrueType font data we tried to mount as <rom> node. The "vfs_ttf.lib.so" is the driver for the "ttf" pseudo file system. It is requested by the VFS when the <ttf> is encountered. The errors can be resolved by extending the archives file and the runtime file's <content> node accordingly.

The following lines must be added to pkg/unix/archives

 genodelabs/raw/ttf-bitstream-vera-minimal
 genodelabs/src/vfs_ttf

The following lines must be added to the <content> node in pkg/unix/runtime

 <content>
   ...
   <rom label="VeraMono.ttf"/>
   <rom label="vfs_ttf.lib.so"/>
 </content>

Good news! On the next try of goa run, we can see the error gone and are greeted with a black screen instead. The log output of /bin/bash looks as usual.

Connecting bash with the terminal

With the current scenario, bash and the GUI stack are running peacefully side by side but they do not interact with each other. To connect them, we will do the following:

  1. Mount a terminal session to the VFS of the VFS server at /dev/terminal.

    This can be done by changing the content of the <start> node of the VFS server. As a reminder, this is how it looks so far:

     <config>
       <vfs> <tar name="bash.tar"/> </vfs>
       <default-policy root="/" />
     </config>
     <route> <any-service> <parent/> </any-service> </route>
    

    We change it to the following:

     <config>
       <vfs>
         <tar name="bash.tar"/>
         <dir name="dev"> <terminal/> </dir>
       </vfs>
       <default-policy root="/" />
       <policy label_prefix="/bin/bash" root="/" writeable="yes" />
     </config>
     <route>
       <service name="Terminal"> <child name="terminal"/> </service>
       <any-service> <parent/> </any-service>
     </route>
    

    The <vfs> node gained the configuration of /dev/terminal. When the VFS encounters the <terminal> node upon initialization, it will request a session to a "Terminal" service. The added route tells init to route the terminal session towards the "terminal" component. The added <policy> node defines that a file-system client labeled as "/bin/bash" is allowed to access the entirety of the VFS in a writeable fashion.

  2. Mount the file system as provided by the VFS server into the VFS of the bash shell. This way, all files provided by the VFS server become visible in the file name space of bash. This can be done by extending the <vfs> of bash by adding an <fs/> node:

     <vfs>
       <dir name="dev"> <null/> <log/> </dir>
       <fs/>
     </vfs>
    

    When the VFS of bash encounters the <fs/> node, it will request a session to a "File_system" service. To let this request reach the VFS server, we have to add a new entry to the <route> definition.

     <route>
       <service name="File_system"> <child name="vfs"/> </service>
       ...
     </route>
    

To have a visible effect, let's redirect the output of the "echo" command executed by bash to the pseudo file /dev/terminal. Change the bash argument to the following (just appending the "> /dev/terminal"):

 <arg value="echo files at /dev: /dev/* > /dev/terminal"/>

Upon the next attempt of goa run, magic happens:

We have just redirected the output of the bash command to our terminal, which used our TrueType pseudo-file-system driver to render glyphs on a pixel buffer that, in turn, was blitted by the nitpicker GUI server to screen. Could our day become any better? Sure! How about interacting with bash directly?

Change the <libc> configuration of bash to the following:

 <libc stdin="/dev/terminal" stdout="/dev/terminal" stderr="/dev/terminal"
       rtc="/dev/null"/>

This change wires up the standard input and output of bash with /dev/terminal. Let's also drop the -c arguments from the bash <config> so that bash will wait for a command typed in via stdin. The next goa run will greet us with a shell prompt where we can type in bash commands like echo:

Of course, we feel a sudden urge to also execute the ls command.

The ls command is a separate Unix command that is not yet part of our scenario. It is covered by the next episode...

Edit (2023-05-02): updated to Sculpt OS 23.04

Edit (2023-11-15): updated to Sculpt OS 23.10