libbls 0.3 user guide

Authors: Alexandros Frantzis
Michael Iatrou

Legal Notices

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License (GFDL), Version 1.3 or any later version published by the Free Software Foundation with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.


Table of Contents

1.   Introduction

The libbls library provides an editable data buffer that can contain data from various sources (memory, files) and provide efficient editing operations.

The purpose of this text is to describe how to use the libbls library in your application. It's not supposed to be a complete API reference but rather a high level overview of the API.

For a more detailed API reference refer to the Doxygen documentation of the public API which can be found online (http://libbls.hellug.gr) or in doc/user/html in the libbls tarball.

2.   Building and installing libbls

libbls uses the scons build system. To build libbls change into the libbls directory and type:

$ scons

To install libbls under /usr use (as root):

# scons install prefix=/usr
# ldconfig

To see all available options use:

$ scons -h

If there are packages for your distribution or operating system it is preferable to just use them (remember to install -dev packages!).

3.   Using libbls in your programs

To use libbls in your application you must include the header:

/* Typical libbls installation */
#include <bls/buffer.h>
           or
/* Custom installation */
#include "buffer.h"

Additionally you must make sure that you build your application with the correct flags so that:

  1. The header files can be found (for compilation).
  2. The library can be found (for linking).
  3. You use the same LFS (Large File Support) settings as libbls.

In case of a typical libbls installation all three can be automated using pkg-config:

pkg-config bls-X.Y --cflags
pkg-config bls-X.Y --libs

Note that libbls versions are parallel installable. To build against another installed version of libbls just change the version in the pkg-config command.

4.   General API considerations

4.1.   Error handling

Every function in the API returns an integer that represents the error code of the operation that was performed. An error code of 0 means that the operation completed successfully. Any other value means that the operation failed. To get the string representation of an error code use the function strerror() from the system header string.h.

A typical API call with error checking will look like:

int err;
err = api_func();

if (err)
    printf("%s\n", strerror(err));

4.2.   Object creation

The API is based around objects using opaque data types (usually ending in _t eg bless_buffer_t) to increase encapsulation.

To create an object use its associated constructor functions. The constructor functions create the object (including memory allocation) and return it to the user by means of value-return argument (usually using a double pointer). It is the user's responsibility to pass the address of valid pointer for the function to store the result:

/* prototype: int bless_buffer_new(bless_buffer_t **buf) */

/* Correct creation. Use the address of the buf variable. */
bless_buffer_t *buf;

err = bless_buffer_new(&buf);

/*
 * Incorrect creation. dbuf doesn't point to a valid
 * bless_buffer_t * variable.
 */
bless_buffer_t **dbuf;

err = bless_buffer_new(dbuf);

4.3.   Out of memory situations

libbls dutifully checks all memory allocations it makes internally and gracefully returns ENOMEM if an allocation fails. This, of course, requires that the underlying system can correctly inform libbls when there is not enough memory.

Unfortunately for us, many systems use an optimistic over-committing memory scheme when allocating memory. This means that an allocation can apparently succeed but an access to the supposedly allocated memory may fail!

If you are planning to use libbls (or any other program for that matter) in situations that may lead to out of memory conditions it is advisable to disallow such overcommitting behaviour. Check your operating system's documentation for more information on this matter.

5.   Creating and freeing a buffer

An empty bless_buffer_t is created by using the function bless_buffer_new():

int bless_buffer_new(bless_buffer_t **buf)

and freed by using the function bless_buffer_free():

int bless_buffer_free(bless_buffer_t *buf)

For example:

int err;
bless_buffer_t *buf;

err = bless_buffer_new(&buf);
if (err)
   ...

/* Use the buffer */
   ...

/* Free the buffer */
err = bless_buffer_free(buf);
if (err)
   ...

6.   Editing a buffer

6.1.   Buffer sources

6.1.1.   Types of buffer sources

All data added to the buffer must come from some source. libbls abstracts these sources using the bless_buffer_source_t opaque type.

There are currently two kinds of sources: memory sources and file sources. A memory source represents data from a memory region. A file source represents data from a file (regular or block device).

A file source consumes a small constant amount of memory regardless of the size of the file or how it is used in buffers. This means that one can add large amounts of file data into a buffer even if that data would not normally fit into main memory. This is one of the great advantages of using libbls.

The small price to pay for this feature is that the associated file must not change externally while the file source is still in use.

6.1.2.   Creating a buffer source

Memory and file sources are created using functions bless_buffer_source_memory() and bless_buffer_source_file(), respectively:

int bless_buffer_source_memory(bless_buffer_source_t **src, void *data,
        size_t length, bless_mem_free_func *mem_free);

int bless_buffer_source_file(bless_buffer_source_t **src, int fd,
        bless_file_close_func *file_close);

When a source is created it may assume ownership of the related resources. That means that it takes responsibility for all aspects of the resources' memory management. For a memory source this means freeing the data when it is not needed by the buffer, for a file it means closing the file.

The source cannot magically know how to perform the cleanup actions mentioned above. The mem_free and file_close parameters in the sources' creation functions are used to define the function that should be called to cleanup the resource. The cleanup function is typically one of free or close depending on the source type. It can additionally be a NULL value to denote that no cleanup should take place when the resource is no longer needed.

The above entails that when some resource is used to create a bless_buffer_source_t and a cleanup function is given, the user must not use or even assume the validity of any reference to that resource. This means, for example, that if you create a memory source and you want to be able to access the original data at all times you must either keep another copy of them or use NULL as the data_free function.

The code snippet below shows how to create a buffer source from various resources:

int fd = open("myfile", O_RDONLY);
void *smem = "LIBBLS ROCKS"
void *mem = malloc(10);

bless_buffer_source_t *mem_source;
bless_buffer_source_t *smem_source;
bless_buffer_source_t *file_source;
int err;

/*
 * Memory source with data from "mem". These data must
 * not be used from now on by the user.
 */
err = bless_buffer_source_memory(&mem_source, mem, 10, free);
if (err)
    ...

/*
 * Memory source with data from "smem". These data
 * can be used by the user because they are not freed.
 * (Actually, we cannot free them anyway because they point to
 *  a static string)
 */
err = bless_buffer_source_memory(&smem_source, smem, 14, NULL);
if (err)
    ...

/*
 * File source using file descriptor "fd". The file descriptor must not be
 * used from now on by the user.
 */
err = bless_buffer_source_file(&file_source, fd, close);
if (err)
    ...

6.1.3.   Unreferencing a buffer source

After you are done using a bless_buffer_source_t you must explicitly tell it that you, as the user, are not planning to use it anymore. This is accomplished using the function bless_buffer_source_unref():

int bless_buffer_source_unref(bless_buffer_source_t *src)

For example:

int fd = open("myfile", O_RDONLY);

bless_buffer_source_t *file_source;

/* Create the buffer source */
err = bless_buffer_source_file(&file_source, fd, close);
if (err)
    ...

/* Use buffer source here */

/*
 * We are done using the buffer source. Must not use it
 * from now on.
 */
err = bless_buffer_source_unref(file_source);
if (err)
    ...

The function bless_buffer_source_unref(), as its name suggests, does not directly free a bless_buffer_source_t object. It just says we are not, as users, using it anymore. The buffer source and its resources are only freed when it is not used by neither the user nor internally in the buffers.

If the call to bless_buffer_source_unref() is forgotten, then the buffer source will not be freed even when no buffer is using it, probably resulting in a memory leak.

6.2.   Adding data to a buffer

Data can be either appended to the end of a buffer or inserted in some internal position in the buffer. These are accomplished by using the functions bless_buffer_append() and bless_buffer_insert(), respectively:

int bless_buffer_append(bless_buffer_t *buf, bless_buffer_source_t *src,
        off_t src_offset, off_t length)

int bless_buffer_insert(bless_buffer_t *buf, off_t offset,
        bless_buffer_source_t *src, off_t src_offset, off_t length)

As noted in the previous section data can be added only in the form of buffer sources.

Note that it is an error to try to insert data beyond the end of the buffer. This means that the only way to initially add to an empty buffer is to append to it.

For example:

bless_buffer_t *buf;
int err;

err = bless_buffer_new(&buf);
if (err)
    ...

int fd = open("myfile", O_RDONLY);

bless_buffer_source_t *file_source;

/* Create the buffer source */
err = bless_buffer_source_file(&file_source, fd, close);
if (err)
    ...

/* Use buffer source */
err = bless_buffer_append(buf, file_source, 0, 10);
if (err)
    ...

err = bless_buffer_insert(buf, 5, file_source, 10, 20);
if (err)
    ...

/*
 * We are done using the buffer source. Must not use it
 * from now on.
 */
err = bless_buffer_source_unref(file_source);
if (err)
    ...

6.3.   Deleting data from a buffer

A data range can be deleted from a buffer by using the bless_buffer_delete() function:

int bless_buffer_delete(bless_buffer_t *buf, off_t offset, off_t length)

The offset and length arguments must represent a valid range in the buffer.

For example:

/* Assume "buf" is initialized and contains some data */
bless_buffer_t *buf;
int err;

/* Delete 10 bytes starting at offset 5 */
err = bless_buffer_delete(buf, 5, 10);
if (err)
    ...

6.4.   Undoing and redoing operations

libbls buffers have built-in undo-redo capabilities. The functions used to perform undo and redo are unsuprisingly named bless_buffer_undo and bless_buffer_redo:

int bless_buffer_undo(bless_buffer_t *buf);

int bless_buffer_redo(bless_buffer_t *buf);

There are two related functions that query if there are any actions to undo or redo:

int bless_buffer_can_undo(bless_buffer_t *buf, int *can_undo);

int bless_buffer_can_redo(bless_buffer_t *buf, int *can_redo);

As usual for a linear undo-redo scheme, when you edit the buffer the list of actions that can be redone is cleared.

The maximum number of actions that can be undone/redone is controlled by the BLESS_BUF_UNDO_LIMIT buffer option (see Setting buffer options). The default value for this options is "infinite" which means that there is no undo limiting.

Note that undo/redo capabilities incur a performance and memory overhead so if you don't need them it is recommended that you turn them off (set BLESS_BUF_UNDO_LIMIT to "0").

By default, libbls tries to retain as much as possible of undo/redo history after a save. This is controlled by the BLESS_BUF_UNDO_AFTER_SAVE buffer option (see Setting buffer options).

6.5.   Grouping multiple buffer actions

It is sometimes useful to be able to consider multiple buffer actions as one single action in terms of undoing and redoing. For example, deleting all 0x0d bytes from a file may be considered a single high-level action that is atomically done or undone.

To support such use cases, libbls provides the bless_buffer_begin_multi_action(), and bless_buffer_end_multi_action() functions:

int bless_buffer_begin_multi_action(bless_buffer_t *buf);

int bless_buffer_end_multi_action(bless_buffer_t *buf);

Every buffer edit action (append, insert or delete) that is performed between calls to bless_buffer_begin_multi_action() and bless_buffer_end_multi_action() is considered part of the same single multi-action. When undoing or redoing this multi-action all simple actions that comprise it are undone or redone.

Actions that are part of a multi-action do not cause an event to be emitted when they are performed (see Monitoring buffer events). A single BLESS_BUFFER_EVENT_EDIT event with action of BLESS_BUFFER_ACTION_MULTI is emitted when the function bless_buffer_end_multi_action() is called.

An example of how to group multiple actions:

/* Assume "buf" is initialized and contains some data */
bless_buffer_t *buf;

/* Assume "source" points to a valid data source */
bless_buffer_source_t *source;

int err;

/*
 * Begin multi action. All edit actions are considered part of
 * a single compound action until we end the multi action.
 */
err = bless_buffer_begin_multi_action(buf);
if (err)
    ...

err = bless_buffer_delete(buf, 2, 1);
if (err)
    ...

err = bless_buffer_append(buf, source, 0, 10);
if (err)
    ...

err = bless_buffer_insert(buf, 6, source, 13, 2);
if (err)
    ...

/* End multi action */
err = bless_buffer_end_multi_action(buf);
if (err)
    ...

/*
 * After the undo the buffer is in its initial state (before the
 * first delete action).
 */
err = bless_buffer_undo(buf);
if (err)
    ...

/*
 * After the redo the buffer is back to its final state (after the
 * last insert action).
 */
err = bless_buffer_redo(buf);
if (err)
    ...

7.   Reading data from the buffer

Data from the buffer is read by copying it to user-provided memory area. This is accomplished by using the function bless_buffer_read():

int bless_buffer_read(bless_buffer_t *src, off_t src_offset, void *dst,
        size_t dst_offset, size_t length)

For example:

/* Assume "buf" is initialized and contains some data */
bless_buffer_t *buf;
int err;

void *mem = malloc(13);

/*
 * Read 10 bytes starting at offset 5 in the buffer and store them in memory
 * area starting at "mem" + 3.
 */
err = bless_buffer_read(buf, 5, mem, 3, 10);
if (err)
    ...

8.   Saving the buffer contents to a file

The whole buffer contents can be saved to a user-provided file by using the bless_buffer_save() function:

int bless_buffer_save(bless_buffer_t *buf, int fd, bless_progress_func *func)

The file descriptor fd must be opened with both read and write permissions.

[Progress callback not implemented yet, details may change] The func function, if it is not NULL, is periodically called from within the save function to report the progress of the save operation. Its return value is checked by the save function to determine if the save should continue or not. Note, that the save function is free to ignore the cancel request if that would leave the saved file in an inconsistent state.

Note that in some situations, if the buffer contains data from the target file of the save operation, additional temporary storage space may be needed. Main memory is utilized first, followed by temporary files on disk, if memory is not sufficient. The directory used to store temporary files can be set using the BLESS_BUF_TMP_DIR option (see Setting buffer options).

By default, libbls tries to retain as much as possible of undo/redo history after a save. This is controlled by the BLESS_BUF_UNDO_AFTER_SAVE buffer option (see Setting buffer options).

An example of how to save a buffer:

/* Assume "buf" is initialized and contains some data */
bless_buffer_t *buf;
int err;

int wfd = creat("target", 0644);

/*
 * Save buffer contents to a file
 */
err = bless_buffer_save(buf, wfd, NULL);
if (err)
    ...

9.   Setting buffer options

Some aspects of the buffer can be configured using buffer options. To set or a buffer option use the bless_buffer_set_option() and bless_buffer_get_option() functions respectively:

int bless_buffer_set_option(bless_buffer_t *buf, bless_buffer_option_t opt,
       char *val)

int bless_buffer_get_option(bless_buffer_t *buf, char **val,
       bless_buffer_option_t opt)

Note that the string value returned by bless_buffer_get_option() is the actually value that is used internally (not a copy). If you alter it in any way the world may come to a horrific end (so don't do it!).

The options that are currently implemented are:

BLESS_BUF_TMP_DIR
The directory to store temporary files in (see Saving the buffer contents to a file). The default value is "/tmp".
BLESS_BUF_UNDO_LIMIT
The maximum number of actions that can be undone or redone. Acceptable values are strings representing natural numbers or "infinite" to turn off undo limiting. A value of "0" turns off undo/redo capabilities. The default value is "infinite" (no undo limiting).
BLESS_BUF_UNDO_AFTER_SAVE
Whether to be able to undo/redo after saving a buffer. The acceptable values are "always", "never" and "best_effort". If the value is "always" a save won't be performed unless it is guaranteed that all undo/redo actions can be safely performed after the save. A value of "never" clears all undo redo actions after a successful save. If the value is "best_effort" libbls does its best to keep as much history as it can (eg what fits in memory). The default value is "best_effort".

An example of setting a buffer option:

/* Assume "buf" is initialized */
bless_buffer_t *buf;
int err;

err = bless_buffer_set_option(buf, BLESS_BUF_TMP_DIR, "/mydir");
if (err)
    ...

char *val;

err = bless_buffer_get_option(buf, &val, BLESS_BUF_TMP_DIR);
if (err)
    ...

/* Don't change the string stored in "val"! */

10.   Monitoring buffer events

It is sometimes useful to know when something interesting has happened to a bless_buffer_t. The buffer has the ability to inform any interested parties by means of a callback function:

typedef void (bless_buffer_event_func_t)(
    bless_buffer_t *buf,
    struct bless_buffer_event_info *info,
    void *user_data
    );

This callback function can be set using the bless_buffer_set_event_callback() function:

int bless_buffer_set_event_callback(
    bless_buffer_t *buf,
    bless_buffer_event_func_t *func,
    void *user_data
    );

Note that you can disable the callback function by setting it to NULL.

When something interesting happens to the buffer the callback function is called with an appropriately filled struct bless_buffer_event_info:

/**
 * Information about a buffer event.
 */
struct bless_buffer_event_info {
    int event_type;     /**< The event type (BLESS_BUFFER_EVENT_*) */
    int action_type;    /**< The action type (BLESS_BUFFER_ACTION_*) */
    off_t range_start;  /**< The start of the range of the buffer that
                          was affected by an event */
    off_t range_length; /**< The length of the range of the buffer that
                          was affected by an event */
    int save_fd;        /**< The descriptor of the file the buffer was saved to */
};

The currently implemented events that can be found in the event_type field are:

BLESS_BUFFER_EVENT_EDIT
The buffer has been edited in some way. The action_type field gives more information about the kind of editing (eg BLESS_BUFFER_INSERT) and the range_start and range_length contain the range of the buffer that was affected by the editing operation.
BLESS_BUFFER_EVENT_UNDO
The last buffer action has been undone. As with BLESS_BUFFER_EVENT_EDIT the action_type, range_start and range_length fields give more information about the undone action.
BLESS_BUFFER_EVENT_REDO
The last undone buffer action has been redone. As with BLESS_BUFFER_EVENT_EDIT the action_type, range_start and range_length fields give more information about the redone action.
BLESS_BUFFER_EVENT_SAVE
The buffer has been successfully saved to a file. The file descriptor of the file is placed in the save_fd field. The contents of the other fields should be ignored.
BLESS_BUFFER_EVENT_DESTROY
The buffer is about to be destroyed (freed). No fields are valid for this event.

The currently implemented actions for the action_type field are:

BLESS_BUFFER_ACTION_APPEND

BLESS_BUFFER_ACTION_INSERT

BLESS_BUFFER_ACTION_DELETE

BLESS_BUFFER_ACTION_MULTI

An example of how to use the callback function:

void event_callback(bless_buffer_t *buf, struct bless_buffer_event_info *info,
                    void *user_data)
{
    switch(info->event_type) {
        case BLESS_BUFFER_EVENT_EDIT:
        case BLESS_BUFFER_EVENT_UNDO:
        case BLESS_BUFFER_EVENT_REDO:
            printf("Buffer [%p] changed at offset %lld\n", buf, info->range_start);
            break;

        case BLESS_BUFFER_EVENT_SAVE:
            printf("Buffer [%p] has been save to fd=%d\n", buf, info->save_fd);
            break;

        case BLESS_BUFFER_EVENT_DESTROY:
            printf("Buffer [%p] is about to be destroyed. Bye bye.\n", buf);
            break;

        default:
            printf("Buffer [%p] has issued unexpected event %d\n", buf, info->event_type);
            break;
    }
}

int main(void)
{
    bless_buffer_t *buf;
    int err;

    /* Initialize buffer... */

    /* Enable event reporting */
    err = bless_buffer_set_event_callback(buf, event_callback, NULL);
    if (err)
        ...

    /* Use buffer... */

    /* Disable event reporting */
    err = bless_buffer_set_event_callback(buf, NULL, NULL);
    if (err)
        ...
}