para_write: Return proper error code.
[paraslash.git] / aft.c
diff --git a/aft.c b/aft.c
index db4c494d07bcd7c759de9708d1f2c30d4ba278aa..266188b5b370a3e7e3e8aaa092ca093aca7c6c04 100644 (file)
--- a/aft.c
+++ b/aft.c
@@ -1,29 +1,33 @@
 /*
- * Copyright (C) 2007-2009 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2007-2011 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
 /** \file aft.c Audio file table functions. */
 
+#include <regex.h>
 #include <dirent.h> /* readdir() */
-#include <osl.h>
-#include "para.h"
-#include "error.h"
-#include "string.h"
+#include <openssl/rc4.h>
 #include <sys/mman.h>
 #include <fnmatch.h>
 #include <sys/shm.h>
+#include <osl.h>
 
+#include "para.h"
+#include "error.h"
+#include "crypt.h"
+#include "string.h"
 #include "afh.h"
 #include "afs.h"
 #include "net.h"
-#include "vss.h"
 #include "fd.h"
 #include "ipc.h"
 #include "portable_io.h"
 
 static struct osl_table *audio_file_table;
+static char *status_items;
+static char *parser_friendly_status_items;
 
 /** The different sorting methods of the ls command. */
 enum ls_sorting_method {
@@ -64,7 +68,9 @@ enum ls_listing_mode {
        /** -lm */
        LS_MODE_MBOX,
        /** -lc */
-       LS_MODE_CHUNKS
+       LS_MODE_CHUNKS,
+       /** -lp */
+       LS_MODE_PARSER,
 };
 
 /** The flags accepted by the ls command. */
@@ -75,6 +81,8 @@ enum ls_flags {
        LS_FLAG_ADMISSIBLE_ONLY = 2,
        /** -r */
        LS_FLAG_REVERSE = 4,
+       /** -d */
+       LS_FLAG_UNIXDATE = 8,
 };
 
 /**
@@ -229,7 +237,7 @@ enum audio_file_table_columns {
  *
  * \sa osl_compare_func, uint32_compare().
  */
-int aft_hash_compare(const struct osl_object *obj1, const struct osl_object *obj2)
+static int aft_hash_compare(const struct osl_object *obj1, const struct osl_object *obj2)
 {
        return hash_compare((HASH_TYPE *)obj1->data, (HASH_TYPE *)obj2->data);
 }
@@ -336,54 +344,66 @@ enum afhi_offsets {
        AFHI_BITRATE_OFFSET = 4,
        /** Position of the frequency. */
        AFHI_FREQUENCY_OFFSET = 8,
-       /** Location of the audio file header. */
-       AFHI_HEADER_OFFSET_OFFSET = 12,
+       /** Was: Location of the audio file header. */
+       AFHI_UNUSED1_OFFSET = 12,
        /* Length of the audio file header. Zero means: No header. */
        AFHI_HEADER_LEN_OFFSET = 16,
        /** The total number of chunks (4 bytes). */
        CHUNKS_TOTAL_OFFSET = 20,
        /** The length of the audio file header (4 bytes). */
        HEADER_LEN_OFFSET = 24,
-       /** The start of the audio file header (4 bytes). */
-       HEADER_OFFSET_OFFSET = 28,
+       /** Was: The start of the audio file header (4 bytes). */
+       AFHI_UNUSED2_OFFSET = 28,
        /** The seconds part of the chunk time (4 bytes). */
        CHUNK_TV_TV_SEC_OFFSET = 32,
        /** The microseconds part of the chunk time (4 bytes). */
        CHUNK_TV_TV_USEC_OFFSET = 36,
        /** Number of channels is stored here. (1 byte) */
        AFHI_CHANNELS_OFFSET = 40,
-       /** EOF timeout in ms. (2 byte) */
-       AFHI_EOF_OFFSET = 41,
        /** The tag info position. */
-       AFHI_INFO_STRING_OFFSET = 43,
+       AFHI_INFO_STRING_OFFSET = 41,
        /** Minimal on-disk size of a valid afhi struct. */
-       MIN_AFHI_SIZE = 44
+       MIN_AFHI_SIZE = 47, /* at least 6 null bytes for techinfo/tags */
 };
 
 static unsigned sizeof_afhi_buf(const struct afh_info *afhi)
 {
        if (!afhi)
                return 0;
-       return strlen(afhi->info_string) + MIN_AFHI_SIZE;
+       return MIN_AFHI_SIZE
+               + strlen(afhi->techinfo)
+               + strlen(afhi->tags.artist)
+               + strlen(afhi->tags.title)
+               + strlen(afhi->tags.year)
+               + strlen(afhi->tags.album)
+               + strlen(afhi->tags.comment);
 }
 
 static void save_afhi(struct afh_info *afhi, char *buf)
 {
+       char *p;
+
        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_u32(buf + AFHI_HEADER_OFFSET_OFFSET, afhi->header_offset);
+       write_u32(buf + AFHI_UNUSED1_OFFSET, 0);
        write_u32(buf + AFHI_HEADER_LEN_OFFSET, afhi->header_len);
        write_u8(buf + AFHI_CHANNELS_OFFSET, afhi->channels);
        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 + AFHI_UNUSED2_OFFSET, 0);
        write_u32(buf + CHUNK_TV_TV_SEC_OFFSET, afhi->chunk_tv.tv_sec);
        write_u32(buf + CHUNK_TV_TV_USEC_OFFSET, afhi->chunk_tv.tv_usec);
-       write_u16(buf + AFHI_EOF_OFFSET, tv2ms(&afhi->eof_tv));
-       strcpy(buf + AFHI_INFO_STRING_OFFSET, afhi->info_string); /* OK */
+       p = buf + AFHI_INFO_STRING_OFFSET;
+       /* The sprintf's below are OK as our caller made sure that buf is large enough */
+       p += sprintf(p, "%s", afhi->techinfo) + 1;
+       p += sprintf(p, "%s", afhi->tags.artist) + 1;
+       p += sprintf(p, "%s", afhi->tags.title) + 1;
+       p += sprintf(p, "%s", afhi->tags.year) + 1;
+       p += sprintf(p, "%s", afhi->tags.album) + 1;
+       sprintf(p, "%s", afhi->tags.comment);
 }
 
 static void load_afhi(const char *buf, struct afh_info *afhi)
