Re: How to add subshell support for ash

Okay guys, I guess I have a working integration for ash subshell support
(tested with BusyBox ash/sh on mipsel) now. I am offering it to you
developers for inspection and adaption, so it may soon become a part of
the mainstream MC code base. I am aware of a few shortcomings and have
mentioned them as TODO items in code comments.

The first file 040-ash_as_subshell.patch contains the functional part of
the patch, while 040-ash_as_subshell_additional.patch contains some
minor additions to documentation and test code which are not necessary
to run mc + ash. Feel free and encouraged to criticise and improve the
code - as I said, I am not a C programmer and rather got my results by
continuous testing with strace as a debugging tool because I do not know
how to handle gdb.

What I have so far:

  - Ash mode will be used if the login shell is /bin/sh (on my
    platform it is safe to assume this, for general use this needs to
    be adapted to really recognise if /bin/sh is an ash or dash. I know
    how to do this via shell script, but not cleanly via C, sorry.

  - Ash as a subshell currently displays a fixed PS1 prompt like
    "user@host:/my/path/$ " ("user@host:/my/path/# " for root). It
    should be easy enough to change this, it just served my purpose
    like this. It is just a detail.

  - Chdir via subshell "cd" and via MC panel works as expected.

  - Analogous to Bash, it is possible to use an init file
    ~/.local/share/mc/ashrc (with auto-fallback to ~/.profile). This is
    not even implemented upstream for Zsh and Tcsh at the moment.

  - Plese refer to code comments for how and why I implemented the
    precmd via PS1 with two-fold indirection. This was necessary to
    enable the start of a sub-subshell (user command "ash" from
    subshell) without the sub-shell freezing (kill -STOP) or throwing
    an error. This is the trickiest part and I am somewhat proud I got
    it resolved with my rudimentary knowledge because it was a pain in
    the a**. Maybe there is a much simpler way.

  - I added some minimal documentation for Ash subshell mode to the
    help file, but only in English (needs translation), also extending
    Bash mode by mentioning ~/.bashrc (upstream just mentions

Remark: In order to get ENV into the environment for the init file, I
had to uncomment "g_free (putenv_str)". Initially I had used g_free
because Bash mode also uses it for INPUTRC. I wonder if the g_free also
needs to be removed in that case. Some MC professional might want to
check this.

I hope you are interested in this feature even though until now nobody
has answered my earlier inquiries. I want to thank Harald (ralda) from
the BusyxBox list though for sharing his thoughts and insights with me.
e.g. he gave me the important hint with (not to use) g_free.

Kind regards
Alexander Kriegisch

Alexander Kriegisch, 03.03.2012 18:39:
> Never mind, I am one step further:
>>     case ASH:
>>         /* ash does not support precmd, we need to read cwd from the prompt */
>>         g_snprintf (precmd, sizeof (precmd),
>>                     "PS1='$(pwd>&%d; kill -STOP $$)\\u@\\h:\\w\\$ '\n", subshell_pipe[WRITE]);
>>         break;
> This works for BusyBox ash (tested on mipsel platform). The command will
> be contained in the shell editing history, but for me this is acceptable.
> The next thing I want to do is set ENV so as to enable loading my shell
> profile without having to use a login shell. This would be the
> equivalent to Bash's "-rcfile" parameter. I did this:
>>     case ASH:
>>         /* Do we have a custom init file ~/.local/share/mc/ashrc? */
>>         init_file = mc_config_get_full_path ("ashrc");
>>         /* Otherwise use ~/.profile */
>>         if (access (init_file, R_OK) == -1)
>>         {
>>             g_free (init_file);
>>             init_file = g_strdup (".profile");
>>         }
>>         /* Put init file to ENV variable used by ash */
>>         char *putenv_str = g_strconcat ("ENV=", init_file, NULL);
>>         putenv (putenv_str);
>>         g_free (putenv_str);
>>         break;
> And later this:
>>     /* TODO for upstream patch: Execute correct ash/dash/busybox shell (not necessary for Freetz) */
>>     case ASH:
>>         execl (shell, "sh", (char *) NULL);
>>         break;
> The problem is that ENV seems to be unset in my subshell's environment,
> i.e. that ~ /.profile (whiche exists) is not evaluated. Do I need to
> somehow export the ENV variable to make it available to the subshell?
> BTW, I am compiling against uClibc 0.9.29, if this is relevant.
> Alexander Kriegisch, 03.03.2012 14:41:
>> I am on an embedded environment. Bash is available as a separate 
>> package, but huge in comparison with the built-in ash of BusyBox.
>> Disclaimer before I continue: I am *not* a C programmer. I can read
>> a bit of C, but not develop anything meaningful other than copy,
>> paste and modify existing code. So bear with me anyway, if you
>> please.
>> I looked into src/subshell.c and tried to figure out what happens
>> there. I have started to add ash support, but MC seems to depend on 
>> PROMPT_COMMAND/precmd/fish_prompt, i.e. on a function or alias which
>> is executed each time just before a prompt is printed. Ash does not
>> have anything like that, AFAIK. If I understand correctly, using
>> those pre-commands are MC's way of determining the cwd. I wonder why
>> you do not just use something like pwd or $PWD, but OTOH I do not
>> understand the difficulties of corresponding with the subshell.
>> Anyway, in my case it would be absolutely fine to set a fixed value
>> of PS1='\w\$ ' for the subshell and read the cwd from the prompt. In
>> any case it would be better than not having the option of using ash
>> as a subshell at all.
>> Could anyone maybe provide me with a code snippet or a patch which
>> shows me how to extract the cwd from the prompt assuming that PS1 has
>> the value '\w\$ '? (BTW, '\$' will be evaluated to '$' or '#' in
>> ash, depending on whether you are root or not.)
>> Thank you so much. If I succeed in adding ash as a subshell, I will 
>> provide a (raw) patch for you to inspect and maybe refine and
>> include upstream.
--- src/subshell.c	2012-03-02 13:55:52.018954847 +0100
+++ src/subshell.c	2012-03-04 05:10:53.750406898 +0100
@@ -126,6 +126,7 @@
 static enum
+    ASH,
@@ -269,8 +270,10 @@
     switch (subshell_type)
     case BASH:
+        /* Do we have a custom init file ~/.local/share/mc/bashrc? */
         init_file = mc_config_get_full_path ("bashrc");
+        /* Otherwise use ~/.bashrc */
         if (access (init_file, R_OK) == -1)
             g_free (init_file);
@@ -294,6 +297,24 @@
+    case ASH:
+        /* Do we have a custom init file ~/.local/share/mc/ashrc? */
+        init_file = mc_config_get_full_path ("ashrc");
+        /* Otherwise use ~/.profile */
+        if (access (init_file, R_OK) == -1)
+        {
+            g_free (init_file);
+            init_file = g_strdup (".profile");
+        }
+        /* Put init file to ENV variable used by ash */
+        char *putenv_str = g_strconcat ("ENV=", init_file, NULL);
+        putenv (putenv_str);
+        // Do not use "g_free (putenv_str)", otherwise ENV will be undefined!
+        break;
         /* TODO: Find a way to pass initfile to TCSH and ZSH */
     case TCSH:
     case ZSH:
@@ -332,6 +353,11 @@
         execl (shell, "bash", "-rcfile", init_file, (char *) NULL);
+    /* TODO for upstream patch: Execute correct ash/dash/busybox shell (not necessary for Freetz) */
+    case ASH:
+        execl (shell, "sh", (char *) NULL);
+        break;
     case TCSH:
         execl (shell, "tcsh", (char *) NULL);
@@ -796,6 +822,9 @@
             subshell_type = BASH;
         else if (strstr (shell, "/fish"))
             subshell_type = FISH;
+        /* TODO for upstream patch: Check if "sh" really points to ash/dash/busybox (not necessary for Freetz) */
+        else if (strstr (shell, "/ash") || strstr (shell, "/dash") || strstr (shell, "/sh"))
+            subshell_type = ASH;
             mc_global.tty.use_subshell = FALSE;
@@ -846,7 +875,7 @@
-        else /* subshell_type is BASH or ZSH */ if (pipe (subshell_pipe))
+        else /* subshell_type is BASH, ASH or ZSH */ if (pipe (subshell_pipe))
             perror (__FILE__ ": couldn't create pipe");
             mc_global.tty.use_subshell = FALSE;
@@ -883,6 +912,24 @@
                     " PROMPT_COMMAND='pwd>&%d;kill -STOP $$'\n", subshell_pipe[WRITE]);
+    case ASH:
+        /* Ash needs a somewhat complicated precmd emulation via PS1.
+           BUF_SMALL (defined in lib/global.h) is the length limit for precmd. */
+        g_snprintf (precmd, sizeof (precmd),
+                    // A: This leads to a stopped subshell (=frozen mc) if user calls "sh" command
+                    //"PS1='$(pwd>&%d; kill -STOP $$)\\\\u@\\\\h:\\\\w\\\\$ '\n",
+                    // B: This leads to "sh: precmd: not found" in sub-subshell if user calls "sh" command
+                    //"precmd(){ pwd>&%d;kill -STOP $$; }; PS1='$(precmd)\\\\u@\\\\h:\\\\w\\\\$ '\n",
+                    // C: This works if user calls "sh" command because in sub-subshell
+                    //    PRECMD is unfedined, thus evaluated to empty string - no damage done
+                    "precmd(){ pwd>&%d;kill -STOP $$; }; PRECMD=precmd; PS1='$(eval $PRECMD)\\\\u@\\\\h:\\\\w\\\\$ '\n",
+                    subshell_pipe[WRITE]);
+        break;
     case ZSH:
         g_snprintf (precmd, sizeof (precmd),
                     " precmd(){ pwd>&%d;kill -STOP $$ }\n", subshell_pipe[WRITE]);
@@ -1103,6 +1150,13 @@
         quote_cmd_start = "(printf \"%b\" '";
         quote_cmd_end = "')";
+    /* TODO: When BusyBox printf is fixed, get rid of this "else if", see
+ */
+    else if (subshell_type == ASH)
+    {
+        quote_cmd_start = "\"`echo -en '";
+        quote_cmd_end = "'`\"";
+    }
         quote_cmd_start = "\"`printf \"%b\" '";
--- lib/mcconfig/paths.c	2012-03-04 04:28:07.000000000 +0100
+++ lib/mcconfig/paths.c	2012-03-04 04:28:43.000000000 +0100
@@ -82,6 +82,7 @@
     /* data */
     { "skins",                                 &mc_data_str, MC_SKINS_SUBDIR},
     { "fish",                                  &mc_data_str, FISH_PREFIX},
+    { "ashrc",                                 &mc_data_str, "ashrc"},
     { "bashrc",                                &mc_data_str, "bashrc"},
     { "inputrc",                               &mc_data_str, "inputrc"},
     { "extfs.d",                               &mc_data_str, MC_EXTFS_DIR},
--- tests/lib/mcconfig/user_configs_path.c	2012-03-04 04:27:47.000000000 +0100
+++ tests/lib/mcconfig/user_configs_path.c	2012-03-04 05:33:48.418447747 +0100
@@ -96,6 +96,7 @@
     path_fail_unless (CONF_DATA, MC_SKINS_SUBDIR);
     path_fail_unless (CONF_DATA, FISH_PREFIX);
+    path_fail_unless (CONF_DATA, "ashrc");
     path_fail_unless (CONF_DATA, "bashrc");
     path_fail_unless (CONF_DATA, "inputrc");
     path_fail_unless (CONF_DATA, MC_EXTFS_DIR);
--- doc/man/	2012-03-04 05:18:35.970419532 +0100
+++ doc/man/	2012-03-04 05:35:58.262451703 +0100
@@ -2408,7 +2408,7 @@
 .\"NODE "  The subshell support"
 .SH "  The subshell support"
 The subshell support is a compile time option, that works with the
-shells: bash, tcsh and zsh.
+shells: bash, ash, tcsh and zsh.
 When the subshell code is activated the Midnight Commander will
 spawn a concurrent copy of your shell (the one defined in the
@@ -2423,8 +2423,10 @@
 If you are using
 .B bash
 you can specify startup
-commands for the subshell in your ~/.local/share/mc/bashrc file and
+commands for the subshell in your ~/.local/share/mc/bashrc file (fallback ~/.bashrc) and
 special keyboard maps in the ~/.local/share/mc/inputrc file.
+.B ash
+users may specify startup commands in ~/.local/share/mc/ashrc (fallback ~/.profile).
 .B tcsh
 users may specify startup commands in the ~/.local/share/mc/tcshrc file.

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]