From: Andre Noll Date: Sun, 15 Oct 2023 15:54:41 +0000 (+0200) Subject: Merge topic branch t/afs-ls-a into master X-Git-Tag: v0.7.3~12 X-Git-Url: http://git.tuebingen.mpg.de/?a=commitdiff_plain;h=4345aa176aecdbd9a876e7efdcc034acd29616a7;hp=-c;p=paraslash.git Merge topic branch t/afs-ls-a into master A new feature for the ls command. Unfortunately, several bugs were found after the topic graduated to next, so the series contains a few fixup commits on top of the single patch which implements the feature. * refs/heads/t/afs-ls-a: afs: Really fix memory leak in mood_load(). afs: Fix memory leak in mood_load(). playlist: Fix error handling of playlist_load(). server: Fix NULL pointer dereference in com_ls(). Implement ls --admissible=m/foo. --- 4345aa176aecdbd9a876e7efdcc034acd29616a7 diff --combined NEWS.md index a0484f09,598db71f..22816b19 --- a/NEWS.md +++ b/NEWS.md @@@ -1,21 -1,6 +1,24 @@@ NEWS ==== +---------------------------------------------- +0.7.3 (to be announced) "weighted correctness" +---------------------------------------------- + +- Old style PEM keys are now deprecated. They still work but their + use results in a run-time warning. The removal of PEM key support is + scheduled for paraslash-0.8.0. +- Version 1.0 of the openssl library has been deprecated. A warning + is printed at compile-time on systems which have this outdated version + because it will no longer be supported once paraslash-0.8.0 comes out. +- A spring cleanup for the senescent code in fd.c. ++- The --admissible option of the ls command now takes an optional ++ argument. When invoked like --admissible=m/foo, only files which are ++ admissible with respect to mood foo are listed. + +Downloads: +[tarball](./releases/paraslash-git.tar.xz) + ------------------------------------- 0.7.2 (2023-03-08) "optical friction" ------------------------------------- diff --combined afs.c index 865effde,9e679dcf..3083084c --- a/afs.c +++ b/afs.c @@@ -437,18 -437,18 +437,18 @@@ static int activate_mood_or_playlist(co int ret; char *msg; - if (!arg) { - ret = mood_load(NULL, &msg); + if (!arg) { /* load dummy mood */ + ret = mood_load(NULL, NULL, &msg); mode = PLAY_MODE_MOOD; } else if (!strncmp(arg, "p/", 2)) { - ret = playlist_load(arg + 2, &msg); + ret = playlist_load(arg + 2, NULL, &msg); mode = PLAY_MODE_PLAYLIST; } else if (!strncmp(arg, "m/", 2)) { - ret = mood_load(arg + 2, &msg); + ret = mood_load(arg + 2, NULL, &msg); mode = PLAY_MODE_MOOD; } else { ret = -ERRNO_TO_PARA_ERROR(EINVAL); - msg = make_message("%s: parse error", arg); + msg = make_message("%s: parse error\n", arg); } if (pb) para_printf(pb, "%s", msg); @@@ -580,6 -580,17 +580,6 @@@ static void get_database_dir(void PARA_INFO_LOG("afs_database dir %s\n", database_dir); } -static int make_database_dir(void) -{ - int ret; - - get_database_dir(); - ret = para_mkdir(database_dir, 0777); - if (ret >= 0 || ret == -ERRNO_TO_PARA_ERROR(EEXIST)) - return 1; - return ret; -} - static int open_afs_tables(void) { int i, ret; @@@ -951,8 -962,7 +951,8 @@@ __noreturn void afs_init(int socket_fd } ret = schedule(&s); sched_shutdown(&s); - mood_unload(); - playlist_unload(); + mood_unload(NULL); ++ playlist_unload(NULL); out_close: close_afs_tables(); out: @@@ -976,9 -986,9 +976,9 @@@ static int com_select_callback(struct a arg = lls_input(0, aca->lpr); score_clear(); if (current_play_mode == PLAY_MODE_MOOD) - mood_unload(); + mood_unload(NULL); else - playlist_unload(); + playlist_unload(NULL); ret = activate_mood_or_playlist(arg, &aca->pbout); if (ret >= 0) goto free_lpr; @@@ -1051,8 -1061,7 +1051,8 @@@ static int com_init(struct command_cont .size = sizeof(table_mask)}; unsigned num_inputs = lls_num_inputs(lpr); - ret = make_database_dir(); + get_database_dir(); + ret = para_mkdir(database_dir); if (ret < 0) return ret; if (num_inputs > 0) { diff --combined aft.c index 0aa9c6ba,5ecd7720..4ea8641b --- a/aft.c +++ b/aft.c @@@ -6,7 -6,6 +6,7 @@@ #include #include #include +#include #include #include @@@ -1362,23 -1361,60 +1362,60 @@@ 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; time_t current_time; - const struct lls_opt_result *r_r; + const struct lls_opt_result *r_r, *r_a; 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 = score_loop(prepare_ls_row, opts); - 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) @@@ -1907,53 -1943,6 +1944,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; diff --combined mood.c index e85cf36a,18c02f7f..b4d50c88 --- a/mood.c +++ b/mood.c @@@ -59,6 -59,8 +59,8 @@@ struct mood_instance struct mp_context *parser_context; /** To compute the score. */ struct afs_statistics stats; + /** NULL means to operate on the global score table. */ + struct osl_table *score_table; }; /* @@@ -132,6 -134,8 +134,8 @@@ static void destroy_mood(struct mood_in if (!m) return; mp_shutdown(m->parser_context); + if (m->score_table) + score_close(m->score_table); free(m->name); free(m); } @@@ -437,7 -441,7 +441,7 @@@ static void update_afs_statistics(struc } static int add_to_score_table(const struct osl_row *aft_row, - const struct afs_statistics *stats) + struct mood_instance *m) { long score; struct afs_info afsi; @@@ -445,8 -449,8 +449,8 @@@ if (ret < 0) return ret; - score = compute_score(&afsi, stats); - return score_add(aft_row, score); + score = compute_score(&afsi, &m->stats); + return score_add(aft_row, score, m->score_table); } static int delete_from_statistics_and_score_table(const struct osl_row *aft_row) @@@ -504,7 -508,7 +508,7 @@@ static int mood_update_audio_file(cons ret = add_afs_statistics(aft_row, ¤t_mood->stats); if (ret < 0) return ret; - return add_to_score_table(aft_row, ¤t_mood->stats); + return add_to_score_table(aft_row, current_mood); } /* update score */ ret = get_afsi_of_row(aft_row, &afsi); @@@ -529,6 -533,8 +533,8 @@@ static char *get_statistics(struct mood unsigned n = m->stats.num; int mean_days, sigma_days; + if (n == 0) + return make_message("no admissible files\n"); mean_days = (sse - m->stats.last_played_sum / n) / 3600 / 24; sigma_days = int_sqrt(m->stats.last_played_qd / n) / 3600 / 24; return make_message( @@@ -544,9 -550,17 +550,17 @@@ ); } - /** Free all resources of the current mood, if any. */ - void mood_unload(void) + /** + * Free all resources of a mood instance. + * + * \param m As obtained by \ref mood_load(). If NULL, unload the current mood. + * + * It's OK to call this with m == NULL even if no current mood is loaded. + */ + void mood_unload(struct mood_instance *m) { + if (m) + return destroy_mood(m); destroy_mood(current_mood); current_mood = NULL; } @@@ -568,23 -582,42 +582,42 @@@ static void compute_correction_factors( } /** - * Change the current mood. + * Populate a score table with admissible files for the given mood. + * + * This consults the mood table to initialize the mood parser with the mood + * expression stored in the blob object which corresponds to the given name. A + * score table is allocated and populated with references to those entries of + * the audio file table which evaluate as admissible with respect to the mood + * expression. For each audio file a score value is computed and stored along + * with the file reference. * * \param mood_name The name of the mood to load. + * \param result Opaque, refers to the mood parser and the score table. * \param msg Error message or mood info is returned here. * - * If \a mood_name is \a NULL, load the dummy mood that accepts every audio file - * and uses a scoring method based only on the \a last_played information. + * If the mood name is NULL, the dummy mood is loaded. This mood regards every + * audio file as admissible. + * + * A NULL result pointer instructs the function to operate on the current mood. + * That is, on the mood instance which is used by the server to select the next + * audio file for streaming. In this mode of operation, the mood which was + * active before the call, if any, is unloaded on success. + * + * If result is not NULL, the current mood is unaffected and *result points to + * an initialized mood instance on success. The caller can pass this reference + * to \ref mood_loop() to iterate over the admissible files, and should call + * \ref mood_unload() to free the mood instance afterwards. * * If the message pointer is not NULL, a suitable message is returned there in * all cases. The caller must free this string. * - * \return The number of admissible files on success, negative on errors. It is + * \return The number of admissible files on success, negative on errors. On + * errors, the current mood remains unaffected even if result is NULL. It is * not considered an error if no files are admissible. * - * \sa struct \ref afs_info::last_played, \ref mp_eval_row(). + * \sa \ref mp_eval_row(). */ - int mood_load(const char *mood_name, char **msg) + int mood_load(const char *mood_name, struct mood_instance **result, char **msg) { int i, ret; struct admissible_array aa = {.size = 0}; @@@ -609,14 -642,10 +642,10 @@@ } clock_get_realtime(&rnow); compute_correction_factors(rnow.tv_sec, &aa.m->stats); - if (aa.m->stats.num == 0) { - if (msg) - *msg = make_message("no admissible files\n"); - ret = 0; - goto out; - } + if (result) + score_open(&aa.m->score_table); for (i = 0; i < aa.m->stats.num; i++) { - ret = add_to_score_table(aa.array[i], &aa.m->stats); + ret = add_to_score_table(aa.array[i], aa.m); if (ret < 0) { if (msg) *msg = make_message( @@@ -628,22 -657,42 +657,43 @@@ if (msg) *msg = get_statistics(aa.m, rnow.tv_sec); ret = aa.m->stats.num; - mood_unload(); - current_mood = aa.m; + if (result) + *result = aa.m; + else { + mood_unload(NULL); + current_mood = aa.m; + } + ret = 1; out: free(aa.array); - if (ret < 0) + if (ret <= 0) /* error, or no admissible files */ destroy_mood(aa.m); return ret; } + /** + * Iterate over the admissible files of a mood instance. + * + * This wrapper around \ref score_loop() is the mood counterpart of \ref + * playlist_loop(). + * + * \param m Determines the score table to iterate. Must not be NULL. + * \param func See \ref score_loop(). + * \param data See \ref score_loop(). + * + * \return See \ref score_loop(), \ref playlist_loop(). + */ + int mood_loop(struct mood_instance *m, osl_rbtree_loop_func *func, void *data) + { + return score_loop(func, m->score_table, data); + } + /* * Empty the score table and start over. * - * This function is called on events which render the current list of - * admissible files useless, for example if an attribute is removed from the - * attribute table. + * This function is called on events which render the current set of admissible + * files invalid, for example if an attribute is removed from the attribute + * table. */ static int reload_current_mood(void) { @@@ -656,8 -705,8 +706,8 @@@ current_mood->name : "(dummy)"); if (current_mood->name) mood_name = para_strdup(current_mood->name); - mood_unload(); - ret = mood_load(mood_name, NULL); + mood_unload(NULL); + ret = mood_load(mood_name, NULL, NULL); free(mood_name); return ret; }