@@ -391,16 +411,18 @@ static void load_afhi(const char *buf, struct afh_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->header_offset = read_u32(buf + AFHI_HEADER_OFFSET_OFFSET);
        afhi->header_len = read_u32(buf + AFHI_HEADER_LEN_OFFSET);
        afhi->channels = read_u8(buf + AFHI_CHANNELS_OFFSET);
        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_OFFSET);
-       ms2tv(read_u16(buf + AFHI_EOF_OFFSET), &afhi->eof_tv);
-       afhi->info_string = para_strdup(buf + AFHI_INFO_STRING_OFFSET);
+       afhi->techinfo = (char *)buf + AFHI_INFO_STRING_OFFSET;
+       afhi->tags.artist = afhi->techinfo + strlen(afhi->techinfo) + 1;
+       afhi->tags.title = afhi->tags.artist + strlen(afhi->tags.artist) + 1;
+       afhi->tags.year = afhi->tags.title + strlen(afhi->tags.title) + 1;
+       afhi->tags.album = afhi->tags.year + strlen(afhi->tags.year) + 1;
+       afhi->tags.comment = afhi->tags.album + strlen(afhi->tags.album) + 1;
 }
 
 static unsigned sizeof_chunk_table(struct afh_info *afhi)
@@ -410,12 +432,26 @@ static unsigned sizeof_chunk_table(struct afh_info *afhi)
        return 4 * (afhi->chunks_total + 1);
 }
 
-static void save_chunk_table(struct afh_info *afhi, char *buf)
+static uint32_t save_chunk_table(struct afh_info *afhi, char *buf)
 {
        int i;
-
-       for (i = 0; i <= afhi->chunks_total; i++)
-               write_u32(buf + 4 * i, afhi->chunk_table[i]);
+       uint32_t max = 0, old = 0;
+
+       for (i = 0; i <= afhi->chunks_total; i++) {
+               uint32_t val = afhi->chunk_table[i];
+               write_u32(buf + 4 * i, val);
+               /*
+                * If the first chunk is the header, do not consider it for the
+                * calculation of the largest chunk size.
+                */
+               if (i == 0 || (i == 1 && afhi->header_len > 0)) {
+                       old = val;
+                       continue;
+               }
+               max = PARA_MAX(max, val - old);
+               old = val;
+       }
+       return max;
 }
 
 static void load_chunk_table(struct afh_info *afhi, char *buf)
@@ -446,11 +482,11 @@ int aft_get_row_of_path(const char *path, struct osl_row **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.
+ * \param row Result pointer.
  *
  * \return Standard.
  */
-int aft_get_row_of_hash(HASH_TYPE *hash, struct osl_row **row)
+static 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(osl_get_row(audio_file_table, AFTCOL_HASH, &obj, row));
@@ -478,7 +514,7 @@ int get_afsi_object_of_row(const struct osl_row *row, struct osl_object *obj)
  *
  * \return Positive on success, negative on errors.
  */
-int get_afsi_object_of_path(const char *path, struct osl_object *obj)
+static int get_afsi_object_of_path(const char *path, struct osl_object *obj)
 {
        struct osl_row *row;
        int ret = aft_get_row_of_path(path, &row);
@@ -616,10 +652,10 @@ static int save_afd(struct audio_file_data *afd)
        ret = shm_attach(shmid, ATTACH_RW, &shm_afd);
        if (ret < 0)
                goto err;
-       *(struct audio_file_data *)shm_afd = *afd;
        buf = shm_afd;
        buf += sizeof(*afd);
-       save_chunk_table(&afd->afhi, buf);
+       afd->max_chunk_size = save_chunk_table(&afd->afhi, buf);
+       *(struct audio_file_data *)shm_afd = *afd;
        shm_detach(shm_afd);
        return shmid;
 err:
@@ -627,6 +663,18 @@ err:
        return ret;
 }
 
+/**
+ * Extract a afd stored in a shared memory area.
+ *
+ * Attach the shared memory area given by \a shmid, load the audio file data
+ * stored therein and detach the area afterwards.  Called by vss, after
+ * receiving a positive response to the request for the next audio file.
+ +
+ * \param shmid The identifier of the shared memory area containing the afd.
+ * \param afd Result pointer.
+ *
+ * \return Standard.
+ */
 int load_afd(int shmid, struct audio_file_data *afd)
 {
        void *shm_afd;
@@ -706,56 +754,68 @@ static void get_duration_buf(int seconds, char *buf, struct ls_options *opts)
        }
 }
 
