diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 17:44:12 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 17:44:12 +0000 |
commit | 8ccb487c21368a7fdc8c7c72315325bf0aa06147 (patch) | |
tree | b2056fae01d325924508a41731edfbd4c3cddd23 /src/subshell | |
parent | Initial commit. (diff) | |
download | mc-8ccb487c21368a7fdc8c7c72315325bf0aa06147.tar.xz mc-8ccb487c21368a7fdc8c7c72315325bf0aa06147.zip |
Adding upstream version 3:4.8.29.upstream/3%4.8.29upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/subshell/Makefile.am | 9 | ||||
-rw-r--r-- | src/subshell/Makefile.in | 740 | ||||
-rw-r--r-- | src/subshell/common.c | 1870 | ||||
-rw-r--r-- | src/subshell/internal.h | 29 | ||||
-rw-r--r-- | src/subshell/proxyfunc.c | 113 | ||||
-rw-r--r-- | src/subshell/subshell.h | 55 |
6 files changed, 2816 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..8c918c5 --- /dev/null +++ b/src/subshell/Makefile.in @@ -0,0 +1,740 @@ +# 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/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_CPPFLAGS = @PCRE_CPPFLAGS@ +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..61bf527 --- /dev/null +++ b/src/subshell/common.c @@ -0,0 +1,1870 @@ +/* + Concurrent shell support for the Midnight Commander + + Copyright (C) 1994-2022 + 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 "+" + +/*** 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 unforseen 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 should_reset_prompt = TRUE; + 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)); + if (should_reset_prompt) + { + should_reset_prompt = FALSE; + clear_subshell_prompt_string (); + } + + 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) + { + const char *translate; + + translate = vfs_translate_path (vfs_path_as_str (vpath)); + if (translate != NULL) + { + GString *temp; + + temp = subshell_name_quote (translate); + write_all (mc_global.tty.subshell_pty, temp->str, temp->len); + g_string_free (temp, TRUE); + } + else + { + write_all (mc_global.tty.subshell_pty, ".", 1); + } + } + else + { + write_all (mc_global.tty.subshell_pty, "/", 1); + } + 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..f9e8a73 --- /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-2022 + 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 */ |