Ticket #3692: 3692-mc_subshell-V2.patch
File 3692-mc_subshell-V2.patch, 32.7 KB (added by alllexx88, 8 years ago) |
---|
-
lib/shell.c
Author: alllexx88 <opotapenko@gmail.com> Date: Thu, 6 Oct 2016 16:04:53 +0300 More sophisticated shell type detection method * Make tests on shell binary instead of trying to guess shell type from path. Most supported shells set specific variables, hence by testing whether such variables are set, we can guess shell type in a more reliable way. This works with bash, zsh, tcsh and fish. For guessing dash or BusyBox ash (which are treated the same), we run a more peculiar test on whether shell supports expansion in PS1 as a prompt string. The latter test is also designed to differentiate a legacy pre 1.20 BusyBox ash, which allows to apply printf workaround in the case of such shell. * Remove chdir command from subshell initialization code, and use full paths for init_file instead. Changing dir only allows to use relative init_file paths, but can instead potentially lead to some problems, as previously noted in the comments; so by not doing this we add additional layer of protection against bugs. * Remove unneeded SHELL_SH shell type, and 'name' mc_shell_t field, since the latter was only being used as arg0 when initializing subshell, and all shells apparently initialize fine regardless of arg0 after being correctly detected, except for zsh, which has to be invoked as "zsh", probably because it behaves in a bourne compatible way otherwise (so we just leave arg0 as "zsh" for it) * Also add a little error verbosity in scope of detecting shell type and subshell initialization --- lib/shell.c | 512 ++++++++++++++++++++++++++++++++++++++++++++------ lib/shell.h | 50 ++++- src/subshell/common.c | 58 ++---- 3 files changed, 521 insertions(+), 99 deletions(-) diff --git a/lib/shell.c b/lib/shell.c index 6f07cb0..f655f12 100644
a b 33 33 #include <stdarg.h> 34 34 #include <stdio.h> 35 35 #include <stdlib.h> 36 #include <sys/wait.h> 37 #include <poll.h> 36 38 37 39 #include "global.h" 38 40 #include "util.h" … … 44 46 45 47 /*** file scope type declarations ****************************************************************/ 46 48 49 /* For pipes */ 50 enum 51 { 52 READ = 0, 53 WRITE = 1 54 }; 55 47 56 /*** file scope variables ************************************************************************/ 48 57 49 58 static char rp_shell[PATH_MAX]; … … mc_shell_get_from_env (void) 140 149 /* --------------------------------------------------------------------------------------------- */ 141 150 142 151 static void 143 mc_shell_ recognize_real_path (mc_shell_t * mc_shell)152 mc_shell_append_string (char ** dest, const char * append) 144 153 { 145 if (strstr (mc_shell->path, "/zsh") != NULL || strstr (mc_shell->real_path, "/zsh") != NULL 146 || getenv ("ZSH_VERSION") != NULL) 154 char * str; 155 156 if ((dest == NULL) || (append == NULL) || (append[0] == '\0')) 157 return; 158 159 if (*dest == NULL) 160 str = g_strdup (append); 161 else 162 str = g_strdup_printf ("%s%s", *dest, append); 163 164 g_free (*dest); 165 *dest = str; 166 } 167 168 /* Close fd (we expect a pipe), appending all remaining data in case of input pipe to *dest. 169 If (dest == NULL), we still read all the data in an input pipe to close it properly. 170 Set the fd to -1 to indicate it's closed */ 171 172 static void 173 mc_shell_finalize_fd (int * fd, char ** dest) 174 { 175 struct pollfd fdinfo; 176 int ret; 177 char buff[256]; 178 ssize_t size; 179 180 if ((fd == NULL) || (*fd < 0)) 181 return; 182 183 fdinfo.fd = *fd; 184 fdinfo.events = POLLIN; 185 186 ret = poll (&fdinfo, 1, 0); 187 188 if ((ret > 0) && ((fdinfo.revents & POLLIN) != 0)) 189 fcntl (*fd, F_SETFD, fcntl (*fd, F_GETFD) | O_NONBLOCK); 190 191 while ((ret > 0) && ((fdinfo.revents & POLLIN) != 0)) 147 192 { 148 /* Also detects ksh symlinked to zsh */ 149 mc_shell->type = SHELL_ZSH; 150 mc_shell->name = "zsh"; 193 size = read (*fd, &buff[0], 255); 194 195 if (size > 0) 196 { 197 buff[size] = '\0'; 198 mc_shell_append_string (dest, buff); 199 } 200 201 ret = poll (&fdinfo, 1, 0); 151 202 } 152 else if (strstr (mc_shell->path, "/tcsh") != NULL 153 || strstr (mc_shell->real_path, "/tcsh") != NULL) 203 204 if ((fdinfo.revents & POLLNVAL) == 0) 205 close (*fd); 206 207 *fd = -1; 208 } 209 210 static void * 211 mc_shell_waitpid_thread (int * args) 212 { 213 int pid = args[0], * exit_status = &args[1]; 214 215 /* wait for child to terminate and get exit_status, 216 or set exit_status to '-1' on waitpid error */ 217 if (waitpid (pid, exit_status, 0) == -1) 218 *exit_status = -1; 219 220 /* this signales the parent thread that child terminated */ 221 args[0] = 0; 222 223 return NULL; 224 } 225 226 /* Wait for child process to terminate and read/write requested pipes in the process. 227 In case the child is running longer than the timeout value, terminate it with a 228 SIGKILL signal. Timeout is in milliseconds, 0 timeout means no timeout. 229 Returns child exit_status, or '-1' on non positive pid or waitpid () failure */ 230 static int 231 mc_shell_child_wait (int pid, guint timeout, pipes_data_t * pipes) 232 { 233 int args[2] = {pid}, i; 234 gint64 start = g_get_monotonic_time (); 235 GThread * thread; 236 237 if (pid <= 0) 238 return -1; 239 240 /* launch thread that waits for child process termination and gets the exit status */ 241 thread = g_thread_new ("waitpid_thread", (GThreadFunc)mc_shell_waitpid_thread, args); 242 243 if ((pipes != NULL) && (pipes->n > 0)) 154 244 { 155 /* Also detects csh symlinked to tcsh */ 156 mc_shell->type = SHELL_TCSH; 157 mc_shell->name = "tcsh"; 158 } 159 else if (strstr (mc_shell->path, "/csh") != NULL 160 || strstr (mc_shell->real_path, "/csh") != NULL) 245 /* we have pipes to poll */ 246 int ret, n = pipes->n; 247 struct pollfd fdinfo[n]; 248 ssize_t size; 249 char buff[256]; 250 251 /* prepare pollfd struct array for poll () and make all pipes non blocking */ 252 for (i = 0; i < n; i++) 253 { 254 fdinfo[i].fd = pipes->poll_fds[i]; 255 fdinfo[i].events = POLLIN | POLLOUT; 256 fcntl (fdinfo[i].fd, F_SETFD, fcntl (fdinfo[i].fd, F_GETFD) | O_NONBLOCK); 257 } 258 259 ret = poll (fdinfo, n, 0); 260 261 /* while child is running, read/write from/to all requested pipes */ 262 while (args[0] > 0) 263 { 264 if ((timeout > 0) && (g_get_monotonic_time () >= start + 1000 * timeout)) 265 { 266 /* kill spawned child on timeout */ 267 kill (pid, SIGKILL); 268 break; 269 } else if (ret > 0) 270 for (i = n - 1; i >= 0; i--) 271 { 272 if ((fdinfo[i].revents & POLLIN) != 0) 273 { 274 /* this is an input pipe, and it has data ready for reading */ 275 size = read (fdinfo[i].fd, buff, 255); 276 277 if ((pipes->buff[i] != NULL) && (size > 0)) 278 { 279 buff[size] = '\0'; 280 mc_shell_append_string (pipes->buff[i], buff); 281 } 282 } else if ((fdinfo[i].revents & POLLOUT) != 0) 283 { 284 /* this is an output pipe, and it is ready for writing */ 285 286 if ((pipes->buff[i] != NULL) && (*(pipes->buff[i]) != NULL)) 287 { 288 char * source = *(pipes->buff[i]); 289 290 size = write (fdinfo[i].fd, source, strlen (source)); 291 292 if (size > 0) 293 { 294 *(pipes->buff[i]) = g_strdup (&source[size]); 295 g_free (source); 296 source = *(pipes->buff[i]); 297 } 298 299 if (source[0] == '\0') 300 { 301 /* done writing: close the pipe, and remove from fdinfo */ 302 close (fdinfo[i].fd); 303 fdinfo[i].fd = pipes->poll_fds[i] = -1; 304 } 305 } else 306 { 307 /* nothing to write: close the pipe, and remove from fdinfo */ 308 close (fdinfo[i].fd); 309 fdinfo[i].fd = pipes->poll_fds[i] = -1; 310 } 311 } 312 } 313 314 ret = poll (fdinfo, n, 0); 315 } 316 } else if (timeout > 0) 317 while (args[0] > 0) /* loop here until child terminates or command timeouts */ 318 if (g_get_monotonic_time () >= start + 1000 * timeout) 319 { 320 /* kill spawned child on timeout */ 321 kill (pid, SIGKILL); 322 break; 323 } 324 325 g_thread_join (thread); 326 g_thread_unref (thread); 327 328 /* read all the remaining data in the pipes and close them */ 329 if ((pipes != NULL) && (pipes->n > 0)) 330 for (i = 0; i < pipes->n; i++) 331 mc_shell_finalize_fd (&pipes->poll_fds[i], pipes->buff[i]); 332 333 return args[1]; 334 } 335 336 /* --------------------------------------------------------------------------------------------- 337 This function is similar to g_spawn_sync () function with some additional functionality: 338 * add optional timeout to SIGKILL the child after its expiration 339 * optionally feed a string to child's stdin 340 * optionally poll and read/write requested pipes during child execution 341 --------------------------------------------------------------------------------------------- */ 342 343 static gboolean 344 mc_shell_execute (const char * working_directory, 345 char ** argv, 346 char ** envp, 347 GSpawnFlags flags, 348 GSpawnChildSetupFunc child_setup, 349 void * user_data, 350 const char * feed_standard_input, 351 char ** standard_output, 352 char ** standard_error, 353 guint timeout, /* timeout is in milliseconds, 0 timeout means no timeout */ 354 pipes_data_t * pipes, 355 int * exit_status, 356 GError ** error) 357 { 358 int i, pid, stdin_fd, stdout_fd, stderr_fd, status, 359 * stdin_return = NULL, * stdout_return = NULL, * stderr_return = NULL; 360 char * standard_input = NULL; 361 pipes_data_t pipes_data; 362 363 pipes_data.n = 0; 364 365 if ((pipes != NULL) && (pipes->n > 0)) 161 366 { 162 mc_shell->type = SHELL_TCSH; 163 mc_shell->name = "csh"; 367 pipes_data.n = pipes->n; 368 369 /* without this the requested pipes will be closed for the child */ 370 flags |= G_SPAWN_LEAVE_DESCRIPTORS_OPEN; 371 372 /* make parent sides of the pipes get closed for the child */ 373 for (i = 0; i < pipes->n; i++) 374 fcntl (pipes->poll_fds[i], F_SETFD, fcntl (pipes->poll_fds[i], F_GETFD) | FD_CLOEXEC); 164 375 } 165 else if (strstr (mc_shell->path, "/fish") != NULL 166 || strstr (mc_shell->real_path, "/fish") != NULL) 376 377 /* we'll reap the child ourselves with waitpid () */ 378 flags |= G_SPAWN_DO_NOT_REAP_CHILD; 379 380 if (feed_standard_input != NULL) 167 381 { 168 mc_shell->type = SHELL_FISH;169 mc_shell->name = "fish";382 stdin_return = &stdin_fd; 383 pipes_data.n++; 170 384 } 171 else if (strstr (mc_shell->path, "/dash") != NULL 172 || strstr (mc_shell->real_path, "/dash")!= NULL)385 386 if (standard_output != NULL) 173 387 { 174 /* Debian ash (also found if symlinked to by ash/sh) */ 175 mc_shell->type = SHELL_DASH; 176 mc_shell->name = "dash"; 388 stdout_return = &stdout_fd; 389 pipes_data.n++; 177 390 } 178 else if (strstr (mc_shell->real_path, "/busybox") != NULL) 391 else 392 flags |= G_SPAWN_STDOUT_TO_DEV_NULL; 393 394 if (standard_error != NULL) 179 395 { 180 /* If shell is symlinked to busybox, assume it is an ash, even though theoretically 181 * it could also be a hush (a mini shell for non-MMU systems deactivated by default). 182 * For simplicity's sake we assume that busybox always contains an ash, not a hush. 183 * On embedded platforms or on server systems, /bin/sh often points to busybox. 184 * Sometimes even bash is symlinked to busybox (CONFIG_FEATURE_BASH_IS_ASH option), 185 * so we need to check busybox symlinks *before* checking for the name "bash" 186 * in order to avoid that case. */ 187 mc_shell->type = SHELL_ASH_BUSYBOX; 188 mc_shell->name = mc_shell->path; 396 stderr_return = &stderr_fd; 397 pipes_data.n++; 189 398 } 190 399 else 191 mc_shell->type = SHELL_NONE; 400 flags |= G_SPAWN_STDERR_TO_DEV_NULL; 401 402 if (!g_spawn_async_with_pipes (working_directory, argv, envp, flags, 403 child_setup, user_data, &pid, stdin_return, stdout_return, stderr_return, error)) 404 { 405 g_strfreev (envp); 406 return FALSE; 407 } 408 409 pipes_data.poll_fds = g_new(int, pipes_data.n); 410 pipes_data.buff = g_new(char**, pipes_data.n); 411 pipes_data.n = 0; 412 413 if ((pipes != NULL) && (pipes->n > 0)) 414 { 415 pipes_data.n = pipes->n; 416 417 for (i = 0; i < pipes->n; i++) 418 { 419 pipes_data.poll_fds[i] = pipes->poll_fds[i]; 420 pipes_data.buff[i] = pipes->buff[i]; 421 /* close child sides of pipes for the parent */ 422 mc_shell_finalize_fd (&pipes->close_fds[i], NULL); 423 } 424 } 425 426 if (standard_output != NULL) 427 { 428 pipes_data.poll_fds[pipes_data.n] = stdout_fd; 429 pipes_data.buff[pipes_data.n] = standard_output; 430 pipes_data.n++; 431 } 432 433 if (standard_error != NULL) 434 { 435 pipes_data.poll_fds[pipes_data.n] = stderr_fd; 436 pipes_data.buff[pipes_data.n] = standard_error; 437 pipes_data.n++; 438 } 439 440 if (feed_standard_input != NULL) 441 { 442 standard_input = g_strdup (feed_standard_input); 443 pipes_data.poll_fds[pipes_data.n] = stdin_fd; 444 pipes_data.buff[pipes_data.n] = &standard_input; 445 pipes_data.n++; 446 } 447 448 status = mc_shell_child_wait (pid, timeout, &pipes_data); 449 450 if (exit_status != NULL) 451 *exit_status = status; 452 453 if ((pipes != NULL) && (pipes->n > 0)) 454 { 455 /* the poll_fds are supposed to be closed now, and pipes_data.poll_fds[i] set to '-1', so let 456 the function caller know they're closed by setting pipes->poll_fds[i] to '-1' too */ 457 for (i = 0; i < pipes->n; i++) 458 pipes->poll_fds[i] = pipes_data.poll_fds[i]; 459 } 460 461 g_free (standard_input); 462 g_free (pipes_data.poll_fds); 463 g_free (pipes_data.buff); 464 465 return TRUE; 466 } 467 468 /* --------------------------------------------------------------------------------------------- 469 This function returns TRUE for a shell if it sets a variable with respective name. We unset 470 environmental variable of the same name in the child envp to make sure it's not inherited. 471 We use three different commands for the respective shell syntaxes: bourne, C and fish. 472 We test for exit code and stdout output to check if the command was successful. 473 If we test a shell with a wrong syntax, it returns error code, and function returns FALSE, 474 so in fact we test for syntax first, and only then for shell setting the variable. 475 --------------------------------------------------------------------------------------------- */ 476 static gboolean 477 mc_shell_internal_variable_set (mc_shell_t * mc_shell, const char * name, 478 const shell_syntax_t shell_syntax) 479 { 480 /* define arg1 instead of using "-c" directly when assigning argv to 481 silence [-Wdiscarded-qualifiers] compiler warning */ 482 char arg1[] = "-c", * argv[] = {mc_shell->path, arg1, NULL, NULL}, * standard_output = NULL; 483 char ** envp = g_environ_unsetenv (g_get_environ (), name); 484 int exit_status; 485 gboolean success; 486 487 /* for proper shells these commands return 0 exit code and print 'name' value to stdout */ 488 if (shell_syntax == SHELL_SYNTAX_BOURNE) 489 argv[2] = g_strdup_printf ("if [ -z ${%s+x} ]; then " 490 "exit 1;" 491 "else " 492 "printf '%s';" 493 "fi", name, name); 494 else if (shell_syntax == SHELL_SYNTAX_C) 495 argv[2] = g_strdup_printf ("if !( $?%s ) then\n" 496 "exit 1\n" 497 "else\n" 498 "printf '%s'\n" 499 "endif", name, name); 500 else /* shell_syntax == SHELL_SYNTAX_FISH */ 501 argv[2] = g_strdup_printf ("if set -q %s;" 502 "printf '%s';" 503 "else;" 504 "exit 1;" 505 "end", name, name); 506 507 508 success = mc_shell_execute (NULL, argv, envp, 0, NULL, NULL, NULL, &standard_output, 509 NULL, 100, NULL, &exit_status, NULL) && 510 g_spawn_check_exit_status (exit_status, NULL) && 511 (g_strcmp0 (standard_output, name) == 0); 512 513 g_free (argv[2]); 514 g_free (standard_output); 515 516 return success; 192 517 } 193 518 194 519 /* --------------------------------------------------------------------------------------------- */ 195 520 196 521 static void 197 mc_shell_recognize_ path(mc_shell_t * mc_shell)522 mc_shell_recognize_from_internal_variable (mc_shell_t * mc_shell) 198 523 { 199 /* If shell is not symlinked to busybox, it is safe to assume it is a real shell */200 if (strstr (mc_shell->path, "/bash") != NULL || getenv ("BASH") != NULL)201 {524 /* These tests recognize bash, zsh, tcsh and fish by testing for 525 variables that only these shells set */ 526 if (mc_shell_internal_variable_set (mc_shell, "BASH", SHELL_SYNTAX_BOURNE)) { 202 527 mc_shell->type = SHELL_BASH; 203 mc_shell->name = "bash";204 528 } 205 else if ( strstr (mc_shell->path, "/sh") != NULL || getenv ("SH") != NULL)529 else if (mc_shell_internal_variable_set (mc_shell, "ZSH_NAME", SHELL_SYNTAX_BOURNE)) 206 530 { 207 mc_shell->type = SHELL_SH; 208 mc_shell->name = "sh"; 531 mc_shell->type = SHELL_ZSH; 209 532 } 210 else if ( strstr (mc_shell->path, "/ash") != NULL || getenv ("ASH") != NULL)533 else if (mc_shell_internal_variable_set (mc_shell, "tcsh", SHELL_SYNTAX_C)) 211 534 { 212 mc_shell->type = SHELL_ASH_BUSYBOX; 213 mc_shell->name = "ash"; 535 mc_shell->type = SHELL_TCSH; 536 } 537 else if (mc_shell_internal_variable_set (mc_shell, "fish_greeting", SHELL_SYNTAX_FISH)) 538 { 539 mc_shell->type = SHELL_FISH; 214 540 } 215 541 else 216 542 mc_shell->type = SHELL_NONE; 217 543 } 218 544 545 /* --------------------------------------------------------------------------------------------- 546 This function tests whether a shell treats PS1 as prompt string that is being expanded. 547 We test for an old BusyBox ash 4-digit octal codes bug in printf along the way too. 548 mc_shell->type will be set to: 549 SHELL_DASH: Test for PS1 expansion succeeds fully. This can mean dash, or BusyBox ash 550 with CONFIG_ASH_EXPAND_PRMT enabled, or something other compatible 551 SHELL_ASH_BUSYBOX_LEGACY: PS1 is being expanded, but printf suffers from the 4-digit octal 552 codes bug, so apply the printf workaround 553 SHELL_NONE: Test failed. Possible reasons: PS1 is not being treated as a prompt string, 554 PS1 is not being expanded (no CONFIG_ASH_EXPAND_PRMT in BusyBox ash?), 555 shell doesn't recognize syntax, failed to execute shell, etc. 556 --------------------------------------------------------------------------------------------- */ 557 static void 558 mc_shell_test_prompt_expansion (mc_shell_t * mc_shell) 559 { 560 /* define arg1 instead of using "-i" directly when assigning argv to 561 silence [-Wdiscarded-qualifiers] compiler warning */ 562 char arg1[] = "-i", * argv[] = {mc_shell->path, arg1, NULL}, 563 ** envp, * standard_input; 564 int subshell_pipe[2], exit_status; 565 char * out_buff = NULL, ** out_buff_addr = &out_buff; 566 pipes_data_t pipes; 567 568 if (pipe (subshell_pipe)) 569 { 570 /* failed to create pipe */ 571 mc_shell->type = SHELL_NONE; 572 return; 573 } 574 575 envp = g_environ_setenv (g_get_environ (), "PS1", "$ ", TRUE); 576 577 /* Check if executing `PS1='$(printf "%b" "\\0057a\\0057\\n" >&subshell_pipe[WRITE])'` command 578 in interactive mode gets PS1 evaluated before the next `exit 0` command, by capturing the pipe 579 output, and comparing it to the expected printf output ("/a/"), and if it doesn't match - 580 to BusyBox pre 1.20 broken printf output ("\005""7a""\005""7") */ 581 582 standard_input = g_strdup_printf ("PS1='$(printf \"%%b\" \"\\\\0057a\\\\0057\" >&%d)'\n" 583 "exit 0\n", subshell_pipe[WRITE]); 584 585 pipes.buff = &out_buff_addr; /* pipes.buff is an array of pointers to strings */ 586 pipes.poll_fds = &subshell_pipe[READ]; /* int array of pipes to poll */ 587 pipes.close_fds = &subshell_pipe[WRITE]; /* int array of pipes to close for the parent */ 588 pipes.n = 1; /* pipes.n is the number of pipes */ 589 590 if (mc_shell_execute (NULL, argv, envp, 0, NULL, NULL, standard_input, NULL, 591 NULL, 100, &pipes, &exit_status, NULL) && 592 g_spawn_check_exit_status (exit_status, NULL)) 593 { 594 if (g_strcmp0 (out_buff, "/a/") == 0) 595 mc_shell->type = SHELL_DASH; 596 else if (g_strcmp0 (out_buff, "\005""7a""\005""7") == 0) 597 mc_shell->type = SHELL_ASH_BUSYBOX_LEGACY; 598 else 599 mc_shell->type = SHELL_NONE; 600 } 601 else 602 mc_shell->type = SHELL_NONE; 603 604 g_free (out_buff); 605 g_free (standard_input); 606 607 /* if mc_shell_execute () failed */ 608 mc_shell_finalize_fd (&subshell_pipe[WRITE], NULL); 609 mc_shell_finalize_fd (&subshell_pipe[READ], NULL); 610 } 611 219 612 /* --------------------------------------------------------------------------------------------- */ 220 613 /*** public functions ****************************************************************************/ 221 614 /* --------------------------------------------------------------------------------------------- */ … … mc_shell_init (void) 232 625 233 626 mc_shell->real_path = mc_realpath (mc_shell->path, rp_shell); 234 627 235 /* Find out what type of shell we have. Also consider real paths (resolved symlinks) 236 * because e.g. csh might point to tcsh, ash to dash or busybox, sh to anything. */ 628 /* Find out what type of shell we have. Use tests for specific variables that 629 * different shells set for most shell types. To recognize dash, or compatible 630 * BusyBox ash, we test whether prompt expansion works. */ 237 631 238 if (mc_shell->real_path != NULL) 239 mc_shell_recognize_real_path(mc_shell);632 if (mc_shell->real_path != NULL) { 633 mc_shell_recognize_from_internal_variable (mc_shell); 240 634 241 if (mc_shell->type == SHELL_NONE)242 mc_shell_recognize_path(mc_shell);635 if (mc_shell->type == SHELL_NONE) 636 mc_shell_test_prompt_expansion (mc_shell); 243 637 244 if (mc_shell->type == SHELL_NONE) 245 mc_global.tty.use_subshell = FALSE; 638 if (mc_shell->type == SHELL_NONE) 639 fprintf (stderr, __FILE__ ": failed to recognize shell \"%s\" as supported subshell. " 640 "Supported shells are: bash, zsh, tcsh, fish, dash and " 641 "BusyBox ash with enabled CONFIG_ASH_EXPAND_PRMT\r\n", 642 mc_shell->path); 643 } else { 644 mc_shell->type = SHELL_NONE; 645 fprintf (stderr, __FILE__ ": wrong \"%s\" shell: No such file\r\n", mc_shell->path); 646 } 246 647 648 mc_global.tty.use_subshell = mc_shell->type != SHELL_NONE; 247 649 mc_global.shell = mc_shell; 248 650 } 249 651 -
lib/shell.h
diff --git a/lib/shell.h b/lib/shell.h index 9afcd90..1ec2495 100644
a b 12 12 typedef enum 13 13 { 14 14 SHELL_NONE, 15 SHELL_SH,16 15 SHELL_BASH, 17 SHELL_ ASH_BUSYBOX, /* BusyBox default shell (ash)*/18 SHELL_ DASH, /* Debian variant of ash*/16 SHELL_DASH, /* Debian variant of ash, or BusyBox ash shell with CONFIG_ASH_EXPAND_PRMT */ 17 SHELL_ASH_BUSYBOX_LEGACY, /* Legacy BusyBox ash shell with broken printf */ 19 18 SHELL_TCSH, 20 19 SHELL_ZSH, 21 20 SHELL_FISH 22 21 } shell_type_t; 23 22 23 typedef enum 24 { 25 SHELL_SYNTAX_BOURNE, 26 SHELL_SYNTAX_C, 27 SHELL_SYNTAX_FISH 28 } shell_syntax_t; 29 24 30 /*** structures declarations (and typedefs of structures)*****************************************/ 25 31 26 32 typedef struct 27 33 { 28 34 shell_type_t type; 29 const char *name;30 35 char *path; 31 36 char *real_path; 32 37 } mc_shell_t; 33 38 39 /* This structure stores information on input/output pipes fds and buffers. 40 Input pipes should have their data appended to *(buff[i]), 41 and output pipes should have *(buff[i]) written to them. 42 On successful writes to output pipes respective *(buff[i]) gets 43 gradually emptied, and when all data is written, the write end of the 44 pipe should be closed */ 45 typedef struct 46 { 47 int * poll_fds; 48 int * close_fds; 49 char *** buff; 50 int n; 51 } pipes_data_t; 52 34 53 /*** global variables defined in .c file *********************************************************/ 35 54 36 55 /*** declarations of public functions ************************************************************/ … … void mc_shell_deinit (void); 40 59 41 60 /*** inline functions **************************************************/ 42 61 62 static inline const char* shell_type_string (shell_type_t shell_type) 63 { 64 switch (shell_type) 65 { 66 case SHELL_NONE: 67 return "NONE"; 68 case SHELL_BASH: 69 return "BASH"; 70 case SHELL_DASH: 71 return "DASH"; 72 case SHELL_ASH_BUSYBOX_LEGACY: 73 return "ASH_BUSYBOX_LEGACY"; 74 case SHELL_TCSH: 75 return "TCSH"; 76 case SHELL_ZSH: 77 return "ZSH"; 78 case SHELL_FISH: 79 return "FISH"; 80 default: 81 return "UNKNOWN"; 82 } 83 } 84 43 85 #endif /* MC_SHELL_H */ -
src/subshell/common.c
diff --git a/src/subshell/common.c b/src/subshell/common.c index 98968fa..b3cf8bc 100644
a b init_subshell_child (const char *pty_name) 249 249 tty_resize (subshell_pty_slave); 250 250 251 251 /* Set up the subshell's environment and init file name */ 252 253 /* It simplifies things to change to our home directory here, */254 /* and the user's startup file may do a 'cd' command anyway */255 252 { 256 253 int ret; 257 254 258 ret = chdir (mc_config_get_home_dir ()); /* FIXME? What about when we re-run the subshell? */259 255 (void) ret; 260 256 } 261 257 … … init_subshell_child (const char *pty_name) 279 275 if (!exist_file (init_file)) 280 276 { 281 277 g_free (init_file); 282 init_file = g_ strdup (".bashrc");278 init_file = g_build_filename (g_getenv ("HOME"), ".bashrc", (char *) NULL); 283 279 } 284 280 285 281 /* Make MC's special commands not show up in bash's history and also suppress … … init_subshell_child (const char *pty_name) 301 297 302 298 break; 303 299 304 case SHELL_ASH_BUSYBOX :300 case SHELL_ASH_BUSYBOX_LEGACY: 305 301 case SHELL_DASH: 306 302 /* Do we have a custom init file ~/.local/share/mc/ashrc? */ 307 303 init_file = mc_config_get_full_path ("ashrc"); … … init_subshell_child (const char *pty_name) 310 306 if (!exist_file (init_file)) 311 307 { 312 308 g_free (init_file); 313 init_file = g_ strdup (".profile");309 init_file = g_build_filename (g_getenv ("HOME"), ".profile", (char *) NULL); 314 310 } 315 311 316 312 /* Put init file to ENV variable used by ash */ … … init_subshell_child (const char *pty_name) 327 323 break; 328 324 329 325 default: 330 fprintf (stderr, __FILE__ ": unimplemented subshell type % d\r\n", mc_global.shell->type);326 fprintf (stderr, __FILE__ ": unimplemented subshell type %s\r\n", shell_type_string (mc_global.shell->type)); 331 327 my_exit (FORK_FAILURE); 332 328 } 333 329 … … init_subshell_child (const char *pty_name) 355 351 switch (mc_global.shell->type) 356 352 { 357 353 case SHELL_BASH: 358 execl (mc_global.shell->path, "bash", "-rcfile", init_file, (char *) NULL);354 execl (mc_global.shell->path, mc_global.shell->path, "-rcfile", init_file, (char *) NULL); 359 355 break; 360 356 361 357 case SHELL_ZSH: 362 358 /* Use -g to exclude cmds beginning with space from history 363 359 * and -Z to use the line editor on non-interactive term */ 360 /* zsh is sensitive about arg0 value to the point of failing 361 to initialize subshell when arg0 basename isn't "zsh" */ 364 362 execl (mc_global.shell->path, "zsh", "-Z", "-g", (char *) NULL); 365 363 366 364 break; 367 365 368 case SHELL_ASH_BUSYBOX :366 case SHELL_ASH_BUSYBOX_LEGACY: 369 367 case SHELL_DASH: 370 368 case SHELL_TCSH: 371 369 case SHELL_FISH: … … init_subshell_precmd (char *precmd, size_t buff_size) 801 799 "PS1='\\u@\\h:\\w\\$ '\n", subshell_pipe[WRITE]); 802 800 break; 803 801 804 case SHELL_ASH_BUSYBOX: 805 /* BusyBox ash needs a somewhat complicated precmd emulation via PS1, and it is vital 806 * that BB be built with active CONFIG_ASH_EXPAND_PRMT, but this is the default anyway. 807 * 808 * A: This leads to a stopped subshell (=frozen mc) if user calls "ash" command 809 * "PS1='$(pwd>&%d; kill -STOP $$)\\u@\\h:\\w\\$ '\n", 810 * 811 * B: This leads to "sh: precmd: not found" in sub-subshell if user calls "ash" command 812 * "precmd() { pwd>&%d; kill -STOP $$; }; " 813 * "PS1='$(precmd)\\u@\\h:\\w\\$ '\n", 814 * 815 * C: This works if user calls "ash" command because in sub-subshell 816 * PRECMD is unfedined, thus evaluated to empty string - no damage done. 817 * Attention: BusyBox must be built with FEATURE_EDITING_FANCY_PROMPT to 818 * permit \u, \w, \h, \$ escape sequences. Unfortunately this cannot be guaranteed, 819 * especially on embedded systems where people try to save space, so let's use 820 * the dash version below. It should work on virtually all systems. 821 * "precmd() { pwd>&%d; kill -STOP $$; }; " 822 * "PRECMD=precmd; " 823 * "PS1='$(eval $PRECMD)\\u@\\h:\\w\\$ '\n", 824 */ 802 case SHELL_ASH_BUSYBOX_LEGACY: 825 803 case SHELL_DASH: 826 /* Debian ash needs a precmd emulation via PS1, similar to BusyBox ash, 827 * but does not support escape sequences for user, host and cwd in prompt. 804 /* Debian ash needs a precmd emulation via PS1. 828 805 * Attention! Make sure that the buffer for precmd is big enough. 829 806 * 830 * We want to have a fancy dynamic prompt with user@host:cwd just like in the BusyBox831 * examples above,but because replacing the home directory part of the path by "~" is807 * We want to have a fancy dynamic prompt with user@host:cwd, 808 * but because replacing the home directory part of the path by "~" is 832 809 * complicated, it bloats the precmd to a size > BUF_SMALL (128). 833 810 * 834 811 * The following example is a little less fancy (home directory not replaced) … … subshell_name_quote (const char *s) 924 901 quote_cmd_start = "(printf \"%b\" '"; 925 902 quote_cmd_end = "')"; 926 903 } 927 /* TODO: When BusyBox printf is fixed, get rid of this "else if", see 928 http://lists.busybox.net/pipermail/busybox/2012-March/077460.html */ 929 /* else if (subshell_type == ASH_BUSYBOX) 904 /* see http://lists.busybox.net/pipermail/busybox/2012-March/077460.html */ 905 else if (mc_global.shell->type == SHELL_ASH_BUSYBOX_LEGACY) 930 906 { 931 907 quote_cmd_start = "\"`echo -en '"; 932 908 quote_cmd_end = "'`\""; 933 } */909 } 934 910 else 935 911 { 936 912 quote_cmd_start = "\"`printf \"%b\" '"; … … init_subshell (void) 1059 1035 return; 1060 1036 } 1061 1037 } 1062 else if (pipe (subshell_pipe)) /* subshell_type is BASH, ASH_BUSYBOX , DASH or ZSH */1038 else if (pipe (subshell_pipe)) /* subshell_type is BASH, ASH_BUSYBOX_LEGACY, DASH or ZSH */ 1063 1039 { 1064 1040 perror (__FILE__ ": couldn't create pipe"); 1065 1041 mc_global.tty.use_subshell = FALSE; … … init_subshell (void) 1102 1078 tty_disable_interrupt_key (); 1103 1079 if (!subshell_alive) 1104 1080 mc_global.tty.use_subshell = FALSE; /* Subshell died instantly, so don't use it */ 1081 if (!mc_global.tty.use_subshell) 1082 fprintf (stderr, __FILE__ ": failed to initialize \"%s\" shell of type %s\r\n", mc_global.shell->path, shell_type_string (mc_global.shell->type)); 1105 1083 } 1106 1084 1107 1085 /* --------------------------------------------------------------------------------------------- */