-static char *make_attribute_lines(const char *att_bitmap, struct afs_info *afsi)
+
+static int write_attribute_items(struct para_buffer *b,
+               const char *att_bitmap, struct afs_info *afsi)
 {
-       char *att_text, *att_lines;
+       char *att_text;
+       int ret;
 
-       get_attribute_text(&afsi->attributes, " ", &att_text);
-       if (!att_text)
-               return para_strdup(att_bitmap);
-       att_lines = make_message("%s: %s\n%s: %s",
-               status_item_list[SI_ATTRIBUTES_BITMAP], att_bitmap,
-               status_item_list[SI_ATTRIBUTES_TXT], att_text);
+       ret = WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_BITMAP, "%s\n", att_bitmap);
+       if (ret < 0)
+               return ret;
+       ret = get_attribute_text(&afsi->attributes, " ", &att_text);
+       if (ret < 0)
+               return ret;
+       ret = WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_TXT, "%s\n", att_text);
        free(att_text);
-       return att_lines;
+       return ret;
 }
 
-static char *make_lyrics_lines(struct afs_info *afsi)
+static int write_lyrics_items(struct para_buffer *b, struct afs_info *afsi)
 {
        char *lyrics_name;
+       int ret;
 
+       ret = WRITE_STATUS_ITEM(b, SI_LYRICS_ID, "%u\n", afsi->lyrics_id);
+       if (ret < 0)
+               return ret;
        lyr_get_name_by_id(afsi->lyrics_id, &lyrics_name);
-       return make_message("%s: %u\n%s: %s\n",
-               status_item_list[SI_LYRICS_ID], afsi->lyrics_id,
-               status_item_list[SI_LYRICS_NAME], lyrics_name?
-                       lyrics_name : "(none)");
+       return WRITE_STATUS_ITEM(b, SI_LYRICS_NAME, "%s\n", lyrics_name?
+               lyrics_name : "(none)");
 }
 
-static char *make_image_lines(struct afs_info *afsi)
+static int write_image_items(struct para_buffer *b, struct afs_info *afsi)
 {
        char *image_name;
+       int ret;
+
+       ret = WRITE_STATUS_ITEM(b, SI_IMAGE_ID, "%u\n", afsi->image_id);
+       if (ret < 0)
+               return ret;
        img_get_name_by_id(afsi->image_id, &image_name);
-       return make_message("%s: %u\n%s: %s\n",
-               status_item_list[SI_IMAGE_ID], afsi->image_id,
-               status_item_list[SI_IMAGE_NAME], image_name?
-                       image_name : "(none)");
+       return WRITE_STATUS_ITEM(b, SI_IMAGE_NAME, "%s\n", image_name?
+               image_name : "(none)");
 }
 
-static char *make_filename_lines(const char *path, unsigned flags)
+static int write_filename_items(struct para_buffer *b, const char *path,
+               unsigned flags)
 {
-       char *dirname, *ret;
-       const char *basename;
+       char *val;
+       int ret;
 
        if (!(flags & LS_FLAG_FULL_PATH))
-               return make_message("%s: %s\n",
-                       status_item_list[SI_BASENAME], path);
-       basename = para_basename(path),
-       dirname = para_dirname(path);
-       ret = make_message("%s: %s\n%s: %s\n%s: %s\n",
-               status_item_list[SI_PATH], path,
-               status_item_list[SI_DIRECTORY], dirname? dirname : "?",
-               status_item_list[SI_BASENAME], basename? basename : "?");
-       free(dirname);
+               return WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", path);
+       ret = WRITE_STATUS_ITEM(b, SI_PATH, "%s\n", path);
+       if (ret < 0)
+               return ret;
+       val = para_basename(path);
+       ret = WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", val? val : "");
+       if (ret < 0)
+               return ret;
+       val = para_dirname(path);
+       ret = WRITE_STATUS_ITEM(b, SI_DIRECTORY, "%s\n", val? val : "");
+       free(val);
        return ret;
 }
 
@@ -793,6 +853,14 @@ out:
        return ret;
 }
 
+static int write_score(struct para_buffer *b, struct ls_data *d,
+               struct ls_options *opts)
+{
+       if (!(opts->flags & LS_FLAG_ADMISSIBLE_ONLY)) /* no score*/
+               return 0;
+       return WRITE_STATUS_ITEM(b, SI_SCORE, "%li\n", d->score);
+}
+
 static int print_list_item(struct ls_data *d, struct ls_options *opts,
        struct para_buffer *b, time_t current_time)
 {
@@ -800,13 +868,9 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
        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 afh_info *afhi = &d->afhi;
-       struct ls_widths *w = &opts->widths;
-       int have_score = opts->flags & LS_FLAG_ADMISSIBLE_ONLY;
        char asc_hash[2 * HASH_SIZE + 1];
-       char *att_lines, *lyrics_lines, *image_lines, *filename_lines;
 
        if (opts->mode == LS_MODE_SHORT) {
                ret = para_printf(b, "%s\n", d->path);
@@ -817,21 +881,25 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                goto out;
        }
        get_attribute_bitmap(&afsi->attributes, att_buf);
-       ret = get_local_time(&afsi->last_played, last_played_time,
-               sizeof(last_played_time), current_time, opts->mode);
-       if (ret < 0)
-               goto out;
-       get_duration_buf(afhi->seconds_total, duration_buf, opts);
-       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->flags & LS_FLAG_UNIXDATE)
+               sprintf(last_played_time, "%llu",
+                       (long long unsigned)afsi->last_played);
+       else {
+               ret = get_local_time(&afsi->last_played, last_played_time,
+                       sizeof(last_played_time), current_time, opts->mode);
+               if (ret < 0)
+                       goto out;
        }
