UDisks, GVfs, and desktop storage

Follow desktop storage requests through services and mount interfaces.

14 min readChapter 118 of 168Intermediate → advanced
In this chapter

Before you start#

Review mount namespaces, udev events, D-Bus, and polkit. The source baselines are UDisks 2.10.1 commit 18c9faf089e306ad6f3f51f5cb887a6b9aa08350 and GVfs 1.54.2 commit 7ffc15fd6210568c009a64b6b4172e9ec8bba137, independently selected historical versions, not a tested host stack.

Learning goals#

Trace a removable-volume request across device discovery, D-Bus, authorization, mounting and desktop presentation. Distinguish a job, mount, user-interface row and open file descriptor. Explain cancellation and removal races without probing or mounting a real device.

Source walkthrough#

A mount request and desktop state
  1. Device

    Kernel and udev state inform userspace device objects.

  2. Request

    The client asks the daemon to mount an identified filesystem.

  3. Decision

    Object validation and authorization are separate checks.

  4. Result

    Mount and desktop callbacks can succeed, fail or race removal.

Conceptual model. Conditional model, not a real mount or captured bus request; device removal and policy can stop the path.

UDisks is a userspace service that exposes storage-related objects and operations over D-Bus. Its object model can represent drives, block devices and filesystem interfaces; a block object and a drive object do not mean a filesystem is mounted. The daemon can use kernel, udev, filesystem and policy interfaces to perform an operation. UDisks is not the filesystem implementation, and GVfs is not the kernel's block layer. A desktop application can observe a volume or mount through GLib/GIO interfaces without owning the daemon's device object.

The exact excerpt below comes from UDisks 2.10.1 src/udiskslinuxfilesystem.c, handle_mount; the file is GPL-2.0-or-later under its notice. Earlier declarations and later option handling are omitted:

c
  object = udisks_daemon_util_dup_object (filesystem, &error);
  if (object == NULL)
    {
      g_dbus_method_invocation_take_error (invocation, error);
      goto out;
    }

The handler obtains a referenced object associated with the requested filesystem. If that fails, it transfers the error to the method invocation and branches to the function's cleanup path instead of dereferencing a missing object. The surrounding code takes a per-filesystem mutex before this lookup, so normal and error exits must respect that lock and reference lifetime. A device can disappear while a request is pending; an object reference retains a userspace object, not the physical device or a guarantee that the later mount will succeed.

The next exact excerpt from GVfs 1.54.2 daemon/gvfsjob.c, g_vfs_job_cancel, is LGPL-2.0-or-later according to the file notice:

c
  if (job->cancelled || job->finished)
    return;

  job->cancelled = TRUE;

The job checks whether cancellation or completion already happened and returns in that case. Otherwise it records cancellation before the following source emits a signal and cancels the associated GCancellable. Cancellation is not the same as undoing a completed mount or proving that every callback has stopped. The job and cancellable have references and cleanup paths; application code must account for a completion callback racing with a cancellation request.

From device discovery to a desktop request#

The kernel block and device subsystems expose device state and generate relevant events; udev can add properties and database entries. UDisks observes the information available to it and exposes objects through its D-Bus interfaces, including ObjectManager-style discovery in supported deployments. A client receives object paths and properties but must handle changes and removal. A path naming a D-Bus object is not a durable kernel device reference, and an object can disappear between listing it and invoking a method.

A client may request a filesystem mount on an appropriate object. The method arguments and options are data that the daemon must validate. The service obtains caller information and can ask polkit for authorization under its configured policy and action. Bus delivery policy, polkit authorization and the daemon's own option and object validation are distinct checks. The caller's UID or session context can influence a policy or mount choice, but exact rules and default mount paths depend on the UDisks version, distribution and configuration. Do not assume a universal /run/media/... path or a particular prompt.

