X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=aft.c;h=c04d4f9c99e89afb451afe94be7fcc64461b15e1;hp=15769d55498b9b5ad6aa11f86efdebde31afc417;hb=HEAD;hpb=53af5c6efb309565990203fd8504a812ec9166c9 diff --git a/aft.c b/aft.c index 15769d55..f1aca7fb 100644 --- a/aft.c +++ b/aft.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,7 @@ struct ls_data { */ 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; @@ -49,46 +51,28 @@ static struct ls_data status_item_ls_data; /** 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, + 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 { - /** 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, + 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 */ }; /** @@ -243,7 +227,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, @@ -268,7 +252,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 @@ -413,7 +397,7 @@ static void save_chunk_table(struct afh_info *afhi, char *buf) { uint32_t n; - if (!afhi->chunk_table) + 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]); @@ -424,12 +408,12 @@ static void load_chunk_table(struct afh_info *afhi, const struct osl_object *ct) int i; size_t sz; - if (!ct->data || ct->size < 4) { + 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); + afhi->chunk_table = alloc(sz); for (i = 0; i <= afhi->chunks_total && i * 4 + 3 < ct->size; i++) afhi->chunk_table[i] = read_u32(ct->data + 4 * i); } @@ -459,7 +443,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)); } @@ -606,8 +590,10 @@ static int get_hash_of_row(const struct osl_row *row, unsigned char **hash) 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); @@ -736,7 +722,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; @@ -744,10 +731,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); } @@ -791,18 +780,17 @@ static void write_image_items(struct para_buffer *b, struct afs_info *afsi) 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); 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); + 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) @@ -812,6 +800,12 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b) int ret, i; char *buf; + para_printf(b, "%s\nchunk_time: %lu:%lu\n", d->path, + (long unsigned) d->afhi.chunk_tv.tv_sec, + (long unsigned) d->afhi.chunk_tv.tv_usec + ); + if (afh_supports_dynamic_chunks(d->afsi.audio_format_id)) + return 0; ret = aft_get_row_of_hash(d->hash, &aft_row); if (ret < 0) return ret; @@ -819,12 +813,7 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b) AFTCOL_CHUNKS, &chunk_table_obj)); if (ret < 0) return ret; - para_printf(b, "%s\n" - "chunk_time: %lu:%lu\nchunk_offsets: ", - d->path, - (long unsigned) d->afhi.chunk_tv.tv_sec, - (long unsigned) d->afhi.chunk_tv.tv_usec - ); + para_printf(b, "chunk_offsets: "); buf = chunk_table_obj.data; for ( i = 0; @@ -850,7 +839,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); @@ -871,7 +860,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)) @@ -908,13 +898,13 @@ 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)) @@ -924,7 +914,7 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, goto out; write_image_items(b, afsi); write_lyrics_items(b, afsi); - hash_to_asc(d->hash, asc_hash); + hash2_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", @@ -982,13 +972,18 @@ out: 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"}; @@ -999,7 +994,7 @@ static int make_status_items(void) free_status_items(); if (!status_item_ls_data.path) /* no audio file open */ - return 0; + return; ret = lls_parse(ARRAY_SIZE(argv), argv, cmd, &opts.lpr, NULL); assert(ret >= 0); time(¤t_time); @@ -1019,16 +1014,25 @@ static int make_status_items(void) parser_friendly_status_items = pb.buf; ret = 1; out: - if (ret < 0) + 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 @@ -1036,37 +1040,54 @@ 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 = alloc(HASH2_SIZE); + memcpy(d->hash, tmp_hash, HASH2_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) { @@ -1078,11 +1099,11 @@ again: } 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, &afd->fd); + 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); - ret = hash_compare(file_hash, d->hash); + hash2_function(map.data, map.size, file_hash); + ret = hash2_compare(file_hash, d->hash); para_munmap(map.data, map.size); if (ret) { ret = -E_HASH_MISMATCH; @@ -1093,8 +1114,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); + 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; /* @@ -1104,9 +1125,9 @@ 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); + free(afd.afhi.chunk_table); if (chunk_table_obj.data) osl_close_disk_object(&chunk_table_obj); if (ret < 0) { @@ -1121,7 +1142,7 @@ out: 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, HASH_SIZE); + return memcmp(d1->hash, d2->hash, HASH2_SIZE); } static int ls_audio_format_compare(const void *a, const void *b) @@ -1205,7 +1226,7 @@ static int sort_matching_paths(struct ls_options *options) int (*compar)(const void *, const void *); int i; - options->data_ptr = para_malloc(nmemb * sizeof(*options->data_ptr)); + options->data_ptr = arr_alloc(nmemb, sizeof(*options->data_ptr)); for (i = 0; i < nmemb; i++) options->data_ptr[i] = options->data + i; @@ -1298,8 +1319,8 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts) 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)); + options->data = arr_realloc(options->data, options->array_size, + sizeof(*options->data)); } d = options->data + tmp; ret = get_afsi_of_row(aft_row, &d->afsi); @@ -1341,28 +1362,67 @@ err: return ret; } +static int mop_loop(const char *arg, struct afs_callback_arg *aca, + struct ls_options *opts) +{ + int ret; + char *msg; + + if (!arg || strcmp(arg, ".") == 0) + return score_loop(prepare_ls_row, NULL, opts); + if (!strncmp(arg, "m/", 2)) { + struct mood_instance *m; + ret = mood_load(arg + 2, &m, &msg); + if (ret < 0) + afs_error(aca, "%s", msg); + free(msg); + if (ret < 0) + return ret; + ret = mood_loop(m, prepare_ls_row, opts); + mood_unload(m); + return ret; + } + if (!strncmp(arg, "p/", 2)) { + struct playlist_instance *pi; + ret = playlist_load(arg + 2, &pi, &msg); + if (ret < 0) + afs_error(aca, "%s", msg); + free(msg); + if (ret < 0) + return ret; + ret = playlist_loop(pi, prepare_ls_row, opts); + playlist_unload(pi); + return ret; + } + afs_error(aca, "bad mood/playlist specifier: %s\n", arg); + return -ERRNO_TO_PARA_ERROR(EINVAL); +} + static int com_ls_callback(struct afs_callback_arg *aca) { const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS); struct ls_options *opts = aca->query.data; - int i = 0, ret; + int ret; time_t current_time; - const struct lls_opt_result *r_r; + const struct lls_opt_result *r_r, *r_a; + uint32_t limit, k, n; ret = lls_deserialize_parse_result( (char *)aca->query.data + sizeof(*opts), cmd, &opts->lpr); assert(ret >= 0); r_r = SERVER_CMD_OPT_RESULT(LS, REVERSE, opts->lpr); - + r_a = SERVER_CMD_OPT_RESULT(LS, ADMISSIBLE, opts->lpr); aca->pbout.flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0; - if (admissible_only(opts)) - ret = admissible_file_loop(opts, prepare_ls_row); - else + if (admissible_only(opts)) { + const char *arg = lls_string_val(0, r_a); + ret = mop_loop(arg, aca, opts); + } else ret = osl(osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts, prepare_ls_row)); if (ret < 0) goto out; - if (opts->num_matching_paths == 0) { + n = opts->num_matching_paths; + if (n == 0) { ret = lls_num_inputs(opts->lpr) > 0? -E_NO_MATCH : 0; goto out; } @@ -1370,20 +1430,14 @@ static int com_ls_callback(struct afs_callback_arg *aca) if (ret < 0) goto out; time(¤t_time); - if (lls_opt_given(r_r)) - for (i = opts->num_matching_paths - 1; i >= 0; i--) { - ret = print_list_item(opts->data_ptr[i], opts, - &aca->pbout, current_time); - if (ret < 0) - goto out; - } - else - for (i = 0; i < opts->num_matching_paths; i++) { - ret = print_list_item(opts->data_ptr[i], opts, - &aca->pbout, current_time); - if (ret < 0) - goto out; - } + limit = SERVER_CMD_UINT32_VAL(LS, LIMIT, opts->lpr); + for (k = 0; k < n && (limit == 0 || k < limit); k++) { + uint32_t idx = lls_opt_given(r_r)? n - 1 - k : k; + ret = print_list_item(opts->data_ptr[idx], opts, &aca->pbout, + current_time); + if (ret < 0) + goto out; + } out: lls_free_parse_result(opts->lpr, cmd); free(opts->data); @@ -1405,7 +1459,7 @@ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr) ret = lls_serialize_parse_result(lpr, cmd, NULL, &query.size); assert(ret >= 0); query.size += sizeof(*opts); - query.data = para_malloc(query.size); + query.data = alloc(query.size); opts = query.data; memset(opts, 0, sizeof(*opts)); slpr = query.data + sizeof(*opts); @@ -1424,11 +1478,11 @@ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr) else if (!strcmp(val, "m") || !strcmp(val, "mbox")) opts->mode = LS_MODE_MBOX; else if (!strcmp(val, "c") || !strcmp(val, "chunk-table")) - opts->mode = LS_MODE_MBOX; + opts->mode = LS_MODE_CHUNKS; 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; } } @@ -1459,7 +1513,7 @@ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr) 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; } } @@ -1516,7 +1570,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), }; /* @@ -1534,12 +1588,12 @@ static void save_add_callback_buffer(unsigned char *hash, const char *path, size_t afhi_size = sizeof_afhi_buf(afhi); size_t size = CAB_PATH_OFFSET + path_len + afhi_size + sizeof_chunk_table(afhi) + slpr_size; - char *buf = para_malloc(size); + char *buf = alloc(size); uint32_t pos; 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); @@ -1615,10 +1669,11 @@ 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); + uint32_t slpr_offset = read_u32(buf + CAB_LPR_OFFSET); + char *slpr = buf + slpr_offset; struct afs_info default_afsi = {.last_played = 0}; uint16_t afhi_offset, chunks_offset; const struct lls_command *cmd = SERVER_CMD_CMD_PTR(ADD); @@ -1630,9 +1685,9 @@ static int com_add_callback(struct afs_callback_arg *aca) r_v = SERVER_CMD_OPT_RESULT(ADD, VERBOSE, aca->lpr); hash = (unsigned char *)buf + CAB_HASH_OFFSET; - hash_to_asc(hash, asc); + hash2_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; @@ -1686,6 +1741,7 @@ static int com_add_callback(struct afs_callback_arg *aca) /* no hs or force mode, child must have sent afhi */ afhi_offset = read_u32(buf + CAB_AFHI_OFFSET_POS); chunks_offset = read_u32(buf + CAB_CHUNKS_OFFSET_POS); + assert(chunks_offset <= slpr_offset); objs[AFTCOL_AFHI].data = buf + afhi_offset; objs[AFTCOL_AFHI].size = chunks_offset - afhi_offset; @@ -1693,14 +1749,14 @@ static int com_add_callback(struct afs_callback_arg *aca) if (!objs[AFTCOL_AFHI].size) /* "impossible" */ goto out; objs[AFTCOL_CHUNKS].data = buf + chunks_offset; - objs[AFTCOL_CHUNKS].size = aca->query.size - chunks_offset; + objs[AFTCOL_CHUNKS].size = slpr_offset - 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) goto out; - hash_to_asc(old_hash, old_asc); + hash2_to_asc(old_hash, old_asc); if (lls_opt_given(r_v)) para_printf(&aca->pbout, "file change: %s -> %s\n", old_asc, asc); @@ -1742,7 +1798,7 @@ static int com_add_callback(struct afs_callback_arg *aca) ret = afs_event(AUDIO_FILE_ADD, &aca->pbout, aft_row); out: if (ret < 0) - para_printf(&aca->pbout, "could not add %s\n", path); + afs_error(aca, "could not add %s\n", path); lls_free_parse_result(aca->lpr, cmd); return ret; } @@ -1800,7 +1856,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); @@ -1828,11 +1884,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) @@ -1884,6 +1940,53 @@ out_free: return send_ret; } +/* + * Call back once for each regular file below a directory. + * + * Traverse the given directory recursively and call the supplied callback for + * each regular file encountered. The first argument to the callback will be + * the path to the regular file and the second argument will be the data + * pointer. All file types except regular files and directories are ignored. In + * particular, symlinks are not followed. Subdirectories are ignored silently + * if the calling process has insufficient access permissions. + */ +static int for_each_file_in_dir(const char *dirname, + int (*func)(const char *, void *), void *data) +{ + int ret; + DIR *dir; + struct dirent *entry; + + dir = opendir(dirname); + if (!dir) + return errno == EACCES? 1 : -ERRNO_TO_PARA_ERROR(errno); + /* scan cwd recursively */ + while ((entry = readdir(dir))) { + char *tmp; + struct stat s; + + if (!strcmp(entry->d_name, ".")) + continue; + if (!strcmp(entry->d_name, "..")) + continue; + tmp = make_message("%s/%s", dirname, entry->d_name); + ret = 0; + if (lstat(tmp, &s) != -1) { + if (S_ISREG(s.st_mode)) + ret = func(tmp, data); + else if (S_ISDIR(s.st_mode)) + ret = for_each_file_in_dir(tmp, func, data); + } + free(tmp); + if (ret < 0) + goto out; + } + ret = 1; +out: + closedir(dir); + return ret; +} + static int com_add(struct command_context *cc, struct lls_parse_result *lpr) { int i, ret; @@ -1960,12 +2063,12 @@ static int touch_audio_file(__a_unused struct osl_table *table, ret = get_afsi_object_of_row(row, &obj); if (ret < 0) { - para_printf(&aca->pbout, "cannot touch %s\n", name); + afs_error(aca, "cannot touch %s\n", name); return ret; } ret = load_afsi(&old_afsi, &obj); if (ret < 0) { - para_printf(&aca->pbout, "cannot touch %s\n", name); + afs_error(aca, "cannot touch %s\n", name); return ret; } new_afsi = old_afsi; @@ -2019,7 +2122,7 @@ static int com_touch_callback(struct afs_callback_arg *aca) uint32_t id = lls_uint32_val(0, r_i); ret = img_get_name_by_id(id, NULL); if (ret < 0) { - para_printf(&aca->pbout, "invalid image ID: %u\n", id); + afs_error(aca, "invalid image ID: %u\n", id); return ret; } } @@ -2028,7 +2131,7 @@ static int com_touch_callback(struct afs_callback_arg *aca) uint32_t id = lls_uint32_val(0, r_y); ret = lyr_get_name_by_id(id, NULL); if (ret < 0) { - para_printf(&aca->pbout, "invalid lyrics ID: %u\n", id); + afs_error(aca, "invalid lyrics ID: %u\n", id); return ret; } } @@ -2071,7 +2174,7 @@ static int remove_audio_file(__a_unused struct osl_table *table, return ret; ret = osl(osl_del_row(audio_file_table, row)); if (ret < 0) - para_printf(&aca->pbout, "cannot remove %s\n", name); + afs_error(aca, "cannot remove %s\n", name); return ret; } @@ -2301,13 +2404,13 @@ static int com_setatt_callback(struct afs_callback_arg *aca) goto out; /* no attribute modifier given */ goto set_atts; } - p = para_malloc(len); + p = alloc(len); memcpy(p, arg, len - 1); p[len - 1] = '\0'; ret = get_attribute_bitnum_by_name(p, &bitnum); free(p); if (ret < 0) { - para_printf(&aca->pbout, "invalid argument: %s\n", arg); + afs_error(aca, "invalid argument: %s\n", arg); goto out; } if (c == '+') @@ -2479,28 +2582,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 \ref 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 \ref osl_open_table(). - */ static int aft_open(const char *dir) { int ret; @@ -2511,12 +2616,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; } @@ -2567,6 +2687,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; @@ -2575,6 +2706,10 @@ 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: @@ -2582,8 +2717,14 @@ static int aft_event_handler(enum afs_events event, struct para_buffer *pb, /* * These events are rare. We don't bother to check whether the * current status items are affected and simply recreate them - * every time. + * 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: @@ -2591,15 +2732,10 @@ 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->open = aft_open; - t->close = aft_close; - t->create = aft_create; - t->event_handler = aft_event_handler; -} +/** The audio file table contains information about known audio files. */ +const struct afs_table_operations aft_ops = { + .open = aft_open, + .close = aft_close, + .create = aft_create, + .event_handler = aft_event_handler, +};