-
+       get_duration_buf(afhi->seconds_total, duration_buf, opts);
        if (opts->mode == LS_MODE_LONG) {
+               struct ls_widths *w = &opts->widths;
+               if (opts->flags & LS_FLAG_ADMISSIBLE_ONLY) {
+                       ret = para_printf(b, "%*li ",
+                               opts->widths.score_width, d->score);
+                       if (ret < 0)
+                               goto out;
+               }
                ret = para_printf(b,
-                       "%s"    /* score */
                        "%s "   /* attributes */
                        "%*u "  /* amp */
                        "%*d "  /* image_id  */
@@ -844,7 +912,6 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                        "%*d "  /* num_played */
                        "%s "   /* last_played */
                        "%s\n", /* path */
-                       score_buf,
                        att_buf,
                        w->amp_width, afsi->amp,
                        w->image_id_width, afsi->image_id,
@@ -860,11 +927,6 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                );
                goto out;
        }
-       hash_to_asc(d->hash, asc_hash);
-       att_lines = make_attribute_lines(att_buf, afsi);
-       lyrics_lines = make_lyrics_lines(afsi);
-       image_lines = make_image_lines(afsi);
-       filename_lines = make_filename_lines(d->path, opts->flags);
        if (opts->mode == LS_MODE_MBOX) {
                const char *bn = para_basename(d->path);
                ret = para_printf(b,
@@ -876,47 +938,78 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                if (ret < 0)
                        goto out;
        }
-       ret = para_printf(b,
-               "%s" /* filename stuff */
-               "%s%s%s%s" /* score */
-               "%s\n" /* attributes */
-               "%s: %s\n" /* hash */
-               "%s" /* image id, image name */
-               "%s" /* lyrics */
-               "%s: %dkbit/s\n" /* bitrate */
-               "%s: %s\n" /* format */
-               "%s: %dHz\n" /* frequency */
-               "%s: %d\n" /* channels */
-               "%s: %s\n" /* duration */
-               "%s: %lu\n" /* seconds total */
-               "%s: %s\n" /* last played time */
-               "%s: %d\n" /* num_played */
-               "%s: %u\n" /* ampplification */
-               "%s" /* tag info */
-               "%s: %lu\n" /* chunk time */
-               "%s: %lu\n", /* num chunks */
-               filename_lines,
-               have_score? status_item_list[SI_SCORE] : "",
-                       have_score? ": " : "",
-                       score_buf,
-                       have_score? "\n" : "",
-               att_lines,
-               status_item_list[SI_HASH], asc_hash,
-               image_lines,
-               lyrics_lines,
-               status_item_list[SI_BITRATE], afhi->bitrate,
-               status_item_list[SI_FORMAT], audio_format_name(afsi->audio_format_id),
-               status_item_list[SI_FREQUENCY], afhi->frequency,
-               status_item_list[SI_CHANNELS], afhi->channels,
-               status_item_list[SI_DURATION], duration_buf,
-               status_item_list[SI_SECONDS_TOTAL], afhi->seconds_total,
-               status_item_list[SI_LAST_PLAYED], last_played_time,
-               status_item_list[SI_NUM_PLAYED], afsi->num_played,
-               status_item_list[SI_AMPLIFICATION], afsi->amp,
-               afhi->info_string,
-               status_item_list[SI_CHUNK_TIME], tv2ms(&afhi->chunk_tv),
-               status_item_list[SI_NUM_CHUNKS], afhi->chunks_total
-       );
+       ret = write_filename_items(b, d->path, opts->flags);
+       if (ret < 0)
+               goto out;
+       ret = write_score(b, d, opts);
+       if (ret < 0)
+               goto out;
+       ret = write_attribute_items(b, att_buf, afsi);
+       if (ret < 0)
+               goto out;
+       ret = write_image_items(b, afsi);
+       if (ret < 0)
+               goto out;
+       ret = write_lyrics_items(b, afsi);
+       if (ret < 0)
+               goto out;
+       hash_to_asc(d->hash, asc_hash);
+       ret = WRITE_STATUS_ITEM(b, SI_HASH, "%s\n", asc_hash);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_BITRATE, "%dkbit/s\n", afhi->bitrate);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_FORMAT, "%s\n",
+               audio_format_name(afsi->audio_format_id));
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_FREQUENCY, "%dHz\n", afhi->frequency);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_CHANNELS, "%d\n", afhi->channels);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_DURATION, "%s\n", duration_buf);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_SECONDS_TOTAL, "%lu\n",
+               afhi->seconds_total);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_LAST_PLAYED, "%s\n", last_played_time);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_NUM_PLAYED, "%d\n", afsi->num_played);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_AMPLIFICATION, "%u\n", afsi->amp);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_CHUNK_TIME, "%lu\n",
+               tv2ms(&afhi->chunk_tv));
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_NUM_CHUNKS, "%lu\n",
+               afhi->chunks_total);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_TECHINFO, "%s\n", afhi->techinfo);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_ARTIST, "%s\n", afhi->tags.artist);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_TITLE, "%s\n", afhi->tags.title);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_YEAR, "%s\n", afhi->tags.year);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_ALBUM, "%s\n", afhi->tags.album);
+       if (ret < 0)
+               goto out;
+       ret = WRITE_STATUS_ITEM(b, SI_COMMENT, "%s\n", afhi->tags.comment);
        if (ret < 0)
                goto out;
        if (opts->mode == LS_MODE_MBOX) {
@@ -928,129 +1021,10 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts,
                        osl_close_disk_object(&lyrics_def);
                }
        }
