Merge the new afs code.
authorAndre Noll <maan@systemlinux.org>
Sat, 8 Sep 2007 09:17:19 +0000 (11:17 +0200)
committerAndre Noll <maan@systemlinux.org>
Sat, 8 Sep 2007 09:17:19 +0000 (11:17 +0200)
31 files changed:
afs.c [new file with mode: 0644]
afs.h [new file with mode: 0644]
aft.c [new file with mode: 0644]
attribute.c [new file with mode: 0644]
audiod.c
blob.c [new file with mode: 0644]
configure.ac
error.h
gcc-compat.h
gui.c
gui_common.c
gui_common.h [new file with mode: 0644]
hash.h [new file with mode: 0644]
mood.c [new file with mode: 0644]
mp3_afh.c
osl.c [new file with mode: 0644]
osl.h [new file with mode: 0644]
osl_core.h [new file with mode: 0644]
para.h
playlist.c [new file with mode: 0644]
playlist_selector.c
portable_io.h [new file with mode: 0644]
rbtree.c [new file with mode: 0644]
rbtree.h [new file with mode: 0644]
score.c [new file with mode: 0644]
sdl_gui.c
sha1.c [new file with mode: 0644]
sha1.h [new file with mode: 0644]
stat.c
string.c
string.h

diff --git a/afs.c b/afs.c
new file mode 100644 (file)
index 0000000..c5673c6
--- /dev/null
+++ b/afs.c
@@ -0,0 +1,902 @@
+#include "para.h"
+#include "error.h"
+#include <dirent.h> /* readdir() */
+#include <sys/mman.h>
+#include <sys/time.h>
+
+
+#include "net.h"
+#include "afs.h"
+#include "ipc.h"
+#include "string.h"
+
+/** \file afs.c Paraslash's audio file selector. */
+
+/**
+ * Compare two osl objects of string type.
+ *
+ * \param obj1 Pointer to the first object.
+ * \param obj2 Pointer to the second object.
+ *
+ * In any case, only \p MIN(obj1->size, obj2->size) characters of each string
+ * are taken into account.
+ *
+ * \return It returns an integer less than, equal to, or greater than zero if
+ * \a obj1 is found, respectively, to be less than, to match, or be greater than
+ * obj2.
+ *
+ * \sa strcmp(3), strncmp(3), osl_compare_func.
+ */
+int string_compare(const struct osl_object *obj1, const struct osl_object *obj2)
+{
+       const char *str1 = (const char *)obj1->data;
+       const char *str2 = (const char *)obj2->data;
+       return strncmp(str1, str2, PARA_MIN(obj1->size, obj2->size));
+}
+
+/** The osl tables used by afs. \sa blob.c */
+enum afs_table_num {
+       /** Contains audio file information. See aft.c. */
+       TBLNUM_AUDIO_FILES,
+       /** The table for the paraslash attributes. See attribute.c. */
+       TBLNUM_ATTRIBUTES,
+       /**
+        * Paraslash's scoring system is based on Gaussian normal
+        * distributions, and the relevant data is stored in the rbtrees of an
+        * osl table containing only volatile columns.  See score.c for
+        * details.
+        */
+       TBLNUM_SCORES,
+       /**
+        * A standard blob table containing the mood definitions. For details
+        * see mood.c.
+        */
+       TBLNUM_MOODS,
+       /** A blob table containing lyrics on a per-song basis. */
+       TBLNUM_LYRICS,
+       /** Another blob table for images (for example album cover art). */
+       TBLNUM_IMAGES,
+       /** Yet another blob table for storing standard playlists. */
+       TBLNUM_PLAYLIST,
+       /** How many tables are in use? */
+       NUM_AFS_TABLES
+};
+
+static struct table_info afs_tables[NUM_AFS_TABLES];
+
+
+/**
+ * A wrapper for strtol(3).
+ *
+ * \param str The string to be converted to a long integer.
+ * \param result The converted value is stored here.
+ *
+ * \return Positive on success, -E_ATOL on errors.
+ *
+ * \sa strtol(3), atoi(3).
+ */
+int para_atol(const char *str, long *result)
+{
+       char *endptr;
+       long val;
+       int ret, base = 10;
+
+       errno = 0; /* To distinguish success/failure after call */
+       val = strtol(str, &endptr, base);
+       ret = -E_ATOL;
+       if (errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
+               goto out; /* overflow */
+       if (errno != 0 && val == 0)
+               goto out; /* other error */
+       if (endptr == str)
+               goto out; /* No digits were found */
+       if (*endptr != '\0')
+               goto out; /* Further characters after number */
+       *result = val;
+       ret = 1;
+out:
+       return ret;
+}
+
+/**
+ * Struct to let para_server call a function specified from child context.
+ *
+ * Commands that need to change the state of para_server can't
+ * change the relevant data structures directly because commands
+ * are executed in a child process, i.e. they get their own
+ * virtual address space. This structure must be used to let
+ * para_server (i.e. the parent process) call a function specified
+ * by the child (the command handler).
+ *
+ * \sa fork(2), ipc.c.
+ */
+struct callback_data {
+       /** The function to be called. */
+       callback_function *handler;
+       /** The sma for the parameters of the callback function. */
+       int query_shmid;
+       /** The size of the query sma. */
+       size_t query_size;
+       /** If the callback produced a result, it is stored in this sma. */
+       int result_shmid;
+       /** The size of the result sma. */
+       size_t result_size;
+       /** The return value of the callback function. */
+       int callback_ret;
+       /** The return value of the callback() procedure. */
+       int sma_ret;
+};
+
+static struct callback_data *shm_callback_data;
+static int callback_mutex;
+static int child_mutex;
+static int result_mutex;
+
+/**
+ * Ask the parent process to call a given function.
+ *
+ * \param f The function to be called.
+ * \param query Pointer to arbitrary data for the callback.
+ * \param result Callback result will be stored here.
+ *
+ * This function creates a shared memory area, copies the buffer pointed to by
+ * \a buf to that area and notifies the parent process that  \a f should be
+ * called ASAP. It provides proper locking via semaphores to protect against
+ * concurent access to the shared memory area and against concurrent access by
+ * another child process that asks to call the same function.
+ *
+ * \return Negative, if the shared memory area could not be set up. The return
+ * value of the callback function otherwise.
+ *
+ * \sa shm_new(), shm_attach(), shm_detach(), mutex_lock(), mutex_unlock(),
+ * shm_destroy(), struct callback_data, send_option_arg_callback_request(),
+ * send_standard_callback_request().
+ */
+int send_callback_request(callback_function *f, struct osl_object *query,
+               struct osl_object *result)
+{
+       struct callback_data cbd = {.handler = f};
+       int ret;
+       void *query_sma;
+
+       assert(query->data && query->size);
+       ret = shm_new(query->size);
+       if (ret < 0)
+               return ret;
+       cbd.query_shmid = ret;
+       cbd.query_size = query->size;
+       ret = shm_attach(cbd.query_shmid, ATTACH_RW, &query_sma);
+       if (ret < 0)
+               goto out;
+       memcpy(query_sma, query->data, query->size);
+       ret = shm_detach(query_sma);
+       if (ret < 0)
+               goto out;
+       /* prevent other children from interacting */
+       mutex_lock(child_mutex);
+       /* prevent parent from messing with shm_callback_data. */
+       mutex_lock(callback_mutex);
+       /* all three mutexes are locked, set parameters for callback */
+       *shm_callback_data = cbd;
+       /* unblock parent */
+       mutex_unlock(callback_mutex);
+       kill(getppid(), SIGUSR1); /* wake up parent */
+       /*
+        * At this time only the parent can run. It will execute our callback
+        * and unlock the result_mutex when ready to indicate that the child
+        * may use the result. So let's sleep on this mutex.
+        */
+       mutex_lock(result_mutex);
+       /* No need to aquire the callback mutex again */
+       ret = shm_callback_data->sma_ret;
+       if (ret < 0) /* sma problem, callback might not have been executed */
+               goto unlock_child_mutex;
+       if (shm_callback_data->result_shmid >= 0) { /* parent provided a result */
+               void *sma;
+               ret = shm_attach(shm_callback_data->result_shmid, ATTACH_RO,
+                       &sma);
+               if (ret >= 0) {
+                       if (result) { /* copy result */
+                               result->size = shm_callback_data->result_size;
+                               result->data = para_malloc(result->size);
+                               memcpy(result->data, sma, result->size);
+                               ret = shm_detach(sma);
+                               if (ret < 0)
+                                       PARA_ERROR_LOG("can not detach result\n");
+                       } else
+                               PARA_WARNING_LOG("no result pointer\n");
+               } else
+                       PARA_ERROR_LOG("attach result failed: %d\n", ret);
+               if (shm_destroy(shm_callback_data->result_shmid) < 0)
+                       PARA_ERROR_LOG("destroy result failed\n");
+       } else { /* no result from callback */
+               if (result) {
+                       PARA_WARNING_LOG("callback has no result\n");
+                       result->data = NULL;
+                       result->size = 0;
+               }
+       }
+       ret = shm_callback_data->callback_ret;
+unlock_child_mutex:
+       /* give other children a chance */
+       mutex_unlock(child_mutex);
+out:
+       if (shm_destroy(cbd.query_shmid) < 0)
+               PARA_ERROR_LOG("%s\n", "shm destroy error");
+       PARA_DEBUG_LOG("callback_ret: %d\n", ret);
+       return ret;
+}
+
+/**
+ * Send a callback request passing an options structure and an argument vector.
+ *
+ * \param options pointer to an arbitrary data structure.
+ * \param argc Argument count.
+ * \param argv Standard argument vector.
+ * \param f The callback function.
+ * \param result The result of the query is stored here.
+ *
+ * Some commands have a couple of options that are parsed in child context for
+ * syntactic correctness and are stored in a special options structure for that
+ * command. This function allows to pass such a structure together with a list
+ * of further arguments (often a list of audio files) to the parent process.
+ *
+ * \sa send_standard_callback_request(), send_callback_request().
+ */
+int send_option_arg_callback_request(struct osl_object *options,
+               int argc, const char **argv, callback_function *f,
+               struct osl_object *result)
+{
+       char *p;
+       int i, ret;
+       struct osl_object query = {.size = options? options->size : 0};
+
+       for (i = 0; i < argc; i++)
+               query.size += strlen(argv[i]) + 1;
+       query.data = para_malloc(query.size);
+       p = query.data;
+       if (options) {
+               memcpy(query.data, options->data, options->size);
+               p += options->size;
+       }
+       for (i = 0; i < argc; i++) {
+               strcpy(p, argv[i]); /* OK */
+               p += strlen(argv[i]) + 1;
+       }
+       ret = send_callback_request(f, &query, result);
+       free(query.data);
+       return ret;
+}
+
+/**
+ * Send a callback request with an argument vector only.
+ *
+ * \param argc The same meaning as in send_option_arg_callback_request().
+ * \param argv The same meaning as in send_option_arg_callback_request().
+ * \param f The same meaning as in send_option_arg_callback_request().
+ * \param result The same meaning as in send_option_arg_callback_request().
+ *
+ * This is similar to send_option_arg_callback_request(), but no options buffer
+ * is passed to the parent process.
+ *
+ * \return The return value of the underlying call to
+ * send_option_arg_callback_request().
+ */
+int send_standard_callback_request(int argc, const char **argv,
+               callback_function *f, struct osl_object *result)
+{
+       return send_option_arg_callback_request(NULL, argc, argv, f, result);
+}
+
+/*
+ * write input from fd to dynamically allocated char array,
+ * but maximal max_size byte. Return size.
+ */
+static int fd2buf(int fd, char **buf, int max_size)
+{
+       const size_t chunk_size = 1024;
+       size_t size = 2048;
+       char *p;
+       int ret;
+
+       *buf = para_malloc(size * sizeof(char));
+       p = *buf;
+       while ((ret = read(fd, p, chunk_size)) > 0) {
+               p += ret;
+               if ((p - *buf) + chunk_size >= size) {
+                       char *tmp;
+
+                       size *= 2;
+                       if (size > max_size) {
+                               ret = -E_INPUT_TOO_LARGE;
+                               goto out;
+                       }
+                       tmp = para_realloc(*buf, size);
+                       p = (p - *buf) + tmp;
+                       *buf = tmp;
+               }
+       }
+       if (ret < 0) {
+               ret = -E_READ;
+               goto out;
+       }
+       ret = p - *buf;
+out:
+       if (ret < 0 && *buf)
+               free(*buf);
+       return ret;
+}
+
+/**
+ * Read from stdin, and send the result to the parent process.
+ *
+ * \param arg_obj Pointer to the arguments to \a f.
+ * \param f The callback function.
+ * \param max_len Don't read more than that many bytes from stdin.
+ * \param result The result of the query is stored here.
+ *
+ * This function is used by commands that wish to let para_server store
+ * arbitrary data specified by the user (for instance the add_blob family of
+ * commands). First, at most \a max_len bytes are read from stdin, the result
+ * is concatenated with the buffer given by \a arg_obj, and the combined buffer
+ * is made available to the parent process via shared memory.
+ *
+ * \return Negative on errors, the return value of the underlying call to
+ * send_callback_request() otherwise.
+ */
+int stdin_command(struct osl_object *arg_obj, callback_function *f,
+               unsigned max_len, struct osl_object *result)
+{
+       char *stdin_buf;
+       size_t stdin_len;
+       struct osl_object query;
+       int ret = fd2buf(STDIN_FILENO, &stdin_buf, max_len);
+
+       if (ret < 0)
+               return ret;
+       stdin_len = ret;
+       query.size = arg_obj->size + stdin_len;
+       query.data = para_malloc(query.size);
+       memcpy(query.data, arg_obj->data, arg_obj->size);
+       memcpy((char *)query.data + arg_obj->size, stdin_buf, stdin_len);
+       free(stdin_buf);
+       ret = send_callback_request(f, &query, result);
+       free(query.data);
+       return ret;
+}
+
+static void para_init_random_seed(void)
+{
+       struct timeval now;
+       unsigned int seed;
+
+       gettimeofday(&now, NULL);
+       seed = now.tv_usec;
+       srand(seed);
+}
+
+/**
+ * Open the audio file with highest score.
+ *
+ * \param afd Audio file data is returned here.
+ *
+ * This stores all information for streaming the "best" audio file
+ * in the \a afd structure.
+ *
+ * \return Positive on success, negative on errors.
+ *
+ * \sa close_audio_file(), open_and_update_audio_file().
+ */
+int open_next_audio_file(struct audio_file_data *afd)
+{
+       struct osl_row *aft_row;
+       int ret;
+       for (;;) {
+               ret = score_get_best(&aft_row, &afd->score);
+               if (ret < 0)
+                       return ret;
+               ret = open_and_update_audio_file(aft_row, afd);
+               if (ret >= 0)
+                       return ret;
+       }
+}
+
+/**
+ * Free all resources which were allocated by open_next_audio_file().
+ *
+ * \param afd The structure previously filled in by open_next_audio_file().
+ *
+ * \return The return value of the underlying call to para_munmap().
+ *
+ * \sa open_next_audio_file().
+ */
+int close_audio_file(struct audio_file_data *afd)
+{
+       free(afd->afhi.chunk_table);
+       return para_munmap(afd->map.data, afd->map.size);
+}
+
+static void play_loop(enum play_mode current_play_mode)
+{
+       int i, ret;
+       struct audio_file_data afd;
+
+       afd.current_play_mode = current_play_mode;
+       for (i = 0; i < 0; i++) {
+               ret = open_next_audio_file(&afd);
+               if (ret < 0) {
+                       PARA_ERROR_LOG("failed to open next audio file: %d\n", ret);
+                       return;
+               }
+               PARA_NOTICE_LOG("next audio file: %s, score: %li\n", afd.path, afd.score);
+               sleep(1);
+               close_audio_file(&afd);
+       }
+}
+
+static enum play_mode init_admissible_files(void)
+{
+       int ret;
+       char *given_mood, *given_playlist;
+
+       given_mood = "mood_that_was_given_at_the_command_line";
+       given_playlist = "given_playlist";
+
+       if (given_mood) {
+               ret = mood_open(given_mood);
+               if (ret >= 0) {
+                       if (given_playlist)
+                               PARA_WARNING_LOG("ignoring playlist %s\n",
+                                       given_playlist);
+                       return PLAY_MODE_MOOD;
+               }
+       }
+       if (given_playlist) {
+               ret = playlist_open(given_playlist);
+               if (ret >= 0)
+                       return PLAY_MODE_PLAYLIST;
+       }
+       ret = mood_open(NULL); /* open first available mood */
+       if (ret >= 0)
+               return PLAY_MODE_MOOD;
+       mood_open(""); /* open dummy mood, always successful */
+       return PLAY_MODE_MOOD;
+}
+
+static int afs_init(void)
+{
+       int ret, shmid;
+       void *shm_area;
+       enum play_mode current_play_mode;
+
+       para_init_random_seed();
+
+       ret = attribute_init(&afs_tables[TBLNUM_ATTRIBUTES]);
+       PARA_DEBUG_LOG("ret %d\n", ret);
+       if (ret < 0)
+               return ret;
+       ret = moods_init(&afs_tables[TBLNUM_MOODS]);
+       if (ret < 0)
+               goto moods_init_error;
+       ret = playlists_init(&afs_tables[TBLNUM_PLAYLIST]);
+       if (ret < 0)
+               goto playlists_init_error;
+       ret = lyrics_init(&afs_tables[TBLNUM_LYRICS]);
+       if (ret < 0)
+               goto lyrics_init_error;
+       ret = images_init(&afs_tables[TBLNUM_IMAGES]);
+       if (ret < 0)
+               goto images_init_error;
+       ret = score_init(&afs_tables[TBLNUM_SCORES]);
+       if (ret < 0)
+               goto score_init_error;
+       ret = aft_init(&afs_tables[TBLNUM_AUDIO_FILES]);
+       if (ret < 0)
+               goto aft_init_error;
+
+       current_play_mode = init_admissible_files();
+       play_loop(current_play_mode);
+
+       ret = shm_new(sizeof(struct callback_data));
+       if (ret < 0)
+               return ret;
+       shmid = ret;
+       ret = shm_attach(shmid, ATTACH_RW, &shm_area);
+       if (ret < 0)
+               return ret;
+       shm_callback_data = shm_area;
+       ret = mutex_new();
+       if (ret < 0)
+               return ret;
+       callback_mutex = ret;
+       ret = mutex_new();
+       if (ret < 0)
+               return ret;
+       child_mutex = ret;
+       ret = mutex_new();
+       if (ret < 0)
+               return ret;
+       result_mutex = ret;
+       mutex_lock(result_mutex);
+       return 1;
+aft_init_error:
+       score_shutdown(OSL_MARK_CLEAN);
+score_init_error:
+       images_shutdown(OSL_MARK_CLEAN);
+images_init_error:
+       lyrics_shutdown(OSL_MARK_CLEAN);
+lyrics_init_error:
+       playlists_shutdown(OSL_MARK_CLEAN);
+playlists_init_error:
+       moods_shutdown(OSL_MARK_CLEAN);
+moods_init_error:
+       attribute_shutdown(OSL_MARK_CLEAN);
+       return ret;
+}
+
+static uint32_t afs_socket_cookie;
+static int para_random(unsigned max)
+{
+       return ((max + 0.0) * (rand() / (RAND_MAX + 1.0)));
+}
+
+int setup(void)
+{
+       int ret, afs_server_socket[2];
+
+       para_init_random_seed();
+       ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, afs_server_socket);
+       if (ret < 0)
+               exit(EXIT_FAILURE);
+       afs_socket_cookie = para_random((uint32_t)-1);
+       ret = fork();
+       if (ret < 0)
+               exit(EXIT_FAILURE);
+       if (!ret) { /* child (afs) */
+               char *socket_name = "/tmp/afs_command_socket";
+               struct sockaddr_un unix_addr;
+               int fd;
+
+               unlink(socket_name);
+               ret = create_local_socket(socket_name, &unix_addr,
+                       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH);
+               if (ret < 0)
+                       exit(EXIT_FAILURE);
+               fd = ret;
+               if (listen(fd , 5) < 0) {
+                       PARA_EMERG_LOG("%s", "can not listen on socket\n");
+                       exit(EXIT_FAILURE);
+               }
+               ret = afs_init();
+               if (ret < 0)
+                       exit(EXIT_FAILURE);
+               PARA_NOTICE_LOG("accepting\n");
+               ret = para_accept(fd, &unix_addr, sizeof(struct sockaddr_un));
+               return ret;
+       }
+       ret = fork();
+       if (ret < 0)
+               exit(EXIT_FAILURE);
+       if (!ret) { /* child (handler) */
+               PARA_NOTICE_LOG("reading stdin\n");
+               for (;;) {
+                       char buf[255];
+                       read(0, buf, 255);
+                       PARA_NOTICE_LOG("read: %s\n", buf);
+               }
+
+       }
+       for (;;) {
+               sleep(10);
+               PARA_NOTICE_LOG("sending next requerst\n");
+       }
+}
+
+
+static int create_all_tables(void)
+{
+       int i, ret;
+
+       for (i = 0; i < NUM_AFS_TABLES; i++) {
+               struct table_info *ti = afs_tables + i;
+
+               if (ti->flags & TBLFLAG_SKIP_CREATE)
+                       continue;
+               ret = osl_create_table(ti->desc);
+               if (ret < 0)
+                       return ret;
+       }
+       return 1;
+}
+
+/* TODO load tables after init */
+static int com_init(__a_unused int fd, int argc, const char **argv)
+{
+       int i, j, ret;
+       if (argc == 1)
+               return create_all_tables();
+       for (i = 1; i < argc; i++) {
+               for (j = 0; j < NUM_AFS_TABLES; j++) {
+                       struct table_info *ti = afs_tables + j;
+
+                       if (ti->flags & TBLFLAG_SKIP_CREATE)
+                               continue;
+                       if (strcmp(argv[i], ti->desc->name))
+                               continue;
+                       PARA_NOTICE_LOG("creating table %s\n", argv[i]);
+                       ret = osl_create_table(ti->desc);
+                       if (ret < 0)
+                               return ret;
+                       break;
+               }
+               if (j == NUM_AFS_TABLES)
+                       return -E_BAD_TABLE_NAME;
+       }
+       return 1;
+}
+/** Describes a command of para_server. */
+struct command {
+       /** The name of the command. */
+       const char *name;
+       /** The handler function. */
+       int (*handler)(int fd, int argc, const char **argv);
+};
+
+static struct command cmd[] = {
+{
+       .name = "add",
+       .handler = com_add,
+},
+{
+       .name = "addlyr",
+       .handler = com_addlyr,
+},
+{
+       .name = "addimg",
+       .handler = com_addimg,
+},
+{
+       .name = "addmood",
+       .handler = com_addmood,
+},
+{
+       .name = "addpl",
+       .handler = com_addpl,
+},
+{
+       .name = "catlyr",
+       .handler = com_catlyr,
+},
+{
+       .name = "catimg",
+       .handler = com_catimg,
+},
+{
+       .name = "mvimg",
+       .handler = com_mvimg,
+},
+{
+       .name = "mvlyr",
+       .handler = com_mvlyr,
+},
+{
+       .name = "mvmood",
+       .handler = com_mvmood,
+},
+{
+       .name = "mvpl",
+       .handler = com_mvpl,
+},
+{
+       .name = "catmood",
+       .handler = com_catmood,
+},
+{
+       .name = "catpl",
+       .handler = com_catpl,
+},
+{
+       .name = "rmatt",
+       .handler = com_rmatt,
+},
+{
+       .name = "init",
+       .handler = com_init,
+},
+{
+       .name = "lsatt",
+       .handler = com_lsatt,
+},
+{
+       .name = "ls",
+       .handler = com_afs_ls,
+},
+{
+       .name = "lslyr",
+       .handler = com_lslyr,
+},
+{
+       .name = "lsimg",
+       .handler = com_lsimg,
+},
+{
+       .name = "lsmood",
+       .handler = com_lsmood,
+},
+{
+       .name = "lspl",
+       .handler = com_lspl,
+},
+{
+       .name = "setatt",
+       .handler = com_setatt,
+},
+{
+       .name = "addatt",
+       .handler = com_addatt,
+},
+{
+       .name = "rm",
+       .handler = com_afs_rm,
+},
+{
+       .name = "rmlyr",
+       .handler = com_rmlyr,
+},
+{
+       .name = "rmimg",
+       .handler = com_rmimg,
+},
+{
+       .name = "rmmood",
+       .handler = com_rmmood,
+},
+{
+       .name = "rmpl",
+       .handler = com_rmpl,
+},
+{
+       .name = "touch",
+       .handler = com_touch,
+},
+{
+       .name = NULL,
+}
+};
+
+static void call_callback(void)
+{
+       struct osl_object query, result = {.data = NULL};
+       int ret, ret2;
+
+       shm_callback_data->result_shmid = -1; /* no result */
+       ret = shm_attach(shm_callback_data->query_shmid, ATTACH_RW,
+               &query.data);
+       if (ret < 0)
+               goto out;
+       query.size = shm_callback_data->query_size;
+       shm_callback_data->callback_ret = shm_callback_data->handler(&query,
+               &result);
+       if (result.data && result.size) {
+               void *sma;
+               ret = shm_new(result.size);
+               if (ret < 0)
+                       goto detach_query;
+               shm_callback_data->result_shmid = ret;
+               shm_callback_data->result_size = result.size;
+               ret = shm_attach(shm_callback_data->result_shmid, ATTACH_RW, &sma);
+               if (ret < 0)
+                       goto destroy_result;
+               memcpy(sma, result.data, result.size);
+               ret = shm_detach(sma);
+               if (ret < 0) {
+                       PARA_ERROR_LOG("detach result failed\n");
+                       goto destroy_result;
+               }
+       }
+       ret = 1;
+       goto detach_query;
+destroy_result:
+       if (shm_destroy(shm_callback_data->result_shmid) < 0)
+               PARA_ERROR_LOG("destroy result failed\n");
+       shm_callback_data->result_shmid = -1;
+detach_query:
+       free(result.data);
+       ret2 = shm_detach(query.data);
+       if (ret2 < 0) {
+               PARA_ERROR_LOG("detach query failed\n");
+               if (ret >= 0)
+                       ret = ret2;
+       }
+out:
+       if (ret < 0)
+               PARA_ERROR_LOG("sma error %d\n", ret);
+       shm_callback_data->sma_ret = ret;
+       shm_callback_data->handler = NULL;
+       mutex_unlock(result_mutex); /* wake up child */
+}
+
+static void dummy(__a_unused int s)
+{}
+
+static void afs_shutdown(enum osl_close_flags flags)
+{
+       score_shutdown(flags);
+       attribute_shutdown(flags);
+       mood_close();
+       playlist_close();
+       moods_shutdown(flags);
+       playlists_shutdown(flags);
+       lyrics_shutdown(flags);
+       images_shutdown(flags);
+       aft_shutdown(flags);
+}
+
+static int got_sigchld;
+static void sigchld_handler(__a_unused int s)
+{
+       got_sigchld = 1;
+}
+
+static void server_loop(int child_pid)
+{
+//     int status;
+
+       PARA_DEBUG_LOG("server pid: %d, child pid: %d\n",
+               getpid(), child_pid);
+       for (;;)  {
+               mutex_lock(callback_mutex);
+               if (shm_callback_data->handler)
+                       call_callback();
+               mutex_unlock(callback_mutex);
+               usleep(100);
+               if (!got_sigchld)
+                       continue;
+               mutex_destroy(result_mutex);
+               mutex_destroy(callback_mutex);
+               mutex_destroy(child_mutex);
+               afs_shutdown(OSL_MARK_CLEAN);
+               exit(EXIT_SUCCESS);
+       }
+}
+
+#if 0
+int main(int argc, const char **argv)
+{
+       int i, ret = -E_AFS_SYNTAX;
+
+       signal(SIGUSR1, dummy);
+       signal(SIGCHLD, sigchld_handler);
+       if (argc < 2)
+               goto out;
+       ret = setup();
+//     ret = afs_init();
+       if (ret < 0) {
+               PARA_EMERG_LOG("afs_init returned %d\n", ret);
+               exit(EXIT_FAILURE);
+       }
+       ret = fork();
+       if (ret < 0) {
+               ret = -E_FORK;
+               goto out;
+       }
+       if (ret)
+               server_loop(ret);
+       for (i = 0; cmd[i].name; i++) {
+               if (strcmp(cmd[i].name, argv[1]))
+                       continue;
+               ret = cmd[i].handler(1, argc - 1 , argv + 1);
+               goto out;
+
+       }
+       PARA_ERROR_LOG("unknown command: %s\n", argv[1]);
+       ret = -1;
+out:
+       if (ret < 0)
+               PARA_ERROR_LOG("error %d\n", ret);
+       else
+               PARA_DEBUG_LOG("%s", "success\n");
+       afs_shutdown(0);
+       return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
+}
+#endif
diff --git a/afs.h b/afs.h
new file mode 100644 (file)
index 0000000..fdc42f1
--- /dev/null
+++ b/afs.h
@@ -0,0 +1,199 @@
+#include <regex.h>
+#include "osl.h"
+#include "afh.h"
+#include "hash.h"
+
+#define DATABASE_DIR "/home/maan/tmp/osl" /* FIXME */
+
+struct afs_info {
+       uint64_t last_played;
+       uint64_t attributes;
+       uint32_t num_played;
+       uint32_t image_id;
+       uint32_t lyrics_id;
+       uint8_t audio_format_id;
+};
+
+enum afs_table_flags {TBLFLAG_SKIP_CREATE};
+
+struct table_info {
+       const struct osl_table_description *desc;
+       struct osl_table *table;
+       enum afs_table_flags flags;
+};
+
+enum ls_sorting_method {
+       LS_SORT_BY_PATH, /* -sp (default) */
+       LS_SORT_BY_SCORE, /* -ss */
+       LS_SORT_BY_LAST_PLAYED, /* -sl */
+       LS_SORT_BY_NUM_PLAYED, /* -sn */
+       LS_SORT_BY_FREQUENCY, /* -sf */
+       LS_SORT_BY_CHANNELS, /* -sc */
+       LS_SORT_BY_IMAGE_ID, /* -si */
+       LS_SORT_BY_LYRICS_ID, /* -sy */
+       LS_SORT_BY_BITRATE, /* -sb */
+       LS_SORT_BY_DURATION, /* -sd */
+       LS_SORT_BY_AUDIO_FORMAT, /* -sa */
+       LS_SORT_BY_HASH, /* -sh */
+};
+
+enum ls_listing_mode {
+       LS_MODE_SHORT,
+       LS_MODE_LONG,
+       LS_MODE_VERBOSE,
+       LS_MODE_MBOX
+};
+
+enum ls_flags {
+       LS_FLAG_FULL_PATH = 1,
+       LS_FLAG_ADMISSIBLE_ONLY = 2,
+       LS_FLAG_REVERSE = 4,
+};
+
+struct ls_widths {
+       unsigned short score_width;
+       unsigned short image_id_width;
+       unsigned short lyrics_id_width;
+       unsigned short bitrate_width;
+       unsigned short frequency_width;
+       unsigned short duration_width;
+       unsigned short num_played_width;
+};
+
+struct ls_data {
+       struct audio_format_info afhi;
+       struct afs_info afsi;
+       char *path;
+       long score;
+       HASH_TYPE *hash;
+};
+
+struct ls_options {
+       unsigned flags;
+       enum ls_sorting_method sorting;
+       enum ls_listing_mode mode;
+       char **patterns;
+       int num_patterns;
+       struct ls_widths widths;
+       uint32_t array_size;
+       uint32_t num_matching_paths;
+       struct ls_data *data;
+       struct ls_data **data_ptr;
+};
+
+enum play_mode {PLAY_MODE_MOOD, PLAY_MODE_PLAYLIST};
+
+struct audio_file_data {
+       enum play_mode current_play_mode;
+       long score;
+       struct afs_info afsi;
+       struct audio_format_info afhi;
+       char *path;
+       struct osl_object map;
+};
+
+/* afs */
+typedef int callback_function(const struct osl_object *, struct osl_object *);
+int send_callback_request(callback_function *f, struct osl_object *query,
+       struct osl_object *result);
+int send_standard_callback_request(int argc, const char **argv,
+               callback_function *f, struct osl_object *result);
+int send_option_arg_callback_request(struct osl_object *options,
+       int argc, const char **argv, callback_function *f,
+       struct osl_object *result);
+int stdin_command(struct osl_object *arg_obj, callback_function *f,
+               unsigned max_len, struct osl_object *result);
+int string_compare(const struct osl_object *obj1, const struct osl_object *obj2);
+int para_atol(const char *str, long *result);
+int open_next_audio_file(struct audio_file_data *afd);
+int close_audio_file(struct audio_file_data *afd);
+
+/* score */
+int score_init(struct table_info *ti);
+void score_shutdown(enum osl_close_flags flags);
+int admissible_file_loop(void *data, osl_rbtree_loop_func *func);
+int admissible_file_loop_reverse(void *data, osl_rbtree_loop_func *func);
+int score_get_best(struct osl_row **aft_row, long *score);
+int get_score_and_aft_row(struct osl_row *score_row, long *score, struct osl_row **aft_row);
+int score_add(const struct osl_row *row, long score);
+int score_update(const struct osl_row *aft_row, long new_score);
+int get_num_admissible_files(unsigned *num);
+int score_delete(const struct osl_row *aft_row);
+int row_belongs_to_score_table(const struct osl_row *aft_row);
+
+/* attribute */
+int attribute_init(struct table_info *ti);
+void attribute_shutdown(enum osl_close_flags flags);
+void get_attribute_bitmap(uint64_t *atts, char *buf);
+int get_attribute_bitnum_by_name(const char *att_name, unsigned char *bitnum);
+int com_lsatt(int fd, int argc, const char **argv);
+int com_setatt(int fd, int argc, const char **argv);
+int com_addatt(int fd, int argc, const char **argv);
+int com_rmatt(int fd, int argc, const char **argv);
+int get_attribute_text(uint64_t *atts, const char *delim, char **text);
+
+/* aft */
+int aft_init(struct table_info *ti);
+void aft_shutdown(enum osl_close_flags flags);
+int aft_get_row_of_path(char *path, struct osl_row **row);
+int open_and_update_audio_file(struct osl_row *aft_row, struct audio_file_data *afd);
+int load_afsi(struct afs_info *afsi, struct osl_object *obj);
+void save_afsi(struct afs_info *afsi, struct osl_object *obj);
+int get_afsi_of_row(const struct osl_row *row, struct afs_info *afsi);
+int get_audio_file_path_of_row(const struct osl_row *row, char **path);
+int get_afsi_object_of_row(const void *row, struct osl_object *obj);
+int audio_file_loop(void *private_data, osl_rbtree_loop_func *func);
+int com_touch(int fd, int argc, const char **argv);
+int com_add(int fd, int argc, const char **argv);
+int com_afs_ls(int fd, int argc, const char **argv);
+int com_afs_rm(int fd, int argc, const char **argv);
+
+/* mood */
+int mood_open(char *mood_name);
+void mood_close(void);
+int mood_update_audio_file(const struct osl_row *aft_row, struct afs_info *old_afsi);
+int mood_reload(void);
+int mood_delete_audio_file(const struct osl_row *aft_row);
+
+
+/* playlist */
+int playlist_open(char *name);
+void playlist_close(void);
+int playlist_update_audio_file(struct osl_row *aft_row);
+
+/** evaluates to 1 if x < y, to -1 if x > y and to 0 if x == y */
+#define NUM_COMPARE(x, y) ((int)((x) < (y)) - (int)((x) > (y)))
+
+
+#define DECLARE_BLOB_SYMBOLS(table_name, cmd_prefix) \
+       int table_name ## _init(struct table_info *ti); \
+       void table_name ## _shutdown(enum osl_close_flags flags); \
+       int com_add ## cmd_prefix(int fd, int argc, const char **argv); \
+       int com_rm ## cmd_prefix(int fd, int argc, const char **argv); \
+       int com_cat ## cmd_prefix(int fd, int argc, const char **argv); \
+       int com_ls ## cmd_prefix(int fd, int argc, const char **argv); \
+       int com_mv ## cmd_prefix(int fd, int argc, const char **argv); \
+       int cmd_prefix ## _get_name_by_id(uint32_t id, char **name); \
+       extern struct osl_table *table_name ## _table;
+
+DECLARE_BLOB_SYMBOLS(lyrics, lyr);
+DECLARE_BLOB_SYMBOLS(images, img);
+DECLARE_BLOB_SYMBOLS(moods, mood);
+DECLARE_BLOB_SYMBOLS(playlists, pl);
+
+enum blob_table_columns {BLOBCOL_ID, BLOBCOL_NAME, BLOBCOL_DEF, NUM_BLOB_COLUMNS};
+#define DEFINE_BLOB_TABLE_DESC(table_name) \
+       const struct osl_table_description table_name ## _table_desc = { \
+               .dir = DATABASE_DIR, \
+               .name = #table_name, \
+               .num_columns = NUM_BLOB_COLUMNS, \
+               .flags = OSL_LARGE_TABLE, \
+               .column_descriptions = blob_cols \
+       };
+
+#define DEFINE_BLOB_TABLE_PTR(table_name) struct osl_table *table_name ## _table;
+
+#define INIT_BLOB_TABLE(table_name) \
+       DEFINE_BLOB_TABLE_DESC(table_name); \
+       DEFINE_BLOB_TABLE_PTR(table_name);
+
diff --git a/aft.c b/aft.c
new file mode 100644 (file)
index 0000000..aaeab8a
--- /dev/null
+++ b/aft.c
@@ -0,0 +1,1690 @@
+#include "para.h"
+#include "error.h"
+#include <sys/mman.h>
+#include <fnmatch.h>
+#include "afs.h"
+#include "string.h"
+
+int mp3_get_file_info(char *map, size_t numbytes,
+               struct audio_format_info *afi); /* FXIME */
+
+#define AFS_AUDIO_FILE_DIR "/home/mp3"
+
+static void *audio_file_table;
+
+/**
+ * Describes the structure of the mmapped-afs info struct.
+ *
+ * \sa struct afs_info.
+ */
+enum afsi_offsets {
+       /** Where .last_played is stored. */
+       AFSI_LAST_PLAYED_OFFSET = 0,
+       /** Storage position of the attributes bitmap. */
+       AFSI_ATTRIBUTES_OFFSET = 8,
+       /** Storage position of the .num_played field. */
+       AFSI_NUM_PLAYED_OFFSET = 16,
+       /** Storage position of the .image_id field. */
+       AFSI_IMAGE_ID_OFFSET = 20,
+       /** Storage position of the .lyrics_id field. */
+       AFSI_LYRICS_ID_OFFSET = 24,
+       /** Storage position of the .audio_format_id field. */
+       AFSI_AUDIO_FORMAT_ID_OFFSET = 28,
+       /** On-disk storage space needed. */
+       AFSI_SIZE = 29
+};
+
+/**
+ * Convert a struct afs_info to an osl object.
+ *
+ * \param afsi Pointer to the audio file info to be converted.
+ * \param obj Result pointer.
+ *
+ * \sa load_afsi().
+ */
+void save_afsi(struct afs_info *afsi, struct osl_object *obj)
+{
+       struct afs_info default_afs_info = {
+               .last_played = time(NULL) - 365 * 24 * 60 * 60,
+               .attributes = 0,
+               .num_played = 0,
+               .image_id = 0,
+               .lyrics_id = 0,
+               .audio_format_id = 5, /* FIXME */
+       };
+       char *buf = obj->data;
+
+       if (!afsi)
+               afsi = &default_afs_info;
+
+       write_u64(buf + AFSI_LAST_PLAYED_OFFSET, afsi->last_played);
+       write_u64(buf + AFSI_ATTRIBUTES_OFFSET, afsi->attributes);
+       write_u32(buf + AFSI_NUM_PLAYED_OFFSET, afsi->num_played);
+       write_u32(buf + AFSI_IMAGE_ID_OFFSET, afsi->image_id);
+       write_u32(buf + AFSI_LYRICS_ID_OFFSET, afsi->lyrics_id);
+       write_u8(buf + AFSI_AUDIO_FORMAT_ID_OFFSET,
+               afsi->audio_format_id);
+}
+
+/**
+ *  Get the audio file selector info struct stored in an osl object.
+ *
+ * \param afsi Points to the audio_file info structure to be filled in.
+ * \param obj The osl object holding the data.
+ *
+ * \return Positive on success, negative on errors. Possible errors: \p E_BAD_AFS.
+ *
+ * \sa save_afsi().
+ */
+int load_afsi(struct afs_info *afsi, struct osl_object *obj)
+{
+       char *buf = obj->data;
+       if (obj->size < AFSI_SIZE)
+               return -E_BAD_AFS;
+       afsi->last_played = read_u64(buf + AFSI_LAST_PLAYED_OFFSET);
+       afsi->attributes = read_u64(buf + AFSI_ATTRIBUTES_OFFSET);
+       afsi->num_played = read_u32(buf + AFSI_NUM_PLAYED_OFFSET);
+       afsi->image_id = read_u32(buf + AFSI_IMAGE_ID_OFFSET);
+       afsi->lyrics_id = read_u32(buf + AFSI_LYRICS_ID_OFFSET);
+       afsi->audio_format_id = read_u8(buf +
+               AFSI_AUDIO_FORMAT_ID_OFFSET);
+       return 1;
+}
+
+/** The columns of the audio file table. */
+enum audio_file_table_columns {
+       /** The hash on the content of the audio file. */
+       AFTCOL_HASH,
+       /** The full path in the filesystem. */
+       AFTCOL_PATH,
+       /** The audio file selector info. */
+       AFTCOL_AFSI,
+       /** The audio format handler info. */
+       AFTCOL_AFHI,
+       /** The chunk table info and the chunk table of the audio file. */
+       AFTCOL_CHUNKS,
+       /** The number of columns of this table. */
+       NUM_AFT_COLUMNS
+};
+
+static struct osl_column_description aft_cols[] = {
+       [AFTCOL_HASH] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
+               .name = "hash",
+               .compare_function = osl_hash_compare,
+               .data_size = HASH_SIZE
+       },
+       [AFTCOL_PATH] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_RBTREE | OSL_UNIQUE,
+               .name = "path",
+               .compare_function = string_compare,
+       },
+       [AFTCOL_AFSI] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_FIXED_SIZE,
+               .name = "afs_info",
+               .data_size = AFSI_SIZE
+       },
+       [AFTCOL_AFHI] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .name = "afh_info",
+       },
+       [AFTCOL_CHUNKS] = {
+               .storage_type = OSL_DISK_STORAGE,
+               .name = "chunks",
+       }
+};
+
+static const struct osl_table_description audio_file_table_desc = {
+       .dir = DATABASE_DIR,
+       .name = "audio_files",
+       .num_columns = NUM_AFT_COLUMNS,
+       .flags = OSL_LARGE_TABLE,
+       .column_descriptions = aft_cols
+};
+
+static char *prefix_path(const char *prefix, int len, const char *path)
+{
+       int speclen;
+       char *n;
+
+       for (;;) {
+               char c;
+               if (*path != '.')
+                       break;
+               c = path[1];
+               /* "." */
+               if (!c) {
+                       path++;
+                       break;
+               }
+               /* "./" */
+               if (c == '/') {
+                       path += 2;
+                       continue;
+               }
+               if (c != '.')
+                       break;
+               c = path[2];
+               if (!c)
+                       path += 2;
+               else if (c == '/')
+                       path += 3;
+               else
+                       break;
+               /* ".." and "../" */
+               /* Remove last component of the prefix */
+               do {
+                       if (!len)
+                               return NULL;
+                       len--;
+               } while (len && prefix[len-1] != '/');
+               continue;
+       }
+       if (!len)
+               return para_strdup(path);
+       speclen = strlen(path);
+       n = para_malloc(speclen + len + 1);
+       memcpy(n, prefix, len);
+       memcpy(n + len, path, speclen+1);
+       return n;
+}
+
+/*
+ * We fundamentally don't like some paths: we don't want
+ * dot or dot-dot anywhere.
+ *
+ * Also, we don't want double slashes or slashes at the
+ * end that can make pathnames ambiguous.
+ */
+static int verify_dotfile(const char *rest)
+{
+       /*
+        * The first character was '.', but that has already been discarded, we
+        * now test the rest.
+        */
+       switch (*rest) {
+       /* "." is not allowed */
+       case '\0': case '/':
+               return 1;
+
+       case '.':
+               if (rest[1] == '\0' || rest[1] == '/')
+                       return -1;
+       }
+       return 1;
+}
+
+static int verify_path(const char *path, char **resolved_path)
+{
+       const char *orig_path = path;
+       char c;
+       const char prefix[] = AFS_AUDIO_FILE_DIR "/";
+       const size_t prefix_len = strlen(prefix);
+
+       PARA_DEBUG_LOG("path: %s\n", path);
+       c = *path++;
+       if (!c)
+               return -E_BAD_PATH;
+       while (c) {
+               if (c == '/') {
+                       c = *path++;
+                       switch (c) {
+                       default:
+                               continue;
+                       case '/': case '\0':
+                               break;
+                       case '.':
+                               if (verify_dotfile(path) > 0)
+                                       continue;
+                       }
+                       *resolved_path = NULL;
+                       return -E_BAD_PATH;
+               }
+               c = *path++;
+       }
+       if (*orig_path == '/')
+               *resolved_path = prefix_path("", 0, orig_path);
+       else
+               *resolved_path = prefix_path(prefix, prefix_len, orig_path);
+       PARA_DEBUG_LOG("resolved: %s\n", *resolved_path);
+       return *resolved_path? 1: -E_BAD_PATH;
+}
+
+enum afhi_offsets {
+       AFHI_SECONDS_TOTAL_OFFSET = 0,
+       AFHI_BITRATE_OFFSET = 4,
+       AFHI_FREQUENCY_OFFSET = 8,
+       AFHI_CHANNELS_OFFSET = 12,
+       AFHI_INFO_STRING_OFFSET = 13,
+       MIN_AFHI_SIZE = 14
+};
+
+static unsigned sizeof_afhi_buf(const struct audio_format_info *afhi)
+{
+       if (!afhi)
+               return 0;
+       return strlen(afhi->info_string) + MIN_AFHI_SIZE;
+}
+
+static void save_afhi(struct audio_format_info *afhi, char *buf)
+{
+       if (!afhi)
+               return;
+       write_u32(buf + AFHI_SECONDS_TOTAL_OFFSET,
+               afhi->seconds_total);
+       write_u32(buf + AFHI_BITRATE_OFFSET, afhi->bitrate);
+       write_u32(buf + AFHI_FREQUENCY_OFFSET, afhi->frequency);
+       write_u8(buf + AFHI_CHANNELS_OFFSET, afhi->channels);
+       strcpy(buf + AFHI_INFO_STRING_OFFSET, afhi->info_string); /* OK */
+       PARA_DEBUG_LOG("last byte written: %p\n", buf + AFHI_INFO_STRING_OFFSET + strlen(afhi->info_string));
+}
+
+static void load_afhi(const char *buf, struct audio_format_info *afhi)
+{
+       afhi->seconds_total = read_u32(buf + AFHI_SECONDS_TOTAL_OFFSET);
+       afhi->bitrate = read_u32(buf + AFHI_BITRATE_OFFSET);
+       afhi->frequency = read_u32(buf + AFHI_FREQUENCY_OFFSET);
+       afhi->channels = read_u8(buf + AFHI_CHANNELS_OFFSET);
+       strcpy(afhi->info_string, buf + AFHI_INFO_STRING_OFFSET);
+}
+
+static unsigned sizeof_chunk_info_buf(struct audio_format_info *afhi)
+{
+       if (!afhi)
+               return 0;
+       return 4 * afhi->chunks_total + 20;
+
+}
+
+/** The offsets of the data contained in the AFTCOL_CHUNKS column. */
+enum chunk_info_offsets {
+       /** The total number of chunks (4 bytes). */
+       CHUNKS_TOTAL_OFFSET = 0,
+       /** The length of the audio file header (4 bytes). */
+       HEADER_LEN_OFFSET = 4,
+       /** The start of the audio file header (4 bytes). */
+       HEADER_OFFSET_OFFSET = 8,
+       /** The seconds part of the chunk time (4 bytes). */
+       CHUNK_TV_TV_SEC_OFFSET = 12,
+       /** The microseconds part of the chunk time (4 bytes). */
+       CHUNK_TV_TV_USEC = 16,
+       /** Chunk table entries start here. */
+       CHUNK_TABLE_OFFSET = 20,
+};
+
+/* TODO: audio format handlers could just produce this */
+static void save_chunk_info(struct audio_format_info *afhi, char *buf)
+{
+       int i;
+
+       if (!afhi)
+               return;
+       write_u32(buf + CHUNKS_TOTAL_OFFSET, afhi->chunks_total);
+       write_u32(buf + HEADER_LEN_OFFSET, afhi->header_len);
+       write_u32(buf + HEADER_OFFSET_OFFSET, afhi->header_offset);
+       write_u32(buf + CHUNK_TV_TV_SEC_OFFSET, afhi->chunk_tv.tv_sec);
+       write_u32(buf + CHUNK_TV_TV_USEC, afhi->chunk_tv.tv_usec);
+       for (i = 0; i < afhi->chunks_total; i++)
+               write_u32(buf + CHUNK_TABLE_OFFSET + 4 * i, afhi->chunk_table[i]);
+}
+
+static int load_chunk_info(struct osl_object *obj, struct audio_format_info *afhi)
+{
+       char *buf = obj->data;
+       int i;
+
+       if (obj->size < CHUNK_TABLE_OFFSET)
+               return -E_BAD_DATA_SIZE;
+
+       afhi->chunks_total = read_u32(buf + CHUNKS_TOTAL_OFFSET);
+       afhi->header_len = read_u32(buf + HEADER_LEN_OFFSET);
+       afhi->header_offset = read_u32(buf + HEADER_OFFSET_OFFSET);
+       afhi->chunk_tv.tv_sec = read_u32(buf + CHUNK_TV_TV_SEC_OFFSET);
+       afhi->chunk_tv.tv_usec = read_u32(buf + CHUNK_TV_TV_USEC);
+
+       if (afhi->chunks_total * 4 + CHUNK_TABLE_OFFSET > obj->size)
+               return -E_BAD_DATA_SIZE;
+       afhi->chunk_table = para_malloc(afhi->chunks_total * sizeof(size_t));
+       for (i = 0; i < afhi->chunks_total; i++)
+               afhi->chunk_table[i] = read_u32(buf + CHUNK_TABLE_OFFSET + 4 * i);
+       return 1;
+}
+
+/**
+ * Get the row of the audio file table corresponding to the given path.
+ *
+ * \param path The full path of the audio file.
+ * \param row Result pointer.
+ *
+ * \return The return value of the underlying call to osl_get_row().
+ */
+int aft_get_row_of_path(char *path, struct osl_row **row)
+{
+       struct osl_object obj = {.data = path, .size = strlen(path) + 1};
+       return osl_get_row(audio_file_table, AFTCOL_PATH, &obj, row);
+}
+
+/**
+ * Get the row of the audio file table corresponding to the given hash value.
+ *
+ * \param hash The hash value of the desired audio file.
+ * \param row resul pointer.
+ *
+ * \return The return value of the underlying call to osl_get_row().
+ */
+int aft_get_row_of_hash(HASH_TYPE *hash, struct osl_row **row)
+{
+       const struct osl_object obj = {.data = hash, .size = HASH_SIZE};
+       return osl_get_row(audio_file_table, AFTCOL_HASH, &obj, row);
+}
+
+/**
+ * Get the osl object holding the audio file selector info of a row.
+ *
+ * \param row Pointer to a row in the audio file table.
+ * \param obj Result pointer.
+ *
+ * \return The return value of the underlying call to osl_get_object().
+ */
+int get_afsi_object_of_row(const void *row, struct osl_object *obj)
+{
+       return osl_get_object(audio_file_table, row, AFTCOL_AFSI, obj);
+}
+
+/**
+ * Get the osl object holding the audio file selector info, given a path.
+ *
+ *
+ * \param path The full path of the audio file.
+ * \param obj Result pointer.
+ *
+ * \return Positive on success, negative on errors.
+ */
+int get_afsi_object_of_path(char *path, struct osl_object *obj)
+{
+       struct osl_row *row;
+       int ret = aft_get_row_of_path(path, &row);
+       if (ret < 0)
+               return ret;
+       return get_afsi_object_of_row(row, obj);
+}
+
+/**
+ * Get the audio file selector info, given a row of the audio file table.
+ *
+ * \param row Pointer to a row in the audio file table.
+ * \param afsi Result pointer.
+ *
+ * \return Positive on success, negative on errors.
+ */
+int get_afsi_of_row(const struct osl_row *row, struct afs_info *afsi)
+{
+       struct osl_object obj;
+       int ret = get_afsi_object_of_row(row, &obj);
+       if (ret < 0)
+               return ret;
+       return load_afsi(afsi, &obj);
+}
+
+/**
+ * Get the path of an audio file, given a row of the audio file table.
+ *
+ * \param row Pointer to a row in the audio file table.
+ * \param path Result pointer.
+ *
+ * \return Positive on success, negative on errors.
+ */
+int get_audio_file_path_of_row(const struct osl_row *row, char **path)
+{
+       struct osl_object path_obj;
+       int ret = osl_get_object(audio_file_table, row, AFTCOL_PATH,
+               &path_obj);
+       if (ret < 0)
+               return ret;
+       *path = path_obj.data;
+       return 1;
+}
+
+/**
+ * Get the object containing the hash value of an audio file, given a row.
+ *
+ * \param row Pointer to a row of the audio file table.
+ * \param obj Result pointer.
+ *
+ * \return The return value of the underlying call to osl_get_object().
+ *
+ * \sa get_hash_of_row().
+ */
+int get_hash_object_of_aft_row(const void *row, struct osl_object *obj)
+{
+       return osl_get_object(audio_file_table, row, AFTCOL_HASH, obj);
+}
+
+/**
+ * Get the hash value of an audio file, given a row of the audio file table.
+ *
+ * \param row Pointer to a row of the audio file table.
+ * \param hash Result pointer.
+ *
+ * \a hash points to mapped data and must not be freed by the caller.
+ *
+ * \return The return value of the underlying call to
+ * get_hash_object_of_aft_row().
+ */
+static int get_hash_of_row(const void *row, HASH_TYPE **hash)
+{
+       struct osl_object obj;
+       int ret = get_hash_object_of_aft_row(row, &obj);
+
+       if (ret < 0)
+               return ret;
+       *hash = obj.data;
+       return 1;
+}
+
+/**
+ * Get the audio format handler info, given a row of the audio file table.
+ *
+ * \param row Pointer to a row of the audio file table.
+ * \param afhi Result pointer.
+ *
+ * \return The return value of the underlying call to osl_get_object().
+ *
+ * \sa get_chunk_table_of_row().
+ */
+int get_afhi_of_row(const void *row, struct audio_format_info *afhi)
+{
+       struct osl_object obj;
+       int ret = osl_get_object(audio_file_table, row, AFTCOL_AFHI,
+               &obj);
+       if (ret < 0)
+               return ret;
+       load_afhi(obj.data, afhi);
+       return 1;
+}
+
+/**
+ * Get the chunk table of an audio file, given a row of the audio file table.
+ *
+ * \param row Pointer to a row of the audio file table.
+ * \param afhi Result pointer.
+ *
+ * \return The return value of the underlying call to osl_open_disk_object().
+ *
+ * \sa get_afhi_of_row().
+ */
+int get_chunk_table_of_row(const void *row, struct audio_format_info *afhi)
+{
+       struct osl_object obj;
+       int ret = osl_open_disk_object(audio_file_table, row, AFTCOL_CHUNKS,
+               &obj);
+       if (ret < 0)
+               return ret;
+       ret = load_chunk_info(&obj, afhi);
+       osl_close_disk_object(&obj);
+       return ret;
+}
+
+/**
+ * Mmap the given audio file and update statistics.
+ *
+ * \param aft_row Determines the audio file to be opened and updated.
+ * \param afd Result pointer.
+ *
+ * On success, the numplayed field of the audio file selector info is increased
+ * and the lastplayed time is set to the current time. Finally, the score of
+ * the audio file is updated.
+ *
+ * \return Positive on success, negative on errors.
+ */
+int open_and_update_audio_file(struct osl_row *aft_row, struct audio_file_data *afd)
+{
+       HASH_TYPE *aft_hash, file_hash[HASH_SIZE];
+       struct osl_object afsi_obj;
+       struct afs_info new_afsi;
+       int ret = get_hash_of_row(aft_row, &aft_hash);
+
+       if (ret < 0)
+               return ret;
+       ret = get_audio_file_path_of_row(aft_row, &afd->path);
+       if (ret < 0)
+               return ret;
+       ret = get_afsi_object_of_row(aft_row, &afsi_obj);
+       if (ret < 0)
+               return ret;
+       ret = load_afsi(&afd->afsi, &afsi_obj);
+       if (ret < 0)
+               return ret;
+       ret = get_afhi_of_row(aft_row, &afd->afhi);
+       if (ret < 0)
+               return ret;
+       ret = get_chunk_table_of_row(aft_row, &afd->afhi);
+       if (ret < 0)
+               return ret;
+       ret = mmap_full_file(afd->path, O_RDONLY, &afd->map);
+       if (ret < 0)
+               goto err;
+       hash_function(afd->map.data, afd->map.size, file_hash);
+       ret = -E_HASH_MISMATCH;
+       if (hash_compare(file_hash, aft_hash))
+               goto err;
+       new_afsi = afd->afsi;
+       new_afsi.num_played++;
+       new_afsi.last_played = time(NULL);
+       save_afsi(&new_afsi, &afsi_obj); /* in-place update */
+       if (afd->current_play_mode == PLAY_MODE_PLAYLIST)
+               ret = playlist_update_audio_file(aft_row);
+       else
+               ret = mood_update_audio_file(aft_row, &afd->afsi);
+       return ret;
+err:
+       free(afd->afhi.chunk_table);
+       return ret;
+}
+
+time_t now;
+
+static int get_local_time(uint64_t *seconds, char *buf, size_t size)
+{
+       struct tm t;
+
+       if (!localtime_r((time_t *)seconds, &t))
+               return -E_LOCALTIME;
+       if (*seconds + 6 * 30 * 24 * 3600 > now) {
+               if (!strftime(buf, size, "%b %e %k:%M", &t))
+                       return -E_STRFTIME;
+               return 1;
+       }
+       if (!strftime(buf, size, "%b %e  %Y", &t))
+               return -E_STRFTIME;
+       return 1;
+}
+
+#define GET_NUM_DIGITS(x, num) { \
+       typeof((x)) _tmp = PARA_ABS(x); \
+       *num = 1; \
+       if ((x)) \
+               while ((_tmp) > 9) { \
+                       (_tmp) /= 10; \
+                       (*num)++; \
+               } \
+       }
+
+static short unsigned get_duration(int seconds_total, char *buf, short unsigned max_width)
+{
+       short unsigned width;
+       int s = seconds_total;
+       unsigned hours = s / 3600, mins = (s % 3600) / 60, secs = s % 60;
+
+       if (s < 3600) { /* less than one hour => m:ss or mm:ss */
+               GET_NUM_DIGITS(mins, &width); /* 1 or 2 */
+               width += 3; /* 4 or 5 */
+               if (buf)
+                       sprintf(buf, "%*u:%02u", max_width - width + 1, mins, secs);
+               return width;
+       }
+       /* more than one hour => h:mm:ss, hh:mm:ss, hhh:mm:ss, ... */
+       GET_NUM_DIGITS(hours, &width);
+       width += 6;
+       if (buf)
+               sprintf(buf, "%*u:%02u:%02u", max_width - width + 1, hours, mins, secs);
+       return width;
+}
+
+static char *make_attribute_line(const char *att_bitmap, struct afs_info *afsi)
+{
+       char *att_text, *att_line;
+
+       get_attribute_text(&afsi->attributes, " ", &att_text);
+       if (!att_text)
+               return para_strdup(att_bitmap);
+       att_line = make_message("%s (%s)", att_bitmap, att_text);
+       free(att_text);
+       return att_line;
+}
+
+static char *make_lyrics_line(struct afs_info *afsi)
+{
+       char *lyrics_name;
+       lyr_get_name_by_id(afsi->lyrics_id, &lyrics_name);
+       if (!lyrics_name)
+               return make_message("%u", afsi->lyrics_id);
+       return make_message("%u (%s)", afsi->lyrics_id, lyrics_name);
+}
+
+static char *make_image_line(struct afs_info *afsi)
+{
+       char *image_name;
+       img_get_name_by_id(afsi->image_id, &image_name);
+       if (!image_name)
+               return make_message("%u", afsi->image_id);
+       return make_message("%u (%s)", afsi->image_id, image_name);
+}
+
+static int print_list_item(struct ls_data *d, struct ls_options *opts,
+       struct para_buffer *b)
+{
+       int ret;
+       char att_buf[65];
+       char last_played_time[30];
+       char duration_buf[30]; /* nobody has an audio file long enough to overflow this */
+       char score_buf[30] = "";
+       struct afs_info *afsi = &d->afsi;
+       struct audio_format_info *afhi = &d->afhi;
+       struct ls_widths *w = &opts->widths;
+       int have_score = opts->flags & LS_FLAG_ADMISSIBLE_ONLY;
+
+       if (opts->mode == LS_MODE_SHORT) {
+               para_printf(b, "%s\n", d->path);
+               return 1;
+       }
+       get_attribute_bitmap(&afsi->attributes, att_buf);
+       ret = get_local_time(&afsi->last_played, last_played_time,
+               sizeof(last_played_time));
+       if (ret < 0)
+               return ret;
+       get_duration(afhi->seconds_total, duration_buf, w->duration_width);
+       if (have_score) {
+               if (opts->mode == LS_MODE_LONG)
+                       sprintf(score_buf, "%*li ", w->score_width, d->score);
+               else
+                       sprintf(score_buf, "%li ", d->score);
+       }
+
+       if (opts->mode == LS_MODE_LONG) {
+               para_printf(b,
+                       "%s"    /* score */
+                       "%s "   /* attributes */
+                       "%*d "  /* image_id  */
+                       "%*d "  /* lyrics_id */
+                       "%*d "  /* bitrate */
+                       "%s "   /* audio format */
+                       "%*d "  /* frequency */
+                       "%d "   /* channels */
+                       "%s "   /* duration */
+                       "%*d "  /* num_played */
+                       "%s "   /* last_played */
+                       "%s\n", /* path */
+                       score_buf,
+                       att_buf,
+                       w->image_id_width, afsi->image_id,
+                       w->lyrics_id_width, afsi->lyrics_id,
+                       w->bitrate_width, afhi->bitrate,
+                       "mp3", /* FIXME */
+                       w->frequency_width, afhi->frequency,
+                       afhi->channels,
+                       duration_buf,
+                       w->num_played_width, afsi->num_played,
+                       last_played_time,
+                       d->path
+               );
+               return 1;
+       }
+       if (opts->mode == LS_MODE_VERBOSE) {
+               HASH_TYPE asc_hash[2 * HASH_SIZE + 1];
+               char *att_line, *lyrics_line, *image_line;
+
+               hash_to_asc(d->hash, asc_hash);
+               att_line = make_attribute_line(att_buf, afsi);
+               lyrics_line = make_lyrics_line(afsi);
+               image_line = make_image_line(afsi);
+               para_printf(b,
+                       "%s: %s\n" /* path */
+                       "%s%s%s" /* score */
+                       "attributes: %s\n"
+                       "hash: %s\n"
+                       "image_id: %s\n"
+                       "lyrics_id: %s\n"
+                       "bitrate: %dkbit/s\n"
+                       "format: %s\n"
+                       "frequency: %dHz\n"
+                       "channels: %d\n"
+                       "duration: %s\n"
+                       "num_played: %d\n"
+                       "last_played: %s\n\n",
+                       (opts->flags & LS_FLAG_FULL_PATH)?
+                               "path" : "file", d->path,
+                       have_score? "score: " : "", score_buf,
+                               have_score? "\n" : "",
+                       att_line,
+                       asc_hash,
+                       image_line,
+                       lyrics_line,
+                       afhi->bitrate,
+                       "mp3", /* FIXME */
+                       afhi->frequency,
+                       afhi->channels,
+                       duration_buf,
+                       afsi->num_played,
+                       last_played_time
+               );
+               free(att_line);
+               free(lyrics_line);
+               free(image_line);
+               return 1;
+       }
+       return 1;
+}
+
+static int ls_audio_format_compare(const void *a, const void *b)
+{
+       struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+       return NUM_COMPARE(d1->afsi.audio_format_id, d2->afsi.audio_format_id);
+}
+
+static int ls_duration_compare(const void *a, const void *b)
+{
+       struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+       return NUM_COMPARE(d1->afhi.seconds_total, d2->afhi.seconds_total);
+}
+
+static int ls_bitrate_compare(const void *a, const void *b)
+{
+       struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+       return NUM_COMPARE(d1->afhi.bitrate, d2->afhi.bitrate);
+}
+
+static int ls_lyrics_id_compare(const void *a, const void *b)
+{
+       struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+       return NUM_COMPARE(d1->afsi.lyrics_id, d2->afsi.lyrics_id);
+}
+
+static int ls_image_id_compare(const void *a, const void *b)
+{
+       struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+       return NUM_COMPARE(d1->afsi.image_id, d2->afsi.image_id);
+}
+
+static int ls_channels_compare(const void *a, const void *b)
+{
+       struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+       return NUM_COMPARE(d1->afhi.channels, d2->afhi.channels);
+}
+
+static int ls_frequency_compare(const void *a, const void *b)
+{
+       struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+       return NUM_COMPARE(d1->afhi.frequency, d2->afhi.frequency);
+}
+
+static int ls_num_played_compare(const void *a, const void *b)
+{
+       struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+       return NUM_COMPARE(d1->afsi.num_played, d2->afsi.num_played);
+}
+
+static int ls_last_played_compare(const void *a, const void *b)
+{
+       struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+       return NUM_COMPARE(d1->afsi.last_played, d2->afsi.last_played);
+}
+
+static int ls_score_compare(const void *a, const void *b)
+{
+       struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+       return NUM_COMPARE(d1->score, d2->score);
+}
+
+static int ls_path_compare(const void *a, const void *b)
+{
+       struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+       return strcmp(d1->path, d2->path);
+}
+
+static int sort_matching_paths(struct ls_options *options)
+{
+       size_t nmemb = options->num_matching_paths;
+       size_t size = sizeof(uint32_t);
+       int (*compar)(const void *, const void *);
+       int i;
+
+       options->data_ptr = para_malloc(nmemb * sizeof(*options->data_ptr));
+       for (i = 0; i < nmemb; i++)
+               options->data_ptr[i] = options->data + i;
+
+       /* In these cases the array is already sorted */
+       if (options->sorting == LS_SORT_BY_PATH
+               && !(options->flags & LS_FLAG_ADMISSIBLE_ONLY)
+               && (options->flags & LS_FLAG_FULL_PATH))
+               return 1;
+       if (options->sorting == LS_SORT_BY_SCORE &&
+                       options->flags & LS_FLAG_ADMISSIBLE_ONLY)
+               return 1;
+
+       switch (options->sorting) {
+       case LS_SORT_BY_PATH:
+               compar = ls_path_compare; break;
+       case LS_SORT_BY_SCORE:
+               compar = ls_score_compare; break;
+       case LS_SORT_BY_LAST_PLAYED:
+               compar = ls_last_played_compare; break;
+       case LS_SORT_BY_NUM_PLAYED:
+               compar = ls_num_played_compare; break;
+       case LS_SORT_BY_FREQUENCY:
+               compar = ls_frequency_compare; break;
+       case LS_SORT_BY_CHANNELS:
+               compar = ls_channels_compare; break;
+       case LS_SORT_BY_IMAGE_ID:
+               compar = ls_image_id_compare; break;
+       case LS_SORT_BY_LYRICS_ID:
+               compar = ls_lyrics_id_compare; break;
+       case LS_SORT_BY_BITRATE:
+               compar = ls_bitrate_compare; break;
+       case LS_SORT_BY_DURATION:
+               compar = ls_duration_compare; break;
+       case LS_SORT_BY_AUDIO_FORMAT:
+               compar = ls_audio_format_compare; break;
+       default:
+               return -E_BAD_SORT;
+       }
+       qsort(options->data_ptr, nmemb, size, compar);
+       return 1;
+}
+
+/* row is either an aft_row or a row of the score table */
+/* TODO: Only compute widths if we need them */
+static int prepare_ls_row(struct osl_row *row, void *ls_opts)
+{
+       int ret, i;
+       struct ls_options *options = ls_opts;
+       struct ls_data *d;
+       struct ls_widths *w;
+       unsigned short num_digits;
+       unsigned tmp;
+       struct osl_row *aft_row;
+       long score;
+       char *path;
+
+       if (options->flags & LS_FLAG_ADMISSIBLE_ONLY) {
+               ret = get_score_and_aft_row(row, &score, &aft_row);
+               if (ret < 0)
+                       return ret;
+       } else
+               aft_row = row;
+       ret = get_audio_file_path_of_row(aft_row, &path);
+       if (ret < 0)
+               return ret;
+       if (!(options->flags & LS_FLAG_FULL_PATH)) {
+               char *p = strrchr(path, '/');
+               if (p)
+                       path = p + 1;
+       }
+       if (options->num_patterns) {
+               for (i = 0; i < options->num_patterns; i++) {
+                       ret = fnmatch(options->patterns[i], path, FNM_PATHNAME);
+                       if (!ret)
+                               break;
+                       if (ret == FNM_NOMATCH)
+                               continue;
+                       return -E_FNMATCH;
+               }
+               if (i >= options->num_patterns) /* no match */
+                       return 1;
+       }
+       tmp = options->num_matching_paths++;
+       if (options->num_matching_paths > options->array_size) {
+               options->array_size++;
+               options->array_size *= 2;
+               options->data = para_realloc(options->data, options->array_size
+                       * sizeof(*options->data));
+       }
+       d = options->data + tmp;
+       ret = get_afsi_of_row(aft_row, &d->afsi);
+       if (ret < 0)
+               return ret;
+       ret = get_afhi_of_row(aft_row, &d->afhi);
+       if (ret < 0)
+               return ret;
+       d->path = path;
+       ret = get_hash_of_row(aft_row, &d->hash);
+       if (ret < 0)
+               return ret;
+       w = &options->widths;
+       GET_NUM_DIGITS(d->afsi.image_id, &num_digits);
+       w->image_id_width = PARA_MAX(w->image_id_width, num_digits);
+       GET_NUM_DIGITS(d->afsi.lyrics_id, &num_digits);
+       w->lyrics_id_width = PARA_MAX(w->lyrics_id_width, num_digits);
+       GET_NUM_DIGITS(d->afhi.bitrate, &num_digits);
+       w->bitrate_width = PARA_MAX(w->bitrate_width, num_digits);
+       GET_NUM_DIGITS(d->afhi.frequency, &num_digits);
+       w->frequency_width = PARA_MAX(w->frequency_width, num_digits);
+       GET_NUM_DIGITS(d->afsi.num_played, &num_digits);
+       w->num_played_width = PARA_MAX(w->num_played_width, num_digits);
+       /* just get the number of chars to print this amount of time */
+       tmp = get_duration(d->afhi.seconds_total, NULL, 0);
+       w->duration_width = PARA_MAX(w->duration_width, tmp);
+       if (options->flags & LS_FLAG_ADMISSIBLE_ONLY) {
+               GET_NUM_DIGITS(score, &num_digits);
+               num_digits++; /* add one for the sign (space or "-") */
+               w->score_width = PARA_MAX(w->score_width, num_digits);
+               d->score = score;
+       }
+       return 1;
+}
+
+static int com_ls_callback(const struct osl_object *query,
+               struct osl_object *ls_output)
+{
+       struct ls_options *opts = query->data;
+       char *p, *pattern_start = (char *)query->data + sizeof(*opts);
+       struct para_buffer b = {.buf = NULL, .size = 0};
+       int i = 0, ret;
+
+       PARA_NOTICE_LOG("%d patterns\n", opts->num_patterns);
+       if (opts->num_patterns) {
+               opts->patterns = para_malloc(opts->num_patterns * sizeof(char *));
+               for (i = 0, p = pattern_start; i < opts->num_patterns; i++) {
+                       opts->patterns[i] = p;
+                       p += strlen(p) + 1;
+                       PARA_NOTICE_LOG("pattern %d: %s\n", i, opts->patterns[i]);
+               }
+       } else
+               opts->patterns = NULL;
+       if (opts->flags & LS_FLAG_ADMISSIBLE_ONLY)
+               ret = admissible_file_loop(opts, prepare_ls_row);
+       else
+               ret = osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts,
+                       prepare_ls_row);
+       if (ret < 0)
+               goto out;
+       ret = opts->num_patterns? -E_NO_MATCH : 1;
+       if (!opts->num_matching_paths) {
+               PARA_NOTICE_LOG("no match, ret: %d\n", ret);
+               goto out;
+       }
+       ret = sort_matching_paths(opts);
+       if (ret < 0)
+               goto out;
+       if (opts->flags & LS_FLAG_REVERSE)
+               for (i = opts->num_matching_paths - 1; i >= 0; i--) {
+                       ret = print_list_item(opts->data_ptr[i], opts, &b);
+                       if (ret < 0)
+                               break;
+               }
+       else
+               for (i = 0; i < opts->num_matching_paths; i++) {
+                       ret = print_list_item(opts->data_ptr[i], opts, &b);
+                       if (ret < 0)
+                               break;
+               }
+       ret = 1;
+out:
+       ls_output->data = b.buf;
+       ls_output->size = b.size;
+       free(opts->data);
+       free(opts->data_ptr);
+       free(opts->patterns);
+       return ret;
+}
+
+/*
+ * TODO: flags -h (sort by hash)
+ *
+ * long list: list hash, attributes as (xx--x-x-), file size, lastplayed
+ * full list: list everything, including afsi, afhi, atts as clear text
+ *
+ * */
+int com_afs_ls(__a_unused int fd, int argc, const char **argv)
+{
+       int i, ret;
+       unsigned flags = 0;
+       enum ls_sorting_method sort = LS_SORT_BY_PATH;
+       enum ls_listing_mode mode = LS_MODE_SHORT;
+       struct ls_options opts = {.patterns = NULL};
+       struct osl_object query = {.data = &opts, .size = sizeof(opts)},
+               ls_output;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strncmp(arg, "-l", 2)) {
+                       if (!*(arg + 2)) {
+                               mode = LS_MODE_LONG;
+                               continue;
+                       }
+                       if (*(arg + 3))
+                               return -E_AFT_SYNTAX;
+                       switch(*(arg + 2)) {
+                       case 's':
+                               mode = LS_MODE_SHORT;
+                               continue;
+                       case 'l':
+                               mode = LS_MODE_LONG;
+                               continue;
+                       case 'v':
+                               mode = LS_MODE_VERBOSE;
+                               continue;
+                       case 'm':
+                               mode = LS_MODE_MBOX;
+                               continue;
+                       default:
+                               return -E_AFT_SYNTAX;
+                       }
+               }
+               if (!strcmp(arg, "-p")) {
+                       flags |= LS_FLAG_FULL_PATH;
+                       continue;
+               }
+               if (!strcmp(arg, "-a")) {
+                       flags |= LS_FLAG_ADMISSIBLE_ONLY;
+                       continue;
+               }
+               if (!strcmp(arg, "-r")) {
+                       flags |= LS_FLAG_REVERSE;
+                       continue;
+               }
+               if (!strncmp(arg, "-s", 2)) {
+                       if (!*(arg + 2) || *(arg + 3))
+                               return -E_AFT_SYNTAX;
+                       switch(*(arg + 2)) {
+                       case 'p':
+                               sort = LS_SORT_BY_PATH;
+                               continue;
+                       case 's': /* -ss implies -a */
+                               sort = LS_SORT_BY_SCORE;
+                               flags |= LS_FLAG_ADMISSIBLE_ONLY;
+                               continue;
+                       case 'l':
+                               sort = LS_SORT_BY_LAST_PLAYED;
+                               continue;
+                       case 'n':
+                               sort = LS_SORT_BY_NUM_PLAYED;
+                               continue;
+                       case 'f':
+                               sort = LS_SORT_BY_FREQUENCY;
+                               continue;
+                       case 'c':
+                               sort = LS_SORT_BY_CHANNELS;
+                               continue;
+                       case 'i':
+                               sort = LS_SORT_BY_IMAGE_ID;
+                               continue;
+                       case 'y':
+                               sort = LS_SORT_BY_LYRICS_ID;
+                               continue;
+                       case 'b':
+                               sort = LS_SORT_BY_BITRATE;
+                               continue;
+                       case 'd':
+                               sort = LS_SORT_BY_DURATION;
+                               continue;
+                       case 'a':
+                               sort = LS_SORT_BY_AUDIO_FORMAT;
+                               continue;
+                       default:
+                               return -E_AFT_SYNTAX;
+                       }
+               }
+               return -E_AFT_SYNTAX;
+       }
+       time(&now);
+       opts.flags = flags;
+       opts.sorting = sort;
+       opts.mode = mode;
+       opts.num_patterns = argc - i;
+       ret = send_option_arg_callback_request(&query, opts.num_patterns,
+               argv + i, com_ls_callback, &ls_output);
+       if (ret >= 0 && ls_output.data) {
+               printf("%s\n", (char *)ls_output.data);
+               free(ls_output.data);
+       }
+       return ret;
+}
+
+/**
+ * Call the given function for each file in the audio file table.
+ *
+ * \param private_data An arbitrary data pointer, passed to \a func.
+ * \param func The custom function to be called.
+ *
+ * \return The return value of the underlying call to osl_rbtree_loop().
+ */
+int audio_file_loop(void *private_data, osl_rbtree_loop_func *func)
+{
+       return osl_rbtree_loop(audio_file_table, AFTCOL_HASH, private_data,
+               func);
+}
+
+static void *find_hash_sister(HASH_TYPE *hash)
+{
+       const struct osl_object obj = {.data = hash, .size = HASH_SIZE};
+       struct osl_row *row;
+
+       osl_get_row(audio_file_table, AFTCOL_HASH, &obj, &row);
+       return row;
+}
+
+#define AFTROW_HEADER_SIZE 4
+
+enum aft_row_offsets {
+       AFTROW_AFHI_OFFSET_POS = 0,
+       AFTROW_CHUNKS_OFFSET_POS = 2,
+       AFTROW_HASH_OFFSET = AFTROW_HEADER_SIZE,
+       AFTROW_FLAGS_OFFSET = (AFTROW_HASH_OFFSET + HASH_SIZE),
+       AFTROW_PATH_OFFSET = (AFTROW_FLAGS_OFFSET + 4)
+};
+
+/* never save the afsi, as the server knows it too. Note that afhi might be NULL.
+ * In this case, afhi won't be stored in the buffer  */
+static void save_audio_file_info(HASH_TYPE *hash, const char *path,
+               struct audio_format_info *afhi,
+               uint32_t flags, struct osl_object *obj)
+{
+       size_t path_len = strlen(path) + 1;
+       size_t afhi_size = sizeof_afhi_buf(afhi);
+       size_t size = AFTROW_PATH_OFFSET + path_len + afhi_size
+               + sizeof_chunk_info_buf(afhi);
+       char *buf = para_malloc(size);
+       uint16_t pos;
+
+       memcpy(buf + AFTROW_HASH_OFFSET, hash, HASH_SIZE);
+       write_u32(buf + AFTROW_FLAGS_OFFSET, flags);
+       strcpy(buf + AFTROW_PATH_OFFSET, path);
+
+       pos = AFTROW_PATH_OFFSET + path_len;
+       PARA_DEBUG_LOG("size: %zu, afhi starts at %d\n", size, pos);
+       PARA_DEBUG_LOG("last afhi byte: %p, pos %d\n", buf + pos + afhi_size - 1,
+               pos + afhi_size - 1);
+       write_u16(buf + AFTROW_AFHI_OFFSET_POS, pos);
+       save_afhi(afhi, buf + pos);
+
+       pos += afhi_size;
+       PARA_DEBUG_LOG("size: %zu, chunks start at %d\n", size, pos);
+       write_u16(buf + AFTROW_CHUNKS_OFFSET_POS, pos);
+       save_chunk_info(afhi, buf + pos);
+       PARA_DEBUG_LOG("last byte in buf: %p\n", buf + size - 1);
+       obj->data = buf;
+       obj->size = size;
+}
+
+/*
+input:
+~~~~~~
+HS:    hash sister
+PB:    path brother
+F:     force flag given
+
+output:
+~~~~~~~
+AFHI:  whether afhi and chunk table are computed and sent
+ACTION:        table modifications to be performed
+
++---+----+-----+------+---------------------------------------------------+
+| HS | PB | F  | AFHI | ACTION
++---+----+-----+------+---------------------------------------------------+
+| Y |  Y |  Y  |  Y   | if HS != PB: remove PB. HS: force afhi update,
+|                     | update path, keep afsi
++---+----+-----+------+---------------------------------------------------+
+| Y |  Y |  N  |  N   | if HS == PB: do not send callback request at all.
+|                     | otherwise: remove PB, HS: update path, keep afhi,
+|                     | afsi.
++---+----+-----+------+---------------------------------------------------+
+| Y |  N |  Y  |  Y   | (rename) force afhi update of HS, update path of
+|                     | HS, keep afsi
++---+----+-----+------+---------------------------------------------------+
+| Y |  N |  N  |  N   | (file rename) update path of HS, keep afsi, afhi
++---+----+-----+------+---------------------------------------------------+
+| N |  Y |  Y  |  Y   | (file change) update afhi, hash, of PB, keep afsi
+|                     | (force has no effect)
++---+----+-----+------+---------------------------------------------------+
+| N |  Y |  N  |  Y   | (file change) update afhi, hash of PB, keep afsi
++---+----+-----+------+---------------------------------------------------+
+| N |  N |  Y  |  Y   | (new file) create new entry (force has no effect)
++---+----+-----+------+---------------------------------------------------+
+| N |  N |  N  |  Y   | (new file) create new entry
++---+----+-----+------+---------------------------------------------------+
+
+afhi <=> force or no HS
+
+*/
+
+
+#define ADD_FLAG_LAZY 1
+#define ADD_FLAG_FORCE 2
+#define ADD_FLAG_VERBOSE 4
+
+static int com_add_callback(const struct osl_object *query,
+               __a_unused struct osl_object *result)
+{
+       char *buf = query->data, *path;
+       struct osl_row *pb, *aft_row;
+       const struct osl_row *hs;
+       struct osl_object objs[NUM_AFT_COLUMNS];
+       HASH_TYPE *hash;
+       char asc[2 * HASH_SIZE + 1];
+       int ret;
+       char afsi_buf[AFSI_SIZE];
+       uint32_t flags = read_u32(buf + AFTROW_FLAGS_OFFSET);
+
+       hash = buf + AFTROW_HASH_OFFSET;
+       hash_to_asc(hash, asc);;
+       objs[AFTCOL_HASH].data = buf + AFTROW_HASH_OFFSET;
+       objs[AFTCOL_HASH].size = HASH_SIZE;
+
+       path = buf + AFTROW_PATH_OFFSET;
+       objs[AFTCOL_PATH].data = path;
+       objs[AFTCOL_PATH].size = strlen(path) + 1;
+
+       PARA_DEBUG_LOG("request to add %s with hash %s\n", path, asc);
+       hs = find_hash_sister(hash);
+       ret = aft_get_row_of_path(path, &pb);
+       if (ret < 0 && ret != -E_RB_KEY_NOT_FOUND)
+               return ret;
+       if (hs && pb && hs == pb && !(flags & ADD_FLAG_FORCE)) {
+               if (flags & ADD_FLAG_VERBOSE)
+                       PARA_NOTICE_LOG("ignoring duplicate %p\n", path);
+               return 1;
+       }
+       if (hs && hs != pb) {
+               struct osl_object obj;
+               if (pb) { /* hs trumps pb, remove pb */
+                       if (flags & ADD_FLAG_VERBOSE)
+                               PARA_NOTICE_LOG("removing path brother\n");
+                       ret = osl_del_row(audio_file_table, pb);
+                       if (ret < 0)
+                               return ret;
+                       pb = NULL;
+               }
+               /* file rename, update hs' path */
+               ret = osl_get_object(audio_file_table, hs, AFTCOL_PATH, &obj);
+               if (flags & ADD_FLAG_VERBOSE)
+                       PARA_NOTICE_LOG("rename %s -> %s\n", (char *)obj.data, path);
+               ret = osl_update_object(audio_file_table, hs, AFTCOL_PATH,
+                       &objs[AFTCOL_PATH]);
+               if (ret < 0)
+                       return ret;
+               if (!(flags & ADD_FLAG_FORCE))
+                       return ret;
+       }
+       /* no hs or force mode, child must have sent afhi */
+       uint16_t afhi_offset = read_u16(buf + AFTROW_AFHI_OFFSET_POS);
+       uint16_t chunks_offset = read_u16(buf + AFTROW_CHUNKS_OFFSET_POS);
+
+       objs[AFTCOL_AFHI].data = buf + afhi_offset;
+       objs[AFTCOL_AFHI].size = chunks_offset - afhi_offset;
+       if (!objs[AFTCOL_AFHI].size) /* "impossible" */
+               return -E_NO_AFHI;
+       objs[AFTCOL_CHUNKS].data = buf + chunks_offset;
+       objs[AFTCOL_CHUNKS].size = query->size - chunks_offset;
+       if (pb && !hs) { /* update pb's hash */
+               char old_asc[2 * HASH_SIZE + 1];
+               HASH_TYPE *old_hash;
+               ret = get_hash_of_row(pb, &old_hash);
+               if (ret < 0)
+                       return ret;
+               hash_to_asc(old_hash, old_asc);
+               if (flags & ADD_FLAG_VERBOSE)
+                       PARA_NOTICE_LOG("file change: %s %s -> %s\n", path,
+                               old_asc, asc);
+               ret = osl_update_object(audio_file_table, pb, AFTCOL_HASH,
+                       &objs[AFTCOL_HASH]);
+               if (ret < 0)
+                       return ret;
+       }
+       if (hs || pb) { /* (hs != NULL and pb != NULL) implies hs == pb */
+               const void *row = pb? pb : hs;
+               /* update afhi and chunk_table */
+               if (flags & ADD_FLAG_VERBOSE)
+                       PARA_NOTICE_LOG("updating audio format handler info (%zd bytes)\n",
+                               objs[AFTCOL_AFHI].size);
+               ret = osl_update_object(audio_file_table, row, AFTCOL_AFHI,
+                       &objs[AFTCOL_AFHI]);
+               if (ret < 0)
+                       return ret;
+               if (flags & ADD_FLAG_VERBOSE)
+                       PARA_NOTICE_LOG("updating chunk table\n");
+               ret = osl_update_object(audio_file_table, row, AFTCOL_CHUNKS,
+                       &objs[AFTCOL_CHUNKS]);
+               if (ret < 0)
+                       return ret;
+               ret = mood_update_audio_file(row, NULL);
+               if (ret < 0)
+                       return ret;
+       }
+       /* new entry, use default afsi */
+       if (flags & ADD_FLAG_VERBOSE)
+               PARA_NOTICE_LOG("adding %s\n", path);
+       objs[AFTCOL_AFSI].data = &afsi_buf;
+       objs[AFTCOL_AFSI].size = AFSI_SIZE;
+       save_afsi(NULL, &objs[AFTCOL_AFSI]);
+       ret = osl_add_and_get_row(audio_file_table, objs, &aft_row);
+       if (ret < 0)
+               return ret;
+       return mood_update_audio_file(aft_row, NULL);
+}
+
+struct private_add_data {
+       int fd;
+       uint32_t flags;
+};
+
+static int add_one_audio_file(const char *arg, const void *private_data)
+{
+       int ret;
+       const struct private_add_data *pad = private_data;
+       struct audio_format_info afhi, *afhi_ptr = NULL;
+       struct osl_row *pb, *hs; /* path brother/hash sister */
+       struct osl_object map, obj = {.data = NULL};
+       char *path;
+       HASH_TYPE hash[HASH_SIZE];
+
+       afhi.header_offset = 0;
+       afhi.header_len = 0;
+       ret = verify_path(arg, &path);
+       if (ret < 0)
+               return ret;
+       ret = aft_get_row_of_path(path, &pb);
+       if (ret < 0 && ret != -E_RB_KEY_NOT_FOUND)
+               goto out_free;
+       ret = 1;
+       if (pb && (pad->flags & ADD_FLAG_LAZY)) { /* lazy is really cheap */
+               if (pad->flags & ADD_FLAG_VERBOSE)
+                       PARA_NOTICE_LOG("lazy-ignore: %s\n", path);
+               goto out_free;
+       }
+       /* we still want to add this file. Compute its hash and look it up */
+       ret = mmap_full_file(path, O_RDONLY, &map);
+       if (ret < 0)
+               goto out_free;
+       hash_function(map.data, map.size, hash);
+       hs = find_hash_sister(hash);
+       /*
+        * return success if we're pretty sure that we already know this file
+        */
+       ret = 1;
+       if (pb && hs && hs == pb && (!(pad->flags & ADD_FLAG_FORCE))) {
+               if (pad->flags & ADD_FLAG_VERBOSE)
+                       PARA_NOTICE_LOG("not forcing update: %s\n", path);
+               goto out_unmap;
+       }
+       /*
+        * we won't recalculate the audio format info and the chunk table if
+        * there is a hash sister unless in FORCE mode.
+        */
+       if (!hs || (pad->flags & ADD_FLAG_FORCE)) {
+               ret = mp3_get_file_info(map.data, map.size, &afhi);
+               if (ret < 0) {
+                       PARA_WARNING_LOG("audio format of %s not recognized, skipping\n", path);
+                       ret = 1;
+                       goto out_unmap;
+               }
+               afhi_ptr = &afhi;
+       }
+       munmap(map.data, map.size);
+       save_audio_file_info(hash, path, afhi_ptr, pad->flags, &obj);
+       /* ask parent to consider this entry for adding */
+       ret = send_callback_request(com_add_callback, &obj, NULL);
+       goto out_free;
+
+out_unmap:
+       munmap(map.data, map.size);
+out_free:
+       free(obj.data);
+       free(path);
+       if (afhi_ptr)
+               free(afhi_ptr->chunk_table);
+       return ret;
+}
+
+int com_add(int fd, int argc, const char **argv)
+{
+       int i, ret;
+       struct private_add_data pad = {.fd = fd, .flags = 0};
+       struct stat statbuf;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-l")) {
+                       pad.flags |= ADD_FLAG_LAZY;
+                       continue;
+               }
+               if (!strcmp(arg, "-f")) {
+                       pad.flags |= ADD_FLAG_FORCE;
+                       continue;
+               }
+               if (!strcmp(arg, "-v")) {
+                       pad.flags |= ADD_FLAG_VERBOSE;
+                       continue;
+               }
+       }
+       if (argc <= i)
+               return -E_AFT_SYNTAX;
+       for (; i < argc; i++) {
+               ret = stat(argv[i], &statbuf);
+               if (ret < 0)
+                       return -E_AFS_STAT;
+               if (S_ISDIR(statbuf.st_mode)) {
+                       ret = for_each_file_in_dir(argv[i],
+                               add_one_audio_file, &pad);
+                       if (ret < 0)
+                               return ret;
+                       continue;
+               }
+               ret = add_one_audio_file(argv[i], &pad);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = 1;
+out:
+       return ret;
+
+}
+
+struct com_touch_options {
+       long num_played;
+       long last_played;
+       long lyrics_id;
+       long image_id;
+};
+
+static int com_touch_callback(const struct osl_object *query,
+               __a_unused struct osl_object *result)
+{
+       struct com_touch_options *cto = query->data;
+       char *p = (char *)query->data + sizeof(*cto);
+       size_t len;
+       int ret, no_options = cto->num_played < 0 && cto->last_played < 0 &&
+               cto->lyrics_id < 0 && cto->image_id < 0;
+
+       for (;p < (char *)query->data + query->size; p += len + 1) {
+               struct afs_info old_afsi, new_afsi;
+               struct osl_object obj;
+               struct osl_row *row;
+
+               len = strlen(p);
+               ret = aft_get_row_of_path(p, &row);
+               if (ret < 0)
+                       return ret;
+               ret = get_afsi_object_of_row(row, &obj);
+               if (ret < 0)
+                       return ret;
+               ret = load_afsi(&old_afsi, &obj);
+               if (ret < 0)
+                       return ret;
+               new_afsi = old_afsi;
+               if (no_options) {
+                       new_afsi.num_played++;
+                       new_afsi.last_played = time(NULL);
+               } else {
+                       if (cto->lyrics_id >= 0)
+                               new_afsi.lyrics_id = cto->lyrics_id;
+                       if (cto->image_id >= 0)
+                               new_afsi.image_id = cto->image_id;
+                       if (cto->num_played >= 0)
+                               new_afsi.num_played = cto->num_played;
+                       if (cto->last_played >= 0)
+                               new_afsi.last_played = cto->last_played;
+               }
+               save_afsi(&new_afsi, &obj); /* in-place update */
+               ret = mood_update_audio_file(row, &old_afsi);
+               if (ret < 0)
+                       return ret;
+       }
+       return 1;
+}
+
+int com_touch(__a_unused int fd, int argc, const char **argv)
+{
+       struct com_touch_options cto = {
+               .num_played = -1,
+               .last_played = -1,
+               .lyrics_id = -1,
+               .image_id = -1
+       };
+       struct osl_object options = {.data = &cto, .size = sizeof(cto)};
+       int i, ret;
+
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strncmp(arg, "-n", 2)) {
+                       ret = para_atol(arg + 2, &cto.num_played);
+                       if (ret < 0)
+                               goto err;
+                       continue;
+               }
+               if (!strncmp(arg, "-l", 2)) {
+                       ret = para_atol(arg + 2, &cto.last_played);
+                       if (ret < 0)
+                               goto err;
+                       continue;
+               }
+               if (!strncmp(arg, "-y", 2)) {
+                       ret = para_atol(arg + 2, &cto.lyrics_id);
+                       if (ret < 0)
+                               goto err;
+                       continue;
+               }
+               if (!strncmp(arg, "-i", 2)) {
+                       ret = para_atol(arg + 2, &cto.image_id);
+                       if (ret < 0)
+                               goto err;
+                       continue;
+               }
+       }
+       ret = -E_AFT_SYNTAX;
+       if (i >= argc)
+               goto err;
+       return send_option_arg_callback_request(&options, argc - i,
+               argv + i, com_touch_callback, NULL);
+err:
+       return ret;
+}
+
+struct com_rm_options {
+       uint32_t flags;
+};
+
+static int com_rm_callback(const struct osl_object *query,
+               __a_unused struct osl_object *result)
+{
+       struct com_rm_options *cro = query->data;
+       char *p = (char *)query->data + sizeof(*cro);
+       size_t len;
+       int ret;
+
+       for (;p < (char *)query->data + query->size; p += len + 1) {
+               struct osl_row *row;
+
+               len = strlen(p);
+               ret = aft_get_row_of_path(p, &row);
+               if (ret < 0)
+                       return ret;
+               ret = mood_delete_audio_file(row);
+               if (ret < 0)
+                       return ret;
+               ret = osl_del_row(audio_file_table, row);
+               if (ret < 0)
+                       return ret;
+       }
+       return 1;
+}
+
+/*
+ * TODO options: -v verbose, -f dont stop if file not found
+ * -h remove by hash, use fnmatch
+ *
+ * */
+
+int com_afs_rm(__a_unused int fd, int argc, const char **argv)
+{
+       struct com_rm_options cro = {.flags = 0};
+       struct osl_object options = {.data = &cro, .size = sizeof(cro)};
+       int i, ret;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+       }
+       ret = -E_AFT_SYNTAX;
+       if (i >= argc)
+               goto err;
+       return send_option_arg_callback_request(&options, argc - i,
+               argv + i, com_rm_callback, NULL);
+err:
+       return ret;
+}
+
+/**
+ * Close the audio file table.
+ *
+ * \param flags Ususal flags that are passed to osl_close_table().
+ *
+ * \sa osl_close_table().
+ */
+void aft_shutdown(enum osl_close_flags flags)
+{
+       osl_close_table(audio_file_table, flags);
+}
+
+/**
+ * Open the audio file table.
+ *
+ * \param ti Gets initialized by this function
+ *
+ * \return Positive on success, negative on errors.
+ *
+ * \sa osl_open_table().
+ */
+int aft_init(struct table_info *ti)
+{
+       int ret;
+
+       ti->desc = &audio_file_table_desc;
+       ret = osl_open_table(ti->desc, &ti->table);
+       if (ret >= 0) {
+               unsigned num;
+               audio_file_table = ti->table;
+               osl_get_num_rows(audio_file_table, &num);
+               PARA_INFO_LOG("audio file table contains %d files\n", num);
+               return ret;
+       }
+       audio_file_table = NULL;
+       return ret == -E_NOENT? 1 : ret;
+}
diff --git a/attribute.c b/attribute.c
new file mode 100644 (file)
index 0000000..db38fad
--- /dev/null
@@ -0,0 +1,395 @@
+#include "para.h"
+#include "error.h"
+#include "afs.h"
+#include "string.h"
+
+static void *attribute_table;
+static int greatest_att_bitnum;
+
+static int char_compare(const struct osl_object *obj1, const struct osl_object *obj2)
+{
+       const unsigned char *c1 = (const unsigned char*)obj1->data;
+       const unsigned char *c2 = (const unsigned char*)obj2->data;
+       if (*c1 > *c2)
+               return 1;
+       if (*c1 < *c2)
+               return -1;
+       return 0;
+}
+
+enum attribute_table_columns {ATTCOL_BITNUM, ATTCOL_NAME, NUM_ATT_COLUMNS};
+
+static struct osl_column_description att_cols[] = {
+       [ATTCOL_BITNUM] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
+               .name = "bitnum",
+               .compare_function = char_compare,
+               .data_size = 1
+       },
+       [ATTCOL_NAME] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_RBTREE | OSL_UNIQUE,
+               .name = "name",
+               .compare_function = string_compare,
+       }
+};
+
+static const struct osl_table_description attribute_table_desc = {
+       .dir = DATABASE_DIR,
+       .name = "attributes",
+       .num_columns = NUM_ATT_COLUMNS,
+       .flags = 0,
+       .column_descriptions = att_cols
+};
+
+static void find_greatest_att_bitnum(void)
+{
+       unsigned char c = 63;
+       do {
+               struct osl_row *row;
+               struct osl_object obj = {.data = &c, .size = 1};
+               if (osl_get_row(attribute_table, ATTCOL_BITNUM, &obj,
+                               &row) >= 0) {
+                       greatest_att_bitnum = c;
+                       return;
+               }
+       } while (c--);
+       PARA_INFO_LOG("%s\n", "no attributes");
+       greatest_att_bitnum = -E_NO_ATTRIBUTES;
+}
+
+int get_attribute_bitnum_by_name(const char *att_name, unsigned char *bitnum)
+{
+       struct osl_object obj = {.data = (char *)att_name,
+               .size = strlen(att_name) + 1};
+       struct osl_row *row;
+       int ret = osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row);
+
+       if (ret < 0)
+               return ret;
+       ret = osl_get_object(attribute_table, row, ATTCOL_BITNUM, &obj);
+       if (ret < 0)
+               return ret;
+       *bitnum = *(unsigned char *)obj.data;
+       return 1;
+}
+
+#define LAA_FLAG_ALPHA 1
+#define LAA_FLAG_LONG 2
+
+struct private_laa_data {
+       int fd;
+       unsigned flags;
+};
+
+static int log_attribute(struct osl_row *row, void *private_data)
+{
+       struct private_laa_data *pld = private_data;
+       int ret;
+       struct osl_object name_obj, bitnum_obj;
+
+       ret = osl_get_object(attribute_table, row, ATTCOL_NAME, &name_obj);
+       if (ret < 0)
+               return ret;
+       if (!(pld->flags & LAA_FLAG_LONG)) {
+               printf("%s\n", (char *)name_obj.data);
+               return 1;
+       }
+       ret = osl_get_object(attribute_table, row, ATTCOL_BITNUM, &bitnum_obj);
+       if (ret < 0)
+               return ret;
+       printf("%u\t%s\n", *(unsigned char*)bitnum_obj.data,
+               (char *)name_obj.data);
+       return 1;
+}
+
+int com_lsatt(int fd, int argc, const char **argv)
+{
+       struct private_laa_data pld = {.fd = fd, .flags = 0};
+       int i;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-a")) {
+                       pld.flags |= LAA_FLAG_ALPHA;
+                       continue;
+               }
+               if (!strcmp(arg, "-l")) {
+                       pld.flags |= LAA_FLAG_LONG;
+                       continue;
+               }
+       }
+       if (argc > i)
+               return -E_ATTR_SYNTAX;
+       if (pld.flags & LAA_FLAG_ALPHA)
+               return osl_rbtree_loop(attribute_table, ATTCOL_NAME,
+                       &pld, log_attribute);
+       return osl_rbtree_loop(attribute_table, ATTCOL_BITNUM,
+               &pld, log_attribute);
+}
+
+static int com_setatt_callback(const struct osl_object *query,
+               __a_unused struct osl_object *result)
+{
+       char *p;
+       uint64_t add_mask = 0, del_mask = 0;
+       int ret;
+       size_t len;
+       struct osl_object obj;
+       struct osl_row *row;
+
+       for (p = query->data; p < (char *)query->data + query->size; p += len + 1) {
+               char c;
+
+               len = strlen(p);
+               if (!*p)
+                       return -E_ATTR_SYNTAX;
+               c = p[len - 1];
+               if (c != '+' && c != '-')
+                       break;
+               p[len - 1] = '\0';
+               obj.data = p;
+               obj.size = len + 1;
+               ret = osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row);
+               if (ret < 0)
+                       return ret;
+               ret = osl_get_object(attribute_table, row, ATTCOL_BITNUM,
+                       &obj);
+               if (ret < 0)
+                       return ret;
+               if (c == '+')
+                       add_mask |= (1UL << *(unsigned char *)obj.data);
+               else
+                       del_mask |= (1UL << *(unsigned char *)obj.data);
+       }
+       if (!add_mask && !del_mask)
+               return -E_ATTR_SYNTAX;
+       PARA_DEBUG_LOG("masks: %llx:%llx\n", add_mask, del_mask);
+       for (; p < (char *)query->data + query->size; p += len + 1) { /* TODO: fnmatch */
+               struct afs_info old_afsi, new_afsi;
+               struct osl_row *aft_row;
+
+               len = strlen(p);
+               ret = aft_get_row_of_path(p, &aft_row);
+               if (ret < 0)
+                       return ret;
+               ret = get_afsi_object_of_row(p, &obj);
+               if (ret < 0)
+                       return ret;
+               ret = load_afsi(&old_afsi, &obj);
+               if (ret < 0)
+                       return ret;
+               new_afsi = old_afsi;
+               new_afsi.attributes |= add_mask;
+               new_afsi.attributes &= ~del_mask;
+               save_afsi(&new_afsi, &obj); /* in-place update */
+               ret = mood_update_audio_file(aft_row, &old_afsi);
+               if (ret < 0)
+                       return ret;
+       }
+       return 1;
+}
+
+int com_setatt(__a_unused int fd, int argc, const char **argv)
+{
+       if (argc < 2)
+               return -E_ATTR_SYNTAX;
+       return send_standard_callback_request(argc, argv, com_setatt_callback,
+               NULL);
+}
+
+/* TODO: make it faster by only extracting the attribute member from afsi */
+static int logical_and_attribute(struct osl_row *aft_row, void *attribute_ptr)
+{
+       struct afs_info afsi;
+       uint64_t *att = attribute_ptr;
+       struct osl_object obj;
+       int ret = get_afsi_object_of_row(aft_row, &obj);
+       if (ret < 0)
+               return ret;
+       ret = load_afsi(&afsi, &obj);
+       if (ret < 0)
+               return ret;
+       afsi.attributes &= *att;
+       save_afsi(&afsi, &obj);
+       return 1;
+}
+
+static int com_addatt_callback(const struct osl_object *query,
+               __a_unused struct osl_object *result)
+{
+       char *p = query->data;
+       uint64_t atts_added = 0;
+       int ret;
+
+       while (p < (char *)query->data + query->size) {
+               struct osl_object objs[NUM_ATT_COLUMNS];
+               struct osl_row *row;
+               unsigned char bitnum;
+
+               objs[ATTCOL_BITNUM].size = 1;
+               objs[ATTCOL_NAME].data = p;
+               objs[ATTCOL_NAME].size = strlen(p) + 1;
+               ret = osl_get_row(attribute_table, ATTCOL_NAME,
+                       &objs[ATTCOL_NAME], &row); /* expected to fail */
+               if (ret >= 0)
+                       return -E_ATTR_EXISTS;
+               if (ret != -E_RB_KEY_NOT_FOUND) /* error */
+                       return ret;
+               /* find smallest non-used attribute */
+               for (bitnum = 0; bitnum < 64; bitnum++) {
+                       objs[ATTCOL_BITNUM].data = &bitnum;
+                       ret = osl_get_row(attribute_table, ATTCOL_BITNUM,
+                               &objs[ATTCOL_BITNUM], &row);
+                       if (ret == -E_RB_KEY_NOT_FOUND)
+                               break; /* this bitnum is unused, use it */
+                       if (ret < 0) /* error */
+                               return ret;
+                       /* this bit is already in use, try next bit */
+               }
+               if (bitnum == 64)
+                       return -E_ATTR_TABLE_FULL;
+               ret = osl_add_row(attribute_table, objs);
+               if (ret < 0)
+                       return ret;
+               greatest_att_bitnum = PARA_MAX(greatest_att_bitnum, bitnum);
+               atts_added |= 1 << bitnum;
+               p += strlen(p) + 1;
+       }
+       if (!atts_added)
+               return 1;
+       atts_added = ~atts_added;
+       ret = audio_file_loop(&atts_added, logical_and_attribute);
+       if (ret < 0)
+               return ret;
+       find_greatest_att_bitnum();
+       return mood_reload();
+}
+
+int com_addatt(__a_unused int fd, int argc, const char **argv)
+{
+       if (argc < 2)
+               return -E_ATTR_SYNTAX;
+       return send_standard_callback_request(argc, argv, com_addatt_callback,
+               NULL);
+}
+
+static int com_rmatt_callback(const struct osl_object *query,
+               __a_unused struct osl_object *result)
+{
+       char *p = query->data;
+       int ret, atts_removed = 0;
+       while (p < (char *)query->data + query->size) {
+               struct osl_object obj = {
+                       .data = p,
+                       .size = strlen(p) + 1
+               };
+               struct osl_row *row;
+               ret = osl_get_row(attribute_table, ATTCOL_NAME,
+                       &obj, &row);
+               if (ret < 0)
+                       return ret;
+               ret = osl_del_row(attribute_table, row);
+               if (ret < 0)
+                       return ret;
+               atts_removed++;
+               p += strlen(p) + 1;
+       }
+       find_greatest_att_bitnum();
+       if (!atts_removed)
+               return 1;
+       return mood_reload();
+}
+
+int com_rmatt(__a_unused int fd, int argc, const char **argv)
+{
+       if (argc < 2)
+               return -E_ATTR_SYNTAX;
+       return send_standard_callback_request(argc, argv, com_rmatt_callback,
+               NULL);
+}
+
+void get_attribute_bitmap(uint64_t *atts, char *buf)
+{
+       int i;
+       const uint64_t one = 1;
+
+       for (i = 0; i <= greatest_att_bitnum; i++)
+               buf[greatest_att_bitnum - i] = (*atts & (one << i))? 'x' : '-';
+       buf[i] = '\0';
+}
+/**
+ * Get a string containing the set attributes in text form.
+ *
+ * \param atts The attribute bitmap.
+ * \param delim The delimiter to separate matching attribute names.
+ * \param text Result pointer.
+ *
+ * \return Positive on success, negative on errors. If no attributes have
+ * been defined, \a *text is NULL.
+ */
+int get_attribute_text(uint64_t *atts, const char *delim, char **text)
+{
+       int i, ret;
+       const uint64_t one = 1;
+
+       *text = NULL;
+       if (greatest_att_bitnum < 0) /* no attributes available */
+               return 1;
+       for (i = 0; i <= greatest_att_bitnum; i++) {
+               unsigned char bn = i;
+               struct osl_object obj = {.data = &bn, .size = 1};
+               struct osl_row *row;
+
+               if (!(*atts & (one << i)))
+                       continue;
+               ret = osl_get_row(attribute_table, ATTCOL_BITNUM, &obj, &row);
+               if (ret < 0)
+                       goto err;
+               ret = osl_get_object(attribute_table, row, ATTCOL_NAME, &obj);
+               if (ret < 0)
+                       goto err;
+               if (*text) {
+                       char *tmp = make_message("%s%s%s", *text, delim, (char *)obj.data);
+                       free(*text);
+                       *text = tmp;
+               } else
+                       *text = para_strdup(obj.data);
+       }
+       if (!*text) /* no attributes set */
+               *text = para_strdup("");
+       return 1;
+err:
+       free(*text);
+       return ret;
+}
+
+void attribute_shutdown(enum osl_close_flags flags)
+{
+       osl_close_table(attribute_table, flags);
+}
+
+int attribute_init(struct table_info *ti)
+{
+       int ret;
+
+       ti->desc = &attribute_table_desc;
+       ret = osl_open_table(ti->desc, &ti->table);
+       greatest_att_bitnum = -1; /* no atts available */
+       if (ret >= 0) {
+               attribute_table = ti->table;
+               find_greatest_att_bitnum();
+               return ret;
+       }
+       attribute_table = NULL;
+       if (ret == -E_NOENT)
+               return 1;
+       return ret;
+}
index 9b79121..d4abb3b 100644 (file)
--- a/audiod.c
+++ b/audiod.c
@@ -497,7 +497,7 @@ out:
        return count;
 }
 
