root/src/subshell/common.c

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

DEFINITIONS

This source file includes following definitions.
  1. write_all
  2. init_subshell_child
  3. init_raw_mode
  4. synchronize
  5. feed_subshell
  6. pty_open_master
  7. pty_open_slave
  8. pty_open_master
  9. pty_open_slave
  10. init_subshell_precmd
  11. subshell_name_quote
  12. init_subshell
  13. invoke_subshell
  14. read_subshell_prompt
  15. do_update_prompt
  16. exit_subshell
  17. do_subshell_chdir
  18. subshell_get_console_attributes
  19. sigchld_handler

   1 /*
   2    Concurrent shell support for the Midnight Commander
   3 
   4    Copyright (C) 1994-2020
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Alexander Kriegisch <Alexander@Kriegisch.name>
   9    Aliaksey Kandratsenka <alk@tut.by>
  10    Andreas Mohr <and@gmx.li>
  11    Andrew Borodin <aborodin@vmail.ru>
  12    Andrew Borodin <borodin@borodin.zarya>
  13    Andrew V. Samoilov <sav@bcs.zp.ua>
  14    Chris Owen <chris@candu.co.uk>
  15    Claes Nästén <me@pekdon.net>
  16    Egmont Koblinger <egmont@gmail.com>
  17    Enrico Weigelt, metux IT service <weigelt@metux.de>
  18    Igor Urazov <z0rc3r@gmail.com>
  19    Ilia Maslakov <il.smind@gmail.com>
  20    Leonard den Ottolander <leonard@den.ottolander.nl>
  21    Miguel de Icaza <miguel@novell.com>
  22    Mikhail S. Pobolovets <styx.mp@gmail.com>
  23    Norbert Warmuth <nwarmuth@privat.circular.de>
  24    Patrick Winnertz <winnie@debian.org>
  25    Pavel Machek <pavel@suse.cz>
  26    Pavel Roskin <proski@gnu.org>
  27    Pavel Tsekov <ptsekov@gmx.net>
  28    Roland Illig <roland.illig@gmx.de>
  29    Sergei Trofimovich <slyfox@inbox.ru>
  30    Slava Zanko <slavazanko@gmail.com>, 2013,2015.
  31    Timur Bakeyev <mc@bat.ru>
  32    Vit Rosin <vit_r@list.ru>
  33 
  34    This file is part of the Midnight Commander.
  35 
  36    The Midnight Commander is free software: you can redistribute it
  37    and/or modify it under the terms of the GNU General Public License as
  38    published by the Free Software Foundation, either version 3 of the License,
  39    or (at your option) any later version.
  40 
  41    The Midnight Commander is distributed in the hope that it will be useful,
  42    but WITHOUT ANY WARRANTY; without even the implied warranty of
  43    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  44    GNU General Public License for more details.
  45 
  46    You should have received a copy of the GNU General Public License
  47    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  48  */
  49 
  50 /** \file subshell.c
  51  *  \brief Source: concurrent shell support
  52  */
  53 
  54 #include <config.h>
  55 
  56 #ifndef _GNU_SOURCE
  57 #define _GNU_SOURCE 1
  58 #endif
  59 
  60 #include <ctype.h>
  61 #include <stdio.h>
  62 #include <stdlib.h>
  63 #include <errno.h>
  64 #include <string.h>
  65 #include <signal.h>
  66 #ifdef HAVE_SYS_SELECT_H
  67 #include <sys/select.h>
  68 #else
  69 #include <sys/time.h>
  70 #include <unistd.h>
  71 #endif
  72 #include <sys/types.h>
  73 #include <sys/wait.h>
  74 #ifdef HAVE_SYS_IOCTL_H
  75 #include <sys/ioctl.h>
  76 #endif
  77 #include <termios.h>
  78 
  79 #ifdef HAVE_STROPTS_H
  80 #include <stropts.h>            /* For I_PUSH */
  81 #endif /* HAVE_STROPTS_H */
  82 
  83 #ifdef HAVE_OPENPTY
  84 /* includes for openpty() */
  85 #ifdef HAVE_PTY_H
  86 #include <pty.h>
  87 #endif
  88 #ifdef HAVE_UTIL_H
  89 #include <util.h>
  90 #endif
  91 /* <sys/types.h> is a prerequisite of <libutil.h> on FreeBSD 8.0.  */
  92 #ifdef HAVE_LIBUTIL_H
  93 #include <libutil.h>
  94 #endif
  95 #endif /* HAVE_OPENPTY */
  96 
  97 #include "lib/global.h"
  98 
  99 #include "lib/unixcompat.h"
 100 #include "lib/tty/tty.h"        /* LINES */
 101 #include "lib/tty/key.h"        /* XCTRL */
 102 #include "lib/vfs/vfs.h"
 103 #include "lib/strutil.h"
 104 #include "lib/mcconfig.h"
 105 #include "lib/util.h"
 106 #include "lib/widget.h"
 107 
 108 #include "subshell.h"
 109 #include "internal.h"
 110 
 111 /*** global variables ****************************************************************************/
 112 
 113 /* State of the subshell:
 114  * INACTIVE: the default state; awaiting a command
 115  * ACTIVE: remain in the shell until the user hits 'subshell_switch_key'
 116  * RUNNING_COMMAND: return to MC when the current command finishes */
 117 enum subshell_state_enum subshell_state;
 118 
 119 /* Holds the latest prompt captured from the subshell */
 120 GString *subshell_prompt = NULL;
 121 
 122 /* Subshell: if set, then the prompt was not saved on CONSOLE_SAVE */
 123 /* We need to paint it after CONSOLE_RESTORE, see: load_prompt */
 124 gboolean update_subshell_prompt = FALSE;
 125 
 126 /*** file scope macro definitions ****************************************************************/
 127 
 128 #ifndef WEXITSTATUS
 129 #define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
 130 #endif
 131 
 132 #ifndef WIFEXITED
 133 #define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
 134 #endif
 135 
 136 /* Initial length of the buffer for the subshell's prompt */
 137 #define INITIAL_PROMPT_SIZE 10
 138 
 139 /* Used by the child process to indicate failure to start the subshell */
 140 #define FORK_FAILURE 69         /* Arbitrary */
 141 
 142 /* Length of the buffer for all I/O with the subshell */
 143 #define PTY_BUFFER_SIZE BUF_SMALL       /* Arbitrary; but keep it >= 80 */
 144 
 145 /*** file scope type declarations ****************************************************************/
 146 
 147 /* For pipes */
 148 enum
 149 {
 150     READ = 0,
 151     WRITE = 1
 152 };
 153 
 154 /*** file scope variables ************************************************************************/
 155 
 156 /* tcsh closes all non-standard file descriptors, so we have to use a pipe */
 157 static char tcsh_fifo[128];
 158 
 159 static int subshell_pty_slave = -1;
 160 
 161 /* The key for switching back to MC from the subshell */
 162 /* *INDENT-OFF* */
 163 static const char subshell_switch_key = XCTRL ('o') & 255;
 164 /* *INDENT-ON* */
 165 
 166 /* For reading/writing on the subshell's pty */
 167 static char pty_buffer[PTY_BUFFER_SIZE] = "\0";
 168 
 169 /* To pass CWD info from the subshell to MC */
 170 static int subshell_pipe[2];
 171 
 172 /* The subshell's process ID */
 173 static pid_t subshell_pid = 1;
 174 
 175 /* One extra char for final '\n' */
 176 static char subshell_cwd[MC_MAXPATHLEN + 1];
 177 
 178 /* Flag to indicate whether the subshell is ready for next command */
 179 static int subshell_ready;
 180 
 181 /* The following two flags can be changed by the SIGCHLD handler. This is */
 182 /* OK, because the 'int' type is updated atomically on all known machines */
 183 static volatile int subshell_alive, subshell_stopped;
 184 
 185 /* We store the terminal's initial mode here so that we can configure
 186    the pty similarly, and also so we can restore the real terminal to
 187    sanity if we have to exit abruptly */
 188 static struct termios shell_mode;
 189 
 190 /* This is a transparent mode for the terminal where MC is running on */
 191 /* It is used when the shell is active, so that the control signals */
 192 /* are delivered to the shell pty */
 193 static struct termios raw_mode;
 194 
 195 /* --------------------------------------------------------------------------------------------- */
 196 /*** file scope functions ************************************************************************/
 197 /* --------------------------------------------------------------------------------------------- */
 198 /**
 199  *  Write all data, even if the write() call is interrupted.
 200  */
 201 
 202 static ssize_t
 203 write_all (int fd, const void *buf, size_t count)
     /* [previous][next][first][last][top][bottom][index][help]  */
 204 {
 205     ssize_t written = 0;
 206 
 207     while (count > 0)
 208     {
 209         ssize_t ret;
 210 
 211         ret = write (fd, (const unsigned char *) buf + written, count);
 212         if (ret < 0)
 213         {
 214             if (errno == EINTR)
 215             {
 216                 if (tty_got_winch ())
 217                     tty_change_screen_size ();
 218 
 219                 continue;
 220             }
 221 
 222             return written > 0 ? written : ret;
 223         }
 224         count -= ret;
 225         written += ret;
 226     }
 227     return written;
 228 }
 229 
 230 /* --------------------------------------------------------------------------------------------- */
 231 /**
 232  *  Prepare child process to running the shell and run it.
 233  *
 234  *  Modifies the global variables (in the child process only):
 235  *      shell_mode
 236  *
 237  *  Returns: never.
 238  */
 239 
 240 static void
 241 init_subshell_child (const char *pty_name)
     /* [previous][next][first][last][top][bottom][index][help]  */
 242 {
 243     char *init_file = NULL;
 244     char *putenv_str = NULL;
 245     pid_t mc_sid;
 246 
 247     (void) pty_name;
 248     setsid ();                  /* Get a fresh terminal session */
 249 
 250     /* Make sure that it has become our controlling terminal */
 251 
 252     /* Redundant on Linux and probably most systems, but just in case: */
 253 
 254 #ifdef TIOCSCTTY
 255     ioctl (subshell_pty_slave, TIOCSCTTY, 0);
 256 #endif
 257 
 258     /* Configure its terminal modes and window size */
 259 
 260     /* Set up the pty with the same termios flags as our own tty */
 261     if (tcsetattr (subshell_pty_slave, TCSANOW, &shell_mode))
 262     {
 263         fprintf (stderr, "Cannot set pty terminal modes: %s\r\n", unix_error_string (errno));
 264         my_exit (FORK_FAILURE);
 265     }
 266 
 267     /* Set the pty's size (80x25 by default on Linux) according to the */
 268     /* size of the real terminal as calculated by ncurses, if possible */
 269     tty_resize (subshell_pty_slave);
 270 
 271     /* Set up the subshell's environment and init file name */
 272 
 273     /* It simplifies things to change to our home directory here, */
 274     /* and the user's startup file may do a 'cd' command anyway   */
 275     {
 276         int ret;
 277 
 278         ret = chdir (mc_config_get_home_dir ());        /* FIXME? What about when we re-run the subshell? */
 279         (void) ret;
 280     }
 281 
 282     /* Set MC_SID to prevent running one mc from another */
 283     mc_sid = getsid (0);
 284     if (mc_sid != -1)
 285     {
 286         char sid_str[BUF_SMALL];
 287 
 288         g_snprintf (sid_str, sizeof (sid_str), "MC_SID=%ld", (long) mc_sid);
 289         putenv (g_strdup (sid_str));
 290     }
 291 
 292     switch (mc_global.shell->type)
 293     {
 294     case SHELL_BASH:
 295         /* Do we have a custom init file ~/.local/share/mc/bashrc? */
 296         init_file = mc_config_get_full_path ("bashrc");
 297 
 298         /* Otherwise use ~/.bashrc */
 299         if (!exist_file (init_file))
 300         {
 301             g_free (init_file);
 302             init_file = g_strdup (".bashrc");
 303         }
 304 
 305         /* Make MC's special commands not show up in bash's history and also suppress
 306          * consecutive identical commands*/
 307         putenv ((char *) "HISTCONTROL=ignoreboth");
 308 
 309         /* Allow alternative readline settings for MC */
 310         {
 311             char *input_file;
 312 
 313             input_file = mc_config_get_full_path ("inputrc");
 314             if (exist_file (input_file))
 315             {
 316                 putenv_str = g_strconcat ("INPUTRC=", input_file, (char *) NULL);
 317                 putenv (putenv_str);
 318             }
 319             g_free (input_file);
 320         }
 321 
 322         break;
 323 
 324     case SHELL_ASH_BUSYBOX:
 325     case SHELL_DASH:
 326         /* Do we have a custom init file ~/.local/share/mc/ashrc? */
 327         init_file = mc_config_get_full_path ("ashrc");
 328 
 329         /* Otherwise use ~/.profile */
 330         if (!exist_file (init_file))
 331         {
 332             g_free (init_file);
 333             init_file = g_strdup (".profile");
 334         }
 335 
 336         /* Put init file to ENV variable used by ash */
 337         putenv_str = g_strconcat ("ENV=", init_file, (char *) NULL);
 338         putenv (putenv_str);
 339         /* Do not use "g_free (putenv_str)" here, otherwise ENV will be undefined! */
 340 
 341         break;
 342 
 343         /* TODO: Find a way to pass initfile to TCSH, ZSH and FISH */
 344     case SHELL_TCSH:
 345     case SHELL_ZSH:
 346     case SHELL_FISH:
 347         break;
 348 
 349     default:
 350         fprintf (stderr, __FILE__ ": unimplemented subshell type %u\r\n", mc_global.shell->type);
 351         my_exit (FORK_FAILURE);
 352     }
 353 
 354     /* Attach all our standard file descriptors to the pty */
 355 
 356     /* This is done just before the fork, because stderr must still      */
 357     /* be connected to the real tty during the above error messages; */
 358     /* otherwise the user will never see them.                   */
 359 
 360     dup2 (subshell_pty_slave, STDIN_FILENO);
 361     dup2 (subshell_pty_slave, STDOUT_FILENO);
 362     dup2 (subshell_pty_slave, STDERR_FILENO);
 363 
 364     close (subshell_pipe[READ]);
 365     close (subshell_pty_slave); /* These may be FD_CLOEXEC, but just in case... */
 366     /* Close master side of pty.  This is important; apart from */
 367     /* freeing up the descriptor for use in the subshell, it also       */
 368     /* means that when MC exits, the subshell will get a SIGHUP and     */
 369     /* exit too, because there will be no more descriptors pointing     */
 370     /* at the master side of the pty and so it will disappear.  */
 371     close (mc_global.tty.subshell_pty);
 372 
 373     /* Execute the subshell at last */
 374 
 375     switch (mc_global.shell->type)
 376     {
 377     case SHELL_BASH:
 378         execl (mc_global.shell->path, "bash", "-rcfile", init_file, (char *) NULL);
 379         break;
 380 
 381     case SHELL_ZSH:
 382         /* Use -g to exclude cmds beginning with space from history
 383          * and -Z to use the line editor on non-interactive term */
 384         execl (mc_global.shell->path, "zsh", "-Z", "-g", (char *) NULL);
 385 
 386         break;
 387 
 388     case SHELL_ASH_BUSYBOX:
 389     case SHELL_DASH:
 390     case SHELL_TCSH:
 391     case SHELL_FISH:
 392         execl (mc_global.shell->path, mc_global.shell->path, (char *) NULL);
 393         break;
 394 
 395     default:
 396         break;
 397     }
 398 
 399     /* If we get this far, everything failed miserably */
 400     g_free (init_file);
 401     g_free (putenv_str);
 402     my_exit (FORK_FAILURE);
 403 }
 404 
 405 /* --------------------------------------------------------------------------------------------- */
 406 
 407 static void
 408 init_raw_mode (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 409 {
 410     static gboolean initialized = FALSE;
 411 
 412     /* MC calls tty_reset_shell_mode() in pre_exec() to set the real tty to its */
 413     /* original settings.  However, here we need to make this tty very raw,     */
 414     /* so that all keyboard signals, XON/XOFF, etc. will get through to the     */
 415     /* pty.  So, instead of changing the code for execute(), pre_exec(),        */
 416     /* etc, we just set up the modes we need here, before each command.         */
 417 
 418     if (!initialized)           /* First time: initialise 'raw_mode' */
 419     {
 420         tcgetattr (STDOUT_FILENO, &raw_mode);
 421         raw_mode.c_lflag &= ~ICANON;    /* Disable line-editing chars, etc.   */
 422         raw_mode.c_lflag &= ~ISIG;      /* Disable intr, quit & suspend chars */
 423         raw_mode.c_lflag &= ~ECHO;      /* Disable input echoing              */
 424         raw_mode.c_iflag &= ~IXON;      /* Pass ^S/^Q to subshell undisturbed */
 425         raw_mode.c_iflag &= ~ICRNL;     /* Don't translate CRs into LFs       */
 426         raw_mode.c_oflag &= ~OPOST;     /* Don't postprocess output           */
 427         raw_mode.c_cc[VTIME] = 0;       /* IE: wait forever, and return as    */
 428         raw_mode.c_cc[VMIN] = 1;        /* soon as a character is available   */
 429         initialized = TRUE;
 430     }
 431 }
 432 
 433 /* --------------------------------------------------------------------------------------------- */
 434 /**
 435  * Wait until the subshell dies or stops.  If it stops, make it resume.
 436  * Possibly modifies the globals 'subshell_alive' and 'subshell_stopped'
 437  */
 438 
 439 static void
 440 synchronize (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 441 {
 442     sigset_t sigchld_mask, old_mask;
 443 
 444     sigemptyset (&sigchld_mask);
 445     sigaddset (&sigchld_mask, SIGCHLD);
 446     sigprocmask (SIG_BLOCK, &sigchld_mask, &old_mask);
 447 
 448     /*
 449      * SIGCHLD should not be blocked, but we unblock it just in case.
 450      * This is known to be useful for cygwin 1.3.12 and older.
 451      */
 452     sigdelset (&old_mask, SIGCHLD);
 453 
 454     /* Wait until the subshell has stopped */
 455     while (subshell_alive && !subshell_stopped)
 456         sigsuspend (&old_mask);
 457 
 458     if (subshell_state != ACTIVE)
 459     {
 460         /* Discard all remaining data from stdin to the subshell */
 461         tcflush (subshell_pty_slave, TCIFLUSH);
 462     }
 463 
 464     subshell_stopped = FALSE;
 465     kill (subshell_pid, SIGCONT);
 466 
 467     sigprocmask (SIG_SETMASK, &old_mask, NULL);
 468     /* We can't do any better without modifying the shell(s) */
 469 }
 470 
 471 /* --------------------------------------------------------------------------------------------- */
 472 /** Feed the subshell our keyboard input until it says it's finished */
 473 
 474 static gboolean
 475 feed_subshell (int how, gboolean fail_on_error)
     /* [previous][next][first][last][top][bottom][index][help]  */
 476 {
 477     fd_set read_set;            /* For 'select' */
 478     int bytes;                  /* For the return value from 'read' */
 479     int i;                      /* Loop counter */
 480 
 481     struct timeval wtime;       /* Maximum time we wait for the subshell */
 482     struct timeval *wptr;
 483 
 484     /* we wait up to 10 seconds if fail_on_error, forever otherwise */
 485     wtime.tv_sec = 10;
 486     wtime.tv_usec = 0;
 487     wptr = fail_on_error ? &wtime : NULL;
 488 
 489     while (TRUE)
 490     {
 491         int maxfdp;
 492 
 493         if (!subshell_alive)
 494             return FALSE;
 495 
 496         /* Prepare the file-descriptor set and call 'select' */
 497 
 498         FD_ZERO (&read_set);
 499         FD_SET (mc_global.tty.subshell_pty, &read_set);
 500         FD_SET (subshell_pipe[READ], &read_set);
 501         maxfdp = MAX (mc_global.tty.subshell_pty, subshell_pipe[READ]);
 502         if (how == VISIBLY)
 503         {
 504             FD_SET (STDIN_FILENO, &read_set);
 505             maxfdp = MAX (maxfdp, STDIN_FILENO);
 506         }
 507 
 508         if (select (maxfdp + 1, &read_set, NULL, NULL, wptr) == -1)
 509         {
 510             /* Despite using SA_RESTART, we still have to check for this */
 511             if (errno == EINTR)
 512             {
 513                 if (tty_got_winch ())
 514                     tty_change_screen_size ();
 515 
 516                 continue;       /* try all over again */
 517             }
 518             tcsetattr (STDOUT_FILENO, TCSANOW, &shell_mode);
 519             fprintf (stderr, "select (FD_SETSIZE, &read_set...): %s\r\n",
 520                      unix_error_string (errno));
 521             exit (EXIT_FAILURE);
 522         }
 523 
 524         if (FD_ISSET (mc_global.tty.subshell_pty, &read_set))
 525             /* Read from the subshell, write to stdout */
 526 
 527             /* This loop improves performance by reducing context switches
 528                by a factor of 20 or so... unfortunately, it also hangs MC
 529                randomly, because of an apparent Linux bug.  Investigate. */
 530             /* for (i=0; i<5; ++i)  * FIXME -- experimental */
 531         {
 532             bytes = read (mc_global.tty.subshell_pty, pty_buffer, sizeof (pty_buffer));
 533 
 534             /* The subshell has died */
 535             if (bytes == -1 && errno == EIO && !subshell_alive)
 536                 return FALSE;
 537 
 538             if (bytes <= 0)
 539             {
 540 #ifdef PTY_ZEROREAD
 541                 /* On IBM i, read(1) can return 0 for a non-closed fd */
 542                 continue;
 543 #else
 544                 tcsetattr (STDOUT_FILENO, TCSANOW, &shell_mode);
 545                 fprintf (stderr, "read (subshell_pty...): %s\r\n", unix_error_string (errno));
 546                 exit (EXIT_FAILURE);
 547 #endif
 548             }
 549 
 550             if (how == VISIBLY)
 551                 write_all (STDOUT_FILENO, pty_buffer, bytes);
 552         }
 553 
 554         else if (FD_ISSET (subshell_pipe[READ], &read_set))
 555             /* Read the subshell's CWD and capture its prompt */
 556         {
 557             bytes = read (subshell_pipe[READ], subshell_cwd, sizeof (subshell_cwd));
 558             if (bytes <= 0)
 559             {
 560                 tcsetattr (STDOUT_FILENO, TCSANOW, &shell_mode);
 561                 fprintf (stderr, "read (subshell_pipe[READ]...): %s\r\n",
 562                          unix_error_string (errno));
 563                 exit (EXIT_FAILURE);
 564             }
 565 
 566             subshell_cwd[bytes - 1] = 0;        /* Squash the final '\n' */
 567 
 568             synchronize ();
 569 
 570             subshell_ready = TRUE;
 571             if (subshell_state == RUNNING_COMMAND)
 572             {
 573                 subshell_state = INACTIVE;
 574                 return TRUE;
 575             }
 576         }
 577 
 578         else if (FD_ISSET (STDIN_FILENO, &read_set))
 579             /* Read from stdin, write to the subshell */
 580         {
 581             bytes = read (STDIN_FILENO, pty_buffer, sizeof (pty_buffer));
 582             if (bytes <= 0)
 583             {
 584                 tcsetattr (STDOUT_FILENO, TCSANOW, &shell_mode);
 585                 fprintf (stderr,
 586                          "read (STDIN_FILENO, pty_buffer...): %s\r\n", unix_error_string (errno));
 587                 exit (EXIT_FAILURE);
 588             }
 589 
 590             for (i = 0; i < bytes; ++i)
 591                 if (pty_buffer[i] == subshell_switch_key)
 592                 {
 593                     write_all (mc_global.tty.subshell_pty, pty_buffer, i);
 594                     if (subshell_ready)
 595                         subshell_state = INACTIVE;
 596                     return TRUE;
 597                 }
 598 
 599             write_all (mc_global.tty.subshell_pty, pty_buffer, bytes);
 600 
 601             if (pty_buffer[bytes - 1] == '\n' || pty_buffer[bytes - 1] == '\r')
 602                 subshell_ready = FALSE;
 603         }
 604         else
 605             return FALSE;
 606     }
 607 }
 608 
 609 /* --------------------------------------------------------------------------------------------- */
 610 /* pty opening functions */
 611 
 612 #ifndef HAVE_OPENPTY
 613 
 614 #ifdef HAVE_GRANTPT
 615 
 616 /* System V version of pty_open_master */
 617 
 618 static int
 619 pty_open_master (char *pty_name)
     /* [previous][next][first][last][top][bottom][index][help]  */
 620 {
 621     char *slave_name;
 622     int pty_master;
 623 
 624 #ifdef HAVE_POSIX_OPENPT
 625     pty_master = posix_openpt (O_RDWR);
 626 #elif defined HAVE_GETPT
 627     /* getpt () is a GNU extension (glibc 2.1.x) */
 628     pty_master = getpt ();
 629 #elif defined IS_AIX
 630     strcpy (pty_name, "/dev/ptc");
 631     pty_master = open (pty_name, O_RDWR);
 632 #else
 633     strcpy (pty_name, "/dev/ptmx");
 634     pty_master = open (pty_name, O_RDWR);
 635 #endif
 636 
 637     if (pty_master == -1)
 638         return -1;
 639 
 640     if (grantpt (pty_master) == -1      /* Grant access to slave */
 641         || unlockpt (pty_master) == -1  /* Clear slave's lock flag */
 642         || !(slave_name = ptsname (pty_master)))        /* Get slave's name */
 643     {
 644         close (pty_master);
 645         return -1;
 646     }
 647     strcpy (pty_name, slave_name);
 648     return pty_master;
 649 }
 650 
 651 /* --------------------------------------------------------------------------------------------- */
 652 /** System V version of pty_open_slave */
 653 
 654 static int
 655 pty_open_slave (const char *pty_name)
     /* [previous][next][first][last][top][bottom][index][help]  */
 656 {
 657     int pty_slave;
 658 
 659     pty_slave = open (pty_name, O_RDWR);
 660     if (pty_slave == -1)
 661     {
 662         fprintf (stderr, "open (%s, O_RDWR): %s\r\n", pty_name, unix_error_string (errno));
 663         return -1;
 664     }
 665 #if !defined(__osf__) && !defined(__linux__)
 666 #if defined (I_FIND) && defined (I_PUSH)
 667     if (ioctl (pty_slave, I_FIND, "ptem") == 0)
 668         if (ioctl (pty_slave, I_PUSH, "ptem") == -1)
 669         {
 670             fprintf (stderr, "ioctl (%d, I_PUSH, \"ptem\") failed: %s\r\n",
 671                      pty_slave, unix_error_string (errno));
 672             close (pty_slave);
 673             return -1;
 674         }
 675 
 676     if (ioctl (pty_slave, I_FIND, "ldterm") == 0)
 677         if (ioctl (pty_slave, I_PUSH, "ldterm") == -1)
 678         {
 679             fprintf (stderr,
 680                      "ioctl (%d, I_PUSH, \"ldterm\") failed: %s\r\n",
 681                      pty_slave, unix_error_string (errno));
 682             close (pty_slave);
 683             return -1;
 684         }
 685 #if !defined(sgi) && !defined(__sgi)
 686     if (ioctl (pty_slave, I_FIND, "ttcompat") == 0)
 687         if (ioctl (pty_slave, I_PUSH, "ttcompat") == -1)
 688         {
 689             fprintf (stderr,
 690                      "ioctl (%d, I_PUSH, \"ttcompat\") failed: %s\r\n",
 691                      pty_slave, unix_error_string (errno));
 692             close (pty_slave);
 693             return -1;
 694         }
 695 #endif /* sgi || __sgi */
 696 #endif /* I_FIND && I_PUSH */
 697 #endif /* __osf__ || __linux__ */
 698 
 699     fcntl (pty_slave, F_SETFD, FD_CLOEXEC);
 700     return pty_slave;
 701 }
 702 
 703 #else /* !HAVE_GRANTPT */
 704 
 705 /* --------------------------------------------------------------------------------------------- */
 706 /** BSD version of pty_open_master */
 707 static int
 708 pty_open_master (char *pty_name)
     /* [previous][next][first][last][top][bottom][index][help]  */
 709 {
 710     int pty_master;
 711     const char *ptr1, *ptr2;
 712 
 713     strcpy (pty_name, "/dev/ptyXX");
 714     for (ptr1 = "pqrstuvwxyzPQRST"; *ptr1; ++ptr1)
 715     {
 716         pty_name[8] = *ptr1;
 717         for (ptr2 = "0123456789abcdef"; *ptr2 != '\0'; ++ptr2)
 718         {
 719             pty_name[9] = *ptr2;
 720 
 721             /* Try to open master */
 722             pty_master = open (pty_name, O_RDWR);
 723             if (pty_master == -1)
 724             {
 725                 if (errno == ENOENT)    /* Different from EIO */
 726                     return -1;  /* Out of pty devices */
 727                 continue;       /* Try next pty device */
 728             }
 729             pty_name[5] = 't';  /* Change "pty" to "tty" */
 730             if (access (pty_name, 6) != 0)
 731             {
 732                 close (pty_master);
 733                 pty_name[5] = 'p';
 734                 continue;
 735             }
 736             return pty_master;
 737         }
 738     }
 739     return -1;                  /* Ran out of pty devices */
 740 }
 741 
 742 /* --------------------------------------------------------------------------------------------- */
 743 /** BSD version of pty_open_slave */
 744 
 745 static int
 746 pty_open_slave (const char *pty_name)
     /* [previous][next][first][last][top][bottom][index][help]  */
 747 {
 748     int pty_slave;
 749     struct group *group_info;
 750 
 751     group_info = getgrnam ("tty");
 752     if (group_info != NULL)
 753     {
 754         /* The following two calls will only succeed if we are root */
 755         /* [Commented out while permissions problem is investigated] */
 756         /* chown (pty_name, getuid (), group_info->gr_gid);  FIXME */
 757         /* chmod (pty_name, S_IRUSR | S_IWUSR | S_IWGRP);   FIXME */
 758     }
 759     pty_slave = open (pty_name, O_RDWR);
 760     if (pty_slave == -1)
 761         fprintf (stderr, "open (pty_name, O_RDWR): %s\r\n", pty_name);
 762     fcntl (pty_slave, F_SETFD, FD_CLOEXEC);
 763     return pty_slave;
 764 }
 765 #endif /* !HAVE_GRANTPT */
 766 
 767 #endif /* !HAVE_OPENPTY */
 768 
 769 /* --------------------------------------------------------------------------------------------- */
 770 /**
 771  * Set up `precmd' or equivalent for reading the subshell's CWD.
 772  *
 773  * Attention! Never forget that these are *one-liners* even though the concatenated
 774  * substrings contain line breaks and indentation for better understanding of the
 775  * shell code. It is vital that each one-liner ends with a line feed character ("\n" ).
 776  *
 777  * @return initialized pre-command string
 778  */
 779 
 780 static void
 781 init_subshell_precmd (char *precmd, size_t buff_size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 782 {
 783     switch (mc_global.shell->type)
 784     {
 785     case SHELL_BASH:
 786         g_snprintf (precmd, buff_size,
 787                     " PROMPT_COMMAND=${PROMPT_COMMAND:+$PROMPT_COMMAND\n}'pwd>&%d;kill -STOP $$'\n"
 788                     "PS1='\\u@\\h:\\w\\$ '\n", subshell_pipe[WRITE]);
 789         break;
 790 
 791     case SHELL_ASH_BUSYBOX:
 792         /* BusyBox ash needs a somewhat complicated precmd emulation via PS1, and it is vital
 793          * that BB be built with active CONFIG_ASH_EXPAND_PRMT, but this is the default anyway.
 794          *
 795          * A: This leads to a stopped subshell (=frozen mc) if user calls "ash" command
 796          *    "PS1='$(pwd>&%d; kill -STOP $$)\\u@\\h:\\w\\$ '\n",
 797          *
 798          * B: This leads to "sh: precmd: not found" in sub-subshell if user calls "ash" command
 799          *    "precmd() { pwd>&%d; kill -STOP $$; }; "
 800          *    "PS1='$(precmd)\\u@\\h:\\w\\$ '\n",
 801          *
 802          * C: This works if user calls "ash" command because in sub-subshell
 803          *    PRECMD is unfedined, thus evaluated to empty string - no damage done.
 804          *    Attention: BusyBox must be built with FEATURE_EDITING_FANCY_PROMPT to
 805          *    permit \u, \w, \h, \$ escape sequences. Unfortunately this cannot be guaranteed,
 806          *    especially on embedded systems where people try to save space, so let's use
 807          *    the dash version below. It should work on virtually all systems.
 808          *    "precmd() { pwd>&%d; kill -STOP $$; }; "
 809          *    "PRECMD=precmd; "
 810          *    "PS1='$(eval $PRECMD)\\u@\\h:\\w\\$ '\n",
 811          */
 812     case SHELL_DASH:
 813         /* Debian ash needs a precmd emulation via PS1, similar to BusyBox ash,
 814          * but does not support escape sequences for user, host and cwd in prompt.
 815          * Attention! Make sure that the buffer for precmd is big enough.
 816          *
 817          * We want to have a fancy dynamic prompt with user@host:cwd just like in the BusyBox
 818          * examples above, but because replacing the home directory part of the path by "~" is
 819          * complicated, it bloats the precmd to a size > BUF_SMALL (128).
 820          *
 821          * The following example is a little less fancy (home directory not replaced)
 822          * and shows the basic workings of our prompt for easier understanding:
 823          *
 824          * "precmd() { "
 825          *     "echo \"$USER@$(hostname -s):$PWD\"; "
 826          *     "pwd>&%d; "
 827          *     "kill -STOP $$; "
 828          * "}; "
 829          * "PRECMD=precmd; "
 830          * "PS1='$($PRECMD)$ '\n",
 831          */
 832         g_snprintf (precmd, buff_size,
 833                     "precmd() { "
 834                     "if [ ! \"${PWD##$HOME}\" ]; then "
 835                     "MC_PWD=\"~\"; "
 836                     "else "
 837                     "[ \"${PWD##$HOME/}\" = \"$PWD\" ] && MC_PWD=\"$PWD\" || MC_PWD=\"~/${PWD##$HOME/}\"; "
 838                     "fi; "
 839                     "echo \"$USER@$(hostname -s):$MC_PWD\"; "
 840                     "pwd>&%d; "
 841                     "kill -STOP $$; "
 842                     "}; " "PRECMD=precmd; " "PS1='$($PRECMD)$ '\n", subshell_pipe[WRITE]);
 843         break;
 844 
 845     case SHELL_ZSH:
 846         g_snprintf (precmd, buff_size,
 847                     " _mc_precmd(){ pwd>&%d;kill -STOP $$ }; precmd_functions+=(_mc_precmd)\n"
 848                     "PS1='%%n@%%m:%%~%%# '\n", subshell_pipe[WRITE]);
 849         break;
 850 
 851     case SHELL_TCSH:
 852         g_snprintf (precmd, buff_size,
 853                     "set echo_style=both; "
 854                     "set prompt='%%n@%%m:%%~%%# '; "
 855                     "alias precmd 'echo $cwd:q >>%s; kill -STOP $$'\n", tcsh_fifo);
 856         break;
 857 
 858     case SHELL_FISH:
 859         g_snprintf (precmd, buff_size,
 860                     " if not functions -q fish_prompt_mc;"
 861                     "functions -e fish_right_prompt;"
 862                     "functions -c fish_prompt fish_prompt_mc; end;"
 863                     "function fish_prompt;"
 864                     "echo \"$PWD\">&%d; fish_prompt_mc; kill -STOP %%self; end\n",
 865                     subshell_pipe[WRITE]);
 866         break;
 867 
 868     default:
 869         break;
 870     }
 871 }
 872 
 873 /* --------------------------------------------------------------------------------------------- */
 874 /**
 875  * Carefully quote directory name to allow entering any directory safely,
 876  * no matter what weird characters it may contain in its name.
 877  * NOTE: Treat directory name an untrusted data, don't allow it to cause
 878  * executing any commands in the shell.  Escape all control characters.
 879  * Use following technique:
 880  *
 881  * printf(1) with format string containing a single conversion specifier,
 882  * "b", and an argument which contains a copy of the string passed to 
 883  * subshell_name_quote() with all characters, except digits and letters,
 884  * replaced by the backslash-escape sequence \0nnn, where "nnn" is the
 885  * numeric value of the character converted to octal number.
 886  * 
 887  *   cd "`printf '%b' 'ABC\0nnnDEF\0nnnXYZ'`"
 888  *
 889  * N.B.: Use single quotes for conversion specifier to work around
 890  *       tcsh 6.20+ parser breakage, see ticket #3852 for the details.
 891  */
 892 
 893 static GString *
 894 subshell_name_quote (const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 895 {
 896     GString *ret;
 897     const char *su, *n;
 898     const char *quote_cmd_start, *quote_cmd_end;
 899 
 900     if (mc_global.shell->type == SHELL_FISH)
 901     {
 902         quote_cmd_start = "(printf '%b' '";
 903         quote_cmd_end = "')";
 904     }
 905     /* TODO: When BusyBox printf is fixed, get rid of this "else if", see
 906        http://lists.busybox.net/pipermail/busybox/2012-March/077460.html */
 907     /* else if (subshell_type == ASH_BUSYBOX)
 908        {
 909        quote_cmd_start = "\"`echo -en '";
 910        quote_cmd_end = "'`\"";
 911        } */
 912     else
 913     {
 914         quote_cmd_start = "\"`printf '%b' '";
 915         quote_cmd_end = "'`\"";
 916     }
 917 
 918     ret = g_string_sized_new (64);
 919 
 920     /* Prevent interpreting leading '-' as a switch for 'cd' */
 921     if (s[0] == '-')
 922         g_string_append (ret, "./");
 923 
 924     /* Copy the beginning of the command to the buffer */
 925     g_string_append (ret, quote_cmd_start);
 926 
 927     /*
 928      * Print every character except digits and letters as a backslash-escape
 929      * sequence of the form \0nnn, where "nnn" is the numeric value of the
 930      * character converted to octal number.
 931      */
 932     for (su = s; su[0] != '\0'; su = n)
 933     {
 934         n = str_cget_next_char_safe (su);
 935 
 936         if (str_isalnum (su))
 937             g_string_append_len (ret, su, n - su);
 938         else
 939         {
 940             int c;
 941 
 942             for (c = 0; c < n - su; c++)
 943                 g_string_append_printf (ret, "\\0%03o", (unsigned char) su[c]);
 944         }
 945     }
 946 
 947     g_string_append (ret, quote_cmd_end);
 948 
 949     return ret;
 950 }
 951 
 952 /* --------------------------------------------------------------------------------------------- */
 953 /*** public functions ****************************************************************************/
 954 /* --------------------------------------------------------------------------------------------- */
 955 
 956 /* --------------------------------------------------------------------------------------------- */
 957 /**
 958  *  Fork the subshell, and set up many, many things.
 959  *
 960  *  Possibly modifies the global variables:
 961  *      subshell_type, subshell_alive, subshell_stopped, subshell_pid
 962  *      mc_global.tty.use_subshell - Is set to FALSE if we can't run the subshell
 963  *      quit - Can be set to SUBSHELL_EXIT by the SIGCHLD handler
 964  */
 965 
 966 void
 967 init_subshell (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 968 {
 969     /* This must be remembered across calls to init_subshell() */
 970     static char pty_name[BUF_SMALL];
 971     /* Must be considerably longer than BUF_SMALL (128) to support fancy shell prompts */
 972     char precmd[BUF_MEDIUM];
 973 
 974     /* Take the current (hopefully pristine) tty mode and make */
 975     /* a raw mode based on it now, before we do anything else with it */
 976     init_raw_mode ();
 977 
 978     if (mc_global.tty.subshell_pty == 0)
 979     {                           /* First time through */
 980         if (mc_global.shell->type == SHELL_NONE)
 981             return;
 982 
 983         /* Open a pty for talking to the subshell */
 984 
 985         /* FIXME: We may need to open a fresh pty each time on SVR4 */
 986 
 987 #ifdef HAVE_OPENPTY
 988         if (openpty (&mc_global.tty.subshell_pty, &subshell_pty_slave, NULL, NULL, NULL))
 989         {
 990             fprintf (stderr, "Cannot open master and slave sides of pty: %s\n",
 991                      unix_error_string (errno));
 992             mc_global.tty.use_subshell = FALSE;
 993             return;
 994         }
 995 #else
 996         mc_global.tty.subshell_pty = pty_open_master (pty_name);
 997         if (mc_global.tty.subshell_pty == -1)
 998         {
 999             fprintf (stderr, "Cannot open master side of pty: %s\r\n", unix_error_string (errno));
1000             mc_global.tty.use_subshell = FALSE;
1001             return;
1002         }
1003         subshell_pty_slave = pty_open_slave (pty_name);
1004         if (subshell_pty_slave == -1)
1005         {
1006             fprintf (stderr, "Cannot open slave side of pty %s: %s\r\n",
1007                      pty_name, unix_error_string (errno));
1008             mc_global.tty.use_subshell = FALSE;
1009             return;
1010         }
1011 #endif /* HAVE_OPENPTY */
1012 
1013         /* Create a pipe for receiving the subshell's CWD */
1014 
1015         if (mc_global.shell->type == SHELL_TCSH)
1016         {
1017             g_snprintf (tcsh_fifo, sizeof (tcsh_fifo), "%s/mc.pipe.%d",
1018                         mc_tmpdir (), (int) getpid ());
1019             if (mkfifo (tcsh_fifo, 0600) == -1)
1020             {
1021                 fprintf (stderr, "mkfifo(%s) failed: %s\r\n", tcsh_fifo, unix_error_string (errno));
1022                 mc_global.tty.use_subshell = FALSE;
1023                 return;
1024             }
1025 
1026             /* Opening the FIFO as O_RDONLY or O_WRONLY causes deadlock */
1027 
1028             if ((subshell_pipe[READ] = open (tcsh_fifo, O_RDWR)) == -1
1029                 || (subshell_pipe[WRITE] = open (tcsh_fifo, O_RDWR)) == -1)
1030             {
1031                 fprintf (stderr, _("Cannot open named pipe %s\n"), tcsh_fifo);
1032                 perror (__FILE__ ": open");
1033                 mc_global.tty.use_subshell = FALSE;
1034                 return;
1035             }
1036         }
1037         else if (pipe (subshell_pipe))  /* subshell_type is BASH, ASH_BUSYBOX, DASH or ZSH */
1038         {
1039             perror (__FILE__ ": couldn't create pipe");
1040             mc_global.tty.use_subshell = FALSE;
1041             return;
1042         }
1043     }
1044 
1045     /* Fork the subshell */
1046 
1047     subshell_alive = TRUE;
1048     subshell_stopped = FALSE;
1049     subshell_pid = fork ();
1050 
1051     if (subshell_pid == -1)
1052     {
1053         fprintf (stderr, "Cannot spawn the subshell process: %s\r\n", unix_error_string (errno));
1054         /* We exit here because, if the process table is full, the */
1055         /* other method of running user commands won't work either */
1056         exit (EXIT_FAILURE);
1057     }
1058 
1059     if (subshell_pid == 0)
1060     {
1061         /* We are in the child process */
1062         init_subshell_child (pty_name);
1063     }
1064 
1065     init_subshell_precmd (precmd, BUF_MEDIUM);
1066 
1067     write_all (mc_global.tty.subshell_pty, precmd, strlen (precmd));
1068 
1069     /* Wait until the subshell has started up and processed the command */
1070 
1071     subshell_state = RUNNING_COMMAND;
1072     tty_enable_interrupt_key ();
1073     if (!feed_subshell (QUIETLY, TRUE))
1074     {
1075         mc_global.tty.use_subshell = FALSE;
1076     }
1077     tty_disable_interrupt_key ();
1078     if (!subshell_alive)
1079         mc_global.tty.use_subshell = FALSE;     /* Subshell died instantly, so don't use it */
1080 }
1081 
1082 /* --------------------------------------------------------------------------------------------- */
1083 
1084 int
1085 invoke_subshell (const char *command, int how, vfs_path_t ** new_dir_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1086 {
1087     /* Make the MC terminal transparent */
1088     tcsetattr (STDOUT_FILENO, TCSANOW, &raw_mode);
1089 
1090     /* Make the subshell change to MC's working directory */
1091     if (new_dir_vpath != NULL)
1092         do_subshell_chdir (subshell_get_cwd (), TRUE);
1093 
1094     if (command == NULL)        /* The user has done "C-o" from MC */
1095     {
1096         if (subshell_state == INACTIVE)
1097         {
1098             subshell_state = ACTIVE;
1099 
1100             /* FIXME: possibly take out this hack; the user can re-play it by hitting C-hyphen a few times! */
1101             if (subshell_ready && mc_global.mc_run_mode == MC_RUN_FULL)
1102                 write_all (mc_global.tty.subshell_pty, " \b", 2);       /* Hack to make prompt reappear */
1103         }
1104     }
1105     else                        /* MC has passed us a user command */
1106     {
1107         if (how == QUIETLY)
1108             write_all (mc_global.tty.subshell_pty, " ", 1);
1109         /* FIXME: if command is long (>8KB ?) we go comma */
1110         write_all (mc_global.tty.subshell_pty, command, strlen (command));
1111         write_all (mc_global.tty.subshell_pty, "\n", 1);
1112         subshell_state = RUNNING_COMMAND;
1113         subshell_ready = FALSE;
1114     }
1115 
1116     feed_subshell (how, FALSE);
1117 
1118     if (new_dir_vpath != NULL && subshell_alive)
1119     {
1120         const char *pcwd;
1121 
1122         pcwd = vfs_translate_path (vfs_path_as_str (subshell_get_cwd ()));
1123         if (strcmp (subshell_cwd, pcwd) != 0)
1124             *new_dir_vpath = vfs_path_from_str (subshell_cwd);  /* Make MC change to the subshell's CWD */
1125     }
1126 
1127     /* Restart the subshell if it has died by SIGHUP, SIGQUIT, etc. */
1128     while (!subshell_alive && subshell_get_mainloop_quit () == 0 && mc_global.tty.use_subshell)
1129         init_subshell ();
1130 
1131     return subshell_get_mainloop_quit ();
1132 }
1133 
1134 
1135 /* --------------------------------------------------------------------------------------------- */
1136 
1137 gboolean
1138 read_subshell_prompt (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1139 {
1140     int rc = 0;
1141     ssize_t bytes = 0;
1142     struct timeval timeleft = { 0, 0 };
1143     GString *p;
1144     gboolean prompt_was_reset = FALSE;
1145 
1146     fd_set tmp;
1147     FD_ZERO (&tmp);
1148     FD_SET (mc_global.tty.subshell_pty, &tmp);
1149 
1150     /* First time through */
1151     if (subshell_prompt == NULL)
1152         subshell_prompt = g_string_sized_new (INITIAL_PROMPT_SIZE);
1153 
1154     p = g_string_sized_new (INITIAL_PROMPT_SIZE);
1155 
1156     while (subshell_alive
1157            && (rc = select (mc_global.tty.subshell_pty + 1, &tmp, NULL, NULL, &timeleft)) != 0)
1158     {
1159         ssize_t i;
1160 
1161         /* Check for 'select' errors */
1162         if (rc == -1)
1163         {
1164             if (errno == EINTR)
1165             {
1166                 if (tty_got_winch ())
1167                     tty_change_screen_size ();
1168 
1169                 continue;
1170             }
1171 
1172             fprintf (stderr, "select (FD_SETSIZE, &tmp...): %s\r\n", unix_error_string (errno));
1173             exit (EXIT_FAILURE);
1174         }
1175 
1176         bytes = read (mc_global.tty.subshell_pty, pty_buffer, sizeof (pty_buffer));
1177 
1178         /* Extract the prompt from the shell output */
1179         for (i = 0; i < bytes; i++)
1180             if (pty_buffer[i] == '\n' || pty_buffer[i] == '\r')
1181             {
1182                 g_string_set_size (p, 0);
1183                 prompt_was_reset = TRUE;
1184             }
1185             else if (pty_buffer[i] != '\0')
1186                 g_string_append_c (p, pty_buffer[i]);
1187     }
1188 
1189     if (p->len != 0 || prompt_was_reset)
1190         g_string_assign (subshell_prompt, p->str);
1191 
1192     g_string_free (p, TRUE);
1193 
1194     return (rc != 0 || bytes != 0);
1195 }
1196 
1197 /* --------------------------------------------------------------------------------------------- */
1198 
1199 void
1200 do_update_prompt (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1201 {
1202     if (update_subshell_prompt)
1203     {
1204         printf ("\r\n%s", subshell_prompt->str);
1205         fflush (stdout);
1206         update_subshell_prompt = FALSE;
1207     }
1208 }
1209 
1210 /* --------------------------------------------------------------------------------------------- */
1211 
1212 gboolean
1213 exit_subshell (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1214 {
1215     gboolean subshell_quit = TRUE;
1216 
1217     if (subshell_state != INACTIVE && subshell_alive)
1218         subshell_quit =
1219             query_dialog (_("Warning"),
1220                           _("The shell is still active. Quit anyway?"),
1221                           D_NORMAL, 2, _("&Yes"), _("&No")) == 0;
1222 
1223     if (subshell_quit)
1224     {
1225         if (mc_global.shell->type == SHELL_TCSH)
1226         {
1227             if (unlink (tcsh_fifo) == -1)
1228                 fprintf (stderr, "Cannot remove named pipe %s: %s\r\n",
1229                          tcsh_fifo, unix_error_string (errno));
1230         }
1231 
1232         g_string_free (subshell_prompt, TRUE);
1233         subshell_prompt = NULL;
1234         pty_buffer[0] = '\0';
1235     }
1236 
1237     return subshell_quit;
1238 }
1239 
1240 /* --------------------------------------------------------------------------------------------- */
1241 
1242 /** If it actually changed the directory it returns true */
1243 void
1244 do_subshell_chdir (const vfs_path_t * vpath, gboolean update_prompt)
     /* [previous][next][first][last][top][bottom][index][help]  */
1245 {
1246     char *pcwd;
1247 
1248     pcwd = vfs_path_to_str_flags (subshell_get_cwd (), 0, VPF_RECODE);
1249 
1250     if (!(subshell_state == INACTIVE && strcmp (subshell_cwd, pcwd) != 0))
1251     {
1252         /* We have to repaint the subshell prompt if we read it from
1253          * the main program.  Please note that in the code after this
1254          * if, the cd command that is sent will make the subshell
1255          * repaint the prompt, so we don't have to paint it. */
1256         if (update_prompt)
1257             do_update_prompt ();
1258         g_free (pcwd);
1259         return;
1260     }
1261 
1262     /* The initial space keeps this out of the command history (in bash
1263        because we set "HISTCONTROL=ignorespace") */
1264     write_all (mc_global.tty.subshell_pty, " cd ", 4);
1265 
1266     if (vpath != NULL)
1267     {
1268         const char *translate;
1269 
1270         translate = vfs_translate_path (vfs_path_as_str (vpath));
1271         if (translate != NULL)
1272         {
1273             GString *temp;
1274 
1275             temp = subshell_name_quote (translate);
1276             write_all (mc_global.tty.subshell_pty, temp->str, temp->len);
1277             g_string_free (temp, TRUE);
1278         }
1279         else
1280         {
1281             write_all (mc_global.tty.subshell_pty, ".", 1);
1282         }
1283     }
1284     else
1285     {
1286         write_all (mc_global.tty.subshell_pty, "/", 1);
1287     }
1288     write_all (mc_global.tty.subshell_pty, "\n", 1);
1289 
1290     subshell_state = RUNNING_COMMAND;
1291     feed_subshell (QUIETLY, FALSE);
1292 
1293     if (subshell_alive)
1294     {
1295         gboolean bPathNotEq;
1296 
1297         bPathNotEq = strcmp (subshell_cwd, pcwd) != 0;
1298 
1299         if (bPathNotEq && mc_global.shell->type == SHELL_TCSH)
1300         {
1301             char rp_subshell_cwd[PATH_MAX];
1302             char rp_current_panel_cwd[PATH_MAX];
1303             char *p_subshell_cwd, *p_current_panel_cwd;
1304 
1305             p_subshell_cwd = mc_realpath (subshell_cwd, rp_subshell_cwd);
1306             p_current_panel_cwd = mc_realpath (pcwd, rp_current_panel_cwd);
1307 
1308             if (p_subshell_cwd == NULL)
1309                 p_subshell_cwd = subshell_cwd;
1310             if (p_current_panel_cwd == NULL)
1311                 p_current_panel_cwd = pcwd;
1312             bPathNotEq = strcmp (p_subshell_cwd, p_current_panel_cwd) != 0;
1313         }
1314 
1315         if (bPathNotEq && !DIR_IS_DOT (pcwd))
1316         {
1317             char *cwd;
1318 
1319             cwd = vfs_path_to_str_flags (subshell_get_cwd (), 0, VPF_STRIP_PASSWORD);
1320             vfs_print_message (_("Warning: Cannot change to %s.\n"), cwd);
1321             g_free (cwd);
1322         }
1323     }
1324 
1325     /* Really escape Zsh history */
1326     if (mc_global.shell->type == SHELL_ZSH)
1327     {
1328         /* Per Zsh documentation last command prefixed with space lingers in the internal history
1329          * until the next command is entered before it vanishes. To make it vanish right away,
1330          * type a space and press return. */
1331         write_all (mc_global.tty.subshell_pty, " \n", 2);
1332         subshell_state = RUNNING_COMMAND;
1333         feed_subshell (QUIETLY, FALSE);
1334     }
1335 
1336     update_subshell_prompt = FALSE;
1337 
1338     g_free (pcwd);
1339     /* Make sure that MC never stores the CWD in a silly format */
1340     /* like /usr////lib/../bin, or the strcmp() above will fail */
1341 }
1342 
1343 /* --------------------------------------------------------------------------------------------- */
1344 
1345 void
1346 subshell_get_console_attributes (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1347 {
1348     /* Get our current terminal modes */
1349 
1350     if (tcgetattr (STDOUT_FILENO, &shell_mode))
1351     {
1352         fprintf (stderr, "Cannot get terminal settings: %s\r\n", unix_error_string (errno));
1353         mc_global.tty.use_subshell = FALSE;
1354     }
1355 }
1356 
1357 /* --------------------------------------------------------------------------------------------- */
1358 /**
1359  * Figure out whether the subshell has stopped, exited or been killed
1360  * Possibly modifies: 'subshell_alive', 'subshell_stopped' and 'quit' */
1361 
1362 void
1363 sigchld_handler (int sig)
     /* [previous][next][first][last][top][bottom][index][help]  */
1364 {
1365     int status;
1366     pid_t pid;
1367 
1368     (void) sig;
1369 
1370     pid = waitpid (subshell_pid, &status, WUNTRACED | WNOHANG);
1371 
1372     if (pid == subshell_pid)
1373     {
1374         /* Figure out what has happened to the subshell */
1375 
1376         if (WIFSTOPPED (status))
1377         {
1378             if (WSTOPSIG (status) == SIGSTOP)
1379             {
1380                 /* The subshell has received a SIGSTOP signal */
1381                 subshell_stopped = TRUE;
1382             }
1383             else
1384             {
1385                 /* The user has suspended the subshell.  Revive it */
1386                 kill (subshell_pid, SIGCONT);
1387             }
1388         }
1389         else
1390         {
1391             /* The subshell has either exited normally or been killed */
1392             subshell_alive = FALSE;
1393             delete_select_channel (mc_global.tty.subshell_pty);
1394             if (WIFEXITED (status) && WEXITSTATUS (status) != FORK_FAILURE)
1395             {
1396                 int subshell_quit;
1397                 subshell_quit = subshell_get_mainloop_quit () | SUBSHELL_EXIT;  /* Exited normally */
1398                 subshell_set_mainloop_quit (subshell_quit);
1399             }
1400         }
1401     }
1402     subshell_handle_cons_saver ();
1403 
1404     /* If we got here, some other child exited; ignore it */
1405 }
1406 
1407 /* --------------------------------------------------------------------------------------------- */

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