Ticket #3692: 3692-mc_subshell-V3.patch
File 3692-mc_subshell-V3.patch, 33.8 KB (added by alllexx88, 8 years ago) |
---|
-
lib/shell.c
Author: alllexx88 <opotapenko@gmail.com> Date: Mon, 21 Nov 2016 08:04:01 +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 | 554 +++++++++++++++++++++++++++++++++++++++++++++----- lib/shell.h | 50 ++++- src/subshell/common.c | 58 ++---- 3 files changed, 564 insertions(+), 98 deletions(-) diff --git a/lib/shell.c b/lib/shell.c index 6f07cb0..24f837c 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) 153 { 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) 144 174 { 145 if (strstr (mc_shell->path, "/zsh") != NULL || strstr (mc_shell->real_path, "/zsh") != NULL 146 || getenv ("ZSH_VERSION") != NULL) 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 (void * args) 212 { 213 int * argsint = (int*)(args), pid = argsint[0], * exit_status = &argsint[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 argsint[0] = 0; 222 223 pthread_exit (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, err; 234 mc_timer_t * start = NULL; 235 pthread_t thread[1]; 236 237 if (timeout > 0) 238 start = mc_timer_new (); 239 240 if (pid <= 0) 241 return -1; 242 243 /* launch thread that waits for child process termination and gets the exit status */ 244 err = pthread_create(thread, NULL, mc_shell_waitpid_thread, args); 245 246 if (err != 0) /* failed to create thread */ 154 247 { 155 /* Also detects csh symlinked to tcsh */ 156 mc_shell->type = SHELL_TCSH; 157 mc_shell->name = "tcsh"; 248 mc_timer_destroy (start); 249 return -1; 158 250 } 159 else if (strstr (mc_shell->path, "/csh") != NULL 160 || strstr (mc_shell->real_path, "/csh") != NULL)251 252 if ((pipes != NULL) && (pipes->n > 0)) 161 253 { 162 mc_shell->type = SHELL_TCSH; 163 mc_shell->name = "csh"; 254 /* we have pipes to poll */ 255 int ret, n = pipes->n; 256 struct pollfd * fdinfo = g_new (struct pollfd, n); 257 ssize_t size; 258 char buff[256]; 259 260 /* prepare pollfd struct array for poll () and make all pipes non blocking */ 261 for (i = 0; i < n; i++) 262 { 263 fdinfo[i].fd = pipes->poll_fds[i]; 264 fdinfo[i].events = POLLIN | POLLOUT; 265 fcntl (fdinfo[i].fd, F_SETFD, fcntl (fdinfo[i].fd, F_GETFD) | O_NONBLOCK); 266 } 267 268 ret = poll (fdinfo, n, 0); 269 270 /* while child is running, read/write from/to all requested pipes */ 271 while (args[0] > 0) 272 { 273 if (mc_timer_elapsed (start) >= 1000 * timeout) 274 { 275 /* kill spawned child on timeout */ 276 kill (pid, SIGKILL); 277 break; 278 } else if (ret > 0) 279 for (i = n - 1; i >= 0; i--) 280 { 281 if ((fdinfo[i].revents & POLLIN) != 0) 282 { 283 /* this is an input pipe, and it has data ready for reading */ 284 size = read (fdinfo[i].fd, buff, 255); 285 286 if ((pipes->buff[i] != NULL) && (size > 0)) 287 { 288 buff[size] = '\0'; 289 mc_shell_append_string (pipes->buff[i], buff); 290 } 291 } else if ((fdinfo[i].revents & POLLOUT) != 0) 292 { 293 /* this is an output pipe, and it is ready for writing */ 294 295 if ((pipes->buff[i] != NULL) && (*(pipes->buff[i]) != NULL)) 296 { 297 char * source = *(pipes->buff[i]); 298 299 size = write (fdinfo[i].fd, source, strlen (source)); 300 301 if (size > 0) 302 { 303 *(pipes->buff[i]) = g_strdup (&source[size]); 304 g_free (source); 305 source = *(pipes->buff[i]); 306 } 307 308 if (source[0] == '\0') 309 { 310 /* done writing: close the pipe, and remove from fdinfo */ 311 close (fdinfo[i].fd); 312 fdinfo[i].fd = pipes->poll_fds[i] = -1; 313 } 314 } else 315 { 316 /* nothing to write: close the pipe, and remove from fdinfo */ 317 close (fdinfo[i].fd); 318 fdinfo[i].fd = pipes->poll_fds[i] = -1; 319 } 320 } 321 } 322 323 ret = poll (fdinfo, n, 0); 324 } 325 326 g_free (fdinfo); 327 } else if (timeout > 0) 328 while (args[0] > 0) /* loop here until child terminates or command timeouts */ 329 if (mc_timer_elapsed (start) >= 1000 * timeout) 330 { 331 /* kill spawned child on timeout */ 332 kill (pid, SIGKILL); 333 break; 334 } 335 336 /* no need for the timer anymore */ 337 mc_timer_destroy (start); 338 339 pthread_join (thread [0], NULL); 340 341 /* read all the remaining data in the pipes and close them */ 342 if ((pipes != NULL) && (pipes->n > 0)) 343 for (i = 0; i < pipes->n; i++) 344 mc_shell_finalize_fd (&pipes->poll_fds[i], pipes->buff[i]); 345 346 return args[1]; 347 } 348 349 /* --------------------------------------------------------------------------------------------- 350 This function is similar to g_spawn_sync () function with some additional functionality: 351 * add optional timeout to SIGKILL the child after its expiration 352 * optionally feed a string to child's stdin 353 * optionally poll and read/write requested pipes during child execution 354 --------------------------------------------------------------------------------------------- */ 355 356 static gboolean 357 mc_shell_execute (const char * working_directory, 358 char ** argv, 359 char ** envp, 360 GSpawnFlags flags, 361 GSpawnChildSetupFunc child_setup, 362 void * user_data, 363 const char * feed_standard_input, 364 char ** standard_output, 365 char ** standard_error, 366 guint timeout, /* timeout is in milliseconds, 0 timeout means no timeout */ 367 pipes_data_t * pipes, 368 int * exit_status, 369 GError ** error) 370 { 371 int i, pid, stdin_fd, stdout_fd, stderr_fd, status, 372 * stdin_return = NULL, * stdout_return = NULL, * stderr_return = NULL; 373 char * standard_input = NULL; 374 pipes_data_t pipes_data; 375 376 pipes_data.n = 0; 377 378 if ((pipes != NULL) && (pipes->n > 0)) 379 { 380 pipes_data.n = pipes->n; 381 382 /* without this the requested pipes will be closed for the child */ 383 flags |= G_SPAWN_LEAVE_DESCRIPTORS_OPEN; 384 385 /* make parent sides of the pipes get closed for the child */ 386 for (i = 0; i < pipes->n; i++) 387 fcntl (pipes->poll_fds[i], F_SETFD, fcntl (pipes->poll_fds[i], F_GETFD) | FD_CLOEXEC); 164 388 } 165 else if (strstr (mc_shell->path, "/fish") != NULL 166 || strstr (mc_shell->real_path, "/fish") != NULL) 389 390 /* we'll reap the child ourselves with waitpid () */ 391 flags |= G_SPAWN_DO_NOT_REAP_CHILD; 392 393 if (feed_standard_input != NULL) 167 394 { 168 mc_shell->type = SHELL_FISH;169 mc_shell->name = "fish";395 stdin_return = &stdin_fd; 396 pipes_data.n++; 170 397 } 171 else if (strstr (mc_shell->path, "/dash") != NULL 172 || strstr (mc_shell->real_path, "/dash")!= NULL)398 399 if (standard_output != NULL) 173 400 { 174 /* Debian ash (also found if symlinked to by ash/sh) */ 175 mc_shell->type = SHELL_DASH; 176 mc_shell->name = "dash"; 401 stdout_return = &stdout_fd; 402 pipes_data.n++; 177 403 } 178 else if (strstr (mc_shell->real_path, "/busybox") != NULL) 404 else 405 flags |= G_SPAWN_STDOUT_TO_DEV_NULL; 406 407 if (standard_error != NULL) 179 408 { 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; 409 stderr_return = &stderr_fd; 410 pipes_data.n++; 189 411 } 190 412 else 191 mc_shell->type = SHELL_NONE; 413 flags |= G_SPAWN_STDERR_TO_DEV_NULL; 414 415 if (!g_spawn_async_with_pipes (working_directory, argv, envp, flags, 416 child_setup, user_data, &pid, stdin_return, stdout_return, stderr_return, error)) 417 { 418 g_strfreev (envp); 419 return FALSE; 420 } 421 422 pipes_data.poll_fds = g_new(int, pipes_data.n); 423 pipes_data.buff = g_new(char**, pipes_data.n); 424 pipes_data.n = 0; 425 426 if ((pipes != NULL) && (pipes->n > 0)) 427 { 428 pipes_data.n = pipes->n; 429 430 for (i = 0; i < pipes->n; i++) 431 { 432 pipes_data.poll_fds[i] = pipes->poll_fds[i]; 433 pipes_data.buff[i] = pipes->buff[i]; 434 /* close child sides of pipes for the parent */ 435 mc_shell_finalize_fd (&pipes->close_fds[i], NULL); 436 } 437 } 438 439 if (standard_output != NULL) 440 { 441 pipes_data.poll_fds[pipes_data.n] = stdout_fd; 442 pipes_data.buff[pipes_data.n] = standard_output; 443 pipes_data.n++; 444 } 445 446 if (standard_error != NULL) 447 { 448 pipes_data.poll_fds[pipes_data.n] = stderr_fd; 449 pipes_data.buff[pipes_data.n] = standard_error; 450 pipes_data.n++; 451 } 452 453 if (feed_standard_input != NULL) 454 { 455 standard_input = g_strdup (feed_standard_input); 456 pipes_data.poll_fds[pipes_data.n] = stdin_fd; 457 pipes_data.buff[pipes_data.n] = &standard_input; 458 pipes_data.n++; 459 } 460 461 status = mc_shell_child_wait (pid, timeout, &pipes_data); 462 463 if (exit_status != NULL) 464 *exit_status = status; 465 466 if ((pipes != NULL) && (pipes->n > 0)) 467 { 468 /* the poll_fds are supposed to be closed now, and pipes_data.poll_fds[i] set to '-1', so let 469 the function caller know they're closed by setting pipes->poll_fds[i] to '-1' too */ 470 for (i = 0; i < pipes->n; i++) 471 pipes->poll_fds[i] = pipes_data.poll_fds[i]; 472 } 473 474 g_free (standard_input); 475 g_free (pipes_data.poll_fds); 476 g_free (pipes_data.buff); 477 478 return TRUE; 479 } 480 481 /* --------------------------------------------------------------------------------------------- 482 This function returns TRUE for a shell if it sets a variable with respective name. We unset 483 environmental variable of the same name in the child envp to make sure it's not inherited. 484 We use three different commands for the respective shell syntaxes: bourne, C and fish. 485 We test for exit code and stdout output to check if the command was successful. 486 If we test a shell with a wrong syntax, it returns error code, and function returns FALSE, 487 so in fact we test for syntax first, and only then for shell setting the variable. 488 --------------------------------------------------------------------------------------------- */ 489 static gboolean 490 mc_shell_internal_variable_set (mc_shell_t * mc_shell, const char * name, 491 const shell_syntax_t shell_syntax) 492 { 493 /* define arg1 instead of using "-c" directly when assigning argv to 494 silence [-Wdiscarded-qualifiers] compiler warning */ 495 char arg1[] = "-c", * argv[] = {mc_shell->path, arg1, NULL, NULL}, * standard_output = NULL; 496 char ** envp; 497 int exit_status; 498 size_t i, shift, len1, len2; 499 gboolean success; 500 501 /* copy parent's envp and unset the environmental variable in question if set */ 502 envp = g_strdupv (environ); 503 len1 = g_strv_length (envp); 504 len2 = strlen (name); 505 506 for (i = shift = 0; i < len1; i++) 507 { 508 if ((strncmp (name, envp [i], len2) == 0) && (envp [i][len2] == '=')) 509 { 510 g_free (envp [i]); 511 shift++; 512 } else 513 envp [i - shift] = envp [i]; 514 } 515 for (i = len1 - shift; i < len1; i++) 516 envp [i] = NULL; 517 518 /* for proper shells these commands return 0 exit code and print 'name' value to stdout */ 519 if (shell_syntax == SHELL_SYNTAX_BOURNE) 520 argv[2] = g_strdup_printf ("if [ -z ${%s+x} ]; then " 521 "exit 1;" 522 "else " 523 "printf '%s';" 524 "fi", name, name); 525 else if (shell_syntax == SHELL_SYNTAX_C) 526 argv[2] = g_strdup_printf ("if !( $?%s ) then\n" 527 "exit 1\n" 528 "else\n" 529 "printf '%s'\n" 530 "endif", name, name); 531 else /* shell_syntax == SHELL_SYNTAX_FISH */ 532 argv[2] = g_strdup_printf ("if set -q %s;" 533 "printf '%s';" 534 "else;" 535 "exit 1;" 536 "end", name, name); 537 538 539 success = mc_shell_execute (NULL, argv, envp, 0, NULL, NULL, NULL, &standard_output, 540 NULL, 100, NULL, &exit_status, NULL) && 541 g_spawn_check_exit_status (exit_status, NULL) && 542 (g_strcmp0 (standard_output, name) == 0); 543 544 g_free (argv[2]); 545 g_free (standard_output); 546 547 return success; 192 548 } 193 549 194 550 /* --------------------------------------------------------------------------------------------- */ 195 551 196 552 static void 197 mc_shell_recognize_ path(mc_shell_t * mc_shell)553 mc_shell_recognize_from_internal_variable (mc_shell_t * mc_shell) 198 554 { 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 {555 /* These tests recognize bash, zsh, tcsh and fish by testing for 556 variables that only these shells set */ 557 if (mc_shell_internal_variable_set (mc_shell, "BASH", SHELL_SYNTAX_BOURNE)) { 202 558 mc_shell->type = SHELL_BASH; 203 mc_shell->name = "bash";204 559 } 205 else if (strstr (mc_shell->path, "/sh") != NULL || getenv ("SH") != NULL) 560 else if (mc_shell_internal_variable_set (mc_shell, "ZSH_NAME", SHELL_SYNTAX_BOURNE)) 561 { 562 mc_shell->type = SHELL_ZSH; 563 } 564 else if (mc_shell_internal_variable_set (mc_shell, "tcsh", SHELL_SYNTAX_C)) 206 565 { 207 mc_shell->type = SHELL_SH; 208 mc_shell->name = "sh"; 566 mc_shell->type = SHELL_TCSH; 209 567 } 210 else if ( strstr (mc_shell->path, "/ash") != NULL || getenv ("ASH") != NULL)568 else if (mc_shell_internal_variable_set (mc_shell, "fish_greeting", SHELL_SYNTAX_FISH)) 211 569 { 212 mc_shell->type = SHELL_ASH_BUSYBOX; 213 mc_shell->name = "ash"; 570 mc_shell->type = SHELL_FISH; 214 571 } 215 572 else 216 573 mc_shell->type = SHELL_NONE; 217 574 } 218 575 576 /* --------------------------------------------------------------------------------------------- 577 This function tests whether a shell treats PS1 as prompt string that is being expanded. 578 We test for an old BusyBox ash 4-digit octal codes bug in printf along the way too. 579 mc_shell->type will be set to: 580 SHELL_DASH: Test for PS1 expansion succeeds fully. This can mean dash, or BusyBox ash 581 with CONFIG_ASH_EXPAND_PRMT enabled, or something other compatible 582 SHELL_ASH_BUSYBOX_LEGACY: PS1 is being expanded, but printf suffers from the 4-digit octal 583 codes bug, so apply the printf workaround 584 SHELL_NONE: Test failed. Possible reasons: PS1 is not being treated as a prompt string, 585 PS1 is not being expanded (no CONFIG_ASH_EXPAND_PRMT in BusyBox ash?), 586 shell doesn't recognize syntax, failed to execute shell, etc. 587 --------------------------------------------------------------------------------------------- */ 588 static void 589 mc_shell_test_prompt_expansion (mc_shell_t * mc_shell) 590 { 591 /* define arg1 instead of using "-i" directly when assigning argv to 592 silence [-Wdiscarded-qualifiers] compiler warning */ 593 char arg1[] = "-i", * argv[] = {mc_shell->path, arg1, NULL}, 594 ** envp, * standard_input; 595 int subshell_pipe[2], exit_status; 596 size_t i, len; 597 598 char * out_buff = NULL, ** out_buff_addr = &out_buff; 599 pipes_data_t pipes; 600 601 if (pipe (subshell_pipe)) 602 { 603 /* failed to create pipe */ 604 mc_shell->type = SHELL_NONE; 605 return; 606 } 607 608 /* copy parent's envp and override the PS1 env variable to safe non-interactive if set */ 609 envp = g_strdupv (environ); 610 len = g_strv_length (envp); 611 612 for (i = 0; i < len; i++) 613 { 614 if (strncmp ("PS1=", envp [i], 4) == 0) 615 { 616 g_free (envp [i]); 617 envp [i] = g_strdup ("PS1=$ "); 618 } 619 } 620 621 /* Check if executing `PS1='$(printf "%b" "\\0057a\\0057\\n" >&subshell_pipe[WRITE])'` command 622 in interactive mode gets PS1 evaluated before the next `exit 0` command, by capturing the pipe 623 output, and comparing it to the expected output for dash / BusyBox ash ("/a/"), and 624 if it doesn't match - to BusyBox pre 1.20 broken printf output ("\005""7a""\005""7") */ 625 626 standard_input = g_strdup_printf ("PS1='$(printf \"%%b\" \"\\\\0057a\\\\0057\" >&%d)'\n" 627 "exit 0\n", subshell_pipe[WRITE]); 628 629 pipes.buff = &out_buff_addr; /* pipes.buff is an array of pointers to strings */ 630 pipes.poll_fds = &subshell_pipe[READ]; /* int array of pipes to poll */ 631 pipes.close_fds = &subshell_pipe[WRITE]; /* int array of pipes to close for the parent */ 632 pipes.n = 1; /* pipes.n is the number of pipes */ 633 634 if (mc_shell_execute (NULL, argv, envp, 0, NULL, NULL, standard_input, NULL, 635 NULL, 100, &pipes, &exit_status, NULL) && 636 g_spawn_check_exit_status (exit_status, NULL)) 637 { 638 if (g_strcmp0 (out_buff, "/a/") == 0) 639 mc_shell->type = SHELL_DASH; 640 else if (g_strcmp0 (out_buff, "\005""7a""\005""7") == 0) 641 mc_shell->type = SHELL_ASH_BUSYBOX_LEGACY; 642 else 643 mc_shell->type = SHELL_NONE; 644 } 645 else 646 mc_shell->type = SHELL_NONE; 647 648 g_free (out_buff); 649 g_free (standard_input); 650 651 /* if mc_shell_execute () failed */ 652 mc_shell_finalize_fd (&subshell_pipe[WRITE], NULL); 653 mc_shell_finalize_fd (&subshell_pipe[READ], NULL); 654 } 655 219 656 /* --------------------------------------------------------------------------------------------- */ 220 657 /*** public functions ****************************************************************************/ 221 658 /* --------------------------------------------------------------------------------------------- */ … … mc_shell_init (void) 232 669 233 670 mc_shell->real_path = mc_realpath (mc_shell->path, rp_shell); 234 671 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. */ 672 /* Find out what type of shell we have. Use tests for specific variables that 673 * different shells set for most shell types. To recognize dash, or compatible 674 * BusyBox ash, we test whether prompt expansion works. */ 237 675 238 if (mc_shell->real_path != NULL) 239 mc_shell_recognize_real_path(mc_shell);676 if (mc_shell->real_path != NULL) { 677 mc_shell_recognize_from_internal_variable (mc_shell); 240 678 241 if (mc_shell->type == SHELL_NONE)242 mc_shell_recognize_path(mc_shell);679 if (mc_shell->type == SHELL_NONE) 680 mc_shell_test_prompt_expansion (mc_shell); 243 681 244 if (mc_shell->type == SHELL_NONE) 245 mc_global.tty.use_subshell = FALSE; 682 if (mc_shell->type == SHELL_NONE) 683 fprintf (stderr, __FILE__ ": failed to recognize shell \"%s\" as supported subshell. " 684 "Supported shells are: bash, zsh, tcsh, fish, dash and " 685 "BusyBox ash with enabled CONFIG_ASH_EXPAND_PRMT\r\n", 686 mc_shell->path); 687 } else { 688 mc_shell->type = SHELL_NONE; 689 fprintf (stderr, __FILE__ ": wrong \"%s\" shell: No such file\r\n", mc_shell->path); 690 } 246 691 692 mc_global.tty.use_subshell = mc_shell->type != SHELL_NONE; 247 693 mc_global.shell = mc_shell; 248 694 } 249 695 -
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 a1f054e..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 % u\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 /* --------------------------------------------------------------------------------------------- */