-static void check_stat_line(char *line)
+static int check_stat_line(char *line, __a_unused void *data)
 {
        int itemnum;
        size_t ilen = 0;
@@ -506,14 +506,14 @@ static void check_stat_line(char *line)
 
 //     PARA_INFO_LOG("line: %s\n", line);
        if (!line)
-               return;
+               return 1;
        itemnum = stat_line_valid(line);
        if (itemnum < 0) {
                PARA_WARNING_LOG("invalid status line: %s\n", line);
-               return;
+               return 1;
        }
        if (stat_task->clock_diff_count && itemnum != SI_CURRENT_TIME)
-               return;
+               return 1;
        tmp = make_message("%s\n", line);
        stat_client_write(tmp, itemnum);
        free(tmp);
@@ -557,6 +557,7 @@ static void check_stat_line(char *line)
                        stat_task->clock_diff_count--;
                break;
        }
+       return 1;
 }
 
 static void try_to_close_slot(int slot_num)
@@ -1068,7 +1069,7 @@ static void status_post_select(__a_unused struct sched *s, struct task *t)
                return;
        }
        bytes_left = for_each_line(st->pcd->buf, st->pcd->loaded,
-               &check_stat_line);
+               &check_stat_line, NULL);
        if (st->pcd->loaded != bytes_left) {
                st->last_status_read = *now;
                st->pcd->loaded = bytes_left;
diff --git a/blob.c b/blob.c
new file mode 100644 (file)
index 0000000..25dd99e
--- /dev/null
+++ b/blob.c
@@ -0,0 +1,393 @@
+#include "para.h"
+#include "error.h"
+#include "afs.h"
+#include "string.h"
+
+/** \file blob.c Macros and functions for blob handling. */
+
+static struct osl_column_description blob_cols[] = {
+       [BLOBCOL_ID] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_RBTREE | OSL_UNIQUE | OSL_FIXED_SIZE,
+               .name = "id",
+               .data_size = 4,
+               .compare_function = uint32_compare
+       },
+       [BLOBCOL_NAME] = {
+               .storage_type = OSL_MAPPED_STORAGE,
+               .storage_flags = OSL_RBTREE | OSL_UNIQUE,
+               .name = "name",
+               .compare_function = string_compare
+       },
+       [BLOBCOL_DEF] = {
+               .storage_type = OSL_DISK_STORAGE,
+               .storage_flags = 0,
+               .name = "definition"
+       }
+};
+
+/** \cond doxygen isn't smart enough to recognize these */
+INIT_BLOB_TABLE(lyrics);
+INIT_BLOB_TABLE(images);
+INIT_BLOB_TABLE(moods);
+INIT_BLOB_TABLE(playlists);
+/** \endcond */
+
+/** Flags that may be passed to the \p ls functions of each blob  type. */
+enum blob_ls_flags {
+       /** List both id and name. */
+       BLOB_LS_FLAG_LONG = 1,
+       /** Reverse sort order. */
+       BLOB_LS_FLAG_REVERSE = 2,
+       /** Sort by id instead of name. */
+       BLOB_LS_FLAG_SORT_BY_ID = 4,
+};
+
+/** Data passed to \p com_lsbob_callback(). */
+struct com_lsblob_options {
+       /** Given flags for the ls command. */
+       uint32_t flags;
+};
+
+/** Structure passed to the \p print_blob loop function. */
+struct lsblob_loop_data {
+       struct com_lsblob_options *opts;
+       struct para_buffer *pb;
+       struct osl_table *table;
+};
+
+static int print_blob(struct osl_row *row, void *loop_data)
+{
+       struct osl_object obj;
+       char *name;
+       uint32_t id;
+       int ret;
+       struct lsblob_loop_data *lld = loop_data;
+
+       ret = osl_get_object(lld->table, row, BLOBCOL_NAME, &obj);
+       if (ret < 0)
+               return ret;
+       name = obj.data;
+       if (!*name) /* ignore dummy row */
+               return 1;
+       ret = osl_get_object(lld->table, row, BLOBCOL_ID, &obj);
+       if (ret < 0)
+               return ret;
+       id = *(uint32_t *)obj.data;
+       if (lld->opts->flags & BLOB_LS_FLAG_LONG)
+               para_printf(lld->pb, "%u\t%s\n", id, name);
+       else
+               para_printf(lld->pb, "%s\n", name);
+       return 1;
+}
+
+int com_lsblob_callback(struct osl_table *table,
+               const struct osl_object *query, struct osl_object *ls_output)
+{
+       struct para_buffer pb = {.buf = NULL};
+       struct lsblob_loop_data lld = {.opts = query->data, .pb = &pb, .table = table};
+       int ret;
+
+       if (lld.opts->flags & BLOB_LS_FLAG_REVERSE) {
+               if (lld.opts->flags & BLOB_LS_FLAG_SORT_BY_ID)
+                       ret = osl_rbtree_loop(lld.table, BLOBCOL_ID, &lld, print_blob);
+               else
+                       ret = osl_rbtree_loop_reverse(lld.table, BLOBCOL_NAME, &lld, print_blob);
+       } else {
+               if (lld.opts->flags & BLOB_LS_FLAG_SORT_BY_ID)
+                       ret = osl_rbtree_loop_reverse(lld.table, BLOBCOL_ID, &lld, print_blob);
+               else
+                       ret = osl_rbtree_loop(lld.table, BLOBCOL_NAME, &lld, print_blob);
+       }
+       ls_output->data = pb.buf;
+       ls_output->size = pb.size;
+       return ret;
+}
+
+static int com_lsblob(callback_function *f, __a_unused int fd, int argc, const char **argv)
+{
+       struct com_lsblob_options clbo = {.flags = 0};
+       struct osl_object query = {.data = &clbo, .size = sizeof(clbo)},
+               ls_output;
+       int i, ret;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-l")) {
+                       clbo.flags |= BLOB_LS_FLAG_LONG;
+                       continue;
+               }
+               if (!strcmp(arg, "-i")) {
+                       clbo.flags |= BLOB_LS_FLAG_SORT_BY_ID;
+                       continue;
+               }
+               if (!strcmp(arg, "-r")) {
+                       clbo.flags |= BLOB_LS_FLAG_REVERSE;
+                       continue;
+               }
+       }
+       if (argc > i)
+               return -E_BLOB_SYNTAX;
+       ret = send_option_arg_callback_request(&query, argc - i,
+               argv + i, f, &ls_output);
+       if (ret >= 0 && ls_output.data)
+               printf("%s\n", (char *)ls_output.data);
+       free(ls_output.data);
+       return ret;
+}
+
+static int com_catblob_callback(struct osl_table *table,
+               const struct osl_object *query, struct osl_object *output)
+{
+       struct osl_object obj;
+       int ret;
+       struct osl_row *row;
+
+       ret = osl_get_row(table, BLOBCOL_NAME, query, &row);
+       if (ret < 0)
+               return ret;
+       ret = osl_open_disk_object(table, row, BLOBCOL_DEF, &obj);
+       if (ret < 0)
+               return ret;
+       output->data = para_malloc(obj.size);
+       output->size = obj.size;
+       memcpy(output->data, obj.data, obj.size);
+       return osl_close_disk_object(&obj);
+}
+static int com_catblob(callback_function *f, __a_unused int fd, int argc,
+               const char **argv)
+{
+       struct osl_object cat_output = {.data = NULL};
+       int ret;
+
+       if (argc != 2)
+               return -E_BLOB_SYNTAX;
+       if (!*argv[1]) /* empty name is reserved of the dummy row */
+               return -E_BLOB_SYNTAX;
+       ret = send_standard_callback_request(1, argv + 1, f, &cat_output);
+       if (ret >= 0 && cat_output.data)
+               printf("%s\n", (char *)cat_output.data);
+       free(cat_output.data);
+       return ret;
+
+}
+
+static int com_addblob_callback(struct osl_table *table,
+               const struct osl_object *query,
+               __a_unused struct osl_object *result)
+{
+       struct osl_object objs[NUM_BLOB_COLUMNS];
+       char *name = query->data;
+       size_t name_len = strlen(name) + 1;
+       uint32_t id;
+       unsigned num_rows;
+       int ret;
+
+       ret = osl_get_num_rows(table, &num_rows);
+       if (ret < 0)
+               return ret;
+       if (!num_rows) { /* this is the first entry ever added */
+               /* insert dummy row containing the id */
+               id = 2; /* this entry will be entry #1, so 2 is the next */
+               objs[BLOBCOL_ID].data = &id;
+               objs[BLOBCOL_ID].size = sizeof(id);
+               objs[BLOBCOL_NAME].data = "";
+               objs[BLOBCOL_NAME].size = 1;
+               objs[BLOBCOL_DEF].data = "";
+               objs[BLOBCOL_DEF].size = 1;
+               ret = osl_add_row(table, objs);
+               if (ret < 0)
+                       return ret;
+       } else { /* get id of the dummy row and increment it */
+               struct osl_row *row;
+               struct osl_object obj = {.data = "", .size = 1};
+               ret = osl_get_row(table, BLOBCOL_NAME, &obj, &row);
+               if (ret < 0)
+                       return ret;
+               ret = osl_get_object(table, row, BLOBCOL_ID, &obj);
+               if (ret < 0)
+                       return ret;
+               id = *(uint32_t *)obj.data + 1;
+               obj.data = &id;
+               ret = osl_update_object(table, row, BLOBCOL_ID, &obj);
+               if (ret < 0)
+                       return ret;
+       }
+       id--;
+       objs[BLOBCOL_ID].data = &id;
+       objs[BLOBCOL_ID].size = sizeof(id);
+       objs[BLOBCOL_NAME].data = name;
+       objs[BLOBCOL_NAME].size = name_len;
+       objs[BLOBCOL_DEF].data = name + name_len;
+       objs[BLOBCOL_DEF].size = query->size - name_len;
+       return osl_add_row(table, objs);
+}
+
+static int com_addblob(callback_function *f, __a_unused int fd, int argc,
+               const char **argv)
+{
+       struct osl_object arg_obj;
+
+       if (argc != 2)
+               return -E_BLOB_SYNTAX;
+       if (!*argv[1]) /* empty name is reserved for the dummy row */
+               return -E_BLOB_SYNTAX;
+       PARA_NOTICE_LOG("argv[1]: %s\n", argv[1]);
+       arg_obj.size = strlen(argv[1]) + 1;
+       arg_obj.data = (char *)argv[1];
+       return stdin_command(&arg_obj, f, 10 * 1024 * 1024, NULL);
+}
+
+static int com_rmblob_callback(struct osl_table *table,
+               const struct osl_object *query,
+               __a_unused struct osl_object *result)
+{
+       char *p = query->data;
+       size_t len;
+       int ret;
+
+       for (; p < (char *)query->data + query->size; p += len + 1) {
+               struct osl_row *row;
+               struct osl_object obj;
+
+               len = strlen(p);
+               obj.data = p;
+               obj.size = len + 1;
+               ret = osl_get_row(table, BLOBCOL_NAME, &obj, &row);
+               if (ret < 0)
+                       return ret;
+               ret = osl_del_row(table, row);
+               if (ret < 0)
+                       return ret;
+       }
+       return 1;
+}
+
+static int com_rmblob(callback_function *f, __a_unused int fd, int argc,
+               const char **argv)
+{
+       if (argc < 2)
+               return -E_MOOD_SYNTAX;
+       return send_option_arg_callback_request(NULL, argc - 1, argv + 1, f,
+               NULL);
+}
+
+static int com_mvblob_callback(struct osl_table *table,
+               const struct osl_object *query,
+               __a_unused struct osl_object *result)
+{
+       char *src = (char *) query->data;
+       struct osl_object obj = {.data = src, .size = strlen(src) + 1};
+       char *dest = src + obj.size;
+       struct osl_row *row;
+       int ret = osl_get_row(table, BLOBCOL_NAME, &obj, &row);
+
+       if (ret < 0)
+               return ret;
+       obj.data = dest;
+       obj.size = strlen(dest) + 1;
+       return osl_update_object(table, row, BLOBCOL_NAME, &obj);
+}
+
+static int com_mvblob(callback_function *f,  __a_unused int fd,
+               int argc, const char **argv)
+{
+       if (argc != 3)
+               return -E_MOOD_SYNTAX;
+       return send_option_arg_callback_request(NULL, argc - 1, argv + 1, f,
+               NULL);
+}
+
+#define DEFINE_BLOB_COMMAND(cmd_name, table_name, cmd_prefix) \
+       static int com_ ## cmd_name ## cmd_prefix ## _callback(const struct osl_object *query, \
+                       struct osl_object *output) \
+       { \
+               return com_ ## cmd_name ## blob_callback(table_name ## _table, query, output); \
+       } \
+       int com_ ## cmd_name ## cmd_prefix(__a_unused int fd, int argc, const char **argv) \
+       { \
+               return com_ ## cmd_name ## blob(com_ ## cmd_name ## cmd_prefix ## _callback, fd, argc, argv); \
+       }
+
+static int blob_get_name_by_id(struct osl_table *table, uint32_t id,
+               char **name)
+{
+       struct osl_row *row;
+       struct osl_object obj = {.data = &id, .size = sizeof(id)};
+       int ret;
+
+       *name = NULL;
+       if (!id)
+               return 1;
+       ret = osl_get_row(table, BLOBCOL_ID, &obj, &row);
+       if (ret < 0)
+               return ret;
+       ret = osl_get_object(table, row, BLOBCOL_NAME, &obj);
+       if (ret < 0)
+               return ret;
+       *name = (char *)obj.data;
+       return 1;
+}
+/** Define the \p get_name_by_id function for this blob type. */
+#define DEFINE_GET_NAME_BY_ID(table_name, cmd_prefix) \
+       int cmd_prefix ## _get_name_by_id(uint32_t id, char **name) \
+       { \
+               return blob_get_name_by_id(table_name ## _table, id, name); \
+       }
+
+/** Define the \p shutdown function for this blob type. */
+#define DEFINE_BLOB_SHUTDOWN(table_name) \
+       void table_name ## _shutdown(enum osl_close_flags flags) \
+       { \
+               osl_close_table(table_name ## _table, flags); \
+               table_name ## _table = NULL; \
+       }
+
+static int blob_init(struct osl_table **table,
+               const struct osl_table_description *desc,
+               struct table_info *ti)
+{
+       int ret;
+
+       ti->desc = desc;
+       ret = osl_open_table(ti->desc, &ti->table);
+       if (ret >= 0) {
+               *table = ti->table;
+               return ret;
+       }
+       *table = NULL;
+       return ret == -E_NOENT? 1 : ret;
+}
+
+/** Define the \p init function for this blob type. */
+#define DEFINE_BLOB_INIT(table_name) \
+       int table_name ## _init(struct table_info *ti) \
+       { \
+               return blob_init(&table_name ## _table, \
+                       &table_name ## _table_desc, ti); \
+       }
+
+
+/** Define all functions for this blob type. */
+#define DEFINE_BLOB_FUNCTIONS(table_name, cmd_prefix) \
+       DEFINE_BLOB_COMMAND(ls, table_name, cmd_prefix) \
+       DEFINE_BLOB_COMMAND(cat, table_name, cmd_prefix) \
+       DEFINE_BLOB_COMMAND(add, table_name, cmd_prefix) \
+       DEFINE_BLOB_COMMAND(rm, table_name, cmd_prefix) \
+       DEFINE_BLOB_COMMAND(mv, table_name, cmd_prefix) \
+       DEFINE_GET_NAME_BY_ID(table_name, cmd_prefix); \
+       DEFINE_BLOB_SHUTDOWN(table_name); \
+       DEFINE_BLOB_INIT(table_name);
+
+/** \cond doxygen isn't smart enough to recognize these */
+DEFINE_BLOB_FUNCTIONS(lyrics, lyr);
+DEFINE_BLOB_FUNCTIONS(images, img);
+DEFINE_BLOB_FUNCTIONS(moods, mood);
+DEFINE_BLOB_FUNCTIONS(playlists, pl);
+/** \endcond */
index a0d3785..316adf1 100644 (file)
@@ -109,7 +109,8 @@ server_cmdline_objs="server.cmdline server_command_list random_selector_command_
        playlist_selector_command_list"
 server_errlist_objs="server mp3_afh vss command net string signal random_selector
        time daemon stat crypt http_send afs_common close_on_fork playlist_selector
-       ipc dccp dccp_send fd user_list chunk_queue"
+       ipc dccp dccp_send fd user_list chunk_queue afs osl aft mood score attribute
+       blob playlist sha1 rbtree"
 server_ldflags=""
 server_audio_formats=" mp3"
 
diff --git a/error.h b/error.h
index 060268d..8c11391 100644 (file)
--- a/error.h
+++ b/error.h
@@ -64,6 +64,16 @@ enum para_subsystem {
        SS_OSX_WRITE,
        SS_USER_LIST,
        SS_CHUNK_QUEUE,
+       SS_AFS,
+       SS_OSL,
+       SS_AFT,
+       SS_MOOD,
+       SS_SCORE,
+       SS_ATTRIBUTE,
+       SS_BLOB,
+       SS_PLAYLIST,
+       SS_SHA1,
+       SS_RBTREE,
        NUM_SS
 };
 
@@ -78,12 +88,125 @@ enum para_subsystem {
 #define ORTP_SEND_ERRORS
 #define GUI_ERRORS
 #define RINGBUFFER_ERRORS
-
-
+#define SCORE_ERRORS
+#define SHA1_ERRORS
 
 extern const char **para_errlist[];
 /** \endcond */
 
+#define OSL_ERRORS \
+       PARA_ERROR(OSL_OPENDIR, "can not open directory"), \
+       PARA_ERROR(OSL_CHDIR, "fixme"), \
+       PARA_ERROR(BAD_DB_DIR, "fixme"), \
+       PARA_ERROR(NO_COLUMN_DESC, "fixme"), \
+       PARA_ERROR(BAD_BASENAME, "fixme"), \
+       PARA_ERROR(BAD_STORAGE_TYPE, "fixme"), \
+       PARA_ERROR(BAD_STORAGE_FLAGS, "fixme"), \
+       PARA_ERROR(NO_COLUMN_NAME, "fixme"), \
+       PARA_ERROR(NO_COLUMNS, "fixme"), \
+       PARA_ERROR(BAD_COLUMN_NAME, "fixme"), \
+       PARA_ERROR(NO_UNIQUE_RBTREE_COLUMN, "fixme"), \
+       PARA_ERROR(NO_RBTREE_COL, "fixme"), \
+       PARA_ERROR(DUPLICATE_COL_NAME, "fixme"), \
+       PARA_ERROR(BAD_STORAGE_SIZE, "fixme"), \
+       PARA_ERROR(NO_COMPARE_FUNC, "fixme"), \
+       PARA_ERROR(NULL_OBJECT, "fixme"), \
+       PARA_ERROR(BAD_DATA_SIZE, "fixme"), \
+       PARA_ERROR(NOT_MAPPED, "fixme"), \
+       PARA_ERROR(ALREADY_MAPPED, "fixme"), \
+       PARA_ERROR(BAD_SIZE, "fixme"), \
+       PARA_ERROR(TRUNC, "fixme"), \
+       PARA_ERROR(UNLINK, "fixme"), \
+       PARA_ERROR(EXIST, "fixme"), \
+       PARA_ERROR(ISDIR, "fixme"), \
+       PARA_ERROR(NOTDIR, "fixme"), \
+       PARA_ERROR(NOENT, "fixme"), \
+       PARA_ERROR(OSL_PERM, "fixme"), \
+       PARA_ERROR(BAD_TABLE, "fixme"), \
+       PARA_ERROR(BAD_TABLE_HEADER, "fixme"), \
+       PARA_ERROR(BAD_TABLE_DESC, "fixme"), \
+       PARA_ERROR(RB_KEY_EXISTS, "fixme"), \
+       PARA_ERROR(RB_KEY_NOT_FOUND, "fixme"), \
+       PARA_ERROR(BAD_ID, "fixme"), \
+       PARA_ERROR(INDEX_CORRUPTION, "fixme"), \
+       PARA_ERROR(BAD_OFFSET, "fixme"), \
+       PARA_ERROR(INVALID_OBJECT, "fixme"), \
+       PARA_ERROR(MKDIR, "fixme"), \
+       PARA_ERROR(OPEN, "fixme"), \
+       PARA_ERROR(STAT, "fixme"), \
+       PARA_ERROR(FSTAT, "fixme"), \
+       PARA_ERROR(RENAME, "fixme"), \
+       PARA_ERROR(EMPTY, "fixme"), \
+       PARA_ERROR(NOSPC, "fixme"), \
+       PARA_ERROR(MMAP, "fixme"), \
+       PARA_ERROR(MUNMAP, "fixme"), \
+       PARA_ERROR(WRITE, "fixme"), \
+       PARA_ERROR(LSEEK, "fixme"), \
+       PARA_ERROR(BUSY, "fixme"), \
+       PARA_ERROR(SHORT_TABLE, "fixme"), \
+       PARA_ERROR(NO_MAGIC, "fixme"), \
+       PARA_ERROR(VERSION_MISMATCH, "fixme"), \
+       PARA_ERROR(BAD_COLUMN_NUM, "fixme"), \
+       PARA_ERROR(BAD_TABLE_FLAGS, "fixme"), \
+       PARA_ERROR(RBTREE_EMPTY, "fixme"), \
+       PARA_ERROR(BAD_ROW, "fixme"), \
+       PARA_ERROR(OSL_GETCWD, "can not get current working directory"), \
+       PARA_ERROR(OSL_LSTAT, "fixme"), \
+
+
+#define RBTREE_ERRORS \
+
+
+#define AFS_ERRORS \
+       PARA_ERROR(AFS_SYNTAX, "fixme"), \
+       PARA_ERROR(FORK, "fixme"), \
+       PARA_ERROR(BAD_TABLE_NAME, "fixme"), \
+       PARA_ERROR(INPUT_TOO_LARGE, "fixme"), \
+
+
+#define MOOD_ERRORS \
+       PARA_ERROR(MOOD_SYNTAX, "fixme"), \
+       PARA_ERROR(MOOD_REGEX, "fixme"), \
+       PARA_ERROR(NO_MOOD, "fixme"), \
+       PARA_ERROR(MOOD_LOADED, "fixme"), \
+       PARA_ERROR(MOOD_BUSY, "fixme"), \
+       PARA_ERROR(NOT_ADMISSIBLE, "fixme"), \
+       PARA_ERROR(READ, "fixme"), \
+       PARA_ERROR(ATOL, "fixme"), \
+
+
+#define ATTRIBUTE_ERRORS \
+       PARA_ERROR(ATTR_SYNTAX, "fixme"), \
+       PARA_ERROR(ATTR_EXISTS, "fixme"), \
+       PARA_ERROR(ATTR_TABLE_FULL, "fixme"), \
+       PARA_ERROR(NO_ATTRIBUTES, "fixme"), \
+
+#define BLOB_ERRORS \
+       PARA_ERROR(BLOB_SYNTAX, "fixme"), \
+       PARA_ERROR(DUMMY_ROW, "fixme"), \
+
+
+#define PLAYLIST_ERRORS \
+       PARA_ERROR(PLAYLIST_SYNTAX, "fixme"), \
+       PARA_ERROR(NO_PLAYLIST, "fixme"), \
+       PARA_ERROR(PLAYLIST_LOADED, "fixme"), \
+       PARA_ERROR(PLAYLIST_EMPTY, "fixme"), \
+
+
+#define AFT_ERRORS \
+       PARA_ERROR(BAD_AFS, "fixme"), \
+       PARA_ERROR(LOCALTIME, "fixme"), \
+       PARA_ERROR(STRFTIME, "fixme"), \
+       PARA_ERROR(BAD_PATH, "fixme"), \
+       PARA_ERROR(BAD_SORT, "fixme"), \
+       PARA_ERROR(FNMATCH, "fixme"), \
+       PARA_ERROR(NO_MATCH, "fixme"), \
+       PARA_ERROR(NO_AFHI, "fixme"), \
+       PARA_ERROR(AFT_SYNTAX, "fixme"), \
+       PARA_ERROR(AFS_STAT, "fixme"), \
+       PARA_ERROR(HASH_MISMATCH, "fixme"), \
+
+
 #define USER_LIST_ERRORS \
        PARA_ERROR(USERLIST, "failed to open user list file"), \
 
@@ -555,6 +678,17 @@ SS_ENUM(CLIENT_COMMON);
 SS_ENUM(AUDIOC);
 SS_ENUM(USER_LIST);
 SS_ENUM(CHUNK_QUEUE);
+
+SS_ENUM(AFS);
+SS_ENUM(OSL);
+SS_ENUM(AFT);
+SS_ENUM(MOOD);
+SS_ENUM(SCORE);
+SS_ENUM(ATTRIBUTE);
+SS_ENUM(BLOB);
+SS_ENUM(PLAYLIST);
+SS_ENUM(SHA1);
+SS_ENUM(RBTREE);
 /** \endcond */
 #undef PARA_ERROR
 /* rest of the world only sees the error text */
index 985ebd2..0ec8fdd 100644 (file)
@@ -39,3 +39,5 @@
 # else
 # define __must_check  /* no warn_unused_result */
 # endif
+
+#define _static_inline_ static inline
diff --git a/gui.c b/gui.c
index a2d3e6c..37d0d8e 100644 (file)
--- a/gui.c
+++ b/gui.c
@@ -11,7 +11,7 @@
 #include "gui.h"
 #include <curses.h>
 #include "ringbuffer.h"
-#include "string.h"
+#include "gui_common.h"
 #include "fd.h"
 #include "error.h"
 #include "signal.h"
@@ -447,11 +447,12 @@ __printf_2_3 static void outputf(int color, const char* fmt,...)
        wrefresh(bot.win);
 }
 
-static void add_output_line(char *line)
+static int add_output_line(char *line, __a_unused void *data)
 {
        if (!curses_active)
-               return;
+               return 1;
        rb_add_entry(COLOR_OUTPUT, para_strdup(line));
+       return 1;
 }
 
 void para_log(int ll, const char *fmt,...)
@@ -735,7 +736,7 @@ reap_next_child:
 /*
  * print status line if line starts with known command.
  */
-static void check_stat_line(char *line)
+static int check_stat_line(char *line, __a_unused void *data)
 {
        int i;
 
@@ -747,7 +748,7 @@ static void check_stat_line(char *line)
                stat_content[i] = para_strdup(line);
                print_stat_item(i);
        }
-       return;
+       return 1;
 }
 
 /*
@@ -922,7 +923,8 @@ check_return:
                if (cp_numread <= 0 && !cbo) /* command complete */
                        return 0;
                if (cbo)
-                       cbo = for_each_line(command_buf, cbo, &add_output_line);
+                       cbo = for_each_line(command_buf, cbo,
+                               &add_output_line, NULL);
                if (cp_numread <= 0)
                        cbo = 0;
                wrefresh(bot.win);
index 26c3d61..6ca226e 100644 (file)
@@ -1,4 +1,5 @@
 #include "para.h"
+#include "string.h"
 #include "fd.h"
 
 extern const char *status_item_list[NUM_STAT_ITEMS];
@@ -17,7 +18,7 @@ int para_open_audiod_pipe(char *cmd)
        return ret;
 }
 
-int read_audiod_pipe(int fd, void (*line_handler)(char *) )
+int read_audiod_pipe(int fd, line_handler_t *line_handler)
 {
        static char buf[4096];
        const ssize_t bufsize = sizeof(buf) - 1;
@@ -30,7 +31,7 @@ int read_audiod_pipe(int fd, void (*line_handler)(char *) )
        if (ret > 0) {
                loaded += ret;
                buf[loaded] = '\0';
-               loaded = for_each_line(buf, loaded, line_handler);
+               loaded = for_each_line(buf, loaded, line_handler, NULL);
        }
        return ret;
 }
diff --git a/gui_common.h b/gui_common.h
new file mode 100644 (file)
index 0000000..0e8671f
--- /dev/null
@@ -0,0 +1,4 @@
+#include "string.h"
+int para_open_audiod_pipe(char *);
+int read_audiod_pipe(int fd, line_handler_t *line_handler);
+
diff --git a/hash.h b/hash.h
new file mode 100644 (file)
index 0000000..99f4486
--- /dev/null
+++ b/hash.h
@@ -0,0 +1,32 @@
+#include "portable_io.h"
+#define HASH_TYPE unsigned char
+
+//#include "super_fast_hash.h"
+//#define hash_function super_fast_hash
+#include "sha1.h"
+#define hash_function sha1_hash
+
+static inline int hash_compare(HASH_TYPE *h1, HASH_TYPE *h2)
+{
+       int i;
+
+       for (i = 0; i < HASH_SIZE; i++) {
+               if (h1[i] < h2[i])
+                       return -1;
+               if (h1[i] > h2[i])
+                       return 1;
+       }
+       return 0;
+}
+
+static inline void hash_to_asc(HASH_TYPE *hash, char *asc)
+{
+       int i;
+       const char hexchar[] = "0123456789abcdef";
+
+       for (i = 0; i < HASH_SIZE; i++) {
+               asc[2 * i] = hexchar[hash[i] >> 4];
+               asc[2 * i + 1] = hexchar[hash[i] & 0xf];
+       }
+       asc[2 * HASH_SIZE] = '\0';
+}
diff --git a/mood.c b/mood.c
new file mode 100644 (file)
index 0000000..b65561d
--- /dev/null
+++ b/mood.c
@@ -0,0 +1,978 @@
+#include "para.h"
+#include "error.h"
+#include "afs.h"
+#include "list.h"
+#include "string.h"
+
+/** \file mood.c Paraslash's mood handling functions. */
+
+/**
+ * Contains statistical data of the currently admissible audio files.
+ *
+ * It is used to assign normalized score values to each admissbile audio file.
+ */
+struct afs_statistics {
+       /** sum of num played over all admissible files */
+       int64_t num_played_sum;
+       /** sum of last played times over all admissible files */
+       int64_t last_played_sum;
+       /** quadratic deviation of num played time */
+       int64_t num_played_qd;
+       /** quadratic deviation of last played time */
+       int64_t last_played_qd;
+       /** number of admissible files */
+       unsigned num;
+};
+struct afs_statistics statistics;
+
+/**
+ * Assign scores according to a mood_method.
+ *
+ * Each mood_method has its own mood_score_function. The first parameter passed
+ * to that function is a pointer to a row of the audio file table.  It
+ * determines the audio file for which a score is to be assigned.  The second
+ * argument depends on the mood method this function is used for. It usually is
+ * the argument given at the end of a mood line.
+ *
+ * Mood score functions must return values between -100 and +100 inclisively.
+ * Boolean score functions should always return either -100 or +100.
+ *
+ * \sa struct mood_method, mood_parser.
+ */
+typedef int mood_score_function(const struct osl_row*, void *);
+
+/**
+ * Preprocess a mood line.
+ *
+ * The mood_parser of a mood_method is called once at mood open time for each
+ * line of the current mood definition that contains the mood_method's name as
+ * a keyword. The line is passed to the mood_parser as the first argument. The
+ * mood_parser must determine whether the line is syntactically correct and
+ * return a positive value if so and a negative value otherwise.
+ *
+ * Some mood parsers preprocess the data given in the mood line to compute a
+ * structure which depends of the particular mood_method and which is used
+ * later in the mood_score_function of the mood_method. The mood_parser may
+ * store a pointer to its structure via the second argument.
+ *
+ * \sa mood_open(), mood_cleanup_function, mood_score_function.
+ */
+typedef int mood_parser(const char *, void **);
+
+/**
+ * Deallocate resources which were allocated by the mood_parser.
+ *
+ * This optional function of a mood_method is used to free any resources
+ * allocated in mood_open() by the mood_parser. The argument passed is a
+ * pointer to the mood_method specific data structure that was returned by the
+ * mood_parser.
+ *
+ * \sa mood_parser.
+ */
+typedef void mood_cleanup_function(void *);
+
+/**
+ * Used for scoring and to determine whether a file is admissible.
+ */
+struct mood_method {
+       /* The name of the method. */
+       const char *name;
+       /** Pointer to the mood parser. */
+       mood_parser *parser;
+       /** Pointer to the score function */
+       mood_score_function *score_function;
+       /** Optional cleanup function. */
+       mood_cleanup_function *cleanup;
+};
+
+/**
+ * Each line of the current mood corresponds to a mood_item.
+ */
+struct mood_item {
+       /** The method this line is referring to. */
+       const struct mood_method *method;
+       /** The data structure computed by the mood parser. */
+       void *parser_data;
+       /** The given score value, or zero if none was given. */
+       long score_arg;
+       /** Non-zero if random scoring was requested. */
+       int random_score;
+       /** Whether the "not" keyword was given in the mood line. */
+       int logical_not;
+       /** The position in the list of items. */
+       struct list_head mood_item_node;
+};
+
+/**
+ * Created from the mood definition by mood_open().
+ *
+ * When a mood is opened, each line of its definition is investigated, and a
+ * corresponding mood item is produced. Each mood line starts with \p accept,
+ * \p deny, or \p score which determins the type of the mood line.  For each
+ * such type a linked list is maintained whose entries are the mood items.
+ *
+ * \sa mood_item, mood_open().
+ */
+struct mood {
+       /** the name of this mood */
+       char *name;
+       /** The list of mood items of type \p accept. */
+       struct list_head accept_list;
+       /** The list of mood items of type \p deny. */
+       struct list_head deny_list;
+       /** The list of mood items of type \p score. */
+       struct list_head score_list;
+};
+
+static struct mood *current_mood;
+
+/**
+ *  Rough approximation to sqrt.
+ *
+ *  \param x Integer of which to calculate the sqrt.
+ *
+ *  \return An integer res with res * res <= x.
+ */
+static uint64_t int_sqrt(uint64_t x)
+{
+       uint64_t op, res, one = 1;
+       op = x;
+       res = 0;
+
+       one = one << 62;
+       while (one > op)
+               one >>= 2;
+
+       while (one != 0) {
+               if (op >= res + one) {
+                       op = op - (res + one);
+                       res = res +  2 * one;
+               }
+               res /= 2;
+               one /= 4;
+       }
+//     PARA_NOTICE_LOG("sqrt(%llu) = %llu\n", x, res);
+       return res;
+}
+
+static int mm_played_rarely_score_function(const struct osl_row *row,
+       __a_unused void *ignored)
+{
+       struct afs_info afsi;
+       unsigned num;
+       int ret = get_afsi_of_row(row, &afsi);
+
+       if (ret < 0)
+               return 0;
+       ret = get_num_admissible_files(&num);
+       if (ret < 0)
+               return 0;
+       if (statistics.num_played_sum - num * afsi.num_played
+                       > int_sqrt(statistics.num_played_qd * num))
+               return 100;
+       return -100;
+}
+
+static int mm_played_rarely_parser(const char *arg, __a_unused void **ignored)
+{
+       if (*arg)
+               PARA_WARNING_LOG("ignored junk at eol: %s\n", arg);
+       return 1;
+}
+
+static int mm_name_like_score_function(const struct osl_row *row, void *preg)
+{
+       char *path;
+       int ret = get_audio_file_path_of_row(row, &path);
+
+       if (ret < 0)
+               return 0;
+       ret = regexec((regex_t *)preg, path, 42, NULL, 0);
+       return (ret == REG_NOMATCH)? -100 : 100;
+}
+
+static int mm_name_like_parser(const char *arg, void **regex)
+{
+       regex_t *preg = para_malloc(sizeof(*preg));
+       int ret = regcomp(preg, arg, REG_NOSUB);
+
+       if (ret) {
+               free(preg);
+               return -E_MOOD_REGEX;
+       }
+       *regex = preg;
+       return 1;
+}
+
+static void mm_name_like_cleanup(void *preg)
+{
+       regfree(preg);
+       free(preg);
+}
+
+static int mm_is_set_parser(const char *arg, void **bitnum)
+{
+       unsigned char *c = para_malloc(1);
+       int ret = get_attribute_bitnum_by_name(arg, c);
+
+       if (ret >= 0)
+               *bitnum = c;
+       else
+               free(c);
+       return ret;
+}
+
+static int mm_is_set_score_function(const struct osl_row *row, void *bitnum)
+{
+       unsigned char *bn = bitnum;
+       struct afs_info afsi;
+       int ret = get_afsi_of_row(row, &afsi);
+
+       if (ret < 0)
+               return 0;
+       if (afsi.attributes & (1ULL << *bn))
+               return 100;
+       return -100;
+}
+
+static int para_random(unsigned max)
+{
+       return ((max + 0.0) * (rand() / (RAND_MAX + 1.0)));
+}
+
+/* returns 1 if row matches score item, -1 otherwise */
+static int add_item_score(const void *row, struct mood_item *item, long *score,
+               long *score_arg_sum)
+{
+       int ret = 100;
+
+       *score_arg_sum += item->random_score? 100 : PARA_ABS(item->score_arg);
+       if (item->method) {
+               ret = item->method->score_function(row, item->parser_data);
+               if ((ret < 0 && !item->logical_not) || (ret >= 0 && item->logical_not))
+                       return -1; /* no match */
+       }
+       if (item->random_score)
+               *score += PARA_ABS(ret) * para_random(100);
+       else
+               *score += PARA_ABS(ret) * item->score_arg;
+       return 1;
+}
+
+static int compute_mood_score(const void *row, long *result)
+{
+       struct mood_item *item;
+       int match = 0;
+       long score_arg_sum = 0, score = 0;
+
+       if (!current_mood)
+               return -E_NO_MOOD;
+       /* reject audio file if it matches any entry in the deny list */
+       list_for_each_entry(item, &current_mood->deny_list, mood_item_node)
+               if (add_item_score(row, item, &score, &score_arg_sum) > 0)
+                       return -E_NOT_ADMISSIBLE;
+       list_for_each_entry(item, &current_mood->accept_list, mood_item_node)
+               if (add_item_score(row, item, &score, &score_arg_sum) > 0)
+                       match = 1;
+       /* reject if there is no matching entry in the accept list */
+       if (!match && !list_empty(&current_mood->accept_list))
+               return -E_NOT_ADMISSIBLE;
+       list_for_each_entry(item, &current_mood->score_list, mood_item_node) {
+               PARA_INFO_LOG("random: %d\n", para_random(100));
+               add_item_score(row, item, &score, &score_arg_sum);
+       }
+       if (score_arg_sum)
+               score /= score_arg_sum;
+       *result = score;
+       return 1;
+}
+
+static const struct mood_method mood_methods[] = {
+{
+       .parser = mm_played_rarely_parser,
+       .score_function = mm_played_rarely_score_function,
+       .name = "played_rarely"
+},
+{
+       .parser = mm_is_set_parser,
+       .score_function = mm_is_set_score_function,
+       .name = "is_set"
+},
+{
+       .parser = mm_name_like_parser,
+       .score_function = mm_name_like_score_function,
+       .cleanup = mm_name_like_cleanup,
+       .name = "name_like"
+},
+{
+       .parser = NULL
+}
+};
+
+static void cleanup_list_entry(struct mood_item *item)
+{
+       if (item->method && item->method->cleanup)
+               item->method->cleanup(item->parser_data);
+       else
+               free(item->parser_data);
+       list_del(&item->mood_item_node);
+       free(item);
+}
+
+static void destroy_mood(struct mood *m)
+{
+       struct mood_item *tmp, *item;
+
+       if (!m)
+               return;
+       list_for_each_entry_safe(item, tmp, &m->accept_list, mood_item_node)
+               cleanup_list_entry(item);
+       list_for_each_entry_safe(item, tmp, &m->deny_list, mood_item_node)
+               cleanup_list_entry(item);
+       list_for_each_entry_safe(item, tmp, &m->score_list, mood_item_node)
+               cleanup_list_entry(item);
+       free(m->name);
+       free(m);
+}
+
+static struct mood *alloc_new_mood(const char *name)
+{
+       struct mood *m = para_calloc(sizeof(struct mood));
+       m->name = para_strdup(name);
+       INIT_LIST_HEAD(&m->accept_list);
+       INIT_LIST_HEAD(&m->deny_list);
+       INIT_LIST_HEAD(&m->score_list);
+       return m;
+}
+
+/** The different types of a mood line. */
+enum mood_line_type {
+       /** Invalid. */
+       ML_INVALID,
+       /** Accept line. */
+       ML_ACCEPT,
+       /** Deny line. */
+       ML_DENY,
+       /** Score line. */
+       ML_SCORE
+};
+
+/*
+ * <accept [with score <score>] | deny [with score <score>]  | score <score>>
+ *     [if] [not] <mood_method> [options]
+ * <score> is either an integer or "random" which assigns a random score to
+ * all matching files
+ */
+
+/* TODO: Use current_mood as private_data*/
+static int parse_mood_line(char *mood_line, __a_unused void *private_data)
+{
+       char **argv;
+       char *delim = " \t";
+       unsigned num_words;
+       char **w;
+       int i, ret;
+       enum mood_line_type mlt = ML_INVALID;
+       struct mood_item *mi = NULL;
+       struct mood *m = current_mood;
+       char *buf = para_strdup(mood_line);
+
+       num_words = split_args(buf, &argv, delim);
+       ret = 1;
+       if (!num_words) /* empty line */
+               goto out;
+       w = argv;
+       if (**w == '#') /* comment */
+               goto out;
+       if (!strcmp(*w, "accept"))
+               mlt = ML_ACCEPT;
+       else if (!strcmp(*w, "deny"))
+               mlt = ML_DENY;
+       else if (!strcmp(*w, "score"))
+               mlt = ML_SCORE;
+       ret = -E_MOOD_SYNTAX;
+       if (mlt == ML_INVALID)
+               goto out;
+       mi = para_calloc(sizeof(struct mood_item));
+       if (mlt != ML_SCORE) {
+               ret = -E_MOOD_SYNTAX;
+               w++;
+               if (!*w)
+                       goto out;
+               if (!strcmp(*w, "with")) {
+                       w++;
+                       if (!*w)
+                               goto out;
+               }
+       }
+       if (mlt == ML_SCORE || !strcmp(*w, "score")) {
+               ret = -E_MOOD_SYNTAX;
+               w++;
+               if (!*w)
+                       goto out;
+               if (strcmp(*w, "random")) {
+                       mi->random_score = 0;
+                       ret = para_atol(*w, &mi->score_arg);
+                       if (ret < 0)
+                               goto out;
+               } else {
+                       mi->random_score = 1;
+                       if (!*(w + 1))
+                       goto success; /* the line "score random" is valid */
+               }
+       } else
+               mi->score_arg = 0;
+       ret = -E_MOOD_SYNTAX;
+       w++;
+       if (!*w)
+               goto out;
+       if (!strcmp(*w, "if")) {
+               ret = -E_MOOD_SYNTAX;
+               w++;
+               if (!*w)
+                       goto out;
+       }
+       if (!strcmp(*w, "not")) {
+               ret = -E_MOOD_SYNTAX;
+               w++;
+               if (!*w)
+                       goto out;
+               mi->logical_not = 1;
+       } else
+               mi->logical_not = 0;
+       for (i = 0; mood_methods[i].parser; i++) {
+               if (strcmp(*w, mood_methods[i].name))
+                       continue;
+               break;
+       }
+       ret = -E_MOOD_SYNTAX;
+       if (!mood_methods[i].parser)
+               goto out;
+       w++;
+       ret = mood_methods[i].parser(*w, &mi->parser_data);
+       if (ret < 0)
+               goto out;
+       mi->method = &mood_methods[i];
+success:
+       if (mlt == ML_ACCEPT)
+               para_list_add(&mi->mood_item_node, &m->accept_list);
+       else if (mlt == ML_DENY)
+               para_list_add(&mi->mood_item_node, &m->deny_list);
+       else
+               para_list_add(&mi->mood_item_node, &m->score_list);
+       PARA_DEBUG_LOG("%s entry added, method: %p\n", mlt == ML_ACCEPT? "accept" :
+               (mlt == ML_DENY? "deny" : "score"), mi->method);
+       ret = 1;
+out:
+       free(argv);
+       free(buf);
+       if (ret >= 0)
+               return ret;
+       if (mi) {
+               free(mi->parser_data);
+               free(mi);
+       }
+       return ret;
+}
+
+static int load_mood(const void *row)
+{
+       int ret;
+       struct mood *new_mood, *old_mood = current_mood;
+       struct osl_object objs[NUM_BLOB_COLUMNS];
+
+       ret = osl_get_object(moods_table, row, BLOBCOL_NAME, &objs[BLOBCOL_NAME]);
+       if (ret < 0)
+               return ret;
+       if (objs[BLOBCOL_NAME].size <= 1)
+               return -E_DUMMY_ROW;
+       ret = osl_open_disk_object(moods_table, row, BLOBCOL_DEF, &objs[BLOBCOL_DEF]);
+       if (ret < 0)
+               return ret;
+       new_mood = alloc_new_mood((char*)objs[BLOBCOL_NAME].data);
+       current_mood = new_mood;
+       ret = for_each_line_ro(objs[BLOBCOL_DEF].data, objs[BLOBCOL_DEF].size,
+               parse_mood_line, NULL);
+       osl_close_disk_object(&objs[BLOBCOL_DEF]);
+       if (ret < 0) {
+               PARA_ERROR_LOG("unable to load mood %s: %d\n",
+                       (char *)objs[BLOBCOL_NAME].data, ret);
+               destroy_mood(new_mood);
+               current_mood = old_mood;
+               return ret;
+       }
+       destroy_mood(old_mood);
+       current_mood = new_mood;
+       PARA_INFO_LOG("loaded mood %s\n", current_mood->name);
+       return 1;
+}
+
+/* returns -E_MOOD_LOADED on _success_ to terminate the loop */
+static int mood_loop(struct osl_row *row, __a_unused void *private_data)
+{
+       int ret = load_mood(row);
+       if (ret < 0) {
+               if (ret != -E_DUMMY_ROW)
+                       PARA_NOTICE_LOG("invalid mood (%d), trying next mood\n", ret);
+               return 1;
+       }
+       return -E_MOOD_LOADED;
+}
+
+static int load_first_available_mood(void)
+{
+       int ret = osl_rbtree_loop(moods_table, BLOBCOL_NAME, NULL,
+               mood_loop);
+       if (ret == -E_MOOD_LOADED) /* success */
+               return 1;
+       if (ret < 0)
+               return ret; /* error */
+       PARA_NOTICE_LOG("no valid mood found\n");
+       return -E_NO_MOOD;
+}
+
+#if 0
+static unsigned int_log2(uint64_t x)
+{
+       unsigned res = 0;
+
+       while (x) {
+               x /= 2;
+               res++;
+       }
+       return res;
+}
+#endif
+
+static int64_t normalized_value(int64_t x, int64_t n, int64_t sum, int64_t qd)
+{
+       if (!n || !qd)
+               return 0;
+       return 100 * (n * x - sum) / (int64_t)int_sqrt(n * qd);
+}
+
+static long compute_num_played_score(struct afs_info *afsi)
+{
+       return -normalized_value(afsi->num_played, statistics.num,
+               statistics.num_played_sum, statistics.num_played_qd);
+}
+
+static long compute_last_played_score(struct afs_info *afsi)
+{
+       return -normalized_value(afsi->last_played, statistics.num,
+               statistics.last_played_sum, statistics.last_played_qd);
+}
+
+static long compute_dynamic_score(const struct osl_row *aft_row)
+{
+       struct afs_info afsi;
+       int64_t score, nscore = 0, lscore = 0;
+       int ret;
+
+       ret = get_afsi_of_row(aft_row, &afsi);
+       if (ret < 0)
+               return -100;
+       nscore = compute_num_played_score(&afsi);
+       lscore = compute_last_played_score(&afsi);
+       score = nscore + lscore;
+       return score;
+}
+
+static int add_afs_statistics(const struct osl_row *row)
+{
+       uint64_t n, x, s;
+       struct afs_info afsi;
+       int ret;
+
+       ret = get_afsi_of_row(row, &afsi);
+       if (ret < 0)
+               return ret;
+       n = statistics.num;
+       x = afsi.last_played;
+       s = statistics.last_played_sum;
+       if (n > 0)
+               statistics.last_played_qd += (x - s / n) * (x - s / n) * n / (n + 1);
+       statistics.last_played_sum += x;
+
+       x = afsi.num_played;
+       s = statistics.num_played_sum;
+       if (n > 0)
+               statistics.num_played_qd += (x - s / n) * (x - s / n) * n / (n + 1);
+       statistics.num_played_sum += x;
+       statistics.num++;
+       return 1;
+}
+
+static int del_afs_statistics(const struct osl_row *row)
+{
+       uint64_t n, s, q, a, new_s;
+       struct afs_info afsi;
+       int ret;
+       ret = get_afsi_of_row(row, &afsi);
+       if (ret < 0)
+               return ret;
+       n = statistics.num;
+       assert(n);
+       if (n == 1) {
+               memset(&statistics, 0, sizeof(statistics));
+               return 1;
+       }
+
+       s = statistics.last_played_sum;
+       q = statistics.last_played_qd;
+       a = afsi.last_played;
+       new_s = s - a;
+       statistics.last_played_sum = new_s;
+       statistics.last_played_qd = q + s * s / n - a * a
+               - new_s * new_s / (n - 1);
+
+       s = statistics.num_played_sum;
+       q = statistics.num_played_qd;
+       a = afsi.num_played;
+       new_s = s - a;
+       statistics.num_played_sum = new_s;
+       statistics.num_played_qd = q + s * s / n - a * a
+               - new_s * new_s / (n - 1);
+
+       statistics.num--;
+       return 1;
+}
+
+/**
+ * Structure used during mood_open().
+ *
+ * At mood open time, we look at each file in the audio file table in order to
+ * determine whether it is admissible. If a file happens to be admissible, its
+ * mood score is computed by calling each relevant mood_score_function. Next,
+ * we update the afs_statistics and add a struct admissible_file_info to a
+ * temporary array.
+ *
+ * If all files have been processed that way, the final score of each
+ * admissible file is computed by adding the dynamic score (which depends on
+ * the afs_statistics) to the mood score.  Finally, all audio files in the
+ * array are added to the score table and the admissible array is freed.
+ *
+ * \sa mood_method, admissible_array.
+ */
+struct admissible_file_info
+{
+       /** The admissible audio file. */
+       void *aft_row;
+       /** Its score. */
+       long score;
+};
+
+/** The temporary array of admissible files. */
+struct admissible_array {
+       /** The size of the array */
+       unsigned size;
+       /** Pointer to the array of admissible files. */
+       struct admissible_file_info *array;
+};
+
+/**
+ * Add an entry to the array of admissible files.
+ *
+ * \param aft_row The audio file to be added.
+ * \param private_data Pointer to a struct admissible_file_info.
+ *
+ * \return Negative on errors, positive on success.
+ */
+static int add_if_admissible(struct osl_row *aft_row, void *private_data)
+{
+       int ret;
+       struct admissible_array *aa = private_data;
+       long score = 0;
+
+       score = 0;
+       ret = compute_mood_score(aft_row, &score);
+       if (ret < 0)
+               return (ret == -E_NOT_ADMISSIBLE)? 1 : ret;
+       if (statistics.num >= aa->size) {
+               aa->size *= 2;
+               aa->size += 100;
+               aa->array = para_realloc(aa->array,
+                       aa->size * sizeof(struct admissible_file_info));
+       }
+       aa->array[statistics.num].aft_row = aft_row;
+       aa->array[statistics.num].score = score;
+       ret = add_afs_statistics(aft_row);
+       if (ret < 0)
+               return ret;
+       return 1;
+}
+
+/**
+ * Compute the new quadratic deviation in case one element changes.
+ *
+ * \param n Number of elements.
+ * \param old_qd The quadratic deviation before the change.
+ * \param old_val The value that was repaced.
+ * \param new_val The replacement value.
+ * \param old_sum The sum of all elements before the update.
+ *
+ * \return The new quadratic deviation resulting from replacing old_val
+ * by new_val.
+ *
+ * Given n real numbers a_1, ..., a_n, their sum S = a_1 + ... + a_n,
+ * their quadratic deviation
+ *
+ * q = (a_1 - S/n)^2 + ... + (a_n - S/n)^2,
+ *
+ * and a real number b, the quadratic deviation q' of a_1,...a_{n-1}, b (ie.
+ * the last number a_n was replaced by b) may be computed in O(1) time in terms
+ * of n, q, a_n, b, and S as
+ *
+ *     q' = q + d * s - (2 * S + d) * d / n,
+ *
+ * where d = b - a_n, and s = b + a_n.
+ *
+ * Example: n = 3, a_1 = 3, a_2 = 5, a_3 = 7, b = 10. Then S = 15, q = 8, d = 3,
+ * s = 17, so
+ *
+ *     q + d * s - (2 * S + d) * d / n = 8 + 51 - 33 = 26,
+ *
+ * which equals q' = (3 - 6)^2 + (5 - 6)^2 + (10 - 6)^2.
+ *
+ */
+_static_inline_ int64_t update_quadratic_deviation(int64_t n, int64_t old_qd,
+               int64_t old_val, int64_t new_val, int64_t old_sum)
+{
+       int64_t delta = new_val - old_val;
+       int64_t sigma = new_val + old_val;
+       return old_qd + delta * sigma - (2 * old_sum + delta) * delta / n;
+}
+
+static int update_afs_statistics(struct afs_info *old_afsi, struct afs_info *new_afsi)
+{
+       unsigned n;
+       int ret = get_num_admissible_files(&n);
+
+       if (ret < 0)
+               return ret;
+       assert(n);
+
+       statistics.last_played_qd = update_quadratic_deviation(n,
+               statistics.last_played_qd, old_afsi->last_played,
+               new_afsi->last_played, statistics.last_played_sum);
+       statistics.last_played_sum += new_afsi->last_played - old_afsi->last_played;
+
+       statistics.num_played_qd = update_quadratic_deviation(n,
+               statistics.num_played_qd, old_afsi->num_played,
+               new_afsi->num_played, statistics.num_played_sum);
+       statistics.num_played_sum += new_afsi->num_played - old_afsi->num_played;
+       return 1;
+}
+
+static int add_to_score_table(const struct osl_row *aft_row, long mood_score)
+{
+       long score = (compute_dynamic_score(aft_row) + mood_score) / 3;
+       return score_add(aft_row, score);
+}
+
+static int delete_from_statistics_and_score_table(const struct osl_row *aft_row)
+{
+       int ret = del_afs_statistics(aft_row);
+       if (ret < 0)
+               return ret;
+       return score_delete(aft_row);
+}
+
+/**
+ * Delete one entry from the statitics and from the score table.
+ *
+ * \param aft_row The audio file which is no longer admissible.
+ *
+ * \return Positive on success, negative on errors.
+ *
+ * \sa score_delete(), mood_update_audio_file().
+ */
+int mood_delete_audio_file(const struct osl_row *aft_row)
+{
+       int ret;
+
+       ret = row_belongs_to_score_table(aft_row);
+       if (ret < 0)
+               return ret;
+       if (!ret) /* not admissible, nothing to do */
+               return 1;
+       return delete_from_statistics_and_score_table(aft_row);
+}
+
+/**
+ * Compute the new score of an audio file.
+ *
+ * \param aft_row Determines the audio file.
+ * \param old_afsi The audio file selector info before updating.
+ *
+ * The \a old_afsi argument may be \p NULL which indicates that no changes to
+ * the audio file info were made.
+ *
+ * \return Positive on success, negative on errors.
+ */
+int mood_update_audio_file(const struct osl_row *aft_row, struct afs_info *old_afsi)
+{
+       long score, percent;
+       int ret, is_admissible, was_admissible = 0;
+       struct afs_info afsi;
+
+       if (!current_mood)
+               return 1; /* nothing to do */
+       ret = row_belongs_to_score_table(aft_row);
+       if (ret < 0)
+               return ret;
+       was_admissible = ret;
+       ret = compute_mood_score(aft_row, &score);
+       is_admissible = (ret > 0);
+       if (!was_admissible && !is_admissible)
+               return 1;
+       if (was_admissible && !is_admissible)
+               return delete_from_statistics_and_score_table(aft_row);
+       if (!was_admissible && is_admissible) {
+               ret = add_afs_statistics(aft_row);
+               if (ret < 0)
+                       return ret;
+               return add_to_score_table(aft_row, score);
+       }
+       /* update score */
+       ret = get_afsi_of_row(aft_row, &afsi);
+       if (ret < 0)
+               return ret;
+       if (old_afsi) {
+               ret = update_afs_statistics(old_afsi, &afsi);
+               if (ret < 0)
+                       return ret;
+       }
+       score += compute_num_played_score(&afsi);
+       score += compute_last_played_score(&afsi);
+       score /= 3;
+       PARA_NOTICE_LOG("score: %li\n", score);
+       percent = (score + 100) / 3;
+       if (percent > 100)
+               percent = 100;
+       else if (percent < 0)
+               percent = 0;
+       PARA_NOTICE_LOG("re-inserting at %lu%%\n", percent);
+       return score_update(aft_row, percent);
+}
+
+static void log_statistics(void)
+{
+       unsigned n = statistics.num;
+
+       if (!n) {
+               PARA_NOTICE_LOG("no admissible files\n");
+               return;
+       }
+       PARA_NOTICE_LOG("last_played mean: %lli, last_played sigma: %lli\n",
+               statistics.last_played_sum / n, int_sqrt(statistics.last_played_qd / n));
+       PARA_NOTICE_LOG("num_played mean: %lli, num_played sigma: %lli\n",
+               statistics.num_played_sum / n, int_sqrt(statistics.num_played_qd / n));
+}
+
+/**
+ * Open the given mood.
+ *
+ * \param mood_name The name of the mood to open.
+ *
+ * There are two special cases: If \a mood_name is \a NULL, load the
+ * first available mood. If \a mood_name is the empty string "", load
+ * the dummy mood that accepts every audio file and uses a scoring method
+ * based only on the \a last_played information.
+ *
+ * \return Positive on success, negative on errors. Loading the dummy mood
+ * always succeeds.
+ *
+ * \sa struct admissible_file_info, struct admissible_array, struct
+ * afs_info::last_played, mood_close().
+ */
+int mood_open(char *mood_name)
+{
+       int i, ret;
+       struct admissible_array aa = {
+               .size = 0,
+               .array = NULL
+       };
+
+       if (!mood_name) {
+               ret = load_first_available_mood();
+               if (ret < 0)
+                       return ret;
+       } else if (*mood_name) {
+               struct osl_row *row;
+               struct osl_object obj = {
+                       .data = mood_name,
+                       .size = strlen(mood_name) + 1
+               };
+               ret = osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row);
+               if (ret < 0) {
+                       PARA_NOTICE_LOG("no such mood: %s\n", mood_name);
+                       return ret;
+               }
+               ret = load_mood(row);
+               if (ret < 0)
+                       return ret;
+       } else {
+               destroy_mood(current_mood);
+               current_mood = alloc_new_mood("dummy");
+       }
+       PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name);
+       PARA_INFO_LOG("%s\n", "computing statistics of admissible files");
+       ret = audio_file_loop(&aa, add_if_admissible);
+       if (ret < 0)
+               return ret;
+       log_statistics();
+       PARA_NOTICE_LOG("%d admissible files \n", statistics.num);
+       for (i = 0; i < statistics.num; i++) {
+               struct admissible_file_info *a = aa.array + i;
+               ret = add_to_score_table(a->aft_row, a->score);
+               if (ret < 0)
+                       goto out;
+       }
+       PARA_NOTICE_LOG("score add complete\n");
+       ret = 1;
+out:
+       free(aa.array);
+       return ret;
+}
+
+/**
+ * Close the current mood.
+ *
+ * Free all resources of the current mood which were allocated during
+ * mood_open().
+ */
+void mood_close(void)
+{
+       destroy_mood(current_mood);
+       current_mood = NULL;
+       memset(&statistics, 0, sizeof(statistics));
+}
+
+/**
+ * Close and re-open the current mood.
+ *
+ * This function is used if changes to the audio file table or the
+ * attribute table were made that render the current list of admissible
+ * files useless. For example, if an attribute is removed from the
+ * attribute table, this function is called.
+ *
+ * \return Positive on success, negative on errors. If no mood is currently
+ * open, the function returns success.
+ *
+ * \sa mood_open(), mood_close().
+ */
+int mood_reload(void)
+{
+       int ret;
+       char *mood_name;
+
+       if (!current_mood)
+               return 1;
+       score_shutdown(0);
+       mood_name = para_strdup(current_mood->name);
+       mood_close();
+       ret = mood_open(mood_name);
+       free(mood_name);
+       return ret;
+}
index 0eb1188..dde71c5 100644 (file)
--- a/mp3_afh.c
+++ b/mp3_afh.c
@@ -399,7 +399,7 @@ err_out:
 /*
  * Read mp3 information from audio file
  */
