X-Git-Url: http://git.tuebingen.mpg.de/?a=blobdiff_plain;f=play.c;h=d6224aeb4dc9e1356bd61ac3c6dcddd7e09da860;hb=refs%2Fheads%2Fpu;hp=bd94fcd9dedb8481185b79320919bce7ede63c70;hpb=cc210cce03c6e9bd4473159dc8cbcc95f24bfba4;p=paraslash.git diff --git a/play.c b/play.c index bd94fcd9..5b20315a 100644 --- a/play.c +++ b/play.c @@ -7,10 +7,12 @@ #include #include "recv_cmd.lsg.h" +#include "filter_cmd.lsg.h" #include "play_cmd.lsg.h" #include "write_cmd.lsg.h" #include "play.lsg.h" #include "para.h" +#include "lsu.h" #include "list.h" #include "error.h" #include "buffer_tree.h" @@ -22,16 +24,7 @@ #include "recv.h" #include "write.h" #include "fd.h" - -/** - * Besides playback tasks which correspond to the receiver/filter/writer nodes, - * para_play creates two further tasks: The play task and the i9e task. It is - * important whether a function can be called in the context of para_play or - * i9e or both. As a rule, all command handlers are called only in i9e context via - * the line handler (input mode) or the key handler (command mode) below. - * - * Playlist handling is done exclusively in play context. - */ +#include "interactive.h" /** Array of error strings. */ DEFINE_PARA_ERRLIST; @@ -49,7 +42,7 @@ static struct lls_parse_result *play_lpr; * Describes a request to change the state of para_play. * * There is only one variable of this type: \a rq of the global play task - * structure. Command handlers only set this variable and the post_select() + * structure. Command handlers only set this variable and the post_monitor() * function of the play task investigates its value during each iteration of * the scheduler run and performs the actual work. */ @@ -108,10 +101,6 @@ typedef int (*play_cmd_handler_t)(struct lls_parse_result *lpr); struct play_command_info { play_cmd_handler_t handler; }; -#define EXPORT_PLAY_CMD_HANDLER(_cmd) \ - const struct play_command_info lsg_play_cmd_com_ ## _cmd ## _user_data = { \ - .handler = com_ ## _cmd \ - }; static int loglevel = LL_WARNING; @@ -120,7 +109,7 @@ INIT_STDERR_LOGGING(loglevel); char *stat_item_values[NUM_STAT_ITEMS] = {NULL}; -static struct sched sched = {.max_fileno = 0}; +static struct sched sched; static struct play_task play_task, *pt = &play_task; #define AFH_RECV_CMD (lls_cmd(LSG_RECV_CMD_CMD_AFH, recv_cmd_suite)) @@ -139,7 +128,7 @@ static void handle_help_flags(void) if (OPT_GIVEN(DETAILED_HELP)) help = lls_long_help(CMD_PTR); - else if (OPT_GIVEN(HELP)) + else if (OPT_GIVEN(HELP) || lls_num_inputs(play_lpr) == 0) help = lls_short_help(CMD_PTR); else return; @@ -150,76 +139,38 @@ static void handle_help_flags(void) static void parse_config_or_die(int argc, char *argv[]) { - const struct lls_command *cmd = CMD_PTR; - int i, ret, cf_argc; - char *cf, *errctx, **cf_argv; - struct lls_parse_result *cf_lpr, *merged_lpr; + int i, ret; unsigned num_kmas; - void *map; - size_t sz; + char *errctx; - ret = lls(lls_parse(argc, argv, cmd, &play_lpr, &errctx)); - if (ret < 0) - goto fail; + ret = lls(lls_parse(argc, argv, CMD_PTR, &play_lpr, &errctx)); + if (ret < 0) { + if (errctx) + PARA_EMERG_LOG("%s\n", errctx); + free(errctx); + PARA_EMERG_LOG("failed to parse command line options: %s\n", + para_strerror(-ret)); + exit(EXIT_FAILURE); + } loglevel = OPT_UINT32_VAL(LOGLEVEL); version_handle_flag("play", OPT_GIVEN(VERSION)); - handle_help_flags(); - if (OPT_GIVEN(CONFIG_FILE)) - cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE)); - else { - char *home = para_homedir(); - cf = make_message("%s/.paraslash/play.conf", home); - free(home); - } - ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL); + handle_help_flags(); /* also handles the zero-arg case */ + ret = lsu_merge_config_file_options(OPT_STRING_VAL(CONFIG_FILE), + "play.conf", &play_lpr, CMD_PTR, play_suite, 0 /* flags */); if (ret < 0) { - if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT)) - goto free_cf; - if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE)) - goto free_cf; - ret = 0; - goto check_arg_count; + PARA_EMERG_LOG("failed to parse config file: %s\n", + para_strerror(-ret)); + exit(EXIT_FAILURE); } - ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx)); - para_munmap(map, sz); - if (ret < 0) - goto free_cf; - cf_argc = ret; - ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx)); - lls_free_argv(cf_argv); - if (ret < 0) - goto free_cf; - ret = lls(lls_merge(play_lpr, cf_lpr, cmd, &merged_lpr, &errctx)); - lls_free_parse_result(cf_lpr, cmd); - if (ret < 0) - goto free_cf; - lls_free_parse_result(play_lpr, cmd); - play_lpr = merged_lpr; loglevel = OPT_UINT32_VAL(LOGLEVEL); -check_arg_count: - ret = lls(lls_check_arg_count(play_lpr, 1, INT_MAX, &errctx)); - if (ret < 0) - goto free_cf; num_kmas = OPT_GIVEN(KEY_MAP); for (i = 0; i < num_kmas; i++) { const char *kma = lls_string_val(i, OPT_RESULT(KEY_MAP)); if (*kma && strchr(kma + 1, ':')) continue; PARA_EMERG_LOG("invalid key map arg: %s\n", kma); - goto free_cf; + exit(EXIT_FAILURE); } - ret = 1; -free_cf: - free(cf); - if (ret >= 0) - return; - lls_free_parse_result(play_lpr, cmd); -fail: - if (errctx) - PARA_EMERG_LOG("%s\n", errctx); - free(errctx); - PARA_EMERG_LOG("%s\n", para_strerror(-ret)); - exit(EXIT_FAILURE); } static char get_playback_state(void) @@ -233,6 +184,7 @@ static char get_playback_state(void) assert(false); }; +/* returns number of milliseconds */ static long unsigned get_play_time(void) { char state = get_playback_state(); @@ -242,21 +194,20 @@ static long unsigned get_play_time(void) return 0; if (pt->num_chunks == 0 || pt->seconds == 0) return 0; - /* where the stream started (in seconds) */ - result = pt->start_chunk * pt->seconds / pt->num_chunks; + /* where the stream started (in milliseconds) */ + result = 1000ULL * pt->start_chunk * pt->seconds / pt->num_chunks; if (pt->wn.btrn) { /* Add the uptime of the writer node */ struct timeval diff = {.tv_sec = 0}, wstime; btr_get_node_start(pt->wn.btrn, &wstime); if (wstime.tv_sec > 0) tv_diff(now, &wstime, &diff); - result += diff.tv_sec; + result += tv2ms(&diff); } - result = PARA_MIN(result, pt->seconds); + result = PARA_MIN(result, pt->seconds * 1000); result = PARA_MAX(result, 0UL); return result; } - static void wipe_receiver_node(void) { PARA_NOTICE_LOG("cleaning up receiver node\n"); @@ -272,7 +223,7 @@ static int get_playback_error(void) int err; if (!pt->wn.task) - return 0; + return 1; err = task_status(pt->wn.task); if (err >= 0) return 0; @@ -280,8 +231,7 @@ static int get_playback_error(void) return 0; if (task_status(pt->rn.task) >= 0) return 0; - if (err == -E_BTR_EOF || err == -E_RECV_EOF || err == -E_EOF - || err == -E_WRITE_COMMON_EOF) + if (err == -E_EOF) return 1; return err; } @@ -304,9 +254,10 @@ static int eof_cleanup(void) decoder = filter_get(pt->fn.filter_num); task_reap(&pt->fn.task); - if (decoder->close) + if (decoder && decoder->close) decoder->close(&pt->fn); btr_remove_node(&pt->fn.btrn); + lls_free_parse_result(pt->fn.lpr, FILTER_CMD(pt->fn.filter_num)); free(pt->fn.conf); memset(&pt->fn, 0, sizeof(struct filter_node)); @@ -330,7 +281,7 @@ static int shuffle_compare(__a_unused const void *a, __a_unused const void *b) static void init_shuffle_map(void) { unsigned n, num_inputs = lls_num_inputs(play_lpr); - shuffle_map = para_malloc(num_inputs * sizeof(unsigned)); + shuffle_map = arr_alloc(num_inputs, sizeof(unsigned)); for (n = 0; n < num_inputs; n++) shuffle_map[n] = n; if (!OPT_GIVEN(RANDOMIZE)) @@ -447,16 +398,16 @@ static int load_file(void) pt->rn.task = task_register( &(struct task_info) { .name = lls_command_name(AFH_RECV_CMD), - .pre_select = AFH_RECV->pre_select, - .post_select = AFH_RECV->post_select, + .pre_monitor = AFH_RECV->pre_monitor, + .post_monitor = AFH_RECV->post_monitor, .context = &pt->rn }, &sched); sprintf(buf, "%s decoder", af); pt->fn.task = task_register( &(struct task_info) { .name = buf, - .pre_select = decoder->pre_select, - .post_select = decoder->post_select, + .pre_monitor = decoder->pre_monitor, + .post_monitor = decoder->post_monitor, .context = &pt->fn }, &sched); register_writer_node(&pt->wn, pt->fn.btrn, &sched); @@ -471,6 +422,15 @@ static int next_valid_file(void) int i, j = pt->current_file; unsigned num_inputs = lls_num_inputs(play_lpr); + if (j == num_inputs - 1) { + switch (OPT_UINT32_VAL(END_OF_PLAYLIST)) { + case EOP_LOOP: break; + case EOP_STOP: + pt->playing = false; + return 0; + case EOP_QUIT: return -E_EOP; + } + } for (i = 0; i < num_inputs; i++) { j = (j + 1) % num_inputs; if (!pt->invalid[j]) @@ -511,8 +471,6 @@ static void kill_stream(void) task_notify(pt->wn.task, E_EOF); } -#ifdef HAVE_READLINE - /* only called from com_prev(), nec. only if we have readline */ static int previous_valid_file(void) { @@ -529,8 +487,6 @@ static int previous_valid_file(void) return -E_NO_VALID_FILES; } -#include "interactive.h" - /* * Define the default (internal) key mappings and helper functions to get the * key sequence or the command from a key id, which is what we obtain from @@ -614,28 +570,24 @@ static inline char *get_internal_key_map_seq(int key) return para_strdup(default_keyseqs[get_internal_key_map_idx(key)]); } -static char *get_user_key_map_seq(int key) +static char *get_key_map_seq(int key) { - const char *kma = get_user_key_map_arg(key); - const char *p = strchr(kma + 1, ':'); + const char *kma, *p; char *result; int len; - if (!p) - return NULL; + if (is_internal_key(key)) + return get_internal_key_map_seq(key); + kma = get_user_key_map_arg(key); + p = strchr(kma + 1, ':'); + assert(p); /* We checked earlier that kma contains a colon */ len = p - kma; - result = para_malloc(len + 1); + result = alloc(len + 1); memcpy(result, kma, len); result[len] = '\0'; return result; } -static char *get_key_map_seq(int key) -{ - return is_internal_key(key)? - get_internal_key_map_seq(key) : get_user_key_map_seq(key); -} - static char *get_key_map_seq_safe(int key) { const char hex[] = "0123456789abcdef"; @@ -644,7 +596,7 @@ static char *get_key_map_seq_safe(int key) if (len == 1 && isprint(*seq)) return seq; - sseq = para_malloc(2 + 2 * len + 1); + sseq = alloc(2 + 2 * len + 1); sseq[0] = '0'; sseq[1] = 'x'; for (n = 0; n < len; n++) { @@ -684,7 +636,7 @@ static char **get_mapped_keyseqs(void) char **result; int i; - result = para_malloc((NUM_MAPPED_KEYS + 1) * sizeof(char *)); + result = arr_alloc(NUM_MAPPED_KEYS + 1, sizeof(char *)); FOR_EACH_MAPPED_KEY(i) { char *seq = get_key_map_seq(i); result[i] = seq; @@ -709,9 +661,15 @@ I9E_DUMMY_COMPLETER(quit); I9E_DUMMY_COMPLETER(ff); static void help_completer(struct i9e_completion_info *ci, - struct i9e_completion_result *result) + struct i9e_completion_result *cr) { - result->matches = i9e_complete_commands(ci->word, pp_completers); + char *opts[] = {LSG_PLAY_CMD_HELP_OPTS, NULL}; + + if (ci->word[0] == '-') { + i9e_complete_option(opts, ci, cr); + return; + } + cr->matches = i9e_complete_commands(ci->word, pp_completers); } static struct i9e_completer pp_completers[] = { @@ -736,6 +694,11 @@ static void detach_stdout(void) btr_remove_node(&pt->btrn); } +#define EXPORT_PLAY_CMD_HANDLER(_cmd) \ + const struct play_command_info lsg_play_cmd_com_ ## _cmd ## _user_data = { \ + .handler = com_ ## _cmd \ + }; + static int com_quit(__a_unused struct lls_parse_result *lpr) { pt->rq = CRT_TERM_RQ; @@ -745,27 +708,15 @@ EXPORT_PLAY_CMD_HANDLER(quit); static int com_help(struct lls_parse_result *lpr) { - int i, ret; - char *buf, *errctx; + int i; + char *buf; size_t sz; - const struct lls_command *cmd; + unsigned n; + const struct lls_opt_result *r = + lls_opt_result(LSG_PLAY_CMD_HELP_OPT_LONG, lpr); + bool long_help = lls_opt_given(r); - ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx)); - if (ret < 0) { - if (errctx) - PARA_ERROR_LOG("%s\n", errctx); - free(errctx); - return ret; - } - if (lls_num_inputs(lpr) == 0) { - if (pt->background) { - for (i = 1; (cmd = lls_cmd(i, play_cmd_suite)); i++) { - sz = xasprintf(&buf, "%s\t%s\n", - lls_command_name(cmd), lls_purpose(cmd)); - btr_add_output(buf, sz, pt->btrn); - } - return 0; - } + if (!pt->background) { FOR_EACH_MAPPED_KEY(i) { bool internal = is_internal_key(i); int idx = get_key_map_idx(i); @@ -780,18 +731,8 @@ static int com_help(struct lls_parse_result *lpr) } return 0; } - ret = lls(lls_lookup_subcmd(lls_input(0, lpr), play_cmd_suite, - &errctx)); - if (ret < 0) { - if (errctx) - PARA_ERROR_LOG("%s\n", errctx); - free(errctx); - return ret; - } - cmd = lls_cmd(ret, play_cmd_suite); - buf = lls_long_help(cmd); - assert(buf); - btr_add_output(buf, strlen(buf), pt->btrn); + lsu_com_help(long_help, lpr, play_cmd_suite, NULL, &buf, &n); + btr_add_output(buf, n, pt->btrn); return 0; } EXPORT_PLAY_CMD_HANDLER(help); @@ -884,20 +825,21 @@ EXPORT_PLAY_CMD_HANDLER(play); static int com_pause(__a_unused struct lls_parse_result *lpr) { char state; - long unsigned seconds, ss; + uint64_t ms; + unsigned long cn; /* chunk num */ state = get_playback_state(); pt->playing = false; if (state != 'P') return 0; - seconds = get_play_time(); + ms = get_play_time(); pt->playing = false; - ss = 0; + cn = 0; if (pt->seconds > 0) - ss = seconds * pt->num_chunks / pt->seconds + 1; - ss = PARA_MAX(ss, 0UL); - ss = PARA_MIN(ss, pt->num_chunks); - pt->start_chunk = ss; + cn = ms * pt->num_chunks / pt->seconds / 1000 + 1; + cn = PARA_MIN(cn, pt->num_chunks); + pt->start_chunk = cn; + pt->rq = CRT_REPOS; kill_stream(); return 0; } @@ -969,7 +911,7 @@ static int com_jmp(struct lls_parse_result *lpr) return com_next(NULL); if (pt->playing && !pt->fn.btrn) return 0; - pt->start_chunk = percent * pt->num_chunks / 100; + pt->start_chunk = (uint64_t)percent * pt->num_chunks / 100; if (!pt->playing) return 0; pt->rq = CRT_REPOS; @@ -996,10 +938,10 @@ static int com_ff(struct lls_parse_result *lpr) return ret; if (pt->playing && !pt->fn.btrn) return 0; - seconds += get_play_time(); + seconds += (get_play_time() + 500) / 1000; seconds = PARA_MIN(seconds, (typeof(seconds))pt->seconds - 4); seconds = PARA_MAX(seconds, 0); - pt->start_chunk = pt->num_chunks * seconds / pt->seconds; + pt->start_chunk = (uint64_t)pt->num_chunks * seconds / pt->seconds; pt->start_chunk = PARA_MIN(pt->start_chunk, pt->num_chunks - 1); pt->start_chunk = PARA_MAX(pt->start_chunk, 0UL); if (!pt->playing) @@ -1095,10 +1037,14 @@ static void session_open(void) if (OPT_GIVEN(HISTORY_FILE)) history_file = para_strdup(OPT_STRING_VAL(HISTORY_FILE)); else { - char *home = para_homedir(); - history_file = make_message("%s/.paraslash/play.history", - home); - free(home); + const char *confdir = get_confdir(); + + ret = para_mkdir(confdir); + /* warn, but otherwise ignore mkdir error */ + if (ret < 0) + PARA_WARNING_LOG("Can not create %s: %s\n", confdir, + para_strerror(-ret)); + history_file = make_message("%s/play.history", confdir); } ici.history_file = history_file; ici.loglevel = loglevel; @@ -1111,7 +1057,7 @@ static void session_open(void) sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGWINCH, &act, NULL); - sched.select_function = i9e_select; + sched.poll_function = i9e_poll; ici.bound_keyseqs = get_mapped_keyseqs(); pt->btrn = ici.producer = btr_new_node(&(struct btr_node_description) @@ -1139,72 +1085,14 @@ static void session_update_time_string(char *str, unsigned len) if (btr_get_input_queue_size(pt->btrn) > 0) return; } - ie9_print_status_bar(str, len); + i9e_print_status_bar(str, len); } -/* - * If we are about to die we must call i9e_close() to reset the terminal. - * However, i9e_close() must be called in *this* context, i.e. from - * play_task.post_select() rather than from i9e_post_select(), because - * otherwise i9e would access freed memory upon return. So the play task must - * stay alive until the i9e task terminates. - * - * We achieve this by sending a fake SIGTERM signal via i9e_signal_dispatch() - * and reschedule. In the next iteration, i9e->post_select returns an error and - * terminates. Subsequent calls to i9e_get_error() then return negative and we - * are allowed to call i9e_close() and terminate as well. - */ -static int session_post_select(__a_unused struct sched *s) -{ - int ret; - - if (pt->background) - detach_stdout(); - else - attach_stdout(__FUNCTION__); - ret = i9e_get_error(); - if (ret < 0) { - kill_stream(); - i9e_close(); - para_log = stderr_log; - free(ici.history_file); - return ret; - } - if (get_playback_state() == 'X') - i9e_signal_dispatch(SIGTERM); - return 0; -} - -#else /* HAVE_READLINE */ - -static int session_post_select(struct sched *s) -{ - char c; - - if (!FD_ISSET(STDIN_FILENO, &s->rfds)) - return 0; - if (read(STDIN_FILENO, &c, 1)) - do_nothing; - kill_stream(); - return 1; -} - -static void session_open(void) -{ -} - -static void session_update_time_string(char *str, __a_unused unsigned len) -{ - printf("\r%s ", str); - fflush(stdout); -} -#endif /* HAVE_READLINE */ - -static void play_pre_select(struct sched *s, __a_unused void *context) +static void play_pre_monitor(struct sched *s, __a_unused void *context) { char state; - para_fd_set(STDIN_FILENO, &s->rfds, &s->max_fileno); + sched_monitor_readfd(STDIN_FILENO, s); state = get_playback_state(); if (state == 'R' || state == 'F' || state == 'X') return sched_min_delay(s); @@ -1224,7 +1112,7 @@ static unsigned get_time_string(char **result) length = pt->seconds; if (length == 0) return xasprintf(result, "0:00 [0:00] (0%%/0:00)"); - seconds = get_play_time(); + seconds = (get_play_time() + 500) / 1000; return xasprintf(result, "#%u: %d:%02d [%d:%02d] (%d%%/%d:%02d) %s", pt->current_file, seconds / 60, @@ -1238,18 +1126,24 @@ static unsigned get_time_string(char **result) ); } -static int play_post_select(struct sched *s, __a_unused void *context) +static int play_post_monitor(__a_unused struct sched *s, __a_unused void *context) { - int ret; + int ret, i9e_error; + if (pt->background) + detach_stdout(); + else + attach_stdout(__FUNCTION__); + i9e_error = i9e_get_error(); ret = eof_cleanup(); - if (ret < 0) { - pt->rq = CRT_TERM_RQ; - return 0; - } - ret = session_post_select(s); - if (ret < 0) - goto out; + if (pt->rq == CRT_TERM_RQ || i9e_error < 0) /* com_quit() or CTRL+D */ + kill_stream(); /* terminate receiver/filter/writer */ + if ((ret < 0 || pt->rq == CRT_TERM_RQ) && i9e_error >= 0) + i9e_signal_dispatch(SIGTERM); /* terminate the i9e task */ + if (ret < 0) /* unexpected error from the writer node */ + return ret; + if (ret != 0 && i9e_error < 0) /* eof, and i9e has died */ + return i9e_error; if (!pt->wn.btrn && !pt->fn.btrn) { char state = get_playback_state(); if (state == 'P' || state == 'R' || state == 'F') { @@ -1258,8 +1152,7 @@ static int play_post_select(struct sched *s, __a_unused void *context) if (ret < 0) { PARA_ERROR_LOG("%s\n", para_strerror(-ret)); pt->rq = CRT_TERM_RQ; - ret = 1; - goto out; + return 1; } pt->next_update = *now; } @@ -1273,46 +1166,59 @@ static int play_post_select(struct sched *s, __a_unused void *context) free(str); tv_add(now, &delay, &pt->next_update); } - ret = 1; -out: - return ret; + return 1; } /** * The main function of para_play. * - * \param argc Standard. - * \param argv Standard. + * \param argc See man page. + * \param argv See man page. + * + * para_play distributes its work by submitting various tasks to the paraslash + * scheduler. The receiver, filter and writer tasks, which are used to play an + * audio file, require one task each to maintain their underlying buffer tree + * node. These tasks only exist when an audio file is playing. * - * \return \p EXIT_FAILURE or \p EXIT_SUCCESS. + * The i9 task, which is submitted and maintained by the i9e subsystem, reads + * an input line and calls the corresponding command handler such as com_stop() + * which is implemented in this file. The command handlers typically write a + * request to the global play_task structure, whose contents are read and acted + * upon by another task, the play task. + * + * As a rule, playlist handling is performed exclusively in play context, i.e. + * in the post-monitor step of the play task, while command handlers are only + * called in i9e context. + * + * \return EXIT_FAILURE or EXIT_SUCCESS. */ int main(int argc, char *argv[]) { int ret; unsigned num_inputs; - /* needed this early to make help work */ - recv_init(); - - sched.default_timeout.tv_sec = 5; + sched.default_timeout = 5000; parse_config_or_die(argc, argv); - AFH_RECV->init(); session_open(); num_inputs = lls_num_inputs(play_lpr); init_shuffle_map(); - pt->invalid = para_calloc(sizeof(*pt->invalid) * num_inputs); + pt->invalid = arr_zalloc(num_inputs, sizeof(*pt->invalid)); pt->rq = CRT_FILE_CHANGE; - pt->current_file = num_inputs - 1; pt->playing = true; pt->task = task_register(&(struct task_info){ .name = "play", - .pre_select = play_pre_select, - .post_select = play_post_select, + .pre_monitor = play_pre_monitor, + .post_monitor = play_post_monitor, .context = pt, }, &sched); ret = schedule(&sched); sched_shutdown(&sched); + i9e_close(); + wipe_receiver_node(); + para_log = stderr_log; + free(ici.history_file); if (ret < 0) PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + free(get_confdir()); return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; }