--- /dev/null
+#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
--- /dev/null
+#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);
+
--- /dev/null
+#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;
+}
--- /dev/null
+#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;
+}
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;
// 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);
stat_task->clock_diff_count--;
break;
}
+ return 1;
}
static void try_to_close_slot(int slot_num)
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;
--- /dev/null
+#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 */
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"
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
};
#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"), \
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 */
# else
# define __must_check /* no warn_unused_result */
# endif
+
+#define _static_inline_ static inline
#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"
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,...)
/*
* 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;
stat_content[i] = para_strdup(line);
print_stat_item(i);
}
- return;
+ return 1;
}
/*
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);
#include "para.h"
+#include "string.h"
#include "fd.h"
extern const char *status_item_list[NUM_STAT_ITEMS];
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;
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;
}
--- /dev/null
+#include "string.h"
+int para_open_audiod_pipe(char *);
+int read_audiod_pipe(int fd, line_handler_t *line_handler);
+
--- /dev/null
+#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';
+}
--- /dev/null
+#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, ¤t_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, ¤t_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(¤t_mood->accept_list))
+ return -E_NOT_ADMISSIBLE;
+ list_for_each_entry(item, ¤t_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;
+}
/*
* 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;
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+#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;
+}
--- /dev/null
+/*
+ * 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)
/** 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);
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*, ...);
--- /dev/null
+#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);
+}
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;
}
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)
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"
--- /dev/null
+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);
+}
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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 /* _LINUX_RBTREE_H */
--- /dev/null
+/*
+ * Copyright (C) 2007 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file score.c Scoring functions to determine the audio file streaming order. */
+#include "para.h"
+#include "error.h"
+#include "afs.h"
+#include "list.h"
+#include "string.h"
+
+int mood_compute_score(const void *row, long *score);
+
+static void *score_table;
+
+static int ptr_compare(const struct osl_object *obj1, const struct osl_object *obj2)
+{
+ void *d1 = *(void **)obj1->data;
+ void *d2 = *(void **)obj2->data;
+ return NUM_COMPARE(d1, d2);
+}
+
+/**
+ * Compare the score of two audio files
+ *
+ * \param obj1 Pointer to the first score object.
+ * \param obj2 Pointer to the second score object.
+ *
+ * This function first compares the score values as usual integers. If they compare as
+ * equal, the addresss of \a obj1 and \a obj2 are compared. So this compare function
+ * returns zero if and only if \a obj1 and \a obj2 point to the same memory area.
+ *
+ * \sa osl_compare_function.
+ */
+static int score_compare(const struct osl_object *obj1, const struct osl_object *obj2)
+{
+ int d1 = *(int*)obj1->data;
+ int d2 = *(int*)obj2->data;
+ int ret = NUM_COMPARE(d2, d1);
+
+ if (ret)
+ return ret;
+ return NUM_COMPARE(obj2->data, obj1->data);
+}
+
+/**
+ * The score table consists of two colums: The \a aft_row column contains
+ * pointers to the rows of the audio file table, and the score column contains
+ * the current score of the audio file associated with that row.
+ */
+enum score_table_columns {
+ /** The row of the audio file. */
+ SCORECOL_AFT_ROW,
+ /** The score */
+ SCORECOL_SCORE,
+ /** This table has two columns */
+ NUM_SCORE_COLUMNS
+};
+
+static struct osl_column_description score_cols[] = {
+ [SCORECOL_AFT_ROW] = {
+ .storage_type = OSL_NO_STORAGE,
+ .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
+ .name = "aft_row",
+ .compare_function = ptr_compare,
+ .data_size = sizeof(void *)
+ },
+ [SCORECOL_SCORE] = {
+ .storage_type = OSL_NO_STORAGE,
+ .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE,
+ .name = "score",
+ .compare_function = score_compare,
+ .data_size = sizeof(long)
+ }
+};
+
+static struct osl_table_description score_table_desc = {
+ .dir = DATABASE_DIR,
+ .name = "score",
+ .num_columns = NUM_SCORE_COLUMNS,
+ .flags = 0,
+ .column_descriptions = score_cols
+};
+
+/**
+ * Compute the number of files in score table.
+ *
+ * \param num Result is returned here.
+ *
+ * \return Positive on success, negative on errors.
+ *
+ * \sa osl_get_num_rows().
+ */
+int get_num_admissible_files(unsigned *num)
+{
+ return osl_get_num_rows(score_table, num);
+}
+
+/**
+ * Get the score of the audio file associated with given row of the score table.
+ *
+ * \param score_row Pointer to the row in the score table.
+ * \param score Result is returned here on success.
+ *
+ * On errors (negative return value) the content of \a score is undefined.
+ *
+ * \return The return value of the underlying call to osl_get_object().
+ */
+static int get_score_of_row(void *score_row, long *score)
+{
+ struct osl_object obj;
+ int ret = osl_get_object(score_table, score_row, SCORECOL_SCORE, &obj);
+
+ if (ret >= 0)
+ *score = *(long *)obj.data;
+ return ret;
+}
+
+/**
+ * Add an entry to the table of admissible files.
+ *
+ * \param aft_row The audio file to be added.
+ * \param score The score for this file.
+ *
+ * \return The return value of the underlying call to osl_add_row().
+ */
+int score_add(const struct osl_row *aft_row, long score)
+{
+ int ret;
+ struct osl_object score_objs[NUM_SCORE_COLUMNS];
+ size_t size;
+
+ assert(aft_row);
+ size = score_table_desc.column_descriptions[SCORECOL_AFT_ROW].data_size;
+ score_objs[SCORECOL_AFT_ROW].data = para_malloc(size);
+ score_objs[SCORECOL_AFT_ROW].size = size;
+ *(const void **)(score_objs[SCORECOL_AFT_ROW].data) = aft_row;
+
+ size = score_table_desc.column_descriptions[SCORECOL_SCORE].data_size;
+ score_objs[SCORECOL_SCORE].data = para_malloc(size);
+ score_objs[SCORECOL_SCORE].size = size;
+ *(int *)(score_objs[SCORECOL_SCORE].data) = score;
+
+// PARA_DEBUG_LOG("adding %p\n", *(void **) (score_objs[SCORECOL_AFT_ROW].data));
+ ret = osl_add_row(score_table, score_objs);
+ if (ret < 0) {
+ PARA_ERROR_LOG("failed to add row: %d\n", ret);
+ free(score_objs[SCORECOL_AFT_ROW].data);
+ free(score_objs[SCORECOL_SCORE].data);
+ }
+ return ret;
+}
+
+static int get_nth_score(unsigned n, long *score)
+{
+ struct osl_row *row;
+ int ret = osl_get_nth_row(score_table, SCORECOL_SCORE, n, &row);
+
+ if (ret < 0)
+ return ret;
+ return get_score_of_row(row, score);
+}
+
+/**
+ * Replace a row of the score table.
+ *
+ * \param aft_row Determines the audio file to change.
+ * \param percent The position to re-insert the audio file.
+ *
+ * The percent paramenter must be between \p 0 and 100 and. A value of zero
+ * means to re-insert the audio file into the score table with a score lower
+ * than any other admissible file.
+ *
+ * \return Positive on success, negative on errors. Possible errors: Errors
+ * returned by osl_get_row(), get_num_admissible_files(), osl_get_nth_row(),
+ * osl_get_object(), osl_update_object().
+ */
+int score_update(const struct osl_row *aft_row, long percent)
+{
+ struct osl_row *row;
+ long new_score;
+ unsigned n, new_pos;
+ struct osl_object obj = {.data = &aft_row, .size = sizeof(void **)};
+ int ret = osl_get_row(score_table, SCORECOL_AFT_ROW, &obj, &row);
+
+ if (ret < 0)
+ return ret;
+ ret = get_num_admissible_files(&n);
+ if (ret < 0)
+ return ret;
+ new_pos = 1 + (n - 1) * percent / 100;
+ ret = get_nth_score(new_pos, &new_score);
+ if (ret < 0)
+ return ret;
+ new_score--;
+ obj.size = sizeof(long);
+ obj.data = para_malloc(obj.size);
+ *(long *)obj.data = new_score;
+ PARA_NOTICE_LOG("new score: %ld, position: %u/%u\n", new_score,
+ new_pos, n);
+ return osl_update_object(score_table, row, SCORECOL_SCORE, &obj);
+}
+
+/**
+ * Given an admissible file, get the corresponding row in the aft and the score.
+ *
+ * \param score_row Determines the admissible file.
+ * \param score Result pointer.
+ * \param aft_row Result pointer.
+ *
+ * \return Negative on errors, positive on success. Possible errors: Errors
+ * returned by osl_get_object().
+ */
+int get_score_and_aft_row(struct osl_row *score_row, long *score,
+ struct osl_row **aft_row)
+{
+ struct osl_object obj;
+ int ret = get_score_of_row(score_row, score);
+
+ if (ret < 0)
+ return ret;
+ ret = osl_get_object(score_table, score_row, SCORECOL_AFT_ROW, &obj);
+ if (ret < 0)
+ return ret;
+ *aft_row = *(void **)obj.data;
+ return 1;
+}
+
+static int get_score_row_from_aft_row(const struct osl_row *aft_row,
+ struct osl_row **score_row)
+{
+ struct osl_object obj = {.data = &aft_row, .size = sizeof(void **)};
+ return osl_get_row(score_table, SCORECOL_AFT_ROW, &obj, score_row);
+
+}
+
+/**
+ * Loop over all files in the score table.
+ *
+ * \param data A pointer to arbitrary data.
+ * \param func Function to be called for each admissible file.
+ *
+ * \return The return value of the underlying call to osl_rbtree_loop().
+ *
+ * This is used for the ls command. The \a data parameter is passed as the
+ * second argument to \a func.
+ *
+ * \sa admissible_file_loop_reverse().
+ */
+int admissible_file_loop(void *data, osl_rbtree_loop_func *func)
+{
+ return osl_rbtree_loop(score_table, SCORECOL_SCORE, data, func);
+}
+
+/**
+ * Loop over all files in the score table in reverse order.
+ *
+ * \param data As in admissible_file_loop().
+ * \param func As in admissible_file_loop().
+ *
+ * \return Same return value as admissible_file_loop().
+ *
+ * \sa admissible_file_loop(), osl_rbtree_loop_reverse().
+ */
+int admissible_file_loop_reverse(void *data, osl_rbtree_loop_func *func)
+{
+ return osl_rbtree_loop_reverse(score_table, SCORECOL_SCORE, data, func);
+}
+
+/**
+ * Get the admissible audio file with highest score.
+ *
+ * \param aft_row Points to the row in the aft of the "best" audio file.
+ * \param score Highest score value in the score table.
+ *
+ * \return Positive on success, negative on errors. Possible errors: Errors
+ * returned by osl_rbtree_last_row(), osl_get_object().
+ */
+int score_get_best(struct osl_row **aft_row, long *score)
+{
+ struct osl_row *row;
+ struct osl_object obj;
+ int ret = osl_rbtree_last_row(score_table, SCORECOL_SCORE, &row);
+
+ if (ret < 0)
+ return ret;
+ ret = osl_get_object(score_table, row, SCORECOL_AFT_ROW, &obj);
+ if (ret < 0)
+ return ret;
+ *aft_row = *(void **)obj.data;
+ return get_score_of_row(row, score);
+}
+
+/**
+ * Remove an entry from the rbtree of admissible files.
+ *
+ * \param aft_row The file which is no longer admissible.
+ *
+ * \return Positive on success, negative on errors. Possible errors:
+ * Errors returned by osl_get_row() and osl_del_row().
+ *
+ * \sa score_add(), score_shutdown().
+ */
+int score_delete(const struct osl_row *aft_row)
+{
+ struct osl_row *score_row;
+ int ret = get_score_row_from_aft_row(aft_row, &score_row);
+
+ if (ret < 0)
+ return ret;
+ return osl_del_row(score_table, score_row);
+}
+
+/**
+ * Find out whether an audio file is contained in the score table.
+ *
+ * \param aft_row The row of the audio file table.
+ *
+ * \return Positive, if \a aft_row belongs to the audio file table,
+ * zero if not, negative on errors.
+ */
+int row_belongs_to_score_table(const struct osl_row *aft_row)
+{
+ struct osl_row *score_row;
+ int ret = get_score_row_from_aft_row(aft_row, &score_row);
+ if (ret >= 0)
+ return 1;
+ if (ret == -E_RB_KEY_NOT_FOUND)
+ return 0;
+ return ret;
+}
+
+/**
+ * Close the score table.
+ *
+ * \param flags Options passed to osl_close_table().
+ *
+ * \sa score_init().
+ */
+void score_shutdown(enum osl_close_flags flags)
+{
+ osl_close_table(score_table, flags | OSL_FREE_VOLATILE);
+}
+
+/**
+ * Init the scoring subsystem.
+ *
+ * \param ti Gets filled in by the function.
+ *
+ * \return The return value of the underlying call to osl_open_table().
+ *
+ * \sa score_shutdown().
+ */
+int score_init(struct table_info *ti)
+{
+ ti->desc = &score_table_desc;
+ ti->flags = TBLFLAG_SKIP_CREATE;
+ int ret = osl_open_table(ti->desc, &ti->table);
+
+ score_table = ti->table;
+ return ret;
+}
/** \file sdl_gui.c SDL-based interface for paraslash */
#include "para.h"
-#include "string.h"
+#include "gui_common.h"
#include "fd.h"
* Check if buf is a known status line. If so call do_update and return 1.
* Return 0 otherwise.
*/
-void update_status(char *buf)
+int update_status(char *buf, __a_unused void *data)
{
int i;
i = stat_line_valid(buf);
if (i < 0)
- return;
+ return 1;
//free(stat_items[i].content);
stat_items[i].content = para_strdup(buf +
strlen(status_item_list[i]) + 1);
do_update(i);
+ return 1;
}
/*
--- /dev/null
+#include "para.h"
+#include <openssl/sha.h>
+
+/** \file sha1.c Secure Hash Algorithm, provided by openssl. */
+
+/**
+ * Compute the sha1 hash.
+ *
+ * \param data Pointer to the data to compute the hash valure from.
+ * \param len The length of \a data in bytes.
+ * \param sha1 Result pointer
+ *
+ * \a sha1 must point to an area at least 20 bytes large.
+ *
+ * \sa sha(3), openssl(1).
+ * */
+void sha1_hash(const char *data, unsigned long len, unsigned char *sha1)
+{
+ SHA_CTX c;
+ SHA1_Init(&c);
+ SHA1_Update(&c, data, len);
+ SHA1_Final(sha1, &c);
+}
--- /dev/null
+/** \file sha1.h Secure Hash Algorithm prototype */
+
+/** Size of the hahs value in bytes. */
+#define HASH_SIZE 20
+void sha1_hash(const char *data, unsigned long len, unsigned char *sha1);
return -E_UNKNOWN_STAT_ITEM;
}
-/**
- * call a custom function for each complete line
- *
- * \param buf the buffer containing data seperated by newlines
- * \param n the number of bytes in \a buf
- * \param line_handler the custom function
- *
- * If \a line_handler is \p NULL, return number of complete lines in buf.
- * Otherwise, call \a line_handler for each complete line. The rest of the
- * buffer (last chunk containing incomplete line is moved to the beginning of
- * the buffer.
- *
- * \return If line_handler is not NULL, this function returns the number
- * of bytes not handled to \a line_handler.
- */
-size_t for_each_line(char *buf, size_t n, void (*line_handler)(char *))
-{
- char *start = buf, *end;
- size_t num_lines = 0, bytes_left = n;
-
-// PARA_INFO_LOG("buf: %s", buf);
- while (bytes_left) {
- char *next_null;
- char *next_cr;
-
- next_cr = memchr(start, '\n', bytes_left);
- next_null = memchr(start, '\0', bytes_left);
- if (!next_cr && !next_null)
- break;
- if (next_cr && next_null) {
- end = next_cr < next_null? next_cr : next_null;
- } else if (next_null) {
- end = next_null;
- } else
- end = next_cr;
- num_lines++;
- if (line_handler) {
- *end = '\0';
-// PARA_INFO_LOG("calling line handler: %s\n", start);
- line_handler(start);
- }
- start = ++end;
- bytes_left = buf + n - start;
- }
- if (!line_handler)
- return num_lines;
- if (bytes_left && bytes_left != n)
- memmove(buf, start, bytes_left);
- return bytes_left;
-}
-
uname(&u);
return para_strdup(u.nodename);
}
+
+enum for_each_line_modes{LINE_MODE_RO, LINE_MODE_RW};
+
+static int for_each_complete_line(enum for_each_line_modes mode, char *buf,
+ size_t size, line_handler_t *line_handler, void *private_data)
+{
+ char *start = buf, *end;
+ int ret, i, num_lines = 0;
+
+// PARA_NOTICE_LOG("buf: %s\n", buf);
+ while (start < buf + size) {
+ char *next_null;
+ char *next_cr;
+
+ next_cr = memchr(start, '\n', buf + size - start);
+ next_null = memchr(start, '\0', buf + size - start);
+ if (!next_cr && !next_null)
+ break;
+ if (next_cr && next_null) {
+ end = next_cr < next_null? next_cr : next_null;
+ } else if (next_null) {
+ end = next_null;
+ } else
+ end = next_cr;
+ num_lines++;
+ if (!line_handler) {
+ start = ++end;
+ continue;
+ }
+ if (mode == LINE_MODE_RO) {
+ size_t s = end - start;
+ char *b = para_malloc(s + 1);
+ memcpy(b, start, s);
+ b[s] = '\0';
+// PARA_NOTICE_LOG("b: %s, start: %s\n", b, start);
+ ret = line_handler(b, private_data);
+ free(b);
+ } else {
+ *end = '\0';
+ ret = line_handler(start, private_data);
+ }
+ if (ret < 0)
+ return ret;
+ start = ++end;
+ }
+ if (!line_handler || mode == LINE_MODE_RO)
+ return num_lines;
+ i = buf + size - start;
+ if (i && i != size)
+ memmove(buf, start, i);
+ return i;
+}
+
+/**
+ * call a custom function for each complete line
+ *
+ * \param buf the buffer containing data seperated by newlines
+ * \param size the number of bytes in \a buf
+ * \param line_handler the custom function
+ * \param private_data pointer to data which is passed to \a line_handler
+ *
+ * If \p line_handler is \p NULL, the function returns the number of complete
+ * lines in \p buf. Otherwise, \p line_handler is called for each complete
+ * line in \p buf. The first argument to \p line_handler is the current line,
+ * and \p private_data is passed as the second argument. The function returns
+ * if \p line_handler returns a negative value or no more lines are in the
+ * buffer. The rest of the buffer (last chunk containing an incomplete line)
+ * is moved to the beginning of the buffer.
+ *
+ * \return If \p line_handler is not \p NULL, this function returns the number
+ * of bytes not handled to \p line_handler on success, or the negative return
+ * value of the \p line_handler on errors.
+ *
+ * \sa for_each_line_ro()
+ */
+int for_each_line(char *buf, size_t size, line_handler_t *line_handler,
+ void *private_data)
+{
+ return for_each_complete_line(LINE_MODE_RW, buf, size, line_handler,
+ private_data);
+}
+
+/**
+ * call a custom function for each complete line
+ *
+ * \param buf same meaning as in \p for_each_line
+ * \param size same meaning as in \p for_each_line
+ * \param line_handler same meaning as in \p for_each_line
+ * \param private_data same meaning as in \p for_each_line
+ *
+ * This function behaves like \p for_each_line() with the following differences:
+ *
+ * - buf is left unchanged
+ *
+ * \return On success, the function returns the number of complete lines in \p
+ * buf, otherwise the (negative) return value of \p line_handler is returned.
+ *
+ * \sa for_each_line()
+ */
+int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler,
+ void *private_data)
+{
+ return for_each_complete_line(LINE_MODE_RO, buf, size, line_handler,
+ private_data);
+}
+
+/**
+ * Safely print into a buffer at a given offset
+ *
+ * \param b Determines the buffer, its size, and the offset.
+ * \param fmt The format string.
+ *
+ * This function prints into the buffer given by \a b at the offset which is
+ * also given by \a b. If there is not enough space to hold the result, the
+ * buffer size is doubled until the underlying call to vsnprintf() succeeds.
+ * Upon return, the offset of \a b is adjusted accordingly so that subsequent
+ * calls to this function append data to what is already contained in the
+ * buffer.
+ *
+ * It's OK to call this function with \p b->buf being \p NULL. In this case, an
+ * initial buffer is allocated.
+ *
+ * \return The number of bytes printed into the buffer (not including the
+ * therminating \p NULL byte).
+ *
+ * \sa make_message(), vsnprintf(3).
+ */
+__printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...)
+{
+ int ret;
+
+ if (!b->buf) {
+ b->buf = para_malloc(128);
+ b->size = 128;
+ b->offset = 0;
+ } else if (b->size <= b->offset + 1) {
+ b->size *= 2;
+ b->buf = para_realloc(b->buf, b->size);
+ }
+ while (1) {
+ char *p = b->buf + b->offset;
+ size_t size = b->size - b->offset;
+ va_list ap;
+ va_start(ap, fmt);
+ ret = vsnprintf(p, size, fmt, ap);
+ va_end(ap);
+ if (ret > -1 && ret < size) { /* success */
+ b->offset += ret;
+ break;
+ }
+ /* try again with more space */
+ b->size *= 2;
+ b->buf = para_realloc(b->buf, b->size);
+ }
+ return ret;
+}
/** \file string.h exported sybmols from string.c */
+struct para_buffer {
+ char *buf;
+ size_t size;
+ size_t offset;
+};
+
__must_check __malloc void *para_realloc(void *p, size_t size);
__must_check __malloc void *para_malloc(size_t size);
__must_check __malloc void *para_calloc(size_t size);
__must_check unsigned split_args(char *args, char ***argv_ptr, const char *delim);
__malloc char *para_hostname(void);
void valid_fd_012(void);
+__printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...);
+typedef int line_handler_t(char *, void *);
+int for_each_line(char *buf, size_t size, line_handler_t *line_handler,
+ void *private_data);
+int for_each_line_ro(char *buf, size_t size, line_handler_t line_handler,
+ void *private_data);