-       free(att_lines);
-       free(lyrics_lines);
-       free(image_lines);
-       free(filename_lines);
 out:
-       free(afhi->info_string);
        return ret;
 }
 
-/**
- * Write a list of audio-file related status items with empty values.
- *
- * \param buf Result pointer.
- *
- * This is used by vss when currently no audio file is open.
- */
-void make_empty_status_items(char *buf)
-{
-       sprintf(buf,
-               "%s: \n" /* path */
-               "%s: \n" /* dirname */
-               "%s: \n" /* basename */
-               "%s: \n" /* score */
-               "%s: \n" /* attributes bitmap */
-               "%s: \n" /* attributes txt */
-               "%s: \n" /* hash */
-               "%s: \n" /* image id */
-               "%s: \n" /* image name */
-               "%s: \n" /* lyrics id */
-               "%s: \n" /* lyrics name */
-               "%s: \n" /* bitrate */
-               "%s: \n" /* format */
-               "%s: \n" /* frequency */
-               "%s: \n" /* channels */
-               "%s: \n" /* duration */
-               "%s: \n" /* seconds total */
-               "%s: \n" /* num played */
-               "%s: \n" /* last played */
-               "%s: \n" /* audio file info */
-               "%s: \n" /* taginfo1 */
-               "%s: \n" /* taginfo2 */
-               "%s: \n" /* amplification */
-               ,
-               status_item_list[SI_PATH],
-               status_item_list[SI_DIRECTORY],
-               status_item_list[SI_BASENAME],
-               status_item_list[SI_SCORE],
-               status_item_list[SI_ATTRIBUTES_BITMAP],
-               status_item_list[SI_ATTRIBUTES_TXT],
-               status_item_list[SI_HASH],
-               status_item_list[SI_IMAGE_ID],
-               status_item_list[SI_IMAGE_NAME],
-               status_item_list[SI_LYRICS_ID],
-               status_item_list[SI_LYRICS_NAME],
-               status_item_list[SI_BITRATE],
-               status_item_list[SI_FORMAT],
-               status_item_list[SI_FREQUENCY],
-               status_item_list[SI_CHANNELS],
-               status_item_list[SI_DURATION],
-               status_item_list[SI_SECONDS_TOTAL],
-               status_item_list[SI_NUM_PLAYED],
-               status_item_list[SI_LAST_PLAYED],
-               status_item_list[SI_AUDIO_FILE_INFO],
-               status_item_list[SI_TAGINFO1],
-               status_item_list[SI_TAGINFO2],
-               status_item_list[SI_AMPLIFICATION]
-       );
-}
-
-static void fixup_taginfo(char *begin, char *end)
-{
-       char *p = begin;
-
-       for (;;) {
-               p = strchr(p, '\n');
-               if (!p)
-                       break;
-               if (p >= end - 1)
-                       break;
-               *p = ' ';
-               p++;
-       }
-}
-
-/* crap, remove this ASAP. */
-static int fixup_info_string(char *info_string)
-{
-       char *t1, *t2, *end;
-
-       if (strncmp(info_string, "audio_file_info:", 16))
-               return -ERRNO_TO_PARA_ERROR(EINVAL);
-       t1 = strstr(info_string, "\ntaginfo1:");
-       if (!t1)
-               return -ERRNO_TO_PARA_ERROR(EINVAL);
-       t2 = strstr(info_string, "\ntaginfo2: ");
-       if (!t2)
-               return -ERRNO_TO_PARA_ERROR(EINVAL);
-
-       end = t2 + strlen(t2) + 1;
-       fixup_taginfo(info_string + 16, t1);
-       fixup_taginfo(t1 + 10, t2);
-       fixup_taginfo(t2 + 10, end);
-
-       if (t1 - info_string < 80 && t2 - t1 < 80 && end - t2 < 80)
-               return 0;
-       if (t1 - info_string >= 80) {
-               memmove(info_string + 80, t1, end - t1);
-               t1 = info_string + 80;
-               t2 -= t1 - info_string - 80;
-               end -= t1 - info_string - 80;
-       }
-       if (t2 - t1 >= 80) {
-               memmove(t1 + 80, t2, end - t2);
-               end -= t2 - t1 - 80;
-               t2 = t1 + 80;
-       }
-       if (end - t2 >= 80) {
-               t2[78] = '\n';
-               t2[79] = '\0';
-       }
-       return 1;
-}
-
 static int make_status_items(struct audio_file_data *afd,
                struct afs_info *afsi, char *path, long score,
                HASH_TYPE *hash)
@@ -1066,26 +1040,28 @@ static int make_status_items(struct audio_file_data *afd,
                .flags = LS_FLAG_FULL_PATH | LS_FLAG_ADMISSIBLE_ONLY,
                .mode = LS_MODE_VERBOSE,
        };
-       struct para_buffer pb = {.max_size = VERBOSE_LS_OUTPUT_SIZE - 1};
+       struct para_buffer pb = {.max_size = SHMMAX - 1};
        time_t current_time;
        int ret;
 
-       ret = fixup_info_string(afd->afhi.info_string);
-       if (ret < 0) {
-               PARA_WARNING_LOG("ignoring invalid tag info\n");
-               afd->afhi.info_string[0] = '\0';
-       } else if (ret)
-               PARA_NOTICE_LOG("truncated overlong tag info\n");
        time(&current_time);
