Subject: Plans for gnome-vfs replacement

Recently there has been a lot of discussions about the platform and
the correct stacking order and quality of the modules. Gnome-vfs
is a clear problem in this discussion. Having spent the last 4 years
as the gnome-vfs maintainer, and even longer as the primary gnome-vfs
user (in Nautilus) I'm well aware of the problems it has. I think that
we've reached a point where the problems in the gnome-vfs architecture
and its position in the stack are now ranking as one of the most
problematic aspects of the gnome platform, especially considering the
enhancements and quality improvements seen in other parts of the
platform.

So, I think the time has come for a serious look at what gnome-vfs
could be. I've spent much time last week thinking about the weaknesses
and problems of the current gnome-vfs and possibilities inherent in a
redesign, both having learnt from 7 years of gnome-vfs existance and
the improvements in the platform (both Gnome and surrounding
technologies) since 1999 when it was designed.

As soon as you spend some time looking at this problem is evident that
to solve the platform ordering issues we really need a clean cut from
the current gnome-vfs. I think the ideal level for a VFS would be in
glib, in a separate library similar to gthread or gobject. That way
gtk+ would be able to integrate with it and all gnome apps would have
access to it, but it wouldn't affect small apps that just wants to use
the glib core. Furthermore, not being libglib lets us use GObjects in
the vfs, which means we can make a more modern API. Of course, this
places quite some limitations on the vfs, especially in terms of
dependencies and how integration with a UI should work.

Any thoughs on the design of a vfs to replace gnome-vfs must be based
on a solid understanding of the problems with the current system, to
avoid redoing old mistakes and to make sure that we solve all the
known problems of the current system. So, I'm gonna start by
describing what I see as the main architectural and "hard" problems in
gnome-vfs.

The first, and most often discussed problem is of course
compatibility. The various desktops use different vfs implementations,
so they can't read each others files. Many applications use no VFS
at all and have no way to access files on vfs shares. And the
existence of multiple vfs implementations makes it unlikely that they
will start using one.

Gnome-vfs has no concept of Display Names, something which is very
useful in a gui based system. In some places we auto-generate virtual
desktop files to get this feature, but that is very hackish and need
support in all apps to understand them. This is also quite closely
related to handling filename charset encoding, another very weak point
in gnome-vfs. The ideal way to reference a file is the actual real
identifer on disk, database, remote share or what have you, as that
can be passed between implementations, used with other access methods,
etc. But to display something useful to the user you really need a
user understandable utf-8 encoded string and a way to map that to/from
the filename.

There is no support for icons at the level of gnome-vfs. This means
that all users above the vfs must implement it themselves (e.g. in
nautilus and in the file selector). Each implementation have its own
bugs, maintainance load and risk for different behaviour. It also
means that vfs backends cannot supply their own icons, something which
might be very useful for e.g. a network share based on some new fancy
web service. 

The abstraction that gnome-vfs use is very similar to the posix model,
which is problematic in several ways. The posix model matches poorly
with the sort of highlevel operations that gnome applications wants to
do with the vfs. The vfs is not typically used for what I would call
"implementation files", which are things like configuration files,
data files shipped with apps, system files, etc, but rather for what
I'd like to call "user document files". These are the kind of files
you open, save, or download from the internet to look at. Applications
that use these would like highlevel operations that match the kind
of operations you use on them, like read-entire-file, save-file,
copy-file, etc. 

The posix model is also a bad match when implementing gnome-vfs
modules. It requires some features from the implementation that can be
difficult or impossible to implement. For example, its not really
possible to support seeking when writing to a file on a webdav share,
because a webdav put operation is essentially just streaming a copy of
the new file contents. We currently work around this by locally
caching all the file data being written and then sending it all in the
close() call. This is clearly suboptimal for a lot of reasons, like
applications not expecting close() to take a long time, and not
checking its error conditions very closely.

Another problem is that the posix model doesn't contain explicit
operations for some of the things applications need to do. So instead
applications rely on well known knowledge of the behaviour of posix
to implement these operations. However, such behaviour might not be
guaranteed on some gnome-vfs backends. A common example is the atomic
save operation. A typical way to implement this on posix is to write
to a temporary file in the same directory, and then rename over the
target file, thus guaranteeing an atomic replacement of the file on
disk, or a failure that didn't affect the original file. Of course,
if a gnome-vfs application would use this and the backend was a
webdav share to a subversion repository you would get some really
weird versioning history in the repository for no good reason. If the
backend had its own implementation of the save operation we could get 
both optimal behaviour on each backend, and an application API that
doesn't require arcane knowledge of the atomicity of renames.

