From 7f383fbd6b4108ceafbc12c03fe3e09bd758bb7e Mon Sep 17 00:00:00 2001 From: Slava Zanko Date: Wed, 11 Nov 2015 15:43:11 +0300 Subject: [PATCH] Add mc_shell_init() and mc_shell_deinit() functions. Signed-off-by: Slava Zanko --- lib/Makefile.am | 2 +- lib/shell.c | 251 ++++++++++++++++++++++++++++++++++++++++++ lib/shell.h | 19 ++-- src/main.c | 75 +------------ src/subshell/common.c | 123 +++++---------------- 5 files changed, 297 insertions(+), 173 deletions(-) create mode 100644 lib/shell.c diff --git a/lib/Makefile.am b/lib/Makefile.am index 602fd5764..6beb06006 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -40,7 +40,7 @@ libmc_la_SOURCES = \ keybind.c keybind.h \ lock.c lock.h \ serialize.c serialize.h \ - shell.h \ + shell.c shell.h \ timefmt.c timefmt.h \ timer.c timer.h diff --git a/lib/shell.c b/lib/shell.c new file mode 100644 index 000000000..1227a515a --- /dev/null +++ b/lib/shell.c @@ -0,0 +1,251 @@ +/* + Provides a functions for working with shell. + + Copyright (C) 2006-2015 + Free Software Foundation, Inc. + + Written by: + Slava Zanko , 2015. + + This file is part of the Midnight Commander. + + The Midnight Commander is free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + The Midnight Commander is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +/** \file shell.c + * \brief Source: provides a functions for working with shell. + */ + +#include + +#include /* for username in xterm title */ +#include +#include +#include + +#include "global.h" +#include "util.h" + + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +static char rp_shell[PATH_MAX]; + +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ +/** + * Get a system shell. + * + * @return newly allocated string with shell name + */ + +static mc_shell_t * +mc_shell_get_installed_in_system (void) +{ + mc_shell_t *mc_shell; + + mc_shell = g_new0 (mc_shell_t, 1); + + /* 3rd choice: look for existing shells supported as MC subshells. */ + if (access ("/bin/bash", X_OK) == 0) + mc_shell->path = g_strdup ("/bin/bash"); + else if (access ("/bin/ash", X_OK) == 0) + mc_shell->path = g_strdup ("/bin/ash"); + else if (access ("/bin/dash", X_OK) == 0) + mc_shell->path = g_strdup ("/bin/dash"); + else if (access ("/bin/busybox", X_OK) == 0) + mc_shell->path = g_strdup ("/bin/busybox"); + else if (access ("/bin/zsh", X_OK) == 0) + mc_shell->path = g_strdup ("/bin/zsh"); + else if (access ("/bin/tcsh", X_OK) == 0) + mc_shell->path = g_strdup ("/bin/tcsh"); + /* No fish as fallback because it is so much different from other shells and + * in a way exotic (even though user-friendly by name) that we should not + * present it as a subshell without the user's explicit intention. We rather + * will not use a subshell but just a command line. + * else if (access("/bin/fish", X_OK) == 0) + * mc_global.tty.shell = g_strdup ("/bin/fish"); + */ + else + /* Fallback and last resort: system default shell */ + mc_shell->path = g_strdup ("/bin/sh"); + + return mc_shell; +} + +/* --------------------------------------------------------------------------------------------- */ + +static char * +mc_shell_get_name_env (void) +{ + const char *shell_env; + char *shell_name = NULL; + + shell_env = g_getenv ("SHELL"); + if ((shell_env == NULL) || (shell_env[0] == '\0')) + { + /* 2nd choice: user login shell */ + struct passwd *pwd; + + pwd = getpwuid (geteuid ()); + if (pwd != NULL) + shell_name = g_strdup (pwd->pw_shell); + } + else + /* 1st choice: SHELL environment variable */ + shell_name = g_strdup (shell_env); + + return shell_name; +} + +/* --------------------------------------------------------------------------------------------- */ + +static mc_shell_t * +mc_shell_get_from_env (void) +{ + mc_shell_t *mc_shell = NULL; + + char *shell_name; + + shell_name = mc_shell_get_name_env (); + + if (shell_name != NULL) + { + mc_shell = g_new0 (mc_shell_t, 1); + mc_shell->path = shell_name; + } + + return mc_shell; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Get a shell type and store in mc_shell->type variable + */ + +static gboolean +mc_shell_recognize_and_fill_type (mc_shell_t * mc_shell) +{ + gboolean result = TRUE; + + /* Find out what type of shell we have. Also consider real paths (resolved symlinks) + * because e.g. csh might point to tcsh, ash to dash or busybox, sh to anything. */ + + if (strstr (mc_shell->path, "/zsh") != NULL || strstr (mc_shell->real_path, "/zsh") != NULL + || getenv ("ZSH_VERSION") != NULL) + { + /* Also detects ksh symlinked to zsh */ + mc_shell->type = SHELL_ZSH; + mc_shell->name = "zsh"; + } + else if (strstr (mc_shell->path, "/tcsh") != NULL + || strstr (mc_shell->real_path, "/tcsh") != NULL) + { + /* Also detects csh symlinked to tcsh */ + mc_shell->type = SHELL_TCSH; + mc_shell->name = "tcsh"; + } + else if (strstr (mc_shell->path, "/fish") != NULL + || strstr (mc_shell->real_path, "/fish") != NULL) + { + mc_shell->type = SHELL_FISH; + mc_shell->name = "fish"; + } + else if (strstr (mc_shell->path, "/dash") != NULL + || strstr (mc_shell->real_path, "/dash") != NULL) + { + /* Debian ash (also found if symlinked to by ash/sh) */ + mc_shell->type = SHELL_DASH; + mc_shell->name = "dash"; + } + else if (strstr (mc_shell->real_path, "/busybox") != NULL) + { + /* If shell is symlinked to busybox, assume it is an ash, even though theoretically + * it could also be a hush (a mini shell for non-MMU systems deactivated by default). + * For simplicity's sake we assume that busybox always contains an ash, not a hush. + * On embedded platforms or on server systems, /bin/sh often points to busybox. + * Sometimes even bash is symlinked to busybox (CONFIG_FEATURE_BASH_IS_ASH option), + * so we need to check busybox symlinks *before* checking for the name "bash" + * in order to avoid that case. */ + mc_shell->type = SHELL_ASH_BUSYBOX; + mc_shell->name = mc_shell->path; + } + else if (strstr (mc_shell->path, "/bash") != NULL || getenv ("BASH") != NULL) + { + /* If bash is not symlinked to busybox, it is safe to assume it is a real bash */ + mc_shell->type = SHELL_BASH; + mc_shell->name = "bash"; + } + else if (strstr (mc_shell->path, "/sh") != NULL || getenv ("SH") != NULL) + { + /* If bash is not symlinked to busybox, it is safe to assume it is a real bash */ + mc_shell->type = SHELL_SH; + mc_shell->name = "sh"; + } + else if (strstr (mc_shell->path, "/ash") != NULL || getenv ("ASH") != NULL) + { + /* If bash is not symlinked to busybox, it is safe to assume it is a real bash */ + mc_shell->type = SHELL_ASH_BUSYBOX; + mc_shell->name = "ash"; + } + else + { + mc_shell->type = SHELL_NONE; + mc_global.tty.use_subshell = FALSE; + result = FALSE; + } + return result; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +void +mc_shell_init (void) +{ + mc_shell_t *mc_shell; + + mc_shell = mc_shell_get_from_env (); + + if (mc_shell == NULL) + mc_shell = mc_shell_get_installed_in_system (); + + mc_shell->real_path = mc_realpath (mc_shell->path, rp_shell); + + if (!mc_shell_recognize_and_fill_type (mc_shell)) + mc_global.tty.use_subshell = FALSE; + + mc_global.shell = mc_shell; +} + +/* --------------------------------------------------------------------------------------------- */ + +void +mc_shell_deinit (void) +{ + if (mc_global.shell != NULL) + { + g_free (mc_global.shell->path); + MC_PTR_FREE (mc_global.shell); + } +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/lib/shell.h b/lib/shell.h index 231f2eedb..9afcd900e 100644 --- a/lib/shell.h +++ b/lib/shell.h @@ -11,15 +11,16 @@ typedef enum { - BASH, - ASH_BUSYBOX, /* BusyBox default shell (ash) */ - DASH, /* Debian variant of ash */ - TCSH, - ZSH, - FISH + SHELL_NONE, + SHELL_SH, + SHELL_BASH, + SHELL_ASH_BUSYBOX, /* BusyBox default shell (ash) */ + SHELL_DASH, /* Debian variant of ash */ + SHELL_TCSH, + SHELL_ZSH, + SHELL_FISH } shell_type_t; - /*** structures declarations (and typedefs of structures)*****************************************/ typedef struct @@ -30,11 +31,13 @@ typedef struct char *real_path; } mc_shell_t; - /*** global variables defined in .c file *********************************************************/ /*** declarations of public functions ************************************************************/ +void mc_shell_init (void); +void mc_shell_deinit (void); + /*** inline functions **************************************************/ #endif /* MC_SHELL_H */ diff --git a/src/main.c b/src/main.c index 5cdbd8728..842c5d9a4 100644 --- a/src/main.c +++ b/src/main.c @@ -87,9 +87,6 @@ /*** file scope variables ************************************************************************/ /*** file scope functions ************************************************************************/ - -static char rp_shell[PATH_MAX]; - /* --------------------------------------------------------------------------------------------- */ static void @@ -121,75 +118,14 @@ check_codeset (void) } /* --------------------------------------------------------------------------------------------- */ -/** - * Get a system shell. - * - * @return newly allocated string with shell name - */ - -static char * -mc_get_system_shell (void) -{ - char *sh_str; - /* 3rd choice: look for existing shells supported as MC subshells. */ - if (access ("/bin/bash", X_OK) == 0) - sh_str = g_strdup ("/bin/bash"); - else if (access ("/bin/ash", X_OK) == 0) - sh_str = g_strdup ("/bin/ash"); - else if (access ("/bin/dash", X_OK) == 0) - sh_str = g_strdup ("/bin/dash"); - else if (access ("/bin/busybox", X_OK) == 0) - sh_str = g_strdup ("/bin/busybox"); - else if (access ("/bin/zsh", X_OK) == 0) - sh_str = g_strdup ("/bin/zsh"); - else if (access ("/bin/tcsh", X_OK) == 0) - sh_str = g_strdup ("/bin/tcsh"); - /* No fish as fallback because it is so much different from other shells and - * in a way exotic (even though user-friendly by name) that we should not - * present it as a subshell without the user's explicit intention. We rather - * will not use a subshell but just a command line. - * else if (access("/bin/fish", X_OK) == 0) - * mc_global.tty.shell = g_strdup ("/bin/fish"); - */ - else - /* Fallback and last resort: system default shell */ - sh_str = g_strdup ("/bin/sh"); - - return sh_str; -} - -/* --------------------------------------------------------------------------------------------- */ - /** POSIX version. The only version we support. */ + static void OS_Setup (void) { - const char *shell_env; const char *datadir_env; - - mc_global.shell = g_new0 (mc_shell_t, 1); - - shell_env = getenv ("SHELL"); - if ((shell_env == NULL) || (shell_env[0] == '\0')) - { - /* 2nd choice: user login shell */ - struct passwd *pwd; - - pwd = getpwuid (geteuid ()); - if (pwd != NULL) - mc_global.shell->path = g_strdup (pwd->pw_shell); - } - else - /* 1st choice: SHELL environment variable */ - mc_global.shell->path = g_strdup (shell_env); - - if ((mc_global.shell->path == NULL) || (mc_global.shell->path[0] == '\0')) - { - g_free (mc_global.shell->path); - mc_global.shell->path = mc_get_system_shell (); - } - mc_global.shell->real_path = mc_realpath (mc_global.shell->path, rp_shell); + mc_shell_init (); /* This is the directory, where MC was installed, on Unix this is DATADIR */ /* and can be overriden by the MC_DATADIR environment variable */ @@ -303,10 +239,8 @@ main (int argc, char *argv[]) startup_exit_falure: fprintf (stderr, _("Failed to run:\n%s\n"), mcerror->message); g_error_free (mcerror); - - g_free (mc_global.shell->path); - g_free (mc_global.shell); startup_exit_ok: + mc_shell_deinit (); str_uninit_strings (); mc_timer_destroy (mc_global.timer); return exit_code; @@ -512,8 +446,7 @@ main (int argc, char *argv[]) } g_free (last_wd_string); - g_free (mc_global.shell->path); - g_free (mc_global.shell); + mc_shell_deinit (); done_key (); diff --git a/src/subshell/common.c b/src/subshell/common.c index bc297af61..a234fc145 100644 --- a/src/subshell/common.c +++ b/src/subshell/common.c @@ -131,17 +131,6 @@ enum WRITE = 1 }; -/* Subshell type (gleaned from the SHELL environment variable, if available) */ -//static enum -//{ -// BASH, -// ASH_BUSYBOX, /* BusyBox default shell (ash) */ -// DASH, /* Debian variant of ash */ -// TCSH, -// ZSH, -// FISH -//} subshell_type; - /*** file scope variables ************************************************************************/ /* tcsh closes all non-standard file descriptors, so we have to use a pipe */ @@ -282,7 +271,7 @@ init_subshell_child (const char *pty_name) switch (mc_global.shell->type) { - case BASH: + case SHELL_BASH: /* Do we have a custom init file ~/.local/share/mc/bashrc? */ init_file = mc_config_get_full_path ("bashrc"); @@ -312,8 +301,8 @@ init_subshell_child (const char *pty_name) break; - case ASH_BUSYBOX: - case DASH: + case SHELL_ASH_BUSYBOX: + case SHELL_DASH: /* Do we have a custom init file ~/.local/share/mc/ashrc? */ init_file = mc_config_get_full_path ("ashrc"); @@ -332,9 +321,9 @@ init_subshell_child (const char *pty_name) break; /* TODO: Find a way to pass initfile to TCSH, ZSH and FISH */ - case TCSH: - case ZSH: - case FISH: + case SHELL_TCSH: + case SHELL_ZSH: + case SHELL_FISH: break; default: @@ -365,21 +354,21 @@ init_subshell_child (const char *pty_name) switch (mc_global.shell->type) { - case BASH: + case SHELL_BASH: execl (mc_global.shell->path, "bash", "-rcfile", init_file, (char *) NULL); break; - case ZSH: + case SHELL_ZSH: /* Use -g to exclude cmds beginning with space from history * and -Z to use the line editor on non-interactive term */ execl (mc_global.shell->path, "zsh", "-Z", "-g", (char *) NULL); break; - case ASH_BUSYBOX: - case DASH: - case TCSH: - case FISH: + case SHELL_ASH_BUSYBOX: + case SHELL_DASH: + case SHELL_TCSH: + case SHELL_FISH: execl (mc_global.shell->path, mc_global.shell->path, (char *) NULL); break; @@ -790,58 +779,6 @@ pty_open_slave (const char *pty_name) #endif /* !HAVE_GRANTPT */ -/* --------------------------------------------------------------------------------------------- */ -/** - * Get a subshell type and store in subshell_type variable - * - * @return TRUE if subtype was gotten, FALSE otherwise - */ - -static gboolean -init_subshell_type (void) -{ - gboolean result = TRUE; - - /* Find out what type of shell we have. Also consider real paths (resolved symlinks) - * because e.g. csh might point to tcsh, ash to dash or busybox, sh to anything. */ - - if (strstr (mc_global.shell->path, "/zsh") != NULL - || strstr (mc_global.shell->real_path, "/zsh") != NULL || getenv ("ZSH_VERSION") != NULL) - /* Also detects ksh symlinked to zsh */ - mc_global.shell->type = ZSH; - else if (strstr (mc_global.shell->path, "/tcsh") != NULL - || strstr (mc_global.shell->real_path, "/tcsh") != NULL) - /* Also detects csh symlinked to tcsh */ - mc_global.shell->type = TCSH; - else if (strstr (mc_global.shell->path, "/fish") != NULL - || strstr (mc_global.shell->real_path, "/fish") != NULL) - mc_global.shell->type = FISH; - else if (strstr (mc_global.shell->path, "/dash") != NULL - || strstr (mc_global.shell->real_path, "/dash") != NULL) - /* Debian ash (also found if symlinked to by ash/sh) */ - mc_global.shell->type = DASH; - else if (strstr (mc_global.shell->real_path, "/busybox") != NULL) - { - /* If shell is symlinked to busybox, assume it is an ash, even though theoretically - * it could also be a hush (a mini shell for non-MMU systems deactivated by default). - * For simplicity's sake we assume that busybox always contains an ash, not a hush. - * On embedded platforms or on server systems, /bin/sh often points to busybox. - * Sometimes even bash is symlinked to busybox (CONFIG_FEATURE_BASH_IS_ASH option), - * so we need to check busybox symlinks *before* checking for the name "bash" - * in order to avoid that case. */ - mc_global.shell->type = ASH_BUSYBOX; - } - else if (strstr (mc_global.shell->path, "/bash") != NULL || getenv ("BASH") != NULL) - /* If bash is not symlinked to busybox, it is safe to assume it is a real bash */ - mc_global.shell->type = BASH; - else - { - mc_global.tty.use_subshell = FALSE; - result = FALSE; - } - return result; -} - /* --------------------------------------------------------------------------------------------- */ /** * Set up `precmd' or equivalent for reading the subshell's CWD. @@ -858,12 +795,12 @@ init_subshell_precmd (char *precmd, size_t buff_size) { switch (mc_global.shell->type) { - case BASH: + case SHELL_BASH: g_snprintf (precmd, buff_size, " PROMPT_COMMAND='pwd>&%d; kill -STOP $$';\n", subshell_pipe[WRITE]); break; - case ASH_BUSYBOX: + case SHELL_ASH_BUSYBOX: /* BusyBox ash needs a somewhat complicated precmd emulation via PS1, and it is vital * that BB be built with active CONFIG_ASH_EXPAND_PRMT, but this is the default anyway. * @@ -884,7 +821,7 @@ init_subshell_precmd (char *precmd, size_t buff_size) * "PRECMD=precmd; " * "PS1='$(eval $PRECMD)\\u@\\h:\\w\\$ '\n", */ - case DASH: + case SHELL_DASH: /* Debian ash needs a precmd emulation via PS1, similar to BusyBox ash, * but does not support escape sequences for user, host and cwd in prompt. * Attention! Make sure that the buffer for precmd is big enough. @@ -917,20 +854,20 @@ init_subshell_precmd (char *precmd, size_t buff_size) "}; " "PRECMD=precmd; " "PS1='$($PRECMD)$ '\n", subshell_pipe[WRITE]); break; - case ZSH: + case SHELL_ZSH: g_snprintf (precmd, buff_size, " precmd() { pwd>&%d; kill -STOP $$; }; " "PS1='%%n@%%m:%%~%%# '\n", subshell_pipe[WRITE]); break; - case TCSH: + case SHELL_TCSH: g_snprintf (precmd, buff_size, "set echo_style=both; " "set prompt='%%n@%%m:%%~%%# '; " "alias precmd 'echo $cwd:q >>%s; kill -STOP $$'\n", tcsh_fifo); break; - case FISH: + case SHELL_FISH: /* We also want a fancy user@host:cwd prompt here, but fish makes it very easy to also * use colours, which is what we will do. But first here is a simpler, uncoloured version: * "function fish_prompt; " @@ -978,7 +915,7 @@ subshell_name_quote (const char *s) const char *su, *n; const char *quote_cmd_start, *quote_cmd_end; - if (mc_global.shell->type == FISH) + if (mc_global.shell->type == SHELL_FISH) { quote_cmd_start = "(printf \"%b\" '"; quote_cmd_end = "')"; @@ -1071,7 +1008,7 @@ init_subshell (void) if (mc_global.tty.subshell_pty == 0) { /* First time through */ - if (!init_subshell_type ()) + if (mc_global.shell->type == SHELL_NONE) return; /* Open a pty for talking to the subshell */ @@ -1096,7 +1033,7 @@ init_subshell (void) /* Create a pipe for receiving the subshell's CWD */ - if (mc_global.shell->type == TCSH) + if (mc_global.shell->type == SHELL_TCSH) { g_snprintf (tcsh_fifo, sizeof (tcsh_fifo), "%s/mc.pipe.%d", mc_tmpdir (), (int) getpid ()); @@ -1157,13 +1094,13 @@ init_subshell (void) switch (mc_global.shell->type) { - case BASH: + case SHELL_BASH: g_snprintf (precmd, sizeof (precmd), " PROMPT_COMMAND=${PROMPT_COMMAND:+$PROMPT_COMMAND\n}'pwd>&%d;kill -STOP $$'\n" "PS1='\\u@\\h:\\w\\$ '\n", subshell_pipe[WRITE]); break; - case ASH_BUSYBOX: + case SHELL_ASH_BUSYBOX: /* BusyBox ash needs a somewhat complicated precmd emulation via PS1, and it is vital * that BB be built with active CONFIG_ASH_EXPAND_PRMT, but this is the default anyway. * @@ -1184,7 +1121,7 @@ init_subshell (void) * "PRECMD=precmd; " * "PS1='$(eval $PRECMD)\\u@\\h:\\w\\$ '\n", */ - case DASH: + case SHELL_DASH: /* Debian ash needs a precmd emulation via PS1, similar to BusyBox ash, * but does not support escape sequences for user, host and cwd in prompt. * Attention! Make sure that the buffer for precmd is big enough. @@ -1217,20 +1154,20 @@ init_subshell (void) "}; " "PRECMD=precmd; " "PS1='$($PRECMD)$ '\n", subshell_pipe[WRITE]); break; - case ZSH: + case SHELL_ZSH: g_snprintf (precmd, sizeof (precmd), " _mc_precmd(){ pwd>&%d;kill -STOP $$ }; precmd_functions+=(_mc_precmd)\n" "PS1='%%n@%%m:%%~%%# '\n", subshell_pipe[WRITE]); break; - case TCSH: + case SHELL_TCSH: g_snprintf (precmd, sizeof (precmd), "set echo_style=both; " "set prompt='%%n@%%m:%%~%%# '; " "alias precmd 'echo $cwd:q >>%s; kill -STOP $$'\n", tcsh_fifo); break; - case FISH: + case SHELL_FISH: /* Use fish_prompt_mc function for prompt, if not present then copy fish_prompt to it. */ /* We also want a fancy user@host:cwd prompt here, but fish makes it very easy to also * use colours, which is what we will do. But first here is a simpler, uncoloured version: @@ -1414,7 +1351,7 @@ exit_subshell (void) if (subshell_quit) { - if (mc_global.shell->type == TCSH) + if (mc_global.shell->type == SHELL_TCSH) { if (unlink (tcsh_fifo) == -1) fprintf (stderr, "Cannot remove named pipe %s: %s\r\n", @@ -1488,7 +1425,7 @@ do_subshell_chdir (const vfs_path_t * vpath, gboolean update_prompt) bPathNotEq = strcmp (subshell_cwd, pcwd) != 0; - if (bPathNotEq && mc_global.shell->type == TCSH) + if (bPathNotEq && mc_global.shell->type == SHELL_TCSH) { char rp_subshell_cwd[PATH_MAX]; char rp_current_panel_cwd[PATH_MAX]; @@ -1517,7 +1454,7 @@ do_subshell_chdir (const vfs_path_t * vpath, gboolean update_prompt) } /* Really escape Zsh history */ - if (mc_global.shell->type == ZSH) + if (mc_global.shell->type == SHELL_ZSH) { /* Per Zsh documentation last command prefixed with space lingers in the internal history * until the next command is entered before it vanishes. To make it vanish right away,