3 @@ -97,6 +97,7 @@ mc_global_t mc_global = {
4 #endif /* !ENABLE_SUBSHELL */
7 + .shell_realpath = NULL,
13 @@ -255,6 +255,7 @@ typedef struct
15 /* The user's shell */
17 + char *shell_realpath;
19 /* This flag is set by xterm detection routine in function main() */
20 /* It is used by function view_other_cmd() */
21 --- a/lib/mcconfig/paths.c
22 +++ b/lib/mcconfig/paths.c
23 @@ -84,6 +84,7 @@ static const struct
25 { "skins", &mc_data_str, MC_SKINS_SUBDIR},
26 { "fish", &mc_data_str, FISH_PREFIX},
27 + { "ashrc", &mc_data_str, "ashrc"},
28 { "bashrc", &mc_data_str, "bashrc"},
29 { "inputrc", &mc_data_str, "inputrc"},
30 { "extfs.d", &mc_data_str, MC_EXTFS_DIR},
34 /*** file scope variables ************************************************************************/
36 /*** file scope functions ************************************************************************/
38 +static char rp_shell[PATH_MAX];
40 /* --------------------------------------------------------------------------------------------- */
43 @@ -118,6 +121,44 @@ check_codeset (void)
46 /* --------------------------------------------------------------------------------------------- */
48 + * Get a system shell.
50 + * @return newly allocated string with shell name
54 +mc_get_system_shell (void)
57 + /* 3rd choice: look for existing shells supported as MC subshells. */
58 + if (access ("/bin/bash", X_OK) == 0)
59 + sh_str = g_strdup ("/bin/bash");
60 + else if (access ("/bin/ash", X_OK) == 0)
61 + sh_str = g_strdup ("/bin/ash");
62 + else if (access ("/bin/dash", X_OK) == 0)
63 + sh_str = g_strdup ("/bin/dash");
64 + else if (access ("/bin/busybox", X_OK) == 0)
65 + sh_str = g_strdup ("/bin/busybox");
66 + else if (access ("/bin/zsh", X_OK) == 0)
67 + sh_str = g_strdup ("/bin/zsh");
68 + else if (access ("/bin/tcsh", X_OK) == 0)
69 + sh_str = g_strdup ("/bin/tcsh");
70 + /* No fish as fallback because it is so much different from other shells and
71 + * in a way exotic (even though user-friendly by name) that we should not
72 + * present it as a subshell without the user's explicit intention. We rather
73 + * will not use a subshell but just a command line.
74 + * else if (access("/bin/fish", X_OK) == 0)
75 + * mc_global.tty.shell = g_strdup ("/bin/fish");
78 + /* Fallback and last resort: system default shell */
79 + sh_str = g_strdup ("/bin/sh");
84 +/* --------------------------------------------------------------------------------------------- */
86 /** POSIX version. The only version we support. */
88 @@ -126,9 +167,11 @@ OS_Setup (void)
89 const char *shell_env;
90 const char *datadir_env;
93 shell_env = getenv ("SHELL");
94 if ((shell_env == NULL) || (shell_env[0] == '\0'))
96 + /* 2nd choice: user login shell */
99 pwd = getpwuid (geteuid ());
100 @@ -136,13 +179,15 @@ OS_Setup (void)
101 mc_global.tty.shell = g_strdup (pwd->pw_shell);
104 + /* 1st choice: SHELL environment variable */
105 mc_global.tty.shell = g_strdup (shell_env);
107 if ((mc_global.tty.shell == NULL) || (mc_global.tty.shell[0] == '\0'))
109 g_free (mc_global.tty.shell);
110 - mc_global.tty.shell = g_strdup ("/bin/sh");
111 + mc_global.tty.shell = mc_get_system_shell ();
113 + mc_global.tty.shell_realpath = mc_realpath (mc_global.tty.shell, rp_shell);
115 /* This is the directory, where MC was installed, on Unix this is DATADIR */
116 /* and can be overriden by the MC_DATADIR environment variable */
119 @@ -114,6 +114,8 @@ enum
123 + ASH_BUSYBOX, /* BusyBox default shell (ash) */
124 + DASH, /* Debian variant of ash */
128 @@ -209,6 +211,7 @@ static void
129 init_subshell_child (const char *pty_name)
131 char *init_file = NULL;
132 + char *putenv_str = NULL;
136 @@ -257,32 +260,53 @@ init_subshell_child (const char *pty_nam
137 switch (subshell_type)
140 + /* Do we have a custom init file ~/.local/share/mc/bashrc? */
141 init_file = mc_config_get_full_path ("bashrc");
143 + /* Otherwise use ~/.bashrc */
144 if (access (init_file, R_OK) == -1)
147 init_file = g_strdup (".bashrc");
150 - /* Make MC's special commands not show up in bash's history */
151 - putenv ((char *) "HISTCONTROL=ignorespace");
152 + /* Make MC's special commands not show up in bash's history and also suppress
153 + * consecutive identical commands*/
154 + putenv ((char *) "HISTCONTROL=ignoreboth");
156 /* Allow alternative readline settings for MC */
158 char *input_file = mc_config_get_full_path ("inputrc");
159 if (access (input_file, R_OK) == 0)
161 - char *putenv_str = g_strconcat ("INPUTRC=", input_file, NULL);
162 + putenv_str = g_strconcat ("INPUTRC=", input_file, NULL);
164 - g_free (putenv_str);
171 - /* TODO: Find a way to pass initfile to TCSH and ZSH */
174 + /* Do we have a custom init file ~/.local/share/mc/ashrc? */
175 + init_file = mc_config_get_full_path ("ashrc");
177 + /* Otherwise use ~/.profile */
178 + if (access (init_file, R_OK) == -1)
180 + g_free (init_file);
181 + init_file = g_strdup (".profile");
184 + /* Put init file to ENV variable used by ash */
185 + putenv_str = g_strconcat ("ENV=", init_file, NULL);
186 + putenv (putenv_str);
187 + /* Do not use "g_free (putenv_str)" here, otherwise ENV will be undefined! */
191 + /* TODO: Find a way to pass initfile to TCSH, ZSH and FISH */
195 @@ -320,10 +344,6 @@ init_subshell_child (const char *pty_nam
196 execl (mc_global.tty.shell, "bash", "-rcfile", init_file, (char *) NULL);
200 - execl (mc_global.tty.shell, "tcsh", (char *) NULL);
204 /* Use -g to exclude cmds beginning with space from history
205 * and -Z to use the line editor on non-interactive term */
206 @@ -331,8 +351,11 @@ init_subshell_child (const char *pty_nam
214 - execl (mc_global.tty.shell, "fish", (char *) NULL);
215 + execl (mc_global.tty.shell, mc_global.tty.shell, (char *) NULL);
219 @@ -341,6 +364,7 @@ init_subshell_child (const char *pty_nam
221 /* If we get this far, everything failed miserably */
223 + g_free (putenv_str);
224 my_exit (FORK_FAILURE);
227 @@ -742,6 +766,171 @@ pty_open_slave (const char *pty_name)
229 #endif /* !HAVE_GRANTPT */
232 +/* --------------------------------------------------------------------------------------------- */
234 + * Get a subshell type and store in subshell_type variable
236 + * @return TRUE if subtype was gotten, FALSE otherwise
240 +init_subshell_type (void)
242 + gboolean result = TRUE;
244 + /* Find out what type of shell we have. Also consider real paths (resolved symlinks)
245 + * because e.g. csh might point to tcsh, ash to dash or busybox, sh to anything. */
247 + if (strstr (mc_global.tty.shell, "/zsh") || strstr (mc_global.tty.shell_realpath, "/zsh")
248 + || getenv ("ZSH_VERSION"))
249 + /* Also detects ksh symlinked to zsh */
250 + subshell_type = ZSH;
251 + else if (strstr (mc_global.tty.shell, "/tcsh")
252 + || strstr (mc_global.tty.shell_realpath, "/tcsh"))
253 + /* Also detects csh symlinked to tcsh */
254 + subshell_type = TCSH;
255 + else if (strstr (mc_global.tty.shell, "/fish")
256 + || strstr (mc_global.tty.shell_realpath, "/fish"))
257 + subshell_type = FISH;
258 + else if (strstr (mc_global.tty.shell, "/dash")
259 + || strstr (mc_global.tty.shell_realpath, "/dash"))
260 + /* Debian ash (also found if symlinked to by ash/sh) */
261 + subshell_type = DASH;
262 + else if (strstr (mc_global.tty.shell_realpath, "/busybox"))
264 + /* If shell is symlinked to busybox, assume it is an ash, even though theoretically
265 + * it could also be a hush (a mini shell for non-MMU systems deactivated by default).
266 + * For simplicity's sake we assume that busybox always contains an ash, not a hush.
267 + * On embedded platforms or on server systems, /bin/sh often points to busybox.
268 + * Sometimes even bash is symlinked to busybox (CONFIG_FEATURE_BASH_IS_ASH option),
269 + * so we need to check busybox symlinks *before* checking for the name "bash"
270 + * in order to avoid that case. */
271 + subshell_type = ASH_BUSYBOX;
273 + else if (strstr (mc_global.tty.shell, "/bash") || getenv ("BASH"))
274 + /* If bash is not symlinked to busybox, it is safe to assume it is a real bash */
275 + subshell_type = BASH;
278 + mc_global.tty.use_subshell = FALSE;
284 +/* --------------------------------------------------------------------------------------------- */
286 + * Set up `precmd' or equivalent for reading the subshell's CWD.
288 + * Attention! Never forget that these are *one-liners* even though the concatenated
289 + * substrings contain line breaks and indentation for better understanding of the
290 + * shell code. It is vital that each one-liner ends with a line feed character ("\n" ).
292 + * @return initialized pre-command string
296 +init_subshell_precmd (char *precmd, size_t buff_size)
299 + switch (subshell_type)
302 + g_snprintf (precmd, buff_size,
303 + " PROMPT_COMMAND='pwd>&%d; kill -STOP $$';\n", subshell_pipe[WRITE]);
307 + /* BusyBox ash needs a somewhat complicated precmd emulation via PS1, and it is vital
308 + * that BB be built with active CONFIG_ASH_EXPAND_PRMT, but this is the default anyway.
310 + * A: This leads to a stopped subshell (=frozen mc) if user calls "ash" command
311 + * "PS1='$(pwd>&%d; kill -STOP $$)\\u@\\h:\\w\\$ '\n",
313 + * B: This leads to "sh: precmd: not found" in sub-subshell if user calls "ash" command
314 + * "precmd() { pwd>&%d; kill -STOP $$; }; "
315 + * "PS1='$(precmd)\\u@\\h:\\w\\$ '\n",
317 + * C: This works if user calls "ash" command because in sub-subshell
318 + * PRECMD is unfedined, thus evaluated to empty string - no damage done.
319 + * Attention: BusyBox must be built with FEATURE_EDITING_FANCY_PROMPT to
320 + * permit \u, \w, \h, \$ escape sequences. Unfortunately this cannot be guaranteed,
321 + * especially on embedded systems where people try to save space, so let's use
322 + * the dash version below. It should work on virtually all systems.
323 + * "precmd() { pwd>&%d; kill -STOP $$; }; "
324 + * "PRECMD=precmd; "
325 + * "PS1='$(eval $PRECMD)\\u@\\h:\\w\\$ '\n",
328 + /* Debian ash needs a precmd emulation via PS1, similar to BusyBox ash,
329 + * but does not support escape sequences for user, host and cwd in prompt.
330 + * Attention! Make sure that the buffer for precmd is big enough.
332 + * We want to have a fancy dynamic prompt with user@host:cwd just like in the BusyBox
333 + * examples above, but because replacing the home directory part of the path by "~" is
334 + * complicated, it bloats the precmd to a size > BUF_SMALL (128).
336 + * The following example is a little less fancy (home directory not replaced)
337 + * and shows the basic workings of our prompt for easier understanding:
340 + * "echo \"$USER@$(hostname -s):$PWD\"; "
342 + * "kill -STOP $$; "
344 + * "PRECMD=precmd; "
345 + * "PS1='$($PRECMD)$ '\n",
347 + g_snprintf (precmd, buff_size,
349 + "if [ ! \"${PWD##$HOME}\" ]; then "
352 + "[ \"${PWD##$HOME/}\" = \"$PWD\" ] && MC_PWD=\"$PWD\" || MC_PWD=\"~/${PWD##$HOME/}\"; "
354 + "echo \"$USER@openwrt:$MC_PWD\"; "
357 + "}; " "PRECMD=precmd; " "PS1='$($PRECMD)$ '\n", subshell_pipe[WRITE]);
361 + g_snprintf (precmd, buff_size,
362 + " precmd() { pwd>&%d; kill -STOP $$; }; "
363 + "PS1='%%n@%%m:%%~%%# '\n", subshell_pipe[WRITE]);
367 + g_snprintf (precmd, buff_size,
368 + "set echo_style=both; "
369 + "set prompt='%%n@%%m:%%~%%# '; "
370 + "alias precmd 'echo $cwd:q >>%s; kill -STOP $$'\n", tcsh_fifo);
374 + /* We also want a fancy user@host:cwd prompt here, but fish makes it very easy to also
375 + * use colours, which is what we will do. But first here is a simpler, uncoloured version:
376 + * "function fish_prompt; "
377 + * "echo (whoami)@(hostname -s):(pwd)\\$\\ ; "
378 + * "echo \"$PWD\">&%d; "
379 + * "kill -STOP %%self; "
382 + * TODO: fish prompt is shown when panel is hidden (Ctrl-O), but not when it is visible.
383 + * Find out how to fix this.
385 + g_snprintf (precmd, buff_size,
386 + "function fish_prompt; "
387 + "echo (whoami)@(hostname -s):(set_color $fish_color_cwd)(pwd)(set_color normal)\\$\\ ; "
388 + "echo \"$PWD\">&%d; " "kill -STOP %%self; " "end\n", subshell_pipe[WRITE]);
396 /* --------------------------------------------------------------------------------------------- */
397 /*** public functions ****************************************************************************/
398 /* --------------------------------------------------------------------------------------------- */
399 @@ -761,6 +950,7 @@ init_subshell (void)
401 /* This must be remembered across calls to init_subshell() */
402 static char pty_name[BUF_SMALL];
403 + /* Must be considerably longer than BUF_SMALL (128) to support fancy shell prompts */
404 char precmd[BUF_MEDIUM];
406 switch (check_sid ())
407 @@ -782,23 +972,8 @@ init_subshell (void)
409 if (mc_global.tty.subshell_pty == 0)
410 { /* First time through */
411 - /* Find out what type of shell we have */
413 - if (strstr (mc_global.tty.shell, "/zsh") || getenv ("ZSH_VERSION"))
414 - subshell_type = ZSH;
415 - else if (strstr (mc_global.tty.shell, "/tcsh"))
416 - subshell_type = TCSH;
417 - else if (strstr (mc_global.tty.shell, "/csh"))
418 - subshell_type = TCSH;
419 - else if (strstr (mc_global.tty.shell, "/bash") || getenv ("BASH"))
420 - subshell_type = BASH;
421 - else if (strstr (mc_global.tty.shell, "/fish"))
422 - subshell_type = FISH;
425 - mc_global.tty.use_subshell = FALSE;
426 + if (!init_subshell_type ())
430 /* Open a pty for talking to the subshell */
432 @@ -844,7 +1019,7 @@ init_subshell (void)
436 - else /* subshell_type is BASH or ZSH */ if (pipe (subshell_pipe))
437 + else if (pipe (subshell_pipe)) /* subshell_type is BASH, ASH_BUSYBOX, DASH or ZSH */
439 perror (__FILE__ ": couldn't create pipe");
440 mc_global.tty.use_subshell = FALSE;
441 @@ -872,39 +1047,116 @@ init_subshell (void)
442 init_subshell_child (pty_name);
445 - /* Set up 'precmd' or equivalent for reading the subshell's CWD */
446 + init_subshell_precmd (precmd, BUF_MEDIUM);
448 + /* Set up `precmd' or equivalent for reading the subshell's CWD
450 + * Attention! Never forget that these are *one-liners* even though the concatenated
451 + * substrings contain line breaks and indentation for better understanding of the
452 + * shell code. It is vital that each one-liner ends with a line feed character ("\n" ).
455 switch (subshell_type)
458 g_snprintf (precmd, sizeof (precmd),
459 - " PROMPT_COMMAND=${PROMPT_COMMAND:+$PROMPT_COMMAND\n}'pwd>&%d;kill -STOP $$'\n",
460 - subshell_pipe[WRITE]);
461 + " PROMPT_COMMAND=${PROMPT_COMMAND:+$PROMPT_COMMAND\n}'pwd>&%d;kill -STOP $$'\n"
462 + "PS1='\\u@\\h:\\w\\$ '\n", subshell_pipe[WRITE]);
466 + /* BusyBox ash needs a somewhat complicated precmd emulation via PS1, and it is vital
467 + * that BB be built with active CONFIG_ASH_EXPAND_PRMT, but this is the default anyway.
469 + * A: This leads to a stopped subshell (=frozen mc) if user calls "ash" command
470 + * "PS1='$(pwd>&%d; kill -STOP $$)\\u@\\h:\\w\\$ '\n",
472 + * B: This leads to "sh: precmd: not found" in sub-subshell if user calls "ash" command
473 + * "precmd() { pwd>&%d; kill -STOP $$; }; "
474 + * "PS1='$(precmd)\\u@\\h:\\w\\$ '\n",
476 + * C: This works if user calls "ash" command because in sub-subshell
477 + * PRECMD is unfedined, thus evaluated to empty string - no damage done.
478 + * Attention: BusyBox must be built with FEATURE_EDITING_FANCY_PROMPT to
479 + * permit \u, \w, \h, \$ escape sequences. Unfortunately this cannot be guaranteed,
480 + * especially on embedded systems where people try to save space, so let's use
481 + * the dash version below. It should work on virtually all systems.
482 + * "precmd() { pwd>&%d; kill -STOP $$; }; "
483 + * "PRECMD=precmd; "
484 + * "PS1='$(eval $PRECMD)\\u@\\h:\\w\\$ '\n",
487 + /* Debian ash needs a precmd emulation via PS1, similar to BusyBox ash,
488 + * but does not support escape sequences for user, host and cwd in prompt.
489 + * Attention! Make sure that the buffer for precmd is big enough.
491 + * We want to have a fancy dynamic prompt with user@host:cwd just like in the BusyBox
492 + * examples above, but because replacing the home directory part of the path by "~" is
493 + * complicated, it bloats the precmd to a size > BUF_SMALL (128).
495 + * The following example is a little less fancy (home directory not replaced)
496 + * and shows the basic workings of our prompt for easier understanding:
499 + * "echo \"$USER@$(hostname -s):$PWD\"; "
501 + * "kill -STOP $$; "
503 + * "PRECMD=precmd; "
504 + * "PS1='$($PRECMD)$ '\n",
506 + g_snprintf (precmd, sizeof (precmd),
508 + "if [ ! \"${PWD##$HOME}\" ]; then "
511 + "[ \"${PWD##$HOME/}\" = \"$PWD\" ] && MC_PWD=\"$PWD\" || MC_PWD=\"~/${PWD##$HOME/}\"; "
513 + "echo \"$USER@openwrt:$MC_PWD\"; "
516 + "}; " "PRECMD=precmd; " "PS1='$($PRECMD)$ '\n", subshell_pipe[WRITE]);
520 g_snprintf (precmd, sizeof (precmd),
521 - " _mc_precmd(){ pwd>&%d;kill -STOP $$ }; precmd_functions+=(_mc_precmd)\n",
522 - subshell_pipe[WRITE]);
523 + " _mc_precmd(){ pwd>&%d;kill -STOP $$ }; precmd_functions+=(_mc_precmd)\n"
524 + "PS1='%%n@%%m:%%~%%# '\n", subshell_pipe[WRITE]);
528 g_snprintf (precmd, sizeof (precmd),
529 - "set echo_style=both;"
530 - "alias precmd 'echo $cwd:q >>%s;kill -STOP $$'\n", tcsh_fifo);
531 + "set echo_style=both; "
532 + "set prompt='%%n@%%m:%%~%%# '; "
533 + "alias precmd 'echo $cwd:q >>%s; kill -STOP $$'\n", tcsh_fifo);
537 /* Use fish_prompt_mc function for prompt, if not present then copy fish_prompt to it. */
538 + /* We also want a fancy user@host:cwd prompt here, but fish makes it very easy to also
539 + * use colours, which is what we will do. But first here is a simpler, uncoloured version:
540 + * "function fish_prompt; "
541 + * "echo (whoami)@(hostname -s):(pwd)\\$\\ ; "
542 + * "echo \"$PWD\">&%d; "
543 + * "kill -STOP %%self; "
546 + * TODO: fish prompt is shown when panel is hidden (Ctrl-O), but not when it is visible.
547 + * Find out how to fix this.
549 g_snprintf (precmd, sizeof (precmd),
550 "if not functions -q fish_prompt_mc;"
551 "functions -c fish_prompt fish_prompt_mc; end;"
552 - "function fish_prompt; echo $PWD>&%d; fish_prompt_mc; kill -STOP %%self; end\n",
553 + "function fish_prompt;"
554 + "echo (whoami)@(hostname -s):(set_color $fish_color_cwd)(pwd)(set_color normal)\\$\\ ; "
555 + "echo \"$PWD\">&%d; fish_prompt_mc; kill -STOP %%self; end\n",
556 subshell_pipe[WRITE]);
563 write_all (mc_global.tty.subshell_pty, precmd, strlen (precmd));
565 /* Wait until the subshell has started up and processed the command */
566 @@ -1108,6 +1360,13 @@ subshell_name_quote (const char *s)
567 quote_cmd_start = "(printf \"%b\" '";
568 quote_cmd_end = "')";
570 + /* TODO: When BusyBox printf is fixed, get rid of this "else if", see
571 + http://lists.busybox.net/pipermail/busybox/2012-March/077460.html */
572 + /* else if (subshell_type == ASH_BUSYBOX)
574 + quote_cmd_start = "\"`echo -en '";
575 + quote_cmd_end = "'`\"";
579 quote_cmd_start = "\"`printf \"%b\" '";