The selected handle_mount path in the pinned source distinguishes system-managed/fstab-related and dynamically chosen mount handling. It holds state such as a daemon, block object, caller identifiers and mount point and returns a result or error through the D-Bus invocation. Mount helpers, supported filesystem drivers and options vary. A successful method response is evidence about that operation's result under the daemon's interface; it does not prove that every desktop client has refreshed or that later I/O will succeed. Failure after preparation requires cleanup of temporary objects, lock state and any partially created resources according to the actual branch.

In the inspected v2.10.1 handler, after duplicating the object the code obtains the block and daemon, checks existing mount points and selects caller information. It calls either handle_mount_fstab or handle_mount_dynamic under its system_managed condition. On the success path it records the mounted filesystem, triggers a device update and completes the mount invocation. At the shared out label it unlocks the filesystem mutex, checks state where applicable, frees allocated strings and clears the object reference. Authorization checks occur in selected helper paths; their exact action and options depend on the request and configuration. This is the selected source path, not proof that a hot-unplug or helper failure was executed.

In src/udiskslinuxfilesystem.c, UDisks 2.10.1, handle_mount, GPL-2.0-or-later (earlier branches omitted; the label is copied verbatim):

c
 out:
  if (object != NULL)
    udisks_linux_block_object_release_cleanup_lock (UDISKS_LINUX_BLOCK_OBJECT (object));
  g_mutex_unlock (&UDISKS_LINUX_FILESYSTEM (filesystem)->lock);
  if (state != NULL)
    udisks_state_check (state);
  g_free (mount_point_to_use);
  g_free (fstab_mount_options);
  g_free (caller_user_name);
  g_free (device);
  g_clear_object (&object);

Success and goto out failures both reach this label. It releases locks and local references and runs a state check, but the excerpt does not show whether a failed helper rolled back partial work.

A GUI might present a volume before a mount exists. A mount can disappear while the GUI still displays a stale row, or an application can retain an open FD after a pathname or mount relationship changes. The FD refers to an open file description, not to the visual row. Unplugging a device can make later I/O fail even while a userspace object and FD are still referenced. Reference counting controls memory and object lifetime; it does not keep a removable drive physically present.

GIO, GVfs and bridge boundaries#

GIO offers abstractions such as GFile, GVolumeMonitor, GVolume and GMount to applications. A GFile names or represents a location under an API; it does not guarantee that a local POSIX pathname or a live remote connection exists. A volume monitor can report additions, changes and removals asynchronously. A GMount represents a mount-like object under the API's semantics, not necessarily a kernel mount in the application's current namespace. A client should handle a vanished object, canceled operation and a callback arriving after the UI changed.

GVfs supplies userspace backends and daemons for supported virtual or remote locations. An operation can involve a client library, a daemon job, a backend and external protocol or storage. A GVfs FUSE bridge can expose some GVfs locations through a filesystem interface when configured, but it is not the path for every GIO operation or every local mount. The exact backend, session and availability depend on the environment. The pinned daemon/gvfsbackend.c and gvfsjob.c are separate source entry points, not a universal application-to-kernel call chain.

The GVfs job object can own a cancellable and record whether it is finished or canceled. A caller that cancels must still follow the asynchronous API's completion and reference rules. A late callback may report cancellation, an error or a result that was already committed elsewhere. A UI component can be destroyed before a callback arrives; callback data must either hold a valid reference or be invalidated safely under the application's threading and event-loop model. A mutex protecting UDisks's filesystem handler is not a lock on every desktop application's view of the device.

Worked mount and removal timeline#

Use this synthetic case. At T0 a removable teaching volume V appears in a desktop list as a block object and a GVolume; it is not mounted. At T1 client C requests a mount. At T2 the daemon validates options and obtains or checks the requesting identity. At T3 an authorization decision permits the particular request under the invented policy. At T4 a mount helper succeeds and the daemon returns a mount path; a desktop monitor later reports a corresponding mount. These are assumed events, not a captured system-bus trace.

Now insert a removal at T2. The D-Bus object can vanish or the underlying device can become unavailable while the authorization request is pending. A later affirmative authorization decision for the earlier request does not recreate the removed device. The daemon must handle object lookup, mount helper and cleanup errors under its implementation; the UI must handle a failed or canceled asynchronous request and remove or refresh its row. A callback associated with the old object should not silently update a newly inserted device merely because a display name is reused.