One of the most problematic aspects of gnome-vfs is its authentication
framework. The way it works is that you register callbacks to handle
the authentication dialog, and whenever any operation needs to do
authentication these callbacks will be called. The idea is that a
console application would register a set of callbacks that print
prompts on the console, and a Gtk+ application would have a set of
callbacks that displays dialogs. There is a set of standard dialog
based callbacks in libgnomeui that you can install by calling
gnome_authentication_manager_init(). From an initial look this seems
like a reasonable approach, but it turns out that this creates a host
of different problems.

One problem is how you connect this to the application. A lot of
people are unaware that you have to call gnome_auth_manager_init() to
get authentication dialogs, or don't want to depend on libgnomeui to
do so. So a lot of applications don't work with authentication. Those
who do call it generally have pretty poor integration with the
authentication dialogs. For instance, the general authentication
dialogs can't be marked as parents of whatever dialog caused the
authentication (because they have know whay of knowing what caused
it), and all sorts of problems appear when there is a modal dialog
displayed already.

Another problem is the combination of blocking gnome-vfs calls and
authentication. When calling a blocking operation like read() and it
results in a password dialog we have to start up a recursive mainloop
to display it. Not only is this unexpected for the application, it
also brings with it all the type of reentrancy issues that we had in
bonobo. Even worse, there is no way to make this threadsafe. To make
it threadsafe the callback would have to take the gdk lock before
doing any Gtk+ calls, but this would cause a deadlock if the
application called it with the gdk lock held. If we don't take the
gdk lock then you can't do blocking vfs calls on any thread but the
mainloop, or you have to take the gdk lock on any gnome-vfs call.

The authentication callbacks can appear at *any* gnome-vfs entry
point, which makes it very hard to write gnome-vfs applications that
don't accidentally trigger a lot of authentication dialogs. For
instance, the tree sidebar in nautilus has to take particular care not
to stat or otherwise look at the toplevel items until the user
explicitly expands them, otherwise you'd get authentication dialogs
every time you opened a window. Its also easy to get multiple
authentication dialogs for the same entity.

The way threads are used in gnome-vfs is problematic, both from the
point of view of writing backends, and for users of the library. For
users it forces the use of threading, even if the application doesn't
use the asynchronous calls that use the threading. It also enforces
the need for a gnome_vfs_init() function, as thread initialization
must be done very early.

For backend implementations the use of threads forces every
backend to be threadsafe. Many of the backends are inherently single
threaded, either because they use non-threadsafe libraries like the
smb backend, or because the server being wrapped forces serialized
access (like an ftp backend where you really only want one connection
to the server). 

Backends run in context of the application using gnome-vfs, which can
be a gtk+ app, but as well a console application, so they have no
control or guarantee of their environment. For instance, they cannot
rely on the existance of a mainloop, so there is no way to use
e.g. timeouts to handle invalidation of caches. One way we have tried
to solve this is to move some backends to the gnome-vfs daemon, where
they can rely on the existance of the mainloop.

Gnome-vfs use something called "gnome vfs uris" to identify
files. These are similar, but not entierly identical to the types of
uri used in webbrowsers. For instance, we often make us our own types
of URIs when there is no official standard for them (although such
standards might appear later, with incompatible behaviour). We also
have a "well defined" posix-like type of behaviour that isn't the same
as for web uris. The most extreme example would be mailto:, but even
things like ftp:// uris are different. The ftp uri rfc explains how
ftp:///dir/file.txt refers to $(login_dir)/dir/file.txt, and that you
have to use ftp:///%2fdir/file.txt to refer to the absolute path
/dir/file.txt on the server. Clearly we can't have pathname handling
semantics that vary depending on the backend (no app would get it
right), so we ignore the rfcs on this.

Then there is the thing with escaping and unescaping uris. Although
technically not very complex it is just are very hard to get right all
the time. Among the most common questions on the gnome-vfs list is
what the various escape/unescape functions does, what arguments has to
be escaped, and how to display uris "nicely" (i.e. without escapes,
although that makes them invalid uris). This is made extra complicated
due to the poor handling of filename encodings and display names, and
the fact that only "less common" cases (like spaces in filenames)
break if you get it wrong.

Last but not least, the fact that gnome-vfs uses something called a
"uri" gives people the wrong impression of what the library is
designed for. It causes people to complain when it doesn't have some
support for mailto: links, and it makes people want support for
cookies, extra http headers and other things typically used by a web
browser. This isn't really the kind of use that vfs is targeted at. A
library specific to that sort use would probably fit these apps much 
better. 

Most gnome-vfs state is tied to the application that uses it, which I
think is quite unexpected by the user. For instance, when you log into
a network share in nautilus and then click on a file to open it, the
opening  application will have to re-connect and re-authenticate to
the share, much to the users surprise. I really think most people
expect a login like that is somehow session global. We do sometimes
misuse gnome-keyring to "solve" the authentication issue, but even
then we still have multiple connections to the network share, which
can cause problem, for instance with ftp shares that use round-robin
dns where the mirrors aren't fully sync:ed up. Again, some backends
(smb) are now in the daemon which solves this issue.

