Ticket #3692: 3692-mc_subshell-V5.patch

File 3692-mc_subshell-V5.patch, 33.3 KB (added by alllexx88, 7 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  
    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) 
    137146    return mc_shell; 
    138147} 
    139148 
    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 */ 
    141152 
    142153static void 
    143 mc_shell_recognize_real_path (mc_shell_t * mc_shell) 
     154mc_shell_finalize_fd (int * fd, GString * dest) 
    144155{ 
    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)) 
    147173    { 
    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); 
    151183    } 
    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 */ 
     195static int 
     196mc_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)) 
    154207    { 
    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 
     309static gboolean 
     310mc_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)) 
    161331    { 
    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); 
    164340    } 
    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) 
    167346    { 
    168         mc_shell->type = SHELL_FISH; 
    169         mc_shell->name = "fish"; 
     347        stdin_return = &stdin_fd; 
     348        pipes_data.n++; 
    170349    } 
    171     else if (strstr (mc_shell->path, "/dash") != NULL 
    172              || strstr (mc_shell->real_path, "/dash") != NULL) 
     350 
     351    if (standard_output != NULL) 
    173352    { 
    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++; 
    177355    } 
    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) 
    179360    { 
    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++; 
    189363    } 
    190364    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   --------------------------------------------------------------------------------------------- */ 
     457static gboolean 
     458mc_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; 
    192516} 
    193517 
    194518/* --------------------------------------------------------------------------------------------- */ 
    195519 
    196520static void 
    197 mc_shell_recognize_path (mc_shell_t * mc_shell) 
     521mc_shell_recognize_from_internal_variable (mc_shell_t * mc_shell) 
    198522{ 
    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)) { 
    202526        mc_shell->type = SHELL_BASH; 
    203         mc_shell->name = "bash"; 
    204527    } 
    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)) 
    206533    { 
    207         mc_shell->type = SHELL_SH; 
    208         mc_shell->name = "sh"; 
     534        mc_shell->type = SHELL_TCSH; 
    209535    } 
    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)) 
    211537    { 
    212         mc_shell->type = SHELL_ASH_BUSYBOX; 
    213         mc_shell->name = "ash"; 
     538        mc_shell->type = SHELL_FISH; 
    214539    } 
    215540    else 
    216541        mc_shell->type = SHELL_NONE; 
    217542} 
    218543 
     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   --------------------------------------------------------------------------------------------- */ 
     556static void 
     557mc_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 
    219624/* --------------------------------------------------------------------------------------------- */ 
    220625/*** public functions ****************************************************************************/ 
    221626/* --------------------------------------------------------------------------------------------- */ 
    mc_shell_init (void) 
    232637 
    233638    mc_shell->real_path = mc_realpath (mc_shell->path, rp_shell); 
    234639 
    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. */ 
    237643 
    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); 
    240646 
    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); 
    243649 
    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   } 
    246659 
     660    mc_global.tty.use_subshell = mc_shell->type != SHELL_NONE; 
    247661    mc_global.shell = mc_shell; 
    248662} 
    249663 
  • lib/shell.h

    diff --git a/lib/shell.h b/lib/shell.h
    index 9afcd90..039d5dd 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    GString ** 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 a1f054e..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 %u\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/* --------------------------------------------------------------------------------------------- */