At T5, in a separate branch where the mount did succeed, application A opens a file as descriptor 6. The user interface can remove its row at T6 while A still holds that descriptor. The open-file reference and the UI object's lifetime are separate. If the physical device is unplugged, later reads through descriptor 6 can fail; a live FD does not promise accessible media. Closing the FD releases A's reference, while unmounting and daemon object removal have their own lifetimes and namespace rules. No actual block device or mount is used in this example.

Authorization and cleanup are staged#

A mount request can fail before authorization because the method arguments or object are invalid. It can be denied by bus policy, polkit or the daemon's own checks. It can pass those checks and then fail when opening the device, selecting a helper, creating a mount point or invoking the kernel mount operation. A helper can return an error after making some changes; the exact rollback path depends on the implementation and context. Never assert that a failed D-Bus call proves no filesystem state changed, or that a successful authorization proves the device is still present.

In the same file, src/udiskslinuxfilesystem.c, the dispatch that follows the object checks, under the same GPL-2.0-or-later notice (copied verbatim):

c
  /* Mount it */
  if (system_managed)
    {
      if (!handle_mount_fstab (daemon,
                               object,
                               caller_uid,
                               caller_gid,
                               opt_as_user != NULL,
                               mount_point_to_use,
                               fstab_mount_options,
                               invocation,
                               options))
          goto out;
    }
  else
    {
      if (!handle_mount_dynamic (daemon,
                                 object,
                                 caller_uid,
                                 caller_gid,
                                 caller_user_name,
                                 opt_as_user != NULL,
                                 &mount_point_to_use,
                                 &mpoint_persistent,
                                 invocation,
                                 options))
          goto out;
    }

Either helper that returns false jumps straight to out. The excerpt does not show the helper internals or their authorization checks, which are separate code paths.

handle_mount stages in UDisks 2.10.1
  1. Lock and lookup

    The filesystem mutex is taken; a failed object lookup is returned on the invocation and exits.

  2. Dispatch

    system_managed chooses the fstab or dynamic helper; a false result jumps to out.

  3. Record

    Success records the mounted filesystem, triggers a uevent sync and completes the invocation.

  4. Out

    Releases the cleanup lock and mutex, frees strings and clears the object reference.

Source map. Source map of udiskslinuxfilesystem.c handle_mount, simplified; authorization and helper internals are not shown, and no device was mounted.

The filesystem handler's mutex serializes relevant operations on that object in the selected implementation. It does not prevent the kernel from reporting removal, another process from closing an FD, or an asynchronous policy decision from taking time. Holding a userspace reference protects the referenced object under its API, not the continued existence of a block device. Errors must be transferred or freed according to GLib ownership rules, and a method invocation must receive at most the appropriate terminal response. An application that retries needs to decide whether the prior request may have completed despite a lost reply.

A remote GVfs backend introduces additional failure stages such as transport disconnect, authentication and remote object semantics. A local UDisks filesystem mount and a remote GVfs location do not necessarily use the same kernel operations or authorization path. A FUSE bridge, when present, adds another connection and caching boundary. These possibilities are reasons to identify the selected backend and namespace, not to infer every component was involved from a desktop icon.

Making an asynchronous result reviewable#

A desktop request should carry enough identity to associate a completion with the original operation. A callback for volume V can arrive after the view has been rebuilt, the user switched sessions or a similarly named device appeared. An application can retain an appropriate object reference while the operation is pending, but must still ask whether the referenced object is the one currently shown and whether the underlying device remains available. A reference prevents premature memory reclamation under the API; it is not a physical-presence check or an authorization grant.

Four lifetimes after a removal
D-Bus invocation
Ends with one terminal reply; a lost reply can hide whether the helper acted.
UDisks object
A reference keeps the userspace object alive; it does not keep the device present.
Mount
Created by a helper and removed by unmount; it is separate from the desktop row.
Open FD
A process-local reference that can outlive the row; later reads can fail after removal.
Conceptual model. Conceptual comparison of the lifetimes discussed in this chapter; each ends under its own rules, and none proves that a device is present.

