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=420a1f30cc06482f36371d096635846f8800e198;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. --- diff --git a/NEWS.md b/NEWS.md index a0484f09..22816b19 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,6 +12,9 @@ NEWS 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) diff --git a/afs.c b/afs.c index 865effde..3083084c 100644 --- a/afs.c +++ b/afs.c @@ -437,14 +437,14 @@ static int activate_mood_or_playlist(const char *arg, struct para_buffer *pb) 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); @@ -951,8 +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 +976,9 @@ static int com_select_callback(struct afs_callback_arg *aca) 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; diff --git a/afs.h b/afs.h index 9a1d7d9c..e8b8c865 100644 --- a/afs.h +++ b/afs.h @@ -238,10 +238,12 @@ int for_each_matching_row(struct pattern_match_data *pmd); /* score */ extern const struct afs_table_operations score_ops; -int score_loop(osl_rbtree_loop_func *func, void *data); +void score_open(struct osl_table **result); +void score_close(struct osl_table *t); +int score_loop(osl_rbtree_loop_func *func, struct osl_table *t, void *data); int score_get_best(struct osl_row **aft_row, long *score); int get_score_and_aft_row(struct osl_row *score_row, long *score, struct osl_row **aft_row); -int score_add(const struct osl_row *row, long score); +int score_add(const struct osl_row *aft_row, long score, struct osl_table *t); int score_update(const struct osl_row *aft_row, long new_score); int score_delete(const struct osl_row *aft_row); void score_clear(void); @@ -268,13 +270,17 @@ int aft_check_callback(struct afs_callback_arg *aca); void free_status_items(void); /* mood */ -int mood_load(const char *mood_name, char **msg); -void mood_unload(void); +struct mood_instance; +int mood_load(const char *mood_name, struct mood_instance **result, char **msg); +int mood_loop(struct mood_instance *m, osl_rbtree_loop_func *func, void *data); +void mood_unload(struct mood_instance *m); int mood_check_callback(struct afs_callback_arg *aca); /* playlist */ -int playlist_load(const char *name, char **msg); -void playlist_unload(void); +struct playlist_instance; +int playlist_load(const char *name, struct playlist_instance **result, char **msg); +int playlist_loop(struct playlist_instance *pi, osl_rbtree_loop_func *func, void *data); +void playlist_unload(struct playlist_instance *pi); int playlist_check_callback(struct afs_callback_arg *aca); /** evaluates to 1 if x < y, to -1 if x > y and to 0 if x == y */ diff --git a/aft.c b/aft.c index 0aa9c6ba..4ea8641b 100644 --- a/aft.c +++ b/aft.c @@ -1362,23 +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) diff --git a/m4/lls/server_cmd.suite.m4 b/m4/lls/server_cmd.suite.m4 index 8200c624..1cec6816 100644 --- a/m4/lls/server_cmd.suite.m4 +++ b/m4/lls/server_cmd.suite.m4 @@ -233,9 +233,19 @@ m4_include(`com_ll.m4') [option admissible] short_opt = a summary = list only admissible files + arg_type = string + arg_info = optional_arg + typestr = specifier/name + default_val = . [help] - List only files which are admissible with respect to the current mood - or playlist. + If the optional argument is supplied, it must be of the form "p/foo" + or "m/bar" (which refer to the playlist named "foo" and the mood named + "bar", respectively). The command then restricts its output to the set + of files which are admissible with respect to the thusly identified + mood or playlist. + + If no argument is given, or if the argument is the special value ".", + the current mood or playlist is assumed. [/help] [option reverse] short_opt = r diff --git a/mood.c b/mood.c index e85cf36a..b4d50c88 100644 --- a/mood.c +++ b/mood.c @@ -59,6 +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 @@ static void destroy_mood(struct mood_instance *m) 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 @@ static void update_afs_statistics(struct afs_info *old_afsi, } 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 @@ static int add_to_score_table(const struct osl_row *aft_row, 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 @@ static int mood_update_audio_file(const struct osl_row *aft_row, 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 @@ static char *get_statistics(struct mood_instance *m, int64_t sse) 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 @@ static char *get_statistics(struct mood_instance *m, int64_t sse) ); } -/** 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 @@ static void compute_correction_factors(int64_t sse, struct afs_statistics *s) } /** - * 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 @@ int mood_load(const char *mood_name, char **msg) } 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,8 +657,12 @@ int mood_load(const char *mood_name, char **msg) 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); @@ -638,12 +671,29 @@ out: 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 +706,8 @@ static int reload_current_mood(void) 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; } diff --git a/playlist.c b/playlist.c index d02ade3b..c145b0fd 100644 --- a/playlist.c +++ b/playlist.c @@ -14,12 +14,18 @@ /** \file playlist.c Functions for loading and saving playlists. */ -/** Structure used for adding entries to a playlist. */ +/** + * The state of a playlist instance. + * + * A structure of this type is allocated and initialized at playlist load time. + */ struct playlist_instance { /** The name of the playlist. */ char *name; /** The number of entries currently in the playlist. */ unsigned length; + /** Contains all valid paths of the playlist. */ + struct osl_table *score_table; }; static struct playlist_instance current_playlist; @@ -38,7 +44,7 @@ static int playlist_update_audio_file(const struct osl_row *aft_row) static int add_playlist_entry(char *path, void *data) { - struct playlist_instance *playlist = data; + struct playlist_instance *pi = data; struct osl_row *aft_row; int ret = aft_get_row_of_path(path, &aft_row); @@ -46,12 +52,12 @@ static int add_playlist_entry(char *path, void *data) PARA_NOTICE_LOG("%s: %s\n", path, para_strerror(-ret)); return 1; } - ret = score_add(aft_row, -playlist->length); + ret = score_add(aft_row, -pi->length, pi->score_table); if (ret < 0) { PARA_ERROR_LOG("failed to add %s: %s\n", path, para_strerror(-ret)); return ret; } - playlist->length++; + pi->length++; return 1; } @@ -103,11 +109,22 @@ int playlist_check_callback(struct afs_callback_arg *aca) check_playlist)); } -/** Free all resources of the current playlist, if any. */ -void playlist_unload(void) +/** + * Free all resources of the given/current playlist. + * + * \param pi NULL means to unload the current playlist. + */ +void playlist_unload(struct playlist_instance *pi) { + if (pi) { + score_close(pi->score_table); + free(pi->name); + free(pi); + return; + } if (!current_playlist.name) return; + score_clear(); free(current_playlist.name); current_playlist.name = NULL; current_playlist.length = 0; @@ -122,43 +139,78 @@ void playlist_unload(void) * corresponding row of the audio file table is added to the score table. * * \param name The name of the playlist to load. + * \param result Opaque, refers to the underlying score table. * \param msg Error message or playlist info is returned here. * * \return The length of the loaded playlist on success, negative error code * else. Files which are listed in the playlist, but are not contained in the * database are ignored. This is not considered an error. */ -int playlist_load(const char *name, char **msg) +int playlist_load(const char *name, struct playlist_instance **result, char **msg) { int ret; - struct playlist_instance *playlist = ¤t_playlist; + struct playlist_instance *pi; struct osl_object playlist_def; - ret = pl_get_def_by_name(name, &playlist_def); - if (ret < 0) { - *msg = make_message("could not read playlist %s\n", name); - return ret; + if (!name || !*name) { + if (msg) + *msg = make_message("empty playlist name\n"); + return -ERRNO_TO_PARA_ERROR(EINVAL); } - playlist_unload(); + ret = pl_get_def_by_name(name, &playlist_def); + if (ret < 0) + goto err; + pi = zalloc(sizeof(*pi)); + if (result) + score_open(&pi->score_table); ret = for_each_line(FELF_READ_ONLY, playlist_def.data, - playlist_def.size, add_playlist_entry, playlist); + playlist_def.size, add_playlist_entry, pi); osl_close_disk_object(&playlist_def); if (ret < 0) - goto err; + goto close_score_table; ret = -E_PLAYLIST_EMPTY; - if (!playlist->length) - goto err; - playlist->name = para_strdup(name); - *msg = make_message("loaded playlist %s (%u files)\n", playlist->name, - playlist->length); + if (pi->length == 0) + goto close_score_table; /* success */ - return current_playlist.length; + if (msg) + *msg = make_message("loaded playlist %s (%u files)\n", name, + pi->length); + pi->name = para_strdup(name); + if (result) + *result = pi; + else { + playlist_unload(NULL); + current_playlist = *pi; + } + return pi->length; +close_score_table: + if (result) + score_close(pi->score_table); + free(pi); err: PARA_NOTICE_LOG("unable to load playlist %s\n", name); - *msg = make_message("unable to load playlist %s\n", name); + if (msg) + *msg = make_message("unable to load playlist %s\n", name); return ret; } +/** + * Iterate over all admissible audio files of a playlist instance. + * + * This wrapper around \ref score_loop() is the playlist counterpart of \ref + * mood_loop(). + * + * \param pi 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 mood_loop(). + */ +int playlist_loop(struct playlist_instance *pi, osl_rbtree_loop_func *func, void *data) +{ + return score_loop(func, pi->score_table, data); +} + static int search_path(char *path, void *data) { if (strcmp(path, data)) @@ -196,7 +248,7 @@ static int handle_audio_file_event(enum afs_events event, void *data) } /* !was_admissible && is_admissible */ current_playlist.length++; - return score_add(row, 0); /* play it immediately */ + return score_add(row, 0, NULL); /* play it immediately */ } /** diff --git a/score.c b/score.c index 10cd254a..c03e3472 100644 --- a/score.c +++ b/score.c @@ -77,10 +77,10 @@ static struct osl_table_description score_table_desc = { }; /* On errors (negative return value) the content of score is undefined. */ -static int get_score_of_row(void *score_row, long *score) +static int get_score_of_row(struct osl_table *t, void *score_row, long *score) { struct osl_object obj; - int ret = osl(osl_get_object(score_table, score_row, SCORECOL_SCORE, &obj)); + int ret = osl(osl_get_object(t, score_row, SCORECOL_SCORE, &obj)); if (ret >= 0) *score = *(long *)obj.data; @@ -88,14 +88,15 @@ static int get_score_of_row(void *score_row, long *score) } /** - * Add an entry to the table of admissible files. + * Add a (row, score) pair to the score table. * - * \param aft_row The audio file to be added. - * \param score The score for this file. + * \param aft_row Identifies the audio file to be added. + * \param score The score value of the audio file. + * \param t NULL means to operate on the currently active table. * * \return The return value of the underlying call to osl_add_row(). */ -int score_add(const struct osl_row *aft_row, long score) +int score_add(const struct osl_row *aft_row, long score, struct osl_table *t) { int ret; struct osl_object score_objs[NUM_SCORE_COLUMNS]; @@ -112,7 +113,7 @@ int score_add(const struct osl_row *aft_row, long score) *(long *)(score_objs[SCORECOL_SCORE].data) = score; // PARA_DEBUG_LOG("adding %p\n", *(void **) (score_objs[SCORECOL_AFT_ROW].data)); - ret = osl(osl_add_row(score_table, score_objs)); + ret = osl(osl_add_row(t? t : score_table, score_objs)); if (ret < 0) { PARA_ERROR_LOG("%s\n", para_strerror(-ret)); free(score_objs[SCORECOL_SCORE].data); @@ -152,7 +153,7 @@ int score_update(const struct osl_row *aft_row, long percent) ret = osl(osl_get_nth_row(score_table, SCORECOL_SCORE, new_pos, &rrow)); if (ret < 0) return ret; - ret = get_score_of_row(rrow, &new_score); + ret = get_score_of_row(score_table, rrow, &new_score); if (ret < 0) return ret; new_score--; @@ -176,7 +177,7 @@ int get_score_and_aft_row(struct osl_row *score_row, long *score, struct osl_row **aft_row) { struct osl_object obj; - int ret = get_score_of_row(score_row, score); + int ret = get_score_of_row(score_table, score_row, score); if (ret < 0) return ret; @@ -187,26 +188,28 @@ int get_score_and_aft_row(struct osl_row *score_row, long *score, return 1; } -static int get_score_row_from_aft_row(const struct osl_row *aft_row, - struct osl_row **score_row) +static int get_score_row_from_aft_row(struct osl_table *t, + const struct osl_row *aft_row, struct osl_row **score_row) { struct osl_object obj = {.data = (struct osl_row *)aft_row, .size = sizeof(aft_row)}; - return osl(osl_get_row(score_table, SCORECOL_AFT_ROW, &obj, score_row)); + return osl(osl_get_row(t, SCORECOL_AFT_ROW, &obj, score_row)); } /** * Call the given function for each row of the score table. * * \param func Callback, called once per row. + * \param t NULL means to use the currently active score table. * \param data Passed verbatim to the callback. * * \return The return value of the underlying call to osl_rbtree_loop(). The * loop terminates early if the callback returns negative. */ -int score_loop(osl_rbtree_loop_func *func, void *data) +int score_loop(osl_rbtree_loop_func *func, struct osl_table *t, void *data) { - return osl(osl_rbtree_loop(score_table, SCORECOL_SCORE, data, func)); + return osl(osl_rbtree_loop(t? t : score_table, SCORECOL_SCORE, data, + func)); } /** @@ -229,7 +232,7 @@ int score_get_best(struct osl_row **aft_row, long *score) if (ret < 0) return ret; *aft_row = obj.data; - return get_score_of_row(row, score); + return get_score_of_row(score_table, row, score); } /** @@ -244,7 +247,7 @@ int score_get_best(struct osl_row **aft_row, long *score) int score_delete(const struct osl_row *aft_row) { struct osl_row *score_row; - int ret = get_score_row_from_aft_row(aft_row, &score_row); + int ret = get_score_row_from_aft_row(score_table, aft_row, &score_row); if (ret < 0) return ret; @@ -263,7 +266,7 @@ int score_delete(const struct osl_row *aft_row) bool row_belongs_to_score_table(const struct osl_row *aft_row) { struct osl_row *score_row; - int ret = get_score_row_from_aft_row(aft_row, &score_row); + int ret = get_score_row_from_aft_row(score_table, aft_row, &score_row); if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) return false; @@ -271,29 +274,56 @@ bool row_belongs_to_score_table(const struct osl_row *aft_row) return true; } -static void score_close(void) +/** + * Free all volatile objects, then close the table. + * + * \param t As returned from \ref score_open(). + * + * This either succeeds or terminates the calling process. + */ +void score_close(struct osl_table *t) +{ + assert(osl_close_table(t? t : score_table, OSL_FREE_VOLATILE) >= 0); +} + +static void close_global_table(void) { - osl_close_table(score_table, OSL_FREE_VOLATILE); - score_table = NULL; + score_close(NULL); } -static int score_open(__a_unused const char *dir) +static int open_global_table(__a_unused const char *dir) { - assert(osl_open_table(&score_table_desc, &score_table) >= 0); + assert(osl(osl_open_table(&score_table_desc, &score_table)) >= 0); return 1; } +/** + * Allocate a score table instance. + * + * \param result NULL means to open the currently active score table. + * + * Since the score table does no filesystem I/O, this function always succeeds. + * \sa \ref score_close(). + */ +void score_open(struct osl_table **result) +{ + if (result) + assert(osl(osl_open_table(&score_table_desc, result)) >= 0); + else + open_global_table(NULL); +} + /** * Remove all entries from the score table, but keep the table open. */ void score_clear(void) { - score_close(); - score_open(NULL); + close_global_table(); + open_global_table(NULL); } /** The score table stores (aft row, score) pairs in memory. */ const struct afs_table_operations score_ops = { - .open = score_open, - .close = score_close, + .open = open_global_table, + .close = close_global_table, };