summaryrefslogtreecommitdiffstats
path: root/src/subshell
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:22:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:22:03 +0000
commitffccd5b2b05243e7976db80f90f453dccfae9886 (patch)
tree39a43152d27f7390d8f7a6fb276fa6887f87c6e8 /src/subshell
parentInitial commit. (diff)
downloadmc-ffccd5b2b05243e7976db80f90f453dccfae9886.tar.xz
mc-ffccd5b2b05243e7976db80f90f453dccfae9886.zip
Adding upstream version 3:4.8.30.upstream/3%4.8.30
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/subshell')
-rw-r--r--src/subshell/Makefile.am9
-rw-r--r--src/subshell/Makefile.in741
-rw-r--r--src/subshell/common.c1863
-rw-r--r--src/subshell/internal.h29
-rw-r--r--src/subshell/proxyfunc.c113
-rw-r--r--src/subshell/subshell.h55
6 files changed, 2810 insertions, 0 deletions
diff --git a/src/subshell/Makefile.am b/src/subshell/Makefile.am
new file mode 100644
index 0000000..369cb6f
--- /dev/null
+++ b/src/subshell/Makefile.am
@@ -0,0 +1,9 @@
+noinst_LTLIBRARIES = libsubshell.la
+
+libsubshell_la_SOURCES = \
+ common.c \
+ internal.h \
+ proxyfunc.c \
+ subshell.h
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
diff --git a/src/subshell/Makefile.in b/src/subshell/Makefile.in
new file mode 100644
index 0000000..18ef165
--- /dev/null
+++ b/src/subshell/Makefile.in
@@ -0,0 +1,741 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/subshell
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libsubshell_la_LIBADD =
+am_libsubshell_la_OBJECTS = common.lo proxyfunc.lo
+libsubshell_la_OBJECTS = $(am_libsubshell_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/common.Plo ./$(DEPDIR)/proxyfunc.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libsubshell_la_SOURCES)
+DIST_SOURCES = $(libsubshell_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libsubshell.la
+libsubshell_la_SOURCES = \
+ common.c \
+ internal.h \
+ proxyfunc.c \
+ subshell.h
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/subshell/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/subshell/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libsubshell.la: $(libsubshell_la_OBJECTS) $(libsubshell_la_DEPENDENCIES) $(EXTRA_libsubshell_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libsubshell_la_OBJECTS) $(libsubshell_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proxyfunc.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/common.Plo
+ -rm -f ./$(DEPDIR)/proxyfunc.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/common.Plo
+ -rm -f ./$(DEPDIR)/proxyfunc.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/subshell/common.c b/src/subshell/common.c
new file mode 100644
index 0000000..3ea4b5f
--- /dev/null
+++ b/src/subshell/common.c
@@ -0,0 +1,1863 @@
+/*
+ Concurrent shell support for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Alexander Kriegisch <Alexander@Kriegisch.name>
+ Aliaksey Kandratsenka <alk@tut.by>
+ Andreas Mohr <and@gmx.li>
+ Andrew Borodin <aborodin@vmail.ru>
+ Andrew V. Samoilov <sav@bcs.zp.ua>
+ Chris Owen <chris@candu.co.uk>
+ Claes Nästén <me@pekdon.net>
+ Egmont Koblinger <egmont@gmail.com>
+ Enrico Weigelt, metux IT service <weigelt@metux.de>
+ Eric Roberts <ericmrobertsdeveloper@gmail.com>
+ Igor Urazov <z0rc3r@gmail.com>
+ Ilia Maslakov <il.smind@gmail.com>
+ Leonard den Ottolander <leonard@den.ottolander.nl>
+ Miguel de Icaza <miguel@novell.com>
+ Mikhail S. Pobolovets <styx.mp@gmail.com>
+ Norbert Warmuth <nwarmuth@privat.circular.de>
+ Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
+ Patrick Winnertz <winnie@debian.org>
+ Pavel Machek <pavel@suse.cz>
+ Pavel Roskin <proski@gnu.org>
+ Pavel Tsekov <ptsekov@gmx.net>
+ Roland Illig <roland.illig@gmx.de>
+ Sergei Trofimovich <slyfox@inbox.ru>
+ Slava Zanko <slavazanko@gmail.com>
+ Timur Bakeyev <mc@bat.ru>
+ Vit Rosin <vit_r@list.ru>
+
+ 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** \file subshell.c
+ * \brief Source: concurrent shell support
+ */
+
+#include <config.h>
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#else
+#include <sys/time.h>
+#include <unistd.h>
+#endif
+#include <sys/types.h>
+#include <sys/wait.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#include <termios.h>
+
+#ifdef HAVE_STROPTS_H
+#include <stropts.h> /* For I_PUSH */
+#endif /* HAVE_STROPTS_H */
+
+#ifdef HAVE_OPENPTY
+/* includes for openpty() */
+#ifdef HAVE_PTY_H
+#include <pty.h>
+#endif
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+/* <sys/types.h> is a prerequisite of <libutil.h> on FreeBSD 8.0. */
+#ifdef HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+#endif /* HAVE_OPENPTY */
+
+#include "lib/global.h"
+
+#include "lib/fileloc.h"
+#include "lib/unixcompat.h"
+#include "lib/tty/tty.h" /* LINES */
+#include "lib/tty/key.h" /* XCTRL */
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/mcconfig.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+#include "src/filemanager/layout.h" /* setup_cmdline() */
+#include "src/filemanager/command.h" /* cmdline */
+
+#include "subshell.h"
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/* State of the subshell:
+ * INACTIVE: the default state; awaiting a command
+ * ACTIVE: remain in the shell until the user hits 'subshell_switch_key'
+ * RUNNING_COMMAND: return to MC when the current command finishes */
+enum subshell_state_enum subshell_state;
+
+/* Holds the latest prompt captured from the subshell */
+GString *subshell_prompt = NULL;
+
+/* Subshell: if set, then the prompt was not saved on CONSOLE_SAVE */
+/* We need to paint it after CONSOLE_RESTORE, see: load_prompt */
+gboolean update_subshell_prompt = FALSE;
+
+/* If set, then a command has just finished executing, and we need */
+/* to be on the lookout for a new prompt string from the subshell. */
+gboolean should_read_new_subshell_prompt;
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifndef WEXITSTATUS
+#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
+#endif
+
+#ifndef WIFEXITED
+#define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
+#endif
+
+/* Initial length of the buffer for the subshell's prompt */
+#define INITIAL_PROMPT_SIZE 10
+
+/* Used by the child process to indicate failure to start the subshell */
+#define FORK_FAILURE 69 /* Arbitrary */
+
+/* Length of the buffer for all I/O with the subshell */
+#define PTY_BUFFER_SIZE BUF_MEDIUM /* Arbitrary; but keep it >= 80 */
+
+/*** file scope type declarations ****************************************************************/
+
+/* For pipes */
+enum
+{
+ READ = 0,
+ WRITE = 1
+};
+
+/* This is the keybinding that is sent to the shell, to make the shell send us the contents of
+ * the current command buffer. */
+#define SHELL_BUFFER_KEYBINDING "_"
+
+/* This is the keybinding that is sent to the shell, to make the shell send us the location of
+ * the cursor. */
+#define SHELL_CURSOR_KEYBINDING "+"
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* tcsh closes all non-standard file descriptors, so we have to use a pipe */
+static char tcsh_fifo[128];
+
+static int subshell_pty_slave = -1;
+
+/* The key for switching back to MC from the subshell */
+/* *INDENT-OFF* */
+static const char subshell_switch_key = XCTRL ('o') & 255;
+/* *INDENT-ON* */
+
+/* For reading/writing on the subshell's pty */
+static char pty_buffer[PTY_BUFFER_SIZE] = "\0";
+
+/* To pass CWD info from the subshell to MC */
+static int subshell_pipe[2];
+
+/* To pass command buffer info from the subshell to MC */
+static int command_buffer_pipe[2];
+
+/* The subshell's process ID */
+static pid_t subshell_pid = 1;
+
+/* One extra char for final '\n' */
+static char subshell_cwd[MC_MAXPATHLEN + 1];
+
+/* Flag to indicate whether the subshell is ready for next command */
+static int subshell_ready;
+
+/* Flag to indicate if the subshell supports the persistent buffer feature. */
+static gboolean use_persistent_buffer = FALSE;
+
+/* This is the local variable where the subshell prompt is stored while we are working on it. */
+static GString *subshell_prompt_temp_buffer = NULL;
+
+/* The following two flags can be changed by the SIGCHLD handler. This is */
+/* OK, because the 'int' type is updated atomically on all known machines */
+static volatile int subshell_alive, subshell_stopped;
+
+/* We store the terminal's initial mode here so that we can configure
+ the pty similarly, and also so we can restore the real terminal to
+ sanity if we have to exit abruptly */
+static struct termios shell_mode;
+
+/* This is a transparent mode for the terminal where MC is running on */
+/* It is used when the shell is active, so that the control signals */
+/* are delivered to the shell pty */
+static struct termios raw_mode;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Write all data, even if the write() call is interrupted.
+ */
+
+static ssize_t
+write_all (int fd, const void *buf, size_t count)
+{
+ ssize_t written = 0;
+
+ while (count > 0)
+ {
+ ssize_t ret;
+
+ ret = write (fd, (const unsigned char *) buf + written, count);
+ if (ret < 0)
+ {
+ if (errno == EINTR)
+ {
+ if (tty_got_winch ())
+ tty_change_screen_size ();
+
+ continue;
+ }
+
+ return written > 0 ? written : ret;
+ }
+ count -= ret;
+ written += ret;
+ }
+ return written;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Prepare child process to running the shell and run it.
+ *
+ * Modifies the global variables (in the child process only):
+ * shell_mode
+ *
+ * Returns: never.
+ */
+
+static void
+init_subshell_child (const char *pty_name)
+{
+ char *init_file = NULL;
+ pid_t mc_sid;
+
+ (void) pty_name;
+ setsid (); /* Get a fresh terminal session */
+
+ /* Make sure that it has become our controlling terminal */
+
+ /* Redundant on Linux and probably most systems, but just in case: */
+
+#ifdef TIOCSCTTY
+ ioctl (subshell_pty_slave, TIOCSCTTY, 0);
+#endif
+
+ /* Configure its terminal modes and window size */
+
+ /* Set up the pty with the same termios flags as our own tty */
+ if (tcsetattr (subshell_pty_slave, TCSANOW, &shell_mode))
+ {
+ fprintf (stderr, "Cannot set pty terminal modes: %s\r\n", unix_error_string (errno));
+ my_exit (FORK_FAILURE);
+ }
+
+ /* Set the pty's size (80x25 by default on Linux) according to the */
+ /* size of the real terminal as calculated by ncurses, if possible */
+ tty_resize (subshell_pty_slave);
+
+ /* Set up the subshell's environment and init file name */
+
+ /* It simplifies things to change to our home directory here, */
+ /* and the user's startup file may do a 'cd' command anyway */
+ {
+ int ret;
+
+ ret = chdir (mc_config_get_home_dir ()); /* FIXME? What about when we re-run the subshell? */
+ (void) ret;
+ }
+
+ /* Set MC_SID to prevent running one mc from another */
+ mc_sid = getsid (0);
+ if (mc_sid != -1)
+ {
+ char sid_str[BUF_SMALL];
+
+ g_snprintf (sid_str, sizeof (sid_str), "MC_SID=%ld", (long) mc_sid);
+ putenv (g_strdup (sid_str));
+ }
+
+ switch (mc_global.shell->type)
+ {
+ case SHELL_BASH:
+ /* Do we have a custom init file ~/.local/share/mc/bashrc? */
+ init_file = mc_config_get_full_path (MC_BASHRC_FILE);
+
+ /* Otherwise use ~/.bashrc */
+ if (!exist_file (init_file))
+ {
+ g_free (init_file);
+ init_file = g_strdup (".bashrc");
+ }
+
+ /* Make MC's special commands not show up in bash's history and also suppress
+ * consecutive identical commands*/
+ putenv ((char *) "HISTCONTROL=ignoreboth");
+
+ /* Allow alternative readline settings for MC */
+ {
+ char *input_file;
+
+ input_file = mc_config_get_full_path (MC_INPUTRC_FILE);
+ if (exist_file (input_file))
+ g_setenv ("INPUTRC", input_file, TRUE);
+ g_free (input_file);
+ }
+
+ break;
+
+ 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 (MC_ASHRC_FILE);
+
+ /* Otherwise use ~/.profile */
+ if (!exist_file (init_file))
+ {
+ g_free (init_file);
+ init_file = g_strdup (".profile");
+ }
+
+ /* Put init file to ENV variable used by ash */
+ g_setenv ("ENV", init_file, TRUE);
+
+ break;
+
+ case SHELL_ZSH:
+ /* ZDOTDIR environment variable is the only way to point zsh
+ * to an other rc file than the default. */
+
+ /* Don't overwrite $ZDOTDIR */
+ if (g_getenv ("ZDOTDIR") != NULL)
+ {
+ /* Do we have a custom init file ~/.local/share/mc/.zshrc?
+ * Otherwise use standard ~/.zshrc */
+ init_file = mc_config_get_full_path (MC_ZSHRC_FILE);
+ if (exist_file (init_file))
+ {
+ /* Set ZDOTDIR to ~/.local/share/mc */
+ g_setenv ("ZDOTDIR", mc_config_get_data_path (), TRUE);
+ }
+ }
+ break;
+
+ /* TODO: Find a way to pass initfile to TCSH and FISH */
+ case SHELL_TCSH:
+ case SHELL_FISH:
+ break;
+
+ default:
+ fprintf (stderr, __FILE__ ": unimplemented subshell type %u\r\n", mc_global.shell->type);
+ my_exit (FORK_FAILURE);
+ }
+
+ /* Attach all our standard file descriptors to the pty */
+
+ /* This is done just before the exec, because stderr must still */
+ /* be connected to the real tty during the above error messages; */
+ /* otherwise the user will never see them. */
+
+ dup2 (subshell_pty_slave, STDIN_FILENO);
+ dup2 (subshell_pty_slave, STDOUT_FILENO);
+ dup2 (subshell_pty_slave, STDERR_FILENO);
+
+ close (subshell_pipe[READ]);
+
+ if (use_persistent_buffer)
+ close (command_buffer_pipe[READ]);
+
+ close (subshell_pty_slave); /* These may be FD_CLOEXEC, but just in case... */
+ /* Close master side of pty. This is important; apart from */
+ /* freeing up the descriptor for use in the subshell, it also */
+ /* means that when MC exits, the subshell will get a SIGHUP and */
+ /* exit too, because there will be no more descriptors pointing */
+ /* at the master side of the pty and so it will disappear. */
+ close (mc_global.tty.subshell_pty);
+
+ /* Execute the subshell at last */
+
+ switch (mc_global.shell->type)
+ {
+ case SHELL_BASH:
+ execl (mc_global.shell->path, "bash", "-rcfile", init_file, (char *) NULL);
+ break;
+
+ 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 SHELL_ASH_BUSYBOX:
+ case SHELL_DASH:
+ case SHELL_TCSH:
+ case SHELL_FISH:
+ execl (mc_global.shell->path, mc_global.shell->path, (char *) NULL);
+ break;
+
+ default:
+ break;
+ }
+
+ /* If we get this far, everything failed miserably */
+ g_free (init_file);
+ my_exit (FORK_FAILURE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+init_raw_mode (void)
+{
+ static gboolean initialized = FALSE;
+
+ /* MC calls tty_reset_shell_mode() in pre_exec() to set the real tty to its */
+ /* original settings. However, here we need to make this tty very raw, */
+ /* so that all keyboard signals, XON/XOFF, etc. will get through to the */
+ /* pty. So, instead of changing the code for execute(), pre_exec(), */
+ /* etc, we just set up the modes we need here, before each command. */
+
+ if (!initialized) /* First time: initialise 'raw_mode' */
+ {
+ tcgetattr (STDOUT_FILENO, &raw_mode);
+ raw_mode.c_lflag &= ~ICANON; /* Disable line-editing chars, etc. */
+ raw_mode.c_lflag &= ~ISIG; /* Disable intr, quit & suspend chars */
+ raw_mode.c_lflag &= ~ECHO; /* Disable input echoing */
+ raw_mode.c_iflag &= ~IXON; /* Pass ^S/^Q to subshell undisturbed */
+ raw_mode.c_iflag &= ~ICRNL; /* Don't translate CRs into LFs */
+ raw_mode.c_oflag &= ~OPOST; /* Don't postprocess output */
+ raw_mode.c_cc[VTIME] = 0; /* IE: wait forever, and return as */
+ raw_mode.c_cc[VMIN] = 1; /* soon as a character is available */
+ initialized = TRUE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Wait until the subshell dies or stops. If it stops, make it resume.
+ * Possibly modifies the globals 'subshell_alive' and 'subshell_stopped'
+ */
+
+static void
+synchronize (void)
+{
+ sigset_t sigchld_mask, old_mask;
+
+ sigemptyset (&sigchld_mask);
+ sigaddset (&sigchld_mask, SIGCHLD);
+ sigprocmask (SIG_BLOCK, &sigchld_mask, &old_mask);
+
+ /*
+ * SIGCHLD should not be blocked, but we unblock it just in case.
+ * This is known to be useful for cygwin 1.3.12 and older.
+ */
+ sigdelset (&old_mask, SIGCHLD);
+
+ /* Wait until the subshell has stopped */
+ while (subshell_alive && !subshell_stopped)
+ sigsuspend (&old_mask);
+
+ if (subshell_state != ACTIVE)
+ {
+ /* Discard all remaining data from stdin to the subshell */
+ tcflush (subshell_pty_slave, TCIFLUSH);
+ }
+
+ subshell_stopped = FALSE;
+ kill (subshell_pid, SIGCONT);
+
+ sigprocmask (SIG_SETMASK, &old_mask, NULL);
+ /* We can't do any better without modifying the shell(s) */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Get the contents of the current subshell command line buffer, and */
+/* transfer the contents to the panel command prompt. */
+
+static gboolean
+read_command_line_buffer (gboolean test_mode)
+{
+ char subshell_command_buffer[BUF_LARGE];
+ char subshell_cursor_buffer[BUF_SMALL];
+
+ fd_set read_set;
+ int i;
+ ssize_t bytes;
+ struct timeval subshell_prompt_timer = { 0, 0 };
+ int command_buffer_length;
+ int command_buffer_char_length;
+ int bash_version;
+ int cursor_position;
+ int maxfdp;
+ int rc;
+
+ if (!use_persistent_buffer)
+ return TRUE;
+
+ FD_ZERO (&read_set);
+ FD_SET (command_buffer_pipe[READ], &read_set);
+ maxfdp = command_buffer_pipe[READ];
+
+ /* First, flush the command buffer pipe. This pipe shouldn't be written
+ * to under normal circumstances, but if it somehow does get written
+ * to, we need to make sure to discard whatever data is there before
+ * we try to use it. */
+ while ((rc = select (maxfdp + 1, &read_set, NULL, NULL, &subshell_prompt_timer)) != 0)
+ {
+ if (rc == -1)
+ {
+ if (errno == EINTR)
+ continue;
+
+ return FALSE;
+ }
+
+ if (rc == 1)
+ {
+ bytes = read (command_buffer_pipe[READ], subshell_command_buffer,
+ sizeof (subshell_command_buffer));
+ (void) bytes;
+ }
+ }
+
+ /* get contents of command line buffer */
+ write_all (mc_global.tty.subshell_pty, ESC_STR SHELL_BUFFER_KEYBINDING,
+ sizeof (ESC_STR SHELL_CURSOR_KEYBINDING) - 1);
+
+ subshell_prompt_timer.tv_sec = 1;
+ FD_ZERO (&read_set);
+ FD_SET (command_buffer_pipe[READ], &read_set);
+
+ while ((rc = select (maxfdp + 1, &read_set, NULL, NULL, &subshell_prompt_timer)) != 1)
+ {
+ if (rc == -1)
+ {
+ if (errno == EINTR)
+ continue;
+
+ return FALSE;
+ }
+
+ if (rc == 0)
+ return FALSE;
+ }
+
+ bytes =
+ read (command_buffer_pipe[READ], subshell_command_buffer, sizeof (subshell_command_buffer));
+ if (bytes == 0 || bytes == sizeof (subshell_command_buffer))
+ return FALSE;
+
+ command_buffer_char_length = bytes - 1;
+ subshell_command_buffer[command_buffer_char_length] = '\0';
+ command_buffer_length = str_length (subshell_command_buffer);
+
+ /* get cursor position */
+ write_all (mc_global.tty.subshell_pty, ESC_STR SHELL_CURSOR_KEYBINDING,
+ sizeof (ESC_STR SHELL_CURSOR_KEYBINDING) - 1);
+
+ subshell_prompt_timer.tv_sec = 1;
+ subshell_prompt_timer.tv_usec = 0;
+ FD_ZERO (&read_set);
+ FD_SET (command_buffer_pipe[READ], &read_set);
+
+ while ((rc = select (maxfdp + 1, &read_set, NULL, NULL, &subshell_prompt_timer)) != 1)
+ {
+ if (rc == -1)
+ {
+ if (errno == EINTR)
+ continue;
+
+ return FALSE;
+ }
+
+ if (rc == 0)
+ return FALSE;
+ }
+
+ bytes =
+ read (command_buffer_pipe[READ], subshell_cursor_buffer, sizeof (subshell_cursor_buffer));
+ if (bytes == 0)
+ return FALSE;
+
+ subshell_cursor_buffer[bytes - 1] = '\0';
+ if (mc_global.shell->type == SHELL_BASH)
+ {
+ if (sscanf (subshell_cursor_buffer, "%d:%d", &bash_version, &cursor_position) != 2)
+ return FALSE;
+ }
+ else
+ {
+ if (sscanf (subshell_cursor_buffer, "%d", &cursor_position) != 1)
+ return FALSE;
+ bash_version = 1000;
+ }
+
+ if (test_mode)
+ return TRUE;
+
+ /* Substitute non-text characters in the command buffer, such as tab, or newline, as this
+ * could cause problems. */
+ for (i = 0; i < command_buffer_char_length; i++)
+ if ((unsigned char) subshell_command_buffer[i] < 32
+ || (unsigned char) subshell_command_buffer[i] == 127)
+ subshell_command_buffer[i] = ' ';
+
+ input_assign_text (cmdline, "");
+ input_insert (cmdline, subshell_command_buffer, FALSE);
+
+ if (bash_version < 5) /* implies SHELL_BASH */
+ {
+ /* We need to do this because bash < v5 gives the cursor position in a utf-8 string based
+ * on the location in bytes, not in unicode characters. */
+ char *curr, *stop;
+
+ curr = subshell_command_buffer;
+ stop = curr + cursor_position;
+
+ for (cursor_position = 0; curr < stop; cursor_position++)
+ str_next_char_safe (&curr);
+ }
+ if (cursor_position > command_buffer_length)
+ cursor_position = command_buffer_length;
+ cmdline->point = cursor_position;
+ /* We send any remaining data to STDOUT before we finish. */
+ flush_subshell (0, VISIBLY);
+
+ /* Now we erase the current contents of the command line buffer */
+ if (mc_global.shell->type != SHELL_ZSH)
+ {
+ /* In zsh, we can just press c-u to clear the line, without needing to go to the end of
+ * the line first first. In all other shells, we must go to the end of the line first. */
+
+ /* If we are not at the end of the line, we go to the end. */
+ if (cursor_position != command_buffer_length)
+ {
+ write_all (mc_global.tty.subshell_pty, "\005", 1);
+ if (flush_subshell (1, VISIBLY) != 1)
+ return FALSE;
+ }
+ }
+
+ if (command_buffer_length > 0)
+ {
+ /* Now we clear the line. */
+ write_all (mc_global.tty.subshell_pty, "\025", 1);
+ if (flush_subshell (1, VISIBLY) != 1)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+clear_subshell_prompt_string (void)
+{
+ if (subshell_prompt_temp_buffer != NULL)
+ g_string_set_size (subshell_prompt_temp_buffer, 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+parse_subshell_prompt_string (const char *buffer, int bytes)
+{
+ int i;
+
+ if (mc_global.mc_run_mode != MC_RUN_FULL)
+ return;
+
+ /* First time through */
+ if (subshell_prompt == NULL)
+ subshell_prompt = g_string_sized_new (INITIAL_PROMPT_SIZE);
+ if (subshell_prompt_temp_buffer == NULL)
+ subshell_prompt_temp_buffer = g_string_sized_new (INITIAL_PROMPT_SIZE);
+
+ /* Extract the prompt from the shell output */
+ for (i = 0; i < bytes; i++)
+ if (buffer[i] == '\n' || buffer[i] == '\r')
+ g_string_set_size (subshell_prompt_temp_buffer, 0);
+ else if (buffer[i] != '\0')
+ g_string_append_c (subshell_prompt_temp_buffer, buffer[i]);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+set_prompt_string (void)
+{
+ if (mc_global.mc_run_mode != MC_RUN_FULL)
+ return;
+
+ if (subshell_prompt_temp_buffer->len != 0)
+ mc_g_string_copy (subshell_prompt, subshell_prompt_temp_buffer);
+
+ setup_cmdline ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Feed the subshell our keyboard input until it says it's finished */
+
+static gboolean
+feed_subshell (int how, gboolean fail_on_error)
+{
+ fd_set read_set; /* For 'select' */
+ int bytes; /* For the return value from 'read' */
+ int i; /* Loop counter */
+
+ struct timeval wtime; /* Maximum time we wait for the subshell */
+ struct timeval *wptr;
+
+ should_read_new_subshell_prompt = FALSE;
+
+ /* have more than enough time to run subshell:
+ wait up to 10 second if fail_on_error, forever otherwise */
+ wtime.tv_sec = 10;
+ wtime.tv_usec = 0;
+ wptr = fail_on_error ? &wtime : NULL;
+
+ while (TRUE)
+ {
+ int maxfdp;
+
+ if (!subshell_alive)
+ return FALSE;
+
+ /* Prepare the file-descriptor set and call 'select' */
+
+ FD_ZERO (&read_set);
+ FD_SET (mc_global.tty.subshell_pty, &read_set);
+ FD_SET (subshell_pipe[READ], &read_set);
+ maxfdp = MAX (mc_global.tty.subshell_pty, subshell_pipe[READ]);
+ if (how == VISIBLY)
+ {
+ FD_SET (STDIN_FILENO, &read_set);
+ maxfdp = MAX (maxfdp, STDIN_FILENO);
+ }
+
+ if (select (maxfdp + 1, &read_set, NULL, NULL, wptr) == -1)
+ {
+ /* Despite using SA_RESTART, we still have to check for this */
+ if (errno == EINTR)
+ {
+ if (tty_got_winch ())
+ tty_change_screen_size ();
+
+ continue; /* try all over again */
+ }
+ tcsetattr (STDOUT_FILENO, TCSANOW, &shell_mode);
+ fprintf (stderr, "select (FD_SETSIZE, &read_set...): %s\r\n",
+ unix_error_string (errno));
+ exit (EXIT_FAILURE);
+ }
+
+ if (FD_ISSET (mc_global.tty.subshell_pty, &read_set))
+ /* Read from the subshell, write to stdout */
+
+ /* This loop improves performance by reducing context switches
+ by a factor of 20 or so... unfortunately, it also hangs MC
+ randomly, because of an apparent Linux bug. Investigate. */
+ /* for (i=0; i<5; ++i) * FIXME -- experimental */
+ {
+ bytes = read (mc_global.tty.subshell_pty, pty_buffer, sizeof (pty_buffer));
+
+ /* The subshell has died */
+ if (bytes == -1 && errno == EIO && !subshell_alive)
+ return FALSE;
+
+ if (bytes <= 0)
+ {
+#ifdef PTY_ZEROREAD
+ /* On IBM i, read(1) can return 0 for a non-closed fd */
+ continue;
+#else
+ tcsetattr (STDOUT_FILENO, TCSANOW, &shell_mode);
+ fprintf (stderr, "read (subshell_pty...): %s\r\n", unix_error_string (errno));
+ exit (EXIT_FAILURE);
+#endif
+ }
+
+ if (how == VISIBLY)
+ write_all (STDOUT_FILENO, pty_buffer, bytes);
+
+ if (should_read_new_subshell_prompt)
+ parse_subshell_prompt_string (pty_buffer, bytes);
+ }
+
+ else if (FD_ISSET (subshell_pipe[READ], &read_set))
+ /* Read the subshell's CWD and capture its prompt */
+ {
+ bytes = read (subshell_pipe[READ], subshell_cwd, sizeof (subshell_cwd));
+ if (bytes <= 0)
+ {
+ tcsetattr (STDOUT_FILENO, TCSANOW, &shell_mode);
+ fprintf (stderr, "read (subshell_pipe[READ]...): %s\r\n",
+ unix_error_string (errno));
+ exit (EXIT_FAILURE);
+ }
+
+ subshell_cwd[bytes - 1] = '\0'; /* Squash the final '\n' */
+
+ synchronize ();
+
+ clear_subshell_prompt_string ();
+ should_read_new_subshell_prompt = TRUE;
+ subshell_ready = TRUE;
+ if (subshell_state == RUNNING_COMMAND)
+ {
+ subshell_state = INACTIVE;
+ return TRUE;
+ }
+ }
+
+ else if (FD_ISSET (STDIN_FILENO, &read_set))
+ /* Read from stdin, write to the subshell */
+ {
+ should_read_new_subshell_prompt = FALSE;
+ bytes = read (STDIN_FILENO, pty_buffer, sizeof (pty_buffer));
+ if (bytes <= 0)
+ {
+ tcsetattr (STDOUT_FILENO, TCSANOW, &shell_mode);
+ fprintf (stderr,
+ "read (STDIN_FILENO, pty_buffer...): %s\r\n", unix_error_string (errno));
+ exit (EXIT_FAILURE);
+ }
+
+ for (i = 0; i < bytes; ++i)
+ if (pty_buffer[i] == subshell_switch_key)
+ {
+ write_all (mc_global.tty.subshell_pty, pty_buffer, i);
+
+ if (subshell_ready)
+ {
+ subshell_state = INACTIVE;
+ set_prompt_string ();
+ if (subshell_ready && !read_command_line_buffer (FALSE))
+ {
+ /* If we got here, some unforeseen error must have occurred. */
+ if (mc_global.shell->type != SHELL_FISH)
+ {
+ write_all (mc_global.tty.subshell_pty, "\003", 1);
+ subshell_state = RUNNING_COMMAND;
+ if (feed_subshell (QUIETLY, TRUE))
+ if (read_command_line_buffer (FALSE))
+ return TRUE;
+ }
+ subshell_state = ACTIVE;
+ flush_subshell (0, VISIBLY);
+ input_assign_text (cmdline, "");
+ }
+ }
+
+ return TRUE;
+ }
+
+ write_all (mc_global.tty.subshell_pty, pty_buffer, bytes);
+
+ if (pty_buffer[bytes - 1] == '\n' || pty_buffer[bytes - 1] == '\r')
+ {
+ /* We should only clear the command line if we are using a shell that works
+ * with persistent command buffer, otherwise we get awkward results. */
+ if (use_persistent_buffer)
+ input_assign_text (cmdline, "");
+ subshell_ready = FALSE;
+ }
+ }
+ else
+ return FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* pty opening functions */
+
+#ifndef HAVE_OPENPTY
+
+#ifdef HAVE_GRANTPT
+
+/* System V version of pty_open_master */
+
+static int
+pty_open_master (char *pty_name)
+{
+ char *slave_name;
+ int pty_master;
+
+#ifdef HAVE_POSIX_OPENPT
+ pty_master = posix_openpt (O_RDWR);
+#elif defined HAVE_GETPT
+ /* getpt () is a GNU extension (glibc 2.1.x) */
+ pty_master = getpt ();
+#elif defined IS_AIX
+ strcpy (pty_name, "/dev/ptc");
+ pty_master = open (pty_name, O_RDWR);
+#else
+ strcpy (pty_name, "/dev/ptmx");
+ pty_master = open (pty_name, O_RDWR);
+#endif
+
+ if (pty_master == -1)
+ return -1;
+
+ if (grantpt (pty_master) == -1 /* Grant access to slave */
+ || unlockpt (pty_master) == -1 /* Clear slave's lock flag */
+ || !(slave_name = ptsname (pty_master))) /* Get slave's name */
+ {
+ close (pty_master);
+ return -1;
+ }
+ strcpy (pty_name, slave_name);
+ return pty_master;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** System V version of pty_open_slave */
+
+static int
+pty_open_slave (const char *pty_name)
+{
+ int pty_slave;
+
+ pty_slave = open (pty_name, O_RDWR);
+ if (pty_slave == -1)
+ {
+ fprintf (stderr, "open (%s, O_RDWR): %s\r\n", pty_name, unix_error_string (errno));
+ return -1;
+ }
+#if !defined(__osf__) && !defined(__linux__)
+#if defined (I_FIND) && defined (I_PUSH)
+ if (ioctl (pty_slave, I_FIND, "ptem") == 0)
+ if (ioctl (pty_slave, I_PUSH, "ptem") == -1)
+ {
+ fprintf (stderr, "ioctl (%d, I_PUSH, \"ptem\") failed: %s\r\n",
+ pty_slave, unix_error_string (errno));
+ close (pty_slave);
+ return -1;
+ }
+
+ if (ioctl (pty_slave, I_FIND, "ldterm") == 0)
+ if (ioctl (pty_slave, I_PUSH, "ldterm") == -1)
+ {
+ fprintf (stderr,
+ "ioctl (%d, I_PUSH, \"ldterm\") failed: %s\r\n",
+ pty_slave, unix_error_string (errno));
+ close (pty_slave);
+ return -1;
+ }
+#if !defined(sgi) && !defined(__sgi)
+ if (ioctl (pty_slave, I_FIND, "ttcompat") == 0)
+ if (ioctl (pty_slave, I_PUSH, "ttcompat") == -1)
+ {
+ fprintf (stderr,
+ "ioctl (%d, I_PUSH, \"ttcompat\") failed: %s\r\n",
+ pty_slave, unix_error_string (errno));
+ close (pty_slave);
+ return -1;
+ }
+#endif /* sgi || __sgi */
+#endif /* I_FIND && I_PUSH */
+#endif /* __osf__ || __linux__ */
+
+ fcntl (pty_slave, F_SETFD, FD_CLOEXEC);
+ return pty_slave;
+}
+
+#else /* !HAVE_GRANTPT */
+
+/* --------------------------------------------------------------------------------------------- */
+/** BSD version of pty_open_master */
+static int
+pty_open_master (char *pty_name)
+{
+ int pty_master;
+ const char *ptr1, *ptr2;
+
+ strcpy (pty_name, "/dev/ptyXX");
+ for (ptr1 = "pqrstuvwxyzPQRST"; *ptr1; ++ptr1)
+ {
+ pty_name[8] = *ptr1;
+ for (ptr2 = "0123456789abcdef"; *ptr2 != '\0'; ++ptr2)
+ {
+ pty_name[9] = *ptr2;
+
+ /* Try to open master */
+ pty_master = open (pty_name, O_RDWR);
+ if (pty_master == -1)
+ {
+ if (errno == ENOENT) /* Different from EIO */
+ return -1; /* Out of pty devices */
+ continue; /* Try next pty device */
+ }
+ pty_name[5] = 't'; /* Change "pty" to "tty" */
+ if (access (pty_name, 6) != 0)
+ {
+ close (pty_master);
+ pty_name[5] = 'p';
+ continue;
+ }
+ return pty_master;
+ }
+ }
+ return -1; /* Ran out of pty devices */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** BSD version of pty_open_slave */
+
+static int
+pty_open_slave (const char *pty_name)
+{
+ int pty_slave;
+ struct group *group_info;
+
+ group_info = getgrnam ("tty");
+ if (group_info != NULL)
+ {
+ /* The following two calls will only succeed if we are root */
+ /* [Commented out while permissions problem is investigated] */
+ /* chown (pty_name, getuid (), group_info->gr_gid); FIXME */
+ /* chmod (pty_name, S_IRUSR | S_IWUSR | S_IWGRP); FIXME */
+ }
+ pty_slave = open (pty_name, O_RDWR);
+ if (pty_slave == -1)
+ fprintf (stderr, "open (pty_name, O_RDWR): %s\r\n", pty_name);
+ fcntl (pty_slave, F_SETFD, FD_CLOEXEC);
+ return pty_slave;
+}
+#endif /* !HAVE_GRANTPT */
+
+#endif /* !HAVE_OPENPTY */
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Set up `precmd' or equivalent for reading the subshell's CWD.
+ *
+ * Attention! Never forget that these are *one-liners* even though the concatenated
+ * substrings contain line breaks and indentation for better understanding of the
+ * shell code. It is vital that each one-liner ends with a line feed character ("\n" ).
+ *
+ * @return initialized pre-command string
+ */
+
+static void
+init_subshell_precmd (char *precmd, size_t buff_size)
+{
+ switch (mc_global.shell->type)
+ {
+ case SHELL_BASH:
+ g_snprintf (precmd, buff_size,
+ " mc_print_command_buffer () { printf \"%%s\\\\n\" \"$READLINE_LINE\" >&%d; }\n"
+ " bind -x '\"\\e" SHELL_BUFFER_KEYBINDING "\":\"mc_print_command_buffer\"'\n"
+ " bind -x '\"\\e" SHELL_CURSOR_KEYBINDING
+ "\":\"echo $BASH_VERSINFO:$READLINE_POINT >&%d\"'\n"
+ " PROMPT_COMMAND=${PROMPT_COMMAND:+$PROMPT_COMMAND\n}'pwd>&%d;kill -STOP $$'\n"
+ "PS1='\\u@\\h:\\w\\$ '\n", command_buffer_pipe[WRITE],
+ command_buffer_pipe[WRITE], subshell_pipe[WRITE]);
+ break;
+
+ 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.
+ *
+ * A: This leads to a stopped subshell (=frozen mc) if user calls "ash" command
+ * "PS1='$(pwd>&%d; kill -STOP $$)\\u@\\h:\\w\\$ '\n",
+ *
+ * B: This leads to "sh: precmd: not found" in sub-subshell if user calls "ash" command
+ * "precmd() { pwd>&%d; kill -STOP $$; }; "
+ * "PS1='$(precmd)\\u@\\h:\\w\\$ '\n",
+ *
+ * C: This works if user calls "ash" command because in sub-subshell
+ * PRECMD is undefined, thus evaluated to empty string - no damage done.
+ * Attention: BusyBox must be built with FEATURE_EDITING_FANCY_PROMPT to
+ * permit \u, \w, \h, \$ escape sequences. Unfortunately this cannot be guaranteed,
+ * especially on embedded systems where people try to save space, so let's use
+ * the dash version below. It should work on virtually all systems.
+ * "precmd() { pwd>&%d; kill -STOP $$; }; "
+ * "PRECMD=precmd; "
+ * "PS1='$(eval $PRECMD)\\u@\\h:\\w\\$ '\n",
+ */
+ 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.
+ *
+ * We want to have a fancy dynamic prompt with user@host:cwd just like in the BusyBox
+ * examples above, but because replacing the home directory part of the path by "~" is
+ * complicated, it bloats the precmd to a size > BUF_SMALL (128).
+ *
+ * The following example is a little less fancy (home directory not replaced)
+ * and shows the basic workings of our prompt for easier understanding:
+ *
+ * "precmd() { "
+ * "echo \"$USER@$(hostname -s):$PWD\"; "
+ * "pwd>&%d; "
+ * "kill -STOP $$; "
+ * "}; "
+ * "PRECMD=precmd; "
+ * "PS1='$($PRECMD)$ '\n",
+ */
+ g_snprintf (precmd, buff_size,
+ "precmd() { "
+ "if [ ! \"${PWD##$HOME}\" ]; then "
+ "MC_PWD=\"~\"; "
+ "else "
+ "[ \"${PWD##$HOME/}\" = \"$PWD\" ] && MC_PWD=\"$PWD\" || MC_PWD=\"~/${PWD##$HOME/}\"; "
+ "fi; "
+ "echo \"$USER@$(hostname -s):$MC_PWD\"; "
+ "pwd>&%d; "
+ "kill -STOP $$; "
+ "}; " "PRECMD=precmd; " "PS1='$($PRECMD)$ '\n", subshell_pipe[WRITE]);
+ break;
+
+ case SHELL_ZSH:
+ g_snprintf (precmd, buff_size,
+ " mc_print_command_buffer () { printf \"%%s\\\\n\" \"$BUFFER\" >&%d; }\n"
+ " zle -N mc_print_command_buffer\n"
+ " bindkey '^[" SHELL_BUFFER_KEYBINDING "' mc_print_command_buffer\n"
+ " mc_print_cursor_position () { echo $CURSOR >&%d}\n"
+ " zle -N mc_print_cursor_position\n"
+ " bindkey '^[" SHELL_CURSOR_KEYBINDING "' mc_print_cursor_position\n"
+ " _mc_precmd(){ pwd>&%d;kill -STOP $$ }; precmd_functions+=(_mc_precmd)\n"
+ "PS1='%%n@%%m:%%~%%# '\n",
+ command_buffer_pipe[WRITE], command_buffer_pipe[WRITE], subshell_pipe[WRITE]);
+ break;
+
+ case SHELL_TCSH:
+ g_snprintf (precmd, buff_size,
+ "set echo_style=both; "
+ "set prompt='%%n@%%m:%%~%%# '; "
+ "alias precmd 'echo -n;echo $cwd:q >>%s; kill -STOP $$'\n", tcsh_fifo);
+ break;
+ case SHELL_FISH:
+ g_snprintf (precmd, buff_size,
+ " bind \\e" SHELL_BUFFER_KEYBINDING " 'commandline >&%d';"
+ "bind \\e" SHELL_CURSOR_KEYBINDING " 'commandline -C >&%d';"
+ "if not functions -q fish_prompt_mc;"
+ "functions -e fish_right_prompt;"
+ "functions -c fish_prompt fish_prompt_mc; end;"
+ "function fish_prompt;"
+ "echo \"$PWD\">&%d; fish_prompt_mc; kill -STOP %%self; end\n",
+ command_buffer_pipe[WRITE], command_buffer_pipe[WRITE], subshell_pipe[WRITE]);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Carefully quote directory name to allow entering any directory safely,
+ * no matter what weird characters it may contain in its name.
+ * NOTE: Treat directory name an untrusted data, don't allow it to cause
+ * executing any commands in the shell. Escape all control characters.
+ * Use following technique:
+ *
+ * printf(1) with format string containing a single conversion specifier,
+ * "b", and an argument which contains a copy of the string passed to
+ * subshell_name_quote() with all characters, except digits and letters,
+ * replaced by the backslash-escape sequence \0nnn, where "nnn" is the
+ * numeric value of the character converted to octal number.
+ *
+ * cd "`printf '%b' 'ABC\0nnnDEF\0nnnXYZ'`"
+ *
+ * N.B.: Use single quotes for conversion specifier to work around
+ * tcsh 6.20+ parser breakage, see ticket #3852 for the details.
+ */
+
+static GString *
+subshell_name_quote (const char *s)
+{
+ GString *ret;
+ const char *su, *n;
+ const char *quote_cmd_start, *quote_cmd_end;
+
+ if (mc_global.shell->type == SHELL_FISH)
+ {
+ quote_cmd_start = "(printf '%b' '";
+ quote_cmd_end = "')";
+ }
+ /* TODO: When BusyBox printf is fixed, get rid of this "else if", see
+ http://lists.busybox.net/pipermail/busybox/2012-March/077460.html */
+ /* else if (subshell_type == ASH_BUSYBOX)
+ {
+ quote_cmd_start = "\"`echo -en '";
+ quote_cmd_end = "'`\"";
+ } */
+ else
+ {
+ quote_cmd_start = "\"`printf '%b' '";
+ quote_cmd_end = "'`\"";
+ }
+
+ ret = g_string_sized_new (64);
+
+ /* Prevent interpreting leading '-' as a switch for 'cd' */
+ if (s[0] == '-')
+ g_string_append (ret, "./");
+
+ /* Copy the beginning of the command to the buffer */
+ g_string_append (ret, quote_cmd_start);
+
+ /*
+ * Print every character except digits and letters as a backslash-escape
+ * sequence of the form \0nnn, where "nnn" is the numeric value of the
+ * character converted to octal number.
+ */
+ for (su = s; su[0] != '\0'; su = n)
+ {
+ n = str_cget_next_char_safe (su);
+
+ if (str_isalnum (su))
+ g_string_append_len (ret, su, n - su);
+ else
+ {
+ int c;
+
+ for (c = 0; c < n - su; c++)
+ g_string_append_printf (ret, "\\0%03o", (unsigned char) su[c]);
+ }
+ }
+
+ g_string_append (ret, quote_cmd_end);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * This function checks the pipe from which we receive data about the current working directory.
+ * If there is any data waiting, we clear it.
+ */
+
+static void
+clear_cwd_pipe (void)
+{
+ fd_set read_set;
+ struct timeval wtime = { 0, 0 };
+ int maxfdp;
+
+ FD_ZERO (&read_set);
+ FD_SET (subshell_pipe[READ], &read_set);
+ maxfdp = subshell_pipe[READ];
+
+ if (select (maxfdp + 1, &read_set, NULL, NULL, &wtime) > 0
+ && FD_ISSET (subshell_pipe[READ], &read_set))
+ {
+ if (read (subshell_pipe[READ], subshell_cwd, sizeof (subshell_cwd)) <= 0)
+ {
+ tcsetattr (STDOUT_FILENO, TCSANOW, &shell_mode);
+ fprintf (stderr, "read (subshell_pipe[READ]...): %s\r\n", unix_error_string (errno));
+ exit (EXIT_FAILURE);
+ }
+
+ synchronize ();
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Fork the subshell, and set up many, many things.
+ *
+ * Possibly modifies the global variables:
+ * subshell_type, subshell_alive, subshell_stopped, subshell_pid
+ * mc_global.tty.use_subshell - Is set to FALSE if we can't run the subshell
+ * quit - Can be set to SUBSHELL_EXIT by the SIGCHLD handler
+ */
+
+void
+init_subshell (void)
+{
+ /* This must be remembered across calls to init_subshell() */
+ static char pty_name[BUF_SMALL];
+ /* Must be considerably longer than BUF_SMALL (128) to support fancy shell prompts */
+ char precmd[BUF_MEDIUM];
+
+ /* Take the current (hopefully pristine) tty mode and make */
+ /* a raw mode based on it now, before we do anything else with it */
+ init_raw_mode ();
+
+ if (mc_global.tty.subshell_pty == 0)
+ { /* First time through */
+ if (mc_global.shell->type == SHELL_NONE)
+ return;
+
+ /* Open a pty for talking to the subshell */
+
+ /* FIXME: We may need to open a fresh pty each time on SVR4 */
+
+#ifdef HAVE_OPENPTY
+ if (openpty (&mc_global.tty.subshell_pty, &subshell_pty_slave, NULL, NULL, NULL))
+ {
+ fprintf (stderr, "Cannot open master and slave sides of pty: %s\n",
+ unix_error_string (errno));
+ mc_global.tty.use_subshell = FALSE;
+ return;
+ }
+#else
+ mc_global.tty.subshell_pty = pty_open_master (pty_name);
+ if (mc_global.tty.subshell_pty == -1)
+ {
+ fprintf (stderr, "Cannot open master side of pty: %s\r\n", unix_error_string (errno));
+ mc_global.tty.use_subshell = FALSE;
+ return;
+ }
+ subshell_pty_slave = pty_open_slave (pty_name);
+ if (subshell_pty_slave == -1)
+ {
+ fprintf (stderr, "Cannot open slave side of pty %s: %s\r\n",
+ pty_name, unix_error_string (errno));
+ mc_global.tty.use_subshell = FALSE;
+ return;
+ }
+#endif /* HAVE_OPENPTY */
+
+ /* Create a pipe for receiving the subshell's CWD */
+
+ if (mc_global.shell->type == SHELL_TCSH)
+ {
+ g_snprintf (tcsh_fifo, sizeof (tcsh_fifo), "%s/mc.pipe.%d",
+ mc_tmpdir (), (int) getpid ());
+ if (mkfifo (tcsh_fifo, 0600) == -1)
+ {
+ fprintf (stderr, "mkfifo(%s) failed: %s\r\n", tcsh_fifo, unix_error_string (errno));
+ mc_global.tty.use_subshell = FALSE;
+ return;
+ }
+
+ /* Opening the FIFO as O_RDONLY or O_WRONLY causes deadlock */
+
+ if ((subshell_pipe[READ] = open (tcsh_fifo, O_RDWR)) == -1
+ || (subshell_pipe[WRITE] = open (tcsh_fifo, O_RDWR)) == -1)
+ {
+ fprintf (stderr, _("Cannot open named pipe %s\n"), tcsh_fifo);
+ perror (__FILE__ ": open");
+ mc_global.tty.use_subshell = FALSE;
+ return;
+ }
+ }
+ else if (pipe (subshell_pipe) != 0) /* subshell_type is BASH, ASH_BUSYBOX, DASH or ZSH */
+ {
+ perror (__FILE__ ": couldn't create pipe");
+ mc_global.tty.use_subshell = FALSE;
+ return;
+ }
+
+ if (mc_global.mc_run_mode == MC_RUN_FULL &&
+ (mc_global.shell->type == SHELL_BASH || mc_global.shell->type == SHELL_ZSH
+ || mc_global.shell->type == SHELL_FISH))
+ use_persistent_buffer = TRUE;
+ if (use_persistent_buffer && pipe (command_buffer_pipe) != 0)
+ {
+ perror (__FILE__ ": couldn't create pipe");
+ mc_global.tty.use_subshell = FALSE;
+ return;
+ }
+ }
+
+ /* Fork the subshell */
+
+ subshell_alive = TRUE;
+ subshell_stopped = FALSE;
+ subshell_pid = fork ();
+
+ if (subshell_pid == -1)
+ {
+ fprintf (stderr, "Cannot spawn the subshell process: %s\r\n", unix_error_string (errno));
+ /* We exit here because, if the process table is full, the */
+ /* other method of running user commands won't work either */
+ exit (EXIT_FAILURE);
+ }
+
+ if (subshell_pid == 0)
+ {
+ /* We are in the child process */
+ init_subshell_child (pty_name);
+ }
+
+ init_subshell_precmd (precmd, BUF_MEDIUM);
+
+ write_all (mc_global.tty.subshell_pty, precmd, strlen (precmd));
+
+ /* Wait until the subshell has started up and processed the command */
+
+ subshell_state = RUNNING_COMMAND;
+ tty_enable_interrupt_key ();
+ if (!feed_subshell (QUIETLY, TRUE))
+ mc_global.tty.use_subshell = FALSE;
+ tty_disable_interrupt_key ();
+ if (!subshell_alive)
+ mc_global.tty.use_subshell = FALSE; /* Subshell died instantly, so don't use it */
+
+ /* Try out the persistent command buffer feature. If it doesn't work the first time, we
+ * assume there must be something wrong with the shell, and we turn persistent buffer off
+ * for good. This will save the user the trouble of having to wait for the persistent
+ * buffer function to time out every time they try to close the subshell. */
+ if (use_persistent_buffer && !read_command_line_buffer (TRUE))
+ use_persistent_buffer = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+invoke_subshell (const char *command, int how, vfs_path_t ** new_dir_vpath)
+{
+ /* Make the MC terminal transparent */
+ tcsetattr (STDOUT_FILENO, TCSANOW, &raw_mode);
+
+ /* Make the subshell change to MC's working directory */
+ if (new_dir_vpath != NULL)
+ do_subshell_chdir (subshell_get_cwd (), TRUE);
+
+ if (command == NULL) /* The user has done "C-o" from MC */
+ {
+ if (subshell_state == INACTIVE)
+ {
+ subshell_state = ACTIVE;
+
+ /* FIXME: possibly take out this hack; the user can re-play it by hitting C-hyphen a few times! */
+ if (subshell_ready && mc_global.mc_run_mode == MC_RUN_FULL)
+ write_all (mc_global.tty.subshell_pty, " \b", 2); /* Hack to make prompt reappear */
+
+ if (use_persistent_buffer)
+ {
+ const char *s;
+ size_t i;
+ int pos;
+
+ s = input_get_ctext (cmdline);
+
+ /* Check to make sure there are no non text characters in the command buffer,
+ * such as tab, or newline, as this could cause problems. */
+ for (i = 0; i < cmdline->buffer->len; i++)
+ if ((unsigned char) s[i] < 32 || (unsigned char) s[i] == 127)
+ g_string_overwrite_len (cmdline->buffer, i, " ", 1);
+
+ /* Write the command buffer to the subshell. */
+ write_all (mc_global.tty.subshell_pty, s, cmdline->buffer->len);
+
+ /* Put the cursor in the correct place in the subshell. */
+ pos = str_length (s) - cmdline->point;
+ for (i = 0; i < (size_t) pos; i++)
+ write_all (mc_global.tty.subshell_pty, ESC_STR "[D", 3);
+ }
+ }
+ }
+ else /* MC has passed us a user command */
+ {
+ /* Before we write to the command prompt, we need to clear whatever */
+ /* data is there, but only if we are using one of the shells that */
+ /* doesn't support keeping command buffer contents, OR if there was */
+ /* some sort of error. */
+ if (use_persistent_buffer)
+ clear_cwd_pipe ();
+ else
+ {
+ /* We don't need to call feed_subshell here if we are using fish, because of a
+ * quirk in the behavior of that particular shell. */
+ if (mc_global.shell->type != SHELL_FISH)
+ {
+ write_all (mc_global.tty.subshell_pty, "\003", 1);
+ subshell_state = RUNNING_COMMAND;
+ feed_subshell (QUIETLY, FALSE);
+ }
+ }
+
+ if (how == QUIETLY)
+ write_all (mc_global.tty.subshell_pty, " ", 1);
+ /* FIXME: if command is long (>8KB ?) we go comma */
+ write_all (mc_global.tty.subshell_pty, command, strlen (command));
+ write_all (mc_global.tty.subshell_pty, "\n", 1);
+ subshell_state = RUNNING_COMMAND;
+ subshell_ready = FALSE;
+ }
+
+ feed_subshell (how, FALSE);
+
+ if (new_dir_vpath != NULL && subshell_alive)
+ {
+ const char *pcwd;
+
+ pcwd = vfs_translate_path (vfs_path_as_str (subshell_get_cwd ()));
+ if (strcmp (subshell_cwd, pcwd) != 0)
+ *new_dir_vpath = vfs_path_from_str (subshell_cwd); /* Make MC change to the subshell's CWD */
+ }
+
+ /* Restart the subshell if it has died by SIGHUP, SIGQUIT, etc. */
+ while (!subshell_alive && subshell_get_mainloop_quit () == 0 && mc_global.tty.use_subshell)
+ init_subshell ();
+
+ return subshell_get_mainloop_quit ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+flush_subshell (int max_wait_length, int how)
+{
+ int rc = 0;
+ ssize_t bytes = 0;
+ struct timeval timeleft = { 0, 0 };
+ gboolean return_value = FALSE;
+ fd_set tmp;
+
+ timeleft.tv_sec = max_wait_length;
+ FD_ZERO (&tmp);
+ FD_SET (mc_global.tty.subshell_pty, &tmp);
+
+ while (subshell_alive
+ && (rc = select (mc_global.tty.subshell_pty + 1, &tmp, NULL, NULL, &timeleft)) != 0)
+ {
+ /* Check for 'select' errors */
+ if (rc == -1)
+ {
+ if (errno == EINTR)
+ {
+ if (tty_got_winch ())
+ tty_change_screen_size ();
+
+ continue;
+ }
+
+ fprintf (stderr, "select (FD_SETSIZE, &tmp...): %s\r\n", unix_error_string (errno));
+ exit (EXIT_FAILURE);
+ }
+
+ return_value = TRUE;
+ timeleft.tv_sec = 0;
+ timeleft.tv_usec = 0;
+
+ bytes = read (mc_global.tty.subshell_pty, pty_buffer, sizeof (pty_buffer));
+ if (how == VISIBLY)
+ write_all (STDOUT_FILENO, pty_buffer, bytes);
+ }
+
+ return return_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+read_subshell_prompt (void)
+{
+ int rc = 0;
+ ssize_t bytes = 0;
+ struct timeval timeleft = { 0, 0 };
+ gboolean got_new_prompt = FALSE;
+
+ fd_set tmp;
+ FD_ZERO (&tmp);
+ FD_SET (mc_global.tty.subshell_pty, &tmp);
+
+ while (subshell_alive
+ && (rc = select (mc_global.tty.subshell_pty + 1, &tmp, NULL, NULL, &timeleft)) != 0)
+ {
+ /* Check for 'select' errors */
+ if (rc == -1)
+ {
+ if (errno == EINTR)
+ {
+ if (tty_got_winch ())
+ tty_change_screen_size ();
+
+ continue;
+ }
+
+ fprintf (stderr, "select (FD_SETSIZE, &tmp...): %s\r\n", unix_error_string (errno));
+ exit (EXIT_FAILURE);
+ }
+
+ bytes = read (mc_global.tty.subshell_pty, pty_buffer, sizeof (pty_buffer));
+
+ parse_subshell_prompt_string (pty_buffer, bytes);
+ got_new_prompt = TRUE;
+ }
+
+ if (got_new_prompt)
+ set_prompt_string ();
+
+ return (rc != 0 || bytes != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+do_update_prompt (void)
+{
+ if (update_subshell_prompt)
+ {
+ if (subshell_prompt != NULL)
+ {
+ printf ("\r\n%s", subshell_prompt->str);
+ fflush (stdout);
+ }
+ update_subshell_prompt = FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+exit_subshell (void)
+{
+ gboolean subshell_quit = TRUE;
+
+ if (subshell_state != INACTIVE && subshell_alive)
+ subshell_quit =
+ query_dialog (_("Warning"),
+ _("The shell is still active. Quit anyway?"),
+ D_NORMAL, 2, _("&Yes"), _("&No")) == 0;
+
+ if (subshell_quit)
+ {
+ if (mc_global.shell->type == SHELL_TCSH)
+ {
+ if (unlink (tcsh_fifo) == -1)
+ fprintf (stderr, "Cannot remove named pipe %s: %s\r\n",
+ tcsh_fifo, unix_error_string (errno));
+ }
+
+ if (subshell_prompt != NULL)
+ {
+ g_string_free (subshell_prompt, TRUE);
+ subshell_prompt = NULL;
+ }
+
+ if (subshell_prompt_temp_buffer != NULL)
+ {
+ g_string_free (subshell_prompt_temp_buffer, TRUE);
+ subshell_prompt_temp_buffer = NULL;
+ }
+
+ pty_buffer[0] = '\0';
+ }
+
+ return subshell_quit;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** If it actually changed the directory it returns true */
+void
+do_subshell_chdir (const vfs_path_t * vpath, gboolean update_prompt)
+{
+ char *pcwd;
+
+ pcwd = vfs_path_to_str_flags (subshell_get_cwd (), 0, VPF_RECODE);
+
+ if (!(subshell_state == INACTIVE && strcmp (subshell_cwd, pcwd) != 0))
+ {
+ /* We have to repaint the subshell prompt if we read it from
+ * the main program. Please note that in the code after this
+ * if, the cd command that is sent will make the subshell
+ * repaint the prompt, so we don't have to paint it. */
+ if (update_prompt)
+ do_update_prompt ();
+ g_free (pcwd);
+ return;
+ }
+
+ /* If we are using a shell that doesn't support persistent command buffer, we need to clear
+ * the command prompt before we send the cd command. */
+ if (!use_persistent_buffer)
+ {
+ write_all (mc_global.tty.subshell_pty, "\003", 1);
+ subshell_state = RUNNING_COMMAND;
+ if (mc_global.shell->type != SHELL_FISH)
+ if (!feed_subshell (QUIETLY, TRUE))
+ {
+ subshell_state = ACTIVE;
+ return;
+ }
+ }
+ /* The initial space keeps this out of the command history (in bash
+ because we set "HISTCONTROL=ignorespace") */
+ write_all (mc_global.tty.subshell_pty, " cd ", 4);
+
+ if (vpath == NULL)
+ write_all (mc_global.tty.subshell_pty, "/", 1);
+ else
+ {
+ const char *translate;
+
+ translate = vfs_translate_path (vfs_path_as_str (vpath));
+ if (translate == NULL)
+ write_all (mc_global.tty.subshell_pty, ".", 1);
+ else
+ {
+ GString *temp;
+
+ temp = subshell_name_quote (translate);
+ write_all (mc_global.tty.subshell_pty, temp->str, temp->len);
+ g_string_free (temp, TRUE);
+ }
+ }
+
+ write_all (mc_global.tty.subshell_pty, "\n", 1);
+
+ subshell_state = RUNNING_COMMAND;
+ if (!feed_subshell (QUIETLY, TRUE))
+ {
+ subshell_state = ACTIVE;
+ return;
+ }
+
+ if (subshell_alive)
+ {
+ gboolean bPathNotEq;
+
+ bPathNotEq = strcmp (subshell_cwd, pcwd) != 0;
+
+ if (bPathNotEq && mc_global.shell->type == SHELL_TCSH)
+ {
+ char rp_subshell_cwd[PATH_MAX];
+ char rp_current_panel_cwd[PATH_MAX];
+ char *p_subshell_cwd, *p_current_panel_cwd;
+
+ p_subshell_cwd = mc_realpath (subshell_cwd, rp_subshell_cwd);
+ p_current_panel_cwd = mc_realpath (pcwd, rp_current_panel_cwd);
+
+ if (p_subshell_cwd == NULL)
+ p_subshell_cwd = subshell_cwd;
+ if (p_current_panel_cwd == NULL)
+ p_current_panel_cwd = pcwd;
+ bPathNotEq = strcmp (p_subshell_cwd, p_current_panel_cwd) != 0;
+ }
+
+ if (bPathNotEq && !DIR_IS_DOT (pcwd))
+ {
+ char *cwd;
+
+ cwd = vfs_path_to_str_flags (subshell_get_cwd (), 0, VPF_STRIP_PASSWORD);
+ vfs_print_message (_("Warning: Cannot change to %s.\n"), cwd);
+ g_free (cwd);
+ }
+ }
+
+ /* Really escape Zsh history */
+ 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,
+ * type a space and press return. */
+ write_all (mc_global.tty.subshell_pty, " \n", 2);
+ subshell_state = RUNNING_COMMAND;
+ feed_subshell (QUIETLY, TRUE);
+ }
+
+ update_subshell_prompt = FALSE;
+
+ g_free (pcwd);
+ /* Make sure that MC never stores the CWD in a silly format */
+ /* like /usr////lib/../bin, or the strcmp() above will fail */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+subshell_get_console_attributes (void)
+{
+ /* Get our current terminal modes */
+
+ if (tcgetattr (STDOUT_FILENO, &shell_mode))
+ {
+ fprintf (stderr, "Cannot get terminal settings: %s\r\n", unix_error_string (errno));
+ mc_global.tty.use_subshell = FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Figure out whether the subshell has stopped, exited or been killed
+ * Possibly modifies: 'subshell_alive', 'subshell_stopped' and 'quit' */
+
+void
+sigchld_handler (int sig)
+{
+ int status;
+ pid_t pid;
+
+ (void) sig;
+
+ pid = waitpid (subshell_pid, &status, WUNTRACED | WNOHANG);
+
+ if (pid == subshell_pid)
+ {
+ /* Figure out what has happened to the subshell */
+
+ if (WIFSTOPPED (status))
+ {
+ if (WSTOPSIG (status) == SIGSTOP)
+ {
+ /* The subshell has received a SIGSTOP signal */
+ subshell_stopped = TRUE;
+ }
+ else
+ {
+ /* The user has suspended the subshell. Revive it */
+ kill (subshell_pid, SIGCONT);
+ }
+ }
+ else
+ {
+ /* The subshell has either exited normally or been killed */
+ subshell_alive = FALSE;
+ delete_select_channel (mc_global.tty.subshell_pty);
+ if (WIFEXITED (status) && WEXITSTATUS (status) != FORK_FAILURE)
+ {
+ int subshell_quit;
+ subshell_quit = subshell_get_mainloop_quit () | SUBSHELL_EXIT; /* Exited normally */
+ subshell_set_mainloop_quit (subshell_quit);
+ }
+ }
+ }
+ subshell_handle_cons_saver ();
+
+ /* If we got here, some other child exited; ignore it */
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/subshell/internal.h b/src/subshell/internal.h
new file mode 100644
index 0000000..101b85b
--- /dev/null
+++ b/src/subshell/internal.h
@@ -0,0 +1,29 @@
+/** \file internal.h
+ * \brief Header: internal functions and variables
+ */
+
+#ifndef MC__SUBSHELL_INTERNAL_H
+#define MC__SUBSHELL_INTERNAL_H
+
+/* TODO: merge content of layout.h here */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+const vfs_path_t *subshell_get_cwd (void);
+void subshell_handle_cons_saver (void);
+
+int subshell_get_mainloop_quit (void);
+void subshell_set_mainloop_quit (const int param_quit);
+
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__SUBSHELL_INTERNAL_H */
diff --git a/src/subshell/proxyfunc.c b/src/subshell/proxyfunc.c
new file mode 100644
index 0000000..3f180d3
--- /dev/null
+++ b/src/subshell/proxyfunc.c
@@ -0,0 +1,113 @@
+/*
+ Proxy functions for getting access to public variables into 'filemanager' module.
+
+ Copyright (C) 2015-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <signal.h> /* kill() */
+#include <sys/types.h>
+#include <sys/wait.h> /* waitpid() */
+
+#include "lib/global.h"
+
+#include "lib/vfs/vfs.h" /* vfs_get_raw_current_dir() */
+
+#include "src/setup.h" /* quit */
+#include "src/filemanager/filemanager.h" /* current_panel */
+#include "src/consaver/cons.saver.h" /* handle_console() */
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/* path to X clipboard utility */
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+const vfs_path_t *
+subshell_get_cwd (void)
+{
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ return current_panel->cwd_vpath;
+
+ return vfs_get_raw_current_dir ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+subshell_handle_cons_saver (void)
+{
+#ifdef __linux__
+ int status;
+ pid_t pid;
+
+ pid = waitpid (cons_saver_pid, &status, WUNTRACED | WNOHANG);
+
+ if (pid == cons_saver_pid)
+ {
+
+ if (WIFSTOPPED (status))
+ /* Someone has stopped cons.saver - restart it */
+ kill (pid, SIGCONT);
+ else
+ {
+ /* cons.saver has died - disable console saving */
+ handle_console (CONSOLE_DONE);
+ mc_global.tty.console_flag = '\0';
+ }
+
+ }
+#endif /* __linux__ */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+subshell_get_mainloop_quit (void)
+{
+ return quit;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+subshell_set_mainloop_quit (const int param_quit)
+{
+ quit = param_quit;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/subshell/subshell.h b/src/subshell/subshell.h
new file mode 100644
index 0000000..bde19c4
--- /dev/null
+++ b/src/subshell/subshell.h
@@ -0,0 +1,55 @@
+/** \file subshell.h
+ * \brief Header: concurrent shell support
+ */
+
+#ifndef MC__SUBSHELL_H
+#define MC__SUBSHELL_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/* State of the subshell; see subshell.c for an explanation */
+
+enum subshell_state_enum
+{
+ INACTIVE,
+ ACTIVE,
+ RUNNING_COMMAND
+};
+
+/* For the 'how' argument to various functions */
+enum
+{
+ QUIETLY,
+ VISIBLY
+};
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern enum subshell_state_enum subshell_state;
+
+/* Holds the latest prompt captured from the subshell */
+extern GString *subshell_prompt;
+
+extern gboolean update_subshell_prompt;
+
+extern gboolean should_read_new_subshell_prompt;
+
+/*** declarations of public functions ************************************************************/
+
+void init_subshell (void);
+int invoke_subshell (const char *command, int how, vfs_path_t ** new_dir);
+gboolean flush_subshell (int max_wait_length, int how);
+gboolean read_subshell_prompt (void);
+void do_update_prompt (void);
+gboolean exit_subshell (void);
+void do_subshell_chdir (const vfs_path_t * vpath, gboolean update_prompt);
+void subshell_get_console_attributes (void);
+void sigchld_handler (int sig);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__SUBSHELL_H */