Ticket #3692: 3692-mc_subshell-V2.patch

File 3692-mc_subshell-V2.patch, 32.7 KB (added by alllexx88, 3 years ago)
  • lib/shell.c

    Author: alllexx88 <opotapenko@gmail.com>
    Date:   Thu, 6 Oct 2016 16:04:53 +0300
    
        More sophisticated shell type detection method
    
        * Make tests on shell binary instead of trying to guess shell type from
          path. Most supported shells set specific variables, hence by testing
          whether such variables are set, we can guess shell type in a more
          reliable way. This works with bash, zsh, tcsh and fish. For guessing
          dash or BusyBox ash (which are treated the same), we run a more peculiar
          test on whether shell supports expansion in PS1 as a prompt string. The
          latter test is also designed to differentiate a legacy pre 1.20 BusyBox
          ash, which allows to apply printf workaround in the case of such shell.
        * Remove chdir command from subshell initialization code, and use full
          paths for init_file instead. Changing dir only allows to use relative
          init_file paths, but can instead potentially lead to some problems, as
          previously noted in the comments; so by not doing this we add additional
          layer of protection against bugs.
        * Remove unneeded SHELL_SH shell type, and 'name' mc_shell_t field, since
          the latter was only being used as arg0 when initializing subshell, and
          all shells apparently initialize fine regardless of arg0 after being
          correctly detected, except for zsh, which has to be invoked as "zsh",
          probably because it behaves in a bourne compatible way otherwise
          (so we just leave arg0 as "zsh" for it)
        * Also add a little error verbosity in scope of detecting shell type and
          subshell initialization
    
    ---
     lib/shell.c           | 512 ++++++++++++++++++++++++++++++++++++++++++++------
     lib/shell.h           |  50 ++++-
     src/subshell/common.c |  58 ++----
     3 files changed, 521 insertions(+), 99 deletions(-)
    
    diff --git a/lib/shell.c b/lib/shell.c
    index 6f07cb0..f655f12 100644
    a b  
    3333#include <stdarg.h> 
    3434#include <stdio.h> 
    3535#include <stdlib.h> 
     36#include <sys/wait.h> 
     37#include <poll.h> 
    3638 
    3739#include "global.h" 
    3840#include "util.h" 
     
    4446 
    4547/*** file scope type declarations ****************************************************************/ 
    4648 
     49/* For pipes */ 
     50enum 
     51{ 
     52    READ = 0, 
     53    WRITE = 1 
     54}; 
     55 
    4756/*** file scope variables ************************************************************************/ 
    4857 
    4958static char rp_shell[PATH_MAX]; 
    mc_shell_get_from_env (void) 
    140149/* --------------------------------------------------------------------------------------------- */ 
    141150 
    142151static void 
    143 mc_shell_recognize_real_path (mc_shell_t * mc_shell) 
     152mc_shell_append_string (char ** dest, const char * append) 
    144153{ 
    145     if (strstr (mc_shell->path, "/zsh") != NULL || strstr (mc_shell->real_path, "/zsh") != NULL 
    146         || getenv ("ZSH_VERSION") != NULL) 
     154    char * str; 
     155 
     156    if ((dest == NULL) || (append == NULL) || (append[0] == '\0')) 
     157        return; 
     158 
     159    if (*dest == NULL) 
     160        str = g_strdup (append); 
     161    else 
     162        str = g_strdup_printf ("%s%s", *dest, append); 
     163 
     164    g_free (*dest); 
     165    *dest = str; 
     166} 
     167 
     168/* Close fd (we expect a pipe), appending all remaining data in case of input pipe to *dest. 
     169   If (dest == NULL), we still read all the data in an input pipe to close it properly. 
     170   Set the fd to -1 to indicate it's closed */ 
     171 
     172static void 
     173mc_shell_finalize_fd (int * fd, char ** dest) 
     174{ 
     175    struct pollfd fdinfo; 
     176    int ret; 
     177    char buff[256]; 
     178    ssize_t size; 
     179 
     180    if ((fd == NULL) || (*fd < 0)) 
     181        return; 
     182 
     183    fdinfo.fd = *fd; 
     184    fdinfo.events = POLLIN; 
     185 
     186    ret = poll (&fdinfo, 1, 0); 
     187 
     188    if ((ret > 0) && ((fdinfo.revents & POLLIN) != 0)) 
     189        fcntl (*fd, F_SETFD, fcntl (*fd, F_GETFD) | O_NONBLOCK); 
     190 
     191    while ((ret > 0) && ((fdinfo.revents & POLLIN) != 0)) 
    147192    { 
    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); 
    151202    } 
    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 
     210static void * 
     211mc_shell_waitpid_thread (int * args) 
     212{ 
     213    int pid = args[0], * exit_status = &args[1]; 
     214 
     215    /* wait for child to terminate and get exit_status, 
     216       or set exit_status to '-1' on waitpid error */ 
     217    if (waitpid (pid, exit_status, 0) == -1) 
     218        *exit_status = -1; 
     219 
     220    /* this signales the parent thread that child terminated */ 
     221    args[0] = 0; 
     222 
     223    return NULL; 
     224} 
     225 
     226/* Wait for child process to terminate and read/write requested pipes in the process. 
     227   In case the child is running longer than the timeout value, terminate it with a 
     228   SIGKILL signal. Timeout is in milliseconds, 0 timeout means no timeout. 
     229   Returns child exit_status, or '-1' on non positive pid or waitpid () failure */ 
     230static int 
     231mc_shell_child_wait (int pid, guint timeout, pipes_data_t * pipes) 
     232{ 
     233    int args[2] = {pid}, i; 
     234    gint64 start = g_get_monotonic_time (); 
     235    GThread * thread; 
     236 
     237    if (pid <= 0) 
     238        return -1; 
     239 
     240    /* launch thread that waits for child process termination and gets the exit status */ 
     241    thread = g_thread_new ("waitpid_thread", (GThreadFunc)mc_shell_waitpid_thread, args); 
     242 
     243    if ((pipes != NULL) && (pipes->n > 0)) 
    154244    { 
    155         /* Also detects csh symlinked to tcsh */ 
    156         mc_shell->type = SHELL_TCSH; 
    157         mc_shell->name = "tcsh"; 
    158     } 
    159     else if (strstr (mc_shell->path, "/csh") != NULL 
    160              || strstr (mc_shell->real_path, "/csh") != NULL) 
     245        /* we have pipes to poll */ 
     246        int ret, n = pipes->n; 
     247        struct pollfd fdinfo[n]; 
     248        ssize_t size; 
     249        char buff[256]; 
     250 
     251        /* prepare pollfd struct array for poll () and make all pipes non blocking */ 
     252        for (i = 0; i < n; i++) 
     253        { 
     254            fdinfo[i].fd = pipes->poll_fds[i]; 
     255            fdinfo[i].events = POLLIN | POLLOUT; 
     256            fcntl (fdinfo[i].fd, F_SETFD, fcntl (fdinfo[i].fd, F_GETFD) | O_NONBLOCK); 
     257        } 
     258 
     259        ret = poll (fdinfo, n, 0); 
     260 
     261        /* while child is running, read/write from/to all requested pipes */ 
     262        while (args[0] > 0) 
     263        { 
     264            if ((timeout > 0) && (g_get_monotonic_time () >= start + 1000 * timeout)) 
     265            { 
     266                /* kill spawned child on timeout */ 
     267                kill (pid, SIGKILL); 
     268                break; 
     269            } else if (ret > 0) 
     270                for (i = n - 1; i >= 0; i--) 
     271                { 
     272                    if ((fdinfo[i].revents & POLLIN) != 0) 
     273                    { 
     274                        /* this is an input pipe, and it has data ready for reading */ 
     275                        size = read (fdinfo[i].fd, buff, 255); 
     276 
     277                        if ((pipes->buff[i] != NULL) && (size > 0)) 
     278                        { 
     279                            buff[size] = '\0'; 
     280                            mc_shell_append_string (pipes->buff[i], buff); 
     281                        } 
     282                    } else if ((fdinfo[i].revents & POLLOUT) != 0) 
     283                    { 
     284                        /* this is an output pipe, and it is ready for writing */ 
     285 
     286                        if ((pipes->buff[i] != NULL) && (*(pipes->buff[i]) != NULL)) 
     287                        { 
     288                            char * source = *(pipes->buff[i]); 
     289 
     290                            size = write (fdinfo[i].fd, source, strlen (source)); 
     291 
     292                            if (size > 0) 
     293                            { 
     294                                *(pipes->buff[i]) = g_strdup (&source[size]); 
     295                                g_free (source); 
     296                                source = *(pipes->buff[i]); 
     297                            } 
     298 
     299                            if (source[0] == '\0') 
     300                            { 
     301                                /* done writing: close the pipe, and remove from fdinfo */ 
     302                                close (fdinfo[i].fd); 
     303                                fdinfo[i].fd = pipes->poll_fds[i] = -1; 
     304                            } 
     305                        } else 
     306                        { 
     307                            /* nothing to write: close the pipe, and remove from fdinfo */ 
     308                            close (fdinfo[i].fd); 
     309                            fdinfo[i].fd = pipes->poll_fds[i] = -1; 
     310                        } 
     311                    } 
     312                } 
     313 
     314            ret = poll (fdinfo, n, 0); 
     315        } 
     316    } else if (timeout > 0) 
     317        while (args[0] > 0) /* loop here until child terminates or command timeouts */ 
     318            if (g_get_monotonic_time () >= start + 1000 * timeout) 
     319            { 
     320                /* kill spawned child on timeout */ 
     321                kill (pid, SIGKILL); 
     322                break; 
     323            } 
     324 
     325    g_thread_join (thread); 
     326    g_thread_unref (thread); 
     327 
     328    /* read all the remaining data in the pipes and close them */ 
     329    if ((pipes != NULL) && (pipes->n > 0)) 
     330        for (i = 0; i < pipes->n; i++) 
     331            mc_shell_finalize_fd (&pipes->poll_fds[i], pipes->buff[i]); 
     332 
     333    return args[1]; 
     334} 
     335 
     336/* --------------------------------------------------------------------------------------------- 
     337   This function is similar to g_spawn_sync () function with some additional functionality: 
     338       * add optional timeout to SIGKILL the child after its expiration 
     339       * optionally feed a string to child's stdin 
     340       * optionally poll and read/write requested pipes during child execution 
     341   --------------------------------------------------------------------------------------------- */ 
     342 
     343static gboolean 
     344mc_shell_execute (const char * working_directory, 
     345                        char ** argv, 
     346                        char ** envp, 
     347                        GSpawnFlags flags, 
     348                        GSpawnChildSetupFunc child_setup, 
     349                        void * user_data, 
     350                        const char * feed_standard_input, 
     351                        char ** standard_output, 
     352                        char ** standard_error, 
     353                        guint timeout, /* timeout is in milliseconds, 0 timeout means no timeout */ 
     354                        pipes_data_t * pipes, 
     355                        int * exit_status, 
     356                        GError ** error) 
     357{ 
     358    int i, pid, stdin_fd, stdout_fd, stderr_fd, status, 
     359        * stdin_return = NULL, * stdout_return = NULL, * stderr_return = NULL; 
     360    char * standard_input = NULL; 
     361    pipes_data_t pipes_data; 
     362 
     363    pipes_data.n = 0; 
     364 
     365    if ((pipes != NULL) && (pipes->n > 0)) 
    161366    { 
    162         mc_shell->type = SHELL_TCSH; 
    163         mc_shell->name = "csh"; 
     367        pipes_data.n = pipes->n; 
     368 
     369        /* without this the requested pipes will be closed for the child */ 
     370        flags |= G_SPAWN_LEAVE_DESCRIPTORS_OPEN; 
     371 
     372        /* make parent sides of the pipes get closed for the child */ 
     373        for (i = 0; i < pipes->n; i++) 
     374            fcntl (pipes->poll_fds[i], F_SETFD, fcntl (pipes->poll_fds[i], F_GETFD) | FD_CLOEXEC); 
    164375    } 
    165     else if (strstr (mc_shell->path, "/fish") != NULL 
    166              || strstr (mc_shell->real_path, "/fish") != NULL) 
     376 
     377    /* we'll reap the child ourselves with waitpid () */ 
     378    flags |= G_SPAWN_DO_NOT_REAP_CHILD; 
     379 
     380    if (feed_standard_input != NULL) 
    167381    { 
    168         mc_shell->type = SHELL_FISH; 
    169         mc_shell->name = "fish"; 
     382        stdin_return = &stdin_fd; 
     383        pipes_data.n++; 
    170384    } 
    171     else if (strstr (mc_shell->path, "/dash") != NULL 
    172              || strstr (mc_shell->real_path, "/dash") != NULL) 
     385 
     386    if (standard_output != NULL) 
    173387    { 
    174         /* Debian ash (also found if symlinked to by ash/sh) */ 
    175         mc_shell->type = SHELL_DASH; 
    176         mc_shell->name = "dash"; 
     388        stdout_return = &stdout_fd; 
     389        pipes_data.n++; 
    177390    } 
    178     else if (strstr (mc_shell->real_path, "/busybox") != NULL) 
     391    else 
     392        flags |= G_SPAWN_STDOUT_TO_DEV_NULL; 
     393 
     394    if (standard_error != NULL) 
    179395    { 
    180         /* If shell is symlinked to busybox, assume it is an ash, even though theoretically 
    181          * it could also be a hush (a mini shell for non-MMU systems deactivated by default). 
    182          * For simplicity's sake we assume that busybox always contains an ash, not a hush. 
    183          * On embedded platforms or on server systems, /bin/sh often points to busybox. 
    184          * Sometimes even bash is symlinked to busybox (CONFIG_FEATURE_BASH_IS_ASH option), 
    185          * so we need to check busybox symlinks *before* checking for the name "bash" 
    186          * in order to avoid that case. */ 
    187         mc_shell->type = SHELL_ASH_BUSYBOX; 
    188         mc_shell->name = mc_shell->path; 
     396        stderr_return = &stderr_fd; 
     397        pipes_data.n++; 
    189398    } 
    190399    else 
    191         mc_shell->type = SHELL_NONE; 
     400        flags |= G_SPAWN_STDERR_TO_DEV_NULL; 
     401 
     402    if (!g_spawn_async_with_pipes (working_directory, argv, envp, flags, 
     403             child_setup, user_data, &pid, stdin_return, stdout_return, stderr_return, error)) 
     404    { 
     405        g_strfreev (envp); 
     406        return FALSE; 
     407    } 
     408 
     409    pipes_data.poll_fds = g_new(int, pipes_data.n); 
     410    pipes_data.buff = g_new(char**, pipes_data.n); 
     411    pipes_data.n = 0; 
     412 
     413    if ((pipes != NULL) && (pipes->n > 0)) 
     414    { 
     415        pipes_data.n = pipes->n; 
     416 
     417        for (i = 0; i < pipes->n; i++) 
     418        { 
     419            pipes_data.poll_fds[i] = pipes->poll_fds[i]; 
     420            pipes_data.buff[i] = pipes->buff[i]; 
     421            /* close child sides of pipes for the parent */ 
     422            mc_shell_finalize_fd (&pipes->close_fds[i], NULL); 
     423        } 
     424    } 
     425 
     426    if (standard_output != NULL) 
     427    { 
     428        pipes_data.poll_fds[pipes_data.n] = stdout_fd; 
     429        pipes_data.buff[pipes_data.n] = standard_output; 
     430        pipes_data.n++; 
     431    } 
     432 
     433    if (standard_error != NULL) 
     434    { 
     435        pipes_data.poll_fds[pipes_data.n] = stderr_fd; 
     436        pipes_data.buff[pipes_data.n] = standard_error; 
     437        pipes_data.n++; 
     438    } 
     439 
     440    if (feed_standard_input != NULL) 
     441    { 
     442        standard_input = g_strdup (feed_standard_input); 
     443        pipes_data.poll_fds[pipes_data.n] = stdin_fd; 
     444        pipes_data.buff[pipes_data.n] = &standard_input; 
     445        pipes_data.n++; 
     446    } 
     447 
     448    status = mc_shell_child_wait (pid, timeout, &pipes_data); 
     449 
     450    if (exit_status != NULL) 
     451        *exit_status = status; 
     452 
     453    if ((pipes != NULL) && (pipes->n > 0)) 
     454    { 
     455        /* the poll_fds are supposed to be closed now, and pipes_data.poll_fds[i] set to '-1', so let 
     456           the function caller know they're closed by setting pipes->poll_fds[i] to '-1' too */ 
     457        for (i = 0; i < pipes->n; i++) 
     458            pipes->poll_fds[i] = pipes_data.poll_fds[i]; 
     459    } 
     460 
     461    g_free (standard_input); 
     462    g_free (pipes_data.poll_fds); 
     463    g_free (pipes_data.buff); 
     464 
     465    return TRUE; 
     466} 
     467 
     468/* --------------------------------------------------------------------------------------------- 
     469   This function returns TRUE for a shell if it sets a variable with respective name. We unset 
     470   environmental variable of the same name in the child envp to make sure it's not inherited. 
     471   We use three different commands for the respective shell syntaxes: bourne, C and fish. 
     472   We test for exit code and stdout output to check if the command was successful. 
     473   If we test a shell with a wrong syntax, it returns error code, and function returns FALSE, 
     474   so in fact we test for syntax first, and only then for shell setting the variable. 
     475   --------------------------------------------------------------------------------------------- */ 
     476static gboolean 
     477mc_shell_internal_variable_set (mc_shell_t * mc_shell, const char * name, 
     478                                const shell_syntax_t shell_syntax) 
     479{ 
     480    /* define arg1 instead of using "-c" directly when assigning argv to 
     481       silence [-Wdiscarded-qualifiers] compiler warning */ 
     482    char arg1[] = "-c", * argv[] = {mc_shell->path, arg1, NULL, NULL}, * standard_output = NULL; 
     483    char ** envp = g_environ_unsetenv (g_get_environ (), name); 
     484    int exit_status; 
     485    gboolean success; 
     486 
     487    /* for proper shells these commands return 0 exit code and print 'name' value to stdout */ 
     488    if (shell_syntax == SHELL_SYNTAX_BOURNE) 
     489        argv[2] = g_strdup_printf ("if [ -z ${%s+x} ]; then " 
     490                                       "exit 1;" 
     491                                   "else " 
     492                                       "printf '%s';" 
     493                                   "fi", name, name); 
     494    else if (shell_syntax == SHELL_SYNTAX_C) 
     495        argv[2] = g_strdup_printf ("if !( $?%s ) then\n" 
     496                                       "exit 1\n" 
     497                                   "else\n" 
     498                                       "printf '%s'\n" 
     499                                   "endif", name, name); 
     500    else /* shell_syntax == SHELL_SYNTAX_FISH */ 
     501        argv[2] = g_strdup_printf ("if set -q %s;" 
     502                                       "printf '%s';" 
     503                                   "else;" 
     504                                       "exit 1;" 
     505                                   "end", name, name); 
     506 
     507 
     508    success = mc_shell_execute (NULL, argv, envp, 0, NULL, NULL, NULL, &standard_output, 
     509                                NULL, 100, NULL, &exit_status, NULL) && 
     510              g_spawn_check_exit_status (exit_status, NULL) && 
     511              (g_strcmp0  (standard_output, name) == 0); 
     512 
     513    g_free (argv[2]); 
     514    g_free (standard_output); 
     515 
     516    return success; 
    192517} 
    193518 
    194519/* --------------------------------------------------------------------------------------------- */ 
    195520 
    196521static void 
    197 mc_shell_recognize_path (mc_shell_t * mc_shell) 
     522mc_shell_recognize_from_internal_variable (mc_shell_t * mc_shell) 
    198523{ 
    199     /* If shell is not symlinked to busybox, it is safe to assume it is a real shell */ 
    200     if (strstr (mc_shell->path, "/bash") != NULL || getenv ("BASH") != NULL) 
    201     { 
     524    /* These tests recognize bash, zsh, tcsh and fish by testing for 
     525       variables that only these shells set */ 
     526    if (mc_shell_internal_variable_set (mc_shell, "BASH", SHELL_SYNTAX_BOURNE)) { 
    202527        mc_shell->type = SHELL_BASH; 
    203         mc_shell->name = "bash"; 
    204528    } 
    205     else if (strstr (mc_shell->path, "/sh") != NULL || getenv ("SH") != NULL) 
     529    else if (mc_shell_internal_variable_set (mc_shell, "ZSH_NAME", SHELL_SYNTAX_BOURNE)) 
    206530    { 
    207         mc_shell->type = SHELL_SH; 
    208         mc_shell->name = "sh"; 
     531        mc_shell->type = SHELL_ZSH; 
    209532    } 
    210     else if (strstr (mc_shell->path, "/ash") != NULL || getenv ("ASH") != NULL) 
     533    else if (mc_shell_internal_variable_set (mc_shell, "tcsh", SHELL_SYNTAX_C)) 
    211534    { 
    212         mc_shell->type = SHELL_ASH_BUSYBOX; 
    213         mc_shell->name = "ash"; 
     535        mc_shell->type = SHELL_TCSH; 
     536    } 
     537    else if (mc_shell_internal_variable_set (mc_shell, "fish_greeting", SHELL_SYNTAX_FISH)) 
     538    { 
     539        mc_shell->type = SHELL_FISH; 
    214540    } 
    215541    else 
    216542        mc_shell->type = SHELL_NONE; 
    217543} 
    218544 
     545/* --------------------------------------------------------------------------------------------- 
     546   This function tests whether a shell treats PS1 as prompt string that is being expanded. 
     547   We test for an old BusyBox ash 4-digit octal codes bug in printf along the way too. 
     548   mc_shell->type will be set to: 
     549      SHELL_DASH: Test for PS1 expansion succeeds fully. This can mean dash, or BusyBox ash 
     550                  with CONFIG_ASH_EXPAND_PRMT enabled, or something other compatible 
     551      SHELL_ASH_BUSYBOX_LEGACY: PS1 is being expanded, but printf suffers from the 4-digit octal 
     552                                codes bug, so apply the printf workaround 
     553      SHELL_NONE: Test failed. Possible reasons: PS1 is not being treated as a prompt string, 
     554                  PS1 is not being expanded (no CONFIG_ASH_EXPAND_PRMT in BusyBox ash?), 
     555                  shell doesn't recognize syntax, failed to execute shell, etc. 
     556   --------------------------------------------------------------------------------------------- */ 
     557static void 
     558mc_shell_test_prompt_expansion (mc_shell_t * mc_shell) 
     559{ 
     560    /* define arg1 instead of using "-i" directly when assigning argv to 
     561       silence [-Wdiscarded-qualifiers] compiler warning */ 
     562    char arg1[] = "-i", * argv[] = {mc_shell->path, arg1, NULL}, 
     563         ** envp, * standard_input; 
     564    int subshell_pipe[2], exit_status; 
     565    char * out_buff = NULL, ** out_buff_addr = &out_buff; 
     566    pipes_data_t pipes; 
     567 
     568    if (pipe (subshell_pipe)) 
     569    { 
     570        /* failed to create pipe */ 
     571        mc_shell->type = SHELL_NONE; 
     572        return; 
     573    } 
     574 
     575    envp = g_environ_setenv (g_get_environ (), "PS1", "$ ", TRUE); 
     576 
     577    /* Check if executing `PS1='$(printf "%b" "\\0057a\\0057\\n" >&subshell_pipe[WRITE])'` command 
     578    in interactive mode gets PS1 evaluated before the next `exit 0` command, by capturing the pipe 
     579    output, and comparing it to the expected printf output ("/a/"), and if it doesn't match - 
     580    to BusyBox pre 1.20 broken printf output ("\005""7a""\005""7") */ 
     581 
     582    standard_input = g_strdup_printf ("PS1='$(printf \"%%b\" \"\\\\0057a\\\\0057\" >&%d)'\n" 
     583                                      "exit 0\n", subshell_pipe[WRITE]); 
     584 
     585    pipes.buff      = &out_buff_addr;        /* pipes.buff is an array of pointers to strings */ 
     586    pipes.poll_fds  = &subshell_pipe[READ];  /* int array of pipes to poll */ 
     587    pipes.close_fds = &subshell_pipe[WRITE]; /* int array of pipes to close for the parent */ 
     588    pipes.n         = 1;                     /* pipes.n is the number of pipes */ 
     589 
     590    if (mc_shell_execute (NULL, argv, envp, 0, NULL, NULL, standard_input, NULL, 
     591                          NULL, 100, &pipes, &exit_status, NULL) && 
     592        g_spawn_check_exit_status (exit_status, NULL)) 
     593    { 
     594        if (g_strcmp0  (out_buff, "/a/") == 0) 
     595            mc_shell->type = SHELL_DASH; 
     596        else if (g_strcmp0  (out_buff, "\005""7a""\005""7") == 0) 
     597            mc_shell->type = SHELL_ASH_BUSYBOX_LEGACY; 
     598        else 
     599            mc_shell->type = SHELL_NONE; 
     600    } 
     601    else 
     602        mc_shell->type = SHELL_NONE; 
     603 
     604    g_free (out_buff); 
     605    g_free (standard_input); 
     606 
     607    /* if mc_shell_execute () failed */ 
     608    mc_shell_finalize_fd (&subshell_pipe[WRITE], NULL); 
     609    mc_shell_finalize_fd (&subshell_pipe[READ], NULL); 
     610} 
     611 
    219612/* --------------------------------------------------------------------------------------------- */ 
    220613/*** public functions ****************************************************************************/ 
    221614/* --------------------------------------------------------------------------------------------- */ 
    mc_shell_init (void) 
    232625 
    233626    mc_shell->real_path = mc_realpath (mc_shell->path, rp_shell); 
    234627 
    235     /* Find out what type of shell we have. Also consider real paths (resolved symlinks) 
    236      * because e.g. csh might point to tcsh, ash to dash or busybox, sh to anything. */ 
     628    /* Find out what type of shell we have. Use tests for specific variables that 
     629     * different shells set for most shell types. To recognize dash, or compatible 
     630     * BusyBox ash, we test whether prompt expansion works. */ 
    237631 
    238     if (mc_shell->real_path != NULL) 
    239         mc_shell_recognize_real_path (mc_shell); 
     632    if (mc_shell->real_path != NULL) { 
     633       mc_shell_recognize_from_internal_variable (mc_shell); 
    240634 
    241     if (mc_shell->type == SHELL_NONE) 
    242         mc_shell_recognize_path (mc_shell); 
     635       if (mc_shell->type == SHELL_NONE) 
     636           mc_shell_test_prompt_expansion (mc_shell); 
    243637 
    244     if (mc_shell->type == SHELL_NONE) 
    245         mc_global.tty.use_subshell = FALSE; 
     638       if (mc_shell->type == SHELL_NONE) 
     639           fprintf (stderr, __FILE__ ": failed to recognize shell \"%s\" as supported subshell. " 
     640                                       "Supported shells are: bash, zsh, tcsh, fish, dash and " 
     641                                       "BusyBox ash with enabled CONFIG_ASH_EXPAND_PRMT\r\n", 
     642                                        mc_shell->path); 
     643   } else { 
     644       mc_shell->type = SHELL_NONE; 
     645       fprintf (stderr, __FILE__ ": wrong \"%s\" shell: No such file\r\n", mc_shell->path); 
     646   } 
    246647 
     648    mc_global.tty.use_subshell = mc_shell->type != SHELL_NONE; 
    247649    mc_global.shell = mc_shell; 
    248650} 
    249651 
  • lib/shell.h

    diff --git a/lib/shell.h b/lib/shell.h
    index 9afcd90..1ec2495 100644
    a b  
    1212typedef enum 
    1313{ 
    1414    SHELL_NONE, 
    15     SHELL_SH, 
    1615    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 */ 
    1918    SHELL_TCSH, 
    2019    SHELL_ZSH, 
    2120    SHELL_FISH 
    2221} shell_type_t; 
    2322 
     23typedef enum 
     24{ 
     25    SHELL_SYNTAX_BOURNE, 
     26    SHELL_SYNTAX_C, 
     27    SHELL_SYNTAX_FISH 
     28} shell_syntax_t; 
     29 
    2430/*** structures declarations (and typedefs of structures)*****************************************/ 
    2531 
    2632typedef struct 
    2733{ 
    2834    shell_type_t type; 
    29     const char *name; 
    3035    char *path; 
    3136    char *real_path; 
    3237} mc_shell_t; 
    3338 
     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 */ 
     45typedef struct 
     46{ 
     47    int * poll_fds; 
     48    int * close_fds; 
     49    char *** buff; 
     50    int n; 
     51} pipes_data_t; 
     52 
    3453/*** global variables defined in .c file *********************************************************/ 
    3554 
    3655/*** declarations of public functions ************************************************************/ 
    void mc_shell_deinit (void); 
    4059 
    4160/*** inline functions **************************************************/ 
    4261 
     62static 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 
    4385#endif /* MC_SHELL_H */ 
  • src/subshell/common.c

    diff --git a/src/subshell/common.c b/src/subshell/common.c
    index 98968fa..b3cf8bc 100644
    a b init_subshell_child (const char *pty_name) 
    249249    tty_resize (subshell_pty_slave); 
    250250 
    251251    /* 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   */ 
    255252    { 
    256253        int ret; 
    257254 
    258         ret = chdir (mc_config_get_home_dir ());        /* FIXME? What about when we re-run the subshell? */ 
    259255        (void) ret; 
    260256    } 
    261257 
    init_subshell_child (const char *pty_name) 
    279275        if (!exist_file (init_file)) 
    280276        { 
    281277            g_free (init_file); 
    282             init_file = g_strdup (".bashrc"); 
     278            init_file = g_build_filename (g_getenv ("HOME"), ".bashrc", (char *) NULL); 
    283279        } 
    284280 
    285281        /* Make MC's special commands not show up in bash's history and also suppress 
    init_subshell_child (const char *pty_name) 
    301297 
    302298        break; 
    303299 
    304     case SHELL_ASH_BUSYBOX: 
     300    case SHELL_ASH_BUSYBOX_LEGACY: 
    305301    case SHELL_DASH: 
    306302        /* Do we have a custom init file ~/.local/share/mc/ashrc? */ 
    307303        init_file = mc_config_get_full_path ("ashrc"); 
    init_subshell_child (const char *pty_name) 
    310306        if (!exist_file (init_file)) 
    311307        { 
    312308            g_free (init_file); 
    313             init_file = g_strdup (".profile"); 
     309            init_file = g_build_filename (g_getenv ("HOME"), ".profile", (char *) NULL); 
    314310        } 
    315311 
    316312        /* Put init file to ENV variable used by ash */ 
    init_subshell_child (const char *pty_name) 
    327323        break; 
    328324 
    329325    default: 
    330         fprintf (stderr, __FILE__ ": unimplemented subshell type %d\r\n", mc_global.shell->type); 
     326        fprintf (stderr, __FILE__ ": unimplemented subshell type %s\r\n", shell_type_string (mc_global.shell->type)); 
    331327        my_exit (FORK_FAILURE); 
    332328    } 
    333329 
    init_subshell_child (const char *pty_name) 
    355351    switch (mc_global.shell->type) 
    356352    { 
    357353    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); 
    359355        break; 
    360356 
    361357    case SHELL_ZSH: 
    362358        /* Use -g to exclude cmds beginning with space from history 
    363359         * 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" */ 
    364362        execl (mc_global.shell->path, "zsh", "-Z", "-g", (char *) NULL); 
    365363 
    366364        break; 
    367365 
    368     case SHELL_ASH_BUSYBOX: 
     366    case SHELL_ASH_BUSYBOX_LEGACY: 
    369367    case SHELL_DASH: 
    370368    case SHELL_TCSH: 
    371369    case SHELL_FISH: 
    init_subshell_precmd (char *precmd, size_t buff_size) 
    801799                    "PS1='\\u@\\h:\\w\\$ '\n", subshell_pipe[WRITE]); 
    802800        break; 
    803801 
    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: 
    825803    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. 
    828805         * Attention! Make sure that the buffer for precmd is big enough. 
    829806         * 
    830          * We want to have a fancy dynamic prompt with user@host:cwd just like in the BusyBox 
    831          * examples above, but because replacing the home directory part of the path by "~" is 
     807         * 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 
    832809         * complicated, it bloats the precmd to a size > BUF_SMALL (128). 
    833810         * 
    834811         * The following example is a little less fancy (home directory not replaced) 
    subshell_name_quote (const char *s) 
    924901        quote_cmd_start = "(printf \"%b\" '"; 
    925902        quote_cmd_end = "')"; 
    926903    } 
    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) 
    930906       { 
    931907       quote_cmd_start = "\"`echo -en '"; 
    932908       quote_cmd_end = "'`\""; 
    933        } */ 
     909       } 
    934910    else 
    935911    { 
    936912        quote_cmd_start = "\"`printf \"%b\" '"; 
    init_subshell (void) 
    10591035                return; 
    10601036            } 
    10611037        } 
    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 */ 
    10631039        { 
    10641040            perror (__FILE__ ": couldn't create pipe"); 
    10651041            mc_global.tty.use_subshell = FALSE; 
    init_subshell (void) 
    11021078    tty_disable_interrupt_key (); 
    11031079    if (!subshell_alive) 
    11041080        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)); 
    11051083} 
    11061084 
    11071085/* --------------------------------------------------------------------------------------------- */