-static int mp3_get_file_info(char *map, size_t numbytes,
+int mp3_get_file_info(char *map, size_t numbytes,
                struct audio_format_info *afi)
 {
        int ret;
diff --git a/osl.c b/osl.c
new file mode 100644 (file)
index 0000000..1fc45bd
--- /dev/null
+++ b/osl.c
@@ -0,0 +1,2185 @@
+/*
+ * Copyright (C) 2007 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file osl.c Object storage layer functions. */
+#include "para.h"
+#include "error.h"
+#include "list.h"
+#include "osl_core.h"
+#include <dirent.h> /* readdir() */
+#include <assert.h>
+
+//#define FMT_OFF_T "%li"
+
+
+/**
+ * A wrapper for lseek(2).
+ *
+ * \param fd The filedescriptor whose offset is to be to repositioned.
+ * \param offset A value-result parameter.
+ * \param whence Usual repositioning directive.
+ *
+ * Reposition the offset of the file descriptor \a fd to the argument \a offset
+ * according to the directive \a whence. Upon successful return, \a offset
+ * contains the resulting offset location as measured in bytes from the
+ * beginning of the file.
+ *
+ * \return Positive on success. Otherwise, the function returns \p -E_LSEEK.
+ *
+ * \sa lseek(2).
+ */
+int para_lseek(int fd, off_t *offset, int whence)
+{
+       *offset = lseek(fd, *offset, whence);
+       int ret = -E_LSEEK;
+       if (*offset == -1)
+               return ret;
+       return 1;
+}
+
+/**
+ * Waraper for the write system call.
+ *
+ * \param fd The file descriptor to write to.
+ * \param buf The buffer to write.
+ * \param size The length of \a buf in bytes.
+ *
+ * This function writes out the given bufffer and retries if an interrupt
+ * occured during the write.
+ *
+ * \return On success, the number of bytes written is returned, otherwise, the
+ * function returns \p -E_WRITE.
+ *
+ * \sa write(2).
+ */
+ssize_t para_write(int fd, const void *buf, size_t size)
+{
+       ssize_t ret;
+
+       for (;;) {
+               ret = write(fd, buf, size);
+               if ((ret < 0) && (errno == EAGAIN || errno == EINTR))
+                       continue;
+               return ret >= 0? ret : -E_WRITE;
+       }
+}
+
+/**
+ * Write the whole buffer to a file descriptor.
+ *
+ * \param fd The file descriptor to write to.
+ * \param buf The buffer to write.
+ * \param size The length of \a buf in bytes.
+ *
+ * This function writes the given buffer and continues on short writes and
+ * when interrupted by a signal.
+ *
+ * \return Positive on success, negative on errors. Possible errors: any
+ * errors returned by para_write().
+ *
+ * \sa para_write().
+ */
+ssize_t para_write_all(int fd, const void *buf, size_t size)
+{
+       PARA_DEBUG_LOG("writing %zu bytes\n", size);
+       const char *b = buf;
+       while (size) {
+               ssize_t ret = para_write(fd, b, size);
+               PARA_DEBUG_LOG("ret: %d\n", ret);
+               if (ret < 0)
+                       return ret;
+               b += ret;
+               size -= ret;
+       }
+       return 1;
+}
+/**
+ * Wrapper for the open(2) system call.
+ *
+ * \param path The filename.
+ * \param flags The usual open(2) flags.
+ * \param mode Specifies the permissions to use.
+ *
+ * The mode parameter must be specified when O_CREAT is in the flags, and is ignored
+ * otherwise.
+ *
+ * \return Positive on success, negative on errors. Possible errors: \p
+ * E_EXIST, \p E_ISDIR, \p E_NOENT, \p E_OSL_PERM.
+ *
+ * \sa open(2).
+ */
+int para_open(const char *path, int flags, mode_t mode)
+{
+       PARA_DEBUG_LOG("opening %s\n", path);
+       int ret = open(path, flags, mode);
+
+       if (ret >= 0)
+               return ret;
+       switch (errno) {
+       case EEXIST:
+               ret = -E_EXIST;
+               break;
+       case EISDIR:
+               ret = -E_ISDIR;
+               break;
+       case ENOENT:
+               ret = -E_NOENT;
+               break;
+       case EPERM:
+               ret = -E_OSL_PERM;
+               break;
+       };
+       PARA_ERROR_LOG("failed to open %s: %s\n", path, strerror(errno));
+       return ret;
+}
+
+/**
+ * Open a file, write the given buffer and close the file.
+ *
+ * \param filename Full path to the file to open.
+ * \param buf The buffer to write to the file.
+ * \param size The size of \a buf.
+ *
+ * \return Positive on success, negative on errors. Possible errors include:
+ * any errors from para_open() or para_write().
+ *
+ * \sa para_open(), para_write().
+ */
+int para_write_file(const char *filename, const void *buf, size_t size)
+{
+       int ret, fd;
+
+       ret = para_open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644);
+       if (ret < 0)
+               return ret;
+       fd = ret;
+       ret = para_write_all(fd, buf, size);
+       if (ret < 0)
+               goto out;
+       ret = 1;
+out:
+       close(fd);
+       return ret;
+}
+
+static int append_file(const char *filename, char *header, size_t header_size,
+       char *data, size_t data_size, uint32_t *new_pos)
+{
+       int ret, fd;
+
+       PARA_DEBUG_LOG("appending %lu  + %ld bytes\n", header_size, data_size);
+       ret = para_open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644);
+       if (ret < 0)
+               return ret;
+       fd = ret;
+       if (header && header_size) {
+               ret = para_write_all(fd, header, header_size);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = para_write_all(fd, data, data_size);
+       if (ret < 0)
+               goto out;
+       if (new_pos) {
+               off_t offset = 0;
+               ret = para_lseek(fd, &offset, SEEK_END);
+               if (ret < 0)
+                       goto out;
+//             PARA_DEBUG_LOG("new file size: " FMT_OFF_T "\n", offset);
+               *new_pos = offset;
+       }
+       ret = 1;
+out:
+       close(fd);
+       return ret;
+}
+
+/**
+ * Map a file into memory.
+ *
+ * \param path Name of the regular file to map.
+ * \param open_mode Either \p O_RDONLY or \p O_RDWR.
+ * \param obj On success, the mapping is returned here.
+ *
+ * \return Positive on success, negative on errors. Possible errors include: \p
+ * E_FSTAT, any errors returned by para_open(), \p E_EMPTY, \p E_MMAP.
+ *
+ * \sa para_open(), mmap(2).
+ */
+int mmap_full_file(const char *path, int open_mode, struct osl_object *obj)
+{
+       int fd, ret, mmap_prot, mmap_flags;
+       struct stat file_status;
+
+       if (open_mode == O_RDONLY) {
+               mmap_prot = PROT_READ;
+               mmap_flags = MAP_PRIVATE;
+       } else {
+               mmap_prot = PROT_READ | PROT_WRITE;
+               mmap_flags = MAP_SHARED;
+       }
+       ret = para_open(path, open_mode, 0);
+       if (ret < 0)
+               return ret;
+       fd = ret;
+       ret = -E_FSTAT;
+       if (fstat(fd, &file_status) < 0)
+               goto out;
+       obj->size = file_status.st_size;
+       ret = -E_EMPTY;
+       PARA_DEBUG_LOG("%s: size %zu\n", path, obj->size);
+       if (!obj->size)
+               goto out;
+       obj->data = mmap(NULL, obj->size, mmap_prot, mmap_flags, fd, 0);
+       if (obj->data == MAP_FAILED) {
+               obj->data = NULL;
+               ret = -E_MMAP;
+               goto out;
+       }
+       ret = 1;
+out:
+       close(fd);
+       return ret;
+}
+
+/**
+ * Traverse the given directory recursively.
+ *
+ * \param dirname The directory to traverse.
+ * \param func The function to call for each entry.
+ * \param private_data Pointer to an arbitrary data structure.
+ *
+ * For each regular file  in \a dirname, the supplied function \a func is
+ * called.  The full path of the regular file and the \a private_data pointer
+ * are passed to \a func.
+ *
+ * \return On success, 1 is returned. Otherwise, this function returns a
+ * negative value which indicates the kind of the error.
+ */
+int for_each_file_in_dir(const char *dirname,
+               int (*func)(const char *, const void *), const void *private_data)
+{
+       DIR *dir = NULL;
+       struct dirent *entry;
+       /*
+        * Opening the current directory (".") and calling fchdir() to return
+        * is usually faster and more reliable than saving cwd in some buffer
+        * and calling chdir() afterwards (see man 3 getcwd).
+        */
+       int cwd_fd = open(".", O_RDONLY);
+       struct stat s;
+       int ret = -1;
+
+//     PARA_DEBUG_LOG("dirname: %s\n", dirname);
+       if (cwd_fd < 0)
+               return -E_OSL_GETCWD;
+       ret = -E_OSL_CHDIR;
+       if (chdir(dirname) < 0)
+               goto out;
+       ret = -E_OSL_OPENDIR;
+       dir = opendir(".");
+       if (!dir)
+               goto out;
+       /* scan cwd recursively */
+       while ((entry = readdir(dir))) {
+               mode_t m;
+               char *tmp;
+
+               if (!strcmp(entry->d_name, "."))
+                       continue;
+               if (!strcmp(entry->d_name, ".."))
+                       continue;
+               ret = -E_OSL_LSTAT;
+               if (lstat(entry->d_name, &s) == -1)
+                       continue;
+               m = s.st_mode;
+               if (!S_ISREG(m) && !S_ISDIR(m))
+                       continue;
+               tmp = make_message("%s/%s", dirname, entry->d_name);
+               if (!S_ISDIR(m)) {
+                       ret = func(tmp, private_data);
+                       free(tmp);
+                       if (ret < 0)
+                               goto out;
+                       continue;
+               }
+               /* directory */
+               ret = for_each_file_in_dir(tmp, func, private_data);
+               free(tmp);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = 1;
+out:
+       if (dir)
+               closedir(dir);
+       if (fchdir(cwd_fd) < 0 && ret >= 0)
+               ret = -E_OSL_CHDIR;
+       close(cwd_fd);
+       return ret;
+}
+
+int para_mkdir(const char *path, mode_t mode)
+{
+       if (!mkdir(path, mode))
+               return 1;
+       if (errno == EEXIST)
+               return -E_EXIST;
+       if (errno == ENOSPC)
+               return -E_NOSPC;
+       if (errno == ENOTDIR)
+               return -E_NOTDIR;
+       if (errno == EPERM)
+               return E_OSL_PERM;
+       return -E_MKDIR;
+}
+
+static int verify_basename(const char *name)
+{
+       if (!name)
+               return -E_BAD_BASENAME;
+       if (!*name)
+               return -E_BAD_BASENAME;
+       if (strchr(name, '/'))
+               return -E_BAD_BASENAME;
+       if (!strcmp(name, ".."))
+               return -E_BAD_BASENAME;
+       if (!strcmp(name, "."))
+               return -E_BAD_BASENAME;
+       return 1;
+}
+
+/**
+ * Compare two osl objects pointing to unsigned integers of 32 bit size.
+ *
+ * \param obj1 Pointer to the first integer.
+ * \param obj2 Pointer to the second integer.
+ *
+ * \return The values required for an osl compare function.
+ *
+ * \sa osl_compare_func, osl_hash_compare().
+ */
+int uint32_compare(const struct osl_object *obj1, const struct osl_object *obj2)
+{
+       uint32_t d1 = read_u32((const char *)obj1->data);
+       uint32_t d2 = read_u32((const char *)obj2->data);
+
+       if (d1 < d2)
+               return 1;
+       if (d1 > d2)
+               return -1;
+       return 0;
+}
+
+/**
+ * Compare two osl objects pointing to hash values.
+ *
+ * \param obj1 Pointer to the first hash object.
+ * \param obj2 Pointer to the second hash object.
+ *
+ * \return The values required for an osl compare function.
+ *
+ * \sa osl_compare_func, uint32_compare().
+ */
+int osl_hash_compare(const struct osl_object *obj1, const struct osl_object *obj2)
+{
+       return hash_compare((HASH_TYPE *)obj1->data, (HASH_TYPE *)obj2->data);
+}
+
+static char *disk_storage_dirname(const struct osl_table *t, unsigned col_num,
+               const char *ds_name)
+{
+       char *dirname, *column_name = column_filename(t, col_num);
+
+       if (!(t->desc->flags & OSL_LARGE_TABLE))
+               return column_name;
+       dirname = make_message("%s/%.2s", column_name, ds_name);
+       free(column_name);
+       return dirname;
+}
+
+static char *disk_storage_name_of_object(const struct osl_table *t,
+       const struct osl_object *obj)
+{
+       HASH_TYPE hash[HASH_SIZE];
+       hash_object(obj, hash);
+       return disk_storage_name_of_hash(t, hash);
+}
+
+static int disk_storage_name_of_row(const struct osl_table *t,
+               const struct osl_row *row, char **name)
+{
+       struct osl_object obj;
+       int ret = osl_get_object(t, row, t->disk_storage_name_column, &obj);
+
+       if (ret < 0)
+               return ret;
+       *name = disk_storage_name_of_object(t, &obj);
+       return 1;
+}
+
+static void column_name_hash(const char *col_name, HASH_TYPE *hash)
+{
+       return hash_function(col_name, strlen(col_name), hash);
+}
+
+static int init_column_descriptions(struct osl_table *t)
+{
+       int i, j, ret;
+       const struct osl_column_description *cd;
+
+       ret = -E_BAD_TABLE_DESC;
+       ret = verify_basename(t->desc->name);
+       if (ret < 0)
+               goto err;
+       ret = -E_BAD_DB_DIR;
+       if (!t->desc->dir)
+               goto err;
+       /* the size of the index header without column descriptions */
+       t->index_header_size = IDX_COLUMN_DESCRIPTIONS;
+       FOR_EACH_COLUMN(i, t->desc, cd) {
+               struct osl_column *col = t->columns + i;
+               if (cd->storage_flags & OSL_RBTREE) {
+                       if (!cd->compare_function)
+                               return -E_NO_COMPARE_FUNC;
+               }
+               if (cd->storage_type == OSL_NO_STORAGE)
+                       continue;
+               ret = -E_NO_COLUMN_NAME;
+               if (!cd->name || !cd->name[0])
+                       goto err;
+               ret = verify_basename(cd->name);
+               if (ret < 0)
+                       goto err;
+               t->index_header_size += index_column_description_size(cd->name);
+               column_name_hash(cd->name, col->name_hash);
+               ret = -E_DUPLICATE_COL_NAME;
+               for (j = i + 1; j < t->desc->num_columns; j++) {
+                       const char *name2 = get_column_description(t->desc,
+                               j)->name;
+                       if (cd->name && name2 && !strcmp(cd->name, name2))
+                               goto err;
+               }
+       }
+       return 1;
+err:
+       return ret;
+}
+
+/**
+ * Initialize a struct table from given table description.
+ *
+ * \param desc The description of the osl table.
+ * \param table_ptr Result is returned here.
+ *
+ * This function performs several sanity checks on \p desc and returns if any
+ * of these tests fail. On success, a struct \p osl_table is allocated and
+ * initialized with data derived from \p desc.
+ *
+ * \return Positive on success, negative on errors. Possible errors include: \p
+ * E_BAD_TABLE_DESC, \p E_NO_COLUMN_DESC, \p E_NO_COLUMNS, \p
+ * E_BAD_STORAGE_TYPE, \p E_BAD_STORAGE_FLAGS, \p E_BAD_STORAGE_SIZE, \p
+ * E_NO_UNIQUE_RBTREE_COLUMN, \p E_NO_RBTREE_COL.
+ *
+ * \sa struct osl_table.
+ */
+int init_table_structure(const struct osl_table_description *desc,
+               struct osl_table **table_ptr)
+{
+       const struct osl_column_description *cd;
+       struct osl_table *t = para_calloc(sizeof(*t));
+       int i, ret = -E_BAD_TABLE_DESC, have_disk_storage_name_column = 0;
+
+       PARA_INFO_LOG("creating table structure for '%s' from table "
+               "description\n", desc->name);
+       if (!desc)
+               goto err;
+       ret = -E_NO_COLUMN_DESC;
+       if (!desc->column_descriptions)
+               goto err;
+       ret = -E_NO_COLUMNS;
+       if (!desc->num_columns)
+               goto err;
+       t->columns = para_calloc(desc->num_columns * sizeof(struct osl_column));
+       t->desc = desc;
+       FOR_EACH_COLUMN(i, t->desc, cd) {
+               enum osl_storage_type st = cd->storage_type;
+               enum osl_storage_flags sf = cd->storage_flags;
+               struct osl_column *col = &t->columns[i];
+
+               ret = -E_BAD_STORAGE_TYPE;
+               if (st != OSL_MAPPED_STORAGE && st != OSL_DISK_STORAGE
+                               && st != OSL_NO_STORAGE)
+                       goto err;
+               ret = -E_BAD_STORAGE_FLAGS;
+               if (st == OSL_DISK_STORAGE && sf & OSL_RBTREE)
+                       goto err;
+               ret = -E_BAD_STORAGE_SIZE;
+               if (sf & OSL_FIXED_SIZE && !cd->data_size)
+                       goto err;
+               switch (st) {
+               case OSL_DISK_STORAGE:
+                       t->num_disk_storage_columns++;
+                       break;
+               case OSL_MAPPED_STORAGE:
+                       t->num_mapped_columns++;
+                       col->index_offset = t->index_entry_size;
+                       t->index_entry_size += 8;
+                       break;
+               case OSL_NO_STORAGE:
+                       col->volatile_num = t->num_volatile_columns;
+                       t->num_volatile_columns++;
+                       break;
+               }
+               if (sf & OSL_RBTREE) {
+                       col->rbtree_num = t->num_rbtrees;
+                       t->num_rbtrees++;
+                       if ((sf & OSL_UNIQUE) && (st == OSL_MAPPED_STORAGE)) {
+                               if (!have_disk_storage_name_column)
+                                       t->disk_storage_name_column = i;
+                               have_disk_storage_name_column = 1;
+                       }
+               }
+       }
+       ret = -E_NO_UNIQUE_RBTREE_COLUMN;
+       if (t->num_disk_storage_columns && !have_disk_storage_name_column)
+               goto err;
+       ret = -E_NO_RBTREE_COL;
+       if (!t->num_rbtrees)
+               goto err;
+       /* success */
+       PARA_INFO_LOG("OK. Index entry size: %u\n", t->index_entry_size);
+       ret = init_column_descriptions(t);
+       if (ret < 0)
+               goto err;
+       *table_ptr = t;
+       return 1;
+err:
+       free(t->columns);
+       free(t);
+       return ret;
+}
+
+/**
+ * Read the table description from index header.
+ *
+ * \param map The memory mapping of the index file.
+ * \param desc The values found in the index header are returned here.
+ *
+ * Read the index header, check for the paraslash magic string and the table version number.
+ * Read all information stored in the index header into \a desc.
+ *
+ * \return Positive on success, negative on errors.
+ *
+ * \sa struct osl_table_description, osl_create_table.
+ */
+int read_table_desc(struct osl_object *map, struct osl_table_description *desc)
+{
+       char *buf = map->data;
+       uint8_t version;
+       uint16_t header_size;
+       int ret, i;
+       unsigned offset;
+       struct osl_column_description *cd;
+
+       if (map->size < MIN_INDEX_HEADER_SIZE(1))
+               return -E_SHORT_TABLE;
+       if (strncmp(buf + IDX_PARA_MAGIC, PARA_MAGIC, strlen(PARA_MAGIC)))
+               return -E_NO_MAGIC;
+       version = read_u8(buf + IDX_VERSION);
+       if (version < MIN_TABLE_VERSION || version > MAX_TABLE_VERSION)
+               return -E_VERSION_MISMATCH;
+       desc->num_columns = read_u8(buf + IDX_TABLE_FLAGS);
+       desc->flags = read_u8(buf + IDX_TABLE_FLAGS);
+       desc->num_columns = read_u16(buf + IDX_NUM_COLUMNS);
+       PARA_DEBUG_LOG("%u columns\n", desc->num_columns);
+       if (!desc->num_columns)
+               return -E_NO_COLUMNS;
+       header_size = read_u16(buf + IDX_HEADER_SIZE);
+       if (map->size < header_size)
+               return -E_BAD_SIZE;
+       desc->column_descriptions = para_calloc(desc->num_columns
+               * sizeof(struct osl_column_description));
+       offset = IDX_COLUMN_DESCRIPTIONS;
+       FOR_EACH_COLUMN(i, desc, cd) {
+               char *null_byte;
+
+               ret = -E_SHORT_TABLE;
+               if (map->size < offset + MIN_IDX_COLUMN_DESCRIPTION_SIZE) {
+                       PARA_ERROR_LOG("map size = %u < %u = offset + min desc size\n",
+                               map->size, offset + MIN_IDX_COLUMN_DESCRIPTION_SIZE);
+                       goto err;
+               }
+               cd->storage_type = read_u16(buf + offset + IDX_CD_STORAGE_TYPE);
+               cd->storage_flags = read_u16(buf + offset +
+                       IDX_CD_STORAGE_FLAGS);
+               cd->data_size = read_u32(buf + offset + IDX_CD_DATA_SIZE);
+               null_byte = memchr(buf + offset + IDX_CD_NAME, '\0',
+                       map->size - offset - IDX_CD_NAME);
+               ret = -E_INDEX_CORRUPTION;
+               if (!null_byte)
+                       goto err;
+               cd->name = para_strdup(buf + offset + IDX_CD_NAME);
+               offset += index_column_description_size(cd->name);
+       }
+       if (offset != header_size) {
+               ret = -E_INDEX_CORRUPTION;
+               PARA_ERROR_LOG("real header size = %u != %u = stored header size\n",
+                       offset, header_size);
+               goto err;
+       }
+       return 1;
+err:
+       FOR_EACH_COLUMN(i, desc, cd)
+               free(cd->name);
+       return ret;
+}
+
+/*
+ * check whether the table description given by \p t->desc matches the on-disk
+ * table structure stored in the index of \a t.
+ */
+static int compare_table_descriptions(struct osl_table *t)
+{
+       int i, ret;
+       struct osl_table_description desc;
+       const struct osl_column_description *cd1, *cd2;
+
+       /* read the on-disk structure into desc */
+       ret = read_table_desc(&t->index_map, &desc);
+       if (ret < 0)
+               return ret;
+       ret = -E_BAD_TABLE_FLAGS;
+       if (desc.flags != t->desc->flags)
+               goto out;
+       ret = E_BAD_COLUMN_NUM;
+       if (desc.num_columns != t->desc->num_columns)
+               goto out;
+       FOR_EACH_COLUMN(i, t->desc, cd1) {
+               cd2 = get_column_description(&desc, i);
+               ret = -E_BAD_STORAGE_TYPE;
+               if (cd1->storage_type != cd2->storage_type)
+                       goto out;
+               ret = -E_BAD_STORAGE_FLAGS;
+               if (cd1->storage_flags != cd2->storage_flags) {
+                       PARA_ERROR_LOG("sf1 = %u != %u = sf2\n",
+                               cd1->storage_flags, cd2->storage_flags);
+                       goto out;
+               }
+               ret = -E_BAD_DATA_SIZE;
+               if (cd1->storage_flags & OSL_FIXED_SIZE)
+                       if (cd1->data_size != cd2->data_size)
+                               goto out;
+               ret = -E_BAD_COLUMN_NAME;
+               if (strcmp(cd1->name, cd2->name))
+                       goto out;
+       }
+       PARA_INFO_LOG("table description of '%s' matches on-disk data, good\n",
+               t->desc->name);
+       ret = 1;
+out:
+       FOR_EACH_COLUMN(i, &desc, cd1)
+               free(cd1->name);
+       free(desc.column_descriptions);
+       return ret;
+}
+
+static int create_table_index(struct osl_table *t)
+{
+       char *buf, *filename;
+       int i, ret;
+       size_t size = t->index_header_size;
+       const struct osl_column_description *cd;
+       unsigned offset;
+
+       PARA_INFO_LOG("creating %zu byte index for table %s\n", size,
+               t->desc->name);
+       buf = para_calloc(size);
+       sprintf(buf + IDX_PARA_MAGIC, "%s", PARA_MAGIC);
+       write_u8(buf + IDX_TABLE_FLAGS, t->desc->flags);
+       write_u8(buf + IDX_DIRTY_FLAG, 0);
+       write_u8(buf + IDX_VERSION, CURRENT_TABLE_VERSION);
+       write_u16(buf + IDX_NUM_COLUMNS, t->desc->num_columns);
+       write_u16(buf + IDX_HEADER_SIZE, t->index_header_size);
+       offset = IDX_COLUMN_DESCRIPTIONS;
+       FOR_EACH_COLUMN(i, t->desc, cd) {
+               write_u16(buf + offset + IDX_CD_STORAGE_TYPE,
+                       cd->storage_type);
+               write_u16(buf + offset + IDX_CD_STORAGE_FLAGS,
+                       cd->storage_flags);
+               if (cd->storage_flags & OSL_FIXED_SIZE)
+                       write_u32(buf + offset + IDX_CD_DATA_SIZE,
+                               cd->data_size);
+               strcpy(buf + offset + IDX_CD_NAME, cd->name);
+               offset += index_column_description_size(cd->name);
+       }
+       assert(offset = size);
+       filename = index_filename(t->desc);
+       ret = para_write_file(filename, buf, size);
+       free(buf);
+       free(filename);
+       return ret;
+}
+
+/**
+ * Create a new osl table.
+ *
+ * \param desc Pointer to the table description.
+ *
+ * \return Positive on success, negative on errors. Possible errors include: \p
+ * E_BAD_TABLE_DESC, \p E_BAD_DB_DIR, \p E_BAD_BASENAME, \p E_NO_COMPARE_FUNC, \p
+ * E_NO_COLUMN_NAME, \p E_DUPLICATE_COL_NAME, \p E_MKDIR, any errors returned
+ * by para_open().
+ */
+int osl_create_table(const struct osl_table_description *desc)
+{
+       const struct osl_column_description *cd;
+       char *table_dir = NULL, *filename;
+       struct osl_table *t;
+       int i, ret = init_table_structure(desc, &t);
+
+       if (ret < 0)
+               return ret;
+       PARA_INFO_LOG("creating %s\n", desc->name);
+       FOR_EACH_COLUMN(i, t->desc, cd) {
+               if (cd->storage_type == OSL_NO_STORAGE)
+                       continue;
+               if (!table_dir) {
+                       ret = para_mkdir(desc->dir, 0777);
+                       if (ret < 0 && ret != -E_EXIST)
+                               goto out;
+                       table_dir = make_message("%s/%s", desc->dir,
+                               desc->name);
+                       ret = para_mkdir(table_dir, 0777);
+                       if (ret < 0)
+                               goto out;
+               }
+               filename = column_filename(t, i);
+               PARA_INFO_LOG("filename: %s\n", filename);
+               if (cd->storage_type == OSL_MAPPED_STORAGE) {
+                       ret = para_open(filename, O_RDWR | O_CREAT | O_EXCL,
+                               0644);
+                       free(filename);
+                       if (ret < 0)
+                               goto out;
+                       close(ret);
+                       continue;
+               }
+               /* DISK STORAGE */
+               ret = para_mkdir(filename, 0777);
+               free(filename);
+               if (ret < 0)
+                       goto out;
+       }
+       if (t->num_mapped_columns) {
+               ret = create_table_index(t);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = 1;
+out:
+       free(table_dir);
+       free(t->columns);
+       free(t);
+       return ret;
+}
+
+static int table_is_dirty(struct osl_table *t)
+{
+       char *buf = (char *)t->index_map.data + IDX_DIRTY_FLAG;
+       uint8_t dirty = read_u8(buf) & 0x1;
+       return !!dirty;
+}
+
+static void mark_table_dirty(struct osl_table *t)
+{
+       char *buf = (char *)t->index_map.data + IDX_DIRTY_FLAG;
+       write_u8(buf, read_u8(buf) | 1);
+}
+
+static void mark_table_clean(struct osl_table *t)
+{
+       char *buf = (char *)t->index_map.data + IDX_DIRTY_FLAG;
+       write_u8(buf, read_u8(buf) & 0xfe);
+}
+
+/**
+ * Unmap all mapped files of an osl table.
+ *
+ * \param t Pointer to a mapped table.
+ * \param flags Options for unmapping.
+ *
+ * \return Positive on success, negative on errors. Possible errors include:
+ * E_NOT_MAPPED, E_MUNMAP.
+ *
+ * \sa map_table(), enum osl_close_flags, para_munmap().
+ */
+int unmap_table(struct osl_table *t, enum osl_close_flags flags)
+{
+       unsigned i;
+       const struct osl_column_description *cd;
+       int ret;
+
+       if (!t->num_mapped_columns) /* can this ever happen? */
+               return 1;
+       PARA_INFO_LOG("unmapping table '%s'\n", t->desc->name);
+       if (!t->index_map.data)
+               return -E_NOT_MAPPED;
+       if (flags & OSL_MARK_CLEAN)
+               mark_table_clean(t);
+       ret = para_munmap(t->index_map.data, t->index_map.size);
+       if (ret < 0)
+               return ret;
+       t->index_map.data = NULL;
+       if (!t->num_rows)
+               return 1;
+       FOR_EACH_MAPPED_COLUMN(i, t, cd) {
+               struct osl_object map = t->columns[i].data_map;
+               if (!map.data)
+                       continue;
+               ret = para_munmap(map.data, map.size);
+               if (ret < 0)
+                       return ret;
+               map.data = NULL;
+       }
+       return 1;
+}
+
+/**
+ * Map the index file and all columns of type \p OSL_MAPPED_STORAGE into memory.
+ *
+ * \param t Pointer to an initialized table structure.
+ * \param flags Mapping options.
+ *
+ * \return Negative return value on errors; on success the number of rows
+ * (including invalid rows) is returned.
+ *
+ * \sa unmap_table(), enum map_table_flags, osl_open_table(), mmap(2).
+ */
+int map_table(struct osl_table *t, enum map_table_flags flags)
+{
+       char *filename;
+       const struct osl_column_description *cd;
+       int i = 0, ret, num_rows = 0;
+
+       if (!t->num_mapped_columns)
+               return 0;
+       if (t->index_map.data)
+               return -E_ALREADY_MAPPED;
+       filename = index_filename(t->desc);
+       PARA_DEBUG_LOG("mapping table '%s' (index: %s)\n", t->desc->name, filename);
+       ret = mmap_full_file(filename, flags & MAP_TBL_FL_MAP_RDONLY?
+               O_RDONLY : O_RDWR, &t->index_map);
+       free(filename);
+       if (ret < 0)
+               return ret;
+       if (flags & MAP_TBL_FL_VERIFY_INDEX) {
+               ret = compare_table_descriptions(t);
+               if (ret < 0)
+                       goto err;
+       }
+       ret = -E_BUSY;
+       if (!(flags & MAP_TBL_FL_IGNORE_DIRTY)) {
+               if (table_is_dirty(t)) {
+                       PARA_ERROR_LOG("%s is dirty\n", t->desc->name);
+                       goto err;
+               }
+       }
+       mark_table_dirty(t);
+       num_rows = table_num_rows(t);
+       if (!num_rows)
+               return num_rows;
+       /* map data files */
+       FOR_EACH_MAPPED_COLUMN(i, t, cd) {
+               struct stat statbuf;
+               filename = column_filename(t, i);
+               ret = -E_STAT;
+               if (stat(filename, &statbuf) < 0) {
+                       free(filename);
+                       goto err;
+               }
+               if (!(S_IFREG & statbuf.st_mode)) {
+                       free(filename);
+                       goto err;
+               }
+               ret = mmap_full_file(filename, O_RDWR,
+                       &t->columns[i].data_map);
+               free(filename);
+               if (ret < 0)
+                       goto err;
+       }
+       return num_rows;
+err:   /* unmap what is already mapped */
+       for (i--; i >= 0; i--) {
+               struct osl_object map = t->columns[i].data_map;
+               para_munmap(map.data, map.size);
+               map.data = NULL;
+       }
+       para_munmap(t->index_map.data, t->index_map.size);
+       t->index_map.data = NULL;
+       return ret;
+}
+
+/**
+ * Retrieve a mapped object by row and column number.
+ *
+ * \param t Pointer to an open osl table.
+ * \param col_num Number of the mapped column containing the object to retrieve.
+ * \param row_num Number of the row containing the object to retrieve.
+ * \param obj The result is returned here.
+ *
+ * It is considered an error if \a col_num does not refer to a column
+ * of storage type \p OSL_MAPPED_STORAGE.
+ *
+ * \return Positive on success, negative on errors. Possible errors include:
+ * \p E_BAD_ID, \p E_INVALID_OBJECT.
+ *
+ * \sa osl_storage_type.
+ */
+int get_mapped_object(const struct osl_table *t, unsigned col_num,
+       uint32_t row_num, struct osl_object *obj)
+{
+       struct osl_column *col = &t->columns[col_num];
+       uint32_t offset;
+       char *header;
+       unsigned char *index_entry;
+       int ret;
+
+       if (t->num_rows <= row_num)
+               return -E_BAD_ID;
+       ret = get_index_entry(t, row_num, col_num, &index_entry);
+       if (ret < 0)
+               return ret;
+       offset = read_u32(index_entry);
+       obj->size = read_u32(index_entry + 4) - 1;
+       PARA_DEBUG_LOG("index_entry: %p\n", index_entry);
+       header = col->data_map.data + offset;
+       obj->data = header + 1;
+       if (read_u8(header) == 0xff) {
+               PARA_ERROR_LOG("col %d, size %ld, offset %ld\n", col_num,
+                       obj->size, offset);
+               return -E_INVALID_OBJECT;
+       }
+       PARA_DEBUG_LOG("mapped obj row_num: %u, col %u, size: %d\n", row_num,
+               col_num, obj->size);
+       return 1;
+}
+
+static int search_rbtree(const struct osl_object *obj,
+               const struct osl_table *t, unsigned col_num,
+               struct rb_node **result, struct rb_node ***rb_link)
+{
+       struct osl_column *col = &t->columns[col_num];
+       struct rb_node **new = &col->rbtree.rb_node, *parent = NULL;
+       const struct osl_column_description *cd =
+               get_column_description(t->desc, col_num);
+       enum osl_storage_type st = cd->storage_type;
+       while (*new) {
+               struct osl_row *this_row = get_row_pointer(*new,
+                       col->rbtree_num);
+               int ret;
+               struct osl_object this_obj;
+               parent = *new;
+               if (st == OSL_MAPPED_STORAGE) {
+                       ret = get_mapped_object(t, col_num, this_row->id,
+                               &this_obj);
+                       if (ret < 0)
+                               return ret;
+               } else
+                       this_obj = this_row->volatile_objects[col->volatile_num];
+               ret = cd->compare_function(obj, &this_obj);
+               if (!ret) {
+                       if (result)
+                               *result = get_rb_node_pointer(this_row,
+                                       col->rbtree_num);
+                       return 1;
+               }
+               if (ret < 0)
+                       new = &((*new)->rb_left);
+               else
+                       new = &((*new)->rb_right);
+       }
+       if (result)
+               *result = parent;
+       if (rb_link)
+               *rb_link = new;
+       return -E_RB_KEY_NOT_FOUND;
+}
+
+static int insert_rbtree(struct osl_table *t, unsigned col_num,
+       const struct osl_row *row, const struct osl_object *obj)
+{
+       struct rb_node *parent, **rb_link;
+       unsigned rbtree_num;
+       struct rb_node *n;
+       int ret = search_rbtree(obj, t, col_num, &parent, &rb_link);
+
+       if (ret > 0)
+               return -E_RB_KEY_EXISTS;
+       rbtree_num = t->columns[col_num].rbtree_num;
+       n = get_rb_node_pointer(row, rbtree_num);
+       rb_link_node(n, parent, rb_link);
+       rb_insert_color(n, &t->columns[col_num].rbtree);
+       return 1;
+}
+
+static void remove_rb_node(struct osl_table *t, unsigned col_num,
+               const struct osl_row *row)
+{
+       struct osl_column *col = &t->columns[col_num];
+       const struct osl_column_description *cd =
+               get_column_description(t->desc, col_num);
+       enum osl_storage_flags sf = cd->storage_flags;
+       struct rb_node *victim, *splice_out_node, *tmp;
+       if (!(sf & OSL_RBTREE))
+               return;
+       /*
+        * Which node is removed/spliced out actually depends on how many
+        * children the victim node has: If it has no children, it gets
+        * deleted. If it has one child, it gets spliced out. If it has two
+        * children, its successor (which has at most a right child) gets
+        * spliced out.
+        */
+       victim = get_rb_node_pointer(row, col->rbtree_num);
+       if (victim->rb_left && victim->rb_right)
+               splice_out_node = rb_next(victim);
+       else
+               splice_out_node = victim;
+       /* Go up to the root and decrement the size of each node in the path. */
+       for (tmp = splice_out_node; tmp; tmp = rb_parent(tmp))
+               tmp->size--;
+       rb_erase(victim, &col->rbtree);
+}
+
+static int add_row_to_rbtrees(struct osl_table *t, uint32_t id,
+               struct osl_object *volatile_objs, struct osl_row **row_ptr)
+{
+       unsigned i;
+       int ret;
+       struct osl_row *row = allocate_row(t->num_rbtrees);
+       const struct osl_column_description *cd;
+
+       PARA_DEBUG_LOG("row: %p, id: %u\n", row, id);
+       row->id = id;
+       row->volatile_objects = volatile_objs;
+       FOR_EACH_RBTREE_COLUMN(i, t, cd) {
+               if (cd->storage_type == OSL_MAPPED_STORAGE) {
+                       struct osl_object obj;
+                       ret = get_mapped_object(t, i, id, &obj);
+                       if (ret < 0)
+                               goto err;
+                       ret = insert_rbtree(t, i, row, &obj);
+               } else { /* volatile */
+                       const struct osl_object *obj
+                               = volatile_objs + t->columns[i].volatile_num;
+                       PARA_DEBUG_LOG("inserting %p\n", obj->data);
+                       ret = insert_rbtree(t, i, row, obj);
+               }
+               if (ret < 0)
+                       goto err;
+       }
+       if (row_ptr)
+               *row_ptr = row;
+       return 1;
+err: /* rollback changes, i.e. remove added entries from rbtrees */
+       while (i)
+               remove_rb_node(t, i--, row);
+       free(row);
+       return ret;
+}
+
+static void free_volatile_objects(const struct osl_table *t,
+               enum osl_close_flags flags)
+{
+       int i, j;
+       struct rb_node *n;
+       struct osl_column *rb_col;
+       const struct osl_column_description *cd;
+
+       if (!t->num_volatile_columns)
+               return;
+       /* find the first rbtree column (any will do) */
+       FOR_EACH_RBTREE_COLUMN(i, t, cd)
+               break;
+       rb_col = t->columns + i;
+       /* walk that rbtree and free all volatile objects */
+       for (n = rb_first(&rb_col->rbtree); n; n = rb_next(n)) {
+               struct osl_row *r = get_row_pointer(n, rb_col->rbtree_num);
+               if (flags & OSL_FREE_VOLATILE)
+                       for (j = 0; j < t->num_volatile_columns; j++)
+                               free(r->volatile_objects[j].data);
+               free(r->volatile_objects);
+       }
+}
+
+/**
+ * Erase all rbtree nodes and free resources.
+ *
+ * \param t Pointer to an open osl table.
+ *
+ * This function is called by osl_close_table().
+ */
+void clear_rbtrees(struct osl_table *t)
+{
+       const struct osl_column_description *cd;
+       unsigned i, rbtrees_cleared = 0;
+
+       FOR_EACH_RBTREE_COLUMN(i, t, cd) {
+               struct osl_column *col = &t->columns[i];
+               struct rb_node *n;
+               rbtrees_cleared++;
+               for (n = rb_first(&col->rbtree); n;) {
+                       struct osl_row *r;
+                       rb_erase(n, &col->rbtree);
+                       if (rbtrees_cleared == t->num_rbtrees) {
+                               r = get_row_pointer(n, col->rbtree_num);
+                               n = rb_next(n);
+                               free(r);
+                       } else
+                               n = rb_next(n);
+               }
+       }
+
+}
+
+/**
+ * Close an osl table.
+ *
+ * \param t Pointer to the table to be closed.
+ * \param flags Options for what should be cleaned up.
+ *
+ * If osl_open_table() succeeds, the resulting table pointer must later be
+ * passed to this function in order to flush all changes to the filesystem and
+ * to free the resources that were allocated by osl_open_table().
+ *
+ * \return Positive on success, negative on errors. Possible errors: \p E_BAD_TABLE,
+ * errors returned by unmap_table().
+ *
+ * \sa osl_open_table(), unmap_table().
+ */
+int osl_close_table(struct osl_table *t, enum osl_close_flags flags)
+{
+       int ret;
+
+       if (!t)
+               return -E_BAD_TABLE;
+       free_volatile_objects(t, flags);
+       clear_rbtrees(t);
+       ret = unmap_table(t, flags);
+       if (ret < 0)
+               PARA_ERROR_LOG("unmap_table failed: %d\n", ret);
+       free(t->columns);
+       free(t);
+       return ret;
+}
+
+/**
+ * Find out whether the given row number corresponds to an invalid row.
+ *
+ * \param t Pointer to the osl table.
+ * \param row_num The number of the row in question.
+ *
+ * By definition, a row is considered invalid if all its index entries
+ * are invalid.
+ *
+ * \return Positive if \a row_num corresponds to an invalid row,
+ * zero if it corresponds to a valid row, negative on errors.
+ */
+int row_is_invalid(struct osl_table *t, uint32_t row_num)
+{
+       unsigned char *index_entry;
+       int i, ret = get_index_entry_start(t, row_num, &index_entry);
+
+       if (ret < 0)
+               return ret;
+       for (i = 0; i < t->index_entry_size; i++) {
+               if (index_entry[i] != 0xff)
+                       return 0;
+       }
+       PARA_INFO_LOG("row %d is invalid\n", row_num);
+       return 1;
+}
+
+/**
+ * Invalidate a row of an osl table.
+ *
+ * \param t Pointer to an open osl table.
+ * \param row_num Number of the row to mark as invalid.
+ *
+ * This function marks each mapped object in the index entry of \a row as
+ * invalid.
+ *
+ * \return Positive on success, negative on errors.
+ */
+int mark_row_invalid(struct osl_table *t, uint32_t row_num)
+{
+       unsigned char *index_entry;
+       int i, ret = get_index_entry_start(t, row_num, &index_entry);
+
+       PARA_INFO_LOG("marking row %d as invalid\n", row_num);
+       if (ret < 0)
+               return ret;
+       for (i = 0; i < t->index_entry_size; i++)
+               index_entry[i] = 0xff;
+       return 1;
+}
+
+/**
+ * Initialize all rbtrees and compute number of invalid rows.
+ *
+ * \param t The table containing the rbtrees to be initialized.
+ *
+ * \return Positive on success, negative on errors.
+ */
+int init_rbtrees(struct osl_table *t)
+{
+       int i, ret;
+       const struct osl_column_description *cd;
+
+       /* create rbtrees */
+       FOR_EACH_RBTREE_COLUMN(i, t, cd)
+               t->columns[i].rbtree = RB_ROOT;
+       /* add valid rows to rbtrees */
+       t->num_invalid_rows = 0;
+       for (i = 0; i < t->num_rows; i++) {
+               ret = row_is_invalid(t, i);
+               if (ret < 0)
+                       return ret;
+               if (ret) {
+                       t->num_invalid_rows++;
+                       continue;
+               }
+               ret = add_row_to_rbtrees(t, i, NULL, NULL);
+               if (ret < 0)
+                       return ret;
+       }
+       return 1;
+}
+
+/**
+ * Open an osl table.
+ *
+ * Each osl table must be opened before its data can be accessed.
+ *
+ * \param table_desc Describes the table to be opened.
+ * \param result Contains a pointer to the open table on success.
+ *
+ * The table description given by \a desc should coincide with the
+ * description used at creation time.
+ *
+ * \return Positive on success, negative on errors. Possible errors include:
+ * errors returned by init_table_structure(), \p E_NOENT, \p E_STAT, \p \p
+ * E_NOTDIR, \p E_BAD_TABLE_DESC, \p E_BAD_DB_DIR, \p E_NO_COMPARE_FUNC, \p
+ * E_NO_COLUMN_NAME, errors returned by init_rbtrees().
+ */
+int osl_open_table(const struct osl_table_description *table_desc,
+               struct osl_table **result)
+{
+       int i, ret;
+       struct osl_table *t;
+       const struct osl_column_description *cd;
+
+       PARA_INFO_LOG("opening table %s\n", table_desc->name);
+       ret = init_table_structure(table_desc, &t);
+       if (ret < 0)
+               return ret;
+       FOR_EACH_DISK_STORAGE_COLUMN(i, t, cd) {
+               /* check if directory exists */
+               char *dirname = column_filename(t, i);
+               struct stat statbuf;
+               ret = stat(dirname, &statbuf);
+               free(dirname);
+               if (ret < 0) {
+                       if (errno == ENOENT)
+                               ret = -E_NOENT;
+                       else
+                               ret = -E_STAT;
+                       goto err;
+               }
+               ret = -E_NOTDIR;
+               if (!S_ISDIR(statbuf.st_mode))
+                       goto err;
+       }
+       ret = map_table(t, MAP_TBL_FL_VERIFY_INDEX);
+       if (ret < 0)
+               goto err;
+       t->num_rows = ret;
+       PARA_DEBUG_LOG("num rows: %d\n", t->num_rows);
+       ret = init_rbtrees(t);
+       if (ret < 0) {
+               osl_close_table(t, OSL_MARK_CLEAN); /* ignore further errors */
+               return ret;
+       }
+       *result = t;
+       return 1;
+err:
+       free(t->columns);
+       free(t);
+       return ret;
+}
+
+static int create_disk_storage_object_dir(const struct osl_table *t,
+               unsigned col_num, const char *ds_name)
+{
+       char *dirname;
+       int ret;
+
+       if (!(t->desc->flags & OSL_LARGE_TABLE))
+               return 1;
+       dirname = disk_storage_dirname(t, col_num, ds_name);
+       ret = para_mkdir(dirname, 0777);
+       free(dirname);
+       if (ret < 0 && ret != -E_EXIST)
+               return ret;
+       return 1;
+}
+
+static int write_disk_storage_file(const struct osl_table *t, unsigned col_num,
+       const struct osl_object *obj, const char *ds_name)
+{
+       int ret;
+       char *filename;
+
+       ret = create_disk_storage_object_dir(t, col_num, ds_name);
+       if (ret < 0)
+               return ret;
+       filename = disk_storage_path(t, col_num, ds_name);
+       ret = para_write_file(filename, obj->data, obj->size);
+       free(filename);
+       return ret;
+}
+
+static int append_map_file(const struct osl_table *t, unsigned col_num,
+       const struct osl_object *obj, uint32_t *new_size)
+{
+       char *filename = column_filename(t, col_num);
+       int ret;
+       char header = 0; /* zero means valid object */
+
+       PARA_DEBUG_LOG("appending %ld + 1 byte\n", obj->size);
+       ret = append_file(filename, &header, 1, obj->data, obj->size,
+               new_size);
+       free(filename);
+       return ret;
+}
+
+static int append_index_entry(const struct osl_table *t, char *new_index_entry)
+{
+       char *filename;
+       int ret;
+
+       if (!t->num_mapped_columns)
+               return 1;
+       filename = index_filename(t->desc);
+       PARA_DEBUG_LOG("appending %ld bytes\n", t->index_entry_size);
+       ret = append_file(filename, NULL, 0, new_index_entry,
+               t->index_entry_size, NULL);
+       free(filename);
+       return ret;
+}
+
+/**
+ * A wrapper for truncate(2)
+ *
+ * \param path Name of the regular file to truncate
+ * \param size Number of bytes to \b shave \b off
+ *
+ * Truncate the regular file named by \a path by \a size bytes.
+ *
+ * \return Positive on success, negative on errors. Possible errors include: \p
+ * E_STAT, \p E_BAD_SIZE, \p E_TRUNC.
+ *
+ * \sa truncate(2)
+ */
+int para_truncate(const char *path, off_t size)
+{
+       int ret;
+       struct stat statbuf;
+
+       ret = -E_STAT;
+       if (stat(path, &statbuf) < 0)
+               goto out;
+       ret = -E_BAD_SIZE;
+       if (statbuf.st_size < size)
+               goto out;
+       ret = -E_TRUNC;
+       if (truncate(path, statbuf.st_size - size) < 0)
+               goto out;
+       ret = 1;
+out:
+       return ret;
+}
+
+static int truncate_mapped_file(const struct osl_table *t, unsigned col_num,
+               off_t size)
+{
+       char *filename = column_filename(t, col_num);
+       int ret = para_truncate(filename, size);
+       free(filename);
+       return ret;
+}
+
+static int delete_disk_storage_file(const struct osl_table *t, unsigned col_num,
+               const char *ds_name)
+{
+       char *dirname, *filename = disk_storage_path(t, col_num, ds_name);
+       int ret = unlink(filename);
+
+       PARA_INFO_LOG("deleted %s\n", filename);
+       free(filename);
+       if (ret < 0) {
+               if (errno == ENOENT)
+                       return -E_NOENT;
+               return -E_UNLINK;
+       }
+       if (!(t->desc->flags & OSL_LARGE_TABLE))
+               return 1;
+       dirname = disk_storage_dirname(t, col_num, ds_name);
+       rmdir(dirname);
+       free(dirname);
+       return 1;
+}
+
+/**
+ * Add a new row to an osl table and retrieve this row.
+ *
+ * \param t Pointer to an open osl table.
+ * \param objects Array of objects to be added.
+ * \param row Result pointer.
+ *
+ * The \a objects parameter must point to an array containing one object per
+ * column.  The order of the objects in the array is given by the table
+ * description of \a table. Several sanity checks are performed during object
+ * insertion and the function returns without modifying the table if any of
+ * these tests fail.  In fact, it is atomic in the sense that it either
+ * succeeds or leaves the table unchanged (i.e. either all or none of the
+ * objects are added to the table).
+ *
+ * It is considered an error if an object is added to a column with associated
+ * rbtree if this object is equal to an object already contained in that column
+ * (i.e. the compare function for the column's rbtree returns zero).
+ *
+ * Possible errors include: \p E_RB_KEY_EXISTS, \p E_BAD_DATA_SIZE.
+ *
+ * \return Positive on success, negative on errors.
+ *
+ * \sa struct osl_table_description, osl_compare_func, osl_add_row().
+ */
+int osl_add_and_get_row(struct osl_table *t, struct osl_object *objects,
+               struct osl_row **row)
+{
+       int i, ret;
+       char *ds_name = NULL;
+       struct rb_node **rb_parents = NULL, ***rb_links = NULL;
+       char *new_index_entry = NULL;
+       struct osl_object *volatile_objs = NULL;
+       const struct osl_column_description *cd;
+
+       if (!t)
+               return -E_BAD_TABLE;
+       rb_parents = para_malloc(t->num_rbtrees * sizeof(struct rn_node*));
+       rb_links = para_malloc(t->num_rbtrees * sizeof(struct rn_node**));
+       if (t->num_mapped_columns)
+               new_index_entry = para_malloc(t->index_entry_size);
+       /* pass 1: sanity checks */
+       PARA_DEBUG_LOG("sanity tests: %p:%p\n", objects[0].data,
+               objects[1].data);
+       FOR_EACH_COLUMN(i, t->desc, cd) {
+               enum osl_storage_type st = cd->storage_type;
+               enum osl_storage_flags sf = cd->storage_flags;
+
+//             ret = -E_NULL_OBJECT;
+//             if (!objects[i])
+//                     goto out;
+               if (st == OSL_DISK_STORAGE)
+                       continue;
+               if (sf & OSL_RBTREE) {
+                       unsigned rbtree_num = t->columns[i].rbtree_num;
+                       ret = -E_RB_KEY_EXISTS;
+                       PARA_DEBUG_LOG("checking whether %p exists\n",
+                               objects[i].data);
+                       if (search_rbtree(objects + i, t, i,
+                                       &rb_parents[rbtree_num],
+                                       &rb_links[rbtree_num]) > 0)
+                               goto out;
+               }
+               if (sf & OSL_FIXED_SIZE) {
+                       PARA_DEBUG_LOG("fixed size. need: %d, have: %d\n",
+                               objects[i].size, cd->data_size);
+                       ret = -E_BAD_DATA_SIZE;
+                       if (objects[i].size != cd->data_size)
+                               goto out;
+               }
+       }
+       if (t->num_disk_storage_columns)
+               ds_name = disk_storage_name_of_object(t,
+                       &objects[t->disk_storage_name_column]);
+       ret = unmap_table(t, OSL_MARK_CLEAN);
+       if (ret < 0)
+               goto out;
+       PARA_DEBUG_LOG("sanity tests passed%s\n", "");
+       /* pass 2: create data files, append map data */
+       FOR_EACH_COLUMN(i, t->desc, cd) {
+               enum osl_storage_type st = cd->storage_type;
+               if (st == OSL_NO_STORAGE)
+                       continue;
+               if (st == OSL_MAPPED_STORAGE) {
+                       uint32_t new_size;
+                       struct osl_column *col = &t->columns[i];
+                       PARA_DEBUG_LOG("appending object of size %ld\n",
+                               objects[i].size);
+                       ret = append_map_file(t, i, objects + i, &new_size);
+                       if (ret < 0)
+                               goto rollback;
+                       update_index_entry(new_index_entry, col, new_size,
+                               objects[i].size);
+                       continue;
+               }
+               /* DISK_STORAGE */
+               ret = write_disk_storage_file(t, i, objects + i, ds_name);
+               if (ret < 0)
+                       goto rollback;
+       }
+       ret = append_index_entry(t, new_index_entry);
+       if (ret < 0)
+               goto rollback;
+       ret = map_table(t, MAP_TBL_FL_VERIFY_INDEX);
+       if (ret < 0) { /* truncate index and rollback changes */
+               char *filename = index_filename(t->desc);
+               para_truncate(filename, t->index_entry_size);
+               free(filename);
+               goto rollback;
+       }
+       /* pass 3: add entry to rbtrees */
+       if (t->num_volatile_columns) {
+               volatile_objs = para_calloc(t->num_volatile_columns
+                       * sizeof(struct osl_object));
+               FOR_EACH_VOLATILE_COLUMN(i, t, cd)
+                       volatile_objs[t->columns[i].volatile_num] = objects[i];
+       }
+       t->num_rows++;
+       PARA_DEBUG_LOG("adding new entry as row #%d\n", t->num_rows - 1);
+       ret = add_row_to_rbtrees(t, t->num_rows - 1, volatile_objs, row);
+       if (ret < 0)
+               goto out;
+       PARA_DEBUG_LOG("added new entry as row #%d\n", t->num_rows - 1);
+       ret = 1;
+       goto out;
+rollback: /* rollback all changes made, ignore further errors */
+       for (i--; i >= 0; i--) {
+               cd = get_column_description(t->desc, i);
+               enum osl_storage_type st = cd->storage_type;
+               if (st == OSL_NO_STORAGE)
+                       continue;
+
+               if (st == OSL_MAPPED_STORAGE)
+                       truncate_mapped_file(t, i, objects[i].size);
+               else /* disk storage */
+                       delete_disk_storage_file(t, i, ds_name);
+       }
+       /* ignore error and return previous error value */
+       map_table(t, MAP_TBL_FL_VERIFY_INDEX);
+out:
+       free(new_index_entry);
+       free(ds_name);
+       free(rb_parents);
+       free(rb_links);
+       return ret;
+}
+
+/**
+ * Add a new row to an osl table.
+ *
+ * \param t Same meaning as osl_add_and_get_row().
+ * \param objects Same meaning as osl_add_and_get_row().
+ *
+ * \return The return value of the underlying call to osl_add_and_get_row().
+ *
+ * This is equivalent to osl_add_and_get_row(t, objects, NULL).
+ */
+int osl_add_row(struct osl_table *t, struct osl_object *objects)
+{
+       return osl_add_and_get_row(t, objects, NULL);
+}
+
+/**
+ * Retrieve an object identified by row and column
+ *
+ * \param t Pointer to an open osl table.
+ * \param r Pointer to the row.
+ * \param col_num The column number.
+ * \param object The result pointer.
+ *
+ * The column determined by \a col_num must be of type \p OSL_MAPPED_STORAGE
+ * or \p OSL_NO_STORAGE, i.e. no disk storage objects may be retrieved by this
+ * function.
+ *
+ * \return Positive if object was found, negative on errors. Possible errors
+ * include: \p E_BAD_TABLE, \p E_BAD_STORAGE_TYPE.
+ *
+ * \sa osl_storage_type, osl_open_disk_object().
+ */
+int osl_get_object(const struct osl_table *t, const struct osl_row *r,
+       unsigned col_num, struct osl_object *object)
+{
+       const struct osl_column_description *cd;
+
+       if (!t)
+               return -E_BAD_TABLE;
+       cd = get_column_description(t->desc, col_num);
+       /* col must not be disk storage */
+       if (cd->storage_type == OSL_DISK_STORAGE)
+               return -E_BAD_STORAGE_TYPE;
+       if (cd->storage_type == OSL_MAPPED_STORAGE)
+               return get_mapped_object(t, col_num, r->id, object);
+       /* volatile */
+       *object = r->volatile_objects[t->columns[col_num].volatile_num];
+       return 1;
+}
+
+static int mark_mapped_object_invalid(const struct osl_table *t, uint32_t id,
+               unsigned col_num)
+{
+       struct osl_object obj;
+       char *p;
+       int ret = get_mapped_object(t, col_num, id, &obj);
+
+       if (ret < 0)
+               return ret;
+       p = obj.data;
+       p--;
+       *p = 0xff;
+       return 1;
+}
+
+/**
+ * Delete a row from an osl table.
+ *
+ * \param t Pointer to an open osl table.
+ * \param row Pointer to the row to delete.
+ *
+ * This removes all disk storage objects, removes all rbtree nodes,  and frees
+ * all volatile objects belonging to the given row. For mapped columns, the
+ * data is merely marked invalid and may be pruned from time to time by
+ * para_fsck.
+ *
+ * \return Positive on success, negative on errors. Possible errors include:
+ * \p E_BAD_TABLE, errors returned by osl_get_object().
+ */
+int osl_del_row(struct osl_table *t, struct osl_row *row)
+{
+       struct osl_row *r = row;
+       int i, ret;
+       const struct osl_column_description *cd;
+
+       if (!t)
+               return -E_BAD_TABLE;
+       PARA_INFO_LOG("deleting row %p\n", row);
+
+       if (t->num_disk_storage_columns) {
+               char *ds_name;
+               ret = disk_storage_name_of_row(t, r, &ds_name);
+               if (ret < 0)
+                       goto out;
+               FOR_EACH_DISK_STORAGE_COLUMN(i, t, cd)
+                       delete_disk_storage_file(t, i, ds_name);
+               free(ds_name);
+       }
+       FOR_EACH_COLUMN(i, t->desc, cd) {
+               struct osl_column *col = t->columns + i;
+               enum osl_storage_type st = cd->storage_type;
+               remove_rb_node(t, i, r);
+               if (st == OSL_MAPPED_STORAGE) {
+                       mark_mapped_object_invalid(t, r->id, i);
+                       continue;
+               }
+               if (st == OSL_NO_STORAGE)
+                       free(r->volatile_objects[col->volatile_num].data);
+       }
+       if (t->num_mapped_columns) {
+               ret = mark_row_invalid(t, r->id);
+               if (ret < 0)
+                       goto out;
+               t->num_invalid_rows++;
+       } else
+               t->num_rows--;
+       ret = 1;
+out:
+       free(r->volatile_objects);
+       free(r);
+       return ret;
+}
+
+/* test if column has an rbtree */
+static int check_rbtree_col(const struct osl_table *t, unsigned col_num,
+               struct osl_column **col)
+{
+       if (!t)
+               return -E_BAD_TABLE;
+       if (!(get_column_description(t->desc, col_num)->storage_flags & OSL_RBTREE))
+               return -E_BAD_STORAGE_FLAGS;
+       *col = t->columns + col_num;
+       return 1;
+}
+
+/**
+ * Get the row that contains the given object.
+ *
+ * \param t Pointer to an open osl table.
+ * \param col_num The number of the column to be searched.
+ * \param obj The object to be looked up.
+ * \param result Points to the row containing \a obj.
+ *
+ * Lookup \a obj in \a t and return the row containing \a obj. The column
+ * specified by \a col_num must have an associated rbtree.
+ *
+ * \return Positive on success, negative on errors. If an error occured, \a
+ * result is set to \p NULL. Possible errors include: \p E_BAD_TABLE, \p
+ * E_BAD_STORAGE_FLAGS, errors returned by get_mapped_object(), \p
+ * E_RB_KEY_NOT_FOUND.
+ *
+ * \sa osl_storage_flags
+ */
+int osl_get_row(const struct osl_table *t, unsigned col_num,
+               const struct osl_object *obj, struct osl_row **result)
+{
+       int ret;
+       struct rb_node *node;
+       struct osl_row *row;
+       struct osl_column *col;
+
+       *result = NULL;
+       ret = check_rbtree_col(t, col_num, &col);
+       if (ret < 0)
+               return ret;
+       ret = search_rbtree(obj, t, col_num, &node, NULL);
+       if (ret < 0)
+               return ret;
+       row = get_row_pointer(node, t->columns[col_num].rbtree_num);
+       *result = row;
+       return 1;
+}
+
+static int rbtree_loop(struct osl_column *col,  void *private_data,
+               osl_rbtree_loop_func *func)
+{
+       struct rb_node *n;
+
+       for (n = rb_first(&col->rbtree); n; n = rb_next(n)) {
+               struct osl_row *r = get_row_pointer(n, col->rbtree_num);
+               int ret = func(r, private_data);
+               if (ret < 0)
+                       return ret;
+       }
+       return 1;
+}
+
+static int rbtree_loop_reverse(struct osl_column *col,  void *private_data,
+               osl_rbtree_loop_func *func)
+{
+       struct rb_node *n;
+
+       for (n = rb_last(&col->rbtree); n; n = rb_prev(n)) {
+               struct osl_row *r = get_row_pointer(n, col->rbtree_num);
+               int ret = func(r, private_data);
+               if (ret < 0)
+                       return ret;
+       }
+       return 1;
+}
+
+/**
+ * Loop over all nodes in an rbtree.
+ *
+ * \param t Pointer to an open osl table.
+ * \param col_num The column to use for iterating over the elements.
+ * \param private_data Pointer that gets passed to \a func.
+ * \param func The function to be called for each node in the rbtree.
+ *
+ * This function does an in-order walk of the rbtree associated with \a
+ * col_num. It is an error if the \p OSL_RBTREE flag is not set for this
+ * column. For each node in the rbtree, the given function \a func is called
+ * with two \p void* pointers as arguments: The first argument points to the
+ * row that contains the object corresponding to the rbtree node currently
+ * traversed, and the \a private_data pointer is passed to \a func as the
+ * second argument. The loop terminates either if \a func returns a negative
+ * value, or if all nodes of the tree have been visited.
+ *
+ *
+ * \return Positive on success, negative on errors. If the termination of the
+ * loop was caused by \a func returning a negative value, this value is
+ * returned.
+ *
+ * \sa osl_storage_flags, osl_rbtree_loop_reverse(), osl_compare_func.
+ */
+int osl_rbtree_loop(const struct osl_table *t, unsigned col_num,
+       void *private_data, osl_rbtree_loop_func *func)
+{
+       struct osl_column *col;
+
+       int ret = check_rbtree_col(t, col_num, &col);
+       if (ret < 0)
+               return ret;
+       return rbtree_loop(col, private_data, func);
+}
+
+/**
+ * Loop over all nodes in an rbtree in reverse order.
+ *
+ * \param t Identical meaning as in \p osl_rbtree_loop().
+ * \param col_num Identical meaning as in \p osl_rbtree_loop().
+ * \param private_data Identical meaning as in \p osl_rbtree_loop().
+ * \param func Identical meaning as in \p osl_rbtree_loop().
+ *
+ * This function is identical to \p osl_rbtree_loop(), the only difference
+ * is that the tree is walked in reverse order.
+ *
+ * \return The same return value as \p osl_rbtree_loop().
+ *
+ * \sa osl_rbtree_loop().
+ */
+int osl_rbtree_loop_reverse(const struct osl_table *t, unsigned col_num,
+       void *private_data, osl_rbtree_loop_func *func)
+{
+       struct osl_column *col;
+
+       int ret = check_rbtree_col(t, col_num, &col);
+       if (ret < 0)
+               return ret;
+       return rbtree_loop_reverse(col, private_data, func);
+}
+
+/* TODO: Rollback changes on errors */
+static int rename_disk_storage_objects(struct osl_table *t,
+               struct osl_object *old_obj, struct osl_object *new_obj)
+{
+       int i, ret;
+       const struct osl_column_description *cd;
+       char *old_ds_name, *new_ds_name;
+
+       if (!t->num_disk_storage_columns)
+               return 1; /* nothing to do */
+       if (old_obj->size == new_obj->size && !memcmp(new_obj->data,
+                       old_obj->data, new_obj->size))
+               return 1; /* object did not change */
+       old_ds_name = disk_storage_name_of_object(t, old_obj);
+       new_ds_name = disk_storage_name_of_object(t, new_obj);
+       FOR_EACH_DISK_STORAGE_COLUMN(i, t, cd) {
+               char *old_filename, *new_filename;
+               ret = create_disk_storage_object_dir(t, i, new_ds_name);
+               if (ret < 0)
+                       goto out;
+               old_filename = disk_storage_path(t, i, old_ds_name);
+               new_filename = disk_storage_path(t, i, new_ds_name);
+               ret = para_rename(old_filename, new_filename);
+               free(old_filename);
+               free(new_filename);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = 1;
+out:
+       free(old_ds_name);
+       free(new_ds_name);
+       return ret;
+
+}
+
+/**
+ * Change an object in an osl table.
+ *
+ * \param t Pointer to an open osl table.
+ * \param r Pointer to the row containing the object to be updated.
+ * \param col_num Number of the column containing the object to be updated.
+ * \param obj Pointer to the replacement object.
+ *
+ * This function  gets rid of all references to the old object. This includes
+ * removal of the rbtree node in case there is an rbtree associated with \a
+ * col_num. It then inserts \a obj into the table and the rbtree if neccessary.
+ *
+ * If the \p OSL_RBTREE flag is set for \a col_num, you \b MUST call this
+ * function in order to change the contents of an object, even for volatile or
+ * mapped columns of constant size (which may be updated directly if \p
+ * OSL_RBTREE is not set).  Otherwise the rbtree might become corrupted.
+ *
+ * \return Positive on success, negative on errors. Possible errors include: \p
+ * E_BAD_TABLE, \p E_RB_KEY_EXISTS, \p E_BAD_SIZE, \p E_NOENT, \p E_UNLINK,
+ * errors returned by para_write_file(), \p E_MKDIR.
+ */
+int osl_update_object(struct osl_table *t, const struct osl_row *r,
+               unsigned col_num, struct osl_object *obj)
+{
+       struct osl_column *col;
+       const struct osl_column_description *cd;
+       int ret;
+
+       if (!t)
+               return -E_BAD_TABLE;
+       col = &t->columns[col_num];
+       cd = get_column_description(t->desc, col_num);
+       if (cd->storage_flags & OSL_RBTREE) {
+               if (search_rbtree(obj, t, col_num, NULL, NULL) > 0)
+                       return -E_RB_KEY_EXISTS;
+       }
+       if (cd->storage_flags & OSL_FIXED_SIZE) {
+               if (obj->size != cd->data_size)
+                       return -E_BAD_SIZE;
+       }
+       remove_rb_node(t, col_num, r);
+       if (cd->storage_type == OSL_NO_STORAGE) { /* TODO: If fixed size, reuse object? */
+               free(r->volatile_objects[col->volatile_num].data);
+               r->volatile_objects[col->volatile_num] = *obj;
+       } else if (cd->storage_type == OSL_DISK_STORAGE) {
+               char *ds_name;
+               ret = disk_storage_name_of_row(t, r, &ds_name);
+               if (ret < 0)
+                       return ret;
+               ret = delete_disk_storage_file(t, col_num, ds_name);
+               if (ret < 0 && ret != -E_NOENT) {
+                       free(ds_name);
+                       return ret;
+               }
+               ret = write_disk_storage_file(t, col_num, obj, ds_name);
+               free(ds_name);
+               if (ret < 0)
+                       return ret;
+       } else { /* mapped storage */
+               struct osl_object old_obj;
+               ret = get_mapped_object(t, col_num, r->id, &old_obj);
+               if (ret < 0)
+                       return ret;
+               /*
+                * If the updated column is the disk storage name column, the
+                * disk storage name changes, so we have to rename all disk
+                * storage objects accordingly.
+                */
+               if (col_num == t->disk_storage_name_column) {
+                       ret = rename_disk_storage_objects(t, &old_obj, obj);
+                       if (ret < 0)
+                               return ret;
+               }
+               if (cd->storage_flags & OSL_FIXED_SIZE)
+                       memcpy(old_obj.data, obj->data, cd->data_size);
+               else { /* TODO: if the size doesn't change, use old space */
+                       uint32_t new_data_map_size;
+                       unsigned char *index_entry;
+                       ret = get_index_entry_start(t, r->id, &index_entry);
+                       if (ret < 0)
+                               return ret;
+                       ret = mark_mapped_object_invalid(t, r->id, col_num);
+                       if (ret < 0)
+                               return ret;
+                       ret = append_map_file(t, col_num, obj,
+                               &new_data_map_size);
+                       if (ret < 0)
+                               return ret;
+                       update_index_entry(index_entry, col, new_data_map_size,
+                               obj->size);
+               }
+       }
+       if (cd->storage_flags & OSL_RBTREE) {
+               ret = insert_rbtree(t, col_num, r, obj);
+               if (ret < 0)
+                       return ret;
+       }
+       return 1;
+}
+
+/**
+ * Retrieve an object of type \p OSL_DISK_STORAGE by row and column.
+ *
+ * \param t Pointer to an open osl table.
+ * \param r Pointer to the row containing the object.
+ * \param col_num The column number.
+ * \param obj Points to the result upon successful return.
+ *
+ * For columns of type \p OSL_DISK_STORAGE, this function must be used to
+ * retrieve one of its containing objects. Afterwards, osl_close_disk_object()
+ * must be called in order to deallocate the resources.
+ *
+ * \return Positive on success, negative on errors. Possible errors include:
+ * \p E_BAD_TABLE, \p E_BAD_STORAGE_TYPE, errors returned by osl_get_object().
+ *
+ * \sa osl_get_object(), osl_storage_type, osl_close_disk_object().
+ */
+int osl_open_disk_object(const struct osl_table *t, const struct osl_row *r,
+               unsigned col_num, struct osl_object *obj)
+{
+       const struct osl_column_description *cd;
+       char *ds_name, *filename;
+       int ret;
+
+       if (!t)
+               return -E_BAD_TABLE;
+       cd = get_column_description(t->desc, col_num);
+       if (cd->storage_type != OSL_DISK_STORAGE)
+               return -E_BAD_STORAGE_TYPE;
+
+       ret = disk_storage_name_of_row(t, r, &ds_name);
+       if (ret < 0)
+               return ret;
+       filename = disk_storage_path(t, col_num, ds_name);
+       free(ds_name);
+       PARA_DEBUG_LOG("filename: %s\n", filename);
+       ret = mmap_full_file(filename, O_RDONLY, obj);
+       free(filename);
+       return ret;
+}
+
+/**
+ * Free resources that were allocated during osl_open_disk_object().
+ *
+ * \param obj Pointer to the object previously returned by open_disk_object().
+ *
+ * \return The return value of the underlying call to para_munmap().
+ *
+ * \sa para_munmap().
+ */
+int osl_close_disk_object(struct osl_object *obj)
+{
+       return para_munmap(obj->data, obj->size);
+}
+
+/**
+ * Get the number of rows of the given table.
+ *
+ * \param t Pointer to an open osl table.
+ * \param num_rows Result is returned here.
+ *
+ * The number of rows returned via \a num_rows excluding any invalid rows.
+ *
+ * \return Positive on success, \p -E_BAD_TABLE if \a t is \p NULL.
+ */
+int osl_get_num_rows(const struct osl_table *t, unsigned *num_rows)
+{
+       if (!t)
+               return -E_BAD_TABLE;
+       assert(t->num_rows >= t->num_invalid_rows);
+       *num_rows = t->num_rows - t->num_invalid_rows;
+       return 1;
+}
+
+/**
+ * Get the rank of a row.
+ *
+ * \param t An open osl table.
+ * \param r The row to get the rank of.
+ * \param col_num The number of an rbtree column.
+ * \param rank Result pointer.
+ *
+ * The rank is, by definition, the position of the row in the linear order
+ * determined by an inorder tree walk of the rbtree associated with column
+ * number \a col_num of \a table.
+ *
+ * \return Positive on success, negative on errors.
+ *
+ * \sa osl_get_nth_row().
+ */
+int osl_get_rank(const struct osl_table *t, struct osl_row *r,
+               unsigned col_num, unsigned *rank)
+{
+       struct osl_object obj;
+       struct osl_column *col;
+       struct rb_node *node;
+       int ret = check_rbtree_col(t, col_num, &col);
+
+       if (ret < 0)
+               return ret;
+       ret = osl_get_object(t, r, col_num, &obj);
+       if (ret < 0)
+               return ret;
+       ret = search_rbtree(&obj, t, col_num, &node, NULL);
+       if (ret < 0)
+               return ret;
+       ret = rb_rank(node, rank);
+       if (ret < 0)
+               return -E_BAD_ROW;
+       return 1;
+}
+
+/**
+ * Get the row with n-th greatest value.
+ *
+ * \param t Pointer to an open osl table.
+ * \param col_num The column number.
+ * \param n The rank of the desired row.
+ * \param result Row is returned here.
+ *
+ * Retrieve the n-th order statistic with respect to the compare function
+ * of the rbtree column \a col_num. In other words, get that row with
+ * \a n th greatest value in column \a col_num. It's an error if
+ * \a col_num is not a rbtree column, or if \a n is larger than the
+ * number of rows in the table.
+ *
+ * \return Positive on success, negative on errors. Possible errors:
+ * \p E_BAD_TABLE, \p E_BAD_STORAGE_FLAGS, \p E_RB_KEY_NOT_FOUND.
+ *
+ * \sa osl_storage_flags, osl_compare_func, osl_get_row(),
+ * osl_rbtree_last_row(), osl_rbtree_first_row(), osl_get_rank().
+ */
+int osl_get_nth_row(const struct osl_table *t, unsigned col_num,
+               unsigned n, struct osl_row **result)
+{
+       struct osl_column *col;
+       struct rb_node *node;
+       int ret = check_rbtree_col(t, col_num, &col);
+
+       if (ret < 0)
+               return ret;
+       node = rb_nth(col->rbtree.rb_node, n);
+       if (!node)
+               return -E_RB_KEY_NOT_FOUND;
+       *result = get_row_pointer(node, col->rbtree_num);
+       return 1;
+}
+
+/**
+ * Get the row corresponding to the smallest rbtree node of a column.
+ *
+ * \param t An open rbtree table.
+ * \param col_num The number of the rbtree column.
+ * \param result A pointer to the first row is returned here.
+ *
+ * The rbtree node of the smallest object (with respect to the corresponding
+ * compare function) is selected and the row containing this object is
+ * returned. It is an error if \a col_num refers to a column without an
+ * associated rbtree.
+ *
+ * \return Positive on success, negative on errors.
+ *
+ * \sa osl_get_nth_row(), osl_rbtree_last_row().
+ */
+int osl_rbtree_first_row(const struct osl_table *t, unsigned col_num,
+               struct osl_row **result)
+{
+       return osl_get_nth_row(t, col_num, 1, result);
+}
+
+/**
+ * Get the row corresponding to the greatest rbtree node of a column.
+ *
+ * \param t The same meaning as in \p osl_rbtree_first_row().
+ * \param col_num The same meaning as in \p osl_rbtree_first_row().
+ * \param result The same meaning as in \p osl_rbtree_first_row().
+ *
+ * This function works just like osl_rbtree_first_row(), the only difference
+ * is that the row containing the greatest rather than the smallest object is
+ * returned.
+ *
+ * \return Positive on success, negative on errors.
+ *
+ * \sa osl_get_nth_row(), osl_rbtree_first_row().
+ */
+int osl_rbtree_last_row(const struct osl_table *t, unsigned col_num,
+               struct osl_row **result)
+{
+       unsigned num_rows;
+       int ret = osl_get_num_rows(t, &num_rows);
+
+       if (ret < 0)
+               return ret;
+       return osl_get_nth_row(t, col_num, num_rows, result);
+}
diff --git a/osl.h b/osl.h
new file mode 100644 (file)
index 0000000..50570ee
--- /dev/null
+++ b/osl.h
@@ -0,0 +1,206 @@
+#include <sys/mman.h>
+/*
+ * Copyright (C) 2007 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file osl.h User interface for the object storage layer. */
+
+/** decribes an object of the object storage layer (osl) */
+struct osl_object {
+       /** Pointer to the data of the object. */
+       void *data;
+       /** The object's size. */
+       off_t size;
+};
+
+/** Flags that change the internal handling of osl tables. */
+enum osl_table_flags {
+       /** This table will have many rows. */
+       OSL_LARGE_TABLE = 1
+};
+
+/** The three different types of storage for an osl column */
+enum osl_storage_type {
+       /**
+        * All data for this column is stored in one file which gets mmapped by
+        * osl_open_table(). This is suitable for columns that do not hold much
+        * data.
+        */
+       OSL_MAPPED_STORAGE,
+       /**
+        * Each entry is stored on disk and is loaded on demand by
+        * open_disk_object(). This is the preferable storage type for large
+        * objects that need not be in memory all the time.
+        */
+        OSL_DISK_STORAGE,
+       /**
+        * Objects for columns of this type are volatile: They are only stored
+        * in memory and are discarded once the table gets closed.
+        */
+       OSL_NO_STORAGE
+};
+
+/**
+ * Additional per-column flags
+ */
+enum osl_storage_flags {
+       /**
+        * Build an rbtree for this column. This is only possible if the
+        * storage type of the column is either \a OSL_MAPPED_STORAGE or \a
+        * OSL_NO_STORAGE. In order to lookup objects in the table by using \a
+        * osl_get_row(), the lookup column must have an associated rbtree.
+        *
+        * \sa osl_storage_type, osl_get_row()
+        */
+       OSL_RBTREE = 1,
+       /** The data for this column will have constant size. */
+       OSL_FIXED_SIZE = 2,
+       /** All values of this column will be different. */
+       OSL_UNIQUE = 4
+};
+
+struct osl_table;
+struct osl_row;
+
+/**
+ * In order to build up an rbtree a compare function for the objects must be
+ * specified. Such a function always takes pointers to the two objects to be
+ * compared. It must return -1, zero, or 1, if the first argument is considered
+ * to  be  respectively less than, equal to, or greater than the second. If two
+ * members compare as equal, their order in the sorted array is undefined.
+ */
+typedef int osl_compare_func(const struct osl_object *obj1,
+       const struct osl_object *obj2);
+typedef int osl_rbtree_loop_func(struct osl_row *row, void *data);
+
+osl_compare_func osl_hash_compare, uint32_compare;
+
+/**
+ * Describes one column of a osl table.
+ */
+struct osl_column_description {
+       /** One of zje tree possible types of storage */
+       enum osl_storage_type storage_type;
+       /** Specifies further properties of the column */
+       enum osl_storage_flags storage_flags;
+       /**
+        * The column name determines the name of the directory where all data
+        * for this column will be stored. Its hash is stored in the table
+        * header. This field is ignored if the storage type is \a NO_STORAGE
+        */
+       char *name;
+       /**
+        * For columns with an associated rbtree, this must point to a function
+        * that compares the values of two objects, either a builtin function
+        * or a function defined by the application may be supplied.  This
+        * field is ignored if the column does not have an associated rbtree.
+        *
+        * \sa osl_storage_flags, osl_compare_func
+        */
+       osl_compare_func *compare_function;
+       /**
+        * If the \a OSL_FIXED_SIZE flag is set for this column, this value
+        * determines the fixed size of all objects of this column. It is
+        * ignored, if \a OSL_FIXED_SIZE is not set.
+        */
+       uint32_t data_size;
+};
+
+/**
+ * Describes one osl table.
+ */
+struct osl_table_description {
+       /** The directory which contains all files of this table. */
+       const char *dir;
+       /**
+        * The table name. A subdirectory of \a dir called \a name is created
+        * at table creation time. It must be a valid name for a subdirectory.
+        * In particular, no slashes are allowed for \a name.
+        */
+       const char *name;
+       /** The number of columns of this table. */
+       uint16_t num_columns;
+       /** Further table-wide information. */
+       enum osl_table_flags flags;
+       /** The array desribing the individual columns of the table. */
+       struct osl_column_description *column_descriptions;
+};
+
+/** Flags to be passed to \a osl_close_table(). */
+enum osl_close_flags {
+       /**
+        * The table header contains a "dirty" flag which specifies whether
+        * the table is currently open by another process. This flag specifies
+        * that the dirty flag should be cleared before closing the table.
+        */
+       OSL_MARK_CLEAN = 1,
+       /**
+        * If the table contains columns of type \a OSL_NO_STORAGE and this
+        * flag is passed to osl_close_table(), free(3) is called for each
+        * object of each column of type \a OSL_NO_STORAGE.
+        */
+       OSL_FREE_VOLATILE = 2
+};
+
+
+
+int osl_create_table(const struct osl_table_description *desc);
+int osl_open_table(const struct osl_table_description *desc,
+       struct osl_table **result);
+int osl_close_table(struct osl_table *t, enum osl_close_flags flags);
+int osl_get_row(const struct osl_table *t, unsigned col_num,
+       const struct osl_object *obj, struct osl_row **result);
+int osl_get_object(const struct osl_table *t, const struct osl_row *row,
+       unsigned col_num, struct osl_object *object);
+int osl_open_disk_object(const struct osl_table *t,
+       const struct osl_row *r, unsigned col_num, struct osl_object *obj);
+int osl_close_disk_object(struct osl_object *obj);
+int osl_add_and_get_row(struct osl_table *t, struct osl_object *objects,
+       struct osl_row **row);
+int osl_add_row(struct osl_table *t, struct osl_object *objects);
+int osl_del_row(struct osl_table *t, struct osl_row *row);
+int osl_rbtree_loop(const struct osl_table *t, unsigned col_num,
+       void *private_data, osl_rbtree_loop_func *func);
+int osl_rbtree_loop_reverse(const struct osl_table *t, unsigned col_num,
+       void *private_data, osl_rbtree_loop_func *func);
+int osl_update_object(struct osl_table *t, const struct osl_row *r,
+       unsigned col_num, struct osl_object *obj);
+int osl_get_num_rows(const struct osl_table *t, unsigned *num_rows);
+int osl_rbtree_first_row(const struct osl_table *t, unsigned col_num,
+       struct osl_row **result);
+int osl_rbtree_last_row(const struct osl_table *t, unsigned col_num,
+       struct osl_row **result);
+int osl_get_nth_row(const struct osl_table *t, unsigned col_num,
+       unsigned n, struct osl_row **result);
+int osl_get_rank(const struct osl_table *t, struct osl_row *r,
+       unsigned col_num, unsigned *rank);
+
+int for_each_file_in_dir(const char *dirname,
+       int (*func)(const char *, const void *), const void *private_data);
+int para_open(const char *pathname, int flags, mode_t mode);
+int mmap_full_file(const char *filename, int open_mode, struct osl_object *obj);
+ssize_t para_write_all(int fd, const void *buf, size_t size);
+int para_lseek(int fd, off_t *offset, int whence);
+int para_write_file(const char *filename, const void *buf, size_t size);
+int para_mkdir(const char *path, mode_t mode);
+
+/**
+ * A wrapper for munmap(2).
+ *
+ * \param start The start address of the memory mapping.
+ * \param length The size of the mapping.
+ *
+ * \return Positive on success, \p -E_MUNMAP on errors.
+ *
+ * \sa munmap(2), mmap_full_file().
+ */
+_static_inline_ int para_munmap(void *start, size_t length)
+{
+       if (munmap(start, length) >= 0)
+               return 1;
+       PARA_ERROR_LOG("munmap (%p/%zu) failed: %s\n", start, length,
+               strerror(errno));
+       return -E_MUNMAP;
+}
diff --git a/osl_core.h b/osl_core.h
new file mode 100644 (file)
index 0000000..27b808a
--- /dev/null
@@ -0,0 +1,586 @@
+/*
+ * Copyright (C) 2007 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file osl_core.h Object storage layer details, not visible to users. */
+
+#include "rbtree.h"
+#include "osl.h"
+#include "string.h"
+#include "hash.h"
+
+/** Internal representation of a column of an osl table. */
+struct osl_column {
+       /** The memory mapping of this comumn (only used for mapped columns). */
+       struct osl_object data_map;
+       /** The root of the rbtree (only used for columns with rbtrees). */
+       struct rb_root rbtree;
+       /** The index in the array of rb nodes (only used for columns with rbtrees). */
+       unsigned rbtree_num;
+       /** Index for volatile_objects of struct osl_row. */
+       unsigned volatile_num;
+       /**
+        * Starting point of the data for this column within the index
+        * (only used for mapped columns).
+        */
+       uint16_t index_offset;
+       /**
+        * The hash value of the name of this column.
+        *
+        * This is only used for mapped and disk storage columns).
+        */
+       HASH_TYPE name_hash[HASH_SIZE];
+};
+
+/** Internal representation of an osl table */
+struct osl_table {
+       /** Pointer to the table description */
+       const struct osl_table_description *desc;
+       /** The size of the index header of this table. */
+       uint16_t index_header_size;
+       /** Contains the mapping of the table's index file */
+       struct osl_object index_map;
+       /** Total number of rows, including invalid rows. */
+       unsigned num_rows;
+       /** Keeps track of the number of invalid rows in the table. */
+       uint32_t num_invalid_rows;
+       /** Number of columns of type \p OSL_MAPPED_STORAGE. */
+       unsigned num_mapped_columns;
+       /** Number of columns of type \p OSL_NO_STORAGE. */
+       unsigned num_volatile_columns;
+       /** Number of columns of type \p OSL_DISK_STORAGE. */
+       unsigned num_disk_storage_columns;
+       /** Number of columns with associated rbtree. */
+       unsigned num_rbtrees;
+       /**
+        * The number of the column that determines the name of the disk
+        * storage objcts.
+        */
+       unsigned disk_storage_name_column;
+       /** The number of bytes of an index entry of a row. */
+       unsigned index_entry_size;
+       /** Pointer to the internal representation of the columns. */
+       struct osl_column *columns;
+};
+
+/** Internal representation of a row of an osl table */
+struct osl_row {
+       /** The row number only present if there is at least one mapped column. */
+       off_t id;
+       /** Array of size \a num_volatile_columns. */
+       struct osl_object *volatile_objects;
+};
+
+int read_table_desc(struct osl_object *map, struct osl_table_description *desc);
+int init_table_structure(const struct osl_table_description *desc,
+               struct osl_table **table_ptr);
+int row_is_invalid(struct osl_table *t, uint32_t id);
+int get_mapped_object(const struct osl_table *t, unsigned col_num,
+       uint32_t id, struct osl_object *obj);
+int para_truncate(const char *filename, off_t size);
+int unmap_table(struct osl_table *t, enum osl_close_flags flags);
+int init_rbtrees(struct osl_table *t);
+
+/**
+ * Flags to be specified for map_table().
+ *
+ * \sa map_table().
+ */
+enum map_table_flags {
+       /**
+        * Check whether the entries in the table index match the entries in
+        * the table desctiption.
+        */
+       MAP_TBL_FL_VERIFY_INDEX = 1,
+       /** Do not complain even if the dirty flag is set. */
+       MAP_TBL_FL_IGNORE_DIRTY = 2,
+       /** Use read-only mappings. */
+       MAP_TBL_FL_MAP_RDONLY = 4
+};
+
+int map_table(struct osl_table *t, enum map_table_flags flags);
+void clear_rbtrees(struct osl_table *t);
+int mark_row_invalid(struct osl_table *t, uint32_t row_num);
+
+
+/**
+ * Get the description of a column by column number
+ *
+ * \param d Pointer to the table description.
+ * \param col_num The number of the column to get the desctiption for.
+ *
+ * \return The table description.
+ *
+ * \sa struct osl_column_description.
+ */
+_static_inline_ struct osl_column_description *get_column_description(
+               const struct osl_table_description *d, unsigned col_num)
+{
+       return &d->column_descriptions[col_num];
+}
+
+/**
+ * Format of the header of the index file of an osl table.
+ *
+ * Bytes 16-31 are currently unused.
+ *
+ * \sa enum index_column_desc_offsets, HASH_SIZE, osl_table_flags.
+ */
+enum index_header_offsets {
+       /** Bytes 0-8: PARASLASH. */
+       IDX_PARA_MAGIC = 0,
+       /** Byte 9: Dirty flag (nonzero if table is mapped). */
+       IDX_DIRTY_FLAG = 9,
+       /** Byte 10: osl table version number. */
+       IDX_VERSION = 10,
+       /** Byte 11: Table flags.*/
+       IDX_TABLE_FLAGS = 11,
+       /** Byte 12,13: Number of columns. */
+       IDX_NUM_COLUMNS,
+       /** Byte 14,15 Size of the index header. */
+       IDX_HEADER_SIZE = 14,
+       /** Column descriptions start here. */
+       IDX_COLUMN_DESCRIPTIONS = 32
+};
+
+/**
+ * Format of the column description in the index header.
+ *
+ * \sa index_header_offsets.
+ */
+enum index_column_desc_offsets {
+       /** Bytes 0,1: Storage_type. */
+       IDX_CD_STORAGE_TYPE = 0,
+       /** Bytes 2,3: Storage_flags. */
+       IDX_CD_STORAGE_FLAGS = 2,
+       /** Bytes 4 - 7: Data_size (only used for fixed size columns). */
+       IDX_CD_DATA_SIZE = 4,
+       /** Bytes 8 - ... Name of the column. */
+       IDX_CD_NAME = 8,
+};
+
+/** Magic string contained in the header of the index file of each osl table. */
+#define PARA_MAGIC "PARASLASH"
+
+/**
+ * The minimal number of bytes for a column in the index header.
+ *
+ * The column name starts at byte IDX_CD_NAME and must at least contain one
+ * character, plus the terminating NULL byte.
+  */
+#define MIN_IDX_COLUMN_DESCRIPTION_SIZE (IDX_CD_NAME + 2)
+
+/**
+ * Get the number of bytes used for a column in the index header.
+ *
+ * \param name The name of the column.
+ *
+ * The amount of space used for a column in the index header of a table depends
+ * on the (length of the) name of the column.
+ *
+ * \return The total number of bytes needed to store one column of this name.
+ */
+_static_inline_ size_t index_column_description_size(const char *name)
+{
+       return MIN_IDX_COLUMN_DESCRIPTION_SIZE + strlen(name) - 1;
+}
+
+#define CURRENT_TABLE_VERSION 1
+#define MIN_TABLE_VERSION 1
+#define MAX_TABLE_VERSION 1
+/** An index header must be at least that many bytes long. */
+#define MIN_INDEX_HEADER_SIZE(num_cols) (MIN_IDX_COLUMN_DESCRIPTION_SIZE \
+       * num_cols + IDX_COLUMN_DESCRIPTIONS)
+
+/**
+ * Get the number of rows from the size of the memory mapping.
+ *
+ * \param t Pointer to an open table.
+ *
+ * \return The number of rows, including invalid rows.
+ */
+_static_inline_ unsigned table_num_rows(const struct osl_table *t)
+{
+       return (t->index_map.size - t->index_header_size)
+               / t->index_entry_size;
+}
+
+/**
+ * Get the path of the index file from a table description.
+ *
+ * \param d The table description.
+ *
+ * \return The full path of the index file. Result must be freed by
+ * the caller.
+ */
+_static_inline_ char *index_filename(const struct osl_table_description *d)
+{
+       return make_message("%s/%s/index", d->dir, d->name);
+}
+
+/**
+ * Get the path of storage of a column.
+ *
+ * \param t Pointer to an initialized table.
+ * \param col_num The number of the column to get the path for.
+ *
+ * \return The full path of the mapped data file (mapped storage) or the
+ * data directory (disk storage). Result must be freed by the caller.
+ *
+ * \sa osl_storage_type.
+ */
+_static_inline_ char *column_filename(const struct osl_table *t, unsigned col_num)
+{
+       char asc[2 * HASH_SIZE + 1];
+       hash_to_asc(t->columns[col_num].name_hash, asc);
+       return make_message("%s/%s/%s", t->desc->dir, t->desc->name, asc);
+}
+
+/**
+ * Get the start of an index entry
+ *
+ * \param t Pointer to a table which has been mapped.
+ * \param row_num The number of the row whose index entry should be retrieved.
+ * \param index_entry Result is returned here.
+ *
+ * \return Positive on success, \p -E_INDEX_CORRUPTION otherwise.
+ *
+ * \sa get_index_entry().
+ */
+_static_inline_ int get_index_entry_start(const struct osl_table *t, uint32_t row_num,
+               unsigned char **index_entry)
+{
+       uint32_t index_offset;
+       index_offset = t->index_header_size + t->index_entry_size * row_num;
+       if (index_offset + 8 > t->index_map.size) {
+               *index_entry = NULL;
+               return -E_INDEX_CORRUPTION;
+       }
+       *index_entry = (unsigned char *)(t->index_map.data) + index_offset;
+       return 1;
+}
+
+/**
+ * Get the index entry of a row/column pair.
+ *
+ * \param t Pointer to a table which has been mapped.
+ * \param row_num The number of the row whose index entry should be retrieved.
+ * \param col_num The number of the column whose index entry should be retrieved.
+ * \param index_entry Result pointer.
+ *
+ * \return Positive on success, \p -E_INDEX_CORRUPTION otherwise.
+ *
+ * \sa get_index_entry_start().
+ */
+_static_inline_ int get_index_entry(const struct osl_table *t, uint32_t row_num,
+               uint32_t col_num, unsigned char **index_entry)
+{
+       int ret = get_index_entry_start(t, row_num, index_entry);
+       if (ret < 0)
+               return ret;
+       *index_entry += t->columns[col_num].index_offset;
+       return ret;
+}
+
+/**
+ * Change an index entry of a column after object was added.
+ *
+ * \param index_entry_start This determines the row.
+ * \param col Pointer to the column.
+ * \param map_size The new size of the data file.
+ * \param object_size The size of the object just appended to the data file.
+ *
+ * This is called right after an object was appended to the data file for a
+ * mapped column.
+ *
+ * \sa get_index_entry_start().
+ */
+_static_inline_ void update_index_entry(char *index_entry_start, struct osl_column *col,
+               uint32_t map_size, uint32_t object_size)
+{
+       write_u32(index_entry_start + col->index_offset, map_size - object_size - 1);
+       write_u32(index_entry_start + col->index_offset + 4, object_size + 1);
+}
+
+/**
+ * Get the full path of a disk storage object
+ *
+ * \param t Pointer to an initialized table.
+ * \param col_num The number of the column the disk storage object belongs to.
+ * \param ds_name The disk storage name of the object.
+ *
+ * \return Pointer to the full path which must be freed by the caller.
+ *
+ * \sa column_filename(), disk_storage_name_of_row().
+ */
+_static_inline_ char *disk_storage_path(const struct osl_table *t,
+               unsigned col_num, const char *ds_name)
+{
+       char *dirname = column_filename(t, col_num);
+       char *filename = make_message("%s/%s", dirname, ds_name);
+       free(dirname);
+       return filename;
+}
+
+/**
+ * Get the column description of the next column of a given type.
+ *
+ * \param type the desired storage type.
+ * \param col_num the column to start the search.
+ * \param t the osl table.
+ * \param cd result is returned here.
+ *
+ * \return On success, \a cd contains the column description of the next column
+ * of type \a type, and the number of that column is returned.  Otherwise, \a
+ * cd is set to \p NULL and the function returns t->num_columns + 1.
+ *
+ * \sa FOR_EACH_COLUMN_OF_TYPE, FOR_EACH_MAPPED_COLUMN, FOR_EACH_RBTREE_COLUMN,
+ * FOR_EACH_DISK_STORAGE_COLUMN, FOR_EACH_VOLATILE_COLUMN, osl_storage_type.
+ */
+_static_inline_ int next_column_of_type(enum osl_storage_type type, int col_num,
+               const struct osl_table *t,
+               const struct osl_column_description **cd)
+{
+       *cd = NULL;
+       while (col_num < t->desc->num_columns) {
+               *cd = get_column_description(t->desc, col_num);
+               if ((*cd)->storage_type == type)
+                       break;
+               col_num++;
+       }
+       return col_num;
+}
+
+/**
+ * Find the next column with an associated rbtree.
+ *
+ * \param col_num The column to start the search.
+ * \param t The osl table.
+ * \param cd Result is returned here.
+
+ * \return On success, \a cd contains the column description of the next column
+ * with associated rbtree, and the number of that column is returned.
+ * Otherwise, \a cd is set to \p NULL and the function returns t->num_columns +
+ * 1.
+ *
+ * \sa FOR_EACH_COLUMN_OF_TYPE, FOR_EACH_MAPPED_COLUMN, FOR_EACH_RBTREE_COLUMN,
+ * FOR_EACH_DISK_STORAGE_COLUMN, FOR_EACH_VOLATILE_COLUMN, osl_storage_flags.
+ */
+_static_inline_ int next_rbtree_column(int col_num, const struct osl_table *t,
+               const struct osl_column_description **cd)
+{
+       *cd = NULL;
+       while (col_num < t->desc->num_columns) {
+               *cd = get_column_description(t->desc, col_num);
+               if ((*cd)->storage_flags & OSL_RBTREE)
+                       break;
+               col_num++;
+       }
+       return col_num;
+}
+
+
+/* quite some dirty hacks */
+
+/** Round up size of struct osl_row to multiple of 8 */
+#define RB_NODES_OFFSET ((sizeof(struct osl_row) + 7) / 8 * 8)
+
+/**
+ * Allocate a new osl row.
+ *
+ * \param num_rbtrees The number of rbtrees for this row.
+ *
+ * \return A pointer to a zeroed-out area suitable for holding an osl row
+ * with \a num_rbtrees rbtree columns.
+ */
+_static_inline_ struct osl_row *allocate_row(unsigned num_rbtrees)
+{
+       size_t s = RB_NODES_OFFSET + num_rbtrees * sizeof(struct rb_node);
+       return para_calloc(s);
+}
+
+/**
+ * Compute the pointer to a rbtree node embedded in a osl row.
+ *
+ * \param row The row to extract the rbtree pointer from.
+ * \param rbtree_num The number of the rbtree node to extract.
+ *
+ * \return A pointer to the \a rbtree_num th node contained in \a row.
+ */
+_static_inline_ struct rb_node *get_rb_node_pointer(const struct osl_row *row, uint32_t rbtree_num)
+{
+       return ((struct rb_node *)(((char *)row) + RB_NODES_OFFSET)) + rbtree_num;
+}
+
+/**
+ * Get a pointer to the struct containing the given rbtree node.
+ *
+ * \param node Pointer to the rbtree node.
+ * \param rbtree_num Number of \a node in the array of rbtree nodes.
+ *
+ * \return A pointer to the row containing \a node.
+ */
+_static_inline_ struct osl_row *get_row_pointer(const struct rb_node *node,
+               unsigned rbtree_num)
+{
+       node -= rbtree_num;
+       return (struct osl_row *)(((char *)node) - RB_NODES_OFFSET);
+}
+
+/**
+ * Compute a cryptographic hash of an osl object.
+ *
+ * \param obj the Object to compute the hash value from.
+ * \param hash Result is returned here.
+ */
+static inline void hash_object(const struct osl_object *obj, HASH_TYPE *hash)
+{
+       return hash_function(obj->data, obj->size, hash);
+}
+
+/**
+ * Get the relative path of an object, given the hash value.
+ *
+ * \param t Pointer to an initialized osl table.
+ * \param hash An arbitrary hash value.
+ *
+ * This function is typically called with \a hash being the hash of the object
+ * stored in the disk storage name column of a row.  If the OSL_LARGE_TABLE
+ * flag is set, the first two hex digits get separated with a slash from the
+ * remaining digits.
+ *
+ * \return The relative path for all disk storage objects corresponding to \a
+ * hash.
+ *
+ * \sa struct osl_table:disk_storage_name_column.
+ */
+static inline char *disk_storage_name_of_hash(const struct osl_table *t, HASH_TYPE *hash)
+{
+       char asc[2 * HASH_SIZE + 2];
+
+       hash_to_asc(hash, asc);
+       if (t->desc->flags & OSL_LARGE_TABLE)
+               return make_message("%.2s/%s", asc, asc + 2);
+       return para_strdup(asc);
+}
+
+/**
+ * A wrapper for rename(2).
+ *
+ * \param old_path The source path.
+ * \param new_path The destination path.
+ *
+ * \return positive in success, \p -E_RENAME on errors.
+ *
+ * \sa rename(2).
+ */
+static inline int para_rename(const char *old_path, const char *new_path)
+{
+       if (rename(old_path, new_path) < 0)
+               return -E_RENAME;
+       return 1;
+}
+
+/**
+ * Iterate over each column of an initialized table.
+ *
+ * \param col A pointer to a struct osl_column.
+ * \param desc Pointer to the table description.
+ * \param cd Pointer to struct osl_column_description.
+ *
+ * On each iteration, \a col will point to the next column of the table and \a
+ * cd will point to the column description of this column.
+ *
+ * \sa struct osl_column FOR_EACH_RBTREE_COLUMN, FOR_EACH_COLUMN_OF_TYPE,
+ * FOR_EACH_MAPPED_COLUMN, FOR_EACH_DISK_STORAGE_COLUMN,
+ * FOR_EACH_VOLATILE_COLUMN.
+ */
+#define FOR_EACH_COLUMN(col, desc, cd) \
+       for (col = 0; col < (desc)->num_columns && \
+               (cd = get_column_description(desc, col)); col++)
+
+/**
+ * Iterate over each column with associated rbtree.
+ *
+ * \param col Same meaning as in FOR_EACH_COLUMN().
+ * \param table Same meaning as in FOR_EACH_COLUMN().
+ * \param cd Same meaning as in FOR_EACH_COLUMN().
+ *
+ * \sa osl_storage_flags::OSL_RBTREE, FOR_EACH_COLUMN, FOR_EACH_COLUMN_OF_TYPE,
+ * FOR_EACH_MAPPED_COLUMN, FOR_EACH_DISK_STORAGE_COLUMN,
+ * FOR_EACH_VOLATILE_COLUMN.
+ */
+#define FOR_EACH_RBTREE_COLUMN(col, table, cd) \
+       for (col = next_rbtree_column(0, table, &cd); \
+       col < table->desc->num_columns; \
+       col = next_rbtree_column(++col, table, &cd))
+
+/**
+ * Iterate over each column of given storage type.
+ *
+ * \param type The osl_storage_type to iterate over.
+ * \param col Same meaning as in FOR_EACH_COLUMN().
+ * \param table Same meaning as in FOR_EACH_COLUMN().
+ * \param cd Same meaning as in FOR_EACH_COLUMN().
+ *
+ * \sa osl_storage_type, FOR_EACH_COLUMN, FOR_EACH_RBTREE_COLUMN,
+ * FOR_EACH_MAPPED_COLUMN, FOR_EACH_DISK_STORAGE_COLUMN,
+ * FOR_EACH_VOLATILE_COLUMN.
+ */
+#define FOR_EACH_COLUMN_OF_TYPE(type, col, table, cd) \
+       for (col = next_column_of_type(type, 0, table, &cd); \
+       col < table->desc->num_columns; \
+       col = next_column_of_type(type, ++col, table, &cd))
+
+/**
+ * Iterate over each mapped column.
+ *
+ * \param col Same meaning as in FOR_EACH_COLUMN().
+ * \param table Same meaning as in FOR_EACH_COLUMN().
+ * \param cd Same meaning as in FOR_EACH_COLUMN().
+ *
+ * Just like FOR_EACH_COLUMN(), but skip columns which are
+ * not of type \p OSL_MAPPED_STORAGE.
+ *
+ * \sa osl_storage_flags::OSL_MAPPED_STORAGE, FOR_EACH_COLUMN,
+ * FOR_EACH_RBTREE_COLUMN, FOR_EACH_COLUMN_OF_TYPE,
+ * FOR_EACH_DISK_STORAGE_COLUMN, FOR_EACH_VOLATILE_COLUMN.
+ */
+#define FOR_EACH_MAPPED_COLUMN(col, table, cd) \
+       FOR_EACH_COLUMN_OF_TYPE(OSL_MAPPED_STORAGE, col, table, cd)
+
+/**
+ * Iterate over each disk storage column.
+ *
+ * \param col Same meaning as in FOR_EACH_COLUMN().
+ * \param table Same meaning as in FOR_EACH_COLUMN().
+ * \param cd Same meaning as in FOR_EACH_COLUMN().
+ *
+ * Just like FOR_EACH_COLUMN(), but skip columns which are
+ * not of type \p OSL_DISK_STORAGE.
+ *
+ * \sa osl_storage_flags::OSL_DISK_STORAGE, FOR_EACH_COLUMN,
+ * FOR_EACH_RBTREE_COLUMN, FOR_EACH_COLUMN_OF_TYPE, FOR_EACH_MAPPED_COLUMN,
+ * FOR_EACH_VOLATILE_COLUMN.
+ */
+#define FOR_EACH_DISK_STORAGE_COLUMN(col, table, cd) \
+       FOR_EACH_COLUMN_OF_TYPE(OSL_DISK_STORAGE, col, table, cd)
+
+/**
+ * Iterate over each volatile column.
+ *
+ * \param col Same meaning as in FOR_EACH_COLUMN().
+ * \param table Same meaning as in FOR_EACH_COLUMN().
+ * \param cd Same meaning as in FOR_EACH_COLUMN().
+ *
+ * Just like FOR_EACH_COLUMN(), but skip columns which are
+ * not of type \p OSL_NO_STORAGE.
+ *
+ * \sa osl_storage_flags::OSL_NO_STORAGE, FOR_EACH_COLUMN,
+ * FOR_EACH_RBTREE_COLUMN, FOR_EACH_COLUMN_OF_TYPE, FOR_EACH_MAPPED_COLUMN,
+ * FOR_EACH_DISK_STORAGE_COLUMN.
+ */
+#define FOR_EACH_VOLATILE_COLUMN(col, table, cd) \
+       FOR_EACH_COLUMN_OF_TYPE(OSL_NO_STORAGE, col, table, cd)
diff --git a/para.h b/para.h
index a4753d4..f41e8b6 100644 (file)
--- a/para.h
+++ b/para.h
 /** sent by para_client, followed by the decrypted challenge number */
 #define CHALLENGE_RESPONSE_MSG "challenge_response:"
 
-/* gui_common */
-int para_open_audiod_pipe(char *);
-int read_audiod_pipe(int, void (*)(char *));
-
 /* exec */
 int para_exec_cmdline_pid(pid_t *pid, const char *cmdline, int *fds);
 
@@ -177,7 +173,6 @@ int stat_item_valid(const char *item);
 int stat_line_valid(const char *);
 void stat_client_write(const char *msg, int itemnum);
 int stat_client_add(int fd, long unsigned mask);
-size_t for_each_line(char *buf, size_t n, void (*line_handler)(char *));
 #define FOR_EACH_STAT_ITEM(i) for (i = 0; i < NUM_STAT_ITEMS; i++)
 
 __printf_2_3 void para_log(int, const char*, ...);
diff --git a/playlist.c b/playlist.c
new file mode 100644 (file)
index 0000000..9153bf1
--- /dev/null
@@ -0,0 +1,142 @@
+#include "para.h"
+#include "error.h"
+#include "afs.h"
+#include "string.h"
+
+/** \file playlist.c Functions for loading and saving playlists. */
+
+/** Structure used for adding entries to a playlist. */
+struct playlist_info {
+       /** The name of the playlist. */
+       char *name;
+       /** The number of entries currently in the playlist. */
+       unsigned length;
+};
+static struct playlist_info playlist;
+
+/**
+ * Re-insert an audio file into the tree of admissible files.
+ *
+ * \param aft_row Determines the audio file.
+ *
+ * \return The return value of score_update().
+ */
+int playlist_update_audio_file(struct osl_row *aft_row)
+{
+       /* always re-insert to the top of the tree */
+       return score_update(aft_row, 0);
+}
+
+static int add_playlist_entry(char *line, void *private_data)
+{
+       struct osl_row *aft_row;
+       struct playlist_info *pli = private_data;
+       int ret = aft_get_row_of_path(line, &aft_row);
+
+       if (ret < 0) {
+               PARA_NOTICE_LOG("path not found in audio file table: %s\n",
+                       line);
+               return 1;
+       }
+       ret = score_add(aft_row, -pli->length);
+       if (ret < 0) {
+               PARA_ERROR_LOG("failed to add %s: %d\n", line, ret);
+               return ret;
+       }
+       pli->length++;
+       return 1;
+}
+
+static int load_playlist(struct osl_row *row)
+{
+       struct osl_object obj;
+       int ret;
+
+       ret = osl_get_object(playlists_table, row, BLOBCOL_NAME, &obj);
+       if (ret < 0)
+               return ret;
+       playlist.name = para_strdup(obj.data);
+       playlist.length = 0;
+       ret = osl_open_disk_object(playlists_table, row, BLOBCOL_DEF, &obj);
+       if (ret < 0)
+               goto err;
+       ret = for_each_line_ro(obj.data, obj.size, add_playlist_entry,
+               &playlist);
+       osl_close_disk_object(&obj);
+       if (ret < 0)
+               goto err;
+       ret = -E_PLAYLIST_EMPTY;
+       if (!playlist.length)
+               goto err;
+       PARA_NOTICE_LOG("loaded playlist %s (%u files)\n", playlist.name,
+               playlist.length);
+       return 1;
+err:
+       free(playlist.name);
+       return ret;
+}
+
+/* returns -E_PLAYLIST_LOADED on _success_ to terminate the loop */
+static int playlist_loop(struct osl_row *row, __a_unused void *private_data)
+{
+       int ret = load_playlist(row);
+       if (ret < 0) {
+               if (ret != -E_DUMMY_ROW)
+                       PARA_NOTICE_LOG("unable to load playlist, trying next\n");
+               return 1;
+       }
+       return -E_PLAYLIST_LOADED;
+}
+
+static int load_first_available_playlist(void)
+{
+       int ret = osl_rbtree_loop(playlists_table, BLOBCOL_NAME, NULL,
+               playlist_loop);
+       if (ret == -E_PLAYLIST_LOADED) /* success */
+               return 1;
+       if (ret < 0)
+               return ret; /* error */
+       PARA_NOTICE_LOG("no valid playlist found\n");
+       return -E_NO_PLAYLIST;
+}
+
+/**
+ * Close the current playlist.
+ *
+ * \sa playlist_open().
+ */
+void playlist_close(void)
+{
+       free(playlist.name);
+       playlist.name = NULL;
+}
+
+/**
+ * Open the given playlist.
+ *
+ * \param name The name of the playlist to open.
+ *
+ * If name is \p NULL, load the first available playlist. Files which are
+ * listed in the playlist, but not contained in the database are ignored.
+ * This is not considered an error.
+ *
+ * \return Positive on success, negative on errors. Possible errors
+ * include: Given playlist not found, -E_NO_PLAYLIST (no playlist defined).
+ */
+int playlist_open(char *name)
+{
+       struct osl_object obj;
+       int ret;
+       struct osl_row *row;
+
+       if (!name)
+               return load_first_available_playlist();
+       obj.data = name;
+       obj.size = strlen(obj.data);
+       ret = osl_get_row(playlists_table, BLOBCOL_NAME, &obj, &row);
+       if (ret < 0) {
+               PARA_NOTICE_LOG("failed to load playlist %s\n", name);
+               return ret;
+       }
+       return load_playlist(row);
+}
index a497ec1..8372dac 100644 (file)
@@ -53,7 +53,7 @@ static struct audio_file_selector *self;
 
 extern struct misc_meta_data *mmd;
 
-static void playlist_add(char *path)
+static int playlist_add(char *path, __a_unused void *data)
 {
        if (playlist_len >= playlist_size) {
                playlist_size = 2 * playlist_size + 1;
@@ -61,6 +61,7 @@ static void playlist_add(char *path)
        }
        PARA_DEBUG_LOG("adding #%d/%d: %s\n", playlist_len, playlist_size, path);
        playlist[playlist_len++] = para_strdup(path);
+       return 1;
 }
 
 static int send_playlist_to_server(const char *buf, size_t size)
@@ -212,7 +213,7 @@ static void pls_post_select(__a_unused fd_set *rfds, __a_unused fd_set *wfds)
                goto out;
        }
        PARA_DEBUG_LOG("loading new playlist (%zd bytes)\n", pcd->size);
-       ret = for_each_line((char *)shm, pcd->size, &playlist_add);
+       ret = for_each_line((char *)shm, pcd->size, &playlist_add, NULL);
        shm_detach(shm);
        PARA_NOTICE_LOG("new playlist (%d entries)\n", playlist_len);
        sprintf(mmd->selector_info, "dbinfo1:new playlist: %d files\n"
diff --git a/portable_io.h b/portable_io.h
new file mode 100644 (file)
index 0000000..1f3922f
--- /dev/null
@@ -0,0 +1,62 @@
+static inline uint64_t read_portable(unsigned bits, const char *buf)
+{
+       uint64_t ret = 0;
+       int i, num_bytes = bits / 8;
+
+       for (i = 0; i < num_bytes; i++) {
+               unsigned char c = buf[i];
+               ret += ((uint64_t)c << (8 * i));
+       }
+       return ret;
+}
+
+static inline uint64_t read_u64(const char *buf)
+{
+       return read_portable(64, buf);
+}
+
+static inline uint32_t read_u32(const char *buf)
+{
+       return read_portable(32, buf);
+}
+
+static inline uint16_t read_u16(const char *buf)
+{
+       return read_portable(16, buf);
+}
+
+static inline uint8_t read_u8(const char *buf)
+{
+       return read_portable(8, buf);
+}
+
+static inline void write_portable(unsigned bits, char *buf, uint64_t val)
+{
+       int i, num_bytes = bits / 8;
+//     fprintf(stderr, "val: %lu\n", val);
+       for (i = 0; i < num_bytes; i++) {
+               buf[i] = val & 0xff;
+//             fprintf(stderr, "buf[%d]=%x\n", i, buf[i]);
+               val = val >> 8;
+       }
+}
+
+static inline void write_u64(char *buf, uint64_t val)
+{
+       write_portable(64, buf, val);
+}
+
+static inline void write_u32(char *buf, uint32_t val)
+{
+       write_portable(32, buf, (uint64_t) val);
+}
+
+static inline void write_u16(char *buf, uint16_t val)
+{
+       write_portable(16, buf, (uint64_t) val);
+}
+
+static inline void write_u8(char *buf, uint8_t val)
+{
+       write_portable(8, buf, (uint64_t) val);
+}
diff --git a/rbtree.c b/rbtree.c
new file mode 100644 (file)
index 0000000..ed8f0ac
--- /dev/null
+++ b/rbtree.c
@@ -0,0 +1,455 @@
+/*
+  Red Black Trees
+  (C) 1999  Andrea Arcangeli <andrea@suse.de>
+  (C) 2002  David Woodhouse <dwmw2@infradead.org>
+  (C) 2007  Andre Noll <maan@systemlinux.org>
+  
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+  linux/lib/rbtree.c
+*/
+
+#include "stddef.h"
+#include "rbtree.h"
+
+static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
+{
+       struct rb_node *right = node->rb_right;
+       struct rb_node *parent = rb_parent(node);
+
+       if ((node->rb_right = right->rb_left))
+               rb_set_parent(right->rb_left, node);
+       right->rb_left = node;
+
+       rb_set_parent(right, parent);
+
+       if (parent)
+       {
+               if (node == parent->rb_left)
+                       parent->rb_left = right;
+               else
+                       parent->rb_right = right;
+       }
+       else
+               root->rb_node = right;
+       rb_set_parent(node, right);
+       right->size = node->size;
+       node->size = 1;
+       if (node->rb_right)
+               node->size += node->rb_right->size;
+       if (node->rb_left)
+               node->size += node->rb_left->size;
+}
+
+static void __rb_rotate_right(struct rb_node *node, struct rb_root *root)
+{
+       struct rb_node *left = node->rb_left;
+       struct rb_node *parent = rb_parent(node);
+
+       if ((node->rb_left = left->rb_right))
+               rb_set_parent(left->rb_right, node);
+       left->rb_right = node;
+
+       rb_set_parent(left, parent);
+
+       if (parent)
+       {
+               if (node == parent->rb_right)
+                       parent->rb_right = left;
+               else
+                       parent->rb_left = left;
+       }
+       else
+               root->rb_node = left;
+       rb_set_parent(node, left);
+       left->size = node->size;
+       node->size = 1;
+       if (node->rb_right)
+               node->size += node->rb_right->size;
+       if (node->rb_left)
+               node->size += node->rb_left->size;
+}
+
+void rb_insert_color(struct rb_node *node, struct rb_root *root)
+{
+       struct rb_node *parent, *gparent;
+
+       while ((parent = rb_parent(node)) && rb_is_red(parent))
+       {
+               gparent = rb_parent(parent);
+
+               if (parent == gparent->rb_left)
+               {
+                       {
+                               register struct rb_node *uncle = gparent->rb_right;
+                               if (uncle && rb_is_red(uncle))
+                               {
+                                       rb_set_black(uncle);
+                                       rb_set_black(parent);
+                                       rb_set_red(gparent);
+                                       node = gparent;
+                                       continue;
+                               }
+                       }
+
+                       if (parent->rb_right == node)
+                       {
+                               register struct rb_node *tmp;
+                               __rb_rotate_left(parent, root);
+                               tmp = parent;
+                               parent = node;
+                               node = tmp;
+                       }
+
+                       rb_set_black(parent);
+                       rb_set_red(gparent);
+                       __rb_rotate_right(gparent, root);
+               } else {
+                       {
+                               register struct rb_node *uncle = gparent->rb_left;
+                               if (uncle && rb_is_red(uncle))
+                               {
+                                       rb_set_black(uncle);
+                                       rb_set_black(parent);
+                                       rb_set_red(gparent);
+                                       node = gparent;
+                                       continue;
+                               }
+                       }
+
+                       if (parent->rb_left == node)
+                       {
+                               register struct rb_node *tmp;
+                               __rb_rotate_right(parent, root);
+                               tmp = parent;
+                               parent = node;
+                               node = tmp;
+                       }
+
+                       rb_set_black(parent);
+                       rb_set_red(gparent);
+                       __rb_rotate_left(gparent, root);
+               }
+       }
+
+       rb_set_black(root->rb_node);
+}
+
+static void __rb_erase_color(struct rb_node *node, struct rb_node *parent,
+                            struct rb_root *root)
+{
+       struct rb_node *other;
+
+       while ((!node || rb_is_black(node)) && node != root->rb_node)
+       {
+               if (parent->rb_left == node)
+               {
+                       other = parent->rb_right;
+                       if (rb_is_red(other))
+                       {
+                               rb_set_black(other);
+                               rb_set_red(parent);
+                               __rb_rotate_left(parent, root);
+                               other = parent->rb_right;
+                       }
+                       if ((!other->rb_left || rb_is_black(other->rb_left)) &&
+                           (!other->rb_right || rb_is_black(other->rb_right)))
+                       {
+                               rb_set_red(other);
+                               node = parent;
+                               parent = rb_parent(node);
+                       }
+                       else
+                       {
+                               if (!other->rb_right || rb_is_black(other->rb_right))
+                               {
+                                       struct rb_node *o_left;
+                                       if ((o_left = other->rb_left))
+                                               rb_set_black(o_left);
+                                       rb_set_red(other);
+                                       __rb_rotate_right(other, root);
+                                       other = parent->rb_right;
+                               }
+                               rb_set_color(other, rb_color(parent));
+                               rb_set_black(parent);
+                               if (other->rb_right)
+                                       rb_set_black(other->rb_right);
+                               __rb_rotate_left(parent, root);
+                               node = root->rb_node;
+                               break;
+                       }
+               }
+               else
+               {
+                       other = parent->rb_left;
+                       if (rb_is_red(other))
+                       {
+                               rb_set_black(other);
+                               rb_set_red(parent);
+                               __rb_rotate_right(parent, root);
+                               other = parent->rb_left;
+                       }
+                       if ((!other->rb_left || rb_is_black(other->rb_left)) &&
+                           (!other->rb_right || rb_is_black(other->rb_right)))
+                       {
+                               rb_set_red(other);
+                               node = parent;
+                               parent = rb_parent(node);
+                       }
+                       else
+                       {
+                               if (!other->rb_left || rb_is_black(other->rb_left))
+                               {
+                                       register struct rb_node *o_right;
+                                       if ((o_right = other->rb_right))
+                                               rb_set_black(o_right);
+                                       rb_set_red(other);
+                                       __rb_rotate_left(other, root);
+                                       other = parent->rb_left;
+                               }
+                               rb_set_color(other, rb_color(parent));
+                               rb_set_black(parent);
+                               if (other->rb_left)
+                                       rb_set_black(other->rb_left);
+                               __rb_rotate_right(parent, root);
+                               node = root->rb_node;
+                               break;
+                       }
+               }
+       }
+       if (node)
+               rb_set_black(node);
+}
+
+void rb_erase(struct rb_node *node, struct rb_root *root)
+{
+       struct rb_node *child, *parent;
+       int color;
+
+       if (!node->rb_left)
+               child = node->rb_right;
+       else if (!node->rb_right)
+               child = node->rb_left;
+       else
+       {
+               struct rb_node *old = node, *left;
+
+               node = node->rb_right;
+               while ((left = node->rb_left) != NULL)
+                       node = left;
+               child = node->rb_right;
+               parent = rb_parent(node);
+               color = rb_color(node);
+
+               if (child)
+                       rb_set_parent(child, parent);
+               if (parent == old) {
+                       parent->rb_right = child;
+                       parent = node;
+               } else
+                       parent->rb_left = child;
+
+               node->rb_parent_color = old->rb_parent_color;
+               node->rb_right = old->rb_right;
+               node->rb_left = old->rb_left;
+               node->size = old->size;
+
+               if (rb_parent(old))
+               {
+                       if (rb_parent(old)->rb_left == old)
+                               rb_parent(old)->rb_left = node;
+                       else
+                               rb_parent(old)->rb_right = node;
+               } else
+                       root->rb_node = node;
+
+               rb_set_parent(old->rb_left, node);
+               if (old->rb_right)
+                       rb_set_parent(old->rb_right, node);
+               goto color;
+       }
+
+       parent = rb_parent(node);
+       color = rb_color(node);
+
+       if (child)
+               rb_set_parent(child, parent);
+       if (parent)
+       {
+               if (parent->rb_left == node)
+                       parent->rb_left = child;
+               else
+                       parent->rb_right = child;
+       }
+       else
+               root->rb_node = child;
+
+ color:
+       if (color == RB_BLACK)
+               __rb_erase_color(child, parent, root);
+}
+
+/*
+ * This function returns the first node (in sort order) of the tree.
+ */
+struct rb_node *rb_first(struct rb_root *root)
+{
+       struct rb_node  *n;
+
+       n = root->rb_node;
+       if (!n)
+               return NULL;
+       while (n->rb_left)
+               n = n->rb_left;
+       return n;
+}
+
+struct rb_node *rb_last(struct rb_root *root)
+{
+       struct rb_node  *n;
+
+       n = root->rb_node;
+       if (!n)
+               return NULL;
+       while (n->rb_right)
+               n = n->rb_right;
+       return n;
+}
+
+struct rb_node *rb_next(struct rb_node *node)
+{
+       struct rb_node *parent;
+
+       if (rb_parent(node) == node)
+               return NULL;
+
+       /* If we have a right-hand child, go down and then left as far
+          as we can. */
+       if (node->rb_right) {
+               node = node->rb_right; 
+               while (node->rb_left)
+                       node=node->rb_left;
+               return node;
+       }
+
+       /* No right-hand children.  Everything down and left is
+          smaller than us, so any 'next' node must be in the general
+          direction of our parent. Go up the tree; any time the
+          ancestor is a right-hand child of its parent, keep going
+          up. First time it's a left-hand child of its parent, said
+          parent is our 'next' node. */
+       while ((parent = rb_parent(node)) && node == parent->rb_right)
+               node = parent;
+
+       return parent;
+}
+
+struct rb_node *rb_prev(struct rb_node *node)
+{
+       struct rb_node *parent;
+
+       if (rb_parent(node) == node)
+               return NULL;
+
+       /* If we have a left-hand child, go down and then right as far
+          as we can. */
+       if (node->rb_left) {
+               node = node->rb_left; 
+               while (node->rb_right)
+                       node=node->rb_right;
+               return node;
+       }
+
+       /* No left-hand children. Go up till we find an ancestor which
+          is a right-hand child of its parent */
+       while ((parent = rb_parent(node)) && node == parent->rb_left)
+               node = parent;
+
+       return parent;
+}
+
+void rb_replace_node(struct rb_node *victim, struct rb_node *new,
+                    struct rb_root *root)
+{
+       struct rb_node *parent = rb_parent(victim);
+
+       /* Set the surrounding nodes to point to the replacement */
+       if (parent) {
+               if (victim == parent->rb_left)
+                       parent->rb_left = new;
+               else
+                       parent->rb_right = new;
+       } else {
+               root->rb_node = new;
+       }
+       if (victim->rb_left)
+               rb_set_parent(victim->rb_left, new);
+       if (victim->rb_right)
+               rb_set_parent(victim->rb_right, new);
+
+       /* Copy the pointers/colour from the victim to the replacement */
+       *new = *victim;
+}
+
+/**
+ * Get the n-th node (in sort order) of the tree.
+ *
+ * \param node The root of the subtree to consider.
+ * \param n The order statistic to compute.
+ *
+ * \return Pointer to the \a n th greatest node on success, \p NULL on errors.
+ */
+struct rb_node *rb_nth(struct rb_node *node, unsigned n)
+{
+       unsigned size = 1;
+
+       if (!node)
+               return NULL;
+       if (node->rb_left)
+               size += node->rb_left->size;
+       if (n == size)
+               return node;
+       if (n < size)
+               return rb_nth(node->rb_left, n);
+       return rb_nth(node->rb_right, n - size);
+}
+
+/**
+ * Get the rank of a node in O(log n) time.
+ *
+ * \param node The node to get the rank of.
+ * \param rank Result pointer.
+ *
+ * \return Positive on success, -1 on errors.
+ */
+int rb_rank(struct rb_node *node, unsigned *rank)
+{
+       *rank = 1;
+       struct rb_node *parent;
+
+       if (!node)
+               return -1;
+       if (node->rb_left)
+               *rank += node->rb_left->size;
+       while ((parent = rb_parent(node))) {
+               if (node == parent->rb_right) {
+                       (*rank)++;
+                       if (parent->rb_left)
+                               *rank += parent->rb_left->size;
+               }
+               node = parent;
+       }
+       return 1;
+}
diff --git a/rbtree.h b/rbtree.h
new file mode 100644 (file)
index 0000000..1dafb79
--- /dev/null
+++ b/rbtree.h
@@ -0,0 +1,172 @@
+/*
+  Red Black Trees
+  (C) 1999  Andrea Arcangeli <andrea@suse.de>
+  
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+  linux/include/linux/rbtree.h
+
+  To use rbtrees you'll have to implement your own insert and search cores.
+  This will avoid us to use callbacks and to drop drammatically performances.
+  I know it's not the cleaner way,  but in C (not in C++) to get
+  performances and genericity...
+
+  Some example of insert and search follows here. The search is a plain
+  normal search over an ordered tree. The insert instead must be implemented
+  int two steps: as first thing the code must insert the element in
+  order as a red leaf in the tree, then the support library function
+  rb_insert_color() must be called. Such function will do the
+  not trivial work to rebalance the rbtree if necessary.
+
+-----------------------------------------------------------------------
+static inline struct page * rb_search_page_cache(struct inode * inode,
+                                                unsigned long offset)
+{
+       struct rb_node * n = inode->i_rb_page_cache.rb_node;
+       struct page * page;
+
+       while (n)
+       {
+               page = rb_entry(n, struct page, rb_page_cache);
+
+               if (offset < page->offset)
+                       n = n->rb_left;
+               else if (offset > page->offset)
+                       n = n->rb_right;
+               else
+                       return page;
+       }
+       return NULL;
+}
+
+static inline struct page * __rb_insert_page_cache(struct inode * inode,
+                                                  unsigned long offset,
+                                                  struct rb_node * node)
+{
+       struct rb_node ** p = &inode->i_rb_page_cache.rb_node;
+       struct rb_node * parent = NULL;
+       struct page * page;
+
+       while (*p)
+       {
+               parent = *p;
+               page = rb_entry(parent, struct page, rb_page_cache);
+
+               if (offset < page->offset)
+                       p = &(*p)->rb_left;
+               else if (offset > page->offset)
+                       p = &(*p)->rb_right;
+               else
+                       return page;
+       }
+
+       rb_link_node(node, parent, p);
+
+       return NULL;
+}
+
+static inline struct page * rb_insert_page_cache(struct inode * inode,
+                                                unsigned long offset,
+                                                struct rb_node * node)
+{
+       struct page * ret;
+       if ((ret = __rb_insert_page_cache(inode, offset, node)))
+               goto out;
+       rb_insert_color(node, &inode->i_rb_page_cache);
+ out:
+       return ret;
+}
+-----------------------------------------------------------------------
+*/
+
+#ifndef        _LINUX_RBTREE_H
+#define        _LINUX_RBTREE_H
+
+
+/** get the struct this entry is embedded in */
+#define container_of(ptr, type, member) ({                      \
+       const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
+       (type *)( (char *)__mptr - offsetof(type,member) );})
+
+
+struct rb_node
+{
+       unsigned long  rb_parent_color;
+#define        RB_RED          0
+#define        RB_BLACK        1
+       struct rb_node *rb_right;
+       struct rb_node *rb_left;
+       unsigned size;
+} __attribute__((aligned(sizeof(long))));
+    /* The alignment might seem pointless, but allegedly CRIS needs it */
+
+struct rb_root
+{
+       struct rb_node *rb_node;
+};
+
+
+#define rb_parent(r)   ((struct rb_node *)((r)->rb_parent_color & ~3))
+#define rb_color(r)   ((r)->rb_parent_color & 1)
+#define rb_is_red(r)   (!rb_color(r))
+#define rb_is_black(r) rb_color(r)
+#define rb_set_red(r)  do { (r)->rb_parent_color &= ~1; } while (0)
+#define rb_set_black(r)  do { (r)->rb_parent_color |= 1; } while (0)
+
+static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p)
+{
+       rb->rb_parent_color = (rb->rb_parent_color & 3) | (unsigned long)p;
+}
+static inline void rb_set_color(struct rb_node *rb, int color)
+{
+       rb->rb_parent_color = (rb->rb_parent_color & ~1) | color;
+}
+
+#define RB_ROOT        (struct rb_root) { NULL, }
+#define        rb_entry(ptr, type, member) container_of(ptr, type, member)
+
+#define RB_EMPTY_ROOT(root)    ((root)->rb_node == NULL)
+#define RB_EMPTY_NODE(node)    (rb_parent(node) == node)
+#define RB_CLEAR_NODE(node)    (rb_set_parent(node, node))
+
+extern void rb_insert_color(struct rb_node *, struct rb_root *);
+extern void rb_erase(struct rb_node *, struct rb_root *);
+
+/* Find logical next and previous nodes in a tree */
+extern struct rb_node *rb_next(struct rb_node *);
+extern struct rb_node *rb_prev(struct rb_node *);
+extern struct rb_node *rb_first(struct rb_root *);
+extern struct rb_node *rb_last(struct rb_root *);
+extern struct rb_node *rb_nth(struct rb_node *node, unsigned n);
+extern int rb_rank(struct rb_node *node, unsigned *rank);
+
+/* Fast replacement of a single node without remove/rebalance/add/rebalance */
+extern void rb_replace_node(struct rb_node *victim, struct rb_node *new, 
+                           struct rb_root *root);
+
+static inline void rb_link_node(struct rb_node * node, struct rb_node * parent,
+                               struct rb_node ** rb_link)
+{
+       node->size = 1;
+       node->rb_parent_color = (unsigned long )parent;
+       node->rb_left = node->rb_right = NULL;
+
+       *rb_link = node;
+       /* Fixup the size fields in the tree */
+       while ((node = rb_parent(node)))
+               node->size++;
+}
+
+#endif /* _LINU