comparison src/terminal.c @ 11939:ef1febf04d03 v8.0.0849

patch 8.0.0849: crash when job exit callback wipes the terminal commit https://github.com/vim/vim/commit/3c3a80dc59ccc0e0aabb9c8bd58ea84a801dbfc1 Author: Bram Moolenaar <Bram@vim.org> Date: Thu Aug 3 17:06:45 2017 +0200 patch 8.0.0849: crash when job exit callback wipes the terminal Problem: Crash when job exit callback wipes the terminal. Solution: Check for b_term to be NULL. (Yasuhiro Matsumoto, closes https://github.com/vim/vim/issues/1922) Implement options for term_start() to be able to test. Make term_wait() more reliable.
author Christian Brabandt <cb@256bit.org>
date Thu, 03 Aug 2017 17:15:04 +0200
parents c893d6c00497
children 8b9a1be7bb82
comparison
equal deleted inserted replaced
11938:4aeeb22b875d 11939:ef1febf04d03
38 * TODO: 38 * TODO:
39 * - don't allow exiting Vim when a terminal is still running a job 39 * - don't allow exiting Vim when a terminal is still running a job
40 * - MS-Windows: no redraw for 'updatetime' #1915 40 * - MS-Windows: no redraw for 'updatetime' #1915
41 * - in bash mouse clicks are inserting characters. 41 * - in bash mouse clicks are inserting characters.
42 * - mouse scroll: when over other window, scroll that window. 42 * - mouse scroll: when over other window, scroll that window.
43 * - add argument to term_wait() for waiting time.
43 * - For the scrollback buffer store lines in the buffer, only attributes in 44 * - For the scrollback buffer store lines in the buffer, only attributes in
44 * tl_scrollback. 45 * tl_scrollback.
45 * - When the job ends: 46 * - When the job ends:
46 * - Need an option or argument to drop the window+buffer right away, to be 47 * - Need an option or argument to drop the window+buffer right away, to be
47 * used for a shell or Vim. 'termfinish'; "close", "open" (open window when 48 * used for a shell or Vim. 'termfinish'; "close", "open" (open window when
144 #define KEY_BUF_LEN 200 145 #define KEY_BUF_LEN 200
145 146
146 /* 147 /*
147 * Functions with separate implementation for MS-Windows and Unix-like systems. 148 * Functions with separate implementation for MS-Windows and Unix-like systems.
148 */ 149 */
149 static int term_and_job_init(term_T *term, int rows, int cols, char_u *cmd); 150 static int term_and_job_init(term_T *term, int rows, int cols, char_u *cmd, jobopt_T *opt);
150 static void term_report_winsize(term_T *term, int rows, int cols); 151 static void term_report_winsize(term_T *term, int rows, int cols);
151 static void term_free_vterm(term_T *term); 152 static void term_free_vterm(term_T *term);
152 153
153 /************************************** 154 /**************************************
154 * 1. Generic code for all systems. 155 * 1. Generic code for all systems.
183 term->tl_cols_fixed = TRUE; 184 term->tl_cols_fixed = TRUE;
184 } 185 }
185 } 186 }
186 187
187 /* 188 /*
188 * ":terminal": open a terminal window and execute a job in it. 189 * Initialize job options for a terminal job.
189 */ 190 * Caller may overrule some of them.
190 void 191 */
191 ex_terminal(exarg_T *eap) 192 static void
193 init_job_options(jobopt_T *opt)
194 {
195 clear_job_options(opt);
196
197 opt->jo_mode = MODE_RAW;
198 opt->jo_out_mode = MODE_RAW;
199 opt->jo_err_mode = MODE_RAW;
200 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
201
202 opt->jo_io[PART_OUT] = JIO_BUFFER;
203 opt->jo_io[PART_ERR] = JIO_BUFFER;
204 opt->jo_set |= JO_OUT_IO + JO_ERR_IO;
205
206 opt->jo_modifiable[PART_OUT] = 0;
207 opt->jo_modifiable[PART_ERR] = 0;
208 opt->jo_set |= JO_OUT_MODIFIABLE + JO_ERR_MODIFIABLE;
209
210 opt->jo_set |= JO_OUT_BUF + JO_ERR_BUF;
211 }
212
213 /*
214 * Set job options mandatory for a terminal job.
215 */
216 static void
217 setup_job_options(jobopt_T *opt, int rows, int cols)
218 {
219 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
220 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
221 opt->jo_pty = TRUE;
222 opt->jo_term_rows = rows;
223 opt->jo_term_cols = cols;
224 }
225
226 static void
227 term_start(char_u *cmd, jobopt_T *opt)
192 { 228 {
193 exarg_T split_ea; 229 exarg_T split_ea;
194 win_T *old_curwin = curwin; 230 win_T *old_curwin = curwin;
195 term_T *term; 231 term_T *term;
196 char_u *cmd = eap->arg;
197 232
198 if (check_restricted() || check_secure()) 233 if (check_restricted() || check_secure())
199 return; 234 return;
200 235
201 term = (term_T *)alloc_clear(sizeof(term_T)); 236 term = (term_T *)alloc_clear(sizeof(term_T));
254 curbuf->b_p_ma = FALSE; 289 curbuf->b_p_ma = FALSE;
255 set_string_option_direct((char_u *)"buftype", -1, 290 set_string_option_direct((char_u *)"buftype", -1,
256 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0); 291 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
257 292
258 set_term_and_win_size(term); 293 set_term_and_win_size(term);
294 setup_job_options(opt, term->tl_rows, term->tl_cols);
259 295
260 /* System dependent: setup the vterm and start the job in it. */ 296 /* System dependent: setup the vterm and start the job in it. */
261 if (term_and_job_init(term, term->tl_rows, term->tl_cols, cmd) == OK) 297 if (term_and_job_init(term, term->tl_rows, term->tl_cols, cmd, opt) == OK)
262 { 298 {
263 /* store the size we ended up with */ 299 /* store the size we ended up with */
264 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols); 300 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
265 } 301 }
266 else 302 else
269 305
270 /* Wiping out the buffer will also close the window and call 306 /* Wiping out the buffer will also close the window and call
271 * free_terminal(). */ 307 * free_terminal(). */
272 do_buffer(DOBUF_WIPE, DOBUF_CURRENT, FORWARD, 0, TRUE); 308 do_buffer(DOBUF_WIPE, DOBUF_CURRENT, FORWARD, 0, TRUE);
273 } 309 }
310 }
311
312 /*
313 * ":terminal": open a terminal window and execute a job in it.
314 */
315 void
316 ex_terminal(exarg_T *eap)
317 {
318 jobopt_T opt;
319
320 init_job_options(&opt);
321 /* TODO: get options from before the command */
322
323 term_start(eap->arg, &opt);
274 } 324 }
275 325
276 /* 326 /*
277 * Free the scrollback buffer for "term". 327 * Free the scrollback buffer for "term".
278 */ 328 */
963 while (curwin->w_redr_type != 0) 1013 while (curwin->w_redr_type != 0)
964 update_screen(0); 1014 update_screen(0);
965 update_cursor(curbuf->b_term, FALSE); 1015 update_cursor(curbuf->b_term, FALSE);
966 1016
967 c = term_vgetc(); 1017 c = term_vgetc();
968 if (curbuf->b_term->tl_vterm == NULL 1018 if (!term_use_loop())
969 || !term_job_running(curbuf->b_term))
970 /* job finished while waiting for a character */ 1019 /* job finished while waiting for a character */
971 break; 1020 break;
972 1021
973 #ifdef UNIX 1022 #ifdef UNIX
974 may_send_sigint(c, curbuf->b_term->tl_job->jv_pid, 0); 1023 may_send_sigint(c, curbuf->b_term->tl_job->jv_pid, 0);
991 #endif 1040 #endif
992 c = term_vgetc(); 1041 c = term_vgetc();
993 #ifdef FEAT_CMDL_INFO 1042 #ifdef FEAT_CMDL_INFO
994 clear_showcmd(); 1043 clear_showcmd();
995 #endif 1044 #endif
996 if (curbuf->b_term->tl_vterm == NULL 1045 if (!term_use_loop())
997 || !term_job_running(curbuf->b_term))
998 /* job finished while waiting for a character */ 1046 /* job finished while waiting for a character */
999 break; 1047 break;
1000 1048
1001 if (termkey == 0 && c == '.') 1049 if (termkey == 0 && c == '.')
1002 { 1050 {
1612 return 0; 1660 return 0;
1613 return cell2attr(line->sb_cells + col); 1661 return cell2attr(line->sb_cells + col);
1614 } 1662 }
1615 1663
1616 /* 1664 /*
1617 * Set job options common for Unix and MS-Windows.
1618 */
1619 static void
1620 setup_job_options(jobopt_T *opt, int rows, int cols)
1621 {
1622 clear_job_options(opt);
1623 opt->jo_mode = MODE_RAW;
1624 opt->jo_out_mode = MODE_RAW;
1625 opt->jo_err_mode = MODE_RAW;
1626 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
1627
1628 opt->jo_io[PART_OUT] = JIO_BUFFER;
1629 opt->jo_io[PART_ERR] = JIO_BUFFER;
1630 opt->jo_set |= JO_OUT_IO + JO_ERR_IO;
1631
1632 opt->jo_modifiable[PART_OUT] = 0;
1633 opt->jo_modifiable[PART_ERR] = 0;
1634 opt->jo_set |= JO_OUT_MODIFIABLE + JO_ERR_MODIFIABLE;
1635
1636 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
1637 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
1638 opt->jo_pty = TRUE;
1639 opt->jo_set |= JO_OUT_BUF + JO_ERR_BUF;
1640
1641 opt->jo_term_rows = rows;
1642 opt->jo_term_cols = cols;
1643 }
1644
1645 /*
1646 * Create a new vterm and initialize it. 1665 * Create a new vterm and initialize it.
1647 */ 1666 */
1648 static void 1667 static void
1649 create_vterm(term_T *term, int rows, int cols) 1668 create_vterm(term_T *term, int rows, int cols)
1650 { 1669 {
2087 */ 2106 */
2088 void 2107 void
2089 f_term_start(typval_T *argvars, typval_T *rettv) 2108 f_term_start(typval_T *argvars, typval_T *rettv)
2090 { 2109 {
2091 char_u *cmd = get_tv_string_chk(&argvars[0]); 2110 char_u *cmd = get_tv_string_chk(&argvars[0]);
2092 exarg_T ea; 2111 jobopt_T opt;
2093 2112
2094 if (cmd == NULL) 2113 if (cmd == NULL)
2095 return; 2114 return;
2096 ea.arg = cmd; 2115 init_job_options(&opt);
2097 ex_terminal(&ea); 2116 /* TODO: allow more job options */
2117 if (argvars[1].v_type != VAR_UNKNOWN
2118 && get_job_options(&argvars[1], &opt,
2119 JO_TIMEOUT_ALL + JO_STOPONEXIT
2120 + JO_EXIT_CB + JO_CLOSE_CALLBACK) == FAIL)
2121 return;
2122
2123 term_start(cmd, &opt);
2098 2124
2099 if (curbuf->b_term != NULL) 2125 if (curbuf->b_term != NULL)
2100 rettv->vval.v_number = curbuf->b_fnum; 2126 rettv->vval.v_number = curbuf->b_fnum;
2101 } 2127 }
2102 2128
2107 f_term_wait(typval_T *argvars, typval_T *rettv UNUSED) 2133 f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
2108 { 2134 {
2109 buf_T *buf = term_get_buf(argvars); 2135 buf_T *buf = term_get_buf(argvars);
2110 2136
2111 if (buf == NULL) 2137 if (buf == NULL)
2112 return; 2138 {
2139 ch_log(NULL, "term_wait(): invalid argument");
2140 return;
2141 }
2113 2142
2114 /* Get the job status, this will detect a job that finished. */ 2143 /* Get the job status, this will detect a job that finished. */
2115 if (buf->b_term->tl_job != NULL) 2144 if (buf->b_term->tl_job == NULL
2116 (void)job_status(buf->b_term->tl_job); 2145 || STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
2117 2146 {
2118 /* Check for any pending channel I/O. */ 2147 /* The job is dead, keep reading channel I/O until the channel is
2119 vpeekc_any(); 2148 * closed. */
2120 ui_delay(10L, FALSE); 2149 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
2121 2150 {
2122 /* Flushing messages on channels is hopefully sufficient. 2151 mch_char_avail();
2123 * TODO: is there a better way? */ 2152 parse_queued_messages();
2124 parse_queued_messages(); 2153 ui_delay(10L, FALSE);
2154 }
2155 mch_char_avail();
2156 parse_queued_messages();
2157 }
2158 else
2159 {
2160 mch_char_avail();
2161 parse_queued_messages();
2162
2163 /* Wait for 10 msec for any channel I/O. */
2164 /* TODO: use delay from optional argument */
2165 ui_delay(10L, TRUE);
2166 mch_char_avail();
2167
2168 /* Flushing messages on channels is hopefully sufficient.
2169 * TODO: is there a better way? */
2170 parse_queued_messages();
2171 }
2125 } 2172 }
2126 2173
2127 # ifdef WIN3264 2174 # ifdef WIN3264
2128 2175
2129 /************************************** 2176 /**************************************
2207 * Create a new terminal of "rows" by "cols" cells. 2254 * Create a new terminal of "rows" by "cols" cells.
2208 * Store a reference in "term". 2255 * Store a reference in "term".
2209 * Return OK or FAIL. 2256 * Return OK or FAIL.
2210 */ 2257 */
2211 static int 2258 static int
2212 term_and_job_init(term_T *term, int rows, int cols, char_u *cmd) 2259 term_and_job_init(term_T *term, int rows, int cols, char_u *cmd, jobopt_T *opt)
2213 { 2260 {
2214 WCHAR *p; 2261 WCHAR *p;
2215 channel_T *channel = NULL; 2262 channel_T *channel = NULL;
2216 job_T *job = NULL; 2263 job_T *job = NULL;
2217 jobopt_T opt;
2218 DWORD error; 2264 DWORD error;
2219 HANDLE jo = NULL, child_process_handle, child_thread_handle; 2265 HANDLE jo = NULL, child_process_handle, child_thread_handle;
2220 void *winpty_err; 2266 void *winpty_err;
2221 void *spawn_config = NULL; 2267 void *spawn_config = NULL;
2222 2268
2296 winpty_spawn_config_free(spawn_config); 2342 winpty_spawn_config_free(spawn_config);
2297 vim_free(p); 2343 vim_free(p);
2298 2344
2299 create_vterm(term, rows, cols); 2345 create_vterm(term, rows, cols);
2300 2346
2301 setup_job_options(&opt, rows, cols); 2347 channel_set_job(channel, job, opt);
2302 channel_set_job(channel, job, &opt);
2303 2348
2304 job->jv_channel = channel; 2349 job->jv_channel = channel;
2305 job->jv_proc_info.hProcess = child_process_handle; 2350 job->jv_proc_info.hProcess = child_process_handle;
2306 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle); 2351 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
2307 job->jv_job_object = jo; 2352 job->jv_job_object = jo;
2379 * Start job for "cmd". 2424 * Start job for "cmd".
2380 * Store the pointers in "term". 2425 * Store the pointers in "term".
2381 * Return OK or FAIL. 2426 * Return OK or FAIL.
2382 */ 2427 */
2383 static int 2428 static int
2384 term_and_job_init(term_T *term, int rows, int cols, char_u *cmd) 2429 term_and_job_init(term_T *term, int rows, int cols, char_u *cmd, jobopt_T *opt)
2385 { 2430 {
2386 typval_T argvars[2]; 2431 typval_T argvars[2];
2387 jobopt_T opt;
2388 2432
2389 create_vterm(term, rows, cols); 2433 create_vterm(term, rows, cols);
2390 2434
2391 /* TODO: if the command is "NONE" only create a pty. */ 2435 /* TODO: if the command is "NONE" only create a pty. */
2392 argvars[0].v_type = VAR_STRING; 2436 argvars[0].v_type = VAR_STRING;
2393 argvars[0].vval.v_string = cmd; 2437 argvars[0].vval.v_string = cmd;
2394 setup_job_options(&opt, rows, cols); 2438
2395 term->tl_job = job_start(argvars, &opt); 2439 term->tl_job = job_start(argvars, opt);
2396 if (term->tl_job != NULL) 2440 if (term->tl_job != NULL)
2397 ++term->tl_job->jv_refcount; 2441 ++term->tl_job->jv_refcount;
2398 2442
2399 return term->tl_job != NULL 2443 return term->tl_job != NULL
2400 && term->tl_job->jv_channel != NULL 2444 && term->tl_job->jv_channel != NULL