Ticket #3692: 3692-mc_subshell-V3.patch

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

    Author: alllexx88 <opotapenko@gmail.com>
    Date:   Mon, 21 Nov 2016 08:04:01 +0300
    
        More sophisticated shell type detection method
    
        * Make tests on shell binary instead of trying to guess shell type from
          path. Most supported shells set specific variables, hence by testing
          whether such variables are set, we can guess shell type in a more
          reliable way. This works with bash, zsh, tcsh and fish. For guessing
          dash or BusyBox ash (which are treated the same), we run a more peculiar
          test on whether shell supports expansion in PS1 as a prompt string. The
          latter test is also designed to differentiate a legacy pre 1.20 BusyBox
          ash, which allows to apply printf workaround in the case of such shell.
        * Remove chdir command from subshell initialization code, and use full
          paths for init_file instead. Changing dir only allows to use relative
          init_file paths, but can instead potentially lead to some problems, as
          previously noted in the comments; so by not doing this we add additional
          layer of protection against bugs.
        * Remove unneeded SHELL_SH shell type, and 'name' mc_shell_t field, since
          the latter was only being used as arg0 when initializing subshell, and
          all shells apparently initialize fine regardless of arg0 after being
          correctly detected, except for zsh, which has to be invoked as "zsh",
          probably because it behaves in a bourne compatible way otherwise
          (so we just leave arg0 as "zsh" for it)
        * Also add a little error verbosity in scope of detecting shell type and
          subshell initialization
    
    ---
     lib/shell.c           | 554 +++++++++++++++++++++++++++++++++++++++++++++-----
     lib/shell.h           |  50 ++++-
     src/subshell/common.c |  58 ++----
     3 files changed, 564 insertions(+), 98 deletions(-)
    
    diff --git a/lib/shell.c b/lib/shell.c
    index 6f07cb0..24f837c 100644
    a b  
    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) 
     153{ 
     154    char * str; 
     155 
     156    if ((dest == NULL) || (append == NULL) || (append[0] == '\0')) 
     157        return; 
     158 
     159    if (*dest == NULL) 
     160        str = g_strdup (append); 
     161    else 
     162        str = g_strdup_printf ("%s%s", *dest, append); 
     163 
     164    g_free (*dest); 
     165    *dest = str; 
     166} 
     167 
     168/* Close fd (we expect a pipe), appending all remaining data in case of input pipe to *dest. 
     169   If (dest == NULL), we still read all the data in an input pipe to close it properly. 
     170   Set the fd to -1 to indicate it's closed */ 
     171 
     172static void 
     173mc_shell_finalize_fd (int * fd, char ** dest) 
    144174{ 
    145     if (strstr (mc_shell->path, "/zsh") != NULL || strstr (mc_shell->real_path, "/zsh") != NULL 
    146         || getenv ("ZSH_VERSION") != NULL) 
     175    struct pollfd fdinfo; 
     176    int ret; 
     177    char buff[256]; 
     178    ssize_t size; 
     179 
     180    if ((fd == NULL) || (*fd < 0)) 
     181        return; 
     182 
     183    fdinfo.fd = *fd; 
     184    fdinfo.events = POLLIN; 
     185 
     186    ret = poll (&fdinfo, 1, 0); 
     187 
     188    if ((ret > 0) && ((fdinfo.revents & POLLIN) != 0)) 
     189        fcntl (*fd, F_SETFD, fcntl (*fd, F_GETFD) | O_NONBLOCK); 
     190 
     191    while ((ret > 0) && ((fdinfo.revents & POLLIN) != 0)) 
    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 (void * args) 
     212{ 
     213    int * argsint = (int*)(args), pid = argsint[0], * exit_status = &argsint[1]; 
     214 
     215    /* wait for child to terminate and get exit_status, 
     216       or set exit_status to '-1' on waitpid error */ 
     217    if (waitpid (pid, exit_status, 0) == -1) 
     218        *exit_status = -1; 
     219 
     220    /* this signales the parent thread that child terminated */ 
     221    argsint[0] = 0; 
     222 
     223    pthread_exit (NULL); 
     224} 
     225 
     226/* Wait for child process to terminate and read/write requested pipes in the process. 
     227   In case the child is running longer than the timeout value, terminate it with a 
     228   SIGKILL signal. Timeout is in milliseconds, 0 timeout means no timeout. 
     229   Returns child exit_status, or '-1' on non positive pid or waitpid () failure */ 
     230static int 
     231mc_shell_child_wait (int pid, guint timeout, pipes_data_t * pipes) 
     232{ 
     233    int args[2] = {pid}, i, err; 
     234    mc_timer_t * start = NULL; 
     235    pthread_t thread[1]; 
     236 
     237    if (timeout > 0) 
     238        start = mc_timer_new (); 
     239 
     240    if (pid <= 0) 
     241        return -1; 
     242 
     243    /* launch thread that waits for child process termination and gets the exit status */ 
     244    err = pthread_create(thread, NULL, mc_shell_waitpid_thread, args); 
     245 
     246    if (err != 0) /* failed to create thread */ 
    154247    { 
    155         /* Also detects csh symlinked to tcsh */ 
    156         mc_shell->type = SHELL_TCSH; 
    157         mc_shell->name = "tcsh"; 
     248        mc_timer_destroy (start); 
     249        return -1; 
    158250    } 
    159     else if (strstr (mc_shell->path, "/csh") != NULL 
    160              || strstr (mc_shell->real_path, "/csh") != NULL) 
     251 
     252    if ((pipes != NULL) && (pipes->n > 0)) 
    161253    { 
    162         mc_shell->type = SHELL_TCSH; 
    163         mc_shell->name = "csh"; 
     254        /* we have pipes to poll */ 
     255        int ret, n = pipes->n; 
     256        struct pollfd * fdinfo = g_new (struct pollfd, n); 
     257        ssize_t size; 
     258        char buff[256]; 
     259 
     260        /* prepare pollfd struct array for poll () and make all pipes non blocking */ 
     261        for (i = 0; i < n; i++) 
     262        { 
     263            fdinfo[i].fd = pipes->poll_fds[i]; 
     264            fdinfo[i].events = POLLIN | POLLOUT; 
     265            fcntl (fdinfo[i].fd, F_SETFD, fcntl (fdinfo[i].fd, F_GETFD) | O_NONBLOCK); 
     266        } 
     267 
     268        ret = poll (fdinfo, n, 0); 
     269 
     270        /* while child is running, read/write from/to all requested pipes */ 
     271        while (args[0] > 0) 
     272        { 
     273            if (mc_timer_elapsed (start) >= 1000 * timeout) 
     274            { 
     275                /* kill spawned child on timeout */ 
     276                kill (pid, SIGKILL); 
     277                break; 
     278            } else if (ret > 0) 
     279                for (i = n - 1; i >= 0; i--) 
     280                { 
     281                    if ((fdinfo[i].revents & POLLIN) != 0) 
     282                    { 
     283                        /* this is an input pipe, and it has data ready for reading */ 
     284                        size = read (fdinfo[i].fd, buff, 255); 
     285 
     286                        if ((pipes->buff[i] != NULL) && (size > 0)) 
     287                        { 
     288                            buff[size] = '\0'; 
     289                            mc_shell_append_string (pipes->buff[i], buff); 
     290                        } 
     291                    } else if ((fdinfo[i].revents & POLLOUT) != 0) 
     292                    { 
     293                        /* this is an output pipe, and it is ready for writing */ 
     294 
     295                        if ((pipes->buff[i] != NULL) && (*(pipes->buff[i]) != NULL)) 
     296                        { 
     297                            char * source = *(pipes->buff[i]); 
     298 
     299                            size = write (fdinfo[i].fd, source, strlen (source)); 
     300 
     301                            if (size > 0) 
     302                            { 
     303                                *(pipes->buff[i]) = g_strdup (&source[size]); 
     304                                g_free (source); 
     305                                source = *(pipes->buff[i]); 
     306                            } 
     307 
     308                            if (source[0] == '\0') 
     309                            { 
     310                                /* done writing: close the pipe, and remove from fdinfo */ 
     311                                close (fdinfo[i].fd); 
     312                                fdinfo[i].fd = pipes->poll_fds[i] = -1; 
     313                            } 
     314                        } else 
     315                        { 
     316                            /* nothing to write: close the pipe, and remove from fdinfo */ 
     317                            close (fdinfo[i].fd); 
     318                            fdinfo[i].fd = pipes->poll_fds[i] = -1; 
     319                        } 
     320                    } 
     321                } 
     322 
     323            ret = poll (fdinfo, n, 0); 
     324        } 
     325 
     326        g_free (fdinfo); 
     327    } else if (timeout > 0) 
     328        while (args[0] > 0) /* loop here until child terminates or command timeouts */ 
     329            if (mc_timer_elapsed (start) >= 1000 * timeout) 
     330            { 
     331                /* kill spawned child on timeout */ 
     332                kill (pid, SIGKILL); 
     333                break; 
     334            } 
     335 
     336    /* no need for the timer anymore */ 
     337    mc_timer_destroy (start); 
     338 
     339    pthread_join (thread [0], NULL); 
     340 
     341    /* read all the remaining data in the pipes and close them */ 
     342    if ((pipes != NULL) && (pipes->n > 0)) 
     343        for (i = 0; i < pipes->n; i++) 
     344            mc_shell_finalize_fd (&pipes->poll_fds[i], pipes->buff[i]); 
     345 
     346    return args[1]; 
     347} 
     348 
     349/* --------------------------------------------------------------------------------------------- 
     350   This function is similar to g_spawn_sync () function with some additional functionality: 
     351       * add optional timeout to SIGKILL the child after its expiration 
     352       * optionally feed a string to child's stdin 
     353       * optionally poll and read/write requested pipes during child execution 
     354   --------------------------------------------------------------------------------------------- */ 
     355 
     356static gboolean 
     357mc_shell_execute (const char * working_directory, 
     358                        char ** argv, 
     359                        char ** envp, 
     360                        GSpawnFlags flags, 
     361                        GSpawnChildSetupFunc child_setup, 
     362                        void * user_data, 
     363                        const char * feed_standard_input, 
     364                        char ** standard_output, 
     365                        char ** standard_error, 
     366                        guint timeout, /* timeout is in milliseconds, 0 timeout means no timeout */ 
     367                        pipes_data_t * pipes, 
     368                        int * exit_status, 
     369                        GError ** error) 
     370{ 
     371    int i, pid, stdin_fd, stdout_fd, stderr_fd, status, 
     372        * stdin_return = NULL, * stdout_return = NULL, * stderr_return = NULL; 
     373    char * standard_input = NULL; 
     374    pipes_data_t pipes_data; 
     375 
     376    pipes_data.n = 0; 
     377 
     378    if ((pipes != NULL) && (pipes->n > 0)) 
     379    { 
     380        pipes_data.n = pipes->n; 
     381 
     382        /* without this the requested pipes will be closed for the child */ 
     383        flags |= G_SPAWN_LEAVE_DESCRIPTORS_OPEN; 
     384 
     385        /* make parent sides of the pipes get closed for the child */ 
     386        for (i = 0; i < pipes->n; i++) 
     387            fcntl (pipes->poll_fds[i], F_SETFD, fcntl (pipes->poll_fds[i], F_GETFD) | FD_CLOEXEC); 
    164388    } 
    165     else if (strstr (mc_shell->path, "/fish") != NULL 
    166              || strstr (mc_shell->real_path, "/fish") != NULL) 
     389 
     390    /* we'll reap the child ourselves with waitpid () */ 
     391    flags |= G_SPAWN_DO_NOT_REAP_CHILD; 
     392 
     393    if (feed_standard_input != NULL) 
    167394    { 
    168         mc_shell->type = SHELL_FISH; 
    169         mc_shell->name = "fish"; 
     395        stdin_return = &stdin_fd; 
     396        pipes_data.n++; 
    170397    } 
    171     else if (strstr (mc_shell->path, "/dash") != NULL 
    172              || strstr (mc_shell->real_path, "/dash") != NULL) 
     398 
     399    if (standard_output != NULL) 
    173400    { 
    174         /* Debian ash (also found if symlinked to by ash/sh) */ 
    175         mc_shell->type = SHELL_DASH; 
    176         mc_shell->name = "dash"; 
     401        stdout_return = &stdout_fd; 
     402        pipes_data.n++; 
    177403    } 
    178     else if (strstr (mc_shell->real_path, "/busybox") != NULL) 
     404    else 
     405        flags |= G_SPAWN_STDOUT_TO_DEV_NULL; 
     406 
     407    if (standard_error != NULL) 
    179408    { 
    180         /* If shell is symlinked to busybox, assume it is an ash, even though theoretically 
    181          * it could also be a hush (a mini shell for non-MMU systems deactivated by default). 
    182          * For simplicity's sake we assume that busybox always contains an ash, not a hush. 
    183          * On embedded platforms or on server systems, /bin/sh often points to busybox. 
    184          * Sometimes even bash is symlinked to busybox (CONFIG_FEATURE_BASH_IS_ASH option), 
    185          * so we need to check busybox symlinks *before* checking for the name "bash" 
    186          * in order to avoid that case. */ 
    187         mc_shell->type = SHELL_ASH_BUSYBOX; 
    188         mc_shell->name = mc_shell->path; 
     409        stderr_return = &stderr_fd; 
     410        pipes_data.n++; 
    189411    } 
    190412    else 
    191         mc_shell->type = SHELL_NONE; 
     413        flags |= G_SPAWN_STDERR_TO_DEV_NULL; 
     414 
     415    if (!g_spawn_async_with_pipes (working_directory, argv, envp, flags, 
     416             child_setup, user_data, &pid, stdin_return, stdout_return, stderr_return, error)) 
     417    { 
     418        g_strfreev (envp); 
     419        return FALSE; 
     420    } 
     421 
     422    pipes_data.poll_fds = g_new(int, pipes_data.n); 
     423    pipes_data.buff = g_new(char**, pipes_data.n); 
     424    pipes_data.n = 0; 
     425 
     426    if ((pipes != NULL) && (pipes->n > 0)) 
     427    { 
     428        pipes_data.n = pipes->n; 
     429 
     430        for (i = 0; i < pipes->n; i++) 
     431        { 
     432            pipes_data.poll_fds[i] = pipes->poll_fds[i]; 
     433            pipes_data.buff[i] = pipes->buff[i]; 
     434            /* close child sides of pipes for the parent */ 
     435            mc_shell_finalize_fd (&pipes->close_fds[i], NULL); 
     436        } 
     437    } 
     438 
     439    if (standard_output != NULL) 
     440    { 
     441        pipes_data.poll_fds[pipes_data.n] = stdout_fd; 
     442        pipes_data.buff[pipes_data.n] = standard_output; 
     443        pipes_data.n++; 
     444    } 
     445 
     446    if (standard_error != NULL) 
     447    { 
     448        pipes_data.poll_fds[pipes_data.n] = stderr_fd; 
     449        pipes_data.buff[pipes_data.n] = standard_error; 
     450        pipes_data.n++; 
     451    } 
     452 
     453    if (feed_standard_input != NULL) 
     454    { 
     455        standard_input = g_strdup (feed_standard_input); 
     456        pipes_data.poll_fds[pipes_data.n] = stdin_fd; 
     457        pipes_data.buff[pipes_data.n] = &standard_input; 
     458        pipes_data.n++; 
     459    } 
     460 
     461    status = mc_shell_child_wait (pid, timeout, &pipes_data); 
     462 
     463    if (exit_status != NULL) 
     464        *exit_status = status; 
     465 
     466    if ((pipes != NULL) && (pipes->n > 0)) 
     467    { 
     468        /* the poll_fds are supposed to be closed now, and pipes_data.poll_fds[i] set to '-1', so let 
     469           the function caller know they're closed by setting pipes->poll_fds[i] to '-1' too */ 
     470        for (i = 0; i < pipes->n; i++) 
     471            pipes->poll_fds[i] = pipes_data.poll_fds[i]; 
     472    } 
     473 
     474    g_free (standard_input); 
     475    g_free (pipes_data.poll_fds); 
     476    g_free (pipes_data.buff); 
     477 
     478    return TRUE; 
     479} 
     480 
     481/* --------------------------------------------------------------------------------------------- 
     482   This function returns TRUE for a shell if it sets a variable with respective name. We unset 
     483   environmental variable of the same name in the child envp to make sure it's not inherited. 
     484   We use three different commands for the respective shell syntaxes: bourne, C and fish. 
     485   We test for exit code and stdout output to check if the command was successful. 
     486   If we test a shell with a wrong syntax, it returns error code, and function returns FALSE, 
     487   so in fact we test for syntax first, and only then for shell setting the variable. 
     488   --------------------------------------------------------------------------------------------- */ 
     489static gboolean 
     490mc_shell_internal_variable_set (mc_shell_t * mc_shell, const char * name, 
     491                                const shell_syntax_t shell_syntax) 
     492{ 
     493    /* define arg1 instead of using "-c" directly when assigning argv to 
     494       silence [-Wdiscarded-qualifiers] compiler warning */ 
     495    char arg1[] = "-c", * argv[] = {mc_shell->path, arg1, NULL, NULL}, * standard_output = NULL; 
     496    char ** envp; 
     497    int exit_status; 
     498    size_t i, shift, len1, len2; 
     499    gboolean success; 
     500 
     501    /* copy parent's envp and unset the environmental variable in question if set */ 
     502    envp = g_strdupv (environ); 
     503    len1 = g_strv_length (envp); 
     504    len2 = strlen (name); 
     505 
     506    for (i = shift = 0; i < len1; i++) 
     507    { 
     508        if ((strncmp (name, envp [i], len2) == 0) && (envp [i][len2] == '=')) 
     509        { 
     510            g_free (envp [i]); 
     511            shift++; 
     512        } else 
     513            envp [i - shift] = envp [i]; 
     514    } 
     515    for (i = len1 - shift; i < len1; i++) 
     516        envp [i] = NULL; 
     517 
     518    /* for proper shells these commands return 0 exit code and print 'name' value to stdout */ 
     519    if (shell_syntax == SHELL_SYNTAX_BOURNE) 
     520        argv[2] = g_strdup_printf ("if [ -z ${%s+x} ]; then " 
     521                                       "exit 1;" 
     522                                   "else " 
     523                                       "printf '%s';" 
     524                                   "fi", name, name); 
     525    else if (shell_syntax == SHELL_SYNTAX_C) 
     526        argv[2] = g_strdup_printf ("if !( $?%s ) then\n" 
     527                                       "exit 1\n" 
     528                                   "else\n" 
     529                                       "printf '%s'\n" 
     530                                   "endif", name, name); 
     531    else /* shell_syntax == SHELL_SYNTAX_FISH */ 
     532        argv[2] = g_strdup_printf ("if set -q %s;" 
     533                                       "printf '%s';" 
     534                                   "else;" 
     535                                       "exit 1;" 
     536                                   "end", name, name); 
     537 
     538 
     539    success = mc_shell_execute (NULL, argv, envp, 0, NULL, NULL, NULL, &standard_output, 
     540                                NULL, 100, NULL, &exit_status, NULL) && 
     541              g_spawn_check_exit_status (exit_status, NULL) && 
     542              (g_strcmp0  (standard_output, name) == 0); 
     543 
     544    g_free (argv[2]); 
     545    g_free (standard_output); 
     546 
     547    return success; 
    192548} 
    193549 
    194550/* --------------------------------------------------------------------------------------------- */ 
    195551 
    196552static void 
    197 mc_shell_recognize_path (mc_shell_t * mc_shell) 
     553mc_shell_recognize_from_internal_variable (mc_shell_t * mc_shell) 
    198554{ 
    199     /* If shell is not symlinked to busybox, it is safe to assume it is a real shell */ 
    200     if (strstr (mc_shell->path, "/bash") != NULL || getenv ("BASH") != NULL) 
    201     { 
     555    /* These tests recognize bash, zsh, tcsh and fish by testing for 
     556       variables that only these shells set */ 
     557    if (mc_shell_internal_variable_set (mc_shell, "BASH", SHELL_SYNTAX_BOURNE)) { 
    202558        mc_shell->type = SHELL_BASH; 
    203         mc_shell->name = "bash"; 
    204559    } 
    205     else if (strstr (mc_shell->path, "/sh") != NULL || getenv ("SH") != NULL) 
     560    else if (mc_shell_internal_variable_set (mc_shell, "ZSH_NAME", SHELL_SYNTAX_BOURNE)) 
     561    { 
     562        mc_shell->type = SHELL_ZSH; 
     563    } 
     564    else if (mc_shell_internal_variable_set (mc_shell, "tcsh", SHELL_SYNTAX_C)) 
    206565    { 
    207         mc_shell->type = SHELL_SH; 
    208         mc_shell->name = "sh"; 
     566        mc_shell->type = SHELL_TCSH; 
    209567    } 
    210     else if (strstr (mc_shell->path, "/ash") != NULL || getenv ("ASH") != NULL) 
     568    else if (mc_shell_internal_variable_set (mc_shell, "fish_greeting", SHELL_SYNTAX_FISH)) 
    211569    { 
    212         mc_shell->type = SHELL_ASH_BUSYBOX; 
    213         mc_shell->name = "ash"; 
     570        mc_shell->type = SHELL_FISH; 
    214571    } 
    215572    else 
    216573        mc_shell->type = SHELL_NONE; 
    217574} 
    218575 
     576/* --------------------------------------------------------------------------------------------- 
     577   This function tests whether a shell treats PS1 as prompt string that is being expanded. 
     578   We test for an old BusyBox ash 4-digit octal codes bug in printf along the way too. 
     579   mc_shell->type will be set to: 
     580      SHELL_DASH: Test for PS1 expansion succeeds fully. This can mean dash, or BusyBox ash 
     581                  with CONFIG_ASH_EXPAND_PRMT enabled, or something other compatible 
     582      SHELL_ASH_BUSYBOX_LEGACY: PS1 is being expanded, but printf suffers from the 4-digit octal 
     583                                codes bug, so apply the printf workaround 
     584      SHELL_NONE: Test failed. Possible reasons: PS1 is not being treated as a prompt string, 
     585                  PS1 is not being expanded (no CONFIG_ASH_EXPAND_PRMT in BusyBox ash?), 
     586                  shell doesn't recognize syntax, failed to execute shell, etc. 
     587   --------------------------------------------------------------------------------------------- */ 
     588static void 
     589mc_shell_test_prompt_expansion (mc_shell_t * mc_shell) 
     590{ 
     591    /* define arg1 instead of using "-i" directly when assigning argv to 
     592       silence [-Wdiscarded-qualifiers] compiler warning */ 
     593    char arg1[] = "-i", * argv[] = {mc_shell->path, arg1, NULL}, 
     594         ** envp, * standard_input; 
     595    int subshell_pipe[2], exit_status; 
     596    size_t i, len; 
     597 
     598    char * out_buff = NULL, ** out_buff_addr = &out_buff; 
     599    pipes_data_t pipes; 
     600 
     601    if (pipe (subshell_pipe)) 
     602    { 
     603        /* failed to create pipe */ 
     604        mc_shell->type = SHELL_NONE; 
     605        return; 
     606    } 
     607 
     608    /* copy parent's envp and override the PS1 env variable to safe non-interactive if set */ 
     609    envp = g_strdupv (environ); 
     610    len = g_strv_length (envp); 
     611 
     612    for (i = 0; i < len; i++) 
     613    { 
     614        if (strncmp ("PS1=", envp [i], 4) == 0) 
     615        { 
     616            g_free (envp [i]); 
     617            envp [i] = g_strdup ("PS1=$ "); 
     618        } 
     619    } 
     620 
     621    /* Check if executing `PS1='$(printf "%b" "\\0057a\\0057\\n" >&subshell_pipe[WRITE])'` command 
     622    in interactive mode gets PS1 evaluated before the next `exit 0` command, by capturing the pipe 
     623    output, and comparing it to the expected output for dash / BusyBox ash ("/a/"), and 
     624    if it doesn't match - to BusyBox pre 1.20 broken printf output ("\005""7a""\005""7") */ 
     625 
     626    standard_input = g_strdup_printf ("PS1='$(printf \"%%b\" \"\\\\0057a\\\\0057\" >&%d)'\n" 
     627                                      "exit 0\n", subshell_pipe[WRITE]); 
     628 
     629    pipes.buff      = &out_buff_addr;        /* pipes.buff is an array of pointers to strings */ 
     630    pipes.poll_fds  = &subshell_pipe[READ];  /* int array of pipes to poll */ 
     631    pipes.close_fds = &subshell_pipe[WRITE]; /* int array of pipes to close for the parent */ 
     632    pipes.n         = 1;                     /* pipes.n is the number of pipes */ 
     633 
     634    if (mc_shell_execute (NULL, argv, envp, 0, NULL, NULL, standard_input, NULL, 
     635                          NULL, 100, &pipes, &exit_status, NULL) && 
     636        g_spawn_check_exit_status (exit_status, NULL)) 
     637    { 
     638        if (g_strcmp0  (out_buff, "/a/") == 0) 
     639            mc_shell->type = SHELL_DASH; 
     640        else if (g_strcmp0  (out_buff, "\005""7a""\005""7") == 0) 
     641            mc_shell->type = SHELL_ASH_BUSYBOX_LEGACY; 
     642        else 
     643            mc_shell->type = SHELL_NONE; 
     644    } 
     645    else 
     646        mc_shell->type = SHELL_NONE; 
     647 
     648    g_free (out_buff); 
     649    g_free (standard_input); 
     650 
     651    /* if mc_shell_execute () failed */ 
     652    mc_shell_finalize_fd (&subshell_pipe[WRITE], NULL); 
     653    mc_shell_finalize_fd (&subshell_pipe[READ], NULL); 
     654} 
     655 
    219656/* --------------------------------------------------------------------------------------------- */ 
    220657/*** public functions ****************************************************************************/ 
    221658/* --------------------------------------------------------------------------------------------- */ 
    mc_shell_init (void) 
    232669 
    233670    mc_shell->real_path = mc_realpath (mc_shell->path, rp_shell); 
    234671 
    235     /* Find out what type of shell we have. Also consider real paths (resolved symlinks) 
    236      * because e.g. csh might point to tcsh, ash to dash or busybox, sh to anything. */ 
     672    /* Find out what type of shell we have. Use tests for specific variables that 
     673     * different shells set for most shell types. To recognize dash, or compatible 
     674     * BusyBox ash, we test whether prompt expansion works. */ 
    237675 
    238     if (mc_shell->real_path != NULL) 
    239         mc_shell_recognize_real_path (mc_shell); 
     676    if (mc_shell->real_path != NULL) { 
     677       mc_shell_recognize_from_internal_variable (mc_shell); 
    240678 
    241     if (mc_shell->type == SHELL_NONE) 
    242         mc_shell_recognize_path (mc_shell); 
     679       if (mc_shell->type == SHELL_NONE) 
     680           mc_shell_test_prompt_expansion (mc_shell); 
    243681 
    244     if (mc_shell->type == SHELL_NONE) 
    245         mc_global.tty.use_subshell = FALSE; 
     682       if (mc_shell->type == SHELL_NONE) 
     683           fprintf (stderr, __FILE__ ": failed to recognize shell \"%s\" as supported subshell. " 
     684                                       "Supported shells are: bash, zsh, tcsh, fish, dash and " 
     685                                       "BusyBox ash with enabled CONFIG_ASH_EXPAND_PRMT\r\n", 
     686                                        mc_shell->path); 
     687   } else { 
     688       mc_shell->type = SHELL_NONE; 
     689       fprintf (stderr, __FILE__ ": wrong \"%s\" shell: No such file\r\n", mc_shell->path); 
     690   } 
    246691 
     692    mc_global.tty.use_subshell = mc_shell->type != SHELL_NONE; 
    247693    mc_global.shell = mc_shell; 
    248694} 
    249695 
  • 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 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/* --------------------------------------------------------------------------------------------- */