]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge topic branch t/afs-ls-a into master
authorAndre Noll <maan@tuebingen.mpg.de>
Sun, 15 Oct 2023 15:54:41 +0000 (17:54 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 15 Oct 2023 15:55:31 +0000 (17:55 +0200)
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.

1  2 
NEWS.md
afs.c
aft.c
mood.c

diff --combined NEWS.md
index a0484f09d177cb3f7effdd4962650cdce28281ad,598db71fae93734158509fa5f76853ceb09ccb7b..22816b1911a7613a233481df0ff4ac7e07bb6a81
+++ 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 865effde671a4848298ab09d502898c6225a937c,9e679dcf14ebee2a36a83aa02882ba96950e7b21..3083084c25ac793cfd3a5cad1f93c6e78f0fa413
--- 1/afs.c
--- 2/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 0aa9c6ba7ac4588f1889bf4dda8ec52d3b9cf4d7,5ecd7720fab896ac17c346e425da61f40b1ce13e..4ea8641b2acce666e53d7005bd755ae2099cb07a
--- 1/aft.c
--- 2/aft.c
+++ b/aft.c
@@@ -6,7 -6,6 +6,7 @@@
  #include <sys/mman.h>
  #include <fnmatch.h>
  #include <sys/shm.h>
 +#include <dirent.h>
  #include <osl.h>
  #include <lopsub.h>
  
        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 e85cf36a35b9e533370bddd95c5c5db74a214e85,18c02f7fa2f0529175c4b84bfc73600354fa3211..b4d50c88e73533c69e7473c072e3aaef1c196d99
--- 1/mood.c
--- 2/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;
  
        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, &current_mood->stats);
                if (ret < 0)
                        return ret;
-               return add_to_score_table(aft_row, &current_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(
        );
  }
  
- /** 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};
        }
        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(
        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)
  {
                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;
  }