On 12/15/2011 08:35 AM, Richard W.M. Jones wrote:
On Wed, Dec 14, 2011 at 05:07:04PM +0000, Matthew Booth wrote:
> guestmount_mount(g.sock_path(), '/tmp/dir')
> ...filesystem stuff...
> guestmount_umount('/tmp/dir')
>
> g.close()
>
> Note that there are no callbacks, no threads and no races. The code
> can be written sequentially.
There are certainly threads here, although they would have to be
hidden behind the scenes, with a barrier to provide synchronization
before "filesystem stuff" can run.
No, there are no threads and (in this example, anyway) no
synchronization is required. There *is* a fork, though.
guestmount_mount() would:
1. Create a new guestfs connection to the existing appliance.
2. Create a new filesystem using the new connection.
3. Mount it.
4. fork() and run the FUSE event loop from the child.
In this case, guestmount_mount() would effectively return after 3, at
which point the filesystem is already safe to use. Although the above
example doesn't require this, it would also be 'safe' to interleave
filesystem access and guestfs commands. The limitations would be that
changing the appliance mounts while it is guestmounted would have
undesirable consequences and, as with all filesystem access, there would
be the potential for read->modify->write races. However, a simple use
case like the one you proposed won't hit these limitations. If required,
a simple locking scheme could be added later. This could be as simple as
total exclusivity for a single client until released.
In working out what guestmount_mount would do, it occurs to me that you
can do this without requiring either multiple connections to the
appliance or a callback mechanism, as long as the caller takes
responsibility for the guestfs handle. Consider
guestfsmount_mount_handle(g, mountpoint), which does:
1. Create a new filesystem using the given handle.
2. Mount it.
3. Create a new thread to run the FUSE event loop.
You could then do:
g.foo()
h = guestmount_mount_handle(g, mp) (h is an opaque handle)
... it is the responsibility of the caller not to use g here ...
guestmount_unmount(mh)
g.bar()
h would contain at least the mountpoint and the thread handle.
guestmount_unmount(mh) would therefore be:
umount(mh->mp)
mh->thread.join()
> Also, we're not bringing FUSE into the main API.
Is this such a problem? We could dlopen libfuse if the dependency on
libfuse was a problem ...
Actually, I may be softening to this if the API can be more like a
regular API call. I think my guestmount_mount_handle example could be
turned into something usable:
g.foo()
g.mount_local(mp)
... g cannot be used here ...
g.unmount_local()
g.bar()
The handle would be stored in g itself, so only 1 mount would be
supported at any one time.
This could be usefully extended in 2 ways I can think of:
Firstly, guestfs could be made threadsafe by simply putting a mutex
around all api calls. That is, calls can be made simultaneously from 2
different threads, but they will run sequentially. This could be
improved upon by redesigning the protocol, but I suspect it would be
perfectly adequate in practice. This would make g usable between calls
to mount_local and unmount_local(), as long as mount points are not
modified and the handle is not closed.
Secondly, the mechanism I described before for connecting to a running
appliance remains generally useful. guestmount could still be updated to
create a handle which uses an existing appliance.
On the face of it, neither of these extensions seems particularly
onerous to implement either.
Matt
--
Matthew Booth, RHCA, RHCSS
Red Hat Engineering, Virtualisation Team
GPG ID: D33C3490
GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490