gnome_vfs_xfer() is possibly the worst-API call in the whole gnome
platform. Its a single, buggy, do-it-all function with shitloads of
combinations of flags and arguments to do all sort of things, with
little or no semantic specifications or testcases. Its also to a large
extent unnecessary for most applications and could easily be part of
the file manager instead of a generic library. I'm also not sure that
the "first do preflight calculation, then execute operation" model it
uses is right. It is inherently racy, since the target or source could
easily change during the preflight, and it makes error reporting and
handling much more complicated. 

The behaviour of symlink resolution in the UI has been discussed many
times. Should clicking on a symlink "foo" in $dir go to $dir/foo or to
the target directory. The Nautilus maintainers has decided that the
best way to approach this is to have symlinks be used for "filesystem
implementation" (like a symlink for /home -> /mnt/hdb2) and thus not
be resolved on activation. However, we should (this hasn't been
finished yet) support a different form of links (called "shortcuts" in
the UI) that always resolve on activation. At the moment there is no
support for anything like that in gnome-vfs, so we abuse desktop files
for this. We even generate virtual in-memory desktop files in the smb
backend to get this behaviour. Proper support for shortcuts in the
vfs API would let apps automatically work without ugly desktop file
hacks.

Over the years gnome-vfs has accumulated a lot of cruft. It links to a
lot of libraries, including openssl, gconf+ORBit2, avahi, dbus, popt,
libxml, kerberos, libz and libresolv. Very few applications need all
of these, yet every application that uses gnome-vfs links to all of
them. Furthermore, some of the functionallity in gnome-vfs, like the
wrapper for dns-sd, resolving, network utilities, ls parsing
functions, ssl support, pty handling are perhaps not best suited for a
vfs library, nor do they always have great apis and quality
implementations. We could definately clean this up and minimize the
APIs.

At some point in time gnome_vfs_uri_is_local() started detecting and
returning TRUE for NFS mounts and other type of local network
mounts. This is both slow and unexpected, and has led to problems and
unnecessary changes in many places. 

The way the cancellation API for asynchronous operations is set up
creates races and fragile code. The main issue is that if you call
cancel before the operation callback has been called the callback will
not be called. However, the callback typically wants to free some sort
of user_data object passed to it, so that has to be handled also when
you call cancellation. Couple this with the fact that there is no
destroy notifies and you can't cancel after the operation callback has
been called and you get an extremely tricky setup of combined
callbacks. Furthermore, if threads are used there are some inherent
races wrt detecting if the callback has been called when cancel is
called, making it essentially impossible to get this right.

There are also a bunch of issues with the current gnome-vfs that could
technically be fixed like support for hidden file flags,
backend-extensible metadata, no standard vfs dialogs like progress
bars, etc.

Last week I started thinking about a new design for a gnome-vfs
replacement that would solve most of these issues, and at the same
time gives a correct ordering of the platform stack. I've come up with
a highlevel architecture that I think will work, even though I haven't
yet finished it in detail or gotten the API totally worked out. Its
somewhat of a radical departure from gnome-vfs as it is today, so
brace with me as I try to explain the model and the ideas behind it.

The gnome-vfs model is what I would call stateless. You can at any
time throw a URI at it and it will do everything required to access
the location. There is no need to, nor is there a way to set up
anything like a "session" with a remote share. Of course, in practice
this is not the way network shares work, so all sorts of session
initiation, caching and other magic happens under the covers to make
it look stateless. This is the source of all the problems with the
gnome-vfs authentication model.

I'd like to propose using a stateful model, where you have to
explicitly initiate a session ("mount" a share) before you can start
accessing files. This will give a well specified time when all forms
of authentication will happen, when applications expect it and when
they can use a more expressive and suitable API for this kind of
operation. The actual i/o operations will then never cause any sort of
authentication issues, and can thus be purely non-graphical
(i.e. glib-only apps can do i/o). I imagine all/most actual mounting
of shares will happen in the file manager and the file selector, or at
gnome-session startup, so applications don't really need to handle
this themselves.

Not only is the model stateful. I'd like all state to be session
global. That is, all mounts and network connections are shared between
all applications in the session. So, if you pass a file reference from
one app to another there is no need to log in again or anything like
that. I think this is what users expect.

Having a global stateful model means all non-local vfs accesses go
through the vfs daemon. This works pretty well with the smb backend in
the current gnome-vfs, and smb is the backend most likely to have high
bandwidth traffic, so this doesn't seem to be a large performance
problem. Although we do have to take the performance aspect into
consideration when designing the daemon.

