1// This is an open source non-commercial project. Dear PVS-Studio, please check
2// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4#include <string.h>
5#include <assert.h>
6#include <stdbool.h>
7#include <stdlib.h>
8
9#include <uv.h>
10
11#include "nvim/ascii.h"
12#include "nvim/lib/kvec.h"
13#include "nvim/log.h"
14#include "nvim/event/loop.h"
15#include "nvim/event/libuv_process.h"
16#include "nvim/event/rstream.h"
17#include "nvim/os/shell.h"
18#include "nvim/os/signal.h"
19#include "nvim/types.h"
20#include "nvim/main.h"
21#include "nvim/vim.h"
22#include "nvim/message.h"
23#include "nvim/memory.h"
24#include "nvim/ui.h"
25#include "nvim/screen.h"
26#include "nvim/memline.h"
27#include "nvim/option_defs.h"
28#include "nvim/charset.h"
29#include "nvim/strings.h"
30
31#define DYNAMIC_BUFFER_INIT { NULL, 0, 0 }
32#define NS_1_SECOND 1000000000U // 1 second, in nanoseconds
33#define OUT_DATA_THRESHOLD 1024 * 10U // 10KB, "a few screenfuls" of data.
34
35typedef struct {
36 char *data;
37 size_t cap, len;
38} DynamicBuffer;
39
40#ifdef INCLUDE_GENERATED_DECLARATIONS
41# include "os/shell.c.generated.h"
42#endif
43
44/// Builds the argument vector for running the user-configured 'shell' (p_sh)
45/// with an optional command prefixed by 'shellcmdflag' (p_shcf). E.g.:
46///
47/// ["shell", "-extra_args", "-shellcmdflag", "command with spaces"]
48///
49/// @param cmd Command string, or NULL to run an interactive shell.
50/// @param extra_args Extra arguments to the shell, or NULL.
51/// @return Newly allocated argument vector. Must be freed with shell_free_argv.
52char **shell_build_argv(const char *cmd, const char *extra_args)
53 FUNC_ATTR_NONNULL_RET
54{
55 size_t argc = tokenize(p_sh, NULL) + (cmd ? tokenize(p_shcf, NULL) : 0);
56 char **rv = xmalloc((argc + 4) * sizeof(*rv));
57
58 // Split 'shell'
59 size_t i = tokenize(p_sh, rv);
60
61 if (extra_args) {
62 rv[i++] = xstrdup(extra_args); // Push a copy of `extra_args`
63 }
64
65 if (cmd) {
66 i += tokenize(p_shcf, rv + i); // Split 'shellcmdflag'
67 rv[i++] = shell_xescape_xquote(cmd); // Copy (and escape) `cmd`.
68 }
69
70 rv[i] = NULL;
71
72 assert(rv[0]);
73
74 return rv;
75}
76
77/// Releases the memory allocated by `shell_build_argv`.
78///
79/// @param argv The argument vector.
80void shell_free_argv(char **argv)
81{
82 char **p = argv;
83 if (p == NULL) {
84 // Nothing was allocated, return
85 return;
86 }
87 while (*p != NULL) {
88 // Free each argument
89 xfree(*p);
90 p++;
91 }
92 xfree(argv);
93}
94
95/// Joins shell arguments from `argv` into a new string.
96/// If the result is too long it is truncated with ellipsis ("...").
97///
98/// @returns[allocated] `argv` joined to a string.
99char *shell_argv_to_str(char **const argv)
100 FUNC_ATTR_NONNULL_ALL
101{
102 size_t n = 0;
103 char **p = argv;
104 char *rv = xcalloc(256, sizeof(*rv));
105 const size_t maxsize = (256 * sizeof(*rv));
106 if (*p == NULL) {
107 return rv;
108 }
109 while (*p != NULL) {
110 xstrlcat(rv, "'", maxsize);
111 xstrlcat(rv, *p, maxsize);
112 n = xstrlcat(rv, "' ", maxsize);
113 if (n >= maxsize) {
114 break;
115 }
116 p++;
117 }
118 if (n < maxsize) {
119 rv[n - 1] = '\0';
120 } else {
121 // Command too long, show ellipsis: "/bin/bash 'foo' 'bar'..."
122 rv[maxsize - 4] = '.';
123 rv[maxsize - 3] = '.';
124 rv[maxsize - 2] = '.';
125 rv[maxsize - 1] = '\0';
126 }
127 return rv;
128}
129
130/// Calls the user-configured 'shell' (p_sh) for running a command or wildcard
131/// expansion.
132///
133/// @param cmd The command to execute, or NULL to run an interactive shell.
134/// @param opts Options that control how the shell will work.
135/// @param extra_args Extra arguments to the shell, or NULL.
136///
137/// @return shell command exit code
138int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args)
139{
140 DynamicBuffer input = DYNAMIC_BUFFER_INIT;
141 char *output = NULL, **output_ptr = NULL;
142 int current_state = State;
143 bool forward_output = true;
144
145 // While the child is running, ignore terminating signals
146 signal_reject_deadly();
147
148 if (opts & (kShellOptHideMess | kShellOptExpand)) {
149 forward_output = false;
150 } else {
151 State = EXTERNCMD;
152
153 if (opts & kShellOptWrite) {
154 read_input(&input);
155 }
156
157 if (opts & kShellOptRead) {
158 output_ptr = &output;
159 forward_output = false;
160 } else if (opts & kShellOptDoOut) {
161 // Caller has already redirected output
162 forward_output = false;
163 }
164 }
165
166 size_t nread;
167 int exitcode = do_os_system(shell_build_argv((char *)cmd, (char *)extra_args),
168 input.data, input.len, output_ptr, &nread,
169 emsg_silent, forward_output);
170 xfree(input.data);
171
172 if (output) {
173 (void)write_output(output, nread, true);
174 xfree(output);
175 }
176
177 if (!emsg_silent && exitcode != 0 && !(opts & kShellOptSilent)) {
178 MSG_PUTS(_("\nshell returned "));
179 msg_outnum(exitcode);
180 msg_putchar('\n');
181 }
182
183 State = current_state;
184 signal_accept_deadly();
185
186 return exitcode;
187}
188
189/// os_system - synchronously execute a command in the shell
190///
191/// example:
192/// char *output = NULL;
193/// size_t nread = 0;
194/// char *argv[] = {"ls", "-la", NULL};
195/// int exitcode = os_sytem(argv, NULL, 0, &output, &nread);
196///
197/// @param argv The commandline arguments to be passed to the shell. `argv`
198/// will be consumed.
199/// @param input The input to the shell (NULL for no input), passed to the
200/// stdin of the resulting process.
201/// @param len The length of the input buffer (not used if `input` == NULL)
202/// @param[out] output Pointer to a location where the output will be
203/// allocated and stored. Will point to NULL if the shell
204/// command did not output anything. If NULL is passed,
205/// the shell output will be ignored.
206/// @param[out] nread the number of bytes in the returned buffer (if the
207/// returned buffer is not NULL)
208/// @return the return code of the process, -1 if the process couldn't be
209/// started properly
210int os_system(char **argv,
211 const char *input,
212 size_t len,
213 char **output,
214 size_t *nread) FUNC_ATTR_NONNULL_ARG(1)
215{
216 return do_os_system(argv, input, len, output, nread, true, false);
217}
218
219static int do_os_system(char **argv,
220 const char *input,
221 size_t len,
222 char **output,
223 size_t *nread,
224 bool silent,
225 bool forward_output)
226{
227 out_data_decide_throttle(0); // Initialize throttle decider.
228 out_data_ring(NULL, 0); // Initialize output ring-buffer.
229 bool has_input = (input != NULL && input[0] != '\0');
230
231 // the output buffer
232 DynamicBuffer buf = DYNAMIC_BUFFER_INIT;
233 stream_read_cb data_cb = system_data_cb;
234 if (nread) {
235 *nread = 0;
236 }
237
238 if (forward_output) {
239 data_cb = out_data_cb;
240 } else if (!output) {
241 data_cb = NULL;
242 }
243
244 // Copy the program name in case we need to report an error.
245 char prog[MAXPATHL];
246 xstrlcpy(prog, argv[0], MAXPATHL);
247
248 LibuvProcess uvproc = libuv_process_init(&main_loop, &buf);
249 Process *proc = &uvproc.process;
250 MultiQueue *events = multiqueue_new_child(main_loop.events);
251 proc->events = events;
252 proc->argv = argv;
253 int status = process_spawn(proc, has_input, true, true);
254 if (status) {
255 loop_poll_events(&main_loop, 0);
256 // Failed, probably 'shell' is not executable.
257 if (!silent) {
258 MSG_PUTS(_("\nshell failed to start: "));
259 msg_outtrans((char_u *)os_strerror(status));
260 MSG_PUTS(": ");
261 msg_outtrans((char_u *)prog);
262 msg_putchar('\n');
263 }
264 multiqueue_free(events);
265 return -1;
266 }
267
268 // Note: unlike process events, stream events are not queued, as we want to
269 // deal with stream events as fast a possible. It prevents closing the
270 // streams while there's still data in the OS buffer (due to the process
271 // exiting before all data is read).
272 if (has_input) {
273 wstream_init(&proc->in, 0);
274 }
275 rstream_init(&proc->out, 0);
276 rstream_start(&proc->out, data_cb, &buf);
277 rstream_init(&proc->err, 0);
278 rstream_start(&proc->err, data_cb, &buf);
279
280 // write the input, if any
281 if (has_input) {
282 WBuffer *input_buffer = wstream_new_buffer((char *)input, len, 1, NULL);
283
284 if (!wstream_write(&proc->in, input_buffer)) {
285 // couldn't write, stop the process and tell the user about it
286 process_stop(proc);
287 return -1;
288 }
289 // close the input stream after everything is written
290 wstream_set_write_cb(&proc->in, shell_write_cb, NULL);
291 }
292
293 // Invoke busy_start here so LOOP_PROCESS_EVENTS_UNTIL will not change the
294 // busy state.
295 ui_busy_start();
296 ui_flush();
297 if (forward_output) {
298 msg_sb_eol();
299 msg_start();
300 msg_no_more = true;
301 lines_left = -1;
302 }
303 int exitcode = process_wait(proc, -1, NULL);
304 if (!got_int && out_data_decide_throttle(0)) {
305 // Last chunk of output was skipped; display it now.
306 out_data_ring(NULL, SIZE_MAX);
307 }
308 if (forward_output) {
309 // caller should decide if wait_return is invoked
310 no_wait_return++;
311 msg_end();
312 no_wait_return--;
313 msg_no_more = false;
314 }
315
316 ui_busy_stop();
317
318 // prepare the out parameters if requested
319 if (output) {
320 if (buf.len == 0) {
321 // no data received from the process, return NULL
322 *output = NULL;
323 xfree(buf.data);
324 } else {
325 // NUL-terminate to make the output directly usable as a C string
326 buf.data[buf.len] = NUL;
327 *output = buf.data;
328 }
329
330 if (nread) {
331 *nread = buf.len;
332 }
333 }
334
335 assert(multiqueue_empty(events));
336 multiqueue_free(events);
337
338 return exitcode;
339}
340
341/// - ensures at least `desired` bytes in buffer
342///
343/// TODO(aktau): fold with kvec/garray
344static void dynamic_buffer_ensure(DynamicBuffer *buf, size_t desired)
345{
346 if (buf->cap >= desired) {
347 assert(buf->data);
348 return;
349 }
350
351 buf->cap = desired;
352 kv_roundup32(buf->cap);
353 buf->data = xrealloc(buf->data, buf->cap);
354}
355
356static void system_data_cb(Stream *stream, RBuffer *buf, size_t count,
357 void *data, bool eof)
358{
359 DynamicBuffer *dbuf = data;
360
361 size_t nread = buf->size;
362 dynamic_buffer_ensure(dbuf, dbuf->len + nread + 1);
363 rbuffer_read(buf, dbuf->data + dbuf->len, nread);
364 dbuf->len += nread;
365}
366
367/// Tracks output received for the current executing shell command, and displays
368/// a pulsing "..." when output should be skipped. Tracking depends on the
369/// synchronous/blocking nature of ":!".
370///
371/// Purpose:
372/// 1. CTRL-C is more responsive. #1234 #5396
373/// 2. Improves performance of :! (UI, esp. TUI, is the bottleneck).
374/// 3. Avoids OOM during long-running, spammy :!.
375///
376/// Vim does not need this hack because:
377/// 1. :! in terminal-Vim runs in cooked mode, so CTRL-C is caught by the
378/// terminal and raises SIGINT out-of-band.
379/// 2. :! in terminal-Vim uses a tty (Nvim uses pipes), so commands
380/// (e.g. `git grep`) may page themselves.
381///
382/// @param size Length of data, used with internal state to decide whether
383/// output should be skipped. size=0 resets the internal state and
384/// returns the previous decision.
385///
386/// @returns true if output should be skipped and pulse was displayed.
387/// Returns the previous decision if size=0.
388static bool out_data_decide_throttle(size_t size)
389{
390 static uint64_t started = 0; // Start time of the current throttle.
391 static size_t received = 0; // Bytes observed since last throttle.
392 static size_t visit = 0; // "Pulse" count of the current throttle.
393 static char pulse_msg[] = { ' ', ' ', ' ', '\0' };
394
395 if (!size) {
396 bool previous_decision = (visit > 0);
397 started = received = visit = 0;
398 return previous_decision;
399 }
400
401 received += size;
402 if (received < OUT_DATA_THRESHOLD
403 // Display at least the first chunk of output even if it is big.
404 || (!started && received < size + 1000)) {
405 return false;
406 } else if (!visit) {
407 started = os_hrtime();
408 } else {
409 uint64_t since = os_hrtime() - started;
410 if (since < (visit * 0.1L * NS_1_SECOND)) {
411 return true;
412 }
413 if (since > (3 * NS_1_SECOND)) {
414 received = visit = 0;
415 return false;
416 }
417 }
418
419 visit++;
420 // Pulse "..." at the bottom of the screen.
421 size_t tick = visit % 4;
422 pulse_msg[0] = (tick > 0) ? '.' : ' ';
423 pulse_msg[1] = (tick > 1) ? '.' : ' ';
424 pulse_msg[2] = (tick > 2) ? '.' : ' ';
425 if (visit == 1) {
426 msg_putchar('\n');
427 }
428 msg_putchar('\r'); // put cursor at start of line
429 msg_puts(pulse_msg);
430 ui_flush();
431 return true;
432}
433
434/// Saves output in a quasi-ringbuffer. Used to ensure the last ~page of
435/// output for a shell-command is always displayed.
436///
437/// Init mode: Resets the internal state.
438/// output = NULL
439/// size = 0
440/// Print mode: Displays the current saved data.
441/// output = NULL
442/// size = SIZE_MAX
443///
444/// @param output Data to save, or NULL to invoke a special mode.
445/// @param size Length of `output`.
446static void out_data_ring(char *output, size_t size)
447{
448#define MAX_CHUNK_SIZE (OUT_DATA_THRESHOLD / 2)
449 static char last_skipped[MAX_CHUNK_SIZE]; // Saved output.
450 static size_t last_skipped_len = 0;
451
452 assert(output != NULL || (size == 0 || size == SIZE_MAX));
453
454 if (output == NULL && size == 0) { // Init mode
455 last_skipped_len = 0;
456 return;
457 }
458
459 if (output == NULL && size == SIZE_MAX) { // Print mode
460 out_data_append_to_screen(last_skipped, &last_skipped_len, true);
461 return;
462 }
463
464 // This is basically a ring-buffer...
465 if (size >= MAX_CHUNK_SIZE) { // Save mode
466 size_t start = size - MAX_CHUNK_SIZE;
467 memcpy(last_skipped, output + start, MAX_CHUNK_SIZE);
468 last_skipped_len = MAX_CHUNK_SIZE;
469 } else if (size > 0) {
470 // Length of the old data that can be kept.
471 size_t keep_len = MIN(last_skipped_len, MAX_CHUNK_SIZE - size);
472 size_t keep_start = last_skipped_len - keep_len;
473 // Shift the kept part of the old data to the start.
474 if (keep_start) {
475 memmove(last_skipped, last_skipped + keep_start, keep_len);
476 }
477 // Copy the entire new data to the remaining space.
478 memcpy(last_skipped + keep_len, output, size);
479 last_skipped_len = keep_len + size;
480 }
481}
482
483/// Continue to append data to last screen line.
484///
485/// @param output Data to append to screen lines.
486/// @param remaining Size of data.
487/// @param new_line If true, next data output will be on a new line.
488static void out_data_append_to_screen(char *output, size_t *count, bool eof)
489 FUNC_ATTR_NONNULL_ALL
490{
491 char *p = output, *end = output + *count;
492 while (p < end) {
493 if (*p == '\n' || *p == '\r' || *p == TAB || *p == BELL) {
494 msg_putchar_attr((uint8_t)(*p), 0);
495 p++;
496 } else {
497 // Note: this is not 100% precise:
498 // 1. we don't check if received continuation bytes are already invalid
499 // and we thus do some buffering that could be avoided
500 // 2. we don't compose chars over buffer boundaries, even if we see an
501 // incomplete UTF-8 sequence that could be composing with the last
502 // complete sequence.
503 // This will be corrected when we switch to vterm based implementation
504 int i = *p ? utfc_ptr2len_len((char_u *)p, (int)(end-p)) : 1;
505 if (!eof && i == 1 && utf8len_tab_zero[*(uint8_t *)p] > (end-p)) {
506 *count = (size_t)(p - output);
507 goto end;
508 }
509
510 (void)msg_outtrans_len_attr((char_u *)p, i, 0);
511 p += i;
512 }
513 }
514
515end:
516 ui_flush();
517}
518
519static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data,
520 bool eof)
521{
522 size_t cnt;
523 char *ptr = rbuffer_read_ptr(buf, &cnt);
524
525 if (ptr != NULL && cnt > 0
526 && out_data_decide_throttle(cnt)) { // Skip output above a threshold.
527 // Save the skipped output. If it is the final chunk, we display it later.
528 out_data_ring(ptr, cnt);
529 } else if (ptr != NULL) {
530 out_data_append_to_screen(ptr, &cnt, eof);
531 }
532
533 if (cnt) {
534 rbuffer_consumed(buf, cnt);
535 }
536
537 // Move remaining data to start of buffer, so the buffer can never
538 // wrap around.
539 rbuffer_reset(buf);
540}
541
542/// Parses a command string into a sequence of words, taking quotes into
543/// consideration.
544///
545/// @param str The command string to be parsed
546/// @param argv The vector that will be filled with copies of the parsed
547/// words. It can be NULL if the caller only needs to count words.
548/// @return The number of words parsed.
549static size_t tokenize(const char_u *const str, char **const argv)
550 FUNC_ATTR_NONNULL_ARG(1)
551{
552 size_t argc = 0;
553 const char *p = (const char *) str;
554
555 while (*p != NUL) {
556 const size_t len = word_length((const char_u *) p);
557
558 if (argv != NULL) {
559 // Fill the slot
560 argv[argc] = vim_strnsave_unquoted(p, len);
561 }
562
563 argc++;
564 p = (const char *) skipwhite((char_u *) (p + len));
565 }
566
567 return argc;
568}
569
570/// Calculates the length of a shell word.
571///
572/// @param str A pointer to the first character of the word
573/// @return The offset from `str` at which the word ends.
574static size_t word_length(const char_u *str)
575{
576 const char_u *p = str;
577 bool inquote = false;
578 size_t length = 0;
579
580 // Move `p` to the end of shell word by advancing the pointer while it's
581 // inside a quote or it's a non-whitespace character
582 while (*p && (inquote || (*p != ' ' && *p != TAB))) {
583 if (*p == '"') {
584 // Found a quote character, switch the `inquote` flag
585 inquote = !inquote;
586 } else if (*p == '\\' && inquote) {
587 p++;
588 length++;
589 }
590
591 p++;
592 length++;
593 }
594
595 return length;
596}
597
598/// To remain compatible with the old implementation (which forked a process
599/// for writing) the entire text is copied to a temporary buffer before the
600/// event loop starts. If we don't (by writing in chunks returned by `ml_get`)
601/// the buffer being modified might get modified by reading from the process
602/// before we finish writing.
603static void read_input(DynamicBuffer *buf)
604{
605 size_t written = 0, l = 0, len = 0;
606 linenr_T lnum = curbuf->b_op_start.lnum;
607 char_u *lp = ml_get(lnum);
608
609 for (;;) {
610 l = strlen((char *)lp + written);
611 if (l == 0) {
612 len = 0;
613 } else if (lp[written] == NL) {
614 // NL -> NUL translation
615 len = 1;
616 dynamic_buffer_ensure(buf, buf->len + len);
617 buf->data[buf->len++] = NUL;
618 } else {
619 char_u *s = vim_strchr(lp + written, NL);
620 len = s == NULL ? l : (size_t)(s - (lp + written));
621 dynamic_buffer_ensure(buf, buf->len + len);
622 memcpy(buf->data + buf->len, lp + written, len);
623 buf->len += len;
624 }
625
626 if (len == l) {
627 // Finished a line, add a NL, unless this line should not have one.
628 if (lnum != curbuf->b_op_end.lnum
629 || (!curbuf->b_p_bin && curbuf->b_p_fixeol)
630 || (lnum != curbuf->b_no_eol_lnum
631 && (lnum != curbuf->b_ml.ml_line_count || curbuf->b_p_eol))) {
632 dynamic_buffer_ensure(buf, buf->len + 1);
633 buf->data[buf->len++] = NL;
634 }
635 ++lnum;
636 if (lnum > curbuf->b_op_end.lnum) {
637 break;
638 }
639 lp = ml_get(lnum);
640 written = 0;
641 } else if (len > 0) {
642 written += len;
643 }
644 }
645}
646
647static size_t write_output(char *output, size_t remaining, bool eof)
648{
649 if (!output) {
650 return 0;
651 }
652
653 char *start = output;
654 size_t off = 0;
655 while (off < remaining) {
656 if (output[off] == NL) {
657 // Insert the line
658 output[off] = NUL;
659 ml_append(curwin->w_cursor.lnum++, (char_u *)output, (int)off + 1,
660 false);
661 size_t skip = off + 1;
662 output += skip;
663 remaining -= skip;
664 off = 0;
665 continue;
666 }
667
668 if (output[off] == NUL) {
669 // Translate NUL to NL
670 output[off] = NL;
671 }
672 off++;
673 }
674
675 if (eof) {
676 if (remaining) {
677 // append unfinished line
678 ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false);
679 // remember that the NL was missing
680 curbuf->b_no_eol_lnum = curwin->w_cursor.lnum;
681 output += remaining;
682 } else {
683 curbuf->b_no_eol_lnum = 0;
684 }
685 }
686
687 ui_flush();
688
689 return (size_t)(output - start);
690}
691
692static void shell_write_cb(Stream *stream, void *data, int status)
693{
694 if (status) {
695 // Can happen if system() tries to send input to a shell command that was
696 // backgrounded (:call system("cat - &", "foo")). #3529 #5241
697 msg_schedule_emsgf(_("E5677: Error writing input to shell-command: %s"),
698 uv_err_name(status));
699 }
700 stream_close(stream, NULL, NULL);
701}
702
703/// Applies 'shellxescape' (p_sxe) and 'shellxquote' (p_sxq) to a command.
704///
705/// @param cmd Command string
706/// @return Escaped/quoted command string (allocated).
707static char *shell_xescape_xquote(const char *cmd)
708 FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
709{
710 if (*p_sxq == NUL) {
711 return xstrdup(cmd);
712 }
713
714 const char *ecmd = cmd;
715 if (*p_sxe != NUL && STRCMP(p_sxq, "(") == 0) {
716 ecmd = (char *)vim_strsave_escaped_ext((char_u *)cmd, p_sxe, '^', false);
717 }
718 size_t ncmd_size = strlen(ecmd) + STRLEN(p_sxq) * 2 + 1;
719 char *ncmd = xmalloc(ncmd_size);
720
721 // When 'shellxquote' is ( append ).
722 // When 'shellxquote' is "( append )".
723 if (STRCMP(p_sxq, "(") == 0) {
724 vim_snprintf(ncmd, ncmd_size, "(%s)", ecmd);
725 } else if (STRCMP(p_sxq, "\"(") == 0) {
726 vim_snprintf(ncmd, ncmd_size, "\"(%s)\"", ecmd);
727 } else {
728 vim_snprintf(ncmd, ncmd_size, "%s%s%s", p_sxq, ecmd, p_sxq);
729 }
730
731 if (ecmd != cmd) {
732 xfree((void *)ecmd);
733 }
734
735 return ncmd;
736}
737
738