Manual pages: mcmcdiffmceditmcview

root/lib/shell.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. mc_shell_get_installed_in_system
  2. mc_shell_get_name_env
  3. mc_shell_get_from_env
  4. mc_shell_recognize_real_path
  5. mc_shell_recognize_path
  6. mc_shell_init
  7. mc_shell_deinit

   1 /*
   2    Provides a functions for working with shell.
   3 
   4    Copyright (C) 2006-2025
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Slava Zanko <slavazanko@gmail.com>, 2015.
   9 
  10    This file is part of the Midnight Commander.
  11 
  12    The Midnight Commander is free software: you can redistribute it
  13    and/or modify it under the terms of the GNU General Public License as
  14    published by the Free Software Foundation, either version 3 of the License,
  15    or (at your option) any later version.
  16 
  17    The Midnight Commander is distributed in the hope that it will be useful,
  18    but WITHOUT ANY WARRANTY; without even the implied warranty of
  19    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20    GNU General Public License for more details.
  21 
  22    You should have received a copy of the GNU General Public License
  23    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  24  */
  25 
  26 /** \file shell.c
  27  *  \brief Source: provides a functions for working with shell.
  28  */
  29 
  30 #include <config.h>
  31 
  32 #include <pwd.h>  // for username in xterm title
  33 #include <stdarg.h>
  34 #include <stdio.h>
  35 #include <stdlib.h>
  36 
  37 #include "global.h"
  38 #include "util.h"
  39 
  40 /*** global variables ****************************************************************************/
  41 
  42 /*** file scope macro definitions ****************************************************************/
  43 
  44 /*** file scope type declarations ****************************************************************/
  45 
  46 /*** forward declarations (file scope functions) *************************************************/
  47 
  48 /*** file scope variables ************************************************************************/
  49 
  50 static char rp_shell[PATH_MAX];
  51 
  52 /* --------------------------------------------------------------------------------------------- */
  53 /*** file scope functions ************************************************************************/
  54 /* --------------------------------------------------------------------------------------------- */
  55 /**
  56  * Get a system shell.
  57  *
  58  * @return newly allocated mc_shell_t object with shell name
  59  */
  60 
  61 static mc_shell_t *
  62 mc_shell_get_installed_in_system (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
  63 {
  64     mc_shell_t *mc_shell;
  65 
  66     mc_shell = g_new0 (mc_shell_t, 1);
  67 
  68     // 3rd choice: look for existing shells supported as MC subshells.
  69     if (access ("/bin/bash", X_OK) == 0)
  70         mc_shell->path = g_strdup ("/bin/bash");
  71     else if (access ("/bin/zsh", X_OK) == 0)
  72         mc_shell->path = g_strdup ("/bin/zsh");
  73     else if (access ("/bin/oksh", X_OK) == 0)
  74         mc_shell->path = g_strdup ("/bin/oksh");
  75     else if (access ("/bin/ksh", X_OK) == 0)
  76         mc_shell->path = g_strdup ("/bin/ksh");
  77     else if (access ("/bin/ksh93", X_OK) == 0)
  78         mc_shell->path = g_strdup ("/bin/ksh93");
  79     else if (access ("/bin/ash", X_OK) == 0)
  80         mc_shell->path = g_strdup ("/bin/ash");
  81     else if (access ("/bin/dash", X_OK) == 0)
  82         mc_shell->path = g_strdup ("/bin/dash");
  83     else if (access ("/bin/busybox", X_OK) == 0)
  84         mc_shell->path = g_strdup ("/bin/busybox");
  85     else if (access ("/bin/tcsh", X_OK) == 0)
  86         mc_shell->path = g_strdup ("/bin/tcsh");
  87     else if (access ("/bin/csh", X_OK) == 0)
  88         mc_shell->path = g_strdup ("/bin/csh");
  89     else if (access ("/bin/mksh", X_OK) == 0)
  90         mc_shell->path = g_strdup ("/bin/mksh");
  91     else if (access ("/bin/lksh", X_OK) == 0)
  92         mc_shell->path = g_strdup ("/bin/lksh");
  93     /* No fish as fallback because it is so much different from other shells and
  94      * in a way exotic (even though user-friendly by name) that we should not
  95      * present it as a subshell without the user's explicit intention. We rather
  96      * will not use a subshell but just a command line.
  97      * else if (access("/bin/fish", X_OK) == 0)
  98      *     mc_global.tty.shell = g_strdup ("/bin/fish");
  99      */
 100     else
 101         // Fallback and last resort: system default shell
 102         mc_shell->path = g_strdup ("/bin/sh");
 103 
 104     return mc_shell;
 105 }
 106 
 107 /* --------------------------------------------------------------------------------------------- */
 108 
 109 static char *
 110 mc_shell_get_name_env (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 111 {
 112     char *shell_name = NULL;
 113 
 114     const char *shell_env = g_getenv ("SHELL");
 115     if ((shell_env == NULL) || (shell_env[0] == '\0'))
 116     {
 117         // 2nd choice: user login shell
 118         const struct passwd *pwd = getpwuid (geteuid ());
 119         if (pwd != NULL)
 120             shell_name = g_strdup (pwd->pw_shell);
 121     }
 122     else
 123         // 1st choice: SHELL environment variable
 124         shell_name = g_strdup (shell_env);
 125 
 126     return shell_name;
 127 }
 128 
 129 /* --------------------------------------------------------------------------------------------- */
 130 
 131 static mc_shell_t *
 132 mc_shell_get_from_env (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 133 {
 134     char *shell_name = mc_shell_get_name_env ();
 135 
 136     if (shell_name != NULL)
 137     {
 138         mc_shell_t *mc_shell = NULL;
 139         mc_shell = g_new0 (mc_shell_t, 1);
 140         mc_shell->path = shell_name;
 141         return mc_shell;
 142     }
 143 
 144     return NULL;
 145 }
 146 
 147 /* --------------------------------------------------------------------------------------------- */
 148 
 149 static void
 150 mc_shell_recognize_real_path (mc_shell_t *mc_shell)
     /* [previous][next][first][last][top][bottom][index][help]  */
 151 {
 152     if (strstr (mc_shell->path, "/zsh") != NULL || strstr (mc_shell->real_path, "/zsh") != NULL
 153         || getenv ("ZSH_VERSION") != NULL)
 154     {
 155         // Also detects ksh symlinked to zsh
 156         mc_shell->type = SHELL_ZSH;
 157         mc_shell->name = "zsh";
 158     }
 159     else if (strstr (mc_shell->path, "/tcsh") != NULL
 160              || strstr (mc_shell->real_path, "/tcsh") != NULL)
 161     {
 162         // Also detects csh symlinked to tcsh
 163         mc_shell->type = SHELL_TCSH;
 164         mc_shell->name = "tcsh";
 165     }
 166     else if (strstr (mc_shell->path, "/csh") != NULL
 167              || strstr (mc_shell->real_path, "/csh") != NULL)
 168     {
 169         mc_shell->type = SHELL_TCSH;
 170         mc_shell->name = "csh";
 171     }
 172     else if (strstr (mc_shell->path, "/fish") != NULL
 173              || strstr (mc_shell->real_path, "/fish") != NULL)
 174     {
 175         mc_shell->type = SHELL_FISH;
 176         mc_shell->name = "fish";
 177     }
 178     else if (strstr (mc_shell->path, "/dash") != NULL
 179              || strstr (mc_shell->real_path, "/dash") != NULL)
 180     {
 181         // Debian ash (also found if symlinked to by ash/sh)
 182         mc_shell->type = SHELL_DASH;
 183         mc_shell->name = "dash";
 184     }
 185     else if (strstr (mc_shell->real_path, "/busybox") != NULL)
 186     {
 187         /* If shell is symlinked to busybox, assume it is an ash, even though theoretically
 188          * it could also be a hush (a mini shell for non-MMU systems deactivated by default).
 189          * For simplicity's sake we assume that busybox always contains an ash, not a hush.
 190          * On embedded platforms or on server systems, /bin/sh often points to busybox.
 191          * Sometimes even bash is symlinked to busybox (CONFIG_FEATURE_BASH_IS_ASH option),
 192          * so we need to check busybox symlinks *before* checking for the name "bash"
 193          * in order to avoid that case. */
 194         mc_shell->type = SHELL_ASH_BUSYBOX;
 195         mc_shell->name = mc_shell->path;
 196     }
 197     else if (strstr (mc_shell->path, "/ksh") != NULL || strstr (mc_shell->real_path, "/ksh") != NULL
 198              || strstr (mc_shell->path, "/ksh93") != NULL
 199              || strstr (mc_shell->real_path, "/ksh93") != NULL
 200              || strstr (mc_shell->path, "/oksh") != NULL
 201              || strstr (mc_shell->real_path, "/oksh") != NULL)
 202     {
 203         mc_shell->type = SHELL_KSH;
 204         mc_shell->name = "ksh";
 205     }
 206     else if (strstr (mc_shell->path, "/mksh") != NULL
 207              || strstr (mc_shell->real_path, "/mksh") != NULL
 208              || strstr (mc_shell->path, "/lksh") != NULL
 209              || strstr (mc_shell->real_path, "/lksh") != NULL)
 210     {
 211         /* lksh is present on many systems (all Debian derivatives, plus others) and is an mksh
 212          * binary built with POSIX semantics where they cannot be made a run-time option (such as
 213          * the width of the arithmetic data type); see #4634 */
 214         mc_shell->type = SHELL_MKSH;
 215         mc_shell->name = "mksh";
 216     }
 217     else
 218         mc_shell->type = SHELL_NONE;
 219 }
 220 
 221 /* --------------------------------------------------------------------------------------------- */
 222 
 223 static void
 224 mc_shell_recognize_path (mc_shell_t *mc_shell)
     /* [previous][next][first][last][top][bottom][index][help]  */
 225 {
 226     // If shell is not symlinked to busybox, it is safe to assume it is a real shell
 227     if (strstr (mc_shell->path, "/bash") != NULL || getenv ("BASH_VERSION") != NULL)
 228     {
 229         mc_shell->type = SHELL_BASH;
 230         mc_shell->name = "bash";
 231     }
 232     else if (strstr (mc_shell->path, "/sh") != NULL)
 233     {
 234         mc_shell->type = SHELL_SH;
 235         mc_shell->name = "sh";
 236     }
 237     else if (strstr (mc_shell->path, "/ash") != NULL || getenv ("BB_ASH_VERSION") != NULL)
 238     {
 239         mc_shell->type = SHELL_ASH_BUSYBOX;
 240         mc_shell->name = "ash";
 241     }
 242     else if (strstr (mc_shell->path, "/ksh") != NULL || strstr (mc_shell->path, "/ksh93") != NULL
 243              || strstr (mc_shell->path, "/oksh") != NULL
 244              || (getenv ("KSH_VERSION") != NULL
 245                  && strstr (getenv ("KSH_VERSION"), "PD KSH") != NULL))
 246     {
 247         mc_shell->type = SHELL_KSH;
 248         mc_shell->name = "ksh";
 249     }
 250     else if (strstr (mc_shell->path, "/mksh") != NULL || strstr (mc_shell->path, "/lksh") != NULL
 251              || (getenv ("KSH_VERSION") != NULL
 252                  && strstr (getenv ("KSH_VERSION"), "MIRBSD KSH") != NULL))
 253     {
 254         mc_shell->type = SHELL_MKSH;
 255         mc_shell->name = "mksh";
 256     }
 257     else
 258         mc_shell->type = SHELL_NONE;
 259 }
 260 
 261 /* --------------------------------------------------------------------------------------------- */
 262 /*** public functions ****************************************************************************/
 263 /* --------------------------------------------------------------------------------------------- */
 264 
 265 void
 266 mc_shell_init (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 267 {
 268     mc_shell_t *mc_shell = mc_shell_get_from_env ();
 269 
 270     if (mc_shell == NULL)
 271         mc_shell = mc_shell_get_installed_in_system ();
 272 
 273     mc_shell->real_path = mc_realpath (mc_shell->path, rp_shell);
 274 
 275     /* Find out what type of shell we have. Also consider real paths (resolved symlinks)
 276      * because e.g. csh might point to tcsh, ash to dash or busybox, sh to anything. */
 277     if (mc_shell->real_path != NULL)
 278         mc_shell_recognize_real_path (mc_shell);
 279 
 280     if (mc_shell->type == SHELL_NONE)
 281         mc_shell_recognize_path (mc_shell);
 282 
 283     if (mc_shell->type == SHELL_NONE)
 284         mc_global.tty.use_subshell = FALSE;
 285 
 286     mc_global.shell = mc_shell;
 287 }
 288 
 289 /* --------------------------------------------------------------------------------------------- */
 290 
 291 void
 292 mc_shell_deinit (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 293 {
 294     if (mc_global.shell != NULL)
 295     {
 296         g_free (mc_global.shell->path);
 297         MC_PTR_FREE (mc_global.shell);
 298     }
 299 }
 300 
 301 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */