diff options
author | Bram Moolenaar <Bram@vim.org> | 2016-03-12 13:43:33 +0100 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2016-03-12 13:43:33 +0100 |
commit | 8e2c942ce49f2555d7dc2088cf3aa856820c5e32 (patch) | |
tree | 2ff61484264444abee66e0085567fbf6d28c9199 /src/channel.c | |
parent | 77cdfd10382e01cc51f4ba1a9177032351843151 (diff) | |
download | vim-8e2c942ce49f2555d7dc2088cf3aa856820c5e32.zip |
patch 7.4.1539
Problem: Too much code in eval.c.
Solution: Move job and channel code to channel.c.
Diffstat (limited to 'src/channel.c')
-rw-r--r-- | src/channel.c | 1063 |
1 files changed, 1061 insertions, 2 deletions
diff --git a/src/channel.c b/src/channel.c index e17fc5207..10f1bc8d1 100644 --- a/src/channel.c +++ b/src/channel.c @@ -97,11 +97,24 @@ static proftime_T log_start; #endif void -ch_logfile(FILE *file) +ch_logfile(char_u *fname, char_u *opt) { + FILE *file = NULL; + if (log_fd != NULL) fclose(log_fd); + + if (*fname != NUL) + { + file = fopen((char *)fname, *opt == 'w' ? "w" : "a"); + if (file == NULL) + { + EMSG2(_(e_notopen), fname); + return; + } + } log_fd = file; + if (log_fd != NULL) { fprintf(log_fd, "==== start log session ====\n"); @@ -360,7 +373,7 @@ channel_still_useful(channel_T *channel) * killed. * Return TRUE if the channel was freed. */ - int + static int channel_may_free(channel_T *channel) { if (!channel_still_useful(channel)) @@ -372,6 +385,19 @@ channel_may_free(channel_T *channel) } /* + * Decrement the reference count on "channel" and maybe free it when it goes + * down to zero. Don't free it if there is a pending action. + * Returns TRUE when the channel is no longer referenced. + */ + int +channel_unref(channel_T *channel) +{ + if (channel != NULL && --channel->ch_refcount <= 0) + return channel_may_free(channel); + return FALSE; +} + +/* * Close a channel and free all its resources. */ void @@ -820,6 +846,65 @@ channel_open( return channel; } +/* + * Implements ch_open(). + */ + channel_T * +channel_open_func(typval_T *argvars) +{ + char_u *address; + char_u *p; + char *rest; + int port; + jobopt_T opt; + channel_T *channel; + + address = get_tv_string(&argvars[0]); + if (argvars[1].v_type != VAR_UNKNOWN + && (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL)) + { + EMSG(_(e_invarg)); + return NULL; + } + + /* parse address */ + p = vim_strchr(address, ':'); + if (p == NULL) + { + EMSG2(_(e_invarg2), address); + return NULL; + } + *p++ = NUL; + port = strtol((char *)p, &rest, 10); + if (*address == NUL || port <= 0 || *rest != NUL) + { + p[-1] = ':'; + EMSG2(_(e_invarg2), address); + return NULL; + } + + /* parse options */ + clear_job_options(&opt); + opt.jo_mode = MODE_JSON; + opt.jo_timeout = 2000; + if (get_job_options(&argvars[1], &opt, + JO_MODE_ALL + JO_CB_ALL + JO_WAITTIME + JO_TIMEOUT_ALL) == FAIL) + return NULL; + if (opt.jo_timeout < 0) + { + EMSG(_(e_invarg)); + return NULL; + } + + channel = channel_open((char *)address, port, opt.jo_waittime, NULL); + if (channel != NULL) + { + opt.jo_set = JO_ALL; + channel_set_options(channel, &opt); + } + return channel; +} + static void may_close_part(sock_T *fd) { @@ -2307,6 +2392,62 @@ channel_read_json_block( return FAIL; } +/* + * Common for ch_read() and ch_readraw(). + */ + void +common_channel_read(typval_T *argvars, typval_T *rettv, int raw) +{ + channel_T *channel; + int part; + jobopt_T opt; + int mode; + int timeout; + int id = -1; + typval_T *listtv = NULL; + + /* return an empty string by default */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + clear_job_options(&opt); + if (get_job_options(&argvars[1], &opt, JO_TIMEOUT + JO_PART + JO_ID) + == FAIL) + return; + + channel = get_channel_arg(&argvars[0], TRUE); + if (channel != NULL) + { + if (opt.jo_set & JO_PART) + part = opt.jo_part; + else + part = channel_part_read(channel); + mode = channel_get_mode(channel, part); + timeout = channel_get_timeout(channel, part); + if (opt.jo_set & JO_TIMEOUT) + timeout = opt.jo_timeout; + + if (raw || mode == MODE_RAW || mode == MODE_NL) + rettv->vval.v_string = channel_read_block(channel, part, timeout); + else + { + if (opt.jo_set & JO_ID) + id = opt.jo_id; + channel_read_json_block(channel, part, timeout, id, &listtv); + if (listtv != NULL) + { + *rettv = *listtv; + vim_free(listtv); + } + else + { + rettv->v_type = VAR_SPECIAL; + rettv->vval.v_number = VVAL_NONE; + } + } + } +} + # if defined(WIN32) || defined(FEAT_GUI_X11) || defined(FEAT_GUI_GTK) \ || defined(PROTO) /* @@ -2412,6 +2553,146 @@ channel_send(channel_T *channel, int part, char_u *buf, char *fun) return OK; } +/* + * Common for "ch_sendexpr()" and "ch_sendraw()". + * Returns the channel if the caller should read the response. + * Sets "part_read" to the the read fd. + * Otherwise returns NULL. + */ + channel_T * +send_common( + typval_T *argvars, + char_u *text, + int id, + int eval, + jobopt_T *opt, + char *fun, + int *part_read) +{ + channel_T *channel; + int part_send; + + channel = get_channel_arg(&argvars[0], TRUE); + if (channel == NULL) + return NULL; + part_send = channel_part_send(channel); + *part_read = channel_part_read(channel); + + clear_job_options(opt); + if (get_job_options(&argvars[2], opt, JO_CALLBACK + JO_TIMEOUT) == FAIL) + return NULL; + + /* Set the callback. An empty callback means no callback and not reading + * the response. With "ch_evalexpr()" and "ch_evalraw()" a callback is not + * allowed. */ + if (opt->jo_callback != NULL && *opt->jo_callback != NUL) + { + if (eval) + { + EMSG2(_("E917: Cannot use a callback with %s()"), fun); + return NULL; + } + channel_set_req_callback(channel, part_send, opt->jo_callback, id); + } + + if (channel_send(channel, part_send, text, fun) == OK + && opt->jo_callback == NULL) + return channel; + return NULL; +} + +/* + * common for "ch_evalexpr()" and "ch_sendexpr()" + */ + void +ch_expr_common(typval_T *argvars, typval_T *rettv, int eval) +{ + char_u *text; + typval_T *listtv; + channel_T *channel; + int id; + ch_mode_T ch_mode; + int part_send; + int part_read; + jobopt_T opt; + int timeout; + + /* return an empty string by default */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + channel = get_channel_arg(&argvars[0], TRUE); + if (channel == NULL) + return; + part_send = channel_part_send(channel); + + ch_mode = channel_get_mode(channel, part_send); + if (ch_mode == MODE_RAW || ch_mode == MODE_NL) + { + EMSG(_("E912: cannot use ch_evalexpr()/ch_sendexpr() with a raw or nl channel")); + return; + } + + id = channel_get_id(); + text = json_encode_nr_expr(id, &argvars[1], + ch_mode == MODE_JS ? JSON_JS : 0); + if (text == NULL) + return; + + channel = send_common(argvars, text, id, eval, &opt, + eval ? "ch_evalexpr" : "ch_sendexpr", &part_read); + vim_free(text); + if (channel != NULL && eval) + { + if (opt.jo_set & JO_TIMEOUT) + timeout = opt.jo_timeout; + else + timeout = channel_get_timeout(channel, part_read); + timeout = channel_get_timeout(channel, part_read); + if (channel_read_json_block(channel, part_read, timeout, id, &listtv) + == OK) + { + list_T *list = listtv->vval.v_list; + + /* Move the item from the list and then change the type to + * avoid the value being freed. */ + *rettv = list->lv_last->li_tv; + list->lv_last->li_tv.v_type = VAR_NUMBER; + free_tv(listtv); + } + } +} + +/* + * common for "ch_evalraw()" and "ch_sendraw()" + */ + void +ch_raw_common(typval_T *argvars, typval_T *rettv, int eval) +{ + char_u buf[NUMBUFLEN]; + char_u *text; + channel_T *channel; + int part_read; + jobopt_T opt; + int timeout; + + /* return an empty string by default */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + text = get_tv_string_buf(&argvars[1], buf); + channel = send_common(argvars, text, 0, eval, &opt, + eval ? "ch_evalraw" : "ch_sendraw", &part_read); + if (channel != NULL && eval) + { + if (opt.jo_set & JO_TIMEOUT) + timeout = opt.jo_timeout; + else + timeout = channel_get_timeout(channel, part_read); + rettv->vval.v_string = channel_read_block(channel, part_read, timeout); + } +} + # if (defined(UNIX) && !defined(HAVE_SELECT)) || defined(PROTO) /* * Add open channels to the poll struct. @@ -2695,4 +2976,782 @@ channel_get_timeout(channel_T *channel, int part) return channel->ch_part[part].ch_timeout; } +/* + * Get a callback from "arg". It can be a Funcref or a function name. + * When "arg" is zero return an empty string. + * Return NULL for an invalid argument. + */ + static char_u * +get_callback(typval_T *arg) +{ + if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) + return arg->vval.v_string; + if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) + return (char_u *)""; + EMSG(_("E999: Invalid callback argument")); + return NULL; +} + + static int +handle_mode(typval_T *item, jobopt_T *opt, ch_mode_T *modep, int jo) +{ + char_u *val = get_tv_string(item); + + opt->jo_set |= jo; + if (STRCMP(val, "nl") == 0) + *modep = MODE_NL; + else if (STRCMP(val, "raw") == 0) + *modep = MODE_RAW; + else if (STRCMP(val, "js") == 0) + *modep = MODE_JS; + else if (STRCMP(val, "json") == 0) + *modep = MODE_JSON; + else + { + EMSG2(_(e_invarg2), val); + return FAIL; + } + return OK; +} + + static int +handle_io(typval_T *item, int part, jobopt_T *opt) +{ + char_u *val = get_tv_string(item); + + opt->jo_set |= JO_OUT_IO << (part - PART_OUT); + if (STRCMP(val, "null") == 0) + opt->jo_io[part] = JIO_NULL; + else if (STRCMP(val, "pipe") == 0) + opt->jo_io[part] = JIO_PIPE; + else if (STRCMP(val, "file") == 0) + opt->jo_io[part] = JIO_FILE; + else if (STRCMP(val, "buffer") == 0) + opt->jo_io[part] = JIO_BUFFER; + else if (STRCMP(val, "out") == 0 && part == PART_ERR) + opt->jo_io[part] = JIO_OUT; + else + { + EMSG2(_(e_invarg2), val); + return FAIL; + } + return OK; +} + + void +clear_job_options(jobopt_T *opt) +{ + vim_memset(opt, 0, sizeof(jobopt_T)); +} + +/* + * Get the PART_ number from the first character of an option name. + */ + static int +part_from_char(int c) +{ + return c == 'i' ? PART_IN : c == 'o' ? PART_OUT: PART_ERR; +} + +/* + * Get the option entries from the dict in "tv", parse them and put the result + * in "opt". + * Only accept options in "supported". + * If an option value is invalid return FAIL. + */ + int +get_job_options(typval_T *tv, jobopt_T *opt, int supported) +{ + typval_T *item; + char_u *val; + dict_T *dict; + int todo; + hashitem_T *hi; + int part; + + opt->jo_set = 0; + if (tv->v_type == VAR_UNKNOWN) + return OK; + if (tv->v_type != VAR_DICT) + { + EMSG(_(e_invarg)); + return FAIL; + } + dict = tv->vval.v_dict; + if (dict == NULL) + return OK; + + todo = (int)dict->dv_hashtab.ht_used; + for (hi = dict->dv_hashtab.ht_array; todo > 0; ++hi) + if (!HASHITEM_EMPTY(hi)) + { + item = &dict_lookup(hi)->di_tv; + + if (STRCMP(hi->hi_key, "mode") == 0) + { + if (!(supported & JO_MODE)) + break; + if (handle_mode(item, opt, &opt->jo_mode, JO_MODE) == FAIL) + return FAIL; + } + else if (STRCMP(hi->hi_key, "in-mode") == 0) + { + if (!(supported & JO_IN_MODE)) + break; + if (handle_mode(item, opt, &opt->jo_in_mode, JO_IN_MODE) + == FAIL) + return FAIL; + } + else if (STRCMP(hi->hi_key, "out-mode") == 0) + { + if (!(supported & JO_OUT_MODE)) + break; + if (handle_mode(item, opt, &opt->jo_out_mode, JO_OUT_MODE) + == FAIL) + return FAIL; + } + else if (STRCMP(hi->hi_key, "err-mode") == 0) + { + if (!(supported & JO_ERR_MODE)) + break; + if (handle_mode(item, opt, &opt->jo_err_mode, JO_ERR_MODE) + == FAIL) + return FAIL; + } + else if (STRCMP(hi->hi_key, "in-io") == 0 + || STRCMP(hi->hi_key, "out-io") == 0 + || STRCMP(hi->hi_key, "err-io") == 0) + { + if (!(supported & JO_OUT_IO)) + break; + if (handle_io(item, part_from_char(*hi->hi_key), opt) == FAIL) + return FAIL; + } + else if (STRCMP(hi->hi_key, "in-name") == 0 + || STRCMP(hi->hi_key, "out-name") == 0 + || STRCMP(hi->hi_key, "err-name") == 0) + { + part = part_from_char(*hi->hi_key); + + if (!(supported & JO_OUT_IO)) + break; + opt->jo_set |= JO_OUT_NAME << (part - PART_OUT); + opt->jo_io_name[part] = + get_tv_string_buf_chk(item, opt->jo_io_name_buf[part]); + } + else if (STRCMP(hi->hi_key, "in-buf") == 0 + || STRCMP(hi->hi_key, "out-buf") == 0 + || STRCMP(hi->hi_key, "err-buf") == 0) + { + part = part_from_char(*hi->hi_key); + + if (!(supported & JO_OUT_IO)) + break; + opt->jo_set |= JO_OUT_BUF << (part - PART_OUT); + opt->jo_io_buf[part] = get_tv_number(item); + if (opt->jo_io_buf[part] <= 0) + { + EMSG2(_(e_invarg2), get_tv_string(item)); + return FAIL; + } + if (buflist_findnr(opt->jo_io_buf[part]) == NULL) + { + EMSGN(_(e_nobufnr), (long)opt->jo_io_buf[part]); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "in-top") == 0 + || STRCMP(hi->hi_key, "in-bot") == 0) + { + linenr_T *lp; + + if (!(supported & JO_OUT_IO)) + break; + if (hi->hi_key[3] == 't') + { + lp = &opt->jo_in_top; + opt->jo_set |= JO_IN_TOP; + } + else + { + lp = &opt->jo_in_bot; + opt->jo_set |= JO_IN_BOT; + } + *lp = get_tv_number(item); + if (*lp < 0) + { + EMSG2(_(e_invarg2), get_tv_string(item)); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "channel") == 0) + { + if (!(supported & JO_OUT_IO)) + break; + opt->jo_set |= JO_CHANNEL; + if (item->v_type != VAR_CHANNEL) + { + EMSG2(_(e_invarg2), "channel"); + return FAIL; + } + opt->jo_channel = item->vval.v_channel; + } + else if (STRCMP(hi->hi_key, "callback") == 0) + { + if (!(supported & JO_CALLBACK)) + break; + opt->jo_set |= JO_CALLBACK; + opt->jo_callback = get_callback(item); + if (opt->jo_callback == NULL) + { + EMSG2(_(e_invarg2), "callback"); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "out-cb") == 0) + { + if (!(supported & JO_OUT_CALLBACK)) + break; + opt->jo_set |= JO_OUT_CALLBACK; + opt->jo_out_cb = get_callback(item); + if (opt->jo_out_cb == NULL) + { + EMSG2(_(e_invarg2), "out-cb"); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "err-cb") == 0) + { + if (!(supported & JO_ERR_CALLBACK)) + break; + opt->jo_set |= JO_ERR_CALLBACK; + opt->jo_err_cb = get_callback(item); + if (opt->jo_err_cb == NULL) + { + EMSG2(_(e_invarg2), "err-cb"); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "close-cb") == 0) + { + if (!(supported & JO_CLOSE_CALLBACK)) + break; + opt->jo_set |= JO_CLOSE_CALLBACK; + opt->jo_close_cb = get_callback(item); + if (opt->jo_close_cb == NULL) + { + EMSG2(_(e_invarg2), "close-cb"); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "waittime") == 0) + { + if (!(supported & JO_WAITTIME)) + break; + opt->jo_set |= JO_WAITTIME; + opt->jo_waittime = get_tv_number(item); + } + else if (STRCMP(hi->hi_key, "timeout") == 0) + { + if (!(supported & JO_TIMEOUT)) + break; + opt->jo_set |= JO_TIMEOUT; + opt->jo_timeout = get_tv_number(item); + } + else if (STRCMP(hi->hi_key, "out-timeout") == 0) + { + if (!(supported & JO_OUT_TIMEOUT)) + break; + opt->jo_set |= JO_OUT_TIMEOUT; + opt->jo_out_timeout = get_tv_number(item); + } + else if (STRCMP(hi->hi_key, "err-timeout") == 0) + { + if (!(supported & JO_ERR_TIMEOUT)) + break; + opt->jo_set |= JO_ERR_TIMEOUT; + opt->jo_err_timeout = get_tv_number(item); + } + else if (STRCMP(hi->hi_key, "part") == 0) + { + if (!(supported & JO_PART)) + break; + opt->jo_set |= JO_PART; + val = get_tv_string(item); + if (STRCMP(val, "err") == 0) + opt->jo_part = PART_ERR; + else + { + EMSG2(_(e_invarg2), val); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "id") == 0) + { + if (!(supported & JO_ID)) + break; + opt->jo_set |= JO_ID; + opt->jo_id = get_tv_number(item); + } + else if (STRCMP(hi->hi_key, "stoponexit") == 0) + { + if (!(supported & JO_STOPONEXIT)) + break; + opt->jo_set |= JO_STOPONEXIT; + opt->jo_stoponexit = get_tv_string_buf_chk(item, + opt->jo_soe_buf); + if (opt->jo_stoponexit == NULL) + { + EMSG2(_(e_invarg2), "stoponexit"); + return FAIL; + } + } + else if (STRCMP(hi->hi_key, "exit-cb") == 0) + { + if (!(supported & JO_EXIT_CB)) + break; + opt->jo_set |= JO_EXIT_CB; + opt->jo_exit_cb = get_tv_string_buf_chk(item, opt->jo_ecb_buf); + if (opt->jo_exit_cb == NULL) + { + EMSG2(_(e_invarg2), "exit-cb"); + return FAIL; + } + } + else + break; + --todo; + } + if (todo > 0) + { + EMSG2(_(e_invarg2), hi->hi_key); + return FAIL; + } + + return OK; +} + +/* + * Get the channel from the argument. + * Returns NULL if the handle is invalid. + */ + channel_T * +get_channel_arg(typval_T *tv, int check_open) +{ + channel_T *channel = NULL; + + if (tv->v_type == VAR_JOB) + { + if (tv->vval.v_job != NULL) + channel = tv->vval.v_job->jv_channel; + } + else if (tv->v_type == VAR_CHANNEL) + { + channel = tv->vval.v_channel; + } + else + { + EMSG2(_(e_invarg2), get_tv_string(tv)); + return NULL; + } + + if (check_open && (channel == NULL || !channel_is_open(channel))) + { + EMSG(_("E906: not an open channel")); + return NULL; + } + return channel; +} + +static job_T *first_job = NULL; + + static void +job_free(job_T *job) +{ + ch_log(job->jv_channel, "Freeing job"); + if (job->jv_channel != NULL) + { + /* The link from the channel to the job doesn't count as a reference, + * thus don't decrement the refcount of the job. The reference from + * the job to the channel does count the refrence, decrement it and + * NULL the reference. We don't set ch_job_killed, unreferencing the + * job doesn't mean it stops running. */ + job->jv_channel->ch_job = NULL; + channel_unref(job->jv_channel); + } + mch_clear_job(job); + + if (job->jv_next != NULL) + job->jv_next->jv_prev = job->jv_prev; + if (job->jv_prev == NULL) + first_job = job->jv_next; + else + job->jv_prev->jv_next = job->jv_next; + + vim_free(job->jv_stoponexit); + vim_free(job->jv_exit_cb); + vim_free(job); +} + + void +job_unref(job_T *job) +{ + if (job != NULL && --job->jv_refcount <= 0) + { + /* Do not free the job when it has not ended yet and there is a + * "stoponexit" flag or an exit callback. */ + if (job->jv_status != JOB_STARTED + || (job->jv_stoponexit == NULL && job->jv_exit_cb == NULL)) + { + job_free(job); + } + else if (job->jv_channel != NULL) + { + /* Do remove the link to the channel, otherwise it hangs + * around until Vim exits. See job_free() for refcount. */ + job->jv_channel->ch_job = NULL; + channel_unref(job->jv_channel); + job->jv_channel = NULL; + } + } +} + +/* + * Allocate a job. Sets the refcount to one and sets options default. + */ + static job_T * +job_alloc(void) +{ + job_T *job; + + job = (job_T *)alloc_clear(sizeof(job_T)); + if (job != NULL) + { + job->jv_refcount = 1; + job->jv_stoponexit = vim_strsave((char_u *)"term"); + + if (first_job != NULL) + { + first_job->jv_prev = job; + job->jv_next = first_job; + } + first_job = job; + } + return job; +} + + void +job_set_options(job_T *job, jobopt_T *opt) +{ + if (opt->jo_set & JO_STOPONEXIT) + { + vim_free(job->jv_stoponexit); + if (opt->jo_stoponexit == NULL || *opt->jo_stoponexit == NUL) + job->jv_stoponexit = NULL; + else + job->jv_stoponexit = vim_strsave(opt->jo_stoponexit); + } + if (opt->jo_set & JO_EXIT_CB) + { + vim_free(job->jv_exit_cb); + if (opt->jo_exit_cb == NULL || *opt->jo_exit_cb == NUL) + job->jv_exit_cb = NULL; + else + job->jv_exit_cb = vim_strsave(opt->jo_exit_cb); + } +} + +/* + * Called when Vim is exiting: kill all jobs that have the "stoponexit" flag. + */ + void +job_stop_on_exit() +{ + job_T *job; + + for (job = first_job; job != NULL; job = job->jv_next) + if (job->jv_status == JOB_STARTED && job->jv_stoponexit != NULL) + mch_stop_job(job, job->jv_stoponexit); +} + +/* + * Called once in a while: check if any jobs with an "exit-cb" have ended. + */ + void +job_check_ended(void) +{ + static time_t last_check = 0; + time_t now; + job_T *job; + job_T *next; + + /* Only do this once in 10 seconds. */ + now = time(NULL); + if (last_check + 10 < now) + { + last_check = now; + for (job = first_job; job != NULL; job = next) + { + next = job->jv_next; + if (job->jv_status == JOB_STARTED && job->jv_exit_cb != NULL) + job_status(job); /* may free "job" */ + } + } +} + +/* + * "job_start()" function + */ + job_T * +job_start(typval_T *argvars) +{ + job_T *job; + char_u *cmd = NULL; +#if defined(UNIX) +# define USE_ARGV + char **argv = NULL; + int argc = 0; +#else + garray_T ga; +#endif + jobopt_T opt; + int part; + + job = job_alloc(); + if (job == NULL) + return NULL; + + job->jv_status = JOB_FAILED; + + /* Default mode is NL. */ + clear_job_options(&opt); + opt.jo_mode = MODE_NL; + if (get_job_options(&argvars[1], &opt, + JO_MODE_ALL + JO_CB_ALL + JO_TIMEOUT_ALL + + JO_STOPONEXIT + JO_EXIT_CB + JO_OUT_IO) == FAIL) + return job; + + /* Check that when io is "file" that there is a file name. */ + for (part = PART_OUT; part <= PART_IN; ++part) + if ((opt.jo_set & (JO_OUT_IO << (part - PART_OUT))) + && opt.jo_io[part] == JIO_FILE + && (!(opt.jo_set & (JO_OUT_NAME << (part - PART_OUT))) + || *opt.jo_io_name[part] == NUL)) + { + EMSG(_("E920: -io file requires -name to be set")); + return job; + } + + if ((opt.jo_set & JO_IN_IO) && opt.jo_io[PART_IN] == JIO_BUFFER) + { + buf_T *buf = NULL; + + /* check that we can find the buffer before starting the job */ + if (opt.jo_set & JO_IN_BUF) + { + buf = buflist_findnr(opt.jo_io_buf[PART_IN]); + if (buf == NULL) + EMSGN(_(e_nobufnr), (long)opt.jo_io_buf[PART_IN]); + } + else if (!(opt.jo_set & JO_IN_NAME)) + { + EMSG(_("E915: in-io buffer requires in-buf or in-name to be set")); + } + else + buf = buflist_find_by_name(opt.jo_io_name[PART_IN], FALSE); + if (buf == NULL) + return job; + if (buf->b_ml.ml_mfp == NULL) + { + char_u numbuf[NUMBUFLEN]; + char_u *s; + + if (opt.jo_set & JO_IN_BUF) + { + sprintf((char *)numbuf, "%d", opt.jo_io_buf[PART_IN]); + s = numbuf; + } + else + s = opt.jo_io_name[PART_IN]; + EMSG2(_("E918: buffer must be loaded: %s"), s); + return job; + } + job->jv_in_buf = buf; + } + + job_set_options(job, &opt); + +#ifndef USE_ARGV + ga_init2(&ga, (int)sizeof(char*), 20); +#endif + + if (argvars[0].v_type == VAR_STRING) + { + /* Command is a string. */ + cmd = argvars[0].vval.v_string; +#ifdef USE_ARGV + if (mch_parse_cmd(cmd, FALSE, &argv, &argc) == FAIL) + return job; + argv[argc] = NULL; +#endif + } + else if (argvars[0].v_type != VAR_LIST + || argvars[0].vval.v_list == NULL + || argvars[0].vval.v_list->lv_len < 1) + { + EMSG(_(e_invarg)); + return job; + } + else + { + list_T *l = argvars[0].vval.v_list; + listitem_T *li; + char_u *s; + +#ifdef USE_ARGV + /* Pass argv[] to mch_call_shell(). */ + argv = (char **)alloc(sizeof(char *) * (l->lv_len + 1)); + if (argv == NULL) + return job; +#endif + for (li = l->lv_first; li != NULL; li = li->li_next) + { + s = get_tv_string_chk(&li->li_tv); + if (s == NULL) + goto theend; +#ifdef USE_ARGV + argv[argc++] = (char *)s; +#else + /* Only escape when needed, double quotes are not always allowed. */ + if (li != l->lv_first && vim_strpbrk(s, (char_u *)" \t\"") != NULL) + { + s = vim_strsave_shellescape(s, FALSE, TRUE); + if (s == NULL) + goto theend; + ga_concat(&ga, s); + vim_free(s); + } + else + ga_concat(&ga, s); + if (li->li_next != NULL) + ga_append(&ga, ' '); +#endif + } +#ifdef USE_ARGV + argv[argc] = NULL; +#else + cmd = ga.ga_data; +#endif + } + +#ifdef USE_ARGV + if (ch_log_active()) + { + garray_T ga; + int i; + + ga_init2(&ga, (int)sizeof(char), 200); + for (i = 0; i < argc; ++i) + { + if (i > 0) + ga_concat(&ga, (char_u *)" "); + ga_concat(&ga, (char_u *)argv[i]); + } + ch_logs(NULL, "Starting job: %s", (char *)ga.ga_data); + ga_clear(&ga); + } + mch_start_job(argv, job, &opt); +#else + ch_logs(NULL, "Starting job: %s", (char *)cmd); + mch_start_job((char *)cmd, job, &opt); +#endif + + /* If the channel is reading from a buffer, write lines now. */ + if (job->jv_channel != NULL) + channel_write_in(job->jv_channel); + +theend: +#ifdef USE_ARGV + vim_free(argv); +#else + vim_free(ga.ga_data); +#endif + return job; +} + +/* + * Get the status of "job" and invoke the exit callback when needed. + * The returned string is not allocated. + */ + char * +job_status(job_T *job) +{ + char *result; + + if (job->jv_status == JOB_ENDED) + /* No need to check, dead is dead. */ + result = "dead"; + else if (job->jv_status == JOB_FAILED) + result = "fail"; + else + { + result = mch_job_status(job); + if (job->jv_status == JOB_ENDED) + ch_log(job->jv_channel, "Job ended"); + if (job->jv_status == JOB_ENDED && job->jv_exit_cb != NULL) + { + typval_T argv[3]; + typval_T rettv; + int dummy; + + /* invoke the exit callback; make sure the refcount is > 0 */ + ++job->jv_refcount; + argv[0].v_type = VAR_JOB; + argv[0].vval.v_job = job; + argv[1].v_type = VAR_NUMBER; + argv[1].vval.v_number = job->jv_exitval; + call_func(job->jv_exit_cb, (int)STRLEN(job->jv_exit_cb), + &rettv, 2, argv, 0L, 0L, &dummy, TRUE, NULL); + clear_tv(&rettv); + --job->jv_refcount; + } + if (job->jv_status == JOB_ENDED && job->jv_refcount == 0) + { + /* The job was already unreferenced, now that it ended it can be + * freed. Careful: caller must not use "job" after this! */ + job_free(job); + } + } + return result; +} + + int +job_stop(job_T *job, typval_T *argvars) +{ + char_u *arg; + + if (argvars[1].v_type == VAR_UNKNOWN) + arg = (char_u *)""; + else + { + arg = get_tv_string_chk(&argvars[1]); + if (arg == NULL) + { + EMSG(_(e_invarg)); + return 0; + } + } + ch_logs(job->jv_channel, "Stopping job with '%s'", (char *)arg); + if (mch_stop_job(job, arg) == FAIL) + return 0; + + /* Assume that "hup" does not kill the job. */ + if (job->jv_channel != NULL && STRCMP(arg, "hup") != 0) + job->jv_channel->ch_job_killed = TRUE; + + /* We don't try freeing the job, obviously the caller still has a + * reference to it. */ + return 1; +} + #endif /* FEAT_JOB_CHANNEL */ |