-       ret = print_list_item(&d, &opts, &pb, current_time); /* frees info string */
-       afd->afhi.info_string = NULL;
+       ret = print_list_item(&d, &opts, &pb, current_time);
        if (ret < 0)
-               goto out;
-       strncpy(afd->verbose_ls_output, pb.buf, VERBOSE_LS_OUTPUT_SIZE);
-       afd->verbose_ls_output[VERBOSE_LS_OUTPUT_SIZE - 1] = '\0';
-out:
-       free(pb.buf);
-       return ret;
+               return ret;
+       free(status_items);
+       status_items = pb.buf;
+       memset(&pb, 0, sizeof(pb));
+       pb.max_size = SHMMAX - 1;
+       pb.flags = PBF_SIZE_PREFIX;
+       ret = print_list_item(&d, &opts, &pb, current_time);
+       if (ret < 0) {
+               free(status_items);
+               status_items = NULL;
+               return ret;
+       }
+       free(parser_friendly_status_items);
+       parser_friendly_status_items = pb.buf;
+       return 1;
 }
 
 /**
@@ -1117,6 +1093,7 @@ int open_and_update_audio_file(struct osl_row *aft_row, long score,
        ret = get_audio_file_path_of_row(aft_row, &path);
        if (ret < 0)
                return ret;
+       PARA_NOTICE_LOG("%s\n", path);
        ret = get_afsi_object_of_row(aft_row, &afsi_obj);
        if (ret < 0)
                return ret;
@@ -1147,6 +1124,7 @@ int open_and_update_audio_file(struct osl_row *aft_row, long score,
        new_afsi.last_played = time(NULL);
        save_afsi(&new_afsi, &afsi_obj); /* in-place update */
 
+       afd->audio_format_id = old_afsi.audio_format_id;
        load_chunk_table(&afd->afhi, chunk_table_obj.data);
        ret = make_status_items(afd, &old_afsi, path, score, file_hash);
        if (ret < 0)
@@ -1157,7 +1135,6 @@ int open_and_update_audio_file(struct osl_row *aft_row, long score,
        ret = save_afd(afd);
 err:
        free(afd->afhi.chunk_table);
-       free(afd->afhi.info_string);
        osl_close_disk_object(&chunk_table_obj);
        return ret;
 }
@@ -1360,7 +1337,6 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts)
        }
        return 1;
 err:
-       free(d->afhi.info_string);
        return ret;
 }
 
@@ -1369,11 +1345,11 @@ static void com_ls_callback(int fd, const struct osl_object *query)
        struct ls_options *opts = query->data;
        char *p, *pattern_start = (char *)query->data + sizeof(*opts);
        struct para_buffer b = {.max_size = SHMMAX,
+               .flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0,
                .max_size_handler = pass_buffer_as_shm, .private_data = &fd};
        int i = 0, ret;
        time_t current_time;
 
-
        if (opts->num_patterns) {
                opts->patterns = para_malloc(opts->num_patterns * sizeof(char *));
                for (i = 0, p = pattern_start; i < opts->num_patterns; i++) {
@@ -1419,7 +1395,7 @@ out:
 /*
  * TODO: flags -h (sort by hash)
  */
-int com_ls(int fd, int argc, char * const * const argv)
+int com_ls(struct rc4_context *rc4c, int argc, char * const * const argv)
 {
        int i, ret;
        unsigned flags = 0;
@@ -1459,6 +1435,9 @@ int com_ls(int fd, int argc, char * const * const argv)
                        case 'c':
                                mode = LS_MODE_CHUNKS;
                                continue;
+                       case 'p':
+                               mode = LS_MODE_PARSER;
+                               continue;
                        default:
                                return -E_AFT_SYNTAX;
                        }
@@ -1475,6 +1454,10 @@ int com_ls(int fd, int argc, char * const * const argv)
                        flags |= LS_FLAG_REVERSE;
                        continue;
                }
+               if (!strcmp(arg, "-d")) {
+                       flags |= LS_FLAG_UNIXDATE;
+                       continue;
+               }
                if (!strncmp(arg, "-s", 2)) {
                        if (!*(arg + 2) || *(arg + 3))
                                return -E_AFT_SYNTAX;
@@ -1524,7 +1507,7 @@ int com_ls(int fd, int argc, char * const * const argv)
        opts.mode = mode;
        opts.num_patterns = argc - i;
        ret = send_option_arg_callback_request(&query, opts.num_patterns,
-               argv + i, com_ls_callback, send_result, &fd);
+               argv + i, com_ls_callback, rc4_send_result, rc4c);
        return ret;
 }
 
@@ -1811,8 +1794,8 @@ out:
 
 /** Used by com_add(). */
 struct private_add_data {
-       /** The socket file descriptor. */
-       int fd;
+       /** The socket file descriptor, including rc4 keys. */
+       struct rc4_context *rc4c;
        /** The given add flags. */
        uint32_t flags;
 };
@@ -1867,7 +1850,8 @@ static int add_one_audio_file(const char *path, void *private_data)
        ret = 1;
        if (pb && (pad->flags & ADD_FLAG_LAZY)) { /* lazy is really cheap */
                if (pad->flags & ADD_FLAG_VERBOSE)
-                       send_ret = send_va_buffer(pad->fd, "lazy-ignore: %s\n", path);
+                       send_ret = rc4_send_va_buffer(pad->rc4c,
+                               "lazy-ignore: %s\n", path);
                goto out_free;
        }
        /* We still want to add this file. Compute its hash. */