In order to avoid all the problems with threading described above the
vfs daemon will not use threads. In fact, I think the best approach is
to let each active mountpoint be its own process. That way we get
robustness (one mount can't crash the others) and simplify the backend
creation greatly (each backend fully controls its context). It also
will let us do concurrent access to e.g. two smb shares (like a copy
from one to the other). We can't really do this atm since the thread
lock in the smb backend serializes such access. But with two smb
processes this is not a problem.

There might be an issue with using separate processes for the
mountpoints bloating up the desktop, but I don't think that it will be
much of a problem. None of these processes will use the gui libraries
that are the real sources of unshared dirty memory use. I tried a
simple process that just used gobject and ran a mainloop. It only used
78k of dirty memory. Also, each server need only link to and
initialize the few libraries it needs, further keeping memory use down
and avoiding bloat in all applications (e.g. apps need not link to
openssl). 

As a consequence of the stateful model we don't need the stateless
properties that URIs has as identifier. To avoid all the problems
comming from the use of URIs  we use a much simpler form of
identifier. Namely filenames, in a hierarchical tree with
mountpoints. These filenames are from an extended set of strings that
includes the set of normal filenames, but also includes some platform
dependent extensions. On win32 the full set might be some form of
stringified version of the ITEMIDLIST from the windows shell api, and
on unix we would use some out of band prefix to mark a non-local
filename.  

For example, we could be to use "//.network/" as a prefix for the vfs
filename namespace. A smb share might then be accessed as
"//.network/smb/computer:share/dir/file.txt", or a ftp share as
"//.network/ftp/alex@ftp.gnome.org/dir/file.txt". With a setup like
"//.network/$method/$mount_object/" it would be quite easy to find the
process handling the mount. Just ask for a dbus named object like
"org.glib.gvsf.smb.computer:share". It is also very easy to detect
local filenames and short-circuit to in-process i/o.

These filenames would be the real identifier for the files, and as
such not really presentable to the user as it. You'd need to ask for
the display name via the vfs to get a user readable utf8-encoded
string for display. 

The set of operations on files and folders would be both simplified
and extended. We'd remove complicated things like read+write access to
a file, and give less posix-like guarantees. We also make seek and
truncate support optional in the backend. But then we will extend the
set of operations possible to allow things like copy on the remote
side (to avoid a download+upload operation on copy) and to have 
a set of highlevel operations that applications want, like "save" that
implements the best way to save for each particular backend.

We support metadata like display name, mimetypes, icon, and some
general information like length and mtime. But we make support for
getting the full "struct stat" buffer backend optional, as that isn't
a good abstraction for most backends. Also, the API will be designed
on the idea that network latency is expensive, so that there will only
be one call to stat() or readdir() needed to read all the metadata
requested by the application. (Whereas posix will have readdir return
only the names and force you to stat each file in a separate
roundtrip.)

We likely don't want the full gnome/unix vfs implementation in
glib, instead glib will only ship an implementation of the vfs API for
local file access, and one that communicates to the vfs
daemon(s). Then we ship the daemon and the implementations of the
various backends externally.

We will also write a single gnome-vfs backend that allows access to
all the glib vfs shares by using a uri like gvfs:///XXX that just maps
to //.network/XXX. We can also implement a similar backend for kio so
that kde applications can read and write to the shares.

Furthermore, if FUSE is supported on the system we can write a FUSE
filesystem so that we can access the files as $HOME/.network/XXX. This
can be made extra nice if the application (like e.g. acrobat) uses
the gtk+ file selector but not the vfs by having the file selector
detect a filename like this and reverse-mapping it into a vfs pathname
and use the vfs for folder access. 

I've been doing some initial sketching of the glib API, and I've
started by introducing base GInputStream and GOutputStream similar to
the stream objects in Java and .Net. These have async i/o support and
will make the API for reading and writing files nicer and more
modern. There is also a GSeekable interface that streams can
optionally implement if they support seeking.

I've also introduced a GFile object that wraps a file path. This means
you don't have to do tedious string operations on the pathnames to
navigate the filesystem. It also means we can use the openat() class
of file operations when traversing the filesystem tree, avoiding some
forms of races when we do things like recursive copies.

To support the stateful model and still have some form of caching we
will also need to add some cache specific api so that you can trigger
a reload of information from a directory. Otherwise a reload operation
in the file manager wouldn't always get the latest state on something
like a ftp share where we cache things aggressively.

I have some initial code here for some of the basic APIs, but its far
from finished and I'd like to spend some more time working on it
before I present it. However, I think the general architecture is
pretty sound and in a state where it can be discussed.

Hopefully this description of my plans is enought to make people
understand some of my ideas and allow us to start and discussion about
the future of gnome-vfs. Also, consider it a heads up that I and other
people will likely be working on this this in the future.
