Hey Genode team, fellow Genodians,
I'm trying to modify FUSE-server to become a VFS plug-in (see genode-world#193) but it seems "with_libc()" no longer works, when in a plug-in attached to the test-libc_vfs test harness:
[init -> test-libc_vfs] ---->open_Dir [init -> test-libc_vfs] Error: Uncaught exception of type 'Libc::Kernel::Kernel_called_prior_initialization' [init -> test-libc_vfs] Warning: abort called - thread: ep
Questions: ---------
Seems a VFS plug-in is initialized first, before libc has had a chance to be initialized ?
While digging in the code, here is what I can gather: in the previous incarnation of the code, where FUSE is an FS server, the server would call Libc::Component::construct() early, which would initialize the libc/kernel: kernel = *unmanaged_singletonLibc::Kernel(..) and then subsequent calls to libc would work. But now, the VFS plug-in (and its libc-using code) is called first.
Is that the right explanation for my problem?
Alternative explanation: maybe the libc framework from the _host_ component is not shared with its child VFS plug-in at all ?
Tracing: -------
I then removed calls to libc, and added some tracing to the now free-flowing code, to get some sense of chronology, determine who is called when.
The result was interesting: it seems when a VFS plug-in is instantiated, its hooks are called before anything else is called (I'm not the one calling them, as my code is still at an early "skeleton" stage yet, I make no call to opendir() etc, so someone else is doing that), including even before the host "Main" :
[init -> test-libc_vfs] ---->open_Dir [init -> test-libc_vfs] ---->num_dirent [init -> test-libc_vfs] ---->directory [init -> test-libc_vfs] ---->directory [init -> acpi_drv] SMBIOS table (entry point: 0x165700 structures: 0xf5720) [init -> platform_drv] ECAM/MMCONF range 00000000:00000000.0-000000ff:0000001f.7 - addr [00000000b0000000,00000000c0000000) [init -> test-libc_vfs] test-libc-vfs: ctor of struct Main : reading config ROM [init -> test-libc_vfs] test-libc-vfs: entering with_libc() [init -> test-libc_vfs] test-libc-vfs: calling test() from libc context [init -> test-libc_vfs] ---->leaf_path [init -> test-libc_vfs] Error: Uncaught exception of type 'Test_failed'
So based on that 'evidence' alone, it would seem the event chronology is somewhat like this:
- test-harness is loaded by LD - LD looks for its plugins, loads them, and executes them first (even if they require libc) - test-harness / struct Main is called, initializing libc etc
Maybe my FUSE plug-in should detect when libc is not yet initialized, reject all calls to its hooks done at that early time, and only honor them later on ?
Cedric
Hi Cedrik,
Questions:
Seems a VFS plug-in is initialized first, before libc has had a chance to be initialized ?
While digging in the code, here is what I can gather: in the previous incarnation of the code, where FUSE is an FS server, the server would call Libc::Component::construct() early, which would initialize the libc/kernel: kernel = *unmanaged_singletonLibc::Kernel(..) and then subsequent calls to libc would work. But now, the VFS plug-in (and its libc-using code) is called first.
Is that the right explanation for my problem?
that's spot-on! The libc dependency of the fuse plugin makes the VFS implicitly dependent from the libc, which is unfortunate. We have a hen-and-egg problem.
So based on that 'evidence' alone, it would seem the event chronology is somewhat like this:
- test-harness is loaded by LD
- LD looks for its plugins, loads them, and executes them first (even
if they require libc)
- test-harness / struct Main is called, initializing libc etc
Maybe my FUSE plug-in should detect when libc is not yet initialized, reject all calls to its hooks done at that early time, and only honor them later on ?
That is an interesting idea but it violates the execution model of the libc. The VFS is part of the so-called libc kernel, which works like a state machine. Calls from the libc into the VFS are nonblocking. In contrast, the libc's interface is operated at the so-called application context that supports the notion of blocking I/O. Both execution contexts should never be mixed.
I'm afraid that this leaves only one viable solution: To stub the I/O interplay of fuse with the libc and map those interactions directly to the raw VFS, not going through the libc. This is tedious but the only clean way I can see. It would essentially remove the dependency of the fuse VFS plugin from the libc's I/O operations, alleviating the cyclic dependency.
Note that freestanding libc functionality like string operations are ok to use. E.g., the ttf VFS plugin depends on the libc in such a "weak" way because the stb library used for the glyph rendering happens to call few libc utilities. But I/O is a no go.
Cheers Norman
I'm afraid that this leaves only one viable solution: To stub the I/O interplay of fuse with the libc and map those interactions directly to the raw VFS, not going through the libc. This is tedious but the only clean way I can see. It would essentially remove the dependency of the fuse VFS plugin from the libc's I/O operations, alleviating the cyclic dependency.
Note that freestanding libc functionality like string operations are ok to use. E.g., the ttf VFS plugin depends on the libc in such a "weak" way because the stb library used for the glyph rendering happens to call few libc utilities. But I/O is a no go.
Organizing my thoughts and looking for inspiration:
1) Stubbed Libc:
I see there is a "mini libc" which supports the run/demo scenario.
And there is somewhat larger one for lwext4 here, inside include/ and libc.cc:
https://github.com/genodelabs/genode-world/tree/master/src/lib/lwext4
(probably I should look at this (and fatfs, and...) more in depth, see how they deal with access to the "Block device" sans calling read()/seek() but using the Genode API instead.)
So I could do something similar, except I'd stub/shim read(), write() and ioctl() (which NTFS/Fuse make heavy use of) instead of just malloc() and strcpy().
Beyond my NTFS/BFS/XFS use cases there would likely be more use cases, so it might make sense for me to refactor whatever I come up with into an e.g. world/src/lib/generic-libc/ library or some such.
Then, said static (?) library would be common to (and linked to) components that need it, including the various FUSE file systems.
2) Late loading:
Alternatively, I have to wonder whether some runtime loading (instead of launchtime loading) of the VFS plug-in would make sense. Generally speaking, Genode supports dlopen()-style loading of .so libraries, so I suppose if the VFS server gets fully loaded, and then later its "config" ROM gets amended with an extra plug-in, then the plug-in would be initialized with a full LibC context.
Genode/Sculpt has gotten various "dynamic scenarios" over the years, I wonder if that one is implemented ? It might be a "nice-to-have", beyond the immediate problem discussed here, since it's nice to be able to try out new FS plug-ins without rebooting Genode.
Anyway, will dig into all that in the coming weeks...
Cedric
Hi Cedric,
I just noticed that I left one of your questions unanswered.
Alternatively, I have to wonder whether some runtime loading (instead of launchtime loading) of the VFS plug-in would make sense. Generally speaking, Genode supports dlopen()-style loading of .so libraries, so I suppose if the VFS server gets fully loaded, and then later its "config" ROM gets amended with an extra plug-in, then the plug-in would be initialized with a full LibC context.
Genode/Sculpt has gotten various "dynamic scenarios" over the years, I wonder if that one is implemented ? It might be a "nice-to-have", beyond the immediate problem discussed here, since it's nice to be able to try out new FS plug-ins without rebooting Genode.
This comes down to dynamically changing the VFS layout of a component, which would be really cool. We have indeed contemplated the option to dynamically reconfigure the VFS in the past. Given the dynamic re-configurability of most Genode components, this would be a natural and intuitively expected feature after all.
However, not all parts of this problem are well-enough understood to go for it right now. One question is: how to create the smallest possible diff between two VFS configurations? In contrast to, e.g., the init configuration where each start node has some kind of unique identity that can be used to find the corresponding nodes in the old and new config, the VFS configuration is less strict. The diff algorithm would need to be more complicated than the list model (util/list_model.h) we normally use.
Also, the potential side effects of a configuration change on the visibility of files in the union mount are rather complex, which raises the question: what should happen with open files that belong to a VFS plugin that disappears or for a file that becomes shadowed by another VFS plugin in the new configuration?
That's more than enough food to keep the back of the mind spinning. I think we will ultimately find solid answers to those questions down the road. But the attempt to answer them today would probably be too premature to be useful.
Btw, non-structural changes of the VFS configuration can actually be applied today, e.g., changing the font-size parameter of the vfs_ttf plugin as used by Sculpt's dynamic font scaling. But that's a very fine (thin) line to walk on. ;-)
Cheers Norman