+ sl[2 * num_atts] = NULL;
+ i9e_extract_completions(ci->word, sl, &cr->matches);
+out:
+ free(buf);
+ free_argv(sl);
+}
+
+static void lsatt_completer(struct i9e_completion_info *ci,
+ struct i9e_completion_result *cr)
+{
+ char *opts[] = {"-i", "-l", "-r", NULL};
+ i9e_complete_option(opts, ci, cr);
+}
+
+static void mvatt_completer(struct i9e_completion_info *ci,
+ struct i9e_completion_result *cr)
+{
+ complete_attributes(ci->word, &cr->matches);
+}
+
+static void rmatt_completer(struct i9e_completion_info *ci,
+ struct i9e_completion_result *cr)
+{
+ complete_attributes(ci->word, &cr->matches);
+}
+
+static void check_completer(struct i9e_completion_info *ci,
+ struct i9e_completion_result *cr)
+{
+ char *opts[] = {"-a", "-m", "-p", NULL};
+ i9e_complete_option(opts, ci, cr);
+}
+
+static void rm_completer(struct i9e_completion_info *ci,
+ struct i9e_completion_result *cr)
+{
+ char *opts[] = {"-v", "-f", "-p", NULL};
+
+ if (ci->word[0] == '-') {
+ i9e_complete_option(opts, ci, cr);
+ return;
+ }
+ cr->filename_completion_desired = true;
+}
+
+static void touch_completer(struct i9e_completion_info *ci,
+ struct i9e_completion_result *cr)
+{
+ char *opts[] = {"-n=", "-l=", "-y=", "-i=", "-a=", "-v", "-p", NULL};
+
+ if (ci->word[0] == '-')
+ i9e_complete_option(opts, ci, cr);
+ cr->filename_completion_desired = true;
+}
+
+static void cpsi_completer(struct i9e_completion_info *ci,
+ struct i9e_completion_result *cr)
+{
+ char *opts[] = {"-a", "-y", "-i", "-l", "-n", "-v", NULL};
+
+ if (ci->word[0] == '-')
+ i9e_complete_option(opts, ci, cr);
+ cr->filename_completion_desired = true;
+}
+
+static void select_completer(struct i9e_completion_info *ci,
+ struct i9e_completion_result *cr)
+{
+ char *mood_buf, *pl_buf, **moods, **playlists, **mops;
+ int num_moods, num_pl, i, n, ret;
+
+ ret = execute_client_command("lsmood", &mood_buf);
+ if (ret < 0)
+ return;
+ ret = execute_client_command("lspl", &pl_buf);
+ if (ret < 0)
+ goto free_mood_buf;
+
+ ret = create_argv(mood_buf, "\n", &moods);
+ if (ret < 0)
+ goto free_pl_buf;
+ num_moods = ret;
+ ret = create_argv(pl_buf, "\n", &playlists);
+ if (ret < 0)
+ goto free_moods;
+ num_pl = ret;
+ n = num_moods + num_pl;
+ mops = para_malloc((n + 1) * sizeof(char *));
+ for (i = 0; i < num_moods; i++)
+ mops[i] = make_message("m/%s", moods[i]);
+ for (i = 0; i < num_pl; i++)
+ mops[num_moods + i] = make_message("p/%s", playlists[i]);
+ mops[n] = NULL;
+ i9e_extract_completions(ci->word, mops, &cr->matches);
+ free_argv(mops);
+ free_argv(playlists);
+free_moods:
+ free_argv(moods);
+free_pl_buf:
+ free(pl_buf);
+free_mood_buf:
+ free(mood_buf);
+}
+
+#define DEFINE_BLOB_COMPLETER(cmd, blob_type) \
+ static void cmd ## blob_type ## _completer( \
+ struct i9e_completion_info *ci, \
+ struct i9e_completion_result *cr) \
+ {complete_ ## cmd ## blob(#blob_type, ci, cr);}
+
+DEFINE_BLOB_COMPLETER(add, mood)
+DEFINE_BLOB_COMPLETER(add, lyr)
+DEFINE_BLOB_COMPLETER(add, img)
+DEFINE_BLOB_COMPLETER(add, pl)
+DEFINE_BLOB_COMPLETER(cat, mood)
+DEFINE_BLOB_COMPLETER(cat, lyr)
+DEFINE_BLOB_COMPLETER(cat, img)
+DEFINE_BLOB_COMPLETER(cat, pl)
+DEFINE_BLOB_COMPLETER(ls, mood)
+DEFINE_BLOB_COMPLETER(ls, lyr)
+DEFINE_BLOB_COMPLETER(ls, img)
+DEFINE_BLOB_COMPLETER(ls, pl)
+DEFINE_BLOB_COMPLETER(rm, mood)
+DEFINE_BLOB_COMPLETER(rm, lyr)
+DEFINE_BLOB_COMPLETER(rm, img)
+DEFINE_BLOB_COMPLETER(rm, pl)
+DEFINE_BLOB_COMPLETER(mv, mood)
+DEFINE_BLOB_COMPLETER(mv, lyr)
+DEFINE_BLOB_COMPLETER(mv, img)
+DEFINE_BLOB_COMPLETER(mv, pl)
+
+static int client_i9e_line_handler(char *line)
+{
+ int ret;
+
+ client_disconnect(ct);
+ PARA_DEBUG_LOG("line: %s\n", line);
+ ret = make_client_argv(line);
+ if (ret <= 0)
+ return ret;
+ ret = client_connect(ct, &sched, NULL, NULL);
+ if (ret < 0)
+ return ret;
+ i9e_attach_to_stdout(ct->btrn);
+ return 1;
+}
+
+static struct i9e_completer completers[] = {
+ SERVER_COMPLETERS
+ AFS_COMPLETERS
+ {.name = NULL}
+};
+
+__noreturn static void interactive_session(void)
+{
+ int ret;
+ char *history_file;
+ struct sigaction act;
+ struct i9e_client_info ici = {
+ .fds = {0, 1, 2},
+ .prompt = "para_client> ",
+ .line_handler = client_i9e_line_handler,
+ .loglevel = client_loglevel,
+ .completers = completers,
+ };
+
+ PARA_NOTICE_LOG("\n%s\n", version_text("client"));
+ if (ct->conf.history_file_given)
+ history_file = para_strdup(ct->conf.history_file_arg);
+ else {
+ char *home = para_homedir();
+ history_file = make_message("%s/.paraslash/client.history",
+ home);
+ free(home);
+ }
+ ici.history_file = history_file;
+
+ act.sa_handler = i9e_signal_dispatch;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ sigaction(SIGINT, &act, NULL);
+ sched.select_function = i9e_select;
+
+ ret = i9e_open(&ici, &sched);
+ if (ret < 0)
+ goto out;
+ para_log = i9e_log;
+ ret = schedule(&sched);
+ i9e_close();
+ para_log = stderr_log;
+out:
+ if (ret < 0)
+ PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+ exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
+ //client_close(ct);
+}
+
+__noreturn static void print_completions(void)
+{
+ int ret = i9e_print_completions(completers);
+ exit(ret <= 0? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+#else /* HAVE_READLINE */
+
+__noreturn static void interactive_session(void)
+{
+ PARA_EMERG_LOG("interactive sessions not available\n");
+ exit(EXIT_FAILURE);
+}
+
+__noreturn static void print_completions(void)
+{
+ PARA_EMERG_LOG("command completion not available\n");
+ exit(EXIT_FAILURE);
+}
+
+#endif /* HAVE_READLINE */
+
+static int supervisor_post_select(struct sched *s, __a_unused struct task *t)
+{
+ if (ct->task.error < 0)
+ return ct->task.error;