Client Library

The filesystem client library offers abstractions and a number of layers of functionality to support interaction with components and the provision of higher-level mechanisms and abstractions for file access. In particular, the client library is intended for use by a conventional C library, with the functions in the C library invoking client library functions and employing client library structures internally.

The client library is provided by libfsclient within the departure package.

  1. Header Files
  2. File Data Structures
  3. Client Programming Interface
    1. Files
    2. Pipes
    3. Closing Files and Pipes
    4. Reading and Writing
    5. Navigation in Files
    6. Accessing Exposed Memory Regions
    7. Accessing Pipe Content in Memory Regions
    8. Flushing and Synchronisation
    9. Notifications
    10. Blocking Operations

Header Files

The following header files are pertinent to client library usage:

File Description
fsclient/client.h Client functions and data structures
systypes/fcntl.h Flag values for opening operations

File Data Structures

Since files are accessed using file references, the file_t data structure is used to wrap such references and other relevant state. Thus, such structures can be broadly regarded as similar to the traditional FILE data structure.

The members of the file_t data structure are as follows:

Member Description
ref A reference to the component providing file content
memory The memory address of the exposed file region
start_pos The start position of the region in the file
end_pos The end position of the region in the file
data_end The amount or extent of populated data in the region
data_current The offset used to track client position in the region
size The total size of the file
object_flags Flags indicating support for certain file features
can_block Notification flags for blocking access to the file
notifications Notification flags set for the file
flags The flags used to open the file
error Any failure or error condition on the file

Generally, these members should not be accessed directly, mostly being reserved for use by the client library itself.

Client Programming Interface

The client programming interface provides functions somewhat resembling the traditional C library and low-level Unix interfaces for file access, and these functions are intended to support such traditional interfaces.

Files

Files are opened using the following function returning a file data structure:

file_t *client_open(const char *name, flags_t flags);

New instances of open files, employing potentially different flags, can be obtained using the following function:

file_t *client_reopen(file_t *file, flags_t flags);

To test whether the file was successfully open, the following function should be invoked, this returning a true (non-zero) value if the file was successfully opened:

int client_opened(file_t *file);

Each file should be closed using client_close regardless of whether the file was successfully opened.

Example

file_t *file = client_open("somefile", O_RDONLY);

if (client_opened(file))
{
  /* Perform operations on file. */
}

client_close(file);

Pipes

Pipes are opened using a special function returning an error code, setting the supplied reader and writer endpoint parameters:

long client_pipe(file_t **reader, file_t **writer, flags_t flags);

Each returned pipe endpoint may be closed using client_close. If an error condition is reported using a non-zero value, the endpoints will not be retained and will therefore not need to be closed: the error condition is communicated purely via the return value.

Example

file_t *reader, *writer;

if (!client_pipe(&reader, &writer, 0))
{
  /* Perform operations on pipe. */
}

client_close(reader);
client_close(writer);

Closing Files and Pipes

Closing files and pipes involves a common operation:

void client_close(file_t *file);

When client programs terminate, the freeing of their object capabilities should cause the closure of files and pipes, but programs may choose to close such resources explicitly.

Reading and Writing

Reading and writing files and pipes involves functions resembling the traditional low-level read and write functions:

offset_t client_read(file_t *file, void *buf, offset_t count);
offset_t client_write(file_t *file, const void *buf, offset_t count);

Support for navigation in files is provided using functions resembling the traditional higher-level fseek and ftell functions:

offset_t client_seek(file_t *file, offset_t offset, int whence);
long client_tell(file_t *file);

Accessing Exposed Memory Regions

Although the client library (and the provision of files) employs mapped memory, a function can be used to explicitly reference memory for file access:

void *client_mmap(file_t *file, offset_t position, offset_t length,
                  offset_t start_visible, offset_t end_visible,
                  l4re_rm_flags_t region_flags);

Here, a portion of the indicated file is exposed in a memory region, with the memory region corresponding to the contents of the file starting at the given position in the file and having a span of the given length in bytes.

Additional parameters control masking of the file content. If start_visible and end_visible are both non-zero, then they indicate a visible span in the file. Such limits are applied to the mapped region, with locations corresponding to positions before start_visible and positions after end_visible yielding zero byte values.

The region_flags indicate the memory access properties of the mapped region in the task obtaining it. For example, where a region will be populated with code, the L4RE_DS_F_RX flag would be appropriate, this indicating that the region will be read and also have its contents run or executed.

Note: Currently, when masking is applied to a file, any write operations will cause the modified memory to be copied and modified, as opposed to causing modifications to the underlying file content. This behaviour may eventually be more configurable.

Accessing Pipe Content in Memory Regions

Pipes support a different mechanism for navigation involving the following functions:

long client_current_region(file_t *file);
long client_next_region(file_t *file);

Such navigation functions for files and pipes do not need to be used where the higher-level reading, writing and seeking functions are in use.

Flushing and Synchronisation

For synchronisation purposes, either with the filesystem itself or with other users of the filesystem, a function resembling the traditional fflush function is provided:

long client_flush(file_t *file);

This updates the file data structure with new details of the file size, also updating any altered details of the extent of the data in the currently accessed region of the file.

Notifications

Since files and pipes may be accessed by multiple clients, this being of particular significance for any real use of pipes, notifications can be configured to communicate a change in state to other users of these resources when they are accessed. Directories can also be monitored using notifications.

Notification types are specified using values encoding a number of flags, and the following flags are available for this purpose:

Flag Notification Type
NOTIFY_CONTENT_AVAILABLE Content available to read
NOTIFY_FILE_OPENED File opened in directory
NOTIFY_PEER_CLOSED Other party has closed their endpoint
NOTIFY_SPACE_AVAILABLE Space available for writing

The delivery of notifications is requested by subscribing to notifications for a given resource via a notifier object:

long client_subscribe(file_t *file, notify_flags_t flags, file_notifier_t *notifier);

A notifier object can be common throughout all threads in a task, being obtained using the following function:

file_notifier_t *client_notifier_task();

Alternatively, a local notifier can be created for use within a thread:

file_notifier_t *client_notifier_local();

Local notifiers must be closed when they are no longer needed:

void client_notifier_close(file_notifier_t *notifier);

When notifications are no longer needed, an unsubscribe operation can be invoked:

long client_unsubscribe(file_t *file, file_notifier_t *notifier);

Example

file_notifier_t *notifier = client_notifier_local();
file_t *directory = client_open(filename, O_DIRECTORY);

if (client_opened(directory))
{
  if (!client_subscribe(directory, NOTIFY_FILE_OPENED, notifier))
  {
    if (!client_wait_file(directory, notifier))
    {
      /* File opened in directory. */
    }
  }
}

client_close(directory);
client_notifier_close(notifier);

Blocking Operations

Reading and writing operations can be configured to block if data cannot be read or written respectively. The following function is provided for this purpose:

long client_set_blocking(file_t *file, notify_flags_t flags);

For pipes, blocking behaviour is the default and must be disabled explicitly, either by opening using the O_NONBLOCK flag or by calling client_set_blocking with no flags set.

Blocking behaviour is supported using the notification functionality. When access to a file or pipe cannot be satisfied for a particular operation, such as reading not being able to yield more content or writing not being able to submit more content, the task-level notifier will be used to wait for notifications applicable to the file or pipe involved. Consequently, the access will effectively block until notifications are delivered indicating that the state of the file or pipe has changed, and until it is determined that the change of state will allow the operation to proceed successfully.