@@ -1887,7 +1871,7 @@ static int add_one_audio_file(const char *path, void *private_data)
        ret = 1;
        if (pb && hs && hs == pb && !(pad->flags & ADD_FLAG_FORCE)) {
                if (pad->flags & ADD_FLAG_VERBOSE)
-                       send_ret = send_va_buffer(pad->fd,
+                       send_ret = rc4_send_va_buffer(pad->rc4c,
                                "%s exists, not forcing update\n", path);
                goto out_unmap;
        }
@@ -1905,13 +1889,13 @@ static int add_one_audio_file(const char *path, void *private_data)
        munmap(map.data, map.size);
        close(fd);
        if (pad->flags & ADD_FLAG_VERBOSE) {
-               send_ret = send_va_buffer(pad->fd, "adding %s\n", path);
+               send_ret = rc4_send_va_buffer(pad->rc4c, "adding %s\n", path);
                if (send_ret < 0)
                        goto out_free;
        }
        save_add_callback_buffer(hash, path, afhi_ptr, pad->flags, format_num, &obj);
        /* Ask afs to consider this entry for adding. */
-       ret = send_callback_request(com_add_callback, &obj, send_result, &pad->fd);
+       ret = send_callback_request(com_add_callback, &obj, rc4_send_result, pad->rc4c);
        goto out_free;
 
 out_unmap:
@@ -1919,21 +1903,26 @@ out_unmap:
        munmap(map.data, map.size);
 out_free:
        if (ret < 0 && send_ret >= 0)
-               send_ret = send_va_buffer(pad->fd, "failed to add %s (%s)\n", path,
-                       para_strerror(-ret));
+               send_ret = rc4_send_va_buffer(pad->rc4c,
+                       "failed to add %s (%s)\n", path, para_strerror(-ret));
        free(obj.data);
        if (afhi_ptr) {
                free(afhi_ptr->chunk_table);
-               free(afhi_ptr->info_string);
+               free(afhi_ptr->techinfo);
+               free(afhi_ptr->tags.artist);
+               free(afhi_ptr->tags.title);
+               free(afhi_ptr->tags.year);
+               free(afhi_ptr->tags.album);
+               free(afhi_ptr->tags.comment);
        }
        /* Stop adding files only on send errors. */
        return send_ret;
 }
 
-int com_add(int fd, int argc, char * const * const argv)
+int com_add(struct rc4_context *rc4c, int argc, char * const * const argv)
 {
        int i, ret;
-       struct private_add_data pad = {.fd = fd, .flags = 0};
+       struct private_add_data pad = {.rc4c = rc4c, .flags = 0};
        struct stat statbuf;
 
        for (i = 1; i < argc; i++) {
@@ -1967,7 +1956,7 @@ int com_add(int fd, int argc, char * const * const argv)
                char *path;
                ret = verify_path(argv[i], &path);
                if (ret < 0) {
-                       ret = send_va_buffer(fd, "%s: %s\n", argv[i],
+                       ret = rc4_send_va_buffer(rc4c, "%s: %s\n", argv[i],
                                para_strerror(-ret));
                        if (ret < 0)
                                return ret;
@@ -1975,7 +1964,7 @@ int com_add(int fd, int argc, char * const * const argv)
                }
                ret = stat(path, &statbuf);
                if (ret < 0) {
-                       ret = send_va_buffer(fd, "failed to stat %s (%s)\n", path,
+                       ret = rc4_send_va_buffer(rc4c, "failed to stat %s (%s)\n", path,
                                strerror(errno));
                        free(path);
                        if (ret < 0)
@@ -1988,7 +1977,7 @@ int com_add(int fd, int argc, char * const * const argv)
                else
                        ret = add_one_audio_file(path, &pad);
                if (ret < 0) {
-                       send_va_buffer(fd, "%s: %s\n", path, para_strerror(-ret));
+                       rc4_send_va_buffer(rc4c, "%s: %s\n", path, para_strerror(-ret));
                        free(path);
                        return ret;
                }
@@ -2120,7 +2109,7 @@ static void com_touch_callback(int fd, const struct osl_object *query)
        free(tad.pb.buf);
 }
 
-int com_touch(int fd, int argc, char * const * const argv)
+int com_touch(struct rc4_context *rc4c, int argc, char * const * const argv)
 {
        struct com_touch_options cto = {
                .num_played = -1,
@@ -2141,33 +2130,33 @@ int com_touch(int fd, int argc, char * const * const argv)
                        i++;
                        break;
                }
-               if (!strncmp(arg, "-n", 2)) {
-                       ret = para_atoi32(arg + 2, &cto.num_played);
+               if (!strncmp(arg, "-n=", 3)) {
+                       ret = para_atoi32(arg + 3, &cto.num_played);
                        if (ret < 0)
                                return ret;
                        continue;
                }
-               if (!strncmp(arg, "-l", 2)) {
-                       ret = para_atoi64(arg + 2, &cto.last_played);
+               if (!strncmp(arg, "-l=", 3)) {
+                       ret = para_atoi64(arg + 3, &cto.last_played);
                        if (ret < 0)
                                return ret;
                        continue;
                }
-               if (!strncmp(arg, "-y", 2)) {
-                       ret = para_atoi32(arg + 2, &cto.lyrics_id);
+               if (!strncmp(arg, "-y=", 3)) {
+                       ret = para_atoi32(arg + 3, &cto.lyrics_id);
                        if (ret < 0)
                                return ret;
                        continue;
                }
-               if (!strncmp(arg, "-i", 2)) {
-                       ret = para_atoi32(arg + 2, &cto.image_id);
+               if (!strncmp(arg, "-i=", 3)) {
+                       ret = para_atoi32(arg + 3, &cto.image_id);
                        if (ret < 0)
                                return ret;
                        continue;
                }
-               if (!strncmp(arg, "-a", 2)) {
+               if (!strncmp(arg, "-a=", 3)) {
                        int32_t val;
-                       ret = para_atoi32(arg + 2, &val);
+                       ret = para_atoi32(arg + 3, &val);
                        if (ret < 0)
                                return ret;
                        if (val < 0 || val > 255)
@@ -2188,9 +2177,9 @@ int com_touch(int fd, int argc, char * const * const argv)
        if (i >= argc)
                return -E_AFT_SYNTAX;
        ret = send_option_arg_callback_request(&query, argc - i,
-               argv + i, com_touch_callback, send_result, &fd);
+               argv + i, com_touch_callback, rc4_send_result, rc4c);
        if (ret < 0)
-               send_va_buffer(fd, "%s\n", para_strerror(-ret));
+               rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret));
        return ret;
 }
 
@@ -2272,7 +2261,7 @@ static void com_rm_callback(int fd, const struct osl_object *query)
 }
 
 /* TODO options: -r (recursive) */
-int com_rm(int fd, int argc,  char * const * const argv)
+int com_rm(struct rc4_context *rc4c, int argc,  char * const * const argv)
 {
        uint32_t flags = 0;
        struct osl_object query = {.data = &flags, .size = sizeof(flags)};
@@ -2303,9 +2292,9 @@ int com_rm(int fd, int argc,  char * const * const argv)
        if (i >= argc)
                return -E_AFT_SYNTAX;
        ret = send_option_arg_callback_request(&query, argc - i, argv + i,
-               com_rm_callback, send_result, &fd);
+               com_rm_callback, rc4_send_result, rc4c);
        if (ret < 0)
-               send_va_buffer(fd, "%s\n", para_strerror(-ret));
+               rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret));
        return ret;
 }
 
@@ -2420,7 +2409,7 @@ out:
        free(cad.pb.buf);
 }
 
-int com_cpsi(int fd, int argc,  char * const * const argv)
+int com_cpsi(struct rc4_context *rc4c, int argc,  char * const * const argv)
 {
        unsigned flags = 0;
        int i, ret;
@@ -2460,17 +2449,48 @@ int com_cpsi(int fd, int argc,  char * const * const argv)
                }
                break;
        }
-       if (i + 1 >= argc) /* need at least souce file and pattern */
+       if (i + 1 >= argc) /* need at least source file and pattern */
                return -E_AFT_SYNTAX;
        if (!(flags & ~CPSI_FLAG_VERBOSE)) /* no copy flags given */
                flags = ~(unsigned)CPSI_FLAG_VERBOSE | flags;
        ret = send_option_arg_callback_request(&options, argc - i, argv + i,
-               com_cpsi_callback, send_result, &fd);
+               com_cpsi_callback, rc4_send_result, rc4c);
        if (ret < 0)
-               send_va_buffer(fd, "%s\n", para_strerror(-ret));
+               rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret));
        return ret;
 }
 
+static void afs_stat_callback(int fd, const struct osl_object *query)
+{
+       int *parser_friendly = query->data;
+       char *buf = *parser_friendly?
+               parser_friendly_status_items : status_items;
+
+       if (!buf)
+               return;
+       pass_buffer_as_shm(buf, strlen(buf), &fd);
+}
+
+/**
+ * Get the current afs status items from the afs process and send it using RC4.
+ *
+ * \param rc4c The rc4 context for data encryption.
+ * \param parser_friendly Whether parser-friendly output format should be used.
+ *
+ * As the contents of the afs status items change in time and the command
+ * handler only has a COW version created at fork time, it can not send
+ * up-to-date afs status items directly. Therefore the usual callback mechanism
+ * is used to pass the status items from the afs process to the command handler
+ * via a shared memory area and a pipe.
+ */
+int send_afs_status(struct rc4_context *rc4c, int parser_friendly)
+{
+       struct osl_object query = {.data = &parser_friendly,
+               .size = sizeof(parser_friendly)};
+
+       return send_callback_request(afs_stat_callback, &query, rc4_send_result, rc4c);
+}
+
 /* TODO: optionally fix problems by removing offending rows */
 static int check_audio_file(struct osl_row *row, void *data)
 {
@@ -2549,6 +2569,10 @@ static void aft_close(void)
 {
        osl_close_table(audio_file_table, OSL_MARK_CLEAN);
        audio_file_table = NULL;
+       free(status_items);
+       status_items = NULL;
+       free(parser_friendly_status_items);
+       parser_friendly_status_items = NULL;
 }
 
 /**
@@ -2574,7 +2598,7 @@ static int aft_open(const char *dir)
        }
        PARA_INFO_LOG("failed to open audio file table\n");
        audio_file_table = NULL;
-       if (ret >= 0 || is_errno(-ret, ENOENT))
+       if (ret >= 0 || ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT))
                return 1;
        return ret;
 }
@@ -2623,9 +2647,13 @@ static int aft_event_handler(enum afs_events event, struct para_buffer *pb,
        }
 }
 
+/**
+ * Initialize the audio file table.
+ *
+ * \param t Pointer to the structure to be initialized.
+ */
 void aft_init(struct afs_table *t)
 {
-       t->name = audio_file_table_desc.name;
        t->open = aft_open;
        t->close = aft_close;
        t->create = aft_create;