Cancellation has at least three possible positions in the synthetic timeline. Before dispatch, a client can decide not to issue the request. While authorization or a helper is pending, it can request cancellation and await the operation's documented completion behavior. After a mount operation has completed, cancellation of the original asynchronous call is not equivalent to an unmount. A UI should avoid presenting the result as certainly absent until it has received and reconciled the appropriate state. The selected GVfs job guard demonstrates one part of a job lifecycle, not a guarantee about all daemon backends or mount rollback.

File descriptors add a second observation path. An application can have an open file even after a volume monitor has emitted a removal event. The next read might return previously cached data, fail due to an I/O error, or behave differently under the filesystem and failure conditions. Neither an unchanged UI nor a single successful cached read establishes the physical drive's health. Likewise, a permission error on one file is not evidence that the entire mount request failed. A careful incident record names the operation, object, caller, mount namespace and time while omitting unnecessary private pathnames.

For source work, compare a daemon's authorization check and method completion with the consumer's cancellation callback. These are two processes with different objects and locks; a lock in the daemon cannot protect a pointer in a desktop application. Determine which side owns the D-Bus invocation, who holds the job reference, and where each error is returned. If the response is lost after the helper acted, a retry may encounter an already mounted or removed object. The service's actual idempotency and error contract must be checked before claiming that retrying is harmless.

Practice#

This local model contains only invented states. It makes no D-Bus call, does not enumerate devices, and does not mount or inspect host filesystems:

sh
python3 - <<'PY'
def result(present, authorized, helper_ok):
    if not present: return 'device missing'
    if not authorized: return 'denied'
    return 'mounted' if helper_ok else 'mount failed'
for row in [(True, True, True), (False, True, True),
            (True, False, True), (True, True, False)]:
    print('synthetic:', result(*row))
PY

Expected output is mounted, device missing, denied and mount failed. This is not UDisks or GVfs code and cannot model their actual authorization, cancellation or rollback. If Python is absent, evaluate the same rows from the T0–T6 timeline. No service, mount, device, policy or host data is accessed.

Source-only alternative: draw the kernel event, udev, UDisks object, authorization request, helper, GIO monitor and application FD. Mark the removed-before-authorization and stale-UI cases. Explain the quoted error transfer and the GVfs cancel guard, and state what additional evidence would be required to claim a real mount occurred.

Knowledge check#

  1. Why can a displayed volume exist without a mounted filesystem?
  2. What does the UDisks excerpt do if it cannot duplicate the requested object?
  3. Why does a successful authorization not prove a later mount will succeed?
  4. What does the GVfs cancellation guard prevent, and what does it not guarantee about external side effects?
  5. Why can an open FD and a desktop row have different lifetimes after removal?

Answers#

  1. The desktop can display discovered device or volume objects before a mount operation. Discovery, mount state and application access are separate stages.
  2. It transfers the error to the D-Bus invocation and goes to cleanup rather than using a null object. The surrounding lock and references still need their normal cleanup.
  3. The device can disappear, options or helper setup can fail, or the kernel operation can return an error after authorization. Authorization is only one condition.
  4. It returns when the job is already canceled or finished and otherwise records cancellation. It does not reverse arbitrary completed work or automatically make every callback's data safe to free.
  5. The row is a userspace presentation object; the FD is a process-local reference to an open file description. Either can outlive the other, while removal can make later I/O fail.

Checkpoint#

Submit the T0–T6 state table, a mount authorization/error diagram and either the private model output or source-only explanation. Ready means separating object reference, physical device, mount and FD lifetimes and naming both quoted failure/cancellation branches. Retry with one client and no removal before adding the race.

Sources#

A little further beneath the surface.

Check your understanding with the checkpoint. Marking a chapter as read saves a bookmark, not a proficiency assessment.