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