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