X-Git-Url: http://git.tuebingen.mpg.de/?a=blobdiff_plain;f=aft.c;h=e0bde7fc78c8365f33aa8e5aae9f751a3dabb751;hb=fefff8660daa86834673fdcc284ebe5d02c1d37b;hp=3190a9bafa5e4fdd0ef750f3fcf4160057ce7ddf;hpb=27b5b500c5fac0660d0ef0eaf1bea81a091300cc;p=paraslash.git diff --git a/aft.c b/aft.c index 3190a9ba..e0bde7fc 100644 --- a/aft.c +++ b/aft.c @@ -1,8 +1,4 @@ -/* - * Copyright (C) 2007 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ +/* Copyright (C) 2007 Andre Noll , see file COPYING. */ /** \file aft.c Audio file table functions. */ @@ -26,54 +22,6 @@ #include "sideband.h" #include "command.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 { - /** -sp (default) */ - LS_SORT_BY_PATH, - /** -ss */ - LS_SORT_BY_SCORE, - /** -sl */ - LS_SORT_BY_LAST_PLAYED, - /** -sn */ - LS_SORT_BY_NUM_PLAYED, - /** -sf */ - LS_SORT_BY_FREQUENCY, - /** -sc */ - LS_SORT_BY_CHANNELS, - /** -si */ - LS_SORT_BY_IMAGE_ID, - /** -sy */ - LS_SORT_BY_LYRICS_ID, - /** -sb */ - LS_SORT_BY_BITRATE, - /** -sd */ - LS_SORT_BY_DURATION, - /** -sa */ - LS_SORT_BY_AUDIO_FORMAT, - /** -sh */ - LS_SORT_BY_HASH, -}; - -/** The different listing modes of the ls command. */ -enum ls_listing_mode { - /** Default listing mode. */ - LS_MODE_SHORT, - /** -l or -ll */ - LS_MODE_LONG, - /** -lv */ - LS_MODE_VERBOSE, - /** -lm */ - LS_MODE_MBOX, - /** -lc */ - LS_MODE_CHUNKS, - /** -lp */ - LS_MODE_PARSER, -}; - /* Data about one audio file. Needed for ls and stat output. */ struct ls_data { /* Usual audio format handler information. */ @@ -88,6 +36,44 @@ struct ls_data { unsigned char *hash; }; +/* + * The internal state of the audio file table is described by the following + * variables which are private to aft.c. + */ +static struct osl_table *audio_file_table; /* NULL if table not open */ +static struct osl_row *current_aft_row; /* NULL if no audio file open */ +static unsigned char current_hash[HASH2_SIZE]; /* only used on sighup */ + +static char *status_items; +static char *parser_friendly_status_items; +static struct ls_data status_item_ls_data; + +/** The different sorting methods of the ls command. */ +enum ls_sorting_method { + LS_SORT_BY_PATH, /**< -s=p (default) */ + LS_SORT_BY_SCORE, /**< -s=s */ + LS_SORT_BY_LAST_PLAYED, /**< -s=l */ + LS_SORT_BY_NUM_PLAYED, /**< -s=n */ + LS_SORT_BY_FREQUENCY, /**< -s=f */ + LS_SORT_BY_CHANNELS, /**< -s=c */ + LS_SORT_BY_IMAGE_ID, /**< -s=i */ + LS_SORT_BY_LYRICS_ID, /**< -s=y */ + LS_SORT_BY_BITRATE, /**< -s=b */ + LS_SORT_BY_DURATION, /**< -s=d */ + LS_SORT_BY_AUDIO_FORMAT, /**< -s=a */ + LS_SORT_BY_HASH, /**< -s=h */ +}; + +/** The different listing modes of the ls command. */ +enum ls_listing_mode { + LS_MODE_SHORT, /**< Default listing mode. */ + LS_MODE_LONG, /**< -l or -l=l */ + LS_MODE_VERBOSE, /** -l=v */ + LS_MODE_MBOX, /** -l=m */ + LS_MODE_CHUNKS, /** -l=c */ + LS_MODE_PARSER, /** -l=p */ +}; + /** * The size of the individual output fields of the ls command. * @@ -138,7 +124,7 @@ struct ls_options { /** * Describes the layout of the mmapped-afs info struct. * - * \sa struct afs_info. + * \sa struct \ref afs_info. */ enum afsi_offsets { /** Where .last_played is stored. */ @@ -167,7 +153,7 @@ enum afsi_offsets { * \param afsi Pointer to the audio file info to be converted. * \param obj Result pointer. * - * \sa load_afsi(). + * \sa \ref load_afsi(). */ static void save_afsi(struct afs_info *afsi, struct osl_object *obj) { @@ -192,7 +178,7 @@ static void save_afsi(struct afs_info *afsi, struct osl_object *obj) * * \return Standard. * - * \sa save_afsi(). + * \sa \ref save_afsi(). */ static int load_afsi(struct afs_info *afsi, struct osl_object *obj) { @@ -240,7 +226,7 @@ static struct osl_column_description aft_cols[] = { .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE, .name = "hash", .compare_function = aft_hash_compare, - .data_size = HASH_SIZE + .data_size = HASH2_SIZE }, [AFTCOL_PATH] = { .storage_type = OSL_MAPPED_STORAGE, @@ -265,7 +251,7 @@ static struct osl_column_description aft_cols[] = { }; static struct osl_table_description audio_file_table_desc = { - .name = "audio_files", + .name = "audio-files", .num_columns = NUM_AFT_COLUMNS, .flags = OSL_LARGE_TABLE, .column_descriptions = aft_cols @@ -320,8 +306,8 @@ enum afhi_offsets { CHUNKS_TOTAL_OFFSET = 20, /** The length of the audio file header (4 bytes). */ HEADER_LEN_OFFSET = 24, - /** Was: The start of the audio file header (4 bytes). */ - AFHI_UNUSED2_OFFSET = 28, + /** Size of the largest chunk in bytes. (4 bytes). */ + AFHI_MAX_CHUNK_SIZE_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). */ @@ -361,11 +347,14 @@ static void save_afhi(struct afh_info *afhi, char *buf) 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 + AFHI_UNUSED2_OFFSET, 0); + write_u32(buf + AFHI_MAX_CHUNK_SIZE_OFFSET, afhi->max_chunk_size); 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); p = buf + AFHI_INFO_STRING_OFFSET; - /* The sprintf's below are OK as our caller made sure that buf is large enough */ + /* + * The below sprintf(3) calls are OK because our caller already 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; @@ -374,6 +363,7 @@ static void save_afhi(struct afh_info *afhi, char *buf) sprintf(p, "%s", afhi->tags.comment); } +/* does not load the chunk table */ static void load_afhi(const char *buf, struct afh_info *afhi) { afhi->seconds_total = read_u32(buf + AFHI_SECONDS_TOTAL_OFFSET); @@ -383,6 +373,7 @@ static void load_afhi(const char *buf, struct afh_info *afhi) 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->max_chunk_size = read_u32(buf + AFHI_MAX_CHUNK_SIZE_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); afhi->techinfo = (char *)buf + AFHI_INFO_STRING_OFFSET; @@ -393,42 +384,37 @@ static void load_afhi(const char *buf, struct afh_info *afhi) afhi->tags.comment = afhi->tags.album + strlen(afhi->tags.album) + 1; } +/* Only used for saving the chunk table, but not for loading. */ static unsigned sizeof_chunk_table(struct afh_info *afhi) { - if (!afhi) + if (!afhi || !afhi->chunk_table) return 0; return 4 * (afhi->chunks_total + 1); } -static uint32_t save_chunk_table(struct afh_info *afhi, char *buf) +static void save_chunk_table(struct afh_info *afhi, char *buf) { - int i; - uint32_t max = 0, old = 0; + uint32_t n; - 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; + if (!afhi->chunk_table || afhi->chunks_total == 0) + return; + for (n = 0; n <= afhi->chunks_total; n++) + write_u32(buf + 4 * n, afhi->chunk_table[n]); } -static void load_chunk_table(struct afh_info *afhi, char *buf) +static void load_chunk_table(struct afh_info *afhi, const struct osl_object *ct) { int i; + size_t sz; - afhi->chunk_table = para_malloc(sizeof_chunk_table(afhi)); - for (i = 0; i <= afhi->chunks_total; i++) - afhi->chunk_table[i] = read_u32(buf + 4 * i); + if (!ct->data || ct->size < 4 * (afhi->chunks_total + 1)) { + afhi->chunk_table = NULL; + return; + } + sz = PARA_MIN(((size_t)afhi->chunks_total + 1) * 4, ct->size) + 1; + afhi->chunk_table = para_malloc(sz); + for (i = 0; i <= afhi->chunks_total && i * 4 + 3 < ct->size; i++) + afhi->chunk_table[i] = read_u32(ct->data + 4 * i); } /** @@ -456,7 +442,7 @@ int aft_get_row_of_path(const char *path, struct osl_row **row) */ static int aft_get_row_of_hash(unsigned char *hash, struct osl_row **row) { - const struct osl_object obj = {.data = hash, .size = HASH_SIZE}; + const struct osl_object obj = {.data = hash, .size = HASH2_SIZE}; return osl(osl_get_row(audio_file_table, AFTCOL_HASH, &obj, row)); } @@ -532,7 +518,7 @@ static int get_afsi_of_path(const char *path, struct afs_info *afsi) * \param row Pointer to a row in the audio file table. * \param path Result pointer. * - * The result is a pointer to mmapped data. The caller must not attempt + * The result is a pointer to memory-mapped data. The caller must not attempt * to free it. * * \return Standard. @@ -542,10 +528,12 @@ int get_audio_file_path_of_row(const struct osl_row *row, char **path) struct osl_object path_obj; int ret = osl(osl_get_object(audio_file_table, row, AFTCOL_PATH, &path_obj)); + if (ret < 0) - return ret; - *path = path_obj.data; - return 1; + *path = NULL; + else + *path = path_obj.data; + return ret; } /** @@ -556,7 +544,7 @@ int get_audio_file_path_of_row(const struct osl_row *row, char **path) * * \return The return value of the underlying call to osl_get_object(). * - * \sa get_hash_of_row(). + * \sa \ref get_hash_of_row(). */ static int get_hash_object_of_aft_row(const struct osl_row *row, struct osl_object *obj) @@ -594,13 +582,17 @@ static int get_hash_of_row(const struct osl_row *row, unsigned char **hash) * * \return The return value of the underlying call to osl_get_object(). * - * \sa get_chunk_table_of_row(). + * After the call the members of the afhi structure point to mapped memory + * which is owned by the osl table, Hence the caller must not attempt to free + * this memory by calling \ref clear_afhi(). */ int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi) { struct osl_object obj; - int ret = osl(osl_get_object(audio_file_table, row, AFTCOL_AFHI, - &obj)); + int ret; + + assert(row); + ret = osl(osl_get_object(audio_file_table, row, AFTCOL_AFHI, &obj)); if (ret < 0) return ret; load_afhi(obj.data, afhi); @@ -623,7 +615,13 @@ static int save_afd(struct audio_file_data *afd) goto err; buf = shm_afd; buf += sizeof(*afd); - afd->max_chunk_size = save_chunk_table(&afd->afhi, buf); + save_chunk_table(&afd->afhi, buf); + if (afd->afhi.max_chunk_size == 0) { /* v0.5.x on-disk afhi */ + set_max_chunk_size(&afd->afhi); + PARA_NOTICE_LOG("max chunk size unset, re-add required\n"); + } else + PARA_INFO_LOG("using max chunk size from afhi\n"); + afd->max_chunk_size = afd->afhi.max_chunk_size; *(struct audio_file_data *)shm_afd = *afd; shm_detach(shm_afd); return shmid; @@ -648,14 +646,22 @@ int load_afd(int shmid, struct audio_file_data *afd) { void *shm_afd; int ret; + struct osl_object obj; ret = shm_attach(shmid, ATTACH_RO, &shm_afd); if (ret < 0) return ret; + ret = shm_size(shmid, &obj.size); + if (ret < 0) + goto detach; *afd = *(struct audio_file_data *)shm_afd; - load_chunk_table(&afd->afhi, shm_afd + sizeof(*afd)); + obj.data = shm_afd + sizeof(*afd); + obj.size -= sizeof(*afd); + load_chunk_table(&afd->afhi, &obj); + ret = 1; +detach: shm_detach(shm_afd); - return 1; + return ret; } static int get_local_time(uint64_t *seconds, char *buf, size_t size, @@ -684,7 +690,7 @@ static int get_local_time(uint64_t *seconds, char *buf, size_t size, /* * If the given time is more than six month away from the current time, * we print only the year. The additional space character in the format - * string below makes the formated date align nicely with dates that + * string below makes the formatted date align nicely with dates that * contain the time (those written by the above strftime() statement). */ if (!strftime(buf, size, "%b %e %Y", tm)) @@ -715,7 +721,8 @@ __a_const static short unsigned get_duration_width(int seconds) return width + 6; } -static void get_duration_buf(int seconds, char *buf, struct ls_options *opts) +static void get_duration_buf(int seconds, char *buf, size_t bufsize, + struct ls_options *opts) { unsigned hours = seconds / 3600, mins = (seconds % 3600) / 60; short unsigned max_width; @@ -723,10 +730,12 @@ static void get_duration_buf(int seconds, char *buf, struct ls_options *opts) if (!hours) { /* m:ss or mm:ss */ max_width = opts->mode == LS_MODE_LONG? opts->widths.duration_width : 4; + assert(max_width < bufsize - 1); sprintf(buf, "%*u:%02d", max_width - 3, mins, seconds % 60); } else { /* more than one hour => h:mm:ss, hh:mm:ss, hhh:mm:ss, ... */ max_width = opts->mode == LS_MODE_LONG? opts->widths.duration_width : 7; + assert(max_width < bufsize - 1); sprintf(buf, "%*u:%02u:%02d", max_width - 6, hours, mins, seconds % 60); } @@ -738,11 +747,11 @@ static int write_attribute_items(struct para_buffer *b, char *att_text; int ret; - WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_BITMAP, "%s\n", att_bitmap); + WRITE_STATUS_ITEM(b, SI_attributes_bitmap, "%s\n", att_bitmap); ret = get_attribute_text(&afsi->attributes, " ", &att_text); if (ret < 0) return ret; - WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_TXT, "%s\n", att_text); + WRITE_STATUS_ITEM(b, SI_attributes_txt, "%s\n", att_text); free(att_text); return ret; } @@ -751,9 +760,9 @@ static void write_lyrics_items(struct para_buffer *b, struct afs_info *afsi) { char *lyrics_name; - WRITE_STATUS_ITEM(b, SI_LYRICS_ID, "%u\n", afsi->lyrics_id); + WRITE_STATUS_ITEM(b, SI_lyrics_id, "%u\n", afsi->lyrics_id); lyr_get_name_by_id(afsi->lyrics_id, &lyrics_name); - WRITE_STATUS_ITEM(b, SI_LYRICS_NAME, "%s\n", lyrics_name? + WRITE_STATUS_ITEM(b, SI_lyrics_name, "%s\n", lyrics_name? lyrics_name : "(none)"); } @@ -761,27 +770,26 @@ static void write_image_items(struct para_buffer *b, struct afs_info *afsi) { char *image_name; - WRITE_STATUS_ITEM(b, SI_IMAGE_ID, "%u\n", afsi->image_id); + WRITE_STATUS_ITEM(b, SI_image_id, "%u\n", afsi->image_id); img_get_name_by_id(afsi->image_id, &image_name); - WRITE_STATUS_ITEM(b, SI_IMAGE_NAME, "%s\n", image_name? + WRITE_STATUS_ITEM(b, SI_image_name, "%s\n", image_name? image_name : "(none)"); } static void write_filename_items(struct para_buffer *b, const char *path, bool basename) { - char *val; + const char *slash; if (basename) { - WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", path); + WRITE_STATUS_ITEM(b, SI_basename, "%s\n", path); return; } - WRITE_STATUS_ITEM(b, SI_PATH, "%s\n", path); - val = para_basename(path); - WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", val? val : ""); - val = para_dirname(path); - WRITE_STATUS_ITEM(b, SI_DIRECTORY, "%s\n", val? val : ""); - free(val); + WRITE_STATUS_ITEM(b, SI_path, "%s\n", path); + slash = strrchr(path, '/'); + WRITE_STATUS_ITEM(b, SI_basename, "%s\n", slash? slash + 1 : path); + WRITE_STATUS_ITEM(b, SI_directory, "%.*s\n", + slash? (int)(slash - path) : (int)strlen(path), path); } static int print_chunk_table(struct ls_data *d, struct para_buffer *b) @@ -805,7 +813,11 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b) (long unsigned) d->afhi.chunk_tv.tv_usec ); buf = chunk_table_obj.data; - for (i = 0; i <= d->afhi.chunks_total; i++) + for ( + i = 0; + i <= d->afhi.chunks_total && 4 * i + 3 < chunk_table_obj.size; + i++ + ) para_printf(b, "%u ", (unsigned) read_u32(buf + 4 * i)); para_printf(b, "\n"); ret = 1; @@ -825,7 +837,7 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, char duration_buf[30]; /* nobody has an audio file long enough to overflow this */ struct afs_info *afsi = &d->afsi; struct afh_info *afhi = &d->afhi; - char asc_hash[2 * HASH_SIZE + 1]; + char asc_hash[2 * HASH2_SIZE + 1]; if (opts->mode == LS_MODE_SHORT) { para_printf(b, "%s\n", d->path); @@ -846,7 +858,8 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, if (ret < 0) goto out; } - get_duration_buf(afhi->seconds_total, duration_buf, opts); + get_duration_buf(afhi->seconds_total, duration_buf, + sizeof(duration_buf), opts); if (opts->mode == LS_MODE_LONG) { struct ls_widths *w = &opts->widths; if (lls_opt_given(r_a)) @@ -883,44 +896,46 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, goto out; } if (opts->mode == LS_MODE_MBOX) { - const char *bn = para_basename(d->path); + const char *slash = strrchr(d->path, '/'); para_printf(b, "From foo@localhost %s\n" "Received: from\nTo: bar\nFrom: a\n" "Subject: %s\n\n", last_played_time, - bn? bn : "?"); + slash? slash + 1 : "?"); } write_filename_items(b, d->path, lls_opt_given(r_b)); if (lls_opt_given(r_a)) - WRITE_STATUS_ITEM(b, SI_SCORE, "%li\n", d->score); + WRITE_STATUS_ITEM(b, SI_score, "%li\n", d->score); ret = write_attribute_items(b, att_buf, afsi); if (ret < 0) goto out; write_image_items(b, afsi); write_lyrics_items(b, afsi); hash_to_asc(d->hash, asc_hash); - WRITE_STATUS_ITEM(b, SI_HASH, "%s\n", asc_hash); - WRITE_STATUS_ITEM(b, SI_BITRATE, "%dkbit/s\n", afhi->bitrate); - WRITE_STATUS_ITEM(b, SI_FORMAT, "%s\n", + WRITE_STATUS_ITEM(b, SI_hash, "%s\n", asc_hash); + WRITE_STATUS_ITEM(b, SI_bitrate, "%dkbit/s\n", afhi->bitrate); + WRITE_STATUS_ITEM(b, SI_format, "%s\n", audio_format_name(afsi->audio_format_id)); - WRITE_STATUS_ITEM(b, SI_FREQUENCY, "%dHz\n", afhi->frequency); - WRITE_STATUS_ITEM(b, SI_CHANNELS, "%d\n", afhi->channels); - WRITE_STATUS_ITEM(b, SI_DURATION, "%s\n", duration_buf); - WRITE_STATUS_ITEM(b, SI_SECONDS_TOTAL, "%" PRIu32 "\n", + WRITE_STATUS_ITEM(b, SI_frequency, "%dHz\n", afhi->frequency); + WRITE_STATUS_ITEM(b, SI_channels, "%d\n", afhi->channels); + WRITE_STATUS_ITEM(b, SI_duration, "%s\n", duration_buf); + WRITE_STATUS_ITEM(b, SI_seconds_total, "%" PRIu32 "\n", afhi->seconds_total); - WRITE_STATUS_ITEM(b, SI_LAST_PLAYED, "%s\n", last_played_time); - WRITE_STATUS_ITEM(b, SI_NUM_PLAYED, "%u\n", afsi->num_played); - WRITE_STATUS_ITEM(b, SI_AMPLIFICATION, "%u\n", afsi->amp); - WRITE_STATUS_ITEM(b, SI_CHUNK_TIME, "%lu\n", tv2ms(&afhi->chunk_tv)); - WRITE_STATUS_ITEM(b, SI_NUM_CHUNKS, "%" PRIu32 "\n", + WRITE_STATUS_ITEM(b, SI_last_played, "%s\n", last_played_time); + WRITE_STATUS_ITEM(b, SI_num_played, "%u\n", afsi->num_played); + WRITE_STATUS_ITEM(b, SI_amplification, "%u\n", afsi->amp); + WRITE_STATUS_ITEM(b, SI_chunk_time, "%lu\n", tv2ms(&afhi->chunk_tv)); + WRITE_STATUS_ITEM(b, SI_num_chunks, "%" PRIu32 "\n", afhi->chunks_total); - WRITE_STATUS_ITEM(b, SI_TECHINFO, "%s\n", afhi->techinfo); - WRITE_STATUS_ITEM(b, SI_ARTIST, "%s\n", afhi->tags.artist); - WRITE_STATUS_ITEM(b, SI_TITLE, "%s\n", afhi->tags.title); - WRITE_STATUS_ITEM(b, SI_YEAR, "%s\n", afhi->tags.year); - WRITE_STATUS_ITEM(b, SI_ALBUM, "%s\n", afhi->tags.album); - WRITE_STATUS_ITEM(b, SI_COMMENT, "%s\n", afhi->tags.comment); + WRITE_STATUS_ITEM(b, SI_max_chunk_size, "%" PRIu32 "\n", + afhi->max_chunk_size); + WRITE_STATUS_ITEM(b, SI_techinfo, "%s\n", afhi->techinfo); + WRITE_STATUS_ITEM(b, SI_artist, "%s\n", afhi->tags.artist); + WRITE_STATUS_ITEM(b, SI_title, "%s\n", afhi->tags.title); + WRITE_STATUS_ITEM(b, SI_year, "%s\n", afhi->tags.year); + WRITE_STATUS_ITEM(b, SI_album, "%s\n", afhi->tags.album); + WRITE_STATUS_ITEM(b, SI_comment, "%s\n", afhi->tags.comment); if (opts->mode == LS_MODE_MBOX) { struct osl_object lyrics_def; lyr_get_def_by_id(afsi->lyrics_id, &lyrics_def); @@ -934,9 +949,6 @@ out: return ret; } -static struct ls_data status_item_ls_data; -static struct osl_row *current_aft_row; - static void make_inode_status_items(struct para_buffer *pb) { struct stat statbuf = {.st_size = 0}; @@ -954,11 +966,22 @@ static void make_inode_status_items(struct para_buffer *pb) ret = strftime(mtime_str, 29, "%b %d %Y", &mtime_tm); assert(ret > 0); /* number of bytes placed in mtime_str */ out: - WRITE_STATUS_ITEM(pb, SI_MTIME, "%s\n", mtime_str); - WRITE_STATUS_ITEM(pb, SI_FILE_SIZE, "%ld\n", statbuf.st_size / 1024); + WRITE_STATUS_ITEM(pb, SI_mtime, "%s\n", mtime_str); + WRITE_STATUS_ITEM(pb, SI_file_size, "%ld\n", statbuf.st_size / 1024); +} + +/** + * Deallocate and invalidate the status item strings. + * + * This needs to be a public function so that afs.c can call it on shutdown. + */ +void free_status_items(void) +{ + freep(&status_items); + freep(&parser_friendly_status_items); } -static int make_status_items(void) +static void make_status_items(void) { const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS); char *argv[] = {"ls", "--admissible", "--listing-mode=verbose"}; @@ -967,6 +990,9 @@ static int make_status_items(void) time_t current_time; int ret; + free_status_items(); + if (!status_item_ls_data.path) /* no audio file open */ + return; ret = lls_parse(ARRAY_SIZE(argv), argv, cmd, &opts.lpr, NULL); assert(ret >= 0); time(¤t_time); @@ -974,31 +1000,37 @@ static int make_status_items(void) if (ret < 0) goto out; make_inode_status_items(&pb); - free(status_items); status_items = pb.buf; memset(&pb, 0, sizeof(pb)); pb.max_size = shm_get_shmmax() - 1; pb.flags = PBF_SIZE_PREFIX; ret = print_list_item(&status_item_ls_data, &opts, &pb, current_time); - if (ret < 0) { - free(status_items); - status_items = NULL; - return ret; - } + if (ret < 0) + goto out; make_inode_status_items(&pb); - free(parser_friendly_status_items); parser_friendly_status_items = pb.buf; ret = 1; out: + if (ret < 0) { + PARA_WARNING_LOG("could not create status items: %s\n", + para_strerror(-ret)); + free_status_items(); + } lls_free_parse_result(opts.lpr, cmd); - return ret; } /** * Open the audio file with highest score and set up an afd structure. * - * \param afd Result pointer. + * This determines and opens the next audio file, verifies that it did not + * change by comparing the recomputed the hash value of the file contents + * against the value stored in the audio file table. If all goes well, it + * creates a shared memory area containing the serialized version of the afd + * structure, including the chunk table, if any. The caller can then send the + * ID of this area and the open fd to the server process. + * + * \param fd Result pointer for the file descriptor of the audio file. * * 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 @@ -1006,45 +1038,69 @@ out: * * \return Positive shmid on success, negative on errors. */ -int open_and_update_audio_file(struct audio_file_data *afd) +int open_and_update_audio_file(int *fd) { - unsigned char file_hash[HASH_SIZE]; + unsigned char file_hash[HASH2_SIZE]; struct osl_object afsi_obj; struct afs_info new_afsi; int ret; struct afsi_change_event_data aced; struct osl_object map, chunk_table_obj; struct ls_data *d = &status_item_ls_data; + unsigned char *tmp_hash; + struct audio_file_data afd; again: ret = score_get_best(¤t_aft_row, &d->score); if (ret < 0) return ret; - ret = get_hash_of_row(current_aft_row, &d->hash); + /* + * get_hash_of_row() and get_audio_file_path_of_row() initialize + * their pointer argument to point to memory-mapped files. These pointers + * become stale after a new audio file has been added or after the + * server process received SIGHUP. For in both cases libosl unmaps and + * remaps the underlying database files, and this remapping may well + * change the starting address of the mapping. To avoid stale pointer + * references we create copies on the heap. + */ + ret = get_hash_of_row(current_aft_row, &tmp_hash); if (ret < 0) return ret; + if (!d->hash) + d->hash = para_malloc(HASH_SIZE); + memcpy(d->hash, tmp_hash, HASH_SIZE); + free(d->path); ret = get_audio_file_path_of_row(current_aft_row, &d->path); if (ret < 0) return ret; PARA_NOTICE_LOG("%s\n", d->path); + d->path = para_strdup(d->path); + ret = get_afsi_object_of_row(current_aft_row, &afsi_obj); if (ret < 0) return ret; ret = load_afsi(&d->afsi, &afsi_obj); if (ret < 0) return ret; - ret = get_afhi_of_row(current_aft_row, &afd->afhi); + ret = get_afhi_of_row(current_aft_row, &afd.afhi); if (ret < 0) return ret; - d->afhi = afd->afhi; - d->afhi.chunk_table = afd->afhi.chunk_table = NULL; + d->afhi = afd.afhi; + d->afhi.chunk_table = afd.afhi.chunk_table = NULL; ret = osl(osl_open_disk_object(audio_file_table, current_aft_row, AFTCOL_CHUNKS, &chunk_table_obj)); - if (ret < 0) - return ret; - ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, &afd->fd); + if (ret < 0) { + if (!afh_supports_dynamic_chunks(d->afsi.audio_format_id)) + return ret; + PARA_INFO_LOG("no chunk table for %s\n", d->path); + chunk_table_obj.data = NULL; + chunk_table_obj.size = 0; + } else { + PARA_INFO_LOG("chunk table: %zu bytes\n", chunk_table_obj.size); + } + ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, fd); if (ret < 0) goto out; - hash_function(map.data, map.size, file_hash); + hash2_function(map.data, map.size, file_hash); ret = hash_compare(file_hash, d->hash); para_munmap(map.data, map.size); if (ret) { @@ -1056,8 +1112,8 @@ again: new_afsi.last_played = time(NULL); save_afsi(&new_afsi, &afsi_obj); /* in-place update */ - afd->audio_format_id = d->afsi.audio_format_id; - load_chunk_table(&afd->afhi, chunk_table_obj.data); + afd.audio_format_id = d->afsi.audio_format_id; + load_chunk_table(&afd.afhi, &chunk_table_obj); aced.aft_row = current_aft_row; aced.old_afsi = &d->afsi; /* @@ -1067,10 +1123,11 @@ again: ret = afs_event(AFSI_CHANGE, NULL, &aced); if (ret < 0) goto out; - ret = save_afd(afd); + ret = save_afd(&afd); out: - free(afd->afhi.chunk_table); - osl_close_disk_object(&chunk_table_obj); + free(afd.afhi.chunk_table); + if (chunk_table_obj.data) + osl_close_disk_object(&chunk_table_obj); if (ret < 0) { PARA_ERROR_LOG("%s: %s\n", d->path, para_strerror(-ret)); ret = score_delete(current_aft_row); @@ -1080,6 +1137,12 @@ out: return ret; } +static int ls_hash_compare(const void *a, const void *b) +{ + struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; + return memcmp(d1->hash, d2->hash, HASH2_SIZE); +} + 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; @@ -1197,6 +1260,8 @@ static int sort_matching_paths(struct ls_options *options) compar = ls_duration_compare; break; case LS_SORT_BY_AUDIO_FORMAT: compar = ls_audio_format_compare; break; + case LS_SORT_BY_HASH: + compar = ls_hash_compare; break; default: return -E_BAD_SORT; } @@ -1345,7 +1410,6 @@ out: return ret; } -/* TODO: flags -h (sort by hash) */ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr) { const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS); @@ -1383,7 +1447,7 @@ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr) else if (!strcmp(val, "p") || !strcmp(val, "parser-friendly")) opts->mode = LS_MODE_PARSER; else { - ret = -E_AFT_SYNTAX; + ret = -ERRNO_TO_PARA_ERROR(EINVAL); goto out; } } @@ -1411,8 +1475,10 @@ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr) opts->sorting = LS_SORT_BY_DURATION; else if (!strcmp(val, "a") || !strcmp(val, "audio-format")) opts->sorting = LS_SORT_BY_AUDIO_FORMAT; + else if (!strcmp(val, "h") || !strcmp(val, "hash")) + opts->sorting = LS_SORT_BY_HASH; else { - ret = -E_AFT_SYNTAX; + ret = -ERRNO_TO_PARA_ERROR(EINVAL); goto out; } } @@ -1469,7 +1535,7 @@ enum com_add_buffer_offsets { /** The hash of the audio file being added. */ CAB_HASH_OFFSET = 13, /** Start of the path of the audio file. */ - CAB_PATH_OFFSET = (CAB_HASH_OFFSET + HASH_SIZE), + CAB_PATH_OFFSET = (CAB_HASH_OFFSET + HASH2_SIZE), }; /* @@ -1492,7 +1558,7 @@ static void save_add_callback_buffer(unsigned char *hash, const char *path, assert(size <= ~(uint32_t)0); write_u8(buf + CAB_AUDIO_FORMAT_ID_OFFSET, audio_format_num); - memcpy(buf + CAB_HASH_OFFSET, hash, HASH_SIZE); + memcpy(buf + CAB_HASH_OFFSET, hash, HASH2_SIZE); strcpy(buf + CAB_PATH_OFFSET, path); pos = CAB_PATH_OFFSET + path_len; write_u32(buf + CAB_AFHI_OFFSET_POS, pos); @@ -1551,7 +1617,7 @@ ACTION: Table modifications to be done by the callback. +----+----+---+------+---------------------------------------------------+ | N | N | Y | Y | (new file) create new entry (force has no effect) +----+----+---+------+---------------------------------------------------+ -| N | N | N | Y | (new file) create new entry +| N | N | N | Y | (new file) create new entry +----+----+---+------+---------------------------------------------------+ Notes: @@ -1568,7 +1634,7 @@ static int com_add_callback(struct afs_callback_arg *aca) struct osl_row *hs; struct osl_object objs[NUM_AFT_COLUMNS]; unsigned char *hash; - char asc[2 * HASH_SIZE + 1]; + char asc[2 * HASH2_SIZE + 1]; int ret; char afsi_buf[AFSI_SIZE]; char *slpr = buf + read_u32(buf + CAB_LPR_OFFSET); @@ -1585,7 +1651,7 @@ static int com_add_callback(struct afs_callback_arg *aca) hash = (unsigned char *)buf + CAB_HASH_OFFSET; hash_to_asc(hash, asc); objs[AFTCOL_HASH].data = buf + CAB_HASH_OFFSET; - objs[AFTCOL_HASH].size = HASH_SIZE; + objs[AFTCOL_HASH].size = HASH2_SIZE; path = buf + CAB_PATH_OFFSET; objs[AFTCOL_PATH].data = path; @@ -1648,7 +1714,7 @@ static int com_add_callback(struct afs_callback_arg *aca) objs[AFTCOL_CHUNKS].data = buf + chunks_offset; objs[AFTCOL_CHUNKS].size = aca->query.size - chunks_offset; if (pb && !hs) { /* update pb's hash */ - char old_asc[2 * HASH_SIZE + 1]; + char old_asc[2 * HASH2_SIZE + 1]; unsigned char *old_hash; ret = get_hash_of_row(pb, &old_hash); if (ret < 0) @@ -1672,6 +1738,7 @@ static int com_add_callback(struct afs_callback_arg *aca) &objs[AFTCOL_AFHI])); if (ret < 0) goto out; + /* truncate the file to size zero if there is no chunk table */ ret = osl(osl_update_object(audio_file_table, row, AFTCOL_CHUNKS, &objs[AFTCOL_CHUNKS])); if (ret < 0) @@ -1752,7 +1819,7 @@ static int add_one_audio_file(const char *path, void *private_data) struct afh_info afhi, *afhi_ptr = NULL; struct osl_row *pb = NULL, *hs = NULL; /* path brother/hash sister */ struct osl_object map, obj = {.data = NULL}, query; - unsigned char hash[HASH_SIZE]; + unsigned char hash[HASH2_SIZE]; bool a_given = SERVER_CMD_OPT_GIVEN(ADD, ALL, pad->lpr); bool f_given = SERVER_CMD_OPT_GIVEN(ADD, FORCE, pad->lpr); bool l_given = SERVER_CMD_OPT_GIVEN(ADD, LAZY, pad->lpr); @@ -1780,11 +1847,11 @@ static int add_one_audio_file(const char *path, void *private_data) ret = mmap_full_file(path, O_RDONLY, &map.data, &map.size, &fd); if (ret < 0) goto out_free; - hash_function(map.data, map.size, hash); + hash2_function(map.data, map.size, hash); /* Check whether the database contains a file with the same hash. */ query.data = hash; - query.size = HASH_SIZE; + query.size = HASH2_SIZE; ret = send_callback_request(hash_sister_callback, &query, get_row_pointer_from_result, &hs); if (ret < 0) @@ -1882,11 +1949,7 @@ out: } EXPORT_SERVER_CMD_HANDLER(add); -/** - * Flags used by the touch command. - * - * \sa com_touch(). - */ +/** Flags used by the touch command. */ enum touch_flags { /** Whether the \p FNM_PATHNAME flag should be passed to fnmatch(). */ TOUCH_FLAG_FNM_PATHNAME = 1, @@ -2112,7 +2175,9 @@ static int copy_selector_info(__a_unused struct osl_table *table, ret = get_afsi_object_of_row(row, &target_afsi_obj); if (ret < 0) return ret; - load_afsi(&target_afsi, &target_afsi_obj); + ret = load_afsi(&target_afsi, &target_afsi_obj); + if (ret < 0) + return ret; old_afsi = target_afsi; if (cad->copy_all || y_given) target_afsi.lyrics_id = cad->source_afsi.lyrics_id; @@ -2370,8 +2435,6 @@ static int check_audio_file(struct osl_row *row, void *data) * \param aca Only ->pbout is used for diagnostics. * * \return Standard. Inconsistencies are reported but not regarded as an error. - * - * \sa com_check(). */ int aft_check_callback(struct afs_callback_arg *aca) { @@ -2435,28 +2498,30 @@ int aft_check_attributes(uint64_t att_mask, struct para_buffer *pb) return audio_file_loop(&acad, check_atts_of_audio_file); } -/** - * Close the audio file table. - * - * \param flags Usual flags that are passed to osl_close_table(). - * - * \sa osl_close_table(). +/* + * This sets audio_file_table to NULL, but leaves current_aft_row unmodified, + * though stale (pointing to unmapped memory). If the table is being closed + * because we received SIGHUP, the table will be reopened after the config file + * has been reloaded. We remember the hash of the current audio file here so + * that aft_open() can initialize current_aft_row by looking up the saved hash. */ static void aft_close(void) { + int ret; + unsigned char *p; + + if (current_aft_row) { + ret = get_hash_of_row(current_aft_row, &p); + if (ret < 0) { + PARA_WARNING_LOG("hash lookup failure\n"); + current_aft_row = NULL; + } else + memcpy(current_hash, p, HASH2_SIZE); + } osl_close_table(audio_file_table, OSL_MARK_CLEAN); audio_file_table = NULL; } -/** - * Open the audio file table. - * - * \param dir The database directory. - * - * \return Standard. - * - * \sa osl_open_table(). - */ static int aft_open(const char *dir) { int ret; @@ -2467,12 +2532,27 @@ static int aft_open(const char *dir) unsigned num; osl_get_num_rows(audio_file_table, &num); PARA_INFO_LOG("audio file table contains %u files\n", num); - return ret; + if (!current_aft_row) { + PARA_DEBUG_LOG("no current aft row\n"); + return 1; + } + /* SIGHUP case, update current_aft_row */ + ret = aft_get_row_of_hash(current_hash, ¤t_aft_row); + if (ret < 0) { /* not fatal */ + PARA_WARNING_LOG("current hash lookup failure: %s\n", + para_strerror(-ret)); + current_aft_row = NULL; + return 1; + } + PARA_NOTICE_LOG("current audio file hash lookup: success\n"); + return 1; } - PARA_NOTICE_LOG("failed to open audio file table\n"); audio_file_table = NULL; - if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT)) + if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT)) { + PARA_WARNING_LOG("no audio file table\n"); return 1; + } + PARA_NOTICE_LOG("failed to open audio file table\n"); return ret; } @@ -2523,6 +2603,17 @@ static int aft_event_handler(enum afs_events event, struct para_buffer *pb, status_item_ls_data.afsi.last_played = old_last_played; make_status_items(); return 1; + } case AUDIO_FILE_RENAME: { + char *path; + if (data != current_aft_row) + return 0; + ret = get_audio_file_path_of_row(current_aft_row, &path); + if (ret < 0) + return ret; + free(status_item_ls_data.path); + status_item_ls_data.path = para_strdup(path); + make_status_items(); + return 1; } case AFHI_CHANGE: { if (data != current_aft_row) return 0; @@ -2531,6 +2622,27 @@ static int aft_event_handler(enum afs_events event, struct para_buffer *pb, return ret; make_status_items(); return 1; + } case AUDIO_FILE_REMOVE: { + if (data == current_aft_row) + current_aft_row = NULL; + return 0; + } + case BLOB_RENAME: + case BLOB_REMOVE: + case BLOB_ADD: { + /* + * These events are rare. We don't bother to check whether the + * current status items are affected and simply recreate them + * whenever an audio file is open. + */ + if (!current_aft_row) + return 0; + ret = get_afhi_of_row(current_aft_row, + &status_item_ls_data.afhi); + if (ret < 0) + return ret; + make_status_items(); + return 0; } default: return 0; }