summaryrefslogtreecommitdiffstats
path: root/src
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
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')
-rw-r--r--src/Makefile.am128
-rw-r--r--src/Makefile.in1047
-rw-r--r--src/args.c845
-rw-r--r--src/args.h55
-rw-r--r--src/background.c657
-rw-r--r--src/background.h54
-rw-r--r--src/clipboard.c268
-rw-r--r--src/clipboard.h33
-rw-r--r--src/cons.handler.c502
-rw-r--r--src/consaver/Makefile.am7
-rw-r--r--src/consaver/Makefile.in777
-rw-r--r--src/consaver/cons.saver.c289
-rw-r--r--src/consaver/cons.saver.h46
-rw-r--r--src/diffviewer/Makefile.am8
-rw-r--r--src/diffviewer/Makefile.in740
-rw-r--r--src/diffviewer/internal.h153
-rw-r--r--src/diffviewer/search.c288
-rw-r--r--src/diffviewer/ydiff.c3648
-rw-r--r--src/diffviewer/ydiff.h16
-rw-r--r--src/editor/Makefile.am33
-rw-r--r--src/editor/Makefile.in801
-rw-r--r--src/editor/bookmark.c349
-rw-r--r--src/editor/edit-impl.h278
-rw-r--r--src/editor/edit.c4067
-rw-r--r--src/editor/edit.h84
-rw-r--r--src/editor/editbuffer.c900
-rw-r--r--src/editor/editbuffer.h117
-rw-r--r--src/editor/editcmd.c2108
-rw-r--r--src/editor/editcomplete.c483
-rw-r--r--src/editor/editcomplete.h21
-rw-r--r--src/editor/editdraw.c1122
-rw-r--r--src/editor/editmacros.c437
-rw-r--r--src/editor/editmacros.h24
-rw-r--r--src/editor/editmenu.c338
-rw-r--r--src/editor/editoptions.c241
-rw-r--r--src/editor/editsearch.c1042
-rw-r--r--src/editor/editsearch.h36
-rw-r--r--src/editor/editwidget.c1550
-rw-r--r--src/editor/editwidget.h173
-rw-r--r--src/editor/etags.c468
-rw-r--r--src/editor/etags.h26
-rw-r--r--src/editor/format.c538
-rw-r--r--src/editor/spell.c834
-rw-r--r--src/editor/spell.h36
-rw-r--r--src/editor/syntax.c1606
-rw-r--r--src/events_init.c86
-rw-r--r--src/events_init.h19
-rw-r--r--src/execute.c670
-rw-r--r--src/execute.h51
-rw-r--r--src/file_history.c246
-rw-r--r--src/file_history.h20
-rw-r--r--src/filemanager/Makefile.am40
-rw-r--r--src/filemanager/Makefile.in839
-rw-r--r--src/filemanager/achown.c1107
-rw-r--r--src/filemanager/boxes.c1343
-rw-r--r--src/filemanager/boxes.h37
-rw-r--r--src/filemanager/cd.c310
-rw-r--r--src/filemanager/cd.h23
-rw-r--r--src/filemanager/chattr.c1358
-rw-r--r--src/filemanager/chmod.c664
-rw-r--r--src/filemanager/chown.c552
-rw-r--r--src/filemanager/cmd.c1470
-rw-r--r--src/filemanager/cmd.h172
-rw-r--r--src/filemanager/command.c255
-rw-r--r--src/filemanager/command.h27
-rw-r--r--src/filemanager/dir.c839
-rw-r--r--src/filemanager/dir.h115
-rw-r--r--src/filemanager/ext.c1089
-rw-r--r--src/filemanager/ext.h33
-rw-r--r--src/filemanager/file.c3562
-rw-r--r--src/filemanager/file.h72
-rw-r--r--src/filemanager/filegui.c1498
-rw-r--r--src/filemanager/filegui.h40
-rw-r--r--src/filemanager/filemanager.c1852
-rw-r--r--src/filemanager/filemanager.h53
-rw-r--r--src/filemanager/filenot.c150
-rw-r--r--src/filemanager/filenot.h26
-rw-r--r--src/filemanager/fileopctx.c128
-rw-r--r--src/filemanager/fileopctx.h198
-rw-r--r--src/filemanager/find.c1968
-rw-r--r--src/filemanager/hotlist.c1733
-rw-r--r--src/filemanager/hotlist.h33
-rw-r--r--src/filemanager/info.c377
-rw-r--r--src/filemanager/info.h24
-rw-r--r--src/filemanager/ioblksize.h86
-rw-r--r--src/filemanager/layout.c1580
-rw-r--r--src/filemanager/layout.h98
-rw-r--r--src/filemanager/mountlist.c1575
-rw-r--r--src/filemanager/mountlist.h44
-rw-r--r--src/filemanager/panel.c5428
-rw-r--r--src/filemanager/panel.h285
-rw-r--r--src/filemanager/panelize.c573
-rw-r--r--src/filemanager/panelize.h25
-rw-r--r--src/filemanager/tree.c1345
-rw-r--r--src/filemanager/tree.h35
-rw-r--r--src/filemanager/treestore.c941
-rw-r--r--src/filemanager/treestore.h63
-rw-r--r--src/help.c1181
-rw-r--r--src/help.h57
-rw-r--r--src/history.h61
-rw-r--r--src/keymap.c985
-rw-r--r--src/keymap.h64
-rw-r--r--src/learn.c424
-rw-r--r--src/learn.h21
-rw-r--r--src/main.c556
-rw-r--r--src/man2hlp/Makefile.am1
-rw-r--r--src/man2hlp/Makefile.in584
-rw-r--r--src/man2hlp/man2hlp.in987
-rw-r--r--src/selcodepage.c178
-rw-r--r--src/selcodepage.h39
-rw-r--r--src/setup.c1239
-rw-r--r--src/setup.h162
-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
-rw-r--r--src/textconf.c264
-rw-r--r--src/textconf.h23
-rw-r--r--src/usermenu.c1176
-rw-r--r--src/usermenu.h29
-rw-r--r--src/util.c75
-rw-r--r--src/util.h19
-rw-r--r--src/vfs/Makefile.am47
-rw-r--r--src/vfs/Makefile.in875
-rw-r--r--src/vfs/cpio/Makefile.am7
-rw-r--r--src/vfs/cpio/Makefile.in735
-rw-r--r--src/vfs/cpio/cpio.c905
-rw-r--r--src/vfs/cpio/cpio.h18
-rw-r--r--src/vfs/extfs/Makefile.am12
-rw-r--r--src/vfs/extfs/Makefile.in856
-rw-r--r--src/vfs/extfs/extfs.c1722
-rw-r--r--src/vfs/extfs/extfs.h18
-rw-r--r--src/vfs/extfs/helpers/Makefile.am76
-rw-r--r--src/vfs/extfs/helpers/Makefile.in812
-rw-r--r--src/vfs/extfs/helpers/README200
-rw-r--r--src/vfs/extfs/helpers/README.extfs78
-rw-r--r--src/vfs/extfs/helpers/a+.in126
-rw-r--r--src/vfs/extfs/helpers/apt+.in359
-rwxr-xr-xsrc/vfs/extfs/helpers/audio.in53
-rwxr-xr-xsrc/vfs/extfs/helpers/bpp50
-rwxr-xr-xsrc/vfs/extfs/helpers/changesetfs109
-rw-r--r--src/vfs/extfs/helpers/deb.in203
-rw-r--r--src/vfs/extfs/helpers/deba.in107
-rw-r--r--src/vfs/extfs/helpers/debd.in362
-rw-r--r--src/vfs/extfs/helpers/dpkg+.in337
-rwxr-xr-xsrc/vfs/extfs/helpers/gitfs+39
-rw-r--r--src/vfs/extfs/helpers/hp48+.in132
-rw-r--r--src/vfs/extfs/helpers/iso9660.in235
-rw-r--r--src/vfs/extfs/helpers/lslR.in74
-rw-r--r--src/vfs/extfs/helpers/mailfs.in219
-rw-r--r--src/vfs/extfs/helpers/patchfs.in427
-rwxr-xr-xsrc/vfs/extfs/helpers/patchsetfs104
-rwxr-xr-xsrc/vfs/extfs/helpers/rpm349
-rw-r--r--src/vfs/extfs/helpers/rpms+.in66
-rw-r--r--src/vfs/extfs/helpers/s3+.in490
-rwxr-xr-xsrc/vfs/extfs/helpers/trpm176
-rwxr-xr-xsrc/vfs/extfs/helpers/u7z135
-rw-r--r--src/vfs/extfs/helpers/uace.in67
-rw-r--r--src/vfs/extfs/helpers/ualz.in68
-rw-r--r--src/vfs/extfs/helpers/uar.in60
-rw-r--r--src/vfs/extfs/helpers/uarc.in92
-rw-r--r--src/vfs/extfs/helpers/uarj.in75
-rwxr-xr-xsrc/vfs/extfs/helpers/uc1541702
-rw-r--r--src/vfs/extfs/helpers/ucab.in40
-rw-r--r--src/vfs/extfs/helpers/uha.in52
-rw-r--r--src/vfs/extfs/helpers/ulha.in142
-rw-r--r--src/vfs/extfs/helpers/ulib.in146
-rw-r--r--src/vfs/extfs/helpers/unar.in59
-rw-r--r--src/vfs/extfs/helpers/urar.in180
-rw-r--r--src/vfs/extfs/helpers/uwim.in208
-rw-r--r--src/vfs/extfs/helpers/uzip.in483
-rw-r--r--src/vfs/extfs/helpers/uzoo.in69
-rw-r--r--src/vfs/fish/Makefile.am13
-rw-r--r--src/vfs/fish/Makefile.in857
-rw-r--r--src/vfs/fish/fish.c1805
-rw-r--r--src/vfs/fish/fish.h28
-rw-r--r--src/vfs/fish/fishdef.h236
-rw-r--r--src/vfs/fish/helpers/Makefile.am10
-rw-r--r--src/vfs/fish/helpers/Makefile.in642
-rw-r--r--src/vfs/fish/helpers/README.fish217
-rw-r--r--src/vfs/fish/helpers/append16
-rw-r--r--src/vfs/fish/helpers/chmod6
-rw-r--r--src/vfs/fish/helpers/chown6
-rw-r--r--src/vfs/fish/helpers/fexists3
-rw-r--r--src/vfs/fish/helpers/get105
-rw-r--r--src/vfs/fish/helpers/hardlink8
-rw-r--r--src/vfs/fish/helpers/info44
-rw-r--r--src/vfs/fish/helpers/ln8
-rw-r--r--src/vfs/fish/helpers/ls170
-rw-r--r--src/vfs/fish/helpers/mkdir6
-rw-r--r--src/vfs/fish/helpers/mv6
-rw-r--r--src/vfs/fish/helpers/rmdir6
-rw-r--r--src/vfs/fish/helpers/send17
-rw-r--r--src/vfs/fish/helpers/unlink6
-rw-r--r--src/vfs/fish/helpers/utime13
-rw-r--r--src/vfs/ftpfs/Makefile.am8
-rw-r--r--src/vfs/ftpfs/Makefile.in740
-rw-r--r--src/vfs/ftpfs/ftpfs.c2784
-rw-r--r--src/vfs/ftpfs/ftpfs.h46
-rw-r--r--src/vfs/ftpfs/ftpfs_parse_ls.c1236
-rw-r--r--src/vfs/local/Makefile.am7
-rw-r--r--src/vfs/local/Makefile.in735
-rw-r--r--src/vfs/local/local.c523
-rw-r--r--src/vfs/local/local.h32
-rw-r--r--src/vfs/plugins_init.c123
-rw-r--r--src/vfs/plugins_init.h18
-rw-r--r--src/vfs/sfs/Makefile.am16
-rw-r--r--src/vfs/sfs/Makefile.in794
-rw-r--r--src/vfs/sfs/sfs.c604
-rw-r--r--src/vfs/sfs/sfs.h18
-rw-r--r--src/vfs/sfs/sfs.ini34
-rw-r--r--src/vfs/sftpfs/Makefile.am12
-rw-r--r--src/vfs/sftpfs/Makefile.in759
-rw-r--r--src/vfs/sftpfs/config_parser.c427
-rw-r--r--src/vfs/sftpfs/connection.c970
-rw-r--r--src/vfs/sftpfs/dir.c230
-rw-r--r--src/vfs/sftpfs/file.c424
-rw-r--r--src/vfs/sftpfs/internal.c621
-rw-r--r--src/vfs/sftpfs/internal.h114
-rw-r--r--src/vfs/sftpfs/sftpfs.c866
-rw-r--r--src/vfs/sftpfs/sftpfs.h23
-rw-r--r--src/vfs/tar/Makefile.am10
-rw-r--r--src/vfs/tar/Makefile.in750
-rw-r--r--src/vfs/tar/tar-internal.c482
-rw-r--r--src/vfs/tar/tar-internal.h351
-rw-r--r--src/vfs/tar/tar-sparse.c777
-rw-r--r--src/vfs/tar/tar-xheader.c1051
-rw-r--r--src/vfs/tar/tar.c1302
-rw-r--r--src/vfs/tar/tar.h18
-rw-r--r--src/vfs/undelfs/Makefile.am7
-rw-r--r--src/vfs/undelfs/Makefile.in735
-rw-r--r--src/vfs/undelfs/undelfs.c844
-rw-r--r--src/vfs/undelfs/undelfs.h18
-rw-r--r--src/viewer/Makefile.am21
-rw-r--r--src/viewer/Makefile.in793
-rw-r--r--src/viewer/actions_cmd.c790
-rw-r--r--src/viewer/ascii.c1051
-rw-r--r--src/viewer/coord_cache.c395
-rw-r--r--src/viewer/datasource.c434
-rw-r--r--src/viewer/dialogs.c263
-rw-r--r--src/viewer/display.c404
-rw-r--r--src/viewer/growbuf.c297
-rw-r--r--src/viewer/hex.c484
-rw-r--r--src/viewer/internal.h472
-rw-r--r--src/viewer/lib.c437
-rw-r--r--src/viewer/mcviewer.c469
-rw-r--r--src/viewer/mcviewer.h57
-rw-r--r--src/viewer/move.c415
-rw-r--r--src/viewer/nroff.c287
-rw-r--r--src/viewer/search.c491
252 files changed, 116618 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..cca050c
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,128 @@
+SUBDIRS = filemanager man2hlp vfs viewer
+
+if USE_INTERNAL_EDIT
+SUBDIRS += editor
+endif
+
+if USE_DIFF
+SUBDIRS += diffviewer
+endif
+
+if ENABLE_SUBSHELL
+SUBDIRS += subshell
+endif
+
+noinst_LTLIBRARIES = libinternal.la
+
+AM_CPPFLAGS = \
+ -DSYSCONFDIR=\""$(sysconfdir)/@PACKAGE@/"\" \
+ -DLIBEXECDIR=\""$(libexecdir)/@PACKAGE@/"\" \
+ -DDATADIR=\""$(pkgdatadir)/"\" \
+ -DLOCALEDIR=\""$(localedir)"\" \
+ -DEXTHELPERSDIR=\""@EXTHELPERSDIR@/"\"
+
+if CONS_SAVER
+SUBDIRS += consaver
+AM_CPPFLAGS += -DSAVERDIR=\""$(pkglibexecdir)"\"
+endif
+
+AM_CPPFLAGS += -I$(top_srcdir) $(GLIB_CFLAGS)
+
+localedir = $(datadir)/locale
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+bin_PROGRAMS = mc
+
+if USE_INTERNAL_EDIT
+EDITLIB = editor/libedit.la
+endif
+
+if USE_DIFF
+DIFFLIB = diffviewer/libdiffviewer.la
+endif
+
+if ENABLE_SUBSHELL
+SUBSHELLLIB = subshell/libsubshell.la
+endif
+
+libinternal_la_LIBADD = \
+ filemanager/libmcfilemanager.la \
+ vfs/libmc-vfs.la \
+ viewer/libmcviewer.la \
+ $(DIFFLIB) $(EDITLIB) $(SUBSHELLLIB)
+
+mc_LDADD = \
+ libinternal.la
+
+if ENABLE_MCLIB
+libinternal_la_LIBADD += \
+ $(top_builddir)/lib/libmc.la
+else
+mc_LDADD += \
+ $(top_builddir)/lib/libmc.la
+endif
+
+SRC_mc_conssaver = \
+ cons.handler.c consaver/cons.saver.h
+
+mc_SOURCES = \
+ main.c
+
+libinternal_la_SOURCES = \
+ $(SRC_mc_conssaver) \
+ args.c args.h \
+ clipboard.c clipboard.h \
+ events_init.c events_init.h \
+ execute.c execute.h \
+ file_history.c file_history.h \
+ help.c help.h \
+ history.h \
+ keymap.c keymap.h \
+ learn.c learn.h \
+ setup.c setup.h \
+ textconf.c textconf.h \
+ usermenu.c usermenu.h \
+ util.c util.h
+
+if CHARSET
+ libinternal_la_SOURCES += selcodepage.c selcodepage.h
+endif
+
+
+if ENABLE_BACKGROUND
+ libinternal_la_SOURCES += background.c background.h
+endif
+
+EXTRA_DIST = $(SRC_maintainer) $(SRC_charset)
+
+# end of automated testing
+
+install-exec-hook:
+ $(MAKE) install_mcview
+if USE_INTERNAL_EDIT
+ $(MAKE) install_mcedit
+endif
+if USE_DIFF
+ $(MAKE) install_mcdiff
+endif
+
+#
+# Make relative symlinks, but do the right thing if LN_S is `ln' or `cp'.
+#
+install_mcview:
+ cd $(DESTDIR)$(bindir)/$(binprefix) && rm -f mcview && $(LN_S) mc mcview
+
+install_mcedit:
+ cd $(DESTDIR)$(bindir)/$(binprefix) && rm -f mcedit && $(LN_S) mc mcedit
+
+install_mcdiff:
+ cd $(DESTDIR)$(bindir)/$(binprefix) && rm -f mcdiff && $(LN_S) mc mcdiff
+
+uninstall-hook:
+ rm -f $(DESTDIR)$(bindir)/$(binprefix)/mcview
+if USE_INTERNAL_EDIT
+ rm -f $(DESTDIR)$(bindir)/$(binprefix)/mcedit
+endif
+if USE_DIFF
+ rm -f $(DESTDIR)$(bindir)/$(binprefix)/mcdiff
+endif
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..0a4b851
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,1047 @@
+# 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@
+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@
+@USE_INTERNAL_EDIT_TRUE@am__append_1 = editor
+@USE_DIFF_TRUE@am__append_2 = diffviewer
+@ENABLE_SUBSHELL_TRUE@am__append_3 = subshell
+@CONS_SAVER_TRUE@am__append_4 = consaver
+@CONS_SAVER_TRUE@am__append_5 = -DSAVERDIR=\""$(pkglibexecdir)"\"
+bin_PROGRAMS = mc$(EXEEXT)
+@ENABLE_MCLIB_TRUE@am__append_6 = \
+@ENABLE_MCLIB_TRUE@ $(top_builddir)/lib/libmc.la
+
+@ENABLE_MCLIB_FALSE@am__append_7 = \
+@ENABLE_MCLIB_FALSE@ $(top_builddir)/lib/libmc.la
+
+@CHARSET_TRUE@am__append_8 = selcodepage.c selcodepage.h
+@ENABLE_BACKGROUND_TRUE@am__append_9 = background.c background.h
+subdir = src
+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 =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libinternal_la_DEPENDENCIES = filemanager/libmcfilemanager.la \
+ vfs/libmc-vfs.la viewer/libmcviewer.la $(DIFFLIB) $(EDITLIB) \
+ $(SUBSHELLLIB) $(am__append_6)
+am__libinternal_la_SOURCES_DIST = cons.handler.c consaver/cons.saver.h \
+ args.c args.h clipboard.c clipboard.h events_init.c \
+ events_init.h execute.c execute.h file_history.c \
+ file_history.h help.c help.h history.h keymap.c keymap.h \
+ learn.c learn.h setup.c setup.h textconf.c textconf.h \
+ usermenu.c usermenu.h util.c util.h selcodepage.c \
+ selcodepage.h background.c background.h
+am__objects_1 = cons.handler.lo
+@CHARSET_TRUE@am__objects_2 = selcodepage.lo
+@ENABLE_BACKGROUND_TRUE@am__objects_3 = background.lo
+am_libinternal_la_OBJECTS = $(am__objects_1) args.lo clipboard.lo \
+ events_init.lo execute.lo file_history.lo help.lo keymap.lo \
+ learn.lo setup.lo textconf.lo usermenu.lo util.lo \
+ $(am__objects_2) $(am__objects_3)
+libinternal_la_OBJECTS = $(am_libinternal_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_mc_OBJECTS = main.$(OBJEXT)
+mc_OBJECTS = $(am_mc_OBJECTS)
+mc_DEPENDENCIES = libinternal.la $(am__append_7)
+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)/args.Plo ./$(DEPDIR)/background.Plo \
+ ./$(DEPDIR)/clipboard.Plo ./$(DEPDIR)/cons.handler.Plo \
+ ./$(DEPDIR)/events_init.Plo ./$(DEPDIR)/execute.Plo \
+ ./$(DEPDIR)/file_history.Plo ./$(DEPDIR)/help.Plo \
+ ./$(DEPDIR)/keymap.Plo ./$(DEPDIR)/learn.Plo \
+ ./$(DEPDIR)/main.Po ./$(DEPDIR)/selcodepage.Plo \
+ ./$(DEPDIR)/setup.Plo ./$(DEPDIR)/textconf.Plo \
+ ./$(DEPDIR)/usermenu.Plo ./$(DEPDIR)/util.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 = $(libinternal_la_SOURCES) $(mc_SOURCES)
+DIST_SOURCES = $(am__libinternal_la_SOURCES_DIST) $(mc_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+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)`
+DIST_SUBDIRS = filemanager man2hlp vfs viewer editor diffviewer \
+ subshell consaver
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+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 = $(datadir)/locale
+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@
+SUBDIRS = filemanager man2hlp vfs viewer $(am__append_1) \
+ $(am__append_2) $(am__append_3) $(am__append_4)
+noinst_LTLIBRARIES = libinternal.la
+AM_CPPFLAGS = -DSYSCONFDIR=\""$(sysconfdir)/@PACKAGE@/"\" \
+ -DLIBEXECDIR=\""$(libexecdir)/@PACKAGE@/"\" \
+ -DDATADIR=\""$(pkgdatadir)/"\" -DLOCALEDIR=\""$(localedir)"\" \
+ -DEXTHELPERSDIR=\""@EXTHELPERSDIR@/"\" $(am__append_5) \
+ -I$(top_srcdir) $(GLIB_CFLAGS)
+@USE_INTERNAL_EDIT_TRUE@EDITLIB = editor/libedit.la
+@USE_DIFF_TRUE@DIFFLIB = diffviewer/libdiffviewer.la
+@ENABLE_SUBSHELL_TRUE@SUBSHELLLIB = subshell/libsubshell.la
+libinternal_la_LIBADD = filemanager/libmcfilemanager.la \
+ vfs/libmc-vfs.la viewer/libmcviewer.la $(DIFFLIB) $(EDITLIB) \
+ $(SUBSHELLLIB) $(am__append_6)
+mc_LDADD = libinternal.la $(am__append_7)
+SRC_mc_conssaver = \
+ cons.handler.c consaver/cons.saver.h
+
+mc_SOURCES = \
+ main.c
+
+libinternal_la_SOURCES = $(SRC_mc_conssaver) args.c args.h clipboard.c \
+ clipboard.h events_init.c events_init.h execute.c execute.h \
+ file_history.c file_history.h help.c help.h history.h keymap.c \
+ keymap.h learn.c learn.h setup.c setup.h textconf.c textconf.h \
+ usermenu.c usermenu.h util.c util.h $(am__append_8) \
+ $(am__append_9)
+EXTRA_DIST = $(SRC_maintainer) $(SRC_charset)
+all: all-recursive
+
+.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/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/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):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+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}; \
+ }
+
+libinternal.la: $(libinternal_la_OBJECTS) $(libinternal_la_DEPENDENCIES) $(EXTRA_libinternal_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libinternal_la_OBJECTS) $(libinternal_la_LIBADD) $(LIBS)
+
+mc$(EXEEXT): $(mc_OBJECTS) $(mc_DEPENDENCIES) $(EXTRA_mc_DEPENDENCIES)
+ @rm -f mc$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(mc_OBJECTS) $(mc_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/args.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/background.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/clipboard.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cons.handler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/events_init.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/execute.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_history.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/help.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keymap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/learn.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/selcodepage.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/setup.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/textconf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/usermenu.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/util.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
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(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-recursive
+
+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-recursive
+
+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
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+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-recursive
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/args.Plo
+ -rm -f ./$(DEPDIR)/background.Plo
+ -rm -f ./$(DEPDIR)/clipboard.Plo
+ -rm -f ./$(DEPDIR)/cons.handler.Plo
+ -rm -f ./$(DEPDIR)/events_init.Plo
+ -rm -f ./$(DEPDIR)/execute.Plo
+ -rm -f ./$(DEPDIR)/file_history.Plo
+ -rm -f ./$(DEPDIR)/help.Plo
+ -rm -f ./$(DEPDIR)/keymap.Plo
+ -rm -f ./$(DEPDIR)/learn.Plo
+ -rm -f ./$(DEPDIR)/main.Po
+ -rm -f ./$(DEPDIR)/selcodepage.Plo
+ -rm -f ./$(DEPDIR)/setup.Plo
+ -rm -f ./$(DEPDIR)/textconf.Plo
+ -rm -f ./$(DEPDIR)/usermenu.Plo
+ -rm -f ./$(DEPDIR)/util.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+ @$(NORMAL_INSTALL)
+ $(MAKE) $(AM_MAKEFLAGS) install-exec-hook
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/args.Plo
+ -rm -f ./$(DEPDIR)/background.Plo
+ -rm -f ./$(DEPDIR)/clipboard.Plo
+ -rm -f ./$(DEPDIR)/cons.handler.Plo
+ -rm -f ./$(DEPDIR)/events_init.Plo
+ -rm -f ./$(DEPDIR)/execute.Plo
+ -rm -f ./$(DEPDIR)/file_history.Plo
+ -rm -f ./$(DEPDIR)/help.Plo
+ -rm -f ./$(DEPDIR)/keymap.Plo
+ -rm -f ./$(DEPDIR)/learn.Plo
+ -rm -f ./$(DEPDIR)/main.Po
+ -rm -f ./$(DEPDIR)/selcodepage.Plo
+ -rm -f ./$(DEPDIR)/setup.Plo
+ -rm -f ./$(DEPDIR)/textconf.Plo
+ -rm -f ./$(DEPDIR)/usermenu.Plo
+ -rm -f ./$(DEPDIR)/util.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+ @$(NORMAL_INSTALL)
+ $(MAKE) $(AM_MAKEFLAGS) uninstall-hook
+.MAKE: $(am__recursive_targets) install-am install-exec-am \
+ install-strip uninstall-am
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-binPROGRAMS \
+ 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-binPROGRAMS install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-exec-hook \
+ 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 installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-binPROGRAMS \
+ uninstall-hook
+
+.PRECIOUS: Makefile
+
+
+# end of automated testing
+
+install-exec-hook:
+ $(MAKE) install_mcview
+@USE_INTERNAL_EDIT_TRUE@ $(MAKE) install_mcedit
+@USE_DIFF_TRUE@ $(MAKE) install_mcdiff
+
+#
+# Make relative symlinks, but do the right thing if LN_S is `ln' or `cp'.
+#
+install_mcview:
+ cd $(DESTDIR)$(bindir)/$(binprefix) && rm -f mcview && $(LN_S) mc mcview
+
+install_mcedit:
+ cd $(DESTDIR)$(bindir)/$(binprefix) && rm -f mcedit && $(LN_S) mc mcedit
+
+install_mcdiff:
+ cd $(DESTDIR)$(bindir)/$(binprefix) && rm -f mcdiff && $(LN_S) mc mcdiff
+
+uninstall-hook:
+ rm -f $(DESTDIR)$(bindir)/$(binprefix)/mcview
+@USE_INTERNAL_EDIT_TRUE@ rm -f $(DESTDIR)$(bindir)/$(binprefix)/mcedit
+@USE_DIFF_TRUE@ rm -f $(DESTDIR)$(bindir)/$(binprefix)/mcdiff
+
+# 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/args.c b/src/args.c
new file mode 100644
index 0000000..a66777e
--- /dev/null
+++ b/src/args.c
@@ -0,0 +1,845 @@
+/*
+ Handle command line arguments.
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2009.
+ Andrew Borodin <aborodin@vmail.ru>, 2011, 2012.
+
+ 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 <stdlib.h>
+#include <stdio.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/strutil.h"
+#include "lib/vfs/vfs.h"
+#include "lib/util.h" /* x_basename() */
+
+#include "src/textconf.h"
+
+#include "src/args.h"
+
+/*** external variables **************************************************************************/
+
+/*** global variables ****************************************************************************/
+
+/* If true, assume we are running on an xterm terminal */
+gboolean mc_args__force_xterm = FALSE;
+
+gboolean mc_args__nomouse = FALSE;
+
+/* Force colors, only used by Slang */
+gboolean mc_args__force_colors = FALSE;
+
+/* Don't load keymap from file and use default one */
+gboolean mc_args__nokeymap = FALSE;
+
+char *mc_args__last_wd_file = NULL;
+
+/* when enabled NETCODE, use following file as logfile */
+char *mc_args__netfs_logfile = NULL;
+
+/* keymap file */
+char *mc_args__keymap_file = NULL;
+
+void *mc_run_param0 = NULL;
+char *mc_run_param1 = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static gboolean parse_mc_e_argument (const gchar * option_name, const gchar * value,
+ gpointer data, GError ** mcerror);
+static gboolean parse_mc_v_argument (const gchar * option_name, const gchar * value,
+ gpointer data, GError ** mcerror);
+
+/*** file scope variables ************************************************************************/
+
+/* If true, show version info and exit */
+static gboolean mc_args__show_version = FALSE;
+
+static GOptionContext *context;
+
+#ifdef ENABLE_SUBSHELL
+static gboolean mc_args__nouse_subshell = FALSE;
+#endif /* ENABLE_SUBSHELL */
+static gboolean mc_args__show_datadirs = FALSE;
+static gboolean mc_args__show_datadirs_extended = FALSE;
+#ifdef ENABLE_CONFIGURE_ARGS
+static gboolean mc_args__show_configure_opts = FALSE;
+#endif
+
+static GOptionGroup *main_group;
+
+static const GOptionEntry argument_main_table[] = {
+ /* *INDENT-OFF* */
+ /* generic options */
+ {
+ "version", 'V', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,
+ &mc_args__show_version,
+ N_("Displays the current version"),
+ NULL
+ },
+
+ /* options for wrappers */
+ {
+ "datadir", 'f', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,
+ &mc_args__show_datadirs,
+ N_("Print data directory"),
+ NULL
+ },
+
+ /* show extended information about used data directories */
+ {
+ "datadir-info", 'F', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,
+ &mc_args__show_datadirs_extended,
+ N_("Print extended info about used data directories"),
+ NULL
+ },
+
+#ifdef ENABLE_CONFIGURE_ARGS
+ /* show configure options */
+ {
+ "configure-options", '\0', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,
+ &mc_args__show_configure_opts,
+ N_("Print configure options"),
+ NULL
+ },
+#endif
+
+ {
+ "printwd", 'P', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_STRING,
+ &mc_args__last_wd_file,
+ N_("Print last working directory to specified file"),
+ N_("<file>")
+ },
+
+#ifdef ENABLE_SUBSHELL
+ {
+ "subshell", 'U', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,
+ &mc_global.tty.use_subshell,
+ N_("Enables subshell support (default)"),
+ NULL
+ },
+
+ {
+ "nosubshell", 'u', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,
+ &mc_args__nouse_subshell,
+ N_("Disables subshell support"),
+ NULL
+ },
+#endif
+
+ /* debug options */
+#ifdef ENABLE_VFS_FTP
+ {
+ "ftplog", 'l', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_STRING,
+ &mc_args__netfs_logfile,
+ N_("Log ftp dialog to specified file"),
+ N_("<file>")
+ },
+#endif /* ENABLE_VFS_FTP */
+
+ {
+ /* handle arguments manually */
+ "view", 'v', G_OPTION_FLAG_IN_MAIN | G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ (gpointer) parse_mc_v_argument,
+ N_("Launches the file viewer on a file"),
+ N_("<file>")
+ },
+
+ {
+ /* handle arguments manually */
+ "edit", 'e', G_OPTION_FLAG_IN_MAIN | G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ (gpointer) parse_mc_e_argument,
+ N_("Edit files"),
+ N_("<file> ...")
+ },
+
+ G_OPTION_ENTRY_NULL
+ /* *INDENT-ON* */
+};
+
+static GOptionGroup *terminal_group;
+#define ARGS_TERM_OPTIONS 0
+static const GOptionEntry argument_terminal_table[] = {
+ /* *INDENT-OFF* */
+ /* terminal options */
+ {
+ "xterm", 'x', ARGS_TERM_OPTIONS, G_OPTION_ARG_NONE,
+ &mc_args__force_xterm,
+ N_("Forces xterm features"),
+ NULL
+ },
+
+ {
+ "no-x11", 'X', ARGS_TERM_OPTIONS, G_OPTION_ARG_NONE,
+ &mc_global.tty.disable_x11,
+ N_("Disable X11 support"),
+ NULL
+ },
+
+ {
+ "oldmouse", 'g', ARGS_TERM_OPTIONS, G_OPTION_ARG_NONE,
+ &mc_global.tty.old_mouse,
+ N_("Tries to use an old highlight mouse tracking"),
+ NULL
+ },
+
+ {
+ "nomouse", 'd', ARGS_TERM_OPTIONS, G_OPTION_ARG_NONE,
+ &mc_args__nomouse,
+ N_("Disable mouse support in text version"),
+ NULL
+ },
+
+#ifdef HAVE_SLANG
+ {
+ "termcap", 't', ARGS_TERM_OPTIONS, G_OPTION_ARG_NONE,
+ &SLtt_Try_Termcap,
+ N_("Tries to use termcap instead of terminfo"),
+ NULL
+ },
+#endif
+
+ {
+ "slow", 's', ARGS_TERM_OPTIONS, G_OPTION_ARG_NONE,
+ &mc_global.tty.slow_terminal,
+ N_("To run on slow terminals"),
+ NULL
+ },
+
+ {
+ "stickchars", 'a', ARGS_TERM_OPTIONS, G_OPTION_ARG_NONE,
+ &mc_global.tty.ugly_line_drawing,
+ N_("Use stickchars to draw"),
+ NULL
+ },
+
+#ifdef HAVE_SLANG
+ {
+ "resetsoft", 'k', ARGS_TERM_OPTIONS, G_OPTION_ARG_NONE,
+ &reset_hp_softkeys,
+ N_("Resets soft keys on HP terminals"),
+ NULL
+ },
+#endif
+
+ {
+ "keymap", 'K', ARGS_TERM_OPTIONS, G_OPTION_ARG_STRING,
+ &mc_args__keymap_file,
+ N_("Load definitions of key bindings from specified file"),
+ N_("<file>")
+ },
+
+ {
+ "nokeymap", '\0', ARGS_TERM_OPTIONS, G_OPTION_ARG_NONE,
+ &mc_args__nokeymap,
+ N_("Don't load definitions of key bindings from file, use defaults"),
+ NULL
+ },
+
+ G_OPTION_ENTRY_NULL
+ /* *INDENT-ON* */
+};
+
+#undef ARGS_TERM_OPTIONS
+
+static GOptionGroup *color_group;
+#define ARGS_COLOR_OPTIONS 0
+/* #define ARGS_COLOR_OPTIONS G_OPTION_FLAG_IN_MAIN */
+static const GOptionEntry argument_color_table[] = {
+ /* *INDENT-OFF* */
+ /* color options */
+ {
+ "nocolor", 'b', ARGS_COLOR_OPTIONS, G_OPTION_ARG_NONE,
+ &mc_global.tty.disable_colors,
+ N_("Requests to run in black and white"),
+ NULL
+ },
+
+ {
+ "color", 'c', ARGS_COLOR_OPTIONS, G_OPTION_ARG_NONE,
+ &mc_args__force_colors,
+ N_("Request to run in color mode"),
+ NULL
+ },
+
+ {
+ "colors", 'C', ARGS_COLOR_OPTIONS, G_OPTION_ARG_STRING,
+ &mc_global.tty.command_line_colors,
+ N_("Specifies a color configuration"),
+ N_("<string>")
+ },
+
+ {
+ "skin", 'S', ARGS_COLOR_OPTIONS, G_OPTION_ARG_STRING,
+ &mc_global.tty.skin,
+ N_("Show mc with specified skin"),
+ N_("<string>")
+ },
+
+ G_OPTION_ENTRY_NULL
+ /* *INDENT-ON* */
+};
+
+#undef ARGS_COLOR_OPTIONS
+
+static gchar *mc_args__loc__colors_string = NULL;
+static gchar *mc_args__loc__footer_string = NULL;
+static gchar *mc_args__loc__header_string = NULL;
+static gchar *mc_args__loc__usage_string = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_args_clean_temp_help_strings (void)
+{
+ MC_PTR_FREE (mc_args__loc__colors_string);
+ MC_PTR_FREE (mc_args__loc__footer_string);
+ MC_PTR_FREE (mc_args__loc__header_string);
+ MC_PTR_FREE (mc_args__loc__usage_string);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GOptionGroup *
+mc_args_new_color_group (void)
+{
+ /* *INDENT-OFF* */
+ /* FIXME: to preserve translations, lines should be split. */
+ mc_args__loc__colors_string = g_strdup_printf ("%s\n%s",
+ /* TRANSLATORS: don't translate keywords */
+ _("--colors KEYWORD={FORE},{BACK},{ATTR}:KEYWORD2=...\n\n"
+ "{FORE}, {BACK} and {ATTR} can be omitted, and the default will be used\n"
+ "\n Keywords:\n"
+ " Global: errors, disabled, reverse, gauge, header\n"
+ " input, inputmark, inputunchanged, commandlinemark\n"
+ " bbarhotkey, bbarbutton, statusbar\n"
+ " File display: normal, selected, marked, markselect\n"
+ " Dialog boxes: dnormal, dfocus, dhotnormal, dhotfocus, errdhotnormal,\n"
+ " errdhotfocus\n"
+ " Menus: menunormal, menuhot, menusel, menuhotsel, menuinactive\n"
+ " Popup menus: pmenunormal, pmenusel, pmenutitle\n"
+ " Editor: editnormal, editbold, editmarked, editwhitespace,\n"
+ " editlinestate, editbg, editframe, editframeactive\n"
+ " editframedrag\n"
+ " Viewer: viewnormal,viewbold, viewunderline, viewselected\n"
+ " Help: helpnormal, helpitalic, helpbold, helplink, helpslink\n"),
+ /* TRANSLATORS: don't translate color names and attributes */
+ _("Standard Colors:\n"
+ " black, gray, red, brightred, green, brightgreen, brown,\n"
+ " yellow, blue, brightblue, magenta, brightmagenta, cyan,\n"
+ " brightcyan, lightgray and white\n\n"
+ "Extended colors, when 256 colors are available:\n"
+ " color16 to color255, or rgb000 to rgb555 and gray0 to gray23\n\n"
+ "Attributes:\n"
+ " bold, italic, underline, reverse, blink; append more with '+'\n")
+ );
+ /* *INDENT-ON* */
+
+ return g_option_group_new ("color", mc_args__loc__colors_string,
+ _("Color options"), NULL, NULL);
+
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gchar *
+mc_args_add_usage_info (void)
+{
+ gchar *s;
+
+ switch (mc_global.mc_run_mode)
+ {
+ case MC_RUN_EDITOR:
+ s = g_strdup_printf ("%s\n", _("[+lineno] file1[:lineno] [file2[:lineno]...]"));
+ break;
+ case MC_RUN_VIEWER:
+ s = g_strdup_printf ("%s\n", _("file"));
+ break;
+#ifdef USE_DIFF_VIEW
+ case MC_RUN_DIFFVIEWER:
+ s = g_strdup_printf ("%s\n", _("file1 file2"));
+ break;
+#endif /* USE_DIFF_VIEW */
+ case MC_RUN_FULL:
+ default:
+ s = g_strdup_printf ("%s\n", _("[this_dir] [other_panel_dir]"));
+ }
+
+ mc_args__loc__usage_string = s;
+
+ return mc_args__loc__usage_string;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mc_args_add_extended_info_to_help (void)
+{
+ mc_args__loc__footer_string = g_strdup_printf ("%s",
+ _
+ ("\n"
+ "Please send any bug reports (including the output of 'mc -V')\n"
+ "as tickets at www.midnight-commander.org\n"));
+ mc_args__loc__header_string =
+ g_strdup_printf (_("GNU Midnight Commander %s\n"), mc_global.mc_version);
+
+ g_option_context_set_description (context, mc_args__loc__footer_string);
+ g_option_context_set_summary (context, mc_args__loc__header_string);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GString *
+mc_args__convert_help_to_syscharset (const gchar * charset, const gchar * error_message_str,
+ const gchar * help_str)
+{
+ GString *buffer;
+ GIConv conv;
+ gchar *full_help_str;
+
+ buffer = g_string_new ("");
+ conv = g_iconv_open (charset, "UTF-8");
+ full_help_str = g_strdup_printf ("%s\n\n%s\n", error_message_str, help_str);
+
+ str_convert (conv, full_help_str, buffer);
+
+ g_free (full_help_str);
+ g_iconv_close (conv);
+
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+parse_mc_e_argument (const gchar * option_name, const gchar * value, gpointer data,
+ GError ** mcerror)
+{
+ (void) option_name;
+ (void) value;
+ (void) data;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ mc_global.mc_run_mode = MC_RUN_EDITOR;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+parse_mc_v_argument (const gchar * option_name, const gchar * value, gpointer data,
+ GError ** mcerror)
+{
+ (void) option_name;
+ (void) value;
+ (void) data;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ mc_global.mc_run_mode = MC_RUN_VIEWER;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create mcedit_arg_t object from vfs_path_t object and the line number.
+ *
+ * @param file_vpath file path object
+ * @param line_number line number. If value is 0, try to restore saved position.
+ * @return mcedit_arg_t object
+ */
+
+static mcedit_arg_t *
+mcedit_arg_vpath_new (vfs_path_t * file_vpath, long line_number)
+{
+ mcedit_arg_t *arg;
+
+ arg = g_new (mcedit_arg_t, 1);
+ arg->file_vpath = file_vpath;
+ arg->line_number = line_number;
+
+ return arg;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create mcedit_arg_t object from file name and the line number.
+ *
+ * @param file_name file name
+ * @param line_number line number. If value is 0, try to restore saved position.
+ * @return mcedit_arg_t object
+ */
+
+static mcedit_arg_t *
+mcedit_arg_new (const char *file_name, long line_number)
+{
+ return mcedit_arg_vpath_new (vfs_path_from_str (file_name), line_number);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get list of filenames (and line numbers) from command line, when mc called as editor
+ *
+ * @param argc count of all arguments
+ * @param argv array of strings, contains arguments
+ * @return list of mcedit_arg_t objects
+ */
+
+static GList *
+parse_mcedit_arguments (int argc, char **argv)
+{
+ GList *flist = NULL;
+ int i;
+ long first_line_number = -1;
+
+ for (i = 0; i < argc; i++)
+ {
+ char *tmp;
+ char *end, *p;
+ mcedit_arg_t *arg;
+
+ tmp = argv[i];
+
+ /*
+ * First, try to get line number as +lineno.
+ */
+ if (*tmp == '+')
+ {
+ long lineno;
+ char *error;
+
+ lineno = strtol (tmp + 1, &error, 10);
+
+ if (*error == '\0')
+ {
+ /* this is line number */
+ first_line_number = lineno;
+ continue;
+ }
+ /* this is file name */
+ }
+
+ /*
+ * Check for filename:lineno, followed by an optional colon.
+ * This format is used by many programs (especially compilers)
+ * in error messages and warnings. It is supported so that
+ * users can quickly copy and paste file locations.
+ */
+ end = tmp + strlen (tmp);
+ p = end;
+
+ if (p > tmp && p[-1] == ':')
+ p--;
+ while (p > tmp && g_ascii_isdigit ((gchar) p[-1]))
+ p--;
+
+ if (tmp < p && p < end && p[-1] == ':')
+ {
+ char *fname;
+ vfs_path_t *tmp_vpath, *fname_vpath;
+ struct stat st;
+
+ fname = g_strndup (tmp, p - 1 - tmp);
+ tmp_vpath = vfs_path_from_str (tmp);
+ fname_vpath = vfs_path_from_str (fname);
+
+ /*
+ * Check that the file before the colon actually exists.
+ * If it doesn't exist, create new file.
+ */
+ if (mc_stat (tmp_vpath, &st) == -1 && mc_stat (fname_vpath, &st) != -1)
+ {
+ arg = mcedit_arg_vpath_new (fname_vpath, atoi (p));
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ else
+ {
+ arg = mcedit_arg_vpath_new (tmp_vpath, 0);
+ vfs_path_free (fname_vpath, TRUE);
+ }
+
+ g_free (fname);
+ }
+ else
+ arg = mcedit_arg_new (tmp, 0);
+
+ flist = g_list_prepend (flist, arg);
+ }
+
+ if (flist == NULL)
+ flist = g_list_prepend (flist, mcedit_arg_new (NULL, 0));
+ else if (first_line_number != -1)
+ {
+ /* overwrite line number for first file */
+ GList *l;
+
+ l = g_list_last (flist);
+ ((mcedit_arg_t *) l->data)->line_number = first_line_number;
+ }
+
+ return flist;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_setup_run_mode (char **argv)
+{
+ const char *base;
+
+ base = x_basename (argv[0]);
+
+ if (strncmp (base, "mce", 3) == 0 || strcmp (base, "vi") == 0)
+ {
+ /* mce* or vi is link to mc */
+ mc_global.mc_run_mode = MC_RUN_EDITOR;
+ }
+ else if (strncmp (base, "mcv", 3) == 0 || strcmp (base, "view") == 0)
+ {
+ /* mcv* or view is link to mc */
+ mc_global.mc_run_mode = MC_RUN_VIEWER;
+ }
+#ifdef USE_DIFF_VIEW
+ else if (strncmp (base, "mcd", 3) == 0 || strcmp (base, "diff") == 0)
+ {
+ /* mcd* or diff is link to mc */
+ mc_global.mc_run_mode = MC_RUN_DIFFVIEWER;
+ }
+#endif /* USE_DIFF_VIEW */
+}
+
+gboolean
+mc_args_parse (int *argc, char ***argv, const char *translation_domain, GError ** mcerror)
+{
+ const gchar *_system_codepage;
+ gboolean ok = TRUE;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ _system_codepage = str_detect_termencoding ();
+
+#ifdef ENABLE_NLS
+ if (!str_isutf8 (_system_codepage))
+ bind_textdomain_codeset ("mc", "UTF-8");
+#endif
+
+ context = g_option_context_new (mc_args_add_usage_info ());
+
+ g_option_context_set_ignore_unknown_options (context, FALSE);
+
+ mc_args_add_extended_info_to_help ();
+
+ main_group = g_option_group_new ("main", _("Main options"), _("Main options"), NULL, NULL);
+
+ g_option_group_add_entries (main_group, argument_main_table);
+ g_option_context_set_main_group (context, main_group);
+ g_option_group_set_translation_domain (main_group, translation_domain);
+
+ terminal_group = g_option_group_new ("terminal", _("Terminal options"),
+ _("Terminal options"), NULL, NULL);
+
+ g_option_group_add_entries (terminal_group, argument_terminal_table);
+ g_option_context_add_group (context, terminal_group);
+ g_option_group_set_translation_domain (terminal_group, translation_domain);
+
+ color_group = mc_args_new_color_group ();
+
+ g_option_group_add_entries (color_group, argument_color_table);
+ g_option_context_add_group (context, color_group);
+ g_option_group_set_translation_domain (color_group, translation_domain);
+
+ if (!g_option_context_parse (context, argc, argv, mcerror))
+ {
+ if (*mcerror == NULL)
+ mc_propagate_error (mcerror, 0, "%s\n", _("Arguments parse error!"));
+ else
+ {
+ gchar *help_str;
+
+ help_str = g_option_context_get_help (context, TRUE, NULL);
+
+ if (str_isutf8 (_system_codepage))
+ mc_replace_error (mcerror, (*mcerror)->code, "%s\n\n%s\n", (*mcerror)->message,
+ help_str);
+ else
+ {
+ GString *full_help_str;
+
+ full_help_str =
+ mc_args__convert_help_to_syscharset (_system_codepage, (*mcerror)->message,
+ help_str);
+ mc_replace_error (mcerror, (*mcerror)->code, "%s", full_help_str->str);
+ g_string_free (full_help_str, TRUE);
+ }
+ g_free (help_str);
+ }
+
+ ok = FALSE;
+ }
+
+ g_option_context_free (context);
+ mc_args_clean_temp_help_strings ();
+
+#ifdef ENABLE_NLS
+ if (!str_isutf8 (_system_codepage))
+ bind_textdomain_codeset ("mc", _system_codepage);
+#endif
+
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_args_show_info (void)
+{
+ if (mc_args__show_version)
+ {
+ show_version ();
+ return FALSE;
+ }
+
+ if (mc_args__show_datadirs)
+ {
+ printf ("%s (%s)\n", mc_global.sysconfig_dir, mc_global.share_data_dir);
+ return FALSE;
+ }
+
+ if (mc_args__show_datadirs_extended)
+ {
+ show_datadirs_extended ();
+ return FALSE;
+ }
+
+#ifdef ENABLE_CONFIGURE_ARGS
+ if (mc_args__show_configure_opts)
+ {
+ show_configure_options ();
+ return FALSE;
+ }
+#endif
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mc_setup_by_args (int argc, char **argv, GError ** mcerror)
+{
+ char *tmp;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if (mc_args__force_colors)
+ mc_global.tty.disable_colors = FALSE;
+
+#ifdef ENABLE_SUBSHELL
+ if (mc_args__nouse_subshell)
+ mc_global.tty.use_subshell = FALSE;
+#endif /* ENABLE_SUBSHELL */
+
+#ifdef ENABLE_VFS_FTP
+ if (mc_args__netfs_logfile != NULL)
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str ("ftp://");
+ mc_setctl (vpath, VFS_SETCTL_LOGFILE, (void *) mc_args__netfs_logfile);
+ vfs_path_free (vpath, TRUE);
+ }
+#endif /* ENABLE_VFS_FTP */
+
+ tmp = (argc > 0) ? argv[1] : NULL;
+
+ switch (mc_global.mc_run_mode)
+ {
+ case MC_RUN_EDITOR:
+ mc_run_param0 = parse_mcedit_arguments (argc - 1, &argv[1]);
+ break;
+
+ case MC_RUN_VIEWER:
+ if (tmp == NULL)
+ {
+ mc_propagate_error (mcerror, 0, "%s\n", _("No arguments given to the viewer."));
+ return FALSE;
+ }
+
+ mc_run_param0 = g_strdup (tmp);
+ break;
+
+#ifdef USE_DIFF_VIEW
+ case MC_RUN_DIFFVIEWER:
+ if (argc < 3)
+ {
+ mc_propagate_error (mcerror, 0, "%s\n",
+ _("Two files are required to invoke the diffviewer."));
+ return FALSE;
+ }
+ MC_FALLTHROUGH;
+#endif /* USE_DIFF_VIEW */
+
+ case MC_RUN_FULL:
+ default:
+ /* set the current dir and the other dir for filemanager,
+ or two files for diff viewer */
+ if (tmp != NULL)
+ {
+ mc_run_param0 = g_strdup (tmp);
+ tmp = (argc > 1) ? argv[2] : NULL;
+ if (tmp != NULL)
+ mc_run_param1 = g_strdup (tmp);
+ }
+ break;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Free the mcedit_arg_t object.
+ *
+ * @param arg mcedit_arg_t object
+ */
+
+void
+mcedit_arg_free (mcedit_arg_t * arg)
+{
+ vfs_path_free (arg->file_vpath, TRUE);
+ g_free (arg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/args.h b/src/args.h
new file mode 100644
index 0000000..19099dd
--- /dev/null
+++ b/src/args.h
@@ -0,0 +1,55 @@
+#ifndef MC__ARGS_H
+#define MC__ARGS_H
+
+#include "lib/global.h" /* gboolean */
+#include "lib/vfs/vfs.h" /* vfs_path_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ vfs_path_t *file_vpath;
+ long line_number;
+} mcedit_arg_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern gboolean mc_args__force_xterm;
+extern gboolean mc_args__nomouse;
+extern gboolean mc_args__force_colors;
+extern gboolean mc_args__nokeymap;
+extern char *mc_args__last_wd_file;
+extern char *mc_args__netfs_logfile;
+extern char *mc_args__keymap_file;
+
+/*
+ * MC_RUN_FULL: dir for left panel
+ * MC_RUN_EDITOR: list of files to edit
+ * MC_RUN_VIEWER: file to view
+ * MC_RUN_DIFFVIEWER: first file to compare
+ */
+extern void *mc_run_param0;
+/*
+ * MC_RUN_FULL: dir for right panel
+ * MC_RUN_EDITOR: unused
+ * MC_RUN_VIEWER: unused
+ * MC_RUN_DIFFVIEWER: second file to compare
+ */
+extern char *mc_run_param1;
+
+/*** declarations of public functions ************************************************************/
+
+void mc_setup_run_mode (char **argv);
+gboolean mc_args_parse (int *argc, char ***argv, const char *translation_domain, GError ** mcerror);
+gboolean mc_args_show_info (void);
+gboolean mc_setup_by_args (int argc, char **argv, GError ** mcerror);
+
+void mcedit_arg_free (mcedit_arg_t * arg);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__ARGS_H */
diff --git a/src/background.c b/src/background.c
new file mode 100644
index 0000000..41a7f40
--- /dev/null
+++ b/src/background.c
@@ -0,0 +1,657 @@
+/* {{{ Copyright */
+
+/* Background support.
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1996
+
+ 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 background.c
+ * \brief Source: Background support
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h> /* waitpid() */
+
+#include "lib/global.h"
+
+#include "lib/unixcompat.h"
+#include "lib/tty/key.h" /* add_select_channel(), delete_select_channel() */
+#include "lib/widget.h" /* message() */
+#include "lib/event-types.h"
+
+#include "filemanager/fileopctx.h" /* file_op_context_t */
+
+#include "background.h"
+
+/*** global variables ****************************************************************************/
+
+#define MAXCALLARGS 4 /* Number of arguments supported */
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+enum ReturnType
+{
+ Return_String,
+ Return_Integer
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static int background_attention (int fd, void *closure);
+
+/*** file scope variables ************************************************************************/
+
+/* File descriptor for talking to our parent */
+static int parent_fd;
+
+/* File descriptor for messages from our parent */
+static int from_parent_fd;
+
+TaskList *task_list = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+register_task_running (file_op_context_t * ctx, pid_t pid, int fd, int to_child, char *info)
+{
+ TaskList *new;
+
+ new = g_new (TaskList, 1);
+ new->pid = pid;
+ new->info = info;
+ new->state = Task_Running;
+ new->next = task_list;
+ new->fd = fd;
+ new->to_child_fd = to_child;
+ task_list = new;
+
+ add_select_channel (fd, background_attention, ctx);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+destroy_task (pid_t pid)
+{
+ TaskList *p = task_list;
+ TaskList *prev = NULL;
+
+ while (p != NULL)
+ {
+ if (p->pid == pid)
+ {
+ int fd = p->fd;
+
+ if (prev != NULL)
+ prev->next = p->next;
+ else
+ task_list = p->next;
+ g_free (p->info);
+ g_free (p);
+ return fd;
+ }
+ prev = p;
+ p = p->next;
+ }
+
+ /* pid not found */
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ Parent handlers */
+
+/* Parent/child protocol
+ *
+ * the child (the background) process send the following:
+ * void *routine -- routine to be invoked in the parent
+ * int nargc -- number of arguments
+ * int type -- Return argument type.
+ *
+ * If the routine is zero, then it is a way to tell the parent
+ * that the process is dying.
+ *
+ * nargc arguments in the following format:
+ * int size of the coming block
+ * size bytes with the block
+ *
+ * Now, the parent loads all those and then invokes
+ * the routine with pointers to the information passed
+ * (we just support pointers).
+ *
+ * If the return type is integer:
+ *
+ * the parent then writes an int to the child with
+ * the return value from the routine and the values
+ * of any global variable that is modified in the parent
+ * currently: do_append and recursive_result.
+ *
+ * If the return type is a string:
+ *
+ * the parent writes the resulting string length
+ * if the result string was NULL or the empty string,
+ * then the length is zero.
+ * The parent then writes the string length and frees
+ * the result string.
+ */
+/*
+ * Receive requests from background process and invoke the
+ * specified routine
+ */
+
+static int
+reading_failed (int i, char **data)
+{
+ while (i >= 0)
+ g_free (data[i--]);
+ message (D_ERROR, _("Background protocol error"), "%s", _("Reading failed"));
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+background_attention (int fd, void *closure)
+{
+ file_op_context_t *ctx;
+ int have_ctx;
+ union
+ {
+ int (*have_ctx0) (int);
+ int (*have_ctx1) (int, char *);
+ int (*have_ctx2) (int, char *, char *);
+ int (*have_ctx3) (int, char *, char *, char *);
+ int (*have_ctx4) (int, char *, char *, char *, char *);
+
+ int (*non_have_ctx0) (file_op_context_t *, int);
+ int (*non_have_ctx1) (file_op_context_t *, int, char *);
+ int (*non_have_ctx2) (file_op_context_t *, int, char *, char *);
+ int (*non_have_ctx3) (file_op_context_t *, int, char *, char *, char *);
+ int (*non_have_ctx4) (file_op_context_t *, int, char *, char *, char *, char *);
+
+ char *(*ret_str0) (void);
+ char *(*ret_str1) (char *);
+ char *(*ret_str2) (char *, char *);
+ char *(*ret_str3) (char *, char *, char *);
+ char *(*ret_str4) (char *, char *, char *, char *);
+
+ void *pointer;
+ } routine;
+ /* void *routine; */
+ int argc, i, status;
+ char *data[MAXCALLARGS];
+ ssize_t bytes, ret;
+ TaskList *p;
+ int to_child_fd = -1;
+ enum ReturnType type;
+ const char *background_process_error = _("Background process error");
+
+ ctx = closure;
+
+ bytes = read (fd, &routine.pointer, sizeof (routine));
+ if (bytes == -1 || (size_t) bytes < (sizeof (routine)))
+ {
+ unregister_task_running (ctx->pid, fd);
+
+ if (waitpid (ctx->pid, &status, WNOHANG) == 0)
+ {
+ /* the process is still running, but it misbehaves - kill it */
+ kill (ctx->pid, SIGTERM);
+ message (D_ERROR, background_process_error, "%s", _("Unknown error in child"));
+ return 0;
+ }
+
+ /* 0 means happy end */
+ if (WIFEXITED (status) && (WEXITSTATUS (status) == 0))
+ return 0;
+
+ message (D_ERROR, background_process_error, "%s", _("Child died unexpectedly"));
+
+ return 0;
+ }
+
+ if (read (fd, &argc, sizeof (argc)) != sizeof (argc) ||
+ read (fd, &type, sizeof (type)) != sizeof (type) ||
+ read (fd, &have_ctx, sizeof (have_ctx)) != sizeof (have_ctx))
+ return reading_failed (-1, data);
+
+ if (argc > MAXCALLARGS)
+ message (D_ERROR, _("Background protocol error"), "%s",
+ _("Background process sent us a request for more arguments\n"
+ "than we can handle."));
+
+ if (have_ctx != 0 && read (fd, ctx, sizeof (*ctx)) != sizeof (*ctx))
+ return reading_failed (-1, data);
+
+ for (i = 0; i < argc; i++)
+ {
+ int size;
+
+ if (read (fd, &size, sizeof (size)) != sizeof (size))
+ return reading_failed (i - 1, data);
+
+ data[i] = g_malloc (size + 1);
+
+ if (read (fd, data[i], size) != size)
+ return reading_failed (i, data);
+
+ data[i][size] = '\0'; /* NULL terminate the blocks (they could be strings) */
+ }
+
+ /* Find child task info by descriptor */
+ /* Find before call, because process can destroy self after */
+ for (p = task_list; p != NULL; p = p->next)
+ if (p->fd == fd)
+ break;
+
+ if (p != NULL)
+ to_child_fd = p->to_child_fd;
+
+ if (to_child_fd == -1)
+ message (D_ERROR, background_process_error, "%s", _("Unknown error in child"));
+ else if (type == Return_Integer)
+ {
+ /* Handle the call */
+
+ int result = 0;
+
+ if (have_ctx == 0)
+ switch (argc)
+ {
+ case 0:
+ result = routine.have_ctx0 (Background);
+ break;
+ case 1:
+ result = routine.have_ctx1 (Background, data[0]);
+ break;
+ case 2:
+ result = routine.have_ctx2 (Background, data[0], data[1]);
+ break;
+ case 3:
+ result = routine.have_ctx3 (Background, data[0], data[1], data[2]);
+ break;
+ case 4:
+ result = routine.have_ctx4 (Background, data[0], data[1], data[2], data[3]);
+ break;
+ default:
+ break;
+ }
+ else
+ switch (argc)
+ {
+ case 0:
+ result = routine.non_have_ctx0 (ctx, Background);
+ break;
+ case 1:
+ result = routine.non_have_ctx1 (ctx, Background, data[0]);
+ break;
+ case 2:
+ result = routine.non_have_ctx2 (ctx, Background, data[0], data[1]);
+ break;
+ case 3:
+ result = routine.non_have_ctx3 (ctx, Background, data[0], data[1], data[2]);
+ break;
+ case 4:
+ result =
+ routine.non_have_ctx4 (ctx, Background, data[0], data[1], data[2], data[3]);
+ break;
+ default:
+ break;
+ }
+
+ /* Send the result code and the value for shared variables */
+ ret = write (to_child_fd, &result, sizeof (result));
+ if (have_ctx != 0 && to_child_fd != -1)
+ ret = write (to_child_fd, ctx, sizeof (*ctx));
+ }
+ else if (type == Return_String)
+ {
+ int len;
+ char *resstr = NULL;
+
+ /* FIXME: string routines should also use the Foreground/Background
+ * parameter. Currently, this is not used here
+ */
+ switch (argc)
+ {
+ case 0:
+ resstr = routine.ret_str0 ();
+ break;
+ case 1:
+ resstr = routine.ret_str1 (data[0]);
+ break;
+ case 2:
+ resstr = routine.ret_str2 (data[0], data[1]);
+ break;
+ case 3:
+ resstr = routine.ret_str3 (data[0], data[1], data[2]);
+ break;
+ case 4:
+ resstr = routine.ret_str4 (data[0], data[1], data[2], data[3]);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (resstr != NULL)
+ {
+ len = strlen (resstr);
+ ret = write (to_child_fd, &len, sizeof (len));
+ if (len != 0)
+ ret = write (to_child_fd, resstr, len);
+ g_free (resstr);
+ }
+ else
+ {
+ len = 0;
+ ret = write (to_child_fd, &len, sizeof (len));
+ }
+ }
+
+ for (i = 0; i < argc; i++)
+ g_free (data[i]);
+
+ repaint_screen ();
+
+ (void) ret;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* }}} */
+
+/* {{{ client RPC routines */
+
+/* Sends the header for a call to a routine in the parent process. If the file
+ * operation context is not NULL, then it requests that the first parameter of
+ * the call be a file operation context.
+ */
+
+static void
+parent_call_header (void *routine, int argc, enum ReturnType type, file_op_context_t * ctx)
+{
+ int have_ctx;
+ ssize_t ret;
+
+ have_ctx = ctx != NULL ? 1 : 0;
+
+ ret = write (parent_fd, &routine, sizeof (routine));
+ ret = write (parent_fd, &argc, sizeof (argc));
+ ret = write (parent_fd, &type, sizeof (type));
+ ret = write (parent_fd, &have_ctx, sizeof (have_ctx));
+ if (have_ctx != 0)
+ ret = write (parent_fd, ctx, sizeof (*ctx));
+
+ (void) ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+parent_va_call (void *routine, gpointer data, int argc, va_list ap)
+{
+ int i;
+ ssize_t ret;
+ file_op_context_t *ctx = (file_op_context_t *) data;
+
+ parent_call_header (routine, argc, Return_Integer, ctx);
+ for (i = 0; i < argc; i++)
+ {
+ int len;
+ void *value;
+
+ len = va_arg (ap, int);
+ value = va_arg (ap, void *);
+ ret = write (parent_fd, &len, sizeof (len));
+ ret = write (parent_fd, value, len);
+ }
+
+ ret = read (from_parent_fd, &i, sizeof (i));
+ if (ctx != NULL)
+ ret = read (from_parent_fd, ctx, sizeof (*ctx));
+
+ (void) ret;
+
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+parent_va_call_string (void *routine, int argc, va_list ap)
+{
+ char *str;
+ int i;
+
+ parent_call_header (routine, argc, Return_String, NULL);
+ for (i = 0; i < argc; i++)
+ {
+ int len;
+ void *value;
+
+ len = va_arg (ap, int);
+ value = va_arg (ap, void *);
+ if (write (parent_fd, &len, sizeof (len)) != sizeof (len) ||
+ write (parent_fd, value, len) != len)
+ return NULL;
+ }
+
+ if (read (from_parent_fd, &i, sizeof (i)) != sizeof (i) || i == 0)
+ return NULL;
+
+ str = g_malloc (i + 1);
+ if (read (from_parent_fd, str, i) != i)
+ {
+ g_free (str);
+ return NULL;
+ }
+ str[i] = '\0';
+ return str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+unregister_task_running (pid_t pid, int fd)
+{
+ destroy_task (pid);
+ delete_select_channel (fd);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+unregister_task_with_pid (pid_t pid)
+{
+ int fd;
+
+ fd = destroy_task (pid);
+ if (fd != -1)
+ delete_select_channel (fd);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Try to make the Midnight Commander a background job
+ *
+ * Returns:
+ * 1 for parent
+ * 0 for child
+ * -1 on failure
+ */
+int
+do_background (file_op_context_t * ctx, char *info)
+{
+ int comm[2]; /* control connection stream */
+ int back_comm[2]; /* back connection */
+ pid_t pid;
+
+ if (pipe (comm) == -1)
+ return (-1);
+
+ if (pipe (back_comm) == -1)
+ {
+ (void) close (comm[0]);
+ (void) close (comm[1]);
+
+ return (-1);
+ }
+
+ pid = fork ();
+ if (pid == -1)
+ {
+ int saved_errno = errno;
+
+ (void) close (comm[0]);
+ (void) close (comm[1]);
+ (void) close (back_comm[0]);
+ (void) close (back_comm[1]);
+ errno = saved_errno;
+
+ return (-1);
+ }
+
+ if (pid == 0)
+ {
+ int nullfd;
+
+ parent_fd = comm[1];
+ from_parent_fd = back_comm[0];
+
+ mc_global.we_are_background = TRUE;
+ top_dlg = NULL;
+
+ /* Make stdin/stdout/stderr point somewhere */
+ close (STDIN_FILENO);
+ close (STDOUT_FILENO);
+ close (STDERR_FILENO);
+
+ nullfd = open ("/dev/null", O_RDWR);
+ if (nullfd != -1)
+ {
+ while (dup2 (nullfd, STDIN_FILENO) == -1 && errno == EINTR)
+ ;
+ while (dup2 (nullfd, STDOUT_FILENO) == -1 && errno == EINTR)
+ ;
+ while (dup2 (nullfd, STDERR_FILENO) == -1 && errno == EINTR)
+ ;
+ close (nullfd);
+ }
+
+ return 0;
+ }
+ else
+ {
+ ctx->pid = pid;
+ register_task_running (ctx, pid, comm[0], back_comm[1], info);
+ return 1;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+parent_call (void *routine, file_op_context_t * ctx, int argc, ...)
+{
+ int ret;
+ va_list ap;
+
+ va_start (ap, argc);
+ ret = parent_va_call (routine, (gpointer) ctx, argc, ap);
+ va_end (ap);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+parent_call_string (void *routine, int argc, ...)
+{
+ va_list ap;
+ char *str;
+
+ va_start (ap, argc);
+ str = parent_va_call_string (routine, argc, ap);
+ va_end (ap);
+
+ return str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+gboolean
+background_parent_call (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ ev_background_parent_call_t *event_data = (ev_background_parent_call_t *) data;
+
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+
+ event_data->ret.i =
+ parent_va_call (event_data->routine, event_data->ctx, event_data->argc, event_data->ap);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+gboolean
+background_parent_call_string (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ ev_background_parent_call_t *event_data = (ev_background_parent_call_t *) data;
+
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+
+ event_data->ret.s =
+ parent_va_call_string (event_data->routine, event_data->argc, event_data->ap);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/background.h b/src/background.h
new file mode 100644
index 0000000..a8b0323
--- /dev/null
+++ b/src/background.h
@@ -0,0 +1,54 @@
+/** \file background.h
+ * \brief Header: Background support
+ */
+
+#ifndef MC__BACKGROUND_H
+#define MC__BACKGROUND_H
+
+#include <sys/types.h> /* pid_t */
+#include "filemanager/fileopctx.h"
+/*** typedefs(not structures) and defined constants **********************************************/
+
+enum TaskState
+{
+ Task_Running,
+ Task_Stopped
+};
+
+typedef struct TaskList
+{
+ int fd;
+ int to_child_fd;
+ pid_t pid;
+ int state;
+ char *info;
+ struct TaskList *next;
+} TaskList;
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern TaskList *task_list;
+
+/*** declarations of public functions ************************************************************/
+
+int do_background (file_op_context_t * ctx, char *info);
+int parent_call (void *routine, file_op_context_t * ctx, int argc, ...);
+char *parent_call_string (void *routine, int argc, ...);
+
+void unregister_task_running (pid_t pid, int fd);
+void unregister_task_with_pid (pid_t pid);
+
+gboolean background_parent_call (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data);
+
+gboolean
+background_parent_call_string (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__BACKGROUND_H */
diff --git a/src/clipboard.c b/src/clipboard.c
new file mode 100644
index 0000000..3c31cb0
--- /dev/null
+++ b/src/clipboard.c
@@ -0,0 +1,268 @@
+/*
+ Util for external clipboard.
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2010.
+ Andrew Borodin <aborodin@vmail.ru>, 2014.
+
+ 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 <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+#include "lib/fileloc.h"
+#include "lib/mcconfig.h"
+#include "lib/util.h"
+#include "lib/event.h"
+
+#include "lib/vfs/vfs.h"
+
+#include "src/execute.h"
+
+#include "clipboard.h"
+
+/*** global variables ****************************************************************************/
+
+/* path to X clipboard utility */
+char *clipboard_store_path = NULL;
+char *clipboard_paste_path = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static const int clip_open_flags = O_CREAT | O_WRONLY | O_TRUNC | O_BINARY;
+static const mode_t clip_open_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+gboolean
+clipboard_file_to_ext_clip (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ char *tmp, *cmd;
+
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+ (void) data;
+
+ if (clipboard_store_path == NULL || clipboard_store_path[0] == '\0')
+ return TRUE;
+
+ tmp = mc_config_get_full_path (EDIT_HOME_CLIP_FILE);
+ cmd = g_strconcat (clipboard_store_path, " ", tmp, " 2>/dev/null", (char *) NULL);
+
+ if (cmd != NULL)
+ my_system (EXECUTE_AS_SHELL, mc_global.shell->path, cmd);
+
+ g_free (cmd);
+ g_free (tmp);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+gboolean
+clipboard_file_from_ext_clip (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ mc_pipe_t *p;
+ int file = -1;
+
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+ (void) data;
+
+ if (clipboard_paste_path == NULL || clipboard_paste_path[0] == '\0')
+ return TRUE;
+
+ p = mc_popen (clipboard_paste_path, TRUE, TRUE, NULL);
+ if (p == NULL)
+ return TRUE; /* don't show error message */
+
+ p->out.null_term = FALSE;
+ p->err.null_term = TRUE;
+
+ while (TRUE)
+ {
+ GError *error = NULL;
+
+ p->out.len = MC_PIPE_BUFSIZE;
+ p->err.len = MC_PIPE_BUFSIZE;
+
+ mc_pread (p, &error);
+
+ if (error != NULL)
+ {
+ /* don't show error message */
+ g_error_free (error);
+ break;
+ }
+
+ /* ignore stderr and get stdout */
+ if (p->out.len == MC_PIPE_STREAM_EOF || p->out.len == MC_PIPE_ERROR_READ)
+ break;
+
+ if (p->out.len > 0)
+ {
+ ssize_t nwrite;
+
+ if (file < 0)
+ {
+ vfs_path_t *fname_vpath;
+
+ fname_vpath = mc_config_get_full_vpath (EDIT_HOME_CLIP_FILE);
+ file = mc_open (fname_vpath, clip_open_flags, clip_open_mode);
+ vfs_path_free (fname_vpath, TRUE);
+
+ if (file < 0)
+ break;
+ }
+
+ nwrite = mc_write (file, p->out.buf, p->out.len);
+ (void) nwrite;
+ }
+ }
+
+ if (file >= 0)
+ mc_close (file);
+
+ mc_pclose (p, NULL);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+gboolean
+clipboard_text_to_file (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ int file;
+ vfs_path_t *fname_vpath = NULL;
+ size_t str_len;
+ const char *text = (const char *) data;
+
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+
+ if (text == NULL)
+ return FALSE;
+
+ fname_vpath = mc_config_get_full_vpath (EDIT_HOME_CLIP_FILE);
+ file = mc_open (fname_vpath, clip_open_flags, clip_open_mode);
+ vfs_path_free (fname_vpath, TRUE);
+
+ if (file == -1)
+ return TRUE;
+
+ str_len = strlen (text);
+ {
+ ssize_t ret;
+
+ ret = mc_write (file, text, str_len);
+ (void) ret;
+ }
+ mc_close (file);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+gboolean
+clipboard_text_from_file (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ char buf[BUF_LARGE];
+ FILE *f;
+ char *fname = NULL;
+ gboolean first = TRUE;
+ ev_clipboard_text_from_file_t *event_data = (ev_clipboard_text_from_file_t *) data;
+
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+
+ fname = mc_config_get_full_path (EDIT_HOME_CLIP_FILE);
+ f = fopen (fname, "r");
+ g_free (fname);
+
+ if (f == NULL)
+ {
+ event_data->ret = FALSE;
+ return TRUE;
+ }
+
+ *(event_data->text) = NULL;
+
+ while (fgets (buf, sizeof (buf), f))
+ {
+ size_t len;
+
+ len = strlen (buf);
+ if (len > 0)
+ {
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+
+ if (first)
+ {
+ first = FALSE;
+ *(event_data->text) = g_strdup (buf);
+ }
+ else
+ {
+ /* remove \n on EOL */
+ char *tmp;
+
+ tmp = g_strconcat (*(event_data->text), " ", buf, (char *) NULL);
+ g_free (*(event_data->text));
+ *(event_data->text) = tmp;
+ }
+ }
+ }
+
+ fclose (f);
+ event_data->ret = (*(event_data->text) != NULL);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/clipboard.h b/src/clipboard.h
new file mode 100644
index 0000000..9b2fc22
--- /dev/null
+++ b/src/clipboard.h
@@ -0,0 +1,33 @@
+/** \file clipboard.h
+ * \brief Header: Util for external clipboard
+ */
+
+#ifndef MC__CLIPBOARD_H
+#define MC__CLIPBOARD_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern char *clipboard_store_path;
+extern char *clipboard_paste_path;
+
+/*** declarations of public functions ************************************************************/
+
+gboolean clipboard_file_to_ext_clip (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data);
+gboolean clipboard_file_from_ext_clip (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data);
+
+gboolean clipboard_text_to_file (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data);
+gboolean clipboard_text_from_file (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__CLIPBOARD_H */
diff --git a/src/cons.handler.c b/src/cons.handler.c
new file mode 100644
index 0000000..d747ff3
--- /dev/null
+++ b/src/cons.handler.c
@@ -0,0 +1,502 @@
+/*
+ Client interface for General purpose Linux console save/restore server
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ 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 cons.handler.c
+ * \brief Source: client %interface for General purpose Linux console save/restore server
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#ifdef __FreeBSD__
+#include <sys/consio.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#endif
+
+#include "lib/global.h"
+
+#include "lib/unixcompat.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h" /* tty_set_normal_attrs */
+#include "lib/tty/win.h"
+#include "lib/util.h" /* mc_build_filename() */
+
+#include "consaver/cons.saver.h"
+
+/*** global variables ****************************************************************************/
+
+#ifdef __linux__
+int cons_saver_pid = 1;
+#endif /* __linux__ */
+
+/*** file scope macro definitions ****************************************************************/
+
+#if defined(__FreeBSD__)
+#define FD_OUT 1
+#define cursor_to(x, y) \
+do \
+{ \
+ printf("\x1B[%d;%df", (y) + 1, (x) + 1); \
+ fflush(stdout); \
+} while (0)
+#endif /* __linux__ */
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+#ifdef __linux__
+/* The cons saver can't have a pid of 1, used to prevent bunches of
+ * #ifdef linux */
+static int pipefd1[2] = { -1, -1 };
+static int pipefd2[2] = { -1, -1 };
+#elif defined(__FreeBSD__)
+static struct scrshot screen_shot;
+static struct vid_info screen_info;
+#endif /* __linux__ */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef __linux__
+static void
+show_console_contents_linux (int starty, unsigned char begin_line, unsigned char end_line)
+{
+ unsigned char message = 0;
+ unsigned short bytes = 0;
+ int i;
+ ssize_t ret;
+
+ /* Is tty console? */
+ if (mc_global.tty.console_flag == '\0')
+ return;
+ /* Paranoid: Is the cons.saver still running? */
+ if (cons_saver_pid < 1 || kill (cons_saver_pid, SIGCONT))
+ {
+ cons_saver_pid = 0;
+ mc_global.tty.console_flag = '\0';
+ return;
+ }
+
+ /* Send command to the console handler */
+ message = CONSOLE_CONTENTS;
+ ret = write (pipefd1[1], &message, 1);
+ /* Check for outdated cons.saver */
+ ret = read (pipefd2[0], &message, 1);
+ if (message != CONSOLE_CONTENTS)
+ return;
+
+ /* Send the range of lines that we want */
+ ret = write (pipefd1[1], &begin_line, 1);
+ ret = write (pipefd1[1], &end_line, 1);
+ /* Read the corresponding number of bytes */
+ ret = read (pipefd2[0], &bytes, 2);
+
+ /* Read the bytes and output them */
+ for (i = 0; i < bytes; i++)
+ {
+ if ((i % COLS) == 0)
+ tty_gotoyx (starty + (i / COLS), 0);
+ ret = read (pipefd2[0], &message, 1);
+ tty_print_char (message);
+ }
+
+ /* Read the value of the mc_global.tty.console_flag */
+ ret = read (pipefd2[0], &message, 1);
+ (void) ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+handle_console_linux (console_action_t action)
+{
+ int status;
+
+ switch (action)
+ {
+ case CONSOLE_INIT:
+ /* Close old pipe ends in case it is the 2nd time we run cons.saver */
+ status = close (pipefd1[1]);
+ status = close (pipefd2[0]);
+ /* Create two pipes for communication */
+ if (!((pipe (pipefd1) == 0) && ((pipe (pipefd2)) == 0)))
+ {
+ mc_global.tty.console_flag = '\0';
+ break;
+ }
+ /* Get the console saver running */
+ cons_saver_pid = fork ();
+ if (cons_saver_pid < 0)
+ {
+ /* Cannot fork */
+ /* Delete pipes */
+ status = close (pipefd1[1]);
+ status = close (pipefd1[0]);
+ status = close (pipefd2[1]);
+ status = close (pipefd2[0]);
+ mc_global.tty.console_flag = '\0';
+ }
+ else if (cons_saver_pid > 0)
+ {
+ /* Parent */
+ /* Close the extra pipe ends */
+ status = close (pipefd1[0]);
+ status = close (pipefd2[1]);
+ /* Was the child successful? */
+ status = read (pipefd2[0], &mc_global.tty.console_flag, 1);
+ if (mc_global.tty.console_flag == '\0')
+ {
+ pid_t ret;
+ status = close (pipefd1[1]);
+ status = close (pipefd2[0]);
+ ret = waitpid (cons_saver_pid, &status, 0);
+ (void) ret;
+ }
+ }
+ else
+ {
+ /* Child */
+ char *tty_name;
+
+ /* Close the extra pipe ends */
+ status = close (pipefd1[1]);
+ status = close (pipefd2[0]);
+ tty_name = ttyname (0);
+ /* Bind the pipe 0 to the standard input */
+ do
+ {
+ gboolean ok;
+
+ if (dup2 (pipefd1[0], STDIN_FILENO) == -1)
+ break;
+ status = close (pipefd1[0]);
+ /* Bind the pipe 1 to the standard output */
+ if (dup2 (pipefd2[1], STDOUT_FILENO) == -1)
+ break;
+
+ status = close (pipefd2[1]);
+ /* Bind standard error to /dev/null */
+ status = open ("/dev/null", O_WRONLY);
+ if (status == -1)
+ break;
+ ok = dup2 (status, STDERR_FILENO) != -1;
+ status = close (status);
+ if (!ok)
+ break;
+
+ if (tty_name != NULL)
+ {
+ char *mc_conssaver;
+
+ /* Exec the console save/restore handler */
+ mc_conssaver = mc_build_filename (SAVERDIR, "cons.saver", (char *) NULL);
+ execl (mc_conssaver, "cons.saver", tty_name, (char *) NULL);
+ }
+ /* Console is not a tty or execl() failed */
+ }
+ while (0);
+ mc_global.tty.console_flag = '\0';
+ status = write (1, &mc_global.tty.console_flag, 1);
+ my_exit (3);
+ } /* if (cons_saver_pid ...) */
+ break;
+
+ case CONSOLE_DONE:
+ case CONSOLE_SAVE:
+ case CONSOLE_RESTORE:
+ /* Is tty console? */
+ if (mc_global.tty.console_flag == '\0')
+ return;
+ /* Paranoid: Is the cons.saver still running? */
+ if (cons_saver_pid < 1 || kill (cons_saver_pid, SIGCONT))
+ {
+ cons_saver_pid = 0;
+ mc_global.tty.console_flag = '\0';
+ return;
+ }
+ /* Send command to the console handler */
+ {
+ /* Convert enum (i.e. int) to char to write the correct value
+ * (the least byte) regardless of machine endianness. */
+ char act = (char) action;
+
+ status = write (pipefd1[1], &act, 1);
+ }
+ if (action != CONSOLE_DONE)
+ {
+ /* Wait the console handler to do its job */
+ status = read (pipefd2[0], &mc_global.tty.console_flag, 1);
+ }
+ if (action == CONSOLE_DONE || mc_global.tty.console_flag == '\0')
+ {
+ /* We are done -> Let's clean up */
+ pid_t ret;
+ close (pipefd1[1]);
+ close (pipefd2[0]);
+ ret = waitpid (cons_saver_pid, &status, 0);
+ (void) ret;
+ mc_global.tty.console_flag = '\0';
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+#elif defined(__FreeBSD__)
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * FreeBSD support copyright (C) 2003 Alexander Serkov <serkov@ukrpost.net>.
+ * Support for screenmaps by Max Khon <fjoe@FreeBSD.org>
+ */
+
+static void
+console_init (void)
+{
+ if (mc_global.tty.console_flag != '\0')
+ return;
+
+ screen_info.size = sizeof (screen_info);
+ if (ioctl (FD_OUT, CONS_GETINFO, &screen_info) == -1)
+ return;
+
+ memset (&screen_shot, 0, sizeof (screen_shot));
+ screen_shot.xsize = screen_info.mv_csz;
+ screen_shot.ysize = screen_info.mv_rsz;
+ screen_shot.buf = g_try_malloc (screen_info.mv_csz * screen_info.mv_rsz * 2);
+ if (screen_shot.buf != NULL)
+ mc_global.tty.console_flag = '\001';
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+set_attr (unsigned attr)
+{
+ /*
+ * Convert color indices returned by SCRSHOT (red=4, green=2, blue=1)
+ * to indices for ANSI sequences (red=1, green=2, blue=4).
+ */
+ static const int color_map[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
+ int bc, tc;
+
+ tc = attr & 0xF;
+ bc = (attr >> 4) & 0xF;
+
+ printf ("\x1B[%d;%d;3%d;4%dm", (bc & 8) ? 5 : 25, (tc & 8) ? 1 : 22,
+ color_map[tc & 7], color_map[bc & 7]);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+console_restore (void)
+{
+ int i, last;
+
+ if (mc_global.tty.console_flag == '\0')
+ return;
+
+ cursor_to (0, 0);
+
+ /* restoring all content up to cursor position */
+ last = screen_info.mv_row * screen_info.mv_csz + screen_info.mv_col;
+ for (i = 0; i < last; ++i)
+ {
+ set_attr ((screen_shot.buf[i] >> 8) & 0xFF);
+ putc (screen_shot.buf[i] & 0xFF, stdout);
+ }
+
+ /* restoring cursor color */
+ set_attr ((screen_shot.buf[last] >> 8) & 0xFF);
+
+ fflush (stdout);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+console_shutdown (void)
+{
+ if (mc_global.tty.console_flag == '\0')
+ return;
+
+ g_free (screen_shot.buf);
+
+ mc_global.tty.console_flag = '\0';
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+console_save (void)
+{
+ int i;
+ scrmap_t map;
+ scrmap_t revmap;
+
+ if (mc_global.tty.console_flag == '\0')
+ return;
+
+ /* screen_info.size is already set in console_init() */
+ if (ioctl (FD_OUT, CONS_GETINFO, &screen_info) == -1)
+ {
+ console_shutdown ();
+ return;
+ }
+
+ /* handle console resize */
+ if (screen_info.mv_csz != screen_shot.xsize || screen_info.mv_rsz != screen_shot.ysize)
+ {
+ console_shutdown ();
+ console_init ();
+ }
+
+ if (ioctl (FD_OUT, CONS_SCRSHOT, &screen_shot) == -1)
+ {
+ console_shutdown ();
+ return;
+ }
+
+ if (ioctl (FD_OUT, GIO_SCRNMAP, &map) == -1)
+ {
+ console_shutdown ();
+ return;
+ }
+
+ for (i = 0; i < 256; i++)
+ {
+ char *p = memchr (map.scrmap, i, 256);
+ revmap.scrmap[i] = p ? p - map.scrmap : i;
+ }
+
+ for (i = 0; i < screen_shot.xsize * screen_shot.ysize; i++)
+ {
+ /* *INDENT-OFF* */
+ screen_shot.buf[i] = (screen_shot.buf[i] & 0xff00)
+ | (unsigned char) revmap.scrmap[screen_shot.buf[i] & 0xff];
+ /* *INDENT-ON* */
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+show_console_contents_freebsd (int starty, unsigned char begin_line, unsigned char end_line)
+{
+ int col, line;
+ char c;
+
+ if (mc_global.tty.console_flag == '\0')
+ return;
+
+ for (line = begin_line; line <= end_line; line++)
+ {
+ tty_gotoyx (starty + line - begin_line, 0);
+ for (col = 0; col < MIN (COLS, screen_info.mv_csz); col++)
+ {
+ c = screen_shot.buf[line * screen_info.mv_csz + col] & 0xFF;
+ tty_print_char (c);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+handle_console_freebsd (console_action_t action)
+{
+ switch (action)
+ {
+ case CONSOLE_INIT:
+ console_init ();
+ break;
+
+ case CONSOLE_DONE:
+ console_shutdown ();
+ break;
+
+ case CONSOLE_SAVE:
+ console_save ();
+ break;
+
+ case CONSOLE_RESTORE:
+ console_restore ();
+ break;
+ default:
+ break;
+ }
+}
+#endif /* __FreeBSD__ */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+show_console_contents (int starty, unsigned char begin_line, unsigned char end_line)
+{
+ tty_set_normal_attrs ();
+
+ if (look_for_rxvt_extensions ())
+ {
+ show_rxvt_contents (starty, begin_line, end_line);
+ return;
+ }
+#ifdef __linux__
+ show_console_contents_linux (starty, begin_line, end_line);
+#elif defined (__FreeBSD__)
+ show_console_contents_freebsd (starty, begin_line, end_line);
+#else
+ mc_global.tty.console_flag = '\0';
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+handle_console (console_action_t action)
+{
+ (void) action;
+
+ if (look_for_rxvt_extensions ())
+ return;
+
+#ifdef __linux__
+ handle_console_linux (action);
+#elif defined (__FreeBSD__)
+ handle_console_freebsd (action);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/consaver/Makefile.am b/src/consaver/Makefile.am
new file mode 100644
index 0000000..ba446c1
--- /dev/null
+++ b/src/consaver/Makefile.am
@@ -0,0 +1,7 @@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+pkglibexec_PROGRAMS = cons.saver
+
+cons_saver_SOURCES = cons.saver.c
+
+AM_CPPFLAGS = -I$(top_srcdir)
diff --git a/src/consaver/Makefile.in b/src/consaver/Makefile.in
new file mode 100644
index 0000000..1945611
--- /dev/null
+++ b/src/consaver/Makefile.in
@@ -0,0 +1,777 @@
+# 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@
+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@
+pkglibexec_PROGRAMS = cons.saver$(EXEEXT)
+subdir = src/consaver
+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 =
+am__installdirs = "$(DESTDIR)$(pkglibexecdir)"
+PROGRAMS = $(pkglibexec_PROGRAMS)
+am_cons_saver_OBJECTS = cons.saver.$(OBJEXT)
+cons_saver_OBJECTS = $(am_cons_saver_OBJECTS)
+cons_saver_LDADD = $(LDADD)
+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)/cons.saver.Po
+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 = $(cons_saver_SOURCES)
+DIST_SOURCES = $(cons_saver_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)
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+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@
+cons_saver_SOURCES = cons.saver.c
+AM_CPPFLAGS = -I$(top_srcdir)
+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/consaver/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/consaver/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):
+install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-pkglibexecPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files
+
+clean-pkglibexecPROGRAMS:
+ @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+cons.saver$(EXEEXT): $(cons_saver_OBJECTS) $(cons_saver_DEPENDENCIES) $(EXTRA_cons_saver_DEPENDENCIES)
+ @rm -f cons.saver$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(cons_saver_OBJECTS) $(cons_saver_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cons.saver.Po@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 $(PROGRAMS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkglibexecdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-pkglibexecPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/cons.saver.Po
+ -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-pkglibexecPROGRAMS
+
+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)/cons.saver.Po
+ -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: uninstall-pkglibexecPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-pkglibexecPROGRAMS \
+ 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-pkglibexecPROGRAMS 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 uninstall-pkglibexecPROGRAMS
+
+.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/consaver/cons.saver.c b/src/consaver/cons.saver.c
new file mode 100644
index 0000000..4867ab6
--- /dev/null
+++ b/src/consaver/cons.saver.c
@@ -0,0 +1,289 @@
+/*
+ General purpose Linux console screen save/restore server
+
+ This program should be setuid vcsa and /dev/vcsa* should be
+ owned by the same user too.
+
+ Original idea from Unix Interactive Tools version 3.2b (tty.c)
+ This code requires root privileges.
+ You may want to make the cons.saver setuid root.
+ The code should be safe even if it is setuid but who knows?
+
+ Partly rewritten by Jakub Jelinek <jakub@redhat.com>.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ 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/>.
+ */
+
+/* This code does _not_ need to be setuid root. However, it needs
+ read/write access to /dev/vcsa* (which is privileged
+ operation). You should create user vcsa, make cons.saver setuid
+ user vcsa, and make all vcsa's owned by user vcsa.
+
+ Seeing other peoples consoles is bad thing, but believe me, full
+ root is even worse. */
+
+/** \file cons.saver.c
+ * \brief Source: general purpose Linux console screen save/restore server
+ *
+ * This code does _not_ need to be setuid root. However, it needs
+ * read/write access to /dev/vcsa* (which is privileged
+ * operation). You should create user vcsa, make cons.saver setuid
+ * user vcsa, and make all vcsa's owned by user vcsa.
+ * Seeing other peoples consoles is bad thing, but believe me, full
+ * root is even worse.
+ */
+
+#include <config.h>
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#include <termios.h>
+
+#include "lib/unixcompat.h" /* STDERR_FILENO */
+
+#define LINUX_CONS_SAVER_C
+#include "cons.saver.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+send_contents (char *buffer, unsigned int columns, unsigned int rows)
+{
+ unsigned char begin_line = 0, end_line = 0;
+ unsigned int lastline, lc_index, x;
+ unsigned char message, outbuf[1024], *p;
+ unsigned short bytes;
+
+ lc_index = 2 * rows * columns;
+ for (lastline = rows; lastline > 0; lastline--)
+ for (x = 0; x < columns; x++)
+ {
+ lc_index -= 2;
+ if (buffer[lc_index] != ' ')
+ goto out;
+ }
+ out:
+
+ message = CONSOLE_CONTENTS;
+ if (write (1, &message, 1) != 1)
+ return;
+ if (read (0, &begin_line, 1) != 1)
+ return;
+ if (read (0, &end_line, 1) != 1)
+ return;
+ if (begin_line > lastline)
+ begin_line = lastline;
+ if (end_line > lastline)
+ end_line = lastline;
+
+ lc_index = (end_line - begin_line) * columns;
+ bytes = lc_index;
+
+ if (write (1, &bytes, 2) != 2)
+ return;
+ if (bytes == 0)
+ return;
+
+ p = outbuf;
+ for (lc_index = 2 * begin_line * columns; lc_index < 2 * end_line * columns; lc_index += 2)
+ {
+ *p++ = buffer[lc_index];
+ if (p == outbuf + sizeof (outbuf))
+ {
+ if (write (1, outbuf, sizeof (outbuf)) != sizeof (outbuf))
+ return;
+ p = outbuf;
+ }
+ }
+
+ if (p != outbuf)
+ if (write (1, outbuf, p - outbuf) < (p - outbuf))
+ return;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void __attribute__ ((noreturn)) die (void)
+{
+ unsigned char zero = 0;
+ ssize_t ret;
+ ret = write (1, &zero, 1);
+ (void) ret;
+ exit (3);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+main (int argc, char **argv)
+{
+ unsigned char action = 0, console_flag = 3;
+ int console_fd, vcsa_fd, console_minor, buffer_size;
+ struct stat st;
+ uid_t uid, euid;
+ char *buffer, *tty_name, console_name[16], vcsa_name[16];
+ const char *p, *q;
+ struct winsize winsz;
+
+ close (STDERR_FILENO);
+
+ if (argc != 2)
+ die ();
+
+ tty_name = argv[1];
+ if (strnlen (tty_name, 15) == 15 || strncmp (tty_name, "/dev/", 5))
+ die ();
+
+ setsid ();
+ uid = getuid ();
+ euid = geteuid ();
+
+ if (seteuid (uid) < 0)
+ die ();
+ console_fd = open (tty_name, O_RDONLY);
+ if (console_fd < 0)
+ die ();
+ if (fstat (console_fd, &st) < 0 || !S_ISCHR (st.st_mode))
+ die ();
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ if ((st.st_rdev & 0xff00) != 0x0400)
+ die ();
+ console_minor = (int) (st.st_rdev & 0x00ff);
+#else
+ console_minor = 1; /* FIXME */
+#endif
+ if (console_minor < 1 || console_minor > 63)
+ die ();
+ if (st.st_uid != uid)
+ die ();
+
+ switch (tty_name[5])
+ {
+ /* devfs */
+ case 'v':
+ p = "/dev/vc/%d";
+ q = "/dev/vcc/a%d";
+ break;
+ /* /dev/ttyN */
+ case 't':
+ p = "/dev/tty%d";
+ q = "/dev/vcsa%d";
+ break;
+ default:
+ die ();
+ }
+
+ snprintf (console_name, sizeof (console_name), p, console_minor);
+ if (strncmp (console_name, tty_name, sizeof (console_name)) != 0)
+ die ();
+
+ if (seteuid (euid) < 0)
+ die ();
+
+ snprintf (vcsa_name, sizeof (vcsa_name), q, console_minor);
+ vcsa_fd = open (vcsa_name, O_RDWR);
+ if (vcsa_fd < 0)
+ die ();
+ if (fstat (vcsa_fd, &st) < 0 || !S_ISCHR (st.st_mode))
+ die ();
+
+ if (seteuid (uid) < 0)
+ die ();
+
+ winsz.ws_col = winsz.ws_row = 0;
+ if (ioctl (console_fd, TIOCGWINSZ, &winsz) < 0
+ || winsz.ws_col <= 0 || winsz.ws_row <= 0 || winsz.ws_col >= 256 || winsz.ws_row >= 256)
+ die ();
+
+ buffer_size = 4 + 2 * winsz.ws_col * winsz.ws_row;
+ buffer = calloc (buffer_size, 1);
+ if (buffer == NULL)
+ die ();
+
+ if (write (1, &console_flag, 1) != 1)
+ die ();
+
+ while (console_flag && read (0, &action, 1) == 1)
+ {
+ switch (action)
+ {
+ case CONSOLE_DONE:
+ console_flag = 0;
+ continue;
+ case CONSOLE_SAVE:
+ if (seteuid (euid) < 0
+ || lseek (vcsa_fd, 0, 0) != 0
+ || fstat (console_fd, &st) < 0 || st.st_uid != uid
+ || read (vcsa_fd, buffer, buffer_size) != buffer_size
+ || fstat (console_fd, &st) < 0 || st.st_uid != uid)
+ memset (buffer, 0, buffer_size);
+ if (seteuid (uid) < 0)
+ die ();
+ break;
+ case CONSOLE_RESTORE:
+ if (seteuid (euid) >= 0
+ && lseek (vcsa_fd, 0, 0) == 0 && fstat (console_fd, &st) >= 0 && st.st_uid == uid)
+ if (write (vcsa_fd, buffer, buffer_size) != buffer_size)
+ die ();
+ if (seteuid (uid) < 0)
+ die ();
+ break;
+ case CONSOLE_CONTENTS:
+ send_contents (buffer + 4, winsz.ws_col, winsz.ws_row);
+ break;
+ default:
+ break;
+ }
+
+ if (write (1, &console_flag, 1) != 1)
+ die ();
+ }
+
+ exit (0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/consaver/cons.saver.h b/src/consaver/cons.saver.h
new file mode 100644
index 0000000..a61b77c
--- /dev/null
+++ b/src/consaver/cons.saver.h
@@ -0,0 +1,46 @@
+/** \file cons.saver.h
+ * \brief Header: general purpose Linux console screen save/restore server
+ *
+ * This code does _not_ need to be setuid root. However, it needs
+ * read/write access to /dev/vcsa* (which is privileged
+ * operation). You should create user vcsa, make cons.saver setuid
+ * user vcsa, and make all vcsa's owned by user vcsa.
+ * Seeing other peoples consoles is bad thing, but believe me, full
+ * root is even worse.
+ */
+
+#ifndef MC__CONS_SAVER_H
+#define MC__CONS_SAVER_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ CONSOLE_INIT = '1',
+ CONSOLE_DONE,
+ CONSOLE_SAVE,
+ CONSOLE_RESTORE,
+ CONSOLE_CONTENTS
+} console_action_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+#ifndef LINUX_CONS_SAVER_C
+/* Used only in mc, not in cons.saver */
+extern int cons_saver_pid;
+#endif /* !LINUX_CONS_SAVER_C */
+
+/*** declarations of public functions ************************************************************/
+
+#ifndef LINUX_CONS_SAVER_C
+/* Used only in mc, not in cons.saver */
+void show_console_contents (int starty, unsigned char begin_line, unsigned char end_line);
+void handle_console (console_action_t action);
+#endif /* !LINUX_CONS_SAVER_C */
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__CONS_SAVER_H */
diff --git a/src/diffviewer/Makefile.am b/src/diffviewer/Makefile.am
new file mode 100644
index 0000000..7986645
--- /dev/null
+++ b/src/diffviewer/Makefile.am
@@ -0,0 +1,8 @@
+noinst_LTLIBRARIES = libdiffviewer.la
+
+libdiffviewer_la_SOURCES = \
+ internal.h \
+ search.c \
+ ydiff.c ydiff.h
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
diff --git a/src/diffviewer/Makefile.in b/src/diffviewer/Makefile.in
new file mode 100644
index 0000000..cb5729e
--- /dev/null
+++ b/src/diffviewer/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/diffviewer
+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)
+libdiffviewer_la_LIBADD =
+am_libdiffviewer_la_OBJECTS = search.lo ydiff.lo
+libdiffviewer_la_OBJECTS = $(am_libdiffviewer_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)/search.Plo ./$(DEPDIR)/ydiff.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 = $(libdiffviewer_la_SOURCES)
+DIST_SOURCES = $(libdiffviewer_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 = libdiffviewer.la
+libdiffviewer_la_SOURCES = \
+ internal.h \
+ search.c \
+ ydiff.c ydiff.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/diffviewer/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/diffviewer/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}; \
+ }
+
+libdiffviewer.la: $(libdiffviewer_la_OBJECTS) $(libdiffviewer_la_DEPENDENCIES) $(EXTRA_libdiffviewer_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libdiffviewer_la_OBJECTS) $(libdiffviewer_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/search.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ydiff.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)/search.Plo
+ -rm -f ./$(DEPDIR)/ydiff.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)/search.Plo
+ -rm -f ./$(DEPDIR)/ydiff.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/diffviewer/internal.h b/src/diffviewer/internal.h
new file mode 100644
index 0000000..728d4b5
--- /dev/null
+++ b/src/diffviewer/internal.h
@@ -0,0 +1,153 @@
+#ifndef MC__DIFFVIEW_INTERNAL_H
+#define MC__DIFFVIEW_INTERNAL_H
+
+#include "lib/global.h"
+#include "lib/mcconfig.h"
+#include "lib/search.h"
+#include "lib/tty/color.h"
+#include "lib/widget.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+typedef int (*DFUNC) (void *ctx, int ch, int line, off_t off, size_t sz, const char *str);
+typedef int PAIR[2];
+
+#define error_dialog(h, s) query_dialog(h, s, D_ERROR, 1, _("&Dismiss"))
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ DATA_SRC_MEM = 0,
+ DATA_SRC_TMP = 1,
+ DATA_SRC_ORG = 2
+} DSRC;
+
+typedef enum
+{
+ DIFF_LEFT = 0,
+ DIFF_RIGHT = 1,
+ DIFF_COUNT = 2
+} diff_place_t;
+
+typedef enum
+{
+ DIFF_NONE = 0,
+ DIFF_ADD = 1,
+ DIFF_DEL = 2,
+ DIFF_CHG = 3
+} DiffState;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ int fd;
+ int pos;
+ int len;
+ char *buf;
+ int flags;
+ void *data;
+} FBUF;
+
+typedef struct
+{
+ int a[2][2];
+ int cmd;
+} DIFFCMD;
+
+
+typedef struct
+{
+ int off;
+ int len;
+} BRACKET[DIFF_COUNT];
+
+typedef struct
+{
+ int ch;
+ int line;
+ union
+ {
+ off_t off;
+ size_t len;
+ } u;
+ void *p;
+} DIFFLN;
+
+typedef struct
+{
+ FBUF *f;
+ GArray *a;
+ DSRC dsrc;
+} PRINTER_CTX;
+
+typedef struct WDiff
+{
+ Widget widget;
+
+ const char *args; /* Args passed to diff */
+ const char *file[DIFF_COUNT]; /* filenames */
+ char *label[DIFF_COUNT];
+ FBUF *f[DIFF_COUNT];
+ const char *backup_sufix;
+ gboolean merged[DIFF_COUNT];
+ GArray *a[DIFF_COUNT];
+ GPtrArray *hdiff;
+ int ndiff; /* number of hunks */
+ DSRC dsrc; /* data source: memory or temporary file */
+
+ gboolean view_quit; /* Quit flag */
+
+ int height;
+ int half1;
+ int half2;
+ int width1;
+ int width2;
+ int bias;
+ gboolean new_frame;
+ int skip_rows;
+ int skip_cols;
+ int display_symbols;
+ int display_numbers;
+ gboolean show_cr;
+ int tab_size;
+ diff_place_t ord;
+ gboolean full;
+
+#ifdef HAVE_CHARSET
+ gboolean utf8;
+ /* converter for translation of text */
+ GIConv converter;
+#endif /* HAVE_CHARSET */
+
+ struct
+ {
+ int quality;
+ gboolean strip_trailing_cr;
+ gboolean ignore_tab_expansion;
+ gboolean ignore_space_change;
+ gboolean ignore_all_space;
+ gboolean ignore_case;
+ } opt;
+
+ /* Search variables */
+ struct
+ {
+ mc_search_t *handle;
+ gchar *last_string;
+
+ ssize_t last_found_line;
+ ssize_t last_accessed_num_line;
+ } search;
+} WDiff;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* search.c */
+void dview_search_cmd (WDiff * dview);
+void dview_continue_search_cmd (WDiff * dview);
+
+#endif /* MC__DIFFVIEW_INTERNAL_H */
diff --git a/src/diffviewer/search.c b/src/diffviewer/search.c
new file mode 100644
index 0000000..77d09cd
--- /dev/null
+++ b/src/diffviewer/search.c
@@ -0,0 +1,288 @@
+/*
+ Search functions for diffviewer.
+
+ Copyright (C) 2010-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2010.
+ Andrew Borodin <aborodin@vmail.ru>, 2012-2022
+
+ 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 <stdio.h>
+
+#include "lib/global.h"
+#include "lib/strutil.h"
+#include "lib/tty/key.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "src/history.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct mcdiffview_search_options_struct
+{
+ mc_search_type_t type;
+ gboolean case_sens;
+ gboolean backwards;
+ gboolean whole_words;
+ gboolean all_codepages;
+} mcdiffview_search_options_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static mcdiffview_search_options_t mcdiffview_search_options = {
+ .type = MC_SEARCH_T_NORMAL,
+ .case_sens = FALSE,
+ .backwards = FALSE,
+ .whole_words = FALSE,
+ .all_codepages = FALSE,
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcdiffview_dialog_search (WDiff * dview)
+{
+ char *exp = NULL;
+ int qd_result;
+ size_t num_of_types = 0;
+ gchar **list_of_types;
+
+ list_of_types = mc_search_get_types_strings_array (&num_of_types);
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (N_("Enter search string:"), input_label_above, INPUT_LAST_TEXT,
+ MC_HISTORY_SHARED_SEARCH, &exp, NULL, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_START_COLUMNS,
+ QUICK_RADIO (num_of_types, (const char **) list_of_types,
+ (int *) &mcdiffview_search_options.type, NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("Cas&e sensitive"), &mcdiffview_search_options.case_sens, NULL),
+ QUICK_CHECKBOX (N_("&Backwards"), &mcdiffview_search_options.backwards, NULL),
+ QUICK_CHECKBOX (N_("&Whole words"), &mcdiffview_search_options.whole_words, NULL),
+#ifdef HAVE_CHARSET
+ QUICK_CHECKBOX (N_("&All charsets"), &mcdiffview_search_options.all_codepages, NULL),
+#endif
+ QUICK_STOP_COLUMNS,
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 58 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Search"), "[Input Line Keys]",
+ quick_widgets, NULL, NULL
+ };
+
+ qd_result = quick_dialog (&qdlg);
+ }
+
+ g_strfreev (list_of_types);
+
+ if (qd_result == B_CANCEL || exp[0] == '\0')
+ {
+ g_free (exp);
+ return FALSE;
+ }
+
+#ifdef HAVE_CHARSET
+ {
+ GString *tmp;
+
+ tmp = str_convert_to_input (exp);
+ g_free (exp);
+ if (tmp != NULL)
+ exp = g_string_free (tmp, FALSE);
+ else
+ exp = g_strdup ("");
+ }
+#endif
+
+ g_free (dview->search.last_string);
+ dview->search.last_string = exp;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcdiffview_do_search_backward (WDiff * dview)
+{
+ ssize_t ind;
+
+ if (dview->search.last_accessed_num_line < 0)
+ {
+ dview->search.last_accessed_num_line = -1;
+ return FALSE;
+ }
+
+ if ((size_t) dview->search.last_accessed_num_line >= dview->a[dview->ord]->len)
+ dview->search.last_accessed_num_line = (ssize_t) dview->a[dview->ord]->len;
+
+ for (ind = --dview->search.last_accessed_num_line; ind >= 0; ind--)
+ {
+ DIFFLN *p;
+
+ p = (DIFFLN *) & g_array_index (dview->a[dview->ord], DIFFLN, (size_t) ind);
+ if (p->u.len == 0)
+ continue;
+
+ if (mc_search_run (dview->search.handle, p->p, 0, p->u.len, NULL))
+ {
+ dview->skip_rows = dview->search.last_found_line =
+ dview->search.last_accessed_num_line = ind;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+
+static gboolean
+mcdiffview_do_search_forward (WDiff * dview)
+{
+ size_t ind;
+
+ if (dview->search.last_accessed_num_line < 0)
+ dview->search.last_accessed_num_line = -1;
+ else if ((size_t) dview->search.last_accessed_num_line >= dview->a[dview->ord]->len)
+ {
+ dview->search.last_accessed_num_line = (ssize_t) dview->a[dview->ord]->len;
+ return FALSE;
+ }
+
+ for (ind = (size_t)++ dview->search.last_accessed_num_line; ind < dview->a[dview->ord]->len;
+ ind++)
+ {
+ DIFFLN *p;
+
+ p = (DIFFLN *) & g_array_index (dview->a[dview->ord], DIFFLN, ind);
+ if (p->u.len == 0)
+ continue;
+
+ if (mc_search_run (dview->search.handle, p->p, 0, p->u.len, NULL))
+ {
+ dview->skip_rows = dview->search.last_found_line =
+ dview->search.last_accessed_num_line = (ssize_t) ind;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcdiffview_do_search (WDiff * dview)
+{
+ gboolean present_result = FALSE;
+
+ tty_enable_interrupt_key ();
+
+ if (mcdiffview_search_options.backwards)
+ {
+ present_result = mcdiffview_do_search_backward (dview);
+ }
+ else
+ {
+ present_result = mcdiffview_do_search_forward (dview);
+ }
+
+ tty_disable_interrupt_key ();
+
+ if (!present_result)
+ {
+ dview->search.last_found_line = -1;
+ query_dialog (_("Search"), _(STR_E_NOTFOUND), D_NORMAL, 1, _("&Dismiss"));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dview_search_cmd (WDiff * dview)
+{
+ if (dview->dsrc != DATA_SRC_MEM)
+ {
+ error_dialog (_("Search"), _("Search is disabled"));
+ return;
+ }
+
+ if (!mcdiffview_dialog_search (dview))
+ return;
+
+ mc_search_free (dview->search.handle);
+#ifdef HAVE_CHARSET
+ dview->search.handle = mc_search_new (dview->search.last_string, cp_source);
+#else
+ dview->search.handle = mc_search_new (dview->search.last_string, NULL);
+#endif
+
+ if (dview->search.handle == NULL)
+ return;
+
+ dview->search.handle->search_type = mcdiffview_search_options.type;
+#ifdef HAVE_CHARSET
+ dview->search.handle->is_all_charsets = mcdiffview_search_options.all_codepages;
+#endif
+ dview->search.handle->is_case_sensitive = mcdiffview_search_options.case_sens;
+ dview->search.handle->whole_words = mcdiffview_search_options.whole_words;
+
+ mcdiffview_do_search (dview);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dview_continue_search_cmd (WDiff * dview)
+{
+ if (dview->dsrc != DATA_SRC_MEM)
+ error_dialog (_("Search"), _("Search is disabled"));
+ else if (dview->search.handle == NULL)
+ dview_search_cmd (dview);
+ else
+ mcdiffview_do_search (dview);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/diffviewer/ydiff.c b/src/diffviewer/ydiff.c
new file mode 100644
index 0000000..3afb8af
--- /dev/null
+++ b/src/diffviewer/ydiff.c
@@ -0,0 +1,3648 @@
+/*
+ File difference viewer
+
+ Copyright (C) 2007-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Daniel Borca <dborca@yahoo.com>, 2007
+ Slava Zanko <slavazanko@gmail.com>, 2010, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2010-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2010
+
+ 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 <ctype.h>
+#include <errno.h>
+#include <stddef.h> /* ptrdiff_t */
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/tty/key.h"
+#include "lib/skin.h" /* EDITOR_NORMAL_COLOR */
+#include "lib/vfs/vfs.h" /* mc_opendir, mc_readdir, mc_closedir, */
+#include "lib/util.h"
+#include "lib/widget.h"
+#include "lib/strutil.h"
+#include "lib/strescape.h" /* strutils_glob_escape() */
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+#include "lib/event.h" /* mc_event_raise() */
+
+#include "src/filemanager/cmd.h" /* edit_file_at_line() */
+#include "src/filemanager/panel.h"
+#include "src/filemanager/layout.h" /* Needed for get_current_index and get_other_panel */
+
+#include "src/execute.h" /* toggle_subshell() */
+#include "src/keymap.h"
+#include "src/setup.h"
+#include "src/history.h"
+#ifdef HAVE_CHARSET
+#include "src/selcodepage.h"
+#endif
+
+#include "ydiff.h"
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define g_array_foreach(a, TP, cbf) \
+do { \
+ size_t g_array_foreach_i;\
+ \
+ for (g_array_foreach_i = 0; g_array_foreach_i < a->len; g_array_foreach_i++) \
+ { \
+ TP *g_array_foreach_var; \
+ \
+ g_array_foreach_var = &g_array_index (a, TP, g_array_foreach_i); \
+ (*cbf) (g_array_foreach_var); \
+ } \
+} while (0)
+
+#define FILE_READ_BUF 4096
+#define FILE_FLAG_TEMP (1 << 0)
+
+#define ADD_CH '+'
+#define DEL_CH '-'
+#define CHG_CH '*'
+#define EQU_CH ' '
+
+#define HDIFF_ENABLE 1
+#define HDIFF_MINCTX 5
+#define HDIFF_DEPTH 10
+
+#define FILE_DIRTY(fs) \
+do \
+{ \
+ (fs)->pos = 0; \
+ (fs)->len = 0; \
+} \
+while (0)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef enum
+{
+ FROM_LEFT_TO_RIGHT,
+ FROM_RIGHT_TO_LEFT
+} action_direction_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+TAB_SKIP (int ts, int pos)
+{
+ if (ts > 0 && ts < 9)
+ return ts - pos % ts;
+ else
+ return 8 - pos % 8;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+rewrite_backup_content (const vfs_path_t * from_file_name_vpath, const char *to_file_name)
+{
+ FILE *backup_fd;
+ char *contents;
+ gsize length;
+ const char *from_file_name;
+
+ from_file_name = vfs_path_get_last_path_str (from_file_name_vpath);
+ if (!g_file_get_contents (from_file_name, &contents, &length, NULL))
+ return FALSE;
+
+ backup_fd = fopen (to_file_name, "w");
+ if (backup_fd == NULL)
+ {
+ g_free (contents);
+ return FALSE;
+ }
+
+ length = fwrite ((const void *) contents, length, 1, backup_fd);
+
+ fflush (backup_fd);
+ fclose (backup_fd);
+ g_free (contents);
+ return TRUE;
+}
+
+/* buffered I/O ************************************************************* */
+
+/**
+ * Try to open a temporary file.
+ * @note the name is not altered if this function fails
+ *
+ * @param[out] name address of a pointer to store the temporary name
+ * @return file descriptor on success, negative on error
+ */
+
+static int
+open_temp (void **name)
+{
+ int fd;
+ vfs_path_t *diff_file_name = NULL;
+
+ fd = mc_mkstemps (&diff_file_name, "mcdiff", NULL);
+ if (fd == -1)
+ {
+ message (D_ERROR, MSG_ERROR,
+ _("Cannot create temporary diff file\n%s"), unix_error_string (errno));
+ return -1;
+ }
+
+ *name = vfs_path_free (diff_file_name, FALSE);
+ return fd;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Allocate file structure and associate file descriptor to it.
+ *
+ * @param fd file descriptor
+ * @return file structure
+ */
+
+static FBUF *
+f_dopen (int fd)
+{
+ FBUF *fs;
+
+ if (fd < 0)
+ return NULL;
+
+ fs = g_try_malloc (sizeof (FBUF));
+ if (fs == NULL)
+ return NULL;
+
+ fs->buf = g_try_malloc (FILE_READ_BUF);
+ if (fs->buf == NULL)
+ {
+ g_free (fs);
+ return NULL;
+ }
+
+ fs->fd = fd;
+ FILE_DIRTY (fs);
+ fs->flags = 0;
+ fs->data = NULL;
+
+ return fs;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Free file structure without closing the file.
+ *
+ * @param fs file structure
+ * @return 0 on success, non-zero on error
+ */
+
+static int
+f_free (FBUF * fs)
+{
+ int rv = 0;
+
+ if (fs->flags & FILE_FLAG_TEMP)
+ {
+ rv = unlink (fs->data);
+ g_free (fs->data);
+ }
+ g_free (fs->buf);
+ g_free (fs);
+ return rv;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Open a binary temporary file in R/W mode.
+ * @note the file will be deleted when closed
+ *
+ * @return file structure
+ */
+static FBUF *
+f_temp (void)
+{
+ int fd;
+ FBUF *fs;
+
+ fs = f_dopen (0);
+ if (fs == NULL)
+ return NULL;
+
+ fd = open_temp (&fs->data);
+ if (fd < 0)
+ {
+ f_free (fs);
+ return NULL;
+ }
+
+ fs->fd = fd;
+ fs->flags = FILE_FLAG_TEMP;
+ return fs;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Open a binary file in specified mode.
+ *
+ * @param filename file name
+ * @param flags open mode, a combination of O_RDONLY, O_WRONLY, O_RDWR
+ *
+ * @return file structure
+ */
+
+static FBUF *
+f_open (const char *filename, int flags)
+{
+ int fd;
+ FBUF *fs;
+
+ fs = f_dopen (0);
+ if (fs == NULL)
+ return NULL;
+
+ fd = open (filename, flags);
+ if (fd < 0)
+ {
+ f_free (fs);
+ return NULL;
+ }
+
+ fs->fd = fd;
+ return fs;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Read a line of bytes from file until newline or EOF.
+ * @note does not stop on null-byte
+ * @note buf will not be null-terminated
+ *
+ * @param buf destination buffer
+ * @param size size of buffer
+ * @param fs file structure
+ *
+ * @return number of bytes read
+ */
+
+static size_t
+f_gets (char *buf, size_t size, FBUF * fs)
+{
+ size_t j = 0;
+
+ do
+ {
+ int i;
+ int stop = 0;
+
+ for (i = fs->pos; j < size && i < fs->len && !stop; i++, j++)
+ {
+ buf[j] = fs->buf[i];
+ if (buf[j] == '\n')
+ stop = 1;
+ }
+ fs->pos = i;
+
+ if (j == size || stop)
+ break;
+
+ fs->pos = 0;
+ fs->len = read (fs->fd, fs->buf, FILE_READ_BUF);
+ }
+ while (fs->len > 0);
+
+ return j;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Seek into file.
+ * @note avoids thrashing read cache when possible
+ *
+ * @param fs file structure
+ * @param off offset
+ * @param whence seek directive: SEEK_SET, SEEK_CUR or SEEK_END
+ *
+ * @return position in file, starting from beginning
+ */
+
+static off_t
+f_seek (FBUF * fs, off_t off, int whence)
+{
+ off_t rv;
+
+ if (fs->len && whence != SEEK_END)
+ {
+ rv = lseek (fs->fd, 0, SEEK_CUR);
+ if (rv != -1)
+ {
+ if (whence == SEEK_CUR)
+ {
+ whence = SEEK_SET;
+ off += rv - fs->len + fs->pos;
+ }
+ if (off - rv >= -fs->len && off - rv <= 0)
+ {
+ fs->pos = fs->len + off - rv;
+ return off;
+ }
+ }
+ }
+
+ rv = lseek (fs->fd, off, whence);
+ if (rv != -1)
+ FILE_DIRTY (fs);
+ return rv;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Seek to the beginning of file, thrashing read cache.
+ *
+ * @param fs file structure
+ *
+ * @return 0 if success, non-zero on error
+ */
+
+static off_t
+f_reset (FBUF * fs)
+{
+ off_t rv;
+
+ rv = lseek (fs->fd, 0, SEEK_SET);
+ if (rv != -1)
+ FILE_DIRTY (fs);
+ return rv;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Write bytes to file.
+ * @note thrashes read cache
+ *
+ * @param fs file structure
+ * @param buf source buffer
+ * @param size size of buffer
+ *
+ * @return number of written bytes, -1 on error
+ */
+
+static ssize_t
+f_write (FBUF * fs, const char *buf, size_t size)
+{
+ ssize_t rv;
+
+ rv = write (fs->fd, buf, size);
+ if (rv >= 0)
+ FILE_DIRTY (fs);
+ return rv;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Truncate file to the current position.
+ * @note thrashes read cache
+ *
+ * @param fs file structure
+ *
+ * @return current file size on success, negative on error
+ */
+
+static off_t
+f_trunc (FBUF * fs)
+{
+ off_t off;
+
+ off = lseek (fs->fd, 0, SEEK_CUR);
+ if (off != -1)
+ {
+ int rv;
+
+ rv = ftruncate (fs->fd, off);
+ if (rv != 0)
+ off = -1;
+ else
+ FILE_DIRTY (fs);
+ }
+ return off;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Close file.
+ * @note if this is temporary file, it is deleted
+ *
+ * @param fs file structure
+ * @return 0 on success, non-zero on error
+ */
+
+static int
+f_close (FBUF * fs)
+{
+ int rv = -1;
+
+ if (fs != NULL)
+ {
+ rv = close (fs->fd);
+ f_free (fs);
+ }
+
+ return rv;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Create pipe stream to process.
+ *
+ * @param cmd shell command line
+ * @param flags open mode, either O_RDONLY or O_WRONLY
+ *
+ * @return file structure
+ */
+
+static FBUF *
+p_open (const char *cmd, int flags)
+{
+ FILE *f;
+ FBUF *fs;
+ const char *type = NULL;
+
+ if (flags == O_RDONLY)
+ type = "r";
+ else if (flags == O_WRONLY)
+ type = "w";
+
+ if (type == NULL)
+ return NULL;
+
+ fs = f_dopen (0);
+ if (fs == NULL)
+ return NULL;
+
+ f = popen (cmd, type);
+ if (f == NULL)
+ {
+ f_free (fs);
+ return NULL;
+ }
+
+ fs->fd = fileno (f);
+ fs->data = f;
+ return fs;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Close pipe stream.
+ *
+ * @param fs structure
+ * @return 0 on success, non-zero on error
+ */
+
+static int
+p_close (FBUF * fs)
+{
+ int rv = -1;
+
+ if (fs != NULL)
+ {
+ rv = pclose (fs->data);
+ f_free (fs);
+ }
+
+ return rv;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Get one char (byte) from string
+ *
+ * @param str ...
+ * @param ch ...
+ * @return TRUE on success, FALSE otherwise
+ */
+
+static gboolean
+dview_get_byte (const char *str, int *ch)
+{
+ if (str == NULL)
+ return FALSE;
+
+ *ch = (unsigned char) (*str);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+/**
+ * Get utf multibyte char from string
+ *
+ * @param str ...
+ * @param ch ...
+ * @param ch_length ...
+ * @return TRUE on success, FALSE otherwise
+ */
+
+static gboolean
+dview_get_utf (const char *str, int *ch, int *ch_length)
+{
+ if (str == NULL)
+ return FALSE;
+
+ *ch = g_utf8_get_char_validated (str, -1);
+
+ if (*ch < 0)
+ {
+ *ch = (unsigned char) (*str);
+ *ch_length = 1;
+ }
+ else
+ {
+ char *next_ch;
+
+ /* Calculate UTF-8 char length */
+ next_ch = g_utf8_next_char (str);
+ *ch_length = next_ch - str;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+dview_str_utf8_offset_to_pos (const char *text, size_t length)
+{
+ ptrdiff_t result;
+
+ if (text == NULL || text[0] == '\0')
+ return length;
+
+ if (g_utf8_validate (text, -1, NULL))
+ result = g_utf8_offset_to_pointer (text, length) - text;
+ else
+ {
+ gunichar uni;
+ char *tmpbuf, *buffer;
+
+ buffer = tmpbuf = g_strdup (text);
+ while (tmpbuf[0] != '\0')
+ {
+ uni = g_utf8_get_char_validated (tmpbuf, -1);
+ if ((uni != (gunichar) (-1)) && (uni != (gunichar) (-2)))
+ tmpbuf = g_utf8_next_char (tmpbuf);
+ else
+ {
+ tmpbuf[0] = '.';
+ tmpbuf++;
+ }
+ }
+ result = g_utf8_offset_to_pointer (tmpbuf, length) - tmpbuf;
+ g_free (buffer);
+ }
+ return MAX (length, (size_t) result);
+}
+#endif /*HAVE_CHARSET */
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* diff parse *************************************************************** */
+
+/**
+ * Read decimal number from string.
+ *
+ * @param[in,out] str string to parse
+ * @param[out] n extracted number
+ * @return 0 if success, otherwise non-zero
+ */
+
+static int
+scan_deci (const char **str, int *n)
+{
+ const char *p = *str;
+ char *q;
+
+ errno = 0;
+ *n = strtol (p, &q, 10);
+ if (errno != 0 || p == q)
+ return -1;
+ *str = q;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Parse line for diff statement.
+ *
+ * @param p string to parse
+ * @param ops list of diff statements
+ * @return 0 if success, otherwise non-zero
+ */
+
+static int
+scan_line (const char *p, GArray * ops)
+{
+ DIFFCMD op;
+
+ int f1, f2;
+ int t1, t2;
+ int cmd;
+ int range;
+
+ /* handle the following cases:
+ * NUMaNUM[,NUM]
+ * NUM[,NUM]cNUM[,NUM]
+ * NUM[,NUM]dNUM
+ * where NUM is a positive integer
+ */
+
+ if (scan_deci (&p, &f1) != 0 || f1 < 0)
+ return -1;
+
+ f2 = f1;
+ range = 0;
+ if (*p == ',')
+ {
+ p++;
+ if (scan_deci (&p, &f2) != 0 || f2 < f1)
+ return -1;
+
+ range = 1;
+ }
+
+ cmd = *p++;
+ if (cmd == 'a')
+ {
+ if (range != 0)
+ return -1;
+ }
+ else if (cmd != 'c' && cmd != 'd')
+ return -1;
+
+ if (scan_deci (&p, &t1) != 0 || t1 < 0)
+ return -1;
+
+ t2 = t1;
+ range = 0;
+ if (*p == ',')
+ {
+ p++;
+ if (scan_deci (&p, &t2) != 0 || t2 < t1)
+ return -1;
+
+ range = 1;
+ }
+
+ if (cmd == 'd' && range != 0)
+ return -1;
+
+ op.a[0][0] = f1;
+ op.a[0][1] = f2;
+ op.cmd = cmd;
+ op.a[1][0] = t1;
+ op.a[1][1] = t2;
+ g_array_append_val (ops, op);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Parse diff output and extract diff statements.
+ *
+ * @param f stream to read from
+ * @param ops list of diff statements to fill
+ * @return positive number indicating number of hunks, otherwise negative
+ */
+
+static int
+scan_diff (FBUF * f, GArray * ops)
+{
+ int sz;
+ char buf[BUFSIZ];
+
+ while ((sz = f_gets (buf, sizeof (buf) - 1, f)) != 0)
+ {
+ if (isdigit (buf[0]))
+ {
+ if (buf[sz - 1] != '\n')
+ return -1;
+
+ buf[sz] = '\0';
+ if (scan_line (buf, ops) != 0)
+ return -1;
+
+ continue;
+ }
+
+ while (buf[sz - 1] != '\n' && (sz = f_gets (buf, sizeof (buf), f)) != 0)
+ ;
+ }
+
+ return ops->len;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Invoke diff and extract diff statements.
+ *
+ * @param args extra arguments to be passed to diff
+ * @param extra more arguments to be passed to diff
+ * @param file1 first file to compare
+ * @param file2 second file to compare
+ * @param ops list of diff statements to fill
+ *
+ * @return positive number indicating number of hunks, otherwise negative
+ */
+
+static int
+dff_execute (const char *args, const char *extra, const char *file1, const char *file2,
+ GArray * ops)
+{
+ static const char *opt =
+ " --old-group-format='%df%(f=l?:,%dl)d%dE\n'"
+ " --new-group-format='%dea%dF%(F=L?:,%dL)\n'"
+ " --changed-group-format='%df%(f=l?:,%dl)c%dF%(F=L?:,%dL)\n'"
+ " --unchanged-group-format=''";
+
+ int rv;
+ FBUF *f;
+ char *cmd;
+ int code;
+ char *file1_esc, *file2_esc;
+
+ /* escape potential $ to avoid shell variable substitutions in popen() */
+ file1_esc = strutils_shell_escape (file1);
+ file2_esc = strutils_shell_escape (file2);
+ cmd = g_strdup_printf ("diff %s %s %s %s %s", args, extra, opt, file1_esc, file2_esc);
+ g_free (file1_esc);
+ g_free (file2_esc);
+
+ if (cmd == NULL)
+ return -1;
+
+ f = p_open (cmd, O_RDONLY);
+ g_free (cmd);
+
+ if (f == NULL)
+ return -1;
+
+ rv = scan_diff (f, ops);
+ code = p_close (f);
+
+ if (rv < 0 || code == -1 || !WIFEXITED (code) || WEXITSTATUS (code) == 2)
+ rv = -1;
+
+ return rv;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Reparse and display file according to diff statements.
+ *
+ * @param ord DIFF_LEFT if 1st file is displayed , DIFF_RIGHT if 2nd file is displayed.
+ * @param filename file name to display
+ * @param ops list of diff statements
+ * @param printer printf-like function to be used for displaying
+ * @param ctx printer context
+ *
+ * @return 0 if success, otherwise non-zero
+ */
+
+static int
+dff_reparse (diff_place_t ord, const char *filename, const GArray * ops, DFUNC printer, void *ctx)
+{
+ size_t i;
+ FBUF *f;
+ size_t sz;
+ char buf[BUFSIZ];
+ int line = 0;
+ off_t off = 0;
+ const DIFFCMD *op;
+ diff_place_t eff;
+ int add_cmd;
+ int del_cmd;
+
+ f = f_open (filename, O_RDONLY);
+ if (f == NULL)
+ return -1;
+
+ ord &= 1;
+ eff = ord;
+
+ add_cmd = 'a';
+ del_cmd = 'd';
+ if (ord != 0)
+ {
+ add_cmd = 'd';
+ del_cmd = 'a';
+ }
+#define F1 a[eff][0]
+#define F2 a[eff][1]
+#define T1 a[ ord^1 ][0]
+#define T2 a[ ord^1 ][1]
+ for (i = 0; i < ops->len; i++)
+ {
+ int n;
+
+ op = &g_array_index (ops, DIFFCMD, i);
+ n = op->F1 - (op->cmd != add_cmd);
+
+ while (line < n && (sz = f_gets (buf, sizeof (buf), f)) != 0)
+ {
+ line++;
+ printer (ctx, EQU_CH, line, off, sz, buf);
+ off += sz;
+ while (buf[sz - 1] != '\n')
+ {
+ sz = f_gets (buf, sizeof (buf), f);
+ if (sz == 0)
+ {
+ printer (ctx, 0, 0, 0, 1, "\n");
+ break;
+ }
+ printer (ctx, 0, 0, 0, sz, buf);
+ off += sz;
+ }
+ }
+
+ if (line != n)
+ goto err;
+
+ if (op->cmd == add_cmd)
+ {
+ n = op->T2 - op->T1 + 1;
+ while (n != 0)
+ {
+ printer (ctx, DEL_CH, 0, 0, 1, "\n");
+ n--;
+ }
+ }
+
+ if (op->cmd == del_cmd)
+ {
+ n = op->F2 - op->F1 + 1;
+ while (n != 0 && (sz = f_gets (buf, sizeof (buf), f)) != 0)
+ {
+ line++;
+ printer (ctx, ADD_CH, line, off, sz, buf);
+ off += sz;
+ while (buf[sz - 1] != '\n')
+ {
+ sz = f_gets (buf, sizeof (buf), f);
+ if (sz == 0)
+ {
+ printer (ctx, 0, 0, 0, 1, "\n");
+ break;
+ }
+ printer (ctx, 0, 0, 0, sz, buf);
+ off += sz;
+ }
+ n--;
+ }
+
+ if (n != 0)
+ goto err;
+ }
+
+ if (op->cmd == 'c')
+ {
+ n = op->F2 - op->F1 + 1;
+ while (n != 0 && (sz = f_gets (buf, sizeof (buf), f)) != 0)
+ {
+ line++;
+ printer (ctx, CHG_CH, line, off, sz, buf);
+ off += sz;
+ while (buf[sz - 1] != '\n')
+ {
+ sz = f_gets (buf, sizeof (buf), f);
+ if (sz == 0)
+ {
+ printer (ctx, 0, 0, 0, 1, "\n");
+ break;
+ }
+ printer (ctx, 0, 0, 0, sz, buf);
+ off += sz;
+ }
+ n--;
+ }
+
+ if (n != 0)
+ goto err;
+
+ n = op->T2 - op->T1 - (op->F2 - op->F1);
+ while (n > 0)
+ {
+ printer (ctx, CHG_CH, 0, 0, 1, "\n");
+ n--;
+ }
+ }
+ }
+#undef T2
+#undef T1
+#undef F2
+#undef F1
+
+ while ((sz = f_gets (buf, sizeof (buf), f)) != 0)
+ {
+ line++;
+ printer (ctx, EQU_CH, line, off, sz, buf);
+ off += sz;
+ while (buf[sz - 1] != '\n')
+ {
+ sz = f_gets (buf, sizeof (buf), f);
+ if (sz == 0)
+ {
+ printer (ctx, 0, 0, 0, 1, "\n");
+ break;
+ }
+ printer (ctx, 0, 0, 0, sz, buf);
+ off += sz;
+ }
+ }
+
+ f_close (f);
+ return 0;
+
+ err:
+ f_close (f);
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* horizontal diff ********************************************************** */
+
+/**
+ * Longest common substring.
+ *
+ * @param s first string
+ * @param m length of first string
+ * @param t second string
+ * @param n length of second string
+ * @param ret list of offsets for longest common substrings inside each string
+ * @param min minimum length of common substrings
+ *
+ * @return 0 if success, nonzero otherwise
+ */
+
+static int
+lcsubstr (const char *s, int m, const char *t, int n, GArray * ret, int min)
+{
+ int i, j;
+ int *Lprev, *Lcurr;
+ int z = 0;
+
+ if (m < min || n < min)
+ {
+ /* XXX early culling */
+ return 0;
+ }
+
+ Lprev = g_try_new0 (int, n + 1);
+ if (Lprev == NULL)
+ return -1;
+
+ Lcurr = g_try_new0 (int, n + 1);
+ if (Lcurr == NULL)
+ {
+ g_free (Lprev);
+ return -1;
+ }
+
+ for (i = 0; i < m; i++)
+ {
+ int *L;
+
+ L = Lprev;
+ Lprev = Lcurr;
+ Lcurr = L;
+#ifdef USE_MEMSET_IN_LCS
+ memset (Lcurr, 0, (n + 1) * sizeof (*Lcurr));
+#endif
+ for (j = 0; j < n; j++)
+ {
+#ifndef USE_MEMSET_IN_LCS
+ Lcurr[j + 1] = 0;
+#endif
+ if (s[i] == t[j])
+ {
+ int v;
+
+ v = Lprev[j] + 1;
+ Lcurr[j + 1] = v;
+ if (z < v)
+ {
+ z = v;
+ g_array_set_size (ret, 0);
+ }
+ if (z == v && z >= min)
+ {
+ int off0, off1;
+ size_t k;
+
+ off0 = i - z + 1;
+ off1 = j - z + 1;
+
+ for (k = 0; k < ret->len; k++)
+ {
+ PAIR *p = (PAIR *) g_array_index (ret, PAIR, k);
+ if ((*p)[0] == off0 || (*p)[1] >= off1)
+ break;
+ }
+ if (k == ret->len)
+ {
+ PAIR p2;
+
+ p2[0] = off0;
+ p2[1] = off1;
+ g_array_append_val (ret, p2);
+ }
+ }
+ }
+ }
+ }
+
+ g_free (Lcurr);
+ g_free (Lprev);
+ return z;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Scan recursively for common substrings and build ranges.
+ *
+ * @param s first string
+ * @param t second string
+ * @param bracket current limits for both of the strings
+ * @param min minimum length of common substrings
+ * @param hdiff list of horizontal diff ranges to fill
+ * @param depth recursion depth
+ *
+ * @return 0 if success, nonzero otherwise
+ */
+
+static gboolean
+hdiff_multi (const char *s, const char *t, const BRACKET bracket, int min, GArray * hdiff,
+ unsigned int depth)
+{
+ BRACKET p;
+
+ if (depth-- != 0)
+ {
+ GArray *ret;
+ BRACKET b;
+ int len;
+
+ ret = g_array_new (FALSE, TRUE, sizeof (PAIR));
+ if (ret == NULL)
+ return FALSE;
+
+ len = lcsubstr (s + bracket[DIFF_LEFT].off, bracket[DIFF_LEFT].len,
+ t + bracket[DIFF_RIGHT].off, bracket[DIFF_RIGHT].len, ret, min);
+ if (ret->len != 0)
+ {
+ size_t k = 0;
+ const PAIR *data = (const PAIR *) &g_array_index (ret, PAIR, 0);
+ const PAIR *data2;
+
+ b[DIFF_LEFT].off = bracket[DIFF_LEFT].off;
+ b[DIFF_LEFT].len = (*data)[0];
+ b[DIFF_RIGHT].off = bracket[DIFF_RIGHT].off;
+ b[DIFF_RIGHT].len = (*data)[1];
+ if (!hdiff_multi (s, t, b, min, hdiff, depth))
+ return FALSE;
+
+ for (k = 0; k < ret->len - 1; k++)
+ {
+ data = (const PAIR *) &g_array_index (ret, PAIR, k);
+ data2 = (const PAIR *) &g_array_index (ret, PAIR, k + 1);
+ b[DIFF_LEFT].off = bracket[DIFF_LEFT].off + (*data)[0] + len;
+ b[DIFF_LEFT].len = (*data2)[0] - (*data)[0] - len;
+ b[DIFF_RIGHT].off = bracket[DIFF_RIGHT].off + (*data)[1] + len;
+ b[DIFF_RIGHT].len = (*data2)[1] - (*data)[1] - len;
+ if (!hdiff_multi (s, t, b, min, hdiff, depth))
+ return FALSE;
+ }
+ data = (const PAIR *) &g_array_index (ret, PAIR, k);
+ b[DIFF_LEFT].off = bracket[DIFF_LEFT].off + (*data)[0] + len;
+ b[DIFF_LEFT].len = bracket[DIFF_LEFT].len - (*data)[0] - len;
+ b[DIFF_RIGHT].off = bracket[DIFF_RIGHT].off + (*data)[1] + len;
+ b[DIFF_RIGHT].len = bracket[DIFF_RIGHT].len - (*data)[1] - len;
+ if (!hdiff_multi (s, t, b, min, hdiff, depth))
+ return FALSE;
+
+ g_array_free (ret, TRUE);
+ return TRUE;
+ }
+ }
+
+ p[DIFF_LEFT].off = bracket[DIFF_LEFT].off;
+ p[DIFF_LEFT].len = bracket[DIFF_LEFT].len;
+ p[DIFF_RIGHT].off = bracket[DIFF_RIGHT].off;
+ p[DIFF_RIGHT].len = bracket[DIFF_RIGHT].len;
+ g_array_append_val (hdiff, p);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Build list of horizontal diff ranges.
+ *
+ * @param s first string
+ * @param m length of first string
+ * @param t second string
+ * @param n length of second string
+ * @param min minimum length of common substrings
+ * @param hdiff list of horizontal diff ranges to fill
+ * @param depth recursion depth
+ *
+ * @return 0 if success, nonzero otherwise
+ */
+
+static gboolean
+hdiff_scan (const char *s, int m, const char *t, int n, int min, GArray * hdiff, unsigned int depth)
+{
+ int i;
+ BRACKET b;
+
+ /* dumbscan (single horizontal diff) -- does not compress whitespace */
+ for (i = 0; i < m && i < n && s[i] == t[i]; i++)
+ ;
+ for (; m > i && n > i && s[m - 1] == t[n - 1]; m--, n--)
+ ;
+
+ b[DIFF_LEFT].off = i;
+ b[DIFF_LEFT].len = m - i;
+ b[DIFF_RIGHT].off = i;
+ b[DIFF_RIGHT].len = n - i;
+
+ /* smartscan (multiple horizontal diff) */
+ return hdiff_multi (s, t, b, min, hdiff, depth);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* read line **************************************************************** */
+
+/**
+ * Check if character is inside horizontal diff limits.
+ *
+ * @param k rank of character inside line
+ * @param hdiff horizontal diff structure
+ * @param ord DIFF_LEFT if reading from first file, DIFF_RIGHT if reading from 2nd file
+ *
+ * @return TRUE if inside hdiff limits, FALSE otherwise
+ */
+
+static gboolean
+is_inside (int k, GArray * hdiff, diff_place_t ord)
+{
+ size_t i;
+ BRACKET *b;
+
+ for (i = 0; i < hdiff->len; i++)
+ {
+ int start, end;
+
+ b = &g_array_index (hdiff, BRACKET, i);
+ start = (*b)[ord].off;
+ end = start + (*b)[ord].len;
+ if (k >= start && k < end)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Copy 'src' to 'dst' expanding tabs.
+ * @note The procedure returns when all bytes are consumed from 'src'
+ *
+ * @param dst destination buffer
+ * @param src source buffer
+ * @param srcsize size of src buffer
+ * @param base virtual base of this string, needed to calculate tabs
+ * @param ts tab size
+ *
+ * @return new virtual base
+ */
+
+static int
+cvt_cpy (char *dst, const char *src, size_t srcsize, int base, int ts)
+{
+ int i;
+
+ for (i = 0; srcsize != 0; i++, src++, dst++, srcsize--)
+ {
+ *dst = *src;
+ if (*src == '\t')
+ {
+ int j;
+
+ j = TAB_SKIP (ts, i + base);
+ i += j - 1;
+ while (j-- > 0)
+ *dst++ = ' ';
+ dst--;
+ }
+ }
+ return i + base;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Copy 'src' to 'dst' expanding tabs.
+ *
+ * @param dst destination buffer
+ * @param dstsize size of dst buffer
+ * @param[in,out] _src source buffer
+ * @param srcsize size of src buffer
+ * @param base virtual base of this string, needed to calculate tabs
+ * @param ts tab size
+ *
+ * @return new virtual base
+ *
+ * @note The procedure returns when all bytes are consumed from 'src'
+ * or 'dstsize' bytes are written to 'dst'
+ * @note Upon return, 'src' points to the first unwritten character in source
+ */
+
+static int
+cvt_ncpy (char *dst, int dstsize, const char **_src, size_t srcsize, int base, int ts)
+{
+ int i;
+ const char *src = *_src;
+
+ for (i = 0; i < dstsize && srcsize != 0; i++, src++, dst++, srcsize--)
+ {
+ *dst = *src;
+ if (*src == '\t')
+ {
+ int j;
+
+ j = TAB_SKIP (ts, i + base);
+ if (j > dstsize - i)
+ j = dstsize - i;
+ i += j - 1;
+ while (j-- > 0)
+ *dst++ = ' ';
+ dst--;
+ }
+ }
+ *_src = src;
+ return i + base;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Read line from memory, converting tabs to spaces and padding with spaces.
+ *
+ * @param src buffer to read from
+ * @param srcsize size of src buffer
+ * @param dst buffer to read to
+ * @param dstsize size of dst buffer, excluding trailing null
+ * @param skip number of characters to skip
+ * @param ts tab size
+ * @param show_cr show trailing carriage return as ^M
+ *
+ * @return negative on error, otherwise number of bytes except padding
+ */
+
+static int
+cvt_mget (const char *src, size_t srcsize, char *dst, int dstsize, int skip, int ts,
+ gboolean show_cr)
+{
+ int sz = 0;
+
+ if (src != NULL)
+ {
+ int i;
+ char *tmp = dst;
+ const int base = 0;
+
+ for (i = 0; dstsize != 0 && srcsize != 0 && *src != '\n'; i++, src++, srcsize--)
+ {
+ if (*src == '\t')
+ {
+ int j;
+
+ j = TAB_SKIP (ts, i + base);
+ i += j - 1;
+ while (j-- > 0)
+ {
+ if (skip > 0)
+ skip--;
+ else if (dstsize != 0)
+ {
+ dstsize--;
+ *dst++ = ' ';
+ }
+ }
+ }
+ else if (src[0] == '\r' && (srcsize == 1 || src[1] == '\n'))
+ {
+ if (skip == 0 && show_cr)
+ {
+ if (dstsize > 1)
+ {
+ dstsize -= 2;
+ *dst++ = '^';
+ *dst++ = 'M';
+ }
+ else
+ {
+ dstsize--;
+ *dst++ = '.';
+ }
+ }
+ break;
+ }
+ else if (skip > 0)
+ {
+#ifdef HAVE_CHARSET
+ int ch = 0;
+ int ch_length = 1;
+
+ (void) dview_get_utf (src, &ch, &ch_length);
+
+ if (ch_length > 1)
+ skip += ch_length - 1;
+#endif
+
+ skip--;
+ }
+ else
+ {
+ dstsize--;
+ *dst++ = *src;
+ }
+ }
+ sz = dst - tmp;
+ }
+ while (dstsize != 0)
+ {
+ dstsize--;
+ *dst++ = ' ';
+ }
+ *dst = '\0';
+ return sz;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Read line from memory and build attribute array.
+ *
+ * @param src buffer to read from
+ * @param srcsize size of src buffer
+ * @param dst buffer to read to
+ * @param dstsize size of dst buffer, excluding trailing null
+ * @param skip number of characters to skip
+ * @param ts tab size
+ * @param show_cr show trailing carriage return as ^M
+ * @param hdiff horizontal diff structure
+ * @param ord DIFF_LEFT if reading from first file, DIFF_RIGHT if reading from 2nd file
+ * @param att buffer of attributes
+ *
+ * @return negative on error, otherwise number of bytes except padding
+ */
+
+static int
+cvt_mgeta (const char *src, size_t srcsize, char *dst, int dstsize, int skip, int ts,
+ gboolean show_cr, GArray * hdiff, diff_place_t ord, char *att)
+{
+ int sz = 0;
+
+ if (src != NULL)
+ {
+ int i, k;
+ char *tmp = dst;
+ const int base = 0;
+
+ for (i = 0, k = 0; dstsize != 0 && srcsize != 0 && *src != '\n'; i++, k++, src++, srcsize--)
+ {
+ if (*src == '\t')
+ {
+ int j;
+
+ j = TAB_SKIP (ts, i + base);
+ i += j - 1;
+ while (j-- > 0)
+ {
+ if (skip != 0)
+ skip--;
+ else if (dstsize != 0)
+ {
+ dstsize--;
+ *att++ = is_inside (k, hdiff, ord);
+ *dst++ = ' ';
+ }
+ }
+ }
+ else if (src[0] == '\r' && (srcsize == 1 || src[1] == '\n'))
+ {
+ if (skip == 0 && show_cr)
+ {
+ if (dstsize > 1)
+ {
+ dstsize -= 2;
+ *att++ = is_inside (k, hdiff, ord);
+ *dst++ = '^';
+ *att++ = is_inside (k, hdiff, ord);
+ *dst++ = 'M';
+ }
+ else
+ {
+ dstsize--;
+ *att++ = is_inside (k, hdiff, ord);
+ *dst++ = '.';
+ }
+ }
+ break;
+ }
+ else if (skip != 0)
+ {
+#ifdef HAVE_CHARSET
+ int ch = 0;
+ int ch_length = 1;
+
+ (void) dview_get_utf (src, &ch, &ch_length);
+ if (ch_length > 1)
+ skip += ch_length - 1;
+#endif
+
+ skip--;
+ }
+ else
+ {
+ dstsize--;
+ *att++ = is_inside (k, hdiff, ord);
+ *dst++ = *src;
+ }
+ }
+ sz = dst - tmp;
+ }
+ while (dstsize != 0)
+ {
+ dstsize--;
+ *att++ = '\0';
+ *dst++ = ' ';
+ }
+ *dst = '\0';
+ return sz;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Read line from file, converting tabs to spaces and padding with spaces.
+ *
+ * @param f file stream to read from
+ * @param off offset of line inside file
+ * @param dst buffer to read to
+ * @param dstsize size of dst buffer, excluding trailing null
+ * @param skip number of characters to skip
+ * @param ts tab size
+ * @param show_cr show trailing carriage return as ^M
+ *
+ * @return negative on error, otherwise number of bytes except padding
+ */
+
+static int
+cvt_fget (FBUF * f, off_t off, char *dst, size_t dstsize, int skip, int ts, gboolean show_cr)
+{
+ int base = 0;
+ int old_base = base;
+ size_t amount = dstsize;
+ size_t useful, offset;
+ size_t i;
+ size_t sz;
+ int lastch = '\0';
+ const char *q = NULL;
+ char tmp[BUFSIZ]; /* XXX capacity must be >= MAX{dstsize + 1, amount} */
+ char cvt[BUFSIZ]; /* XXX capacity must be >= MAX_TAB_WIDTH * amount */
+
+ if (sizeof (tmp) < amount || sizeof (tmp) <= dstsize || sizeof (cvt) < 8 * amount)
+ {
+ /* abnormal, but avoid buffer overflow */
+ memset (dst, ' ', dstsize);
+ dst[dstsize] = '\0';
+ return 0;
+ }
+
+ f_seek (f, off, SEEK_SET);
+
+ while (skip > base)
+ {
+ old_base = base;
+ sz = f_gets (tmp, amount, f);
+ if (sz == 0)
+ break;
+
+ base = cvt_cpy (cvt, tmp, sz, old_base, ts);
+ if (cvt[base - old_base - 1] == '\n')
+ {
+ q = &cvt[base - old_base - 1];
+ base = old_base + q - cvt + 1;
+ break;
+ }
+ }
+
+ if (base < skip)
+ {
+ memset (dst, ' ', dstsize);
+ dst[dstsize] = '\0';
+ return 0;
+ }
+
+ useful = base - skip;
+ offset = skip - old_base;
+
+ if (useful <= dstsize)
+ {
+ if (useful != 0)
+ memmove (dst, cvt + offset, useful);
+
+ if (q == NULL)
+ {
+ sz = f_gets (tmp, dstsize - useful + 1, f);
+ if (sz != 0)
+ {
+ const char *ptr = tmp;
+
+ useful += cvt_ncpy (dst + useful, dstsize - useful, &ptr, sz, base, ts) - base;
+ if (ptr < tmp + sz)
+ lastch = *ptr;
+ }
+ }
+ sz = useful;
+ }
+ else
+ {
+ memmove (dst, cvt + offset, dstsize);
+ sz = dstsize;
+ lastch = cvt[offset + dstsize];
+ }
+
+ dst[sz] = lastch;
+ for (i = 0; i < sz && dst[i] != '\n'; i++)
+ {
+ if (dst[i] == '\r' && dst[i + 1] == '\n')
+ {
+ if (show_cr)
+ {
+ if (i + 1 < dstsize)
+ {
+ dst[i++] = '^';
+ dst[i++] = 'M';
+ }
+ else
+ {
+ dst[i++] = '*';
+ }
+ }
+ break;
+ }
+ }
+
+ for (; i < dstsize; i++)
+ dst[i] = ' ';
+ dst[i] = '\0';
+ return sz;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* diff printers et al ****************************************************** */
+
+static void
+cc_free_elt (void *elt)
+{
+ DIFFLN *p = elt;
+
+ if (p != NULL)
+ g_free (p->p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+printer (void *ctx, int ch, int line, off_t off, size_t sz, const char *str)
+{
+ GArray *a = ((PRINTER_CTX *) ctx)->a;
+ DSRC dsrc = ((PRINTER_CTX *) ctx)->dsrc;
+
+ if (ch != 0)
+ {
+ DIFFLN p;
+
+ p.p = NULL;
+ p.ch = ch;
+ p.line = line;
+ p.u.off = off;
+ if (dsrc == DATA_SRC_MEM && line != 0)
+ {
+ if (sz != 0 && str[sz - 1] == '\n')
+ sz--;
+ if (sz > 0)
+ p.p = g_strndup (str, sz);
+ p.u.len = sz;
+ }
+ g_array_append_val (a, p);
+ }
+ else if (dsrc == DATA_SRC_MEM)
+ {
+ DIFFLN *p;
+
+ p = &g_array_index (a, DIFFLN, a->len - 1);
+ if (sz != 0 && str[sz - 1] == '\n')
+ sz--;
+ if (sz != 0)
+ {
+ size_t new_size;
+ char *q;
+
+ new_size = p->u.len + sz;
+ q = g_realloc (p->p, new_size);
+ memcpy (q + p->u.len, str, sz);
+ p->p = q;
+ }
+ p->u.len += sz;
+ }
+ if (dsrc == DATA_SRC_TMP && (line != 0 || ch == 0))
+ {
+ FBUF *f = ((PRINTER_CTX *) ctx)->f;
+ f_write (f, str, sz);
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+redo_diff (WDiff * dview)
+{
+ FBUF *const *f = dview->f;
+ PRINTER_CTX ctx;
+ GArray *ops;
+ int ndiff;
+ int rv;
+ char extra[256];
+
+ extra[0] = '\0';
+ if (dview->opt.quality == 2)
+ strcat (extra, " -d");
+ if (dview->opt.quality == 1)
+ strcat (extra, " --speed-large-files");
+ if (dview->opt.strip_trailing_cr)
+ strcat (extra, " --strip-trailing-cr");
+ if (dview->opt.ignore_tab_expansion)
+ strcat (extra, " -E");
+ if (dview->opt.ignore_space_change)
+ strcat (extra, " -b");
+ if (dview->opt.ignore_all_space)
+ strcat (extra, " -w");
+ if (dview->opt.ignore_case)
+ strcat (extra, " -i");
+
+ if (dview->dsrc != DATA_SRC_MEM)
+ {
+ f_reset (f[DIFF_LEFT]);
+ f_reset (f[DIFF_RIGHT]);
+ }
+
+ ops = g_array_new (FALSE, FALSE, sizeof (DIFFCMD));
+ ndiff = dff_execute (dview->args, extra, dview->file[DIFF_LEFT], dview->file[DIFF_RIGHT], ops);
+ if (ndiff < 0)
+ {
+ if (ops != NULL)
+ g_array_free (ops, TRUE);
+ return -1;
+ }
+
+ ctx.dsrc = dview->dsrc;
+
+ rv = 0;
+ ctx.a = dview->a[DIFF_LEFT];
+ ctx.f = f[DIFF_LEFT];
+ rv |= dff_reparse (DIFF_LEFT, dview->file[DIFF_LEFT], ops, printer, &ctx);
+
+ ctx.a = dview->a[DIFF_RIGHT];
+ ctx.f = f[DIFF_RIGHT];
+ rv |= dff_reparse (DIFF_RIGHT, dview->file[DIFF_RIGHT], ops, printer, &ctx);
+
+ if (ops != NULL)
+ g_array_free (ops, TRUE);
+
+ if (rv != 0 || dview->a[DIFF_LEFT]->len != dview->a[DIFF_RIGHT]->len)
+ return -1;
+
+ if (dview->dsrc == DATA_SRC_TMP)
+ {
+ f_trunc (f[DIFF_LEFT]);
+ f_trunc (f[DIFF_RIGHT]);
+ }
+
+ if (dview->dsrc == DATA_SRC_MEM && HDIFF_ENABLE)
+ {
+ dview->hdiff = g_ptr_array_new ();
+ if (dview->hdiff != NULL)
+ {
+ size_t i;
+
+ for (i = 0; i < dview->a[DIFF_LEFT]->len; i++)
+ {
+ GArray *h = NULL;
+ const DIFFLN *p;
+ const DIFFLN *q;
+
+ p = &g_array_index (dview->a[DIFF_LEFT], DIFFLN, i);
+ q = &g_array_index (dview->a[DIFF_RIGHT], DIFFLN, i);
+ if (p->line && q->line && p->ch == CHG_CH)
+ {
+ h = g_array_new (FALSE, FALSE, sizeof (BRACKET));
+ if (h != NULL)
+ {
+ gboolean runresult;
+
+ runresult =
+ hdiff_scan (p->p, p->u.len, q->p, q->u.len, HDIFF_MINCTX, h,
+ HDIFF_DEPTH);
+ if (!runresult)
+ {
+ g_array_free (h, TRUE);
+ h = NULL;
+ }
+ }
+ }
+ g_ptr_array_add (dview->hdiff, h);
+ }
+ }
+ }
+ return ndiff;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+destroy_hdiff (WDiff * dview)
+{
+ if (dview->hdiff != NULL)
+ {
+ int i;
+ int len;
+
+ len = dview->a[DIFF_LEFT]->len;
+
+ for (i = 0; i < len; i++)
+ {
+ GArray *h;
+
+ h = (GArray *) g_ptr_array_index (dview->hdiff, i);
+ if (h != NULL)
+ g_array_free (h, TRUE);
+ }
+ g_ptr_array_free (dview->hdiff, TRUE);
+ dview->hdiff = NULL;
+ }
+
+ mc_search_free (dview->search.handle);
+ dview->search.handle = NULL;
+ MC_PTR_FREE (dview->search.last_string);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* stuff ******************************************************************** */
+
+static int
+get_digits (unsigned int n)
+{
+ int d = 1;
+
+ while (n /= 10)
+ d++;
+ return d;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+get_line_numbers (const GArray * a, size_t pos, int *linenum, int *lineofs)
+{
+ const DIFFLN *p;
+
+ *linenum = 0;
+ *lineofs = 0;
+
+ if (a->len != 0)
+ {
+ if (pos >= a->len)
+ pos = a->len - 1;
+
+ p = &g_array_index (a, DIFFLN, pos);
+
+ if (p->line == 0)
+ {
+ int n;
+
+ for (n = pos; n > 0; n--)
+ {
+ p--;
+ if (p->line != 0)
+ break;
+ }
+ *lineofs = pos - n + 1;
+ }
+
+ *linenum = p->line;
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+calc_nwidth (const GArray * const *a)
+{
+ int l1, o1;
+ int l2, o2;
+
+ get_line_numbers (a[DIFF_LEFT], a[DIFF_LEFT]->len - 1, &l1, &o1);
+ get_line_numbers (a[DIFF_RIGHT], a[DIFF_RIGHT]->len - 1, &l2, &o2);
+ if (l1 < l2)
+ l1 = l2;
+ return get_digits (l1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+find_prev_hunk (const GArray * a, int pos)
+{
+#if 1
+ while (pos > 0 && ((DIFFLN *) & g_array_index (a, DIFFLN, pos))->ch != EQU_CH)
+ pos--;
+ while (pos > 0 && ((DIFFLN *) & g_array_index (a, DIFFLN, pos))->ch == EQU_CH)
+ pos--;
+ while (pos > 0 && ((DIFFLN *) & g_array_index (a, DIFFLN, pos))->ch != EQU_CH)
+ pos--;
+ if (pos > 0 && (size_t) pos < a->len)
+ pos++;
+#else
+ while (pos > 0 && ((DIFFLN *) & g_array_index (a, DIFFLN, pos - 1))->ch == EQU_CH)
+ pos--;
+ while (pos > 0 && ((DIFFLN *) & g_array_index (a, DIFFLN, pos - 1))->ch != EQU_CH)
+ pos--;
+#endif
+
+ return pos;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static size_t
+find_next_hunk (const GArray * a, size_t pos)
+{
+ while (pos < a->len && ((DIFFLN *) & g_array_index (a, DIFFLN, pos))->ch != EQU_CH)
+ pos++;
+ while (pos < a->len && ((DIFFLN *) & g_array_index (a, DIFFLN, pos))->ch == EQU_CH)
+ pos++;
+ return pos;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Find start and end lines of the current hunk.
+ *
+ * @param dview WDiff widget
+ * @return boolean and
+ * start_line1 first line of current hunk (file[0])
+ * end_line1 last line of current hunk (file[0])
+ * start_line1 first line of current hunk (file[0])
+ * end_line1 last line of current hunk (file[0])
+ */
+
+static int
+get_current_hunk (WDiff * dview, int *start_line1, int *end_line1, int *start_line2, int *end_line2)
+{
+ const GArray *a0 = dview->a[DIFF_LEFT];
+ const GArray *a1 = dview->a[DIFF_RIGHT];
+ size_t pos;
+ int ch;
+ int res = 0;
+
+ *start_line1 = 1;
+ *start_line2 = 1;
+ *end_line1 = 1;
+ *end_line2 = 1;
+
+ pos = dview->skip_rows;
+ ch = ((DIFFLN *) & g_array_index (a0, DIFFLN, pos))->ch;
+ if (ch != EQU_CH)
+ {
+ switch (ch)
+ {
+ case ADD_CH:
+ res = DIFF_DEL;
+ break;
+ case DEL_CH:
+ res = DIFF_ADD;
+ break;
+ case CHG_CH:
+ res = DIFF_CHG;
+ break;
+ default:
+ break;
+ }
+ while (pos > 0 && ((DIFFLN *) & g_array_index (a0, DIFFLN, pos))->ch != EQU_CH)
+ pos--;
+ if (pos > 0)
+ {
+ *start_line1 = ((DIFFLN *) & g_array_index (a0, DIFFLN, pos))->line + 1;
+ *start_line2 = ((DIFFLN *) & g_array_index (a1, DIFFLN, pos))->line + 1;
+ }
+ pos = dview->skip_rows;
+ while (pos < a0->len && ((DIFFLN *) & g_array_index (a0, DIFFLN, pos))->ch != EQU_CH)
+ {
+ int l0, l1;
+
+ l0 = ((DIFFLN *) & g_array_index (a0, DIFFLN, pos))->line;
+ l1 = ((DIFFLN *) & g_array_index (a1, DIFFLN, pos))->line;
+ if (l0 > 0)
+ *end_line1 = MAX (*start_line1, l0);
+ if (l1 > 0)
+ *end_line2 = MAX (*start_line2, l1);
+ pos++;
+ }
+ }
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Remove hunk from file.
+ *
+ * @param dview WDiff widget
+ * @param merge_file file stream for writing data
+ * @param from1 first line of hunk
+ * @param to1 last line of hunk
+ * @param merge_direction in what direction files should be merged
+ */
+
+static void
+dview_remove_hunk (WDiff * dview, FILE * merge_file, int from1, int to1,
+ action_direction_t merge_direction)
+{
+ int line;
+ char buf[BUF_10K];
+ FILE *f0;
+
+ if (merge_direction == FROM_RIGHT_TO_LEFT)
+ f0 = fopen (dview->file[DIFF_RIGHT], "r");
+ else
+ f0 = fopen (dview->file[DIFF_LEFT], "r");
+
+ line = 0;
+ while (fgets (buf, sizeof (buf), f0) != NULL && line < from1 - 1)
+ {
+ line++;
+ fputs (buf, merge_file);
+ }
+ while (fgets (buf, sizeof (buf), f0) != NULL)
+ {
+ line++;
+ if (line >= to1)
+ fputs (buf, merge_file);
+ }
+ fclose (f0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Add hunk to file.
+ *
+ * @param dview WDiff widget
+ * @param merge_file file stream for writing data
+ * @param from1 first line of source hunk
+ * @param from2 first line of destination hunk
+ * @param to1 last line of source hunk
+ * @param merge_direction in what direction files should be merged
+ */
+
+static void
+dview_add_hunk (WDiff * dview, FILE * merge_file, int from1, int from2, int to2,
+ action_direction_t merge_direction)
+{
+ int line;
+ char buf[BUF_10K];
+ FILE *f0;
+ FILE *f1;
+
+ if (merge_direction == FROM_RIGHT_TO_LEFT)
+ {
+ f0 = fopen (dview->file[DIFF_RIGHT], "r");
+ f1 = fopen (dview->file[DIFF_LEFT], "r");
+ }
+ else
+ {
+ f0 = fopen (dview->file[DIFF_LEFT], "r");
+ f1 = fopen (dview->file[DIFF_RIGHT], "r");
+ }
+
+ line = 0;
+ while (fgets (buf, sizeof (buf), f0) != NULL && line < from1 - 1)
+ {
+ line++;
+ fputs (buf, merge_file);
+ }
+ line = 0;
+ while (fgets (buf, sizeof (buf), f1) != NULL && line <= to2)
+ {
+ line++;
+ if (line >= from2)
+ fputs (buf, merge_file);
+ }
+ while (fgets (buf, sizeof (buf), f0) != NULL)
+ fputs (buf, merge_file);
+
+ fclose (f0);
+ fclose (f1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Replace hunk in file.
+ *
+ * @param dview WDiff widget
+ * @param merge_file file stream for writing data
+ * @param from1 first line of source hunk
+ * @param to1 last line of source hunk
+ * @param from2 first line of destination hunk
+ * @param to2 last line of destination hunk
+ * @param merge_direction in what direction files should be merged
+ */
+
+static void
+dview_replace_hunk (WDiff * dview, FILE * merge_file, int from1, int to1, int from2, int to2,
+ action_direction_t merge_direction)
+{
+ int line1 = 0, line2 = 0;
+ char buf[BUF_10K];
+ FILE *f0;
+ FILE *f1;
+
+ if (merge_direction == FROM_RIGHT_TO_LEFT)
+ {
+ f0 = fopen (dview->file[DIFF_RIGHT], "r");
+ f1 = fopen (dview->file[DIFF_LEFT], "r");
+ }
+ else
+ {
+ f0 = fopen (dview->file[DIFF_LEFT], "r");
+ f1 = fopen (dview->file[DIFF_RIGHT], "r");
+ }
+
+ while (fgets (buf, sizeof (buf), f0) != NULL && line1 < from1 - 1)
+ {
+ line1++;
+ fputs (buf, merge_file);
+ }
+ while (fgets (buf, sizeof (buf), f1) != NULL && line2 <= to2)
+ {
+ line2++;
+ if (line2 >= from2)
+ fputs (buf, merge_file);
+ }
+ while (fgets (buf, sizeof (buf), f0) != NULL)
+ {
+ line1++;
+ if (line1 > to1)
+ fputs (buf, merge_file);
+ }
+ fclose (f0);
+ fclose (f1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Merge hunk.
+ *
+ * @param dview WDiff widget
+ * @param merge_direction in what direction files should be merged
+ */
+
+static void
+do_merge_hunk (WDiff * dview, action_direction_t merge_direction)
+{
+ int from1, to1, from2, to2;
+ int hunk;
+ diff_place_t n_merge = (merge_direction == FROM_RIGHT_TO_LEFT) ? DIFF_RIGHT : DIFF_LEFT;
+
+ if (merge_direction == FROM_RIGHT_TO_LEFT)
+ hunk = get_current_hunk (dview, &from2, &to2, &from1, &to1);
+ else
+ hunk = get_current_hunk (dview, &from1, &to1, &from2, &to2);
+
+ if (hunk > 0)
+ {
+ int merge_file_fd;
+ FILE *merge_file;
+ vfs_path_t *merge_file_name_vpath = NULL;
+
+ if (!dview->merged[n_merge])
+ {
+ dview->merged[n_merge] = mc_util_make_backup_if_possible (dview->file[n_merge], "~~~");
+ if (!dview->merged[n_merge])
+ {
+ message (D_ERROR, MSG_ERROR,
+ _("Cannot create backup file\n%s%s\n%s"),
+ dview->file[n_merge], "~~~", unix_error_string (errno));
+ return;
+ }
+ }
+
+ merge_file_fd = mc_mkstemps (&merge_file_name_vpath, "mcmerge", NULL);
+ if (merge_file_fd == -1)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot create temporary merge file\n%s"),
+ unix_error_string (errno));
+ return;
+ }
+
+ merge_file = fdopen (merge_file_fd, "w");
+
+ switch (hunk)
+ {
+ case DIFF_DEL:
+ if (merge_direction == FROM_RIGHT_TO_LEFT)
+ dview_add_hunk (dview, merge_file, from1, from2, to2, FROM_RIGHT_TO_LEFT);
+ else
+ dview_remove_hunk (dview, merge_file, from1, to1, FROM_LEFT_TO_RIGHT);
+ break;
+ case DIFF_ADD:
+ if (merge_direction == FROM_RIGHT_TO_LEFT)
+ dview_remove_hunk (dview, merge_file, from1, to1, FROM_RIGHT_TO_LEFT);
+ else
+ dview_add_hunk (dview, merge_file, from1, from2, to2, FROM_LEFT_TO_RIGHT);
+ break;
+ case DIFF_CHG:
+ dview_replace_hunk (dview, merge_file, from1, to1, from2, to2, merge_direction);
+ break;
+ default:
+ break;
+ }
+ fflush (merge_file);
+ fclose (merge_file);
+ {
+ int res;
+
+ res = rewrite_backup_content (merge_file_name_vpath, dview->file[n_merge]);
+ (void) res;
+ }
+ mc_unlink (merge_file_name_vpath);
+ vfs_path_free (merge_file_name_vpath, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* view routines and callbacks ********************************************** */
+
+static void
+dview_compute_split (WDiff * dview, int i)
+{
+ dview->bias += i;
+ if (dview->bias < 2 - dview->half1)
+ dview->bias = 2 - dview->half1;
+ if (dview->bias > dview->half2 - 2)
+ dview->bias = dview->half2 - 2;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_compute_areas (WDiff * dview)
+{
+ Widget *w = WIDGET (dview);
+
+ dview->height = w->rect.lines - 1;
+ dview->half1 = w->rect.cols / 2;
+ dview->half2 = w->rect.cols - dview->half1;
+
+ dview_compute_split (dview, 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_reread (WDiff * dview)
+{
+ int ndiff;
+
+ destroy_hdiff (dview);
+ if (dview->a[DIFF_LEFT] != NULL)
+ {
+ g_array_foreach (dview->a[DIFF_LEFT], DIFFLN, cc_free_elt);
+ g_array_free (dview->a[DIFF_LEFT], TRUE);
+ }
+ if (dview->a[DIFF_RIGHT] != NULL)
+ {
+ g_array_foreach (dview->a[DIFF_RIGHT], DIFFLN, cc_free_elt);
+ g_array_free (dview->a[DIFF_RIGHT], TRUE);
+ }
+
+ dview->a[DIFF_LEFT] = g_array_new (FALSE, FALSE, sizeof (DIFFLN));
+ dview->a[DIFF_RIGHT] = g_array_new (FALSE, FALSE, sizeof (DIFFLN));
+
+ ndiff = redo_diff (dview);
+ if (ndiff >= 0)
+ dview->ndiff = ndiff;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+static void
+dview_set_codeset (WDiff * dview)
+{
+ const char *encoding_id = NULL;
+
+ dview->utf8 = TRUE;
+ encoding_id =
+ get_codepage_id (mc_global.source_codepage >=
+ 0 ? mc_global.source_codepage : mc_global.display_codepage);
+ if (encoding_id != NULL)
+ {
+ GIConv conv;
+
+ conv = str_crt_conv_from (encoding_id);
+ if (conv != INVALID_CONV)
+ {
+ if (dview->converter != str_cnv_from_term)
+ str_close_conv (dview->converter);
+ dview->converter = conv;
+ }
+ dview->utf8 = (gboolean) str_isutf8 (encoding_id);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_select_encoding (WDiff * dview)
+{
+ if (do_select_codepage ())
+ dview_set_codeset (dview);
+ dview_reread (dview);
+ tty_touch_screen ();
+ repaint_screen ();
+}
+#endif /* HAVE_CHARSET */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_load_options (WDiff * dview)
+{
+ gboolean show_numbers, show_symbols;
+ int tab_size;
+
+ show_symbols = mc_config_get_bool (mc_global.main_config, "DiffView", "show_symbols", FALSE);
+ if (show_symbols)
+ dview->display_symbols = 1;
+ show_numbers = mc_config_get_bool (mc_global.main_config, "DiffView", "show_numbers", FALSE);
+ if (show_numbers)
+ dview->display_numbers = calc_nwidth ((const GArray * const *) dview->a);
+ tab_size = mc_config_get_int (mc_global.main_config, "DiffView", "tab_size", 8);
+ if (tab_size > 0 && tab_size < 9)
+ dview->tab_size = tab_size;
+ else
+ dview->tab_size = 8;
+
+ dview->opt.quality = mc_config_get_int (mc_global.main_config, "DiffView", "diff_quality", 0);
+
+ dview->opt.strip_trailing_cr =
+ mc_config_get_bool (mc_global.main_config, "DiffView", "diff_ignore_tws", FALSE);
+ dview->opt.ignore_all_space =
+ mc_config_get_bool (mc_global.main_config, "DiffView", "diff_ignore_all_space", FALSE);
+ dview->opt.ignore_space_change =
+ mc_config_get_bool (mc_global.main_config, "DiffView", "diff_ignore_space_change", FALSE);
+ dview->opt.ignore_tab_expansion =
+ mc_config_get_bool (mc_global.main_config, "DiffView", "diff_tab_expansion", FALSE);
+ dview->opt.ignore_case =
+ mc_config_get_bool (mc_global.main_config, "DiffView", "diff_ignore_case", FALSE);
+
+ dview->new_frame = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_save_options (WDiff * dview)
+{
+ mc_config_set_bool (mc_global.main_config, "DiffView", "show_symbols",
+ dview->display_symbols != 0);
+ mc_config_set_bool (mc_global.main_config, "DiffView", "show_numbers",
+ dview->display_numbers != 0);
+ mc_config_set_int (mc_global.main_config, "DiffView", "tab_size", dview->tab_size);
+
+ mc_config_set_int (mc_global.main_config, "DiffView", "diff_quality", dview->opt.quality);
+
+ mc_config_set_bool (mc_global.main_config, "DiffView", "diff_ignore_tws",
+ dview->opt.strip_trailing_cr);
+ mc_config_set_bool (mc_global.main_config, "DiffView", "diff_ignore_all_space",
+ dview->opt.ignore_all_space);
+ mc_config_set_bool (mc_global.main_config, "DiffView", "diff_ignore_space_change",
+ dview->opt.ignore_space_change);
+ mc_config_set_bool (mc_global.main_config, "DiffView", "diff_tab_expansion",
+ dview->opt.ignore_tab_expansion);
+ mc_config_set_bool (mc_global.main_config, "DiffView", "diff_ignore_case",
+ dview->opt.ignore_case);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_diff_options (WDiff * dview)
+{
+ const char *quality_str[] = {
+ N_("No&rmal"),
+ N_("&Fastest (Assume large files)"),
+ N_("&Minimal (Find a smaller set of change)")
+ };
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_GROUPBOX (N_("Diff algorithm")),
+ QUICK_RADIO (3, (const char **) quality_str, (int *) &dview->opt.quality, NULL),
+ QUICK_STOP_GROUPBOX,
+ QUICK_START_GROUPBOX (N_("Diff extra options")),
+ QUICK_CHECKBOX (N_("&Ignore case"), &dview->opt.ignore_case, NULL),
+ QUICK_CHECKBOX (N_("Ignore tab &expansion"), &dview->opt.ignore_tab_expansion, NULL),
+ QUICK_CHECKBOX (N_("Ignore &space change"), &dview->opt.ignore_space_change, NULL),
+ QUICK_CHECKBOX (N_("Ignore all &whitespace"), &dview->opt.ignore_all_space, NULL),
+ QUICK_CHECKBOX (N_("Strip &trailing carriage return"), &dview->opt.strip_trailing_cr,
+ NULL),
+ QUICK_STOP_GROUPBOX,
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 56 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Diff Options"), "[Diff Options]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ dview_reread (dview);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+dview_init (WDiff * dview, const char *args, const char *file1, const char *file2,
+ const char *label1, const char *label2, DSRC dsrc)
+{
+ int ndiff;
+ FBUF *f[DIFF_COUNT];
+
+ f[DIFF_LEFT] = NULL;
+ f[DIFF_RIGHT] = NULL;
+
+ if (dsrc == DATA_SRC_TMP)
+ {
+ f[DIFF_LEFT] = f_temp ();
+ if (f[DIFF_LEFT] == NULL)
+ return -1;
+
+ f[DIFF_RIGHT] = f_temp ();
+ if (f[DIFF_RIGHT] == NULL)
+ {
+ f_close (f[DIFF_LEFT]);
+ return -1;
+ }
+ }
+ else if (dsrc == DATA_SRC_ORG)
+ {
+ f[DIFF_LEFT] = f_open (file1, O_RDONLY);
+ if (f[DIFF_LEFT] == NULL)
+ return -1;
+
+ f[DIFF_RIGHT] = f_open (file2, O_RDONLY);
+ if (f[DIFF_RIGHT] == NULL)
+ {
+ f_close (f[DIFF_LEFT]);
+ return -1;
+ }
+ }
+
+ dview->view_quit = FALSE;
+
+ dview->bias = 0;
+ dview->new_frame = TRUE;
+ dview->skip_rows = 0;
+ dview->skip_cols = 0;
+ dview->display_symbols = 0;
+ dview->display_numbers = 0;
+ dview->show_cr = TRUE;
+ dview->tab_size = 8;
+ dview->ord = DIFF_LEFT;
+ dview->full = FALSE;
+
+ dview->search.handle = NULL;
+ dview->search.last_string = NULL;
+ dview->search.last_found_line = -1;
+ dview->search.last_accessed_num_line = -1;
+
+ dview_load_options (dview);
+
+ dview->args = args;
+ dview->file[DIFF_LEFT] = file1;
+ dview->file[DIFF_RIGHT] = file2;
+ dview->label[DIFF_LEFT] = g_strdup (label1);
+ dview->label[DIFF_RIGHT] = g_strdup (label2);
+ dview->f[DIFF_LEFT] = f[0];
+ dview->f[DIFF_RIGHT] = f[1];
+ dview->merged[DIFF_LEFT] = FALSE;
+ dview->merged[DIFF_RIGHT] = FALSE;
+ dview->hdiff = NULL;
+ dview->dsrc = dsrc;
+#ifdef HAVE_CHARSET
+ dview->converter = str_cnv_from_term;
+ dview_set_codeset (dview);
+#endif
+ dview->a[DIFF_LEFT] = g_array_new (FALSE, FALSE, sizeof (DIFFLN));
+ dview->a[DIFF_RIGHT] = g_array_new (FALSE, FALSE, sizeof (DIFFLN));
+
+ ndiff = redo_diff (dview);
+ if (ndiff < 0)
+ {
+ /* goto MSG_DESTROY stage: dview_fini() */
+ f_close (f[DIFF_LEFT]);
+ f_close (f[DIFF_RIGHT]);
+ return -1;
+ }
+
+ dview->ndiff = ndiff;
+
+ dview_compute_areas (dview);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_fini (WDiff * dview)
+{
+ if (dview->dsrc != DATA_SRC_MEM)
+ {
+ f_close (dview->f[DIFF_RIGHT]);
+ f_close (dview->f[DIFF_LEFT]);
+ }
+
+#ifdef HAVE_CHARSET
+ if (dview->converter != str_cnv_from_term)
+ str_close_conv (dview->converter);
+#endif
+
+ destroy_hdiff (dview);
+ if (dview->a[DIFF_LEFT] != NULL)
+ {
+ g_array_foreach (dview->a[DIFF_LEFT], DIFFLN, cc_free_elt);
+ g_array_free (dview->a[DIFF_LEFT], TRUE);
+ dview->a[DIFF_LEFT] = NULL;
+ }
+ if (dview->a[DIFF_RIGHT] != NULL)
+ {
+ g_array_foreach (dview->a[DIFF_RIGHT], DIFFLN, cc_free_elt);
+ g_array_free (dview->a[DIFF_RIGHT], TRUE);
+ dview->a[DIFF_RIGHT] = NULL;
+ }
+
+ g_free (dview->label[DIFF_LEFT]);
+ g_free (dview->label[DIFF_RIGHT]);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+dview_display_file (const WDiff * dview, diff_place_t ord, int r, int c, int height, int width)
+{
+ size_t i, k;
+ int j;
+ char buf[BUFSIZ];
+ FBUF *f = dview->f[ord];
+ int skip = dview->skip_cols;
+ int display_symbols = dview->display_symbols;
+ int display_numbers = dview->display_numbers;
+ gboolean show_cr = dview->show_cr;
+ int tab_size = 8;
+ const DIFFLN *p;
+ int nwidth = display_numbers;
+ int xwidth;
+
+ xwidth = display_symbols + display_numbers;
+ if (dview->tab_size > 0 && dview->tab_size < 9)
+ tab_size = dview->tab_size;
+
+ if (xwidth != 0)
+ {
+ if (xwidth > width && display_symbols)
+ {
+ xwidth--;
+ display_symbols = 0;
+ }
+ if (xwidth > width && display_numbers)
+ {
+ xwidth = width;
+ display_numbers = width;
+ }
+
+ xwidth++;
+ c += xwidth;
+ width -= xwidth;
+ if (width < 0)
+ width = 0;
+ }
+
+ if ((int) sizeof (buf) <= width || (int) sizeof (buf) <= nwidth)
+ {
+ /* abnormal, but avoid buffer overflow */
+ return -1;
+ }
+
+ for (i = dview->skip_rows, j = 0; i < dview->a[ord]->len && j < height; j++, i++)
+ {
+ int ch, next_ch = 0, col;
+ size_t cnt;
+
+ p = (DIFFLN *) & g_array_index (dview->a[ord], DIFFLN, i);
+ ch = p->ch;
+ tty_setcolor (NORMAL_COLOR);
+ if (display_symbols)
+ {
+ tty_gotoyx (r + j, c - 2);
+ tty_print_char (ch);
+ }
+ if (p->line != 0)
+ {
+ if (display_numbers)
+ {
+ tty_gotoyx (r + j, c - xwidth);
+ g_snprintf (buf, display_numbers + 1, "%*d", nwidth, p->line);
+ tty_print_string (str_fit_to_term (buf, nwidth, J_LEFT_FIT));
+ }
+ if (ch == ADD_CH)
+ tty_setcolor (DFF_ADD_COLOR);
+ if (ch == CHG_CH)
+ tty_setcolor (DFF_CHG_COLOR);
+ if (f == NULL)
+ {
+ if (i == (size_t) dview->search.last_found_line)
+ tty_setcolor (MARKED_SELECTED_COLOR);
+ else if (dview->hdiff != NULL && g_ptr_array_index (dview->hdiff, i) != NULL)
+ {
+ char att[BUFSIZ];
+
+#ifdef HAVE_CHARSET
+ if (dview->utf8)
+ k = dview_str_utf8_offset_to_pos (p->p, width);
+ else
+#endif
+ k = width;
+
+ cvt_mgeta (p->p, p->u.len, buf, k, skip, tab_size, show_cr,
+ g_ptr_array_index (dview->hdiff, i), ord, att);
+ tty_gotoyx (r + j, c);
+ col = 0;
+
+ for (cnt = 0; cnt < strlen (buf) && col < width; cnt++)
+ {
+ gboolean ch_res;
+
+#ifdef HAVE_CHARSET
+ if (dview->utf8)
+ {
+ int ch_length = 0;
+
+ ch_res = dview_get_utf (buf + cnt, &next_ch, &ch_length);
+ if (ch_length > 1)
+ cnt += ch_length - 1;
+ if (!g_unichar_isprint (next_ch))
+ next_ch = '.';
+ }
+ else
+#endif
+ ch_res = dview_get_byte (buf + cnt, &next_ch);
+
+ if (ch_res)
+ {
+ tty_setcolor (att[cnt] ? DFF_CHH_COLOR : DFF_CHG_COLOR);
+#ifdef HAVE_CHARSET
+ if (mc_global.utf8_display)
+ {
+ if (!dview->utf8)
+ {
+ next_ch =
+ convert_from_8bit_to_utf_c ((unsigned char) next_ch,
+ dview->converter);
+ }
+ }
+ else if (dview->utf8)
+ next_ch = convert_from_utf_to_current_c (next_ch, dview->converter);
+ else
+ next_ch = convert_to_display_c (next_ch);
+#endif
+ tty_print_anychar (next_ch);
+ col++;
+ }
+ }
+ continue;
+ }
+
+ if (ch == CHG_CH)
+ tty_setcolor (DFF_CHH_COLOR);
+
+#ifdef HAVE_CHARSET
+ if (dview->utf8)
+ k = dview_str_utf8_offset_to_pos (p->p, width);
+ else
+#endif
+ k = width;
+ cvt_mget (p->p, p->u.len, buf, k, skip, tab_size, show_cr);
+ }
+ else
+ cvt_fget (f, p->u.off, buf, width, skip, tab_size, show_cr);
+ }
+ else
+ {
+ if (display_numbers)
+ {
+ tty_gotoyx (r + j, c - xwidth);
+ memset (buf, ' ', display_numbers);
+ buf[display_numbers] = '\0';
+ tty_print_string (buf);
+ }
+ if (ch == DEL_CH)
+ tty_setcolor (DFF_DEL_COLOR);
+ if (ch == CHG_CH)
+ tty_setcolor (DFF_CHD_COLOR);
+ memset (buf, ' ', width);
+ buf[width] = '\0';
+ }
+ tty_gotoyx (r + j, c);
+ /* tty_print_nstring (buf, width); */
+ col = 0;
+ for (cnt = 0; cnt < strlen (buf) && col < width; cnt++)
+ {
+ gboolean ch_res;
+
+#ifdef HAVE_CHARSET
+ if (dview->utf8)
+ {
+ int ch_length = 0;
+
+ ch_res = dview_get_utf (buf + cnt, &next_ch, &ch_length);
+ if (ch_length > 1)
+ cnt += ch_length - 1;
+ if (!g_unichar_isprint (next_ch))
+ next_ch = '.';
+ }
+ else
+#endif
+ ch_res = dview_get_byte (buf + cnt, &next_ch);
+
+ if (ch_res)
+ {
+#ifdef HAVE_CHARSET
+ if (mc_global.utf8_display)
+ {
+ if (!dview->utf8)
+ {
+ next_ch =
+ convert_from_8bit_to_utf_c ((unsigned char) next_ch, dview->converter);
+ }
+ }
+ else if (dview->utf8)
+ next_ch = convert_from_utf_to_current_c (next_ch, dview->converter);
+ else
+ next_ch = convert_to_display_c (next_ch);
+#endif
+
+ tty_print_anychar (next_ch);
+ col++;
+ }
+ }
+ }
+ tty_setcolor (NORMAL_COLOR);
+ k = width;
+ if (width < xwidth - 1)
+ k = xwidth - 1;
+ memset (buf, ' ', k);
+ buf[k] = '\0';
+ for (; j < height; j++)
+ {
+ if (xwidth != 0)
+ {
+ tty_gotoyx (r + j, c - xwidth);
+ /* tty_print_nstring (buf, xwidth - 1); */
+ tty_print_string (str_fit_to_term (buf, xwidth - 1, J_LEFT_FIT));
+ }
+ tty_gotoyx (r + j, c);
+ /* tty_print_nstring (buf, width); */
+ tty_print_string (str_fit_to_term (buf, width, J_LEFT_FIT));
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_status (const WDiff * dview, diff_place_t ord, int width, int c)
+{
+ const char *buf;
+ int filename_width;
+ int linenum, lineofs;
+ vfs_path_t *vpath;
+ char *path;
+
+ tty_setcolor (STATUSBAR_COLOR);
+
+ tty_gotoyx (0, c);
+ get_line_numbers (dview->a[ord], dview->skip_rows, &linenum, &lineofs);
+
+ filename_width = width - 24;
+ if (filename_width < 8)
+ filename_width = 8;
+
+ vpath = vfs_path_from_str (dview->label[ord]);
+ path = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
+ vfs_path_free (vpath, TRUE);
+ buf = str_term_trim (path, filename_width);
+ if (ord == DIFF_LEFT)
+ tty_printf ("%s%-*s %6d+%-4d Col %-4d ", dview->merged[ord] ? "* " : " ", filename_width,
+ buf, linenum, lineofs, dview->skip_cols);
+ else
+ tty_printf ("%s%-*s %6d+%-4d Dif %-4d ", dview->merged[ord] ? "* " : " ", filename_width,
+ buf, linenum, lineofs, dview->ndiff);
+ g_free (path);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_redo (WDiff * dview)
+{
+ if (dview->display_numbers)
+ {
+ int old;
+
+ old = dview->display_numbers;
+ dview->display_numbers = calc_nwidth ((const GArray * const *) dview->a);
+ dview->new_frame = (old != dview->display_numbers);
+ }
+ dview_reread (dview);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_update (WDiff * dview)
+{
+ int height = dview->height;
+ int width1;
+ int width2;
+ int last;
+
+ last = dview->a[DIFF_LEFT]->len - 1;
+
+ if (dview->skip_rows > last)
+ dview->skip_rows = dview->search.last_accessed_num_line = last;
+ if (dview->skip_rows < 0)
+ dview->skip_rows = dview->search.last_accessed_num_line = 0;
+ if (dview->skip_cols < 0)
+ dview->skip_cols = 0;
+
+ if (height < 2)
+ return;
+
+ width1 = dview->half1 + dview->bias;
+ width2 = dview->half2 - dview->bias;
+ if (dview->full)
+ {
+ width1 = COLS;
+ width2 = 0;
+ }
+
+ if (dview->new_frame)
+ {
+ int xwidth;
+
+ tty_setcolor (NORMAL_COLOR);
+ xwidth = dview->display_symbols + dview->display_numbers;
+ if (width1 > 1)
+ tty_draw_box (1, 0, height, width1, FALSE);
+ if (width2 > 1)
+ tty_draw_box (1, width1, height, width2, FALSE);
+
+ if (xwidth != 0)
+ {
+ xwidth++;
+ if (xwidth < width1 - 1)
+ {
+ tty_gotoyx (1, xwidth);
+ tty_print_alt_char (ACS_TTEE, FALSE);
+ tty_gotoyx (height, xwidth);
+ tty_print_alt_char (ACS_BTEE, FALSE);
+ tty_draw_vline (2, xwidth, ACS_VLINE, height - 2);
+ }
+ if (xwidth < width2 - 1)
+ {
+ tty_gotoyx (1, width1 + xwidth);
+ tty_print_alt_char (ACS_TTEE, FALSE);
+ tty_gotoyx (height, width1 + xwidth);
+ tty_print_alt_char (ACS_BTEE, FALSE);
+ tty_draw_vline (2, width1 + xwidth, ACS_VLINE, height - 2);
+ }
+ }
+ dview->new_frame = FALSE;
+ }
+
+ if (width1 > 2)
+ {
+ dview_status (dview, dview->ord, width1, 0);
+ dview_display_file (dview, dview->ord, 2, 1, height - 2, width1 - 2);
+ }
+ if (width2 > 2)
+ {
+ dview_status (dview, dview->ord ^ 1, width2, width1);
+ dview_display_file (dview, dview->ord ^ 1, 2, width1 + 1, height - 2, width2 - 2);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_edit (WDiff * dview, diff_place_t ord)
+{
+ Widget *h;
+ gboolean h_modal;
+ int linenum, lineofs;
+
+ if (dview->dsrc == DATA_SRC_TMP)
+ {
+ error_dialog (_("Edit"), _("Edit is disabled"));
+ return;
+ }
+
+ h = WIDGET (WIDGET (dview)->owner);
+ h_modal = widget_get_state (h, WST_MODAL);
+
+ get_line_numbers (dview->a[ord], dview->skip_rows, &linenum, &lineofs);
+
+ /* disallow edit file in several editors */
+ widget_set_state (h, WST_MODAL, TRUE);
+
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_from_str (dview->file[ord]);
+ edit_file_at_line (tmp_vpath, use_internal_edit, linenum);
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+
+ widget_set_state (h, WST_MODAL, h_modal);
+ dview_redo (dview);
+ dview_update (dview);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_goto_cmd (WDiff * dview, diff_place_t ord)
+{
+ static gboolean first_run = TRUE;
+
+ /* *INDENT-OFF* */
+ static const char *title[2] = {
+ N_("Goto line (left)"),
+ N_("Goto line (right)")
+ };
+ /* *INDENT-ON* */
+
+ int newline;
+ char *input;
+
+ input =
+ input_dialog (_(title[ord]), _("Enter line:"), MC_HISTORY_YDIFF_GOTO_LINE,
+ first_run ? NULL : INPUT_LAST_TEXT, INPUT_COMPLETE_NONE);
+ if (input != NULL)
+ {
+ const char *s = input;
+
+ if (scan_deci (&s, &newline) == 0 && *s == '\0')
+ {
+ size_t i = 0;
+
+ if (newline > 0)
+ {
+ for (; i < dview->a[ord]->len; i++)
+ {
+ const DIFFLN *p;
+
+ p = &g_array_index (dview->a[ord], DIFFLN, i);
+ if (p->line == newline)
+ break;
+ }
+ }
+ dview->skip_rows = dview->search.last_accessed_num_line = (ssize_t) i;
+ }
+ g_free (input);
+ }
+
+ first_run = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_labels (WDiff * dview)
+{
+ Widget *d = WIDGET (dview);
+ WButtonBar *b;
+
+ b = buttonbar_find (DIALOG (d->owner));
+
+ buttonbar_set_label (b, 1, Q_ ("ButtonBar|Help"), d->keymap, d);
+ buttonbar_set_label (b, 2, Q_ ("ButtonBar|Save"), d->keymap, d);
+ buttonbar_set_label (b, 4, Q_ ("ButtonBar|Edit"), d->keymap, d);
+ buttonbar_set_label (b, 5, Q_ ("ButtonBar|Merge"), d->keymap, d);
+ buttonbar_set_label (b, 7, Q_ ("ButtonBar|Search"), d->keymap, d);
+ buttonbar_set_label (b, 9, Q_ ("ButtonBar|Options"), d->keymap, d);
+ buttonbar_set_label (b, 10, Q_ ("ButtonBar|Quit"), d->keymap, d);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+dview_save (WDiff * dview)
+{
+ gboolean res = TRUE;
+
+ if (dview->merged[DIFF_LEFT])
+ {
+ res = mc_util_unlink_backup_if_possible (dview->file[DIFF_LEFT], "~~~");
+ dview->merged[DIFF_LEFT] = !res;
+ }
+ if (dview->merged[DIFF_RIGHT])
+ {
+ res = mc_util_unlink_backup_if_possible (dview->file[DIFF_RIGHT], "~~~");
+ dview->merged[DIFF_RIGHT] = !res;
+ }
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_do_save (WDiff * dview)
+{
+ (void) dview_save (dview);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ * Check if it's OK to close the diff viewer. If there are unsaved changes,
+ * ask user.
+ */
+static gboolean
+dview_ok_to_exit (WDiff * dview)
+{
+ gboolean res = TRUE;
+ int act;
+
+ if (!dview->merged[DIFF_LEFT] && !dview->merged[DIFF_RIGHT])
+ return res;
+
+ act = query_dialog (_("Quit"), !mc_global.midnight_shutdown ?
+ _("File(s) was modified. Save with exit?") :
+ _("Midnight Commander is being shut down.\nSave modified file(s)?"),
+ D_NORMAL, 2, _("&Yes"), _("&No"));
+
+ /* Esc is No */
+ if (mc_global.midnight_shutdown || (act == -1))
+ act = 1;
+
+ switch (act)
+ {
+ case -1: /* Esc */
+ res = FALSE;
+ break;
+ case 0: /* Yes */
+ (void) dview_save (dview);
+ res = TRUE;
+ break;
+ case 1: /* No */
+ if (mc_util_restore_from_backup_if_possible (dview->file[DIFF_LEFT], "~~~"))
+ res = mc_util_unlink_backup_if_possible (dview->file[DIFF_LEFT], "~~~");
+ if (mc_util_restore_from_backup_if_possible (dview->file[DIFF_RIGHT], "~~~"))
+ res = mc_util_unlink_backup_if_possible (dview->file[DIFF_RIGHT], "~~~");
+ MC_FALLTHROUGH;
+ default:
+ res = TRUE;
+ break;
+ }
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+dview_execute_cmd (WDiff * dview, long command)
+{
+ cb_ret_t res = MSG_HANDLED;
+
+ switch (command)
+ {
+ case CK_ShowSymbols:
+ dview->display_symbols ^= 1;
+ dview->new_frame = TRUE;
+ break;
+ case CK_ShowNumbers:
+ dview->display_numbers ^= calc_nwidth ((const GArray * const *) dview->a);
+ dview->new_frame = TRUE;
+ break;
+ case CK_SplitFull:
+ dview->full = !dview->full;
+ dview->new_frame = TRUE;
+ break;
+ case CK_SplitEqual:
+ if (!dview->full)
+ {
+ dview->bias = 0;
+ dview->new_frame = TRUE;
+ }
+ break;
+ case CK_SplitMore:
+ if (!dview->full)
+ {
+ dview_compute_split (dview, 1);
+ dview->new_frame = TRUE;
+ }
+ break;
+
+ case CK_SplitLess:
+ if (!dview->full)
+ {
+ dview_compute_split (dview, -1);
+ dview->new_frame = TRUE;
+ }
+ break;
+ case CK_Tab2:
+ dview->tab_size = 2;
+ break;
+ case CK_Tab3:
+ dview->tab_size = 3;
+ break;
+ case CK_Tab4:
+ dview->tab_size = 4;
+ break;
+ case CK_Tab8:
+ dview->tab_size = 8;
+ break;
+ case CK_Swap:
+ dview->ord ^= 1;
+ break;
+ case CK_Redo:
+ dview_redo (dview);
+ break;
+ case CK_HunkNext:
+ dview->skip_rows = dview->search.last_accessed_num_line =
+ find_next_hunk (dview->a[DIFF_LEFT], dview->skip_rows);
+ break;
+ case CK_HunkPrev:
+ dview->skip_rows = dview->search.last_accessed_num_line =
+ find_prev_hunk (dview->a[DIFF_LEFT], dview->skip_rows);
+ break;
+ case CK_Goto:
+ dview_goto_cmd (dview, DIFF_RIGHT);
+ break;
+ case CK_Edit:
+ dview_edit (dview, dview->ord);
+ break;
+ case CK_Merge:
+ do_merge_hunk (dview, FROM_LEFT_TO_RIGHT);
+ dview_redo (dview);
+ break;
+ case CK_MergeOther:
+ do_merge_hunk (dview, FROM_RIGHT_TO_LEFT);
+ dview_redo (dview);
+ break;
+ case CK_EditOther:
+ dview_edit (dview, dview->ord ^ 1);
+ break;
+ case CK_Search:
+ dview_search_cmd (dview);
+ break;
+ case CK_SearchContinue:
+ dview_continue_search_cmd (dview);
+ break;
+ case CK_Top:
+ dview->skip_rows = dview->search.last_accessed_num_line = 0;
+ break;
+ case CK_Bottom:
+ dview->skip_rows = dview->search.last_accessed_num_line = dview->a[DIFF_LEFT]->len - 1;
+ break;
+ case CK_Up:
+ if (dview->skip_rows > 0)
+ {
+ dview->skip_rows--;
+ dview->search.last_accessed_num_line = dview->skip_rows;
+ }
+ break;
+ case CK_Down:
+ dview->skip_rows++;
+ dview->search.last_accessed_num_line = dview->skip_rows;
+ break;
+ case CK_PageDown:
+ if (dview->height > 2)
+ {
+ dview->skip_rows += dview->height - 2;
+ dview->search.last_accessed_num_line = dview->skip_rows;
+ }
+ break;
+ case CK_PageUp:
+ if (dview->height > 2)
+ {
+ dview->skip_rows -= dview->height - 2;
+ dview->search.last_accessed_num_line = dview->skip_rows;
+ }
+ break;
+ case CK_Left:
+ dview->skip_cols--;
+ break;
+ case CK_Right:
+ dview->skip_cols++;
+ break;
+ case CK_LeftQuick:
+ dview->skip_cols -= 8;
+ break;
+ case CK_RightQuick:
+ dview->skip_cols += 8;
+ break;
+ case CK_Home:
+ dview->skip_cols = 0;
+ break;
+ case CK_Shell:
+ toggle_subshell ();
+ break;
+ case CK_Quit:
+ dview->view_quit = TRUE;
+ break;
+ case CK_Save:
+ dview_do_save (dview);
+ break;
+ case CK_Options:
+ dview_diff_options (dview);
+ break;
+#ifdef HAVE_CHARSET
+ case CK_SelectCodepage:
+ dview_select_encoding (dview);
+ break;
+#endif
+ case CK_Cancel:
+ /* don't close diffviewer due to SIGINT */
+ break;
+ default:
+ res = MSG_NOT_HANDLED;
+ }
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+dview_handle_key (WDiff * dview, int key)
+{
+ long command;
+
+#ifdef HAVE_CHARSET
+ key = convert_from_input_c (key);
+#endif
+
+ command = widget_lookup_key (WIDGET (dview), key);
+ if (command == CK_IgnoreKey)
+ return MSG_NOT_HANDLED;
+
+ return dview_execute_cmd (dview, command);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+dview_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDiff *dview = (WDiff *) w;
+ WDialog *h = DIALOG (w->owner);
+ cb_ret_t i;
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ dview_labels (dview);
+ dview_update (dview);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ dview->new_frame = TRUE;
+ dview_update (dview);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ i = dview_handle_key (dview, parm);
+ if (dview->view_quit)
+ dlg_close (h);
+ else
+ dview_update (dview);
+ return i;
+
+ case MSG_ACTION:
+ i = dview_execute_cmd (dview, parm);
+ if (dview->view_quit)
+ dlg_close (h);
+ else
+ dview_update (dview);
+ return i;
+
+ case MSG_RESIZE:
+ widget_default_callback (w, NULL, MSG_RESIZE, 0, data);
+ dview_compute_areas (dview);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ dview_save_options (dview);
+ dview_fini (dview);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dview_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WDiff *dview = (WDiff *) w;
+
+ (void) event;
+
+ switch (msg)
+ {
+ case MSG_MOUSE_SCROLL_UP:
+ case MSG_MOUSE_SCROLL_DOWN:
+ if (msg == MSG_MOUSE_SCROLL_UP)
+ dview->skip_rows -= 2;
+ else
+ dview->skip_rows += 2;
+
+ dview->search.last_accessed_num_line = dview->skip_rows;
+ dview_update (dview);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+dview_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDiff *dview;
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_ACTION:
+ /* Handle shortcuts. */
+
+ /* Note: the buttonbar sends messages directly to the the WDiff, not to
+ * here, which is why we can pass NULL in the following call. */
+ return dview_execute_cmd (NULL, parm);
+
+ case MSG_VALIDATE:
+ dview = (WDiff *) widget_find_by_type (CONST_WIDGET (h), dview_callback);
+ /* don't stop the dialog before final decision */
+ widget_set_state (w, WST_ACTIVE, TRUE);
+ if (dview_ok_to_exit (dview))
+ dlg_close (h);
+ return MSG_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+dview_get_title (const WDialog * h, size_t len)
+{
+ const WDiff *dview;
+ const char *modified = " (*) ";
+ const char *notmodified = " ";
+ size_t len1;
+ GString *title;
+
+ dview = (const WDiff *) widget_find_by_type (CONST_WIDGET (h), dview_callback);
+ len1 = (len - str_term_width1 (_("Diff:")) - strlen (modified) - 3) / 2;
+
+ title = g_string_sized_new (len);
+ g_string_append (title, _("Diff:"));
+ g_string_append (title, dview->merged[DIFF_LEFT] ? modified : notmodified);
+ g_string_append (title, str_term_trim (dview->label[DIFF_LEFT], len1));
+ g_string_append (title, " | ");
+ g_string_append (title, dview->merged[DIFF_RIGHT] ? modified : notmodified);
+ g_string_append (title, str_term_trim (dview->label[DIFF_RIGHT], len1));
+
+ return g_string_free (title, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+diff_view (const char *file1, const char *file2, const char *label1, const char *label2)
+{
+ int error;
+ WDiff *dview;
+ Widget *w;
+ WDialog *dview_dlg;
+ Widget *dw;
+ WRect r;
+ WGroup *g;
+
+ /* Create dialog and widgets, put them on the dialog */
+ dview_dlg =
+ dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, NULL, dview_dialog_callback, NULL,
+ "[Diff Viewer]", NULL);
+ dw = WIDGET (dview_dlg);
+ widget_want_tab (dw, TRUE);
+ r = dw->rect;
+
+ g = GROUP (dview_dlg);
+
+ dview = g_new0 (WDiff, 1);
+ w = WIDGET (dview);
+ r.lines--;
+ widget_init (w, &r, dview_callback, dview_mouse_callback);
+ w->options |= WOP_SELECTABLE;
+ w->keymap = diff_map;
+ group_add_widget_autopos (g, w, WPOS_KEEP_ALL, NULL);
+
+ w = WIDGET (buttonbar_new ());
+ group_add_widget_autopos (g, w, w->pos_flags, NULL);
+
+ dview_dlg->get_title = dview_get_title;
+
+ error = dview_init (dview, "-a", file1, file2, label1, label2, DATA_SRC_MEM); /* XXX binary diff? */
+
+ if (error == 0)
+ dlg_run (dview_dlg);
+
+ if (error != 0 || widget_get_state (dw, WST_CLOSED))
+ widget_destroy (dw);
+
+ return error == 0 ? 1 : 0;
+}
+
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#define GET_FILE_AND_STAMP(n) \
+do \
+{ \
+ use_copy##n = 0; \
+ real_file##n = file##n; \
+ if (!vfs_file_is_local (file##n)) \
+ { \
+ real_file##n = mc_getlocalcopy (file##n); \
+ if (real_file##n != NULL) \
+ { \
+ use_copy##n = 1; \
+ if (mc_stat (real_file##n, &st##n) != 0) \
+ use_copy##n = -1; \
+ } \
+ } \
+} \
+while (0)
+
+#define UNGET_FILE(n) \
+do \
+{ \
+ if (use_copy##n) \
+ { \
+ int changed = 0; \
+ if (use_copy##n > 0) \
+ { \
+ time_t mtime; \
+ mtime = st##n.st_mtime; \
+ if (mc_stat (real_file##n, &st##n) == 0) \
+ changed = (mtime != st##n.st_mtime); \
+ } \
+ mc_ungetlocalcopy (file##n, real_file##n, changed); \
+ vfs_path_free (real_file##n, TRUE); \
+ } \
+} \
+while (0)
+
+gboolean
+dview_diff_cmd (const void *f0, const void *f1)
+{
+ int rv = 0;
+ vfs_path_t *file0 = NULL;
+ vfs_path_t *file1 = NULL;
+ gboolean is_dir0 = FALSE;
+ gboolean is_dir1 = FALSE;
+
+ switch (mc_global.mc_run_mode)
+ {
+ case MC_RUN_FULL:
+ {
+ /* run from panels */
+ const WPanel *panel0 = (const WPanel *) f0;
+ const WPanel *panel1 = (const WPanel *) f1;
+ const file_entry_t *fe0;
+ const file_entry_t *fe1;
+
+ fe0 = panel_current_entry (panel0);
+ file0 = vfs_path_append_new (panel0->cwd_vpath, fe0->fname->str, (char *) NULL);
+ is_dir0 = S_ISDIR (fe0->st.st_mode);
+ if (is_dir0)
+ {
+ message (D_ERROR, MSG_ERROR, _("\"%s\" is a directory"),
+ path_trunc (fe0->fname->str, 30));
+ goto ret;
+ }
+
+ fe1 = panel_current_entry (panel1);
+ file1 = vfs_path_append_new (panel1->cwd_vpath, fe1->fname->str, (char *) NULL);
+ is_dir1 = S_ISDIR (fe1->st.st_mode);
+ if (is_dir1)
+ {
+ message (D_ERROR, MSG_ERROR, _("\"%s\" is a directory"),
+ path_trunc (fe1->fname->str, 30));
+ goto ret;
+ }
+ break;
+ }
+
+ case MC_RUN_DIFFVIEWER:
+ {
+ /* run from command line */
+ const char *p0 = (const char *) f0;
+ const char *p1 = (const char *) f1;
+ struct stat st;
+
+ file0 = vfs_path_from_str (p0);
+ if (mc_stat (file0, &st) == 0)
+ {
+ is_dir0 = S_ISDIR (st.st_mode);
+ if (is_dir0)
+ {
+ message (D_ERROR, MSG_ERROR, _("\"%s\" is a directory"), path_trunc (p0, 30));
+ goto ret;
+ }
+ }
+ else
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot stat \"%s\"\n%s"),
+ path_trunc (p0, 30), unix_error_string (errno));
+ goto ret;
+ }
+
+ file1 = vfs_path_from_str (p1);
+ if (mc_stat (file1, &st) == 0)
+ {
+ is_dir1 = S_ISDIR (st.st_mode);
+ if (is_dir1)
+ {
+ message (D_ERROR, MSG_ERROR, _("\"%s\" is a directory"), path_trunc (p1, 30));
+ goto ret;
+ }
+ }
+ else
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot stat \"%s\"\n%s"),
+ path_trunc (p1, 30), unix_error_string (errno));
+ goto ret;
+ }
+ break;
+ }
+
+ default:
+ /* this should not happened */
+ message (D_ERROR, MSG_ERROR, _("Diff viewer: invalid mode"));
+ return FALSE;
+ }
+
+ if (rv == 0)
+ {
+ rv = -1;
+ if (file0 != NULL && file1 != NULL)
+ {
+ int use_copy0;
+ int use_copy1;
+ struct stat st0;
+ struct stat st1;
+ vfs_path_t *real_file0;
+ vfs_path_t *real_file1;
+
+ GET_FILE_AND_STAMP (0);
+ GET_FILE_AND_STAMP (1);
+
+ if (real_file0 != NULL && real_file1 != NULL)
+ rv = diff_view (vfs_path_as_str (real_file0), vfs_path_as_str (real_file1),
+ vfs_path_as_str (file0), vfs_path_as_str (file1));
+
+ UNGET_FILE (1);
+ UNGET_FILE (0);
+ }
+ }
+
+ if (rv == 0)
+ message (D_ERROR, MSG_ERROR, _("Two files are needed to compare"));
+
+ ret:
+ vfs_path_free (file1, TRUE);
+ vfs_path_free (file0, TRUE);
+
+ return (rv != 0);
+}
+
+#undef GET_FILE_AND_STAMP
+#undef UNGET_FILE
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/diffviewer/ydiff.h b/src/diffviewer/ydiff.h
new file mode 100644
index 0000000..90462c0
--- /dev/null
+++ b/src/diffviewer/ydiff.h
@@ -0,0 +1,16 @@
+#ifndef MC__DIFFVIEW_YDIFF_H
+#define MC__DIFFVIEW_YDIFF_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+gboolean dview_diff_cmd (const void *f0, const void *f1);
+
+#endif /* MC__DIFFVIEW_YDIFF_H */
diff --git a/src/editor/Makefile.am b/src/editor/Makefile.am
new file mode 100644
index 0000000..304cb35
--- /dev/null
+++ b/src/editor/Makefile.am
@@ -0,0 +1,33 @@
+EXTRA_DIST =
+
+if USE_INTERNAL_EDIT
+noinst_LTLIBRARIES = libedit.la
+else
+noinst_LTLIBRARIES =
+endif
+
+libedit_la_SOURCES = \
+ bookmark.c \
+ edit-impl.h \
+ edit.c edit.h \
+ editcomplete.c editcomplete.h \
+ editbuffer.c editbuffer.h \
+ editcmd.c \
+ editdraw.c \
+ editmacros.c editmacros.h \
+ editmenu.c \
+ editoptions.c \
+ editsearch.c editsearch.h \
+ editwidget.c editwidget.h \
+ etags.c etags.h \
+ format.c \
+ syntax.c
+
+if USE_ASPELL
+if HAVE_GMODULE
+libedit_la_SOURCES += \
+ spell.c spell.h
+endif
+endif
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
diff --git a/src/editor/Makefile.in b/src/editor/Makefile.in
new file mode 100644
index 0000000..b20d678
--- /dev/null
+++ b/src/editor/Makefile.in
@@ -0,0 +1,801 @@
+# 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@
+@HAVE_GMODULE_TRUE@@USE_ASPELL_TRUE@am__append_1 = \
+@HAVE_GMODULE_TRUE@@USE_ASPELL_TRUE@ spell.c spell.h
+
+subdir = src/editor
+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)
+libedit_la_LIBADD =
+am__libedit_la_SOURCES_DIST = bookmark.c edit-impl.h edit.c edit.h \
+ editcomplete.c editcomplete.h editbuffer.c editbuffer.h \
+ editcmd.c editdraw.c editmacros.c editmacros.h editmenu.c \
+ editoptions.c editsearch.c editsearch.h editwidget.c \
+ editwidget.h etags.c etags.h format.c syntax.c spell.c spell.h
+@HAVE_GMODULE_TRUE@@USE_ASPELL_TRUE@am__objects_1 = spell.lo
+am_libedit_la_OBJECTS = bookmark.lo edit.lo editcomplete.lo \
+ editbuffer.lo editcmd.lo editdraw.lo editmacros.lo editmenu.lo \
+ editoptions.lo editsearch.lo editwidget.lo etags.lo format.lo \
+ syntax.lo $(am__objects_1)
+libedit_la_OBJECTS = $(am_libedit_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 =
+@USE_INTERNAL_EDIT_TRUE@am_libedit_la_rpath =
+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)/bookmark.Plo ./$(DEPDIR)/edit.Plo \
+ ./$(DEPDIR)/editbuffer.Plo ./$(DEPDIR)/editcmd.Plo \
+ ./$(DEPDIR)/editcomplete.Plo ./$(DEPDIR)/editdraw.Plo \
+ ./$(DEPDIR)/editmacros.Plo ./$(DEPDIR)/editmenu.Plo \
+ ./$(DEPDIR)/editoptions.Plo ./$(DEPDIR)/editsearch.Plo \
+ ./$(DEPDIR)/editwidget.Plo ./$(DEPDIR)/etags.Plo \
+ ./$(DEPDIR)/format.Plo ./$(DEPDIR)/spell.Plo \
+ ./$(DEPDIR)/syntax.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 = $(libedit_la_SOURCES)
+DIST_SOURCES = $(am__libedit_la_SOURCES_DIST)
+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@
+EXTRA_DIST =
+@USE_INTERNAL_EDIT_FALSE@noinst_LTLIBRARIES =
+@USE_INTERNAL_EDIT_TRUE@noinst_LTLIBRARIES = libedit.la
+libedit_la_SOURCES = bookmark.c edit-impl.h edit.c edit.h \
+ editcomplete.c editcomplete.h editbuffer.c editbuffer.h \
+ editcmd.c editdraw.c editmacros.c editmacros.h editmenu.c \
+ editoptions.c editsearch.c editsearch.h editwidget.c \
+ editwidget.h etags.c etags.h format.c syntax.c $(am__append_1)
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+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/editor/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/editor/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}; \
+ }
+
+libedit.la: $(libedit_la_OBJECTS) $(libedit_la_DEPENDENCIES) $(EXTRA_libedit_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(am_libedit_la_rpath) $(libedit_la_OBJECTS) $(libedit_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bookmark.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/edit.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editbuffer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editcmd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editcomplete.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editdraw.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editmacros.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editmenu.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editoptions.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editsearch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/editwidget.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/etags.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/format.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/spell.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/syntax.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)/bookmark.Plo
+ -rm -f ./$(DEPDIR)/edit.Plo
+ -rm -f ./$(DEPDIR)/editbuffer.Plo
+ -rm -f ./$(DEPDIR)/editcmd.Plo
+ -rm -f ./$(DEPDIR)/editcomplete.Plo
+ -rm -f ./$(DEPDIR)/editdraw.Plo
+ -rm -f ./$(DEPDIR)/editmacros.Plo
+ -rm -f ./$(DEPDIR)/editmenu.Plo
+ -rm -f ./$(DEPDIR)/editoptions.Plo
+ -rm -f ./$(DEPDIR)/editsearch.Plo
+ -rm -f ./$(DEPDIR)/editwidget.Plo
+ -rm -f ./$(DEPDIR)/etags.Plo
+ -rm -f ./$(DEPDIR)/format.Plo
+ -rm -f ./$(DEPDIR)/spell.Plo
+ -rm -f ./$(DEPDIR)/syntax.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)/bookmark.Plo
+ -rm -f ./$(DEPDIR)/edit.Plo
+ -rm -f ./$(DEPDIR)/editbuffer.Plo
+ -rm -f ./$(DEPDIR)/editcmd.Plo
+ -rm -f ./$(DEPDIR)/editcomplete.Plo
+ -rm -f ./$(DEPDIR)/editdraw.Plo
+ -rm -f ./$(DEPDIR)/editmacros.Plo
+ -rm -f ./$(DEPDIR)/editmenu.Plo
+ -rm -f ./$(DEPDIR)/editoptions.Plo
+ -rm -f ./$(DEPDIR)/editsearch.Plo
+ -rm -f ./$(DEPDIR)/editwidget.Plo
+ -rm -f ./$(DEPDIR)/etags.Plo
+ -rm -f ./$(DEPDIR)/format.Plo
+ -rm -f ./$(DEPDIR)/spell.Plo
+ -rm -f ./$(DEPDIR)/syntax.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/editor/bookmark.c b/src/editor/bookmark.c
new file mode 100644
index 0000000..d530660
--- /dev/null
+++ b/src/editor/bookmark.c
@@ -0,0 +1,349 @@
+/*
+ Editor book mark handling
+
+ Copyright (C) 2001-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1996, 1997
+
+ 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
+ * \brief Source: editor book mark handling
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "lib/util.h" /* MAX_SAVED_BOOKMARKS */
+
+#include "editwidget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** note, if there is more than one bookmark on a line, then they are
+ appended after each other and the last one is always the one found
+ by book_mark_found() i.e. last in is the one seen */
+
+static edit_book_mark_t *
+double_marks (WEdit * edit, edit_book_mark_t * p)
+{
+ (void) edit;
+
+ if (p->next != NULL)
+ while (p->next->line == p->line)
+ p = p->next;
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns the first bookmark on or before this line */
+
+edit_book_mark_t *
+book_mark_find (WEdit * edit, long line)
+{
+ edit_book_mark_t *p;
+
+ if (edit->book_mark == NULL)
+ {
+ /* must have an imaginary top bookmark at line -1 to make things less complicated */
+ edit->book_mark = g_new0 (edit_book_mark_t, 1);
+ edit->book_mark->line = -1;
+ return edit->book_mark;
+ }
+
+ for (p = edit->book_mark; p != NULL; p = p->next)
+ {
+ if (p->line > line)
+ break; /* gone past it going downward */
+
+ if (p->next != NULL)
+ {
+ if (p->next->line > line)
+ {
+ edit->book_mark = p;
+ return double_marks (edit, p);
+ }
+ }
+ else
+ {
+ edit->book_mark = p;
+ return double_marks (edit, p);
+ }
+ }
+
+ for (p = edit->book_mark; p != NULL; p = p->prev)
+ {
+ if (p->next != NULL && p->next->line <= line)
+ break; /* gone past it going upward */
+
+ if (p->line <= line)
+ {
+ if (p->next != NULL)
+ {
+ if (p->next->line > line)
+ {
+ edit->book_mark = p;
+ return double_marks (edit, p);
+ }
+ }
+ else
+ {
+ edit->book_mark = p;
+ return double_marks (edit, p);
+ }
+ }
+ }
+
+ return NULL; /* can't get here */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Check if bookmark bookmark exists at this line of this color
+ *
+ * @param edit editor object
+ * @param line line where book mark is
+ * @param c color of book mark
+ * @return TRUE if bookmark exists at this line of color c, FALSE otherwise
+ */
+
+gboolean
+book_mark_query_color (WEdit * edit, long line, int c)
+{
+ if (edit->book_mark != NULL)
+ {
+ edit_book_mark_t *p;
+
+ for (p = book_mark_find (edit, line); p != NULL; p = p->prev)
+ {
+ if (p->line != line)
+ return FALSE;
+ if (p->c == c)
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** insert a bookmark at this line */
+
+void
+book_mark_insert (WEdit * edit, long line, int c)
+{
+ edit_book_mark_t *p, *q;
+
+ p = book_mark_find (edit, line);
+#if 0
+ if (p->line == line)
+ {
+ /* already exists, so just change the color */
+ if (p->c != c)
+ {
+ p->c = c;
+ edit->force |= REDRAW_LINE;
+ }
+ return;
+ }
+#endif
+ /* create list entry */
+ q = g_new (edit_book_mark_t, 1);
+ q->line = line;
+ q->c = c;
+ q->next = p->next;
+ /* insert into list */
+ q->prev = p;
+ if (p->next != NULL)
+ p->next->prev = q;
+ p->next = q;
+
+ edit->force |= REDRAW_LINE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Remove a bookmark if there is one at this line matching this color - c of -1 clear all
+ *
+ * @param edit editor object
+ * @param line line where book mark is
+ * @param c color of book mark or -1 to clear all book marks on this line
+ * @return FALSE if not found, TRUE otherwise
+ */
+
+gboolean
+book_mark_clear (WEdit * edit, long line, int c)
+{
+ edit_book_mark_t *p, *q;
+ gboolean r = FALSE;
+
+ if (edit->book_mark == NULL)
+ return r;
+
+ for (p = book_mark_find (edit, line); p != NULL; p = q)
+ {
+ q = p->prev;
+ if (p->line == line && (p->c == c || c == -1))
+ {
+ r = TRUE;
+ edit->book_mark = p->prev;
+ p->prev->next = p->next;
+ if (p->next != NULL)
+ p->next->prev = p->prev;
+ g_free (p);
+ edit->force |= REDRAW_LINE;
+ break;
+ }
+ }
+ /* if there is only our dummy book mark left, clear it for speed */
+ if (edit->book_mark->line == -1 && edit->book_mark->next == NULL)
+ MC_PTR_FREE (edit->book_mark);
+
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** clear all bookmarks matching this color, if c is -1 clears all */
+
+void
+book_mark_flush (WEdit * edit, int c)
+{
+ edit_book_mark_t *p, *q;
+
+ if (edit->book_mark == NULL)
+ return;
+
+ while (edit->book_mark->prev != NULL)
+ edit->book_mark = edit->book_mark->prev;
+
+ for (q = edit->book_mark->next; q != NULL; q = p)
+ {
+ p = q->next;
+ if (q->c == c || c == -1)
+ {
+ q->prev->next = q->next;
+ if (p != NULL)
+ p->prev = q->prev;
+ g_free (q);
+ }
+ }
+ if (edit->book_mark->next == NULL)
+ MC_PTR_FREE (edit->book_mark);
+
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** shift down bookmarks after this line */
+
+void
+book_mark_inc (WEdit * edit, long line)
+{
+ if (edit->book_mark != NULL)
+ {
+ edit_book_mark_t *p;
+
+ p = book_mark_find (edit, line);
+ for (p = p->next; p != NULL; p = p->next)
+ p->line++;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** shift up bookmarks after this line */
+
+void
+book_mark_dec (WEdit * edit, long line)
+{
+ if (edit->book_mark != NULL)
+ {
+ edit_book_mark_t *p;
+
+ p = book_mark_find (edit, line);
+ for (p = p->next; p != NULL; p = p->next)
+ p->line--;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** prepare line positions of bookmarks to be saved to file */
+
+void
+book_mark_serialize (WEdit * edit, int color)
+{
+ if (edit->serialized_bookmarks != NULL)
+ g_array_set_size (edit->serialized_bookmarks, 0);
+
+ if (edit->book_mark != NULL)
+ {
+ edit_book_mark_t *p;
+
+ if (edit->serialized_bookmarks == NULL)
+ edit->serialized_bookmarks = g_array_sized_new (FALSE, FALSE, sizeof (size_t),
+ MAX_SAVED_BOOKMARKS);
+
+ for (p = book_mark_find (edit, 0); p != NULL; p = p->next)
+ if (p->c == color && p->line >= 0)
+ g_array_append_val (edit->serialized_bookmarks, p->line);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** restore bookmarks from saved line positions */
+
+void
+book_mark_restore (WEdit * edit, int color)
+{
+ if (edit->serialized_bookmarks != NULL)
+ {
+ size_t i;
+
+ for (i = 0; i < edit->serialized_bookmarks->len; i++)
+ book_mark_insert (edit, g_array_index (edit->serialized_bookmarks, size_t, i), color);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/edit-impl.h b/src/editor/edit-impl.h
new file mode 100644
index 0000000..3d00545
--- /dev/null
+++ b/src/editor/edit-impl.h
@@ -0,0 +1,278 @@
+/*
+ editor private API
+ */
+
+/** \file edit-impl.h
+ * \brief Header: editor low level data handling and cursor fundamentals
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#ifndef MC__EDIT_IMPL_H
+#define MC__EDIT_IMPL_H
+
+#include <stdio.h>
+
+#include "lib/search.h" /* mc_search_type_t */
+#include "lib/widget.h" /* cb_ret_t */
+#include "lib/vfs/vfs.h" /* vfs_path_t */
+
+#include "src/setup.h" /* option_tab_spacing */
+
+#include "edit.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define REDRAW_LINE (1 << 0)
+#define REDRAW_LINE_ABOVE (1 << 1)
+#define REDRAW_LINE_BELOW (1 << 2)
+#define REDRAW_AFTER_CURSOR (1 << 3)
+#define REDRAW_BEFORE_CURSOR (1 << 4)
+#define REDRAW_PAGE (1 << 5)
+#define REDRAW_IN_BOUNDS (1 << 6)
+#define REDRAW_CHAR_ONLY (1 << 7)
+#define REDRAW_COMPLETELY (1 << 8)
+
+#define EDIT_TEXT_HORIZONTAL_OFFSET 0
+#define EDIT_TEXT_VERTICAL_OFFSET 0
+
+#define EDIT_RIGHT_EXTREME 0
+#define EDIT_LEFT_EXTREME 0
+#define EDIT_TOP_EXTREME 0
+#define EDIT_BOTTOM_EXTREME 0
+
+/* Initial size of the undo stack, in bytes */
+#define START_STACK_SIZE 32
+
+/* Some codes that may be pushed onto or returned from the undo stack */
+#define CURS_LEFT 601
+#define CURS_RIGHT 602
+#define DELCHAR 603
+#define BACKSPACE 604
+#define STACK_BOTTOM 605
+#define CURS_LEFT_LOTS 606
+#define CURS_RIGHT_LOTS 607
+#define COLUMN_ON 608
+#define COLUMN_OFF 609
+#define DELCHAR_BR 610
+#define BACKSPACE_BR 611
+#define MARK_1 1000
+#define MARK_2 500000000
+#define MARK_CURS 1000000000
+#define KEY_PRESS 1500000000
+
+/* Tabs spaces: (sofar only HALF_TAB_SIZE is used: */
+#define TAB_SIZE option_tab_spacing
+#define HALF_TAB_SIZE ((int) option_tab_spacing / 2)
+
+/* max count stack files */
+#define MAX_HISTORY_MOVETO 50
+#define LINE_STATE_WIDTH 8
+
+#define LB_NAMES (LB_MAC + 1)
+
+#define get_sys_error(s) (s)
+
+#define edit_error_dialog(h,s) query_dialog (h, s, D_ERROR, 1, _("&Dismiss"))
+#define edit_query_dialog(h,s) query_dialog (h, s, D_NORMAL, 1, _("&Dismiss"))
+#define edit_query_dialog2(h,t,a,b) query_dialog (h, t, D_NORMAL, 2, a, b)
+#define edit_query_dialog3(h,t,a,b,c) query_dialog (h, t, D_NORMAL, 3, a, b, c)
+
+/*** enums ***************************************************************************************/
+
+/* line breaks */
+typedef enum
+{
+ LB_ASIS = 0,
+ LB_UNIX,
+ LB_WIN,
+ LB_MAC
+} LineBreaks;
+
+typedef enum
+{
+ EDIT_QUICK_SAVE = 0,
+ EDIT_SAFE_SAVE,
+ EDIT_DO_BACKUP
+} edit_save_mode_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* search/replace options */
+typedef struct edit_search_options_t
+{
+ mc_search_type_t type;
+ gboolean case_sens;
+ gboolean backwards;
+ gboolean only_in_selection;
+ gboolean whole_words;
+ gboolean all_codepages;
+} edit_search_options_t;
+
+typedef struct edit_stack_type
+{
+ long line;
+ vfs_path_t *filename_vpath;
+} edit_stack_type;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern const char VERTICAL_MAGIC[5];
+/* if enable_show_tabs_tws == TRUE then use visible_tab visible_tws */
+extern gboolean enable_show_tabs_tws;
+
+extern edit_search_options_t edit_search_options;
+
+extern unsigned int edit_stack_iterator;
+extern edit_stack_type edit_history_moveto[MAX_HISTORY_MOVETO];
+
+extern int max_undo;
+extern gboolean auto_syntax;
+
+extern gboolean search_create_bookmark;
+
+extern char *edit_window_state_char;
+extern char *edit_window_close_char;
+
+/*** declarations of public functions ************************************************************/
+
+gboolean edit_add_window (WDialog * h, const WRect * r, const vfs_path_t * f, long fline);
+WEdit *edit_find_editor (const WDialog * h);
+gboolean edit_widget_is_editor (const Widget * w);
+gboolean edit_drop_hotkey_menu (WDialog * h, int key);
+void edit_menu_cmd (WDialog * h);
+void user_menu (WEdit * edit, const char *menu_file, int selected_entry);
+void edit_init_menu (WMenuBar * menubar);
+void edit_save_mode_cmd (void);
+off_t edit_move_forward3 (const WEdit * edit, off_t current, long cols, off_t upto);
+void edit_scroll_screen_over_cursor (WEdit * edit);
+void edit_render_keypress (WEdit * edit);
+void edit_scroll_upward (WEdit * edit, long i);
+void edit_scroll_downward (WEdit * edit, long i);
+void edit_scroll_right (WEdit * edit, long i);
+void edit_scroll_left (WEdit * edit, long i);
+void edit_move_up (WEdit * edit, long i, gboolean do_scroll);
+void edit_move_down (WEdit * edit, long i, gboolean do_scroll);
+void edit_move_to_prev_col (WEdit * edit, off_t p);
+long edit_get_col (const WEdit * edit);
+void edit_update_curs_row (WEdit * edit);
+void edit_update_curs_col (WEdit * edit);
+void edit_find_bracket (WEdit * edit);
+gboolean edit_reload_line (WEdit * edit, const vfs_path_t * filename_vpath, long line);
+void edit_set_codeset (WEdit * edit);
+
+void edit_block_copy_cmd (WEdit * edit);
+void edit_block_move_cmd (WEdit * edit);
+int edit_block_delete_cmd (WEdit * edit);
+void edit_delete_line (WEdit * edit);
+
+int edit_delete (WEdit * edit, gboolean byte_delete);
+int edit_backspace (WEdit * edit, gboolean byte_delete);
+void edit_insert (WEdit * edit, int c);
+void edit_insert_over (WEdit * edit);
+void edit_cursor_move (WEdit * edit, off_t increment);
+void edit_push_undo_action (WEdit * edit, long c);
+void edit_push_redo_action (WEdit * edit, long c);
+void edit_push_key_press (WEdit * edit);
+void edit_insert_ahead (WEdit * edit, int c);
+off_t edit_write_stream (WEdit * edit, FILE * f);
+char *edit_get_write_filter (const vfs_path_t * write_name_vpath,
+ const vfs_path_t * filename_vpath);
+gboolean edit_save_confirm_cmd (WEdit * edit);
+gboolean edit_save_as_cmd (WEdit * edit);
+WEdit *edit_init (WEdit * edit, const WRect * r, const vfs_path_t * filename_vpath, long line);
+gboolean edit_clean (WEdit * edit);
+gboolean edit_ok_to_exit (WEdit * edit);
+gboolean edit_load_cmd (WDialog * h);
+gboolean edit_load_file_from_filename (WDialog * h, const vfs_path_t * vpath, long line);
+gboolean edit_load_file_from_history (WDialog * h);
+gboolean edit_load_syntax_file (WDialog * h);
+gboolean edit_load_menu_file (WDialog * h);
+gboolean edit_close_cmd (WEdit * edit);
+void edit_mark_cmd (WEdit * edit, gboolean unmark);
+void edit_mark_current_word_cmd (WEdit * edit);
+void edit_mark_current_line_cmd (WEdit * edit);
+void edit_set_markers (WEdit * edit, off_t m1, off_t m2, long c1, long c2);
+void edit_push_markers (WEdit * edit);
+
+gboolean edit_save_block (WEdit * edit, const char *filename, off_t start, off_t finish);
+gboolean edit_save_block_cmd (WEdit * edit);
+gboolean edit_insert_file_cmd (WEdit * edit);
+
+off_t edit_insert_file (WEdit * edit, const vfs_path_t * filename_vpath);
+gboolean edit_load_back_cmd (WEdit * edit);
+gboolean edit_load_forward_cmd (WEdit * edit);
+void edit_block_process_cmd (WEdit * edit, int macro_number);
+void edit_refresh_cmd (void);
+void edit_syntax_onoff_cmd (WDialog * h);
+void edit_show_tabs_tws_cmd (WDialog * h);
+void edit_show_margin_cmd (WDialog * h);
+void edit_show_numbers_cmd (WDialog * h);
+void edit_date_cmd (WEdit * edit);
+void edit_goto_cmd (WEdit * edit);
+gboolean eval_marks (WEdit * edit, off_t * start_mark, off_t * end_mark);
+void edit_status (WEdit * edit, gboolean active);
+void edit_execute_key_command (WEdit * edit, long command, int char_for_insertion);
+void edit_update_screen (WEdit * edit);
+void edit_save_size (WEdit * edit);
+gboolean edit_handle_move_resize (WEdit * edit, long command);
+void edit_toggle_fullscreen (WEdit * edit);
+void edit_move_to_line (WEdit * e, long line);
+void edit_move_display (WEdit * e, long line);
+void edit_word_wrap (WEdit * edit);
+int edit_sort_cmd (WEdit * edit);
+int edit_ext_cmd (WEdit * edit);
+
+gboolean edit_copy_to_X_buf_cmd (WEdit * edit);
+gboolean edit_cut_to_X_buf_cmd (WEdit * edit);
+gboolean edit_paste_from_X_buf_cmd (WEdit * edit);
+
+void edit_select_codepage_cmd (WEdit * edit);
+void edit_insert_literal_cmd (WEdit * edit);
+
+void edit_paste_from_history (WEdit * edit);
+
+void edit_set_filename (WEdit * edit, const vfs_path_t * name_vpath);
+
+void edit_load_syntax (WEdit * edit, GPtrArray * pnames, const char *type);
+void edit_free_syntax_rules (WEdit * edit);
+int edit_get_syntax_color (WEdit * edit, off_t byte_index);
+void edit_syntax_dialog (WEdit * edit);
+
+void book_mark_insert (WEdit * edit, long line, int c);
+gboolean book_mark_query_color (WEdit * edit, long line, int c);
+struct edit_book_mark_t *book_mark_find (WEdit * edit, long line);
+gboolean book_mark_clear (WEdit * edit, long line, int c);
+void book_mark_flush (WEdit * edit, int c);
+void book_mark_inc (WEdit * edit, long line);
+void book_mark_dec (WEdit * edit, long line);
+void book_mark_serialize (WEdit * edit, int color);
+void book_mark_restore (WEdit * edit, int color);
+
+gboolean edit_line_is_blank (WEdit * edit, long line);
+gboolean is_break_char (char c);
+void edit_options_dialog (WDialog * h);
+void edit_mail_dialog (WEdit * edit);
+void format_paragraph (WEdit * edit, gboolean force);
+
+/* either command or char_for_insertion must be passed as -1 */
+void edit_execute_cmd (WEdit * edit, long command, int char_for_insertion);
+
+int editcmd_dialog_raw_key_query (const char *heading, const char *query, gboolean cancel);
+
+/*** inline functions ****************************************************************************/
+
+/**
+ * Load a new file into the editor. If it fails, preserve the old file.
+ * To do it, allocate a new widget, initialize it and, if the new file
+ * was loaded, copy the data to the old widget.
+ *
+ * @return TRUE on success, FALSE on failure.
+ */
+static inline gboolean
+edit_reload (WEdit * edit, const vfs_path_t * filename_vpath)
+{
+ return edit_reload_line (edit, filename_vpath, 0);
+}
+
+#endif /* MC__EDIT_IMPL_H */
diff --git a/src/editor/edit.c b/src/editor/edit.c
new file mode 100644
index 0000000..dc3b322
--- /dev/null
+++ b/src/editor/edit.c
@@ -0,0 +1,4067 @@
+/*
+ Editor low level data handling and cursor fundamentals.
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer 1996, 1997
+ Ilia Maslakov <il.smind@gmail.com> 2009, 2010, 2011
+ Andrew Borodin <aborodin@vmail.ru> 2012-2022
+
+ 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
+ * \brief Source: editor low level data handling and cursor fundamentals
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <stdint.h> /* UINTMAX_MAX */
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/color.h"
+#include "lib/tty/tty.h" /* attrset() */
+#include "lib/tty/key.h" /* is_idle() */
+#include "lib/skin.h" /* EDITOR_NORMAL_COLOR */
+#include "lib/fileloc.h" /* EDIT_HOME_BLOCK_FILE */
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h" /* utf string functions */
+#include "lib/util.h" /* load_file_position(), save_file_position() */
+#include "lib/timefmt.h" /* time formatting */
+#include "lib/lock.h"
+#include "lib/widget.h"
+
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h" /* get_codepage_id */
+#endif
+
+#include "src/usermenu.h" /* user_menu_cmd() */
+
+#include "src/keymap.h"
+
+#include "edit-impl.h"
+#include "editwidget.h"
+#include "editsearch.h"
+#include "editcomplete.h" /* edit_complete_word_cmd() */
+#include "editmacros.h"
+#include "etags.h" /* edit_get_match_keyword_cmd() */
+#ifdef HAVE_ASPELL
+#include "spell.h"
+#endif
+
+/*** global variables ****************************************************************************/
+
+edit_options_t edit_options = {
+ .word_wrap_line_length = DEFAULT_WRAP_LINE_LENGTH,
+ .typewriter_wrap = FALSE,
+ .auto_para_formatting = FALSE,
+ .fill_tabs_with_spaces = FALSE,
+ .return_does_auto_indent = TRUE,
+ .backspace_through_tabs = FALSE,
+ .fake_half_tabs = TRUE,
+ .persistent_selections = TRUE,
+ .drop_selection_on_copy = TRUE,
+ .cursor_beyond_eol = FALSE,
+ .cursor_after_inserted_block = FALSE,
+ .state_full_filename = FALSE,
+ .line_state = FALSE,
+ .line_state_width = 0,
+ .save_mode = EDIT_QUICK_SAVE,
+ .confirm_save = TRUE,
+ .save_position = TRUE,
+ .syntax_highlighting = TRUE,
+ .group_undo = FALSE,
+ .backup_ext = NULL,
+ .filesize_threshold = NULL,
+ .stop_format_chars = NULL,
+ .visible_tabs = TRUE,
+ .visible_tws = TRUE,
+ .show_right_margin = FALSE,
+ .simple_statusbar = FALSE,
+ .check_nl_at_eof = FALSE
+};
+
+int max_undo = 32768;
+
+gboolean enable_show_tabs_tws = TRUE;
+
+unsigned int edit_stack_iterator = 0;
+edit_stack_type edit_history_moveto[MAX_HISTORY_MOVETO];
+/* magic sequence for say than block is vertical */
+const char VERTICAL_MAGIC[] = { '\1', '\1', '\1', '\1', '\n' };
+
+/*** file scope macro definitions ****************************************************************/
+
+#define TEMP_BUF_LEN 1024
+
+#define space_width 1
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* detecting an error on save is easy: just check if every byte has been written. */
+/* detecting an error on read, is not so easy 'cos there is not way to tell
+ whether you read everything or not. */
+/* FIXME: add proper 'triple_pipe_open' to read, write and check errors. */
+static const struct edit_filters
+{
+ const char *read, *write, *extension;
+} all_filters[] =
+{
+ /* *INDENT-OFF* */
+ { "xz -cd %s 2>&1", "xz > %s", ".xz"},
+ { "zstd -cd %s 2>&1", "zstd > %s", ".zst"},
+ { "lz4 -cd %s 2>&1", "lz4 > %s", ".lz4" },
+ { "lzip -cd %s 2>&1", "lzip > %s", ".lz"},
+ { "lzma -cd %s 2>&1", "lzma > %s", ".lzma" },
+ { "bzip2 -cd %s 2>&1", "bzip2 > %s", ".bz2" },
+ { "gzip -cd %s 2>&1", "gzip > %s", ".gz" },
+ { "gzip -cd %s 2>&1", "gzip > %s", ".Z" }
+ /* *INDENT-ON* */
+};
+
+static const off_t filesize_default_threshold = 64 * 1024 * 1024; /* 64 MB */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+edit_load_status_update_cb (status_msg_t * sm)
+{
+ simple_status_msg_t *ssm = SIMPLE_STATUS_MSG (sm);
+ edit_buffer_read_file_status_msg_t *rsm = (edit_buffer_read_file_status_msg_t *) sm;
+ Widget *wd = WIDGET (sm->dlg);
+
+ if (verbose)
+ label_set_textv (ssm->label, _("Loading: %3d%%"),
+ edit_buffer_calc_percent (rsm->buf, rsm->loaded));
+ else
+ label_set_text (ssm->label, _("Loading..."));
+
+ if (rsm->first)
+ {
+ Widget *lw = WIDGET (ssm->label);
+ WRect r;
+
+ r = wd->rect;
+ r.cols = MAX (r.cols, lw->rect.cols + 6);
+ widget_set_size_rect (wd, &r);
+ r = lw->rect;
+ r.x = wd->rect.x + (wd->rect.cols - r.cols) / 2;
+ widget_set_size_rect (lw, &r);
+ rsm->first = FALSE;
+ }
+
+ return status_msg_common_update (sm);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load file OR text into buffers. Set cursor to the beginning of file.
+ *
+ * @return FALSE on error.
+ */
+
+static gboolean
+edit_load_file_fast (edit_buffer_t * buf, const vfs_path_t * filename_vpath)
+{
+ int file;
+ gboolean ret;
+ edit_buffer_read_file_status_msg_t rsm;
+ gboolean aborted;
+
+ file = mc_open (filename_vpath, O_RDONLY | O_BINARY);
+ if (file < 0)
+ {
+ gchar *errmsg;
+
+ errmsg =
+ g_strdup_printf (_("Cannot open %s for reading"), vfs_path_as_str (filename_vpath));
+ edit_error_dialog (_("Error"), errmsg);
+ g_free (errmsg);
+ return FALSE;
+ }
+
+ rsm.first = TRUE;
+ rsm.buf = buf;
+ rsm.loaded = 0;
+
+ status_msg_init (STATUS_MSG (&rsm), _("Load file"), 1.0, simple_status_msg_init_cb,
+ edit_load_status_update_cb, NULL);
+
+ ret = (edit_buffer_read_file (buf, file, buf->size, &rsm, &aborted) == buf->size);
+
+ status_msg_deinit (STATUS_MSG (&rsm));
+
+ if (!ret && !aborted)
+ {
+ gchar *errmsg;
+
+ errmsg = g_strdup_printf (_("Error reading %s"), vfs_path_as_str (filename_vpath));
+ edit_error_dialog (_("Error"), errmsg);
+ g_free (errmsg);
+ }
+
+ mc_close (file);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Return index of the filter or -1 is there is no appropriate filter */
+
+static int
+edit_find_filter (const vfs_path_t * filename_vpath)
+{
+ if (filename_vpath != NULL)
+ {
+ const char *s;
+ size_t i;
+
+ s = vfs_path_as_str (filename_vpath);
+
+ for (i = 0; i < G_N_ELEMENTS (all_filters); i++)
+ if (g_str_has_suffix (s, all_filters[i].extension))
+ return i;
+ }
+
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+edit_get_filter (const vfs_path_t * filename_vpath)
+{
+ int i;
+ char *p, *quoted_name;
+
+ i = edit_find_filter (filename_vpath);
+ if (i < 0)
+ return NULL;
+
+ quoted_name = name_quote (vfs_path_as_str (filename_vpath), FALSE);
+ p = g_strdup_printf (all_filters[i].read, quoted_name);
+ g_free (quoted_name);
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+edit_insert_stream (WEdit * edit, FILE * f)
+{
+ int c;
+ off_t i;
+
+ for (i = 0; (c = fgetc (f)) >= 0; i++)
+ edit_insert (edit, c);
+
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open file and create it if necessary.
+ *
+ * @param edit editor object
+ * @param filename_vpath file name
+ * @param st buffer for store stat info
+ * @return TRUE for success, FALSE for error.
+ */
+
+static gboolean
+check_file_access (WEdit * edit, const vfs_path_t * filename_vpath, struct stat *st)
+{
+ static uintmax_t threshold = UINTMAX_MAX;
+ int file;
+ gchar *errmsg = NULL;
+ gboolean ret = TRUE;
+
+ /* Try opening an existing file */
+ file = mc_open (filename_vpath, O_NONBLOCK | O_RDONLY | O_BINARY, 0666);
+ if (file < 0)
+ {
+ /*
+ * Try creating the file. O_EXCL prevents following broken links
+ * and opening existing files.
+ */
+ file = mc_open (filename_vpath, O_NONBLOCK | O_RDONLY | O_BINARY | O_CREAT | O_EXCL, 0666);
+ if (file < 0)
+ {
+ errmsg =
+ g_strdup_printf (_("Cannot open %s for reading"), vfs_path_as_str (filename_vpath));
+ goto cleanup;
+ }
+
+ /* New file, delete it if it's not modified or saved */
+ edit->delete_file = 1;
+ }
+
+ /* Check what we have opened */
+ if (mc_fstat (file, st) < 0)
+ {
+ errmsg =
+ g_strdup_printf (_("Cannot get size/permissions for %s"),
+ vfs_path_as_str (filename_vpath));
+ goto cleanup;
+ }
+
+ /* We want to open regular files only */
+ if (!S_ISREG (st->st_mode))
+ {
+ errmsg =
+ g_strdup_printf (_("\"%s\" is not a regular file"), vfs_path_as_str (filename_vpath));
+ goto cleanup;
+ }
+
+ /* get file size threshold for alarm */
+ if (threshold == UINTMAX_MAX)
+ {
+ gboolean err = FALSE;
+
+ threshold = parse_integer (edit_options.filesize_threshold, &err);
+ if (err)
+ threshold = filesize_default_threshold;
+ }
+
+ /*
+ * Don't delete non-empty files.
+ * O_EXCL should prevent it, but let's be on the safe side.
+ */
+ if (st->st_size > 0)
+ edit->delete_file = 0;
+
+ if ((uintmax_t) st->st_size > threshold)
+ {
+ int act;
+
+ errmsg = g_strdup_printf (_("File \"%s\" is too large.\nOpen it anyway?"),
+ vfs_path_as_str (filename_vpath));
+ act = edit_query_dialog2 (_("Warning"), errmsg, _("&Yes"), _("&No"));
+ MC_PTR_FREE (errmsg);
+
+ if (act != 0)
+ ret = FALSE;
+ }
+
+ cleanup:
+ (void) mc_close (file);
+
+ if (errmsg != NULL)
+ {
+ edit_error_dialog (_("Error"), errmsg);
+ g_free (errmsg);
+ ret = FALSE;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Open the file and load it into the buffers, either directly or using
+ * a filter. Return TRUE on success, FALSE on error.
+ *
+ * Fast loading (edit_load_file_fast) is used when the file size is
+ * known. In this case the data is read into the buffers by blocks.
+ * If the file size is not known, the data is loaded byte by byte in
+ * edit_insert_file.
+ *
+ * @param edit editor object
+ * @return TRUE if file was successfully opened and loaded to buffers, FALSE otherwise
+ */
+static gboolean
+edit_load_file (WEdit * edit)
+{
+ gboolean fast_load = TRUE;
+
+ /* Cannot do fast load if a filter is used */
+ if (edit_find_filter (edit->filename_vpath) >= 0)
+ fast_load = FALSE;
+
+ /*
+ * FIXME: line end translation should disable fast loading as well
+ * Consider doing fseek() to the end and ftell() for the real size.
+ */
+ if (edit->filename_vpath != NULL)
+ {
+ /*
+ * VFS may report file size incorrectly, and slow load is not a big
+ * deal considering overhead in VFS.
+ */
+ if (!vfs_file_is_local (edit->filename_vpath))
+ fast_load = FALSE;
+
+ /* If we are dealing with a real file, check that it exists */
+ if (!check_file_access (edit, edit->filename_vpath, &edit->stat1))
+ {
+ edit_clean (edit);
+ return FALSE;
+ }
+ }
+ else
+ {
+ /* nothing to load */
+ fast_load = FALSE;
+ }
+
+ if (fast_load)
+ {
+ edit_buffer_init (&edit->buffer, edit->stat1.st_size);
+
+ if (!edit_load_file_fast (&edit->buffer, edit->filename_vpath))
+ {
+ edit_clean (edit);
+ return FALSE;
+ }
+ }
+ else
+ {
+ edit_buffer_init (&edit->buffer, 0);
+
+ if (edit->filename_vpath != NULL
+ && *(vfs_path_get_by_index (edit->filename_vpath, 0)->path) != '\0')
+ {
+ edit->undo_stack_disable = 1;
+ if (edit_insert_file (edit, edit->filename_vpath) < 0)
+ {
+ edit_clean (edit);
+ return FALSE;
+ }
+ edit->undo_stack_disable = 0;
+ }
+ }
+ edit->lb = LB_ASIS;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Restore saved cursor position and/or bookmarks in the file
+ *
+ * @param edit editor object
+ * @param load_position If TRUE, load bookmarks and cursor position and apply them.
+ * If FALSE, load bookmarks only.
+ */
+
+static void
+edit_load_position (WEdit * edit, gboolean load_position)
+{
+ long line, column;
+ off_t offset;
+
+ if (edit->filename_vpath == NULL
+ || *(vfs_path_get_by_index (edit->filename_vpath, 0)->path) == '\0')
+ return;
+
+ load_file_position (edit->filename_vpath, &line, &column, &offset, &edit->serialized_bookmarks);
+ /* apply bookmarks in any case */
+ book_mark_restore (edit, BOOK_MARK_COLOR);
+
+ if (!load_position)
+ return;
+
+ if (line > 0)
+ {
+ edit_move_to_line (edit, line - 1);
+ edit->prev_col = column;
+ }
+ else if (offset > 0)
+ {
+ edit_cursor_move (edit, offset);
+ line = edit->buffer.curs_line;
+ edit->search_start = edit->buffer.curs1;
+ }
+
+ edit_move_to_prev_col (edit, edit_buffer_get_current_bol (&edit->buffer));
+ edit_move_display (edit, line - (WIDGET (edit)->rect.lines / 2));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Save cursor position in the file */
+
+static void
+edit_save_position (WEdit * edit)
+{
+ if (edit->filename_vpath == NULL
+ || *(vfs_path_get_by_index (edit->filename_vpath, 0)->path) == '\0')
+ return;
+
+ book_mark_serialize (edit, BOOK_MARK_COLOR);
+ save_file_position (edit->filename_vpath, edit->buffer.curs_line + 1, edit->curs_col,
+ edit->buffer.curs1, edit->serialized_bookmarks);
+ edit->serialized_bookmarks = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Clean the WEdit stricture except the widget part */
+
+static void
+edit_purge_widget (WEdit * edit)
+{
+ size_t len = sizeof (WEdit) - sizeof (Widget);
+ char *start = (char *) edit + sizeof (Widget);
+ memset (start, 0, len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ TODO: if the user undos until the stack bottom, and the stack has not wrapped,
+ then the file should be as it was when he loaded up. Then set edit->modified to 0.
+ */
+
+static long
+edit_pop_undo_action (WEdit * edit)
+{
+ long c;
+ unsigned long sp = edit->undo_stack_pointer;
+
+ if (sp == edit->undo_stack_bottom)
+ return STACK_BOTTOM;
+
+ sp = (sp - 1) & edit->undo_stack_size_mask;
+ c = edit->undo_stack[sp];
+ if (c >= 0)
+ {
+ /* edit->undo_stack[sp] = '@'; */
+ edit->undo_stack_pointer = (edit->undo_stack_pointer - 1) & edit->undo_stack_size_mask;
+ return c;
+ }
+
+ if (sp == edit->undo_stack_bottom)
+ return STACK_BOTTOM;
+
+ c = edit->undo_stack[(sp - 1) & edit->undo_stack_size_mask];
+ if (edit->undo_stack[sp] == -2)
+ {
+ /* edit->undo_stack[sp] = '@'; */
+ edit->undo_stack_pointer = sp;
+ }
+ else
+ edit->undo_stack[sp]++;
+
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static long
+edit_pop_redo_action (WEdit * edit)
+{
+ long c;
+ unsigned long sp = edit->redo_stack_pointer;
+
+ if (sp == edit->redo_stack_bottom)
+ return STACK_BOTTOM;
+
+ sp = (sp - 1) & edit->redo_stack_size_mask;
+ c = edit->redo_stack[sp];
+ if (c >= 0)
+ {
+ edit->redo_stack_pointer = (edit->redo_stack_pointer - 1) & edit->redo_stack_size_mask;
+ return c;
+ }
+
+ if (sp == edit->redo_stack_bottom)
+ return STACK_BOTTOM;
+
+ c = edit->redo_stack[(sp - 1) & edit->redo_stack_size_mask];
+ if (edit->redo_stack[sp] == -2)
+ edit->redo_stack_pointer = sp;
+ else
+ edit->redo_stack[sp]++;
+
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static long
+get_prev_undo_action (WEdit * edit)
+{
+ long c;
+ unsigned long sp = edit->undo_stack_pointer;
+
+ if (sp == edit->undo_stack_bottom)
+ return STACK_BOTTOM;
+
+ sp = (sp - 1) & edit->undo_stack_size_mask;
+ c = edit->undo_stack[sp];
+ if (c >= 0)
+ return c;
+
+ if (sp == edit->undo_stack_bottom)
+ return STACK_BOTTOM;
+
+ c = edit->undo_stack[(sp - 1) & edit->undo_stack_size_mask];
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** is called whenever a modification is made by one of the four routines below */
+
+static void
+edit_modification (WEdit * edit)
+{
+ edit->caches_valid = FALSE;
+
+ /* raise lock when file modified */
+ if (!edit->modified && !edit->delete_file)
+ edit->locked = lock_file (edit->filename_vpath);
+ edit->modified = 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* high level cursor movement commands */
+/* --------------------------------------------------------------------------------------------- */
+/** check whether cursor is in indent part of line
+ *
+ * @param edit editor object
+ *
+ * @return TRUE if cursor is in indent, FALSE otherwise
+ */
+
+static gboolean
+is_in_indent (const edit_buffer_t * buf)
+{
+ off_t p;
+
+ for (p = edit_buffer_get_current_bol (buf); p < buf->curs1; p++)
+ if (strchr (" \t", edit_buffer_get_byte (buf, p)) == NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** check whether line in editor is blank or not
+ *
+ * @param edit editor object
+ * @param offset position in file
+ *
+ * @return TRUE if line in blank, FALSE otherwise
+ */
+
+static gboolean
+is_blank (const edit_buffer_t * buf, off_t offset)
+{
+ off_t s, f;
+
+ s = edit_buffer_get_bol (buf, offset);
+ f = edit_buffer_get_eol (buf, offset) - 1;
+ while (s <= f)
+ {
+ int c;
+
+ c = edit_buffer_get_byte (buf, s++);
+ if (!isspace (c))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns the offset of line i */
+
+static off_t
+edit_find_line (WEdit * edit, long line)
+{
+ long i, j = 0;
+ long m = 2000000000; /* what is the magic number? */
+
+ if (!edit->caches_valid)
+ {
+ memset (edit->line_numbers, 0, sizeof (edit->line_numbers));
+ memset (edit->line_offsets, 0, sizeof (edit->line_offsets));
+ /* three offsets that we *know* are line 0 at 0 and these two: */
+ edit->line_numbers[1] = edit->buffer.curs_line;
+ edit->line_offsets[1] = edit_buffer_get_current_bol (&edit->buffer);
+ edit->line_numbers[2] = edit->buffer.lines;
+ edit->line_offsets[2] = edit_buffer_get_bol (&edit->buffer, edit->buffer.size);
+ edit->caches_valid = TRUE;
+ }
+ if (line >= edit->buffer.lines)
+ return edit->line_offsets[2];
+ if (line <= 0)
+ return 0;
+ /* find the closest known point */
+ for (i = 0; i < N_LINE_CACHES; i++)
+ {
+ long n;
+
+ n = labs (edit->line_numbers[i] - line);
+ if (n < m)
+ {
+ m = n;
+ j = i;
+ }
+ }
+ if (m == 0)
+ return edit->line_offsets[j]; /* know the offset exactly */
+ if (m == 1 && j >= 3)
+ i = j; /* one line different - caller might be looping, so stay in this cache */
+ else
+ i = 3 + (rand () % (N_LINE_CACHES - 3));
+ if (line > edit->line_numbers[j])
+ edit->line_offsets[i] =
+ edit_buffer_get_forward_offset (&edit->buffer, edit->line_offsets[j],
+ line - edit->line_numbers[j], 0);
+ else
+ edit->line_offsets[i] =
+ edit_buffer_get_backward_offset (&edit->buffer, edit->line_offsets[j],
+ edit->line_numbers[j] - line);
+ edit->line_numbers[i] = line;
+ return edit->line_offsets[i];
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** moves up until a blank line is reached, or until just
+ before a non-blank line is reached */
+
+static void
+edit_move_up_paragraph (WEdit * edit, gboolean do_scroll)
+{
+ long i = 0;
+
+ if (edit->buffer.curs_line > 1)
+ {
+ if (!edit_line_is_blank (edit, edit->buffer.curs_line))
+ {
+ for (i = edit->buffer.curs_line - 1; i != 0; i--)
+ if (edit_line_is_blank (edit, i))
+ break;
+ }
+ else if (edit_line_is_blank (edit, edit->buffer.curs_line - 1))
+ {
+ for (i = edit->buffer.curs_line - 1; i != 0; i--)
+ if (!edit_line_is_blank (edit, i))
+ {
+ i++;
+ break;
+ }
+ }
+ else
+ {
+ for (i = edit->buffer.curs_line - 1; i != 0; i--)
+ if (edit_line_is_blank (edit, i))
+ break;
+ }
+ }
+
+ edit_move_up (edit, edit->buffer.curs_line - i, do_scroll);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** moves down until a blank line is reached, or until just
+ before a non-blank line is reached */
+
+static void
+edit_move_down_paragraph (WEdit * edit, gboolean do_scroll)
+{
+ long i;
+
+ if (edit->buffer.curs_line >= edit->buffer.lines - 1)
+ i = edit->buffer.lines;
+ else if (!edit_line_is_blank (edit, edit->buffer.curs_line))
+ {
+ for (i = edit->buffer.curs_line + 1; i != 0; i++)
+ if (edit_line_is_blank (edit, i) || i >= edit->buffer.lines)
+ break;
+ }
+ else if (edit_line_is_blank (edit, edit->buffer.curs_line + 1))
+ {
+ for (i = edit->buffer.curs_line + 1; i != 0; i++)
+ if (!edit_line_is_blank (edit, i) || i > edit->buffer.lines)
+ {
+ i--;
+ break;
+ }
+ }
+ else
+ {
+ for (i = edit->buffer.curs_line + 1; i != 0; i++)
+ if (edit_line_is_blank (edit, i) || i >= edit->buffer.lines)
+ break;
+ }
+ edit_move_down (edit, i - edit->buffer.curs_line, do_scroll);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_begin_page (WEdit * edit)
+{
+ edit_update_curs_row (edit);
+ edit_move_up (edit, edit->curs_row, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_end_page (WEdit * edit)
+{
+ edit_update_curs_row (edit);
+ edit_move_down (edit, WIDGET (edit)->rect.lines - edit->curs_row - 1, FALSE);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/** goto beginning of text */
+
+static void
+edit_move_to_top (WEdit * edit)
+{
+ if (edit->buffer.curs_line != 0)
+ {
+ edit_cursor_move (edit, -edit->buffer.curs1);
+ edit_move_to_prev_col (edit, 0);
+ edit->force |= REDRAW_PAGE;
+ edit->search_start = 0;
+ edit_update_curs_row (edit);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** goto end of text */
+
+static void
+edit_move_to_bottom (WEdit * edit)
+{
+ if (edit->buffer.curs_line < edit->buffer.lines)
+ {
+ edit_move_down (edit, edit->buffer.lines - edit->curs_row, FALSE);
+ edit->start_display = edit->buffer.size;
+ edit->start_line = edit->buffer.lines;
+ edit_scroll_upward (edit, WIDGET (edit)->rect.lines - 1);
+ edit->force |= REDRAW_PAGE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** goto beginning of line */
+
+static void
+edit_cursor_to_bol (WEdit * edit)
+{
+ edit_cursor_move (edit, edit_buffer_get_current_bol (&edit->buffer) - edit->buffer.curs1);
+ edit->search_start = edit->buffer.curs1;
+ edit->prev_col = edit_get_col (edit);
+ edit->over_col = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** goto end of line */
+
+static void
+edit_cursor_to_eol (WEdit * edit)
+{
+ edit_cursor_move (edit, edit_buffer_get_current_eol (&edit->buffer) - edit->buffer.curs1);
+ edit->search_start = edit->buffer.curs1;
+ edit->prev_col = edit_get_col (edit);
+ edit->over_col = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static unsigned long
+my_type_of (int c)
+{
+ unsigned long x, r = 0;
+ const char *p, *q;
+ const char chars_move_whole_word[] =
+ "!=&|<>^~ !:;, !'!`!.?!\"!( !) !{ !} !Aa0 !+-*/= |<> ![ !] !\\#! ";
+
+ if (c == 0)
+ return 0;
+ if (c == '!')
+ return 2;
+
+ if (g_ascii_isupper ((gchar) c))
+ c = 'A';
+ else if (g_ascii_islower ((gchar) c))
+ c = 'a';
+ else if (g_ascii_isalpha (c))
+ c = 'a';
+ else if (isdigit (c))
+ c = '0';
+ else if (isspace (c))
+ c = ' ';
+ q = strchr (chars_move_whole_word, c);
+ if (!q)
+ return 0xFFFFFFFFUL;
+ do
+ {
+ for (x = 1, p = chars_move_whole_word; p < q; p++)
+ if (*p == '!')
+ x <<= 1;
+ r |= x;
+ }
+ while ((q = strchr (q + 1, c)));
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_left_word_move (WEdit * edit, int s)
+{
+ while (TRUE)
+ {
+ int c1, c2;
+
+ if (edit->column_highlight
+ && edit->mark1 != edit->mark2
+ && edit->over_col == 0
+ && edit->buffer.curs1 == edit_buffer_get_current_bol (&edit->buffer))
+ break;
+ edit_cursor_move (edit, -1);
+ if (edit->buffer.curs1 == 0)
+ break;
+ c1 = edit_buffer_get_previous_byte (&edit->buffer);
+ c2 = edit_buffer_get_current_byte (&edit->buffer);
+ if (c1 == '\n' || c2 == '\n')
+ break;
+ if ((my_type_of (c1) & my_type_of (c2)) == 0)
+ break;
+ if (isspace (c1) && !isspace (c2))
+ break;
+ if (s != 0 && !isspace (c1) && isspace (c2))
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_left_word_move_cmd (WEdit * edit)
+{
+ edit_left_word_move (edit, 0);
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_right_word_move (WEdit * edit, int s)
+{
+ while (TRUE)
+ {
+ int c1, c2;
+
+ if (edit->column_highlight
+ && edit->mark1 != edit->mark2
+ && edit->over_col == 0
+ && edit->buffer.curs1 == edit_buffer_get_current_eol (&edit->buffer))
+ break;
+ edit_cursor_move (edit, 1);
+ if (edit->buffer.curs1 >= edit->buffer.size)
+ break;
+ c1 = edit_buffer_get_previous_byte (&edit->buffer);
+ c2 = edit_buffer_get_current_byte (&edit->buffer);
+ if (c1 == '\n' || c2 == '\n')
+ break;
+ if ((my_type_of (c1) & my_type_of (c2)) == 0)
+ break;
+ if (isspace (c1) && !isspace (c2))
+ break;
+ if (s != 0 && !isspace (c1) && isspace (c2))
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_right_word_move_cmd (WEdit * edit)
+{
+ edit_right_word_move (edit, 0);
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_right_char_move_cmd (WEdit * edit)
+{
+ int char_length = 1;
+ int c;
+
+#ifdef HAVE_CHARSET
+ if (edit->utf8)
+ {
+ c = edit_buffer_get_utf (&edit->buffer, edit->buffer.curs1, &char_length);
+ if (char_length < 1)
+ char_length = 1;
+ }
+ else
+#endif
+ c = edit_buffer_get_current_byte (&edit->buffer);
+
+ if (edit_options.cursor_beyond_eol && c == '\n')
+ edit->over_col++;
+ else
+ edit_cursor_move (edit, char_length);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_left_char_move_cmd (WEdit * edit)
+{
+ int char_length = 1;
+
+ if (edit->column_highlight
+ && edit_options.cursor_beyond_eol
+ && edit->mark1 != edit->mark2
+ && edit->over_col == 0 && edit->buffer.curs1 == edit_buffer_get_current_bol (&edit->buffer))
+ return;
+#ifdef HAVE_CHARSET
+ if (edit->utf8)
+ {
+ edit_buffer_get_prev_utf (&edit->buffer, edit->buffer.curs1, &char_length);
+ if (char_length < 1)
+ char_length = 1;
+ }
+#endif
+
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit->over_col--;
+ else
+ edit_cursor_move (edit, -char_length);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Up or down cursor moving.
+ direction = TRUE - move up
+ = FALSE - move down
+*/
+
+static void
+edit_move_updown (WEdit * edit, long lines, gboolean do_scroll, gboolean direction)
+{
+ long p;
+ long l = direction ? edit->buffer.curs_line : edit->buffer.lines - edit->buffer.curs_line;
+
+ if (lines > l)
+ lines = l;
+
+ if (lines == 0)
+ return;
+
+ if (lines > 1)
+ edit->force |= REDRAW_PAGE;
+ if (do_scroll)
+ {
+ if (direction)
+ edit_scroll_upward (edit, lines);
+ else
+ edit_scroll_downward (edit, lines);
+ }
+ p = edit_buffer_get_current_bol (&edit->buffer);
+ p = direction ? edit_buffer_get_backward_offset (&edit->buffer, p, lines) :
+ edit_buffer_get_forward_offset (&edit->buffer, p, lines, 0);
+ edit_cursor_move (edit, p - edit->buffer.curs1);
+ edit_move_to_prev_col (edit, p);
+
+#ifdef HAVE_CHARSET
+ /* search start of current multibyte char (like CJK) */
+ if (edit->buffer.curs1 > 0 && edit->buffer.curs1 + 1 < edit->buffer.size
+ && edit_buffer_get_current_byte (&edit->buffer) >= 256)
+ {
+ edit_right_char_move_cmd (edit);
+ edit_left_char_move_cmd (edit);
+ }
+#endif
+
+ edit->search_start = edit->buffer.curs1;
+ edit->found_len = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_right_delete_word (WEdit * edit)
+{
+ while (edit->buffer.curs1 < edit->buffer.size)
+ {
+ int c1, c2;
+
+ c1 = edit_delete (edit, TRUE);
+ c2 = edit_buffer_get_current_byte (&edit->buffer);
+ if (c1 == '\n' || c2 == '\n')
+ break;
+ if ((isspace (c1) == 0) != (isspace (c2) == 0))
+ break;
+ if ((my_type_of (c1) & my_type_of (c2)) == 0)
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_left_delete_word (WEdit * edit)
+{
+ while (edit->buffer.curs1 > 0)
+ {
+ int c1, c2;
+
+ c1 = edit_backspace (edit, TRUE);
+ c2 = edit_buffer_get_previous_byte (&edit->buffer);
+ if (c1 == '\n' || c2 == '\n')
+ break;
+ if ((isspace (c1) == 0) != (isspace (c2) == 0))
+ break;
+ if ((my_type_of (c1) & my_type_of (c2)) == 0)
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ the start column position is not recorded, and hence does not
+ undo as it happed. But who would notice.
+ */
+
+static void
+edit_do_undo (WEdit * edit)
+{
+ long ac;
+ long count = 0;
+
+ edit->undo_stack_disable = 1; /* don't record undo's onto undo stack! */
+ edit->over_col = 0;
+ while ((ac = edit_pop_undo_action (edit)) < KEY_PRESS)
+ {
+ switch ((int) ac)
+ {
+ case STACK_BOTTOM:
+ goto done_undo;
+ case CURS_RIGHT:
+ edit_cursor_move (edit, 1);
+ break;
+ case CURS_LEFT:
+ edit_cursor_move (edit, -1);
+ break;
+ case BACKSPACE:
+ case BACKSPACE_BR:
+ edit_backspace (edit, TRUE);
+ break;
+ case DELCHAR:
+ case DELCHAR_BR:
+ edit_delete (edit, TRUE);
+ break;
+ case COLUMN_ON:
+ edit->column_highlight = 1;
+ break;
+ case COLUMN_OFF:
+ edit->column_highlight = 0;
+ break;
+ default:
+ break;
+ }
+ if (ac >= 256 && ac < 512)
+ edit_insert_ahead (edit, ac - 256);
+ if (ac >= 0 && ac < 256)
+ edit_insert (edit, ac);
+
+ if (ac >= MARK_1 - 2 && ac < MARK_2 - 2)
+ {
+ edit->mark1 = ac - MARK_1;
+ edit->column1 =
+ (long) edit_move_forward3 (edit, edit_buffer_get_bol (&edit->buffer, edit->mark1),
+ 0, edit->mark1);
+ }
+ if (ac >= MARK_2 - 2 && ac < MARK_CURS - 2)
+ {
+ edit->mark2 = ac - MARK_2;
+ edit->column2 =
+ (long) edit_move_forward3 (edit, edit_buffer_get_bol (&edit->buffer, edit->mark2),
+ 0, edit->mark2);
+ }
+ else if (ac >= MARK_CURS - 2 && ac < KEY_PRESS)
+ {
+ edit->end_mark_curs = ac - MARK_CURS;
+ }
+ if (count++)
+ edit->force |= REDRAW_PAGE; /* more than one pop usually means something big */
+ }
+
+ if (edit->start_display > ac - KEY_PRESS)
+ {
+ edit->start_line -=
+ edit_buffer_count_lines (&edit->buffer, ac - KEY_PRESS, edit->start_display);
+ edit->force |= REDRAW_PAGE;
+ }
+ else if (edit->start_display < ac - KEY_PRESS)
+ {
+ edit->start_line +=
+ edit_buffer_count_lines (&edit->buffer, edit->start_display, ac - KEY_PRESS);
+ edit->force |= REDRAW_PAGE;
+ }
+ edit->start_display = ac - KEY_PRESS; /* see push and pop above */
+ edit_update_curs_row (edit);
+
+ done_undo:
+ edit->undo_stack_disable = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_do_redo (WEdit * edit)
+{
+ long ac;
+ long count = 0;
+
+ if (edit->redo_stack_reset)
+ return;
+
+ edit->over_col = 0;
+ while ((ac = edit_pop_redo_action (edit)) < KEY_PRESS)
+ {
+ switch ((int) ac)
+ {
+ case STACK_BOTTOM:
+ goto done_redo;
+ case CURS_RIGHT:
+ edit_cursor_move (edit, 1);
+ break;
+ case CURS_LEFT:
+ edit_cursor_move (edit, -1);
+ break;
+ case BACKSPACE:
+ edit_backspace (edit, TRUE);
+ break;
+ case DELCHAR:
+ edit_delete (edit, TRUE);
+ break;
+ case COLUMN_ON:
+ edit->column_highlight = 1;
+ break;
+ case COLUMN_OFF:
+ edit->column_highlight = 0;
+ break;
+ default:
+ break;
+ }
+ if (ac >= 256 && ac < 512)
+ edit_insert_ahead (edit, ac - 256);
+ if (ac >= 0 && ac < 256)
+ edit_insert (edit, ac);
+
+ if (ac >= MARK_1 - 2 && ac < MARK_2 - 2)
+ {
+ edit->mark1 = ac - MARK_1;
+ edit->column1 =
+ (long) edit_move_forward3 (edit, edit_buffer_get_bol (&edit->buffer, edit->mark1),
+ 0, edit->mark1);
+ }
+ else if (ac >= MARK_2 - 2 && ac < KEY_PRESS)
+ {
+ edit->mark2 = ac - MARK_2;
+ edit->column2 =
+ (long) edit_move_forward3 (edit, edit_buffer_get_bol (&edit->buffer, edit->mark2),
+ 0, edit->mark2);
+ }
+ /* more than one pop usually means something big */
+ if (count++)
+ edit->force |= REDRAW_PAGE;
+ }
+
+ if (edit->start_display > ac - KEY_PRESS)
+ {
+ edit->start_line -=
+ edit_buffer_count_lines (&edit->buffer, ac - KEY_PRESS, edit->start_display);
+ edit->force |= REDRAW_PAGE;
+ }
+ else if (edit->start_display < ac - KEY_PRESS)
+ {
+ edit->start_line +=
+ edit_buffer_count_lines (&edit->buffer, edit->start_display, ac - KEY_PRESS);
+ edit->force |= REDRAW_PAGE;
+ }
+ edit->start_display = ac - KEY_PRESS; /* see push and pop above */
+ edit_update_curs_row (edit);
+
+ done_redo:
+ ;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_group_undo (WEdit * edit)
+{
+ long ac = KEY_PRESS;
+ long cur_ac = KEY_PRESS;
+ while (ac != STACK_BOTTOM && ac == cur_ac)
+ {
+ cur_ac = get_prev_undo_action (edit);
+ edit_do_undo (edit);
+ ac = get_prev_undo_action (edit);
+ /* exit from cycle if edit_options.group_undo is not set,
+ * and make single UNDO operation
+ */
+ if (!edit_options.group_undo)
+ ac = STACK_BOTTOM;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_delete_to_line_end (WEdit * edit)
+{
+ while (edit_buffer_get_current_byte (&edit->buffer) != '\n' && edit->buffer.curs2 != 0)
+ edit_delete (edit, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_delete_to_line_begin (WEdit * edit)
+{
+ while (edit_buffer_get_previous_byte (&edit->buffer) != '\n' && edit->buffer.curs1 != 0)
+ edit_backspace (edit, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_aligned_on_a_tab (WEdit * edit)
+{
+ long curs_col;
+
+ edit_update_curs_col (edit);
+ curs_col = edit->curs_col % (TAB_SIZE * space_width);
+ return (curs_col == 0 || curs_col == (HALF_TAB_SIZE * space_width));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+right_of_four_spaces (WEdit * edit)
+{
+ int i, ch = 0;
+
+ for (i = 1; i <= HALF_TAB_SIZE; i++)
+ ch |= edit_buffer_get_byte (&edit->buffer, edit->buffer.curs1 - i);
+
+ return (ch == ' ' && is_aligned_on_a_tab (edit));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+left_of_four_spaces (WEdit * edit)
+{
+ int i, ch = 0;
+
+ for (i = 0; i < HALF_TAB_SIZE; i++)
+ ch |= edit_buffer_get_byte (&edit->buffer, edit->buffer.curs1 + i);
+
+ return (ch == ' ' && is_aligned_on_a_tab (edit));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_auto_indent (WEdit * edit)
+{
+ off_t p;
+
+ p = edit->buffer.curs1;
+ /* use the previous line as a template */
+ p = edit_buffer_get_backward_offset (&edit->buffer, p, 1);
+ /* copy the leading whitespace of the line */
+ while (TRUE)
+ { /* no range check - the line _is_ \n-terminated */
+ char c;
+
+ c = edit_buffer_get_byte (&edit->buffer, p++);
+ if (!whitespace (c))
+ break;
+ edit_insert (edit, c);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_double_newline (WEdit * edit)
+{
+ edit_insert (edit, '\n');
+ if (edit_buffer_get_current_byte (&edit->buffer) == '\n'
+ || edit_buffer_get_byte (&edit->buffer, edit->buffer.curs1 - 2) == '\n')
+ return;
+ edit->force |= REDRAW_PAGE;
+ edit_insert (edit, '\n');
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+insert_spaces_tab (WEdit * edit, gboolean half)
+{
+ long i;
+
+ edit_update_curs_col (edit);
+ i = TAB_SIZE * space_width;
+ if (half)
+ i /= 2;
+ if (i != 0)
+ {
+ i = ((edit->curs_col / i) + 1) * i - edit->curs_col;
+ while (i > 0)
+ {
+ edit_insert (edit, ' ');
+ i -= space_width;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_tab_cmd (WEdit * edit)
+{
+ if (edit_options.fake_half_tabs && is_in_indent (&edit->buffer))
+ {
+ /* insert a half tab (usually four spaces) unless there is a
+ half tab already behind, then delete it and insert a
+ full tab. */
+ if (edit_options.fill_tabs_with_spaces || !right_of_four_spaces (edit))
+ insert_spaces_tab (edit, TRUE);
+ else
+ {
+ int i;
+
+ for (i = 1; i <= HALF_TAB_SIZE; i++)
+ edit_backspace (edit, TRUE);
+ edit_insert (edit, '\t');
+ }
+ }
+ else if (edit_options.fill_tabs_with_spaces)
+ insert_spaces_tab (edit, FALSE);
+ else
+ edit_insert (edit, '\t');
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+check_and_wrap_line (WEdit * edit)
+{
+ off_t curs;
+
+ if (!edit_options.typewriter_wrap)
+ return;
+ edit_update_curs_col (edit);
+ if (edit->curs_col < edit_options.word_wrap_line_length)
+ return;
+ curs = edit->buffer.curs1;
+ while (TRUE)
+ {
+ int c;
+
+ curs--;
+ c = edit_buffer_get_byte (&edit->buffer, curs);
+ if (c == '\n' || curs <= 0)
+ {
+ edit_insert (edit, '\n');
+ return;
+ }
+ if (whitespace (c))
+ {
+ off_t current = edit->buffer.curs1;
+ edit_cursor_move (edit, curs - edit->buffer.curs1 + 1);
+ edit_insert (edit, '\n');
+ edit_cursor_move (edit, current - edit->buffer.curs1 + 1);
+ return;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** this find the matching bracket in either direction, and sets edit->bracket
+ *
+ * @param edit editor object
+ * @param in_screen search only on the current screen
+ * @param furthest_bracket_search count of the bytes for search
+ *
+ * @return position of the found bracket (-1 if no match)
+ */
+
+static off_t
+edit_get_bracket (WEdit * edit, gboolean in_screen, unsigned long furthest_bracket_search)
+{
+ const char *const b = "{}{[][()(", *p;
+ int i = 1, inc = -1, c, d, n = 0;
+ unsigned long j = 0;
+ off_t q;
+
+ edit_update_curs_row (edit);
+ c = edit_buffer_get_current_byte (&edit->buffer);
+ p = strchr (b, c);
+ /* not on a bracket at all */
+ if (p == NULL || *p == '\0')
+ return -1;
+ /* the matching bracket */
+ d = p[1];
+ /* going left or right? */
+ if (strchr ("{[(", c) != NULL)
+ inc = 1;
+ /* no limit */
+ if (furthest_bracket_search == 0)
+ furthest_bracket_search--; /* ULONG_MAX */
+ for (q = edit->buffer.curs1 + inc;; q += inc)
+ {
+ int a;
+
+ /* out of buffer? */
+ if (q >= edit->buffer.size || q < 0)
+ break;
+ a = edit_buffer_get_byte (&edit->buffer, q);
+ /* don't want to eat CPU */
+ if (j++ > furthest_bracket_search)
+ break;
+ /* out of screen? */
+ if (in_screen)
+ {
+ if (q < edit->start_display)
+ break;
+ /* count lines if searching downward */
+ if (inc > 0 && a == '\n')
+ if (n++ >= WIDGET (edit)->rect.lines - edit->curs_row) /* out of screen */
+ break;
+ }
+ /* count bracket depth */
+ i += (a == c) - (a == d);
+ /* return if bracket depth is zero */
+ if (i == 0)
+ return q;
+ }
+ /* no match */
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_goto_matching_bracket (WEdit * edit)
+{
+ off_t q;
+
+ q = edit_get_bracket (edit, 0, 0);
+ if (q >= 0)
+ {
+ edit->bracket = edit->buffer.curs1;
+ edit->force |= REDRAW_PAGE;
+ edit_cursor_move (edit, q - edit->buffer.curs1);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_move_block_to_right (WEdit * edit)
+{
+ off_t start_mark, end_mark;
+ long cur_bol, start_bol;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return;
+
+ start_bol = edit_buffer_get_bol (&edit->buffer, start_mark);
+ cur_bol = edit_buffer_get_bol (&edit->buffer, end_mark - 1);
+
+ do
+ {
+ edit_cursor_move (edit, cur_bol - edit->buffer.curs1);
+ if (!edit_line_is_blank (edit, edit->buffer.curs_line))
+ {
+ if (edit_options.fill_tabs_with_spaces)
+ insert_spaces_tab (edit, edit_options.fake_half_tabs);
+ else
+ edit_insert (edit, '\t');
+ edit_cursor_move (edit,
+ edit_buffer_get_bol (&edit->buffer, cur_bol) - edit->buffer.curs1);
+ }
+
+ if (cur_bol == 0)
+ break;
+
+ cur_bol = edit_buffer_get_bol (&edit->buffer, cur_bol - 1);
+ }
+ while (cur_bol >= start_bol);
+
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_move_block_to_left (WEdit * edit)
+{
+ off_t start_mark, end_mark;
+ off_t cur_bol, start_bol;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return;
+
+ start_bol = edit_buffer_get_bol (&edit->buffer, start_mark);
+ cur_bol = edit_buffer_get_bol (&edit->buffer, end_mark - 1);
+
+ do
+ {
+ int del_tab_width;
+ int next_char;
+
+ edit_cursor_move (edit, cur_bol - edit->buffer.curs1);
+
+ del_tab_width = edit_options.fake_half_tabs ? HALF_TAB_SIZE : TAB_SIZE;
+
+ next_char = edit_buffer_get_current_byte (&edit->buffer);
+ if (next_char == '\t')
+ edit_delete (edit, TRUE);
+ else if (next_char == ' ')
+ {
+ int i;
+
+ for (i = 0; i < del_tab_width; i++)
+ {
+ if (next_char == ' ')
+ edit_delete (edit, TRUE);
+ next_char = edit_buffer_get_current_byte (&edit->buffer);
+ }
+ }
+
+ if (cur_bol == 0)
+ break;
+
+ cur_bol = edit_buffer_get_bol (&edit->buffer, cur_bol - 1);
+ }
+ while (cur_bol >= start_bol);
+
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * prints at the cursor
+ * @return number of chars printed
+ */
+
+static size_t
+edit_print_string (WEdit * e, const char *s)
+{
+ size_t i = 0;
+
+ while (s[i] != '\0')
+ edit_execute_cmd (e, CK_InsertChar, (unsigned char) s[i++]);
+ e->force |= REDRAW_COMPLETELY;
+ edit_update_screen (e);
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+edit_insert_column_from_file (WEdit * edit, int file, off_t * start_pos, off_t * end_pos,
+ long *col1, long *col2)
+{
+ off_t cursor;
+ long col;
+ off_t blocklen = -1, width = 0;
+ unsigned char *data;
+
+ cursor = edit->buffer.curs1;
+ col = edit_get_col (edit);
+ data = g_malloc0 (TEMP_BUF_LEN);
+
+ while ((blocklen = mc_read (file, (char *) data, TEMP_BUF_LEN)) > 0)
+ {
+ off_t i;
+ char *pn;
+
+ pn = strchr ((char *) data, '\n');
+ width = pn == NULL ? blocklen : pn - (char *) data;
+
+ for (i = 0; i < blocklen; i++)
+ {
+ if (data[i] != '\n')
+ edit_insert (edit, data[i]);
+ else
+ { /* fill in and move to next line */
+ long l;
+ off_t p;
+
+ if (edit_buffer_get_current_byte (&edit->buffer) != '\n')
+ for (l = width - (edit_get_col (edit) - col); l > 0; l -= space_width)
+ edit_insert (edit, ' ');
+
+ for (p = edit->buffer.curs1;; p++)
+ {
+ if (p == edit->buffer.size)
+ {
+ edit_cursor_move (edit, edit->buffer.size - edit->buffer.curs1);
+ edit_insert_ahead (edit, '\n');
+ p++;
+ break;
+ }
+ if (edit_buffer_get_byte (&edit->buffer, p) == '\n')
+ {
+ p++;
+ break;
+ }
+ }
+
+ edit_cursor_move (edit, edit_move_forward3 (edit, p, col, 0) - edit->buffer.curs1);
+
+ for (l = col - edit_get_col (edit); l >= space_width; l -= space_width)
+ edit_insert (edit, ' ');
+ }
+ }
+ }
+ *col1 = col;
+ *col2 = col + width;
+ *start_pos = cursor;
+ *end_pos = edit->buffer.curs1;
+ edit_cursor_move (edit, cursor - edit->buffer.curs1);
+ g_free (data);
+
+ return blocklen;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** User edit menu, like user menu (F2) but only in editor. */
+
+void
+user_menu (WEdit * edit, const char *menu_file, int selected_entry)
+{
+ char *block_file;
+ gboolean nomark;
+ off_t curs;
+ off_t start_mark, end_mark;
+ struct stat status;
+ vfs_path_t *block_file_vpath;
+
+ block_file = mc_config_get_full_path (EDIT_HOME_BLOCK_FILE);
+ block_file_vpath = vfs_path_from_str (block_file);
+ curs = edit->buffer.curs1;
+ nomark = !eval_marks (edit, &start_mark, &end_mark);
+ if (!nomark)
+ edit_save_block (edit, block_file, start_mark, end_mark);
+
+ /* run shell scripts from menu */
+ if (user_menu_cmd (CONST_WIDGET (edit), menu_file, selected_entry)
+ && (mc_stat (block_file_vpath, &status) == 0) && (status.st_size != 0))
+ {
+ int rc = 0;
+ FILE *fd;
+
+ /* i.e. we have marked block */
+ if (!nomark)
+ rc = edit_block_delete_cmd (edit);
+
+ if (rc == 0)
+ {
+ off_t ins_len;
+
+ ins_len = edit_insert_file (edit, block_file_vpath);
+ if (!nomark && ins_len > 0)
+ edit_set_markers (edit, start_mark, start_mark + ins_len, 0, 0);
+ }
+ /* truncate block file */
+ fd = fopen (block_file, "w");
+ if (fd != NULL)
+ fclose (fd);
+ }
+ g_free (block_file);
+ vfs_path_free (block_file_vpath, TRUE);
+
+ edit_cursor_move (edit, curs - edit->buffer.curs1);
+ edit->force |= REDRAW_PAGE;
+ widget_draw (WIDGET (edit));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+edit_get_write_filter (const vfs_path_t * write_name_vpath, const vfs_path_t * filename_vpath)
+{
+ int i;
+ const char *write_name;
+ char *p, *write_name_quoted;
+
+ i = edit_find_filter (filename_vpath);
+ if (i < 0)
+ return NULL;
+
+ write_name = vfs_path_get_last_path_str (write_name_vpath);
+ write_name_quoted = name_quote (write_name, FALSE);
+ p = g_strdup_printf (all_filters[i].write, write_name_quoted);
+ g_free (write_name_quoted);
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * @param edit editor object
+ * @param f value of stream file
+ * @return the length of the file
+ */
+
+off_t
+edit_write_stream (WEdit * edit, FILE * f)
+{
+ long i;
+
+ if (edit->lb == LB_ASIS)
+ {
+ for (i = 0; i < edit->buffer.size; i++)
+ if (fputc (edit_buffer_get_byte (&edit->buffer, i), f) < 0)
+ break;
+ return i;
+ }
+
+ /* change line breaks */
+ for (i = 0; i < edit->buffer.size; i++)
+ {
+ unsigned char c;
+
+ c = edit_buffer_get_byte (&edit->buffer, i);
+ if (!(c == '\n' || c == '\r'))
+ {
+ /* not line break */
+ if (fputc (c, f) < 0)
+ return i;
+ }
+ else
+ { /* (c == '\n' || c == '\r') */
+ unsigned char c1;
+
+ c1 = edit_buffer_get_byte (&edit->buffer, i + 1); /* next char */
+
+ switch (edit->lb)
+ {
+ case LB_UNIX: /* replace "\r\n" or '\r' to '\n' */
+ /* put one line break unconditionally */
+ if (fputc ('\n', f) < 0)
+ return i;
+
+ i++; /* 2 chars are processed */
+
+ if (c == '\r' && c1 == '\n')
+ /* Windows line break; go to the next char */
+ break;
+
+ if (c == '\r' && c1 == '\r')
+ {
+ /* two Macintosh line breaks; put second line break */
+ if (fputc ('\n', f) < 0)
+ return i;
+ break;
+ }
+
+ if (fputc (c1, f) < 0)
+ return i;
+ break;
+
+ case LB_WIN: /* replace '\n' or '\r' to "\r\n" */
+ /* put one line break unconditionally */
+ if (fputc ('\r', f) < 0 || fputc ('\n', f) < 0)
+ return i;
+
+ if (c == '\r' && c1 == '\n')
+ /* Windows line break; go to the next char */
+ i++;
+ break;
+
+ case LB_MAC: /* replace "\r\n" or '\n' to '\r' */
+ /* put one line break unconditionally */
+ if (fputc ('\r', f) < 0)
+ return i;
+
+ i++; /* 2 chars are processed */
+
+ if (c == '\r' && c1 == '\n')
+ /* Windows line break; go to the next char */
+ break;
+
+ if (c == '\n' && c1 == '\n')
+ {
+ /* two Windows line breaks; put second line break */
+ if (fputc ('\r', f) < 0)
+ return i;
+ break;
+ }
+
+ if (fputc (c1, f) < 0)
+ return i;
+ break;
+ case LB_ASIS: /* default without changes */
+ default:
+ break;
+ }
+ }
+ }
+
+ return edit->buffer.size;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+is_break_char (char c)
+{
+ return (isspace (c) || strchr ("{}[]()<>=|/\\!?~-+`'\",.;:#$%^&*", c));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** inserts a file at the cursor, returns count of inserted bytes on success */
+
+off_t
+edit_insert_file (WEdit * edit, const vfs_path_t * filename_vpath)
+{
+ char *p;
+ off_t current;
+ off_t ins_len = 0;
+
+ p = edit_get_filter (filename_vpath);
+ current = edit->buffer.curs1;
+
+ if (p != NULL)
+ {
+ FILE *f;
+
+ f = (FILE *) popen (p, "r");
+ if (f != NULL)
+ {
+ edit_insert_stream (edit, f);
+
+ /* Place cursor at the end of text selection */
+ if (!edit_options.cursor_after_inserted_block)
+ {
+ ins_len = edit->buffer.curs1 - current;
+ edit_cursor_move (edit, -ins_len);
+ }
+ if (pclose (f) > 0)
+ {
+ char *errmsg;
+
+ errmsg = g_strdup_printf (_("Error reading from pipe: %s"), p);
+ edit_error_dialog (_("Error"), errmsg);
+ g_free (errmsg);
+ ins_len = -1;
+ }
+ }
+ else
+ {
+ char *errmsg;
+
+ errmsg = g_strdup_printf (_("Cannot open pipe for reading: %s"), p);
+ edit_error_dialog (_("Error"), errmsg);
+ g_free (errmsg);
+ ins_len = -1;
+ }
+ g_free (p);
+ }
+ else
+ {
+ int file;
+ off_t blocklen;
+ int vertical_insertion = 0;
+ char *buf;
+
+ file = mc_open (filename_vpath, O_RDONLY | O_BINARY);
+ if (file == -1)
+ return -1;
+
+ buf = g_malloc0 (TEMP_BUF_LEN);
+ blocklen = mc_read (file, buf, sizeof (VERTICAL_MAGIC));
+ if (blocklen > 0)
+ {
+ /* if contain signature VERTICAL_MAGIC then it vertical block */
+ if (memcmp (buf, VERTICAL_MAGIC, sizeof (VERTICAL_MAGIC)) == 0)
+ vertical_insertion = 1;
+ else
+ mc_lseek (file, 0, SEEK_SET);
+ }
+
+ if (vertical_insertion)
+ {
+ off_t mark1, mark2;
+ long c1, c2;
+
+ blocklen = edit_insert_column_from_file (edit, file, &mark1, &mark2, &c1, &c2);
+ edit_set_markers (edit, edit->buffer.curs1, mark2, c1, c2);
+
+ /* highlight inserted text then not persistent blocks */
+ if (!edit_options.persistent_selections && edit->modified)
+ {
+ if (!edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_OFF);
+ edit->column_highlight = 1;
+ }
+ }
+ else
+ {
+ off_t i;
+
+ while ((blocklen = mc_read (file, (char *) buf, TEMP_BUF_LEN)) > 0)
+ {
+ for (i = 0; i < blocklen; i++)
+ edit_insert (edit, buf[i]);
+ }
+ /* highlight inserted text then not persistent blocks */
+ if (!edit_options.persistent_selections && edit->modified)
+ {
+ edit_set_markers (edit, edit->buffer.curs1, current, 0, 0);
+ if (edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_ON);
+ edit->column_highlight = 0;
+ }
+
+ /* Place cursor at the end of text selection */
+ if (!edit_options.cursor_after_inserted_block)
+ {
+ ins_len = edit->buffer.curs1 - current;
+ edit_cursor_move (edit, -ins_len);
+ }
+ }
+
+ edit->force |= REDRAW_PAGE;
+ g_free (buf);
+ mc_close (file);
+ if (blocklen != 0)
+ ins_len = 0;
+ }
+
+ return ins_len;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Fill in the edit structure. Return NULL on failure. Pass edit as
+ * NULL to allocate a new structure.
+ *
+ * If line is 0, try to restore saved position. Otherwise put the
+ * cursor on that line and show it in the middle of the screen.
+ */
+
+WEdit *
+edit_init (WEdit * edit, const WRect * r, const vfs_path_t * filename_vpath, long line)
+{
+ gboolean to_free = FALSE;
+
+ auto_syntax = TRUE; /* Resetting to auto on every invocation */
+ edit_options.line_state_width = edit_options.line_state ? LINE_STATE_WIDTH : 0;
+
+ if (edit != NULL)
+ {
+ gboolean fullscreen;
+ WRect loc_prev;
+
+ /* save some widget parameters */
+ fullscreen = edit->fullscreen;
+ loc_prev = edit->loc_prev;
+
+ edit_purge_widget (edit);
+
+ /* restore saved parameters */
+ edit->fullscreen = fullscreen;
+ edit->loc_prev = loc_prev;
+ }
+ else
+ {
+ Widget *w;
+
+ edit = g_malloc0 (sizeof (WEdit));
+ to_free = TRUE;
+
+ w = WIDGET (edit);
+ widget_init (w, r, NULL, NULL);
+ w->options |= WOP_SELECTABLE | WOP_TOP_SELECT | WOP_WANT_CURSOR;
+ w->keymap = editor_map;
+ w->ext_keymap = editor_x_map;
+ edit->fullscreen = TRUE;
+ edit_save_size (edit);
+ }
+
+ edit->drag_state = MCEDIT_DRAG_NONE;
+
+ edit->stat1.st_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+ edit->stat1.st_uid = getuid ();
+ edit->stat1.st_gid = getgid ();
+ edit->stat1.st_mtime = 0;
+
+ edit->over_col = 0;
+ edit->bracket = -1;
+ edit->last_bracket = -1;
+ edit->force |= REDRAW_PAGE;
+
+ /* set file name before load file */
+ edit_set_filename (edit, filename_vpath);
+
+ edit->undo_stack_size = START_STACK_SIZE;
+ edit->undo_stack_size_mask = START_STACK_SIZE - 1;
+ edit->undo_stack = g_malloc0 ((edit->undo_stack_size + 10) * sizeof (long));
+
+ edit->redo_stack_size = START_STACK_SIZE;
+ edit->redo_stack_size_mask = START_STACK_SIZE - 1;
+ edit->redo_stack = g_malloc0 ((edit->redo_stack_size + 10) * sizeof (long));
+
+#ifdef HAVE_CHARSET
+ edit->utf8 = FALSE;
+ edit->converter = str_cnv_from_term;
+ edit_set_codeset (edit);
+#endif
+
+ if (!edit_load_file (edit))
+ {
+ /* edit_load_file already gives an error message */
+ if (to_free)
+ g_free (edit);
+ return NULL;
+ }
+
+ edit->loading_done = 1;
+ edit->modified = 0;
+ edit->locked = 0;
+ edit_load_syntax (edit, NULL, NULL);
+ edit_get_syntax_color (edit, -1);
+
+ /* load saved cursor position and/or boolmarks */
+ if ((line == 0) && edit_options.save_position)
+ edit_load_position (edit, TRUE);
+ else
+ {
+ edit_load_position (edit, FALSE);
+ if (line <= 0)
+ line = 1;
+ edit_move_display (edit, line - 1);
+ edit_move_to_line (edit, line - 1);
+ }
+
+ edit_load_macro_cmd (edit);
+
+ return edit;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Clear the edit struct, freeing everything in it. Return TRUE on success */
+gboolean
+edit_clean (WEdit * edit)
+{
+ if (edit == NULL)
+ return FALSE;
+
+ /* a stale lock, remove it */
+ if (edit->locked)
+ (void) unlock_file (edit->filename_vpath);
+
+ /* save cursor position */
+ if (edit_options.save_position)
+ edit_save_position (edit);
+ else if (edit->serialized_bookmarks != NULL)
+ g_array_free (edit->serialized_bookmarks, TRUE);
+
+ /* File specified on the mcedit command line and never saved */
+ if (edit->delete_file)
+ unlink (vfs_path_get_last_path_str (edit->filename_vpath));
+
+ edit_free_syntax_rules (edit);
+ book_mark_flush (edit, -1);
+
+ edit_buffer_clean (&edit->buffer);
+
+ g_free (edit->undo_stack);
+ g_free (edit->redo_stack);
+ vfs_path_free (edit->filename_vpath, TRUE);
+ vfs_path_free (edit->dir_vpath, TRUE);
+ edit_search_deinit (edit);
+
+#ifdef HAVE_CHARSET
+ if (edit->converter != str_cnv_from_term)
+ str_close_conv (edit->converter);
+#endif
+
+ edit_purge_widget (edit);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Load a new file into the editor and set line. If it fails, preserve the old file.
+ * To do it, allocate a new widget, initialize it and, if the new file
+ * was loaded, copy the data to the old widget.
+ *
+ * @return TRUE on success, FALSE on failure.
+ */
+gboolean
+edit_reload_line (WEdit * edit, const vfs_path_t * filename_vpath, long line)
+{
+ Widget *w = WIDGET (edit);
+ WEdit *e;
+
+ e = g_malloc0 (sizeof (WEdit));
+ *WIDGET (e) = *w;
+ /* save some widget parameters */
+ e->fullscreen = edit->fullscreen;
+ e->loc_prev = edit->loc_prev;
+
+ if (edit_init (e, &w->rect, filename_vpath, line) == NULL)
+ {
+ g_free (e);
+ return FALSE;
+ }
+
+ edit_clean (edit);
+ memcpy (edit, e, sizeof (*edit));
+ g_free (e);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+void
+edit_set_codeset (WEdit * edit)
+{
+ const char *cp_id;
+
+ cp_id =
+ get_codepage_id (mc_global.source_codepage >=
+ 0 ? mc_global.source_codepage : mc_global.display_codepage);
+
+ if (cp_id != NULL)
+ {
+ GIConv conv;
+ conv = str_crt_conv_from (cp_id);
+ if (conv != INVALID_CONV)
+ {
+ if (edit->converter != str_cnv_from_term)
+ str_close_conv (edit->converter);
+ edit->converter = conv;
+ }
+ }
+
+ if (cp_id != NULL)
+ edit->utf8 = str_isutf8 (cp_id);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Recording stack for undo:
+ * The following is an implementation of a compressed stack. Identical
+ * pushes are recorded by a negative prefix indicating the number of times the
+ * same char was pushed. This saves space for repeated curs-left or curs-right
+ * delete etc.
+ *
+ * eg:
+ *
+ * pushed: stored:
+ *
+ * a
+ * b a
+ * b -3
+ * b b
+ * c --> -4
+ * c c
+ * c d
+ * c
+ * d
+ *
+ * If the stack long int is 0-255 it represents a normal insert (from a backspace),
+ * 256-512 is an insert ahead (from a delete), If it is between 600 and 700 it is one
+ * of the cursor functions define'd in edit-impl.h. 1000 through 700'000'000 is to
+ * set edit->mark1 position. 700'000'000 through 1400'000'000 is to set edit->mark2
+ * position.
+ *
+ * The only way the cursor moves or the buffer is changed is through the routines:
+ * insert, backspace, insert_ahead, delete, and cursor_move.
+ * These record the reverse undo movements onto the stack each time they are
+ * called.
+ *
+ * Each key press results in a set of actions (insert; delete ...). So each time
+ * a key is pressed the current position of start_display is pushed as
+ * KEY_PRESS + start_display. Then for undoing, we pop until we get to a number
+ * over KEY_PRESS. We then assign this number less KEY_PRESS to start_display. So undo
+ * tracks scrolling and key actions exactly. (KEY_PRESS is about (2^31) * (2/3) = 1400'000'000)
+ *
+ *
+ *
+ * @param edit editor object
+ * @param c code of the action
+ */
+
+void
+edit_push_undo_action (WEdit * edit, long c)
+{
+ unsigned long sp = edit->undo_stack_pointer;
+ unsigned long spm1;
+
+ /* first enlarge the stack if necessary */
+ if (sp > edit->undo_stack_size - 10)
+ { /* say */
+ if (max_undo < 256)
+ max_undo = 256;
+ if (edit->undo_stack_size < (unsigned long) max_undo)
+ {
+ long *t;
+
+ t = g_realloc (edit->undo_stack, (edit->undo_stack_size * 2 + 10) * sizeof (long));
+ if (t != NULL)
+ {
+ edit->undo_stack = t;
+ edit->undo_stack_size <<= 1;
+ edit->undo_stack_size_mask = edit->undo_stack_size - 1;
+ }
+ }
+ }
+ spm1 = (edit->undo_stack_pointer - 1) & edit->undo_stack_size_mask;
+ if (edit->undo_stack_disable)
+ {
+ edit_push_redo_action (edit, KEY_PRESS);
+ edit_push_redo_action (edit, c);
+ return;
+ }
+
+ if (edit->redo_stack_reset)
+ edit->redo_stack_bottom = edit->redo_stack_pointer = 0;
+
+ if (edit->undo_stack_bottom != sp
+ && spm1 != edit->undo_stack_bottom
+ && ((sp - 2) & edit->undo_stack_size_mask) != edit->undo_stack_bottom)
+ {
+ long d;
+ if (edit->undo_stack[spm1] < 0)
+ {
+ d = edit->undo_stack[(sp - 2) & edit->undo_stack_size_mask];
+ if (d == c && edit->undo_stack[spm1] > -1000000000)
+ {
+ if (c < KEY_PRESS) /* --> no need to push multiple do-nothings */
+ edit->undo_stack[spm1]--;
+ return;
+ }
+ }
+ else
+ {
+ d = edit->undo_stack[spm1];
+ if (d == c)
+ {
+ if (c >= KEY_PRESS)
+ return; /* --> no need to push multiple do-nothings */
+ edit->undo_stack[sp] = -2;
+ goto check_bottom;
+ }
+ }
+ }
+ edit->undo_stack[sp] = c;
+
+ check_bottom:
+ edit->undo_stack_pointer = (edit->undo_stack_pointer + 1) & edit->undo_stack_size_mask;
+
+ /* if the sp wraps round and catches the undo_stack_bottom then erase
+ * the first set of actions on the stack to make space - by moving
+ * undo_stack_bottom forward one "key press" */
+ c = (edit->undo_stack_pointer + 2) & edit->undo_stack_size_mask;
+ if ((unsigned long) c == edit->undo_stack_bottom ||
+ (((unsigned long) c + 1) & edit->undo_stack_size_mask) == edit->undo_stack_bottom)
+ do
+ {
+ edit->undo_stack_bottom = (edit->undo_stack_bottom + 1) & edit->undo_stack_size_mask;
+ }
+ while (edit->undo_stack[edit->undo_stack_bottom] < KEY_PRESS
+ && edit->undo_stack_bottom != edit->undo_stack_pointer);
+
+ /*If a single key produced enough pushes to wrap all the way round then we would notice that the [undo_stack_bottom] does not contain KEY_PRESS. The stack is then initialised: */
+ if (edit->undo_stack_pointer != edit->undo_stack_bottom
+ && edit->undo_stack[edit->undo_stack_bottom] < KEY_PRESS)
+ {
+ edit->undo_stack_bottom = edit->undo_stack_pointer = 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_push_redo_action (WEdit * edit, long c)
+{
+ unsigned long sp = edit->redo_stack_pointer;
+ unsigned long spm1;
+ /* first enlarge the stack if necessary */
+ if (sp > edit->redo_stack_size - 10)
+ { /* say */
+ if (max_undo < 256)
+ max_undo = 256;
+ if (edit->redo_stack_size < (unsigned long) max_undo)
+ {
+ long *t;
+
+ t = g_realloc (edit->redo_stack, (edit->redo_stack_size * 2 + 10) * sizeof (long));
+ if (t != NULL)
+ {
+ edit->redo_stack = t;
+ edit->redo_stack_size <<= 1;
+ edit->redo_stack_size_mask = edit->redo_stack_size - 1;
+ }
+ }
+ }
+ spm1 = (edit->redo_stack_pointer - 1) & edit->redo_stack_size_mask;
+
+ if (edit->redo_stack_bottom != sp
+ && spm1 != edit->redo_stack_bottom
+ && ((sp - 2) & edit->redo_stack_size_mask) != edit->redo_stack_bottom)
+ {
+ long d;
+ if (edit->redo_stack[spm1] < 0)
+ {
+ d = edit->redo_stack[(sp - 2) & edit->redo_stack_size_mask];
+ if (d == c && edit->redo_stack[spm1] > -1000000000)
+ {
+ if (c < KEY_PRESS) /* --> no need to push multiple do-nothings */
+ edit->redo_stack[spm1]--;
+ return;
+ }
+ }
+ else
+ {
+ d = edit->redo_stack[spm1];
+ if (d == c)
+ {
+ if (c >= KEY_PRESS)
+ return; /* --> no need to push multiple do-nothings */
+ edit->redo_stack[sp] = -2;
+ goto redo_check_bottom;
+ }
+ }
+ }
+ edit->redo_stack[sp] = c;
+
+ redo_check_bottom:
+ edit->redo_stack_pointer = (edit->redo_stack_pointer + 1) & edit->redo_stack_size_mask;
+
+ /* if the sp wraps round and catches the redo_stack_bottom then erase
+ * the first set of actions on the stack to make space - by moving
+ * redo_stack_bottom forward one "key press" */
+ c = (edit->redo_stack_pointer + 2) & edit->redo_stack_size_mask;
+ if ((unsigned long) c == edit->redo_stack_bottom ||
+ (((unsigned long) c + 1) & edit->redo_stack_size_mask) == edit->redo_stack_bottom)
+ do
+ {
+ edit->redo_stack_bottom = (edit->redo_stack_bottom + 1) & edit->redo_stack_size_mask;
+ }
+ while (edit->redo_stack[edit->redo_stack_bottom] < KEY_PRESS
+ && edit->redo_stack_bottom != edit->redo_stack_pointer);
+
+ /*
+ * If a single key produced enough pushes to wrap all the way round then
+ * we would notice that the [redo_stack_bottom] does not contain KEY_PRESS.
+ * The stack is then initialised:
+ */
+
+ if (edit->redo_stack_pointer != edit->redo_stack_bottom
+ && edit->redo_stack[edit->redo_stack_bottom] < KEY_PRESS)
+ edit->redo_stack_bottom = edit->redo_stack_pointer = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ Basic low level single character buffer alterations and movements at the cursor.
+ */
+
+void
+edit_insert (WEdit * edit, int c)
+{
+ /* first we must update the position of the display window */
+ if (edit->buffer.curs1 < edit->start_display)
+ {
+ edit->start_display++;
+ if (c == '\n')
+ edit->start_line++;
+ }
+
+ /* Mark file as modified, unless the file hasn't been fully loaded */
+ if (edit->loading_done)
+ edit_modification (edit);
+
+ /* now we must update some info on the file and check if a redraw is required */
+ if (c == '\n')
+ {
+ book_mark_inc (edit, edit->buffer.curs_line);
+ edit->buffer.curs_line++;
+ edit->buffer.lines++;
+ edit->force |= REDRAW_LINE_ABOVE | REDRAW_AFTER_CURSOR;
+ }
+
+ /* save the reverse command onto the undo stack */
+ /* ordinary char and not space */
+ if (c > 32)
+ edit_push_undo_action (edit, BACKSPACE);
+ else
+ edit_push_undo_action (edit, BACKSPACE_BR);
+ /* update markers */
+ edit->mark1 += (edit->mark1 > edit->buffer.curs1) ? 1 : 0;
+ edit->mark2 += (edit->mark2 > edit->buffer.curs1) ? 1 : 0;
+ edit->last_get_rule += (edit->last_get_rule > edit->buffer.curs1) ? 1 : 0;
+
+ edit_buffer_insert (&edit->buffer, c);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** same as edit_insert and move left */
+
+void
+edit_insert_ahead (WEdit * edit, int c)
+{
+ if (edit->buffer.curs1 < edit->start_display)
+ {
+ edit->start_display++;
+ if (c == '\n')
+ edit->start_line++;
+ }
+ edit_modification (edit);
+ if (c == '\n')
+ {
+ book_mark_inc (edit, edit->buffer.curs_line);
+ edit->buffer.lines++;
+ edit->force |= REDRAW_AFTER_CURSOR;
+ }
+ /* ordinary char and not space */
+ if (c > 32)
+ edit_push_undo_action (edit, DELCHAR);
+ else
+ edit_push_undo_action (edit, DELCHAR_BR);
+
+ edit->mark1 += (edit->mark1 >= edit->buffer.curs1) ? 1 : 0;
+ edit->mark2 += (edit->mark2 >= edit->buffer.curs1) ? 1 : 0;
+ edit->last_get_rule += (edit->last_get_rule >= edit->buffer.curs1) ? 1 : 0;
+
+ edit_buffer_insert_ahead (&edit->buffer, c);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_insert_over (WEdit * edit)
+{
+ long i;
+
+ for (i = 0; i < edit->over_col; i++)
+ edit_insert (edit, ' ');
+
+ edit->over_col = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+edit_delete (WEdit * edit, gboolean byte_delete)
+{
+ int p = 0;
+ int char_length = 1;
+ int i;
+
+ if (edit->buffer.curs2 == 0)
+ return 0;
+
+#ifdef HAVE_CHARSET
+ /* if byte_delete == TRUE then delete only one byte not multibyte char */
+ if (edit->utf8 && !byte_delete)
+ {
+ edit_buffer_get_utf (&edit->buffer, edit->buffer.curs1, &char_length);
+ if (char_length < 1)
+ char_length = 1;
+ }
+#else
+ (void) byte_delete;
+#endif
+
+ if (edit->mark2 != edit->mark1)
+ edit_push_markers (edit);
+
+ for (i = 1; i <= char_length; i++)
+ {
+ if (edit->mark1 > edit->buffer.curs1)
+ {
+ edit->mark1--;
+ edit->end_mark_curs--;
+ }
+ if (edit->mark2 > edit->buffer.curs1)
+ edit->mark2--;
+ if (edit->last_get_rule > edit->buffer.curs1)
+ edit->last_get_rule--;
+
+ p = edit_buffer_delete (&edit->buffer);
+
+ edit_push_undo_action (edit, p + 256);
+ }
+
+ edit_modification (edit);
+ if (p == '\n')
+ {
+ book_mark_dec (edit, edit->buffer.curs_line);
+ edit->buffer.lines--;
+ edit->force |= REDRAW_AFTER_CURSOR;
+ }
+ if (edit->buffer.curs1 < edit->start_display)
+ {
+ edit->start_display--;
+ if (p == '\n')
+ edit->start_line--;
+ }
+
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+edit_backspace (WEdit * edit, gboolean byte_delete)
+{
+ int p = 0;
+ int char_length = 1;
+ int i;
+
+ if (edit->buffer.curs1 == 0)
+ return 0;
+
+ if (edit->mark2 != edit->mark1)
+ edit_push_markers (edit);
+
+#ifdef HAVE_CHARSET
+ if (edit->utf8 && !byte_delete)
+ {
+ edit_buffer_get_prev_utf (&edit->buffer, edit->buffer.curs1, &char_length);
+ if (char_length < 1)
+ char_length = 1;
+ }
+#else
+ (void) byte_delete;
+#endif
+
+ for (i = 1; i <= char_length; i++)
+ {
+ if (edit->mark1 >= edit->buffer.curs1)
+ {
+ edit->mark1--;
+ edit->end_mark_curs--;
+ }
+ if (edit->mark2 >= edit->buffer.curs1)
+ edit->mark2--;
+ if (edit->last_get_rule >= edit->buffer.curs1)
+ edit->last_get_rule--;
+
+ p = edit_buffer_backspace (&edit->buffer);
+
+ edit_push_undo_action (edit, p);
+ }
+ edit_modification (edit);
+ if (p == '\n')
+ {
+ book_mark_dec (edit, edit->buffer.curs_line);
+ edit->buffer.curs_line--;
+ edit->buffer.lines--;
+ edit->force |= REDRAW_AFTER_CURSOR;
+ }
+
+ if (edit->buffer.curs1 < edit->start_display)
+ {
+ edit->start_display--;
+ if (p == '\n')
+ edit->start_line--;
+ }
+
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** moves the cursor right or left: increment positive or negative respectively */
+
+void
+edit_cursor_move (WEdit * edit, off_t increment)
+{
+ if (increment < 0)
+ {
+ for (; increment < 0 && edit->buffer.curs1 != 0; increment++)
+ {
+ int c;
+
+ edit_push_undo_action (edit, CURS_RIGHT);
+
+ c = edit_buffer_get_previous_byte (&edit->buffer);
+ edit_buffer_insert_ahead (&edit->buffer, c);
+ c = edit_buffer_backspace (&edit->buffer);
+ if (c == '\n')
+ {
+ edit->buffer.curs_line--;
+ edit->force |= REDRAW_LINE_BELOW;
+ }
+ }
+ }
+ else
+ {
+ for (; increment > 0 && edit->buffer.curs2 != 0; increment--)
+ {
+ int c;
+
+ edit_push_undo_action (edit, CURS_LEFT);
+
+ c = edit_buffer_get_current_byte (&edit->buffer);
+ edit_buffer_insert (&edit->buffer, c);
+ c = edit_buffer_delete (&edit->buffer);
+ if (c == '\n')
+ {
+ edit->buffer.curs_line++;
+ edit->force |= REDRAW_LINE_ABOVE;
+ }
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* If cols is zero this returns the count of columns from current to upto. */
+/* If upto is zero returns index of cols across from current. */
+
+off_t
+edit_move_forward3 (const WEdit * edit, off_t current, long cols, off_t upto)
+{
+ off_t p, q;
+ long col;
+
+ if (upto != 0)
+ {
+ q = upto;
+ cols = -10;
+ }
+ else
+ q = edit->buffer.size + 2;
+
+ for (col = 0, p = current; p < q; p++)
+ {
+ int c, orig_c;
+
+ if (cols != -10)
+ {
+ if (col == cols)
+ return p;
+ if (col > cols)
+ return p - 1;
+ }
+
+ orig_c = c = edit_buffer_get_byte (&edit->buffer, p);
+
+#ifdef HAVE_CHARSET
+ if (edit->utf8)
+ {
+ int utf_ch;
+ int char_length = 1;
+
+ utf_ch = edit_buffer_get_utf (&edit->buffer, p, &char_length);
+ if (mc_global.utf8_display)
+ {
+ if (char_length > 1)
+ col -= char_length - 1;
+ if (g_unichar_iswide (utf_ch))
+ col++;
+ }
+ else if (char_length > 1 && g_unichar_isprint (utf_ch))
+ col -= char_length - 1;
+ }
+
+ c = convert_to_display_c (c);
+#endif
+
+ if (c == '\n')
+ return (upto != 0 ? (off_t) col : p);
+ if (c == '\t')
+ col += TAB_SIZE - col % TAB_SIZE;
+ else if ((c < 32 || c == 127) && (orig_c == c
+#ifdef HAVE_CHARSET
+ || (!mc_global.utf8_display && !edit->utf8)
+#endif
+ ))
+ /* '\r' is shown as ^M, so we must advance 2 characters */
+ /* Caret notation for control characters */
+ col += 2;
+ else
+ col++;
+ }
+ return (off_t) col;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns the current offset of the cursor from the beginning of a file */
+
+off_t
+edit_get_cursor_offset (const WEdit * edit)
+{
+ return edit->buffer.curs1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns the current column position of the cursor */
+
+long
+edit_get_col (const WEdit * edit)
+{
+ return (long) edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer), 0,
+ edit->buffer.curs1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Scrolling functions */
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_update_curs_row (WEdit * edit)
+{
+ edit->curs_row = edit->buffer.curs_line - edit->start_line;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_update_curs_col (WEdit * edit)
+{
+ edit->curs_col = (long) edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer),
+ 0, edit->buffer.curs1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+long
+edit_get_curs_col (const WEdit * edit)
+{
+ return edit->curs_col;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** moves the display start position up by i lines */
+
+void
+edit_scroll_upward (WEdit * edit, long i)
+{
+ long lines_above = edit->start_line;
+
+ if (i > lines_above)
+ i = lines_above;
+ if (i != 0)
+ {
+ edit->start_line -= i;
+ edit->start_display =
+ edit_buffer_get_backward_offset (&edit->buffer, edit->start_display, i);
+ edit->force |= REDRAW_PAGE;
+ edit->force &= (0xfff - REDRAW_CHAR_ONLY);
+ }
+ edit_update_curs_row (edit);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_scroll_downward (WEdit * edit, long i)
+{
+ long lines_below;
+
+ lines_below = edit->buffer.lines - edit->start_line - (WIDGET (edit)->rect.lines - 1);
+ if (lines_below > 0)
+ {
+ if (i > lines_below)
+ i = lines_below;
+ edit->start_line += i;
+ edit->start_display =
+ edit_buffer_get_forward_offset (&edit->buffer, edit->start_display, i, 0);
+ edit->force |= REDRAW_PAGE;
+ edit->force &= (0xfff - REDRAW_CHAR_ONLY);
+ }
+ edit_update_curs_row (edit);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_scroll_right (WEdit * edit, long i)
+{
+ edit->force |= REDRAW_PAGE;
+ edit->force &= (0xfff - REDRAW_CHAR_ONLY);
+ edit->start_col -= i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_scroll_left (WEdit * edit, long i)
+{
+ if (edit->start_col)
+ {
+ edit->start_col += i;
+ if (edit->start_col > 0)
+ edit->start_col = 0;
+ edit->force |= REDRAW_PAGE;
+ edit->force &= (0xfff - REDRAW_CHAR_ONLY);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* high level cursor movement commands */
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_move_to_prev_col (WEdit * edit, off_t p)
+{
+ long prev = edit->prev_col;
+ long over = edit->over_col;
+
+ edit_cursor_move (edit,
+ edit_move_forward3 (edit, p, prev + edit->over_col, 0) - edit->buffer.curs1);
+
+ if (edit_options.cursor_beyond_eol)
+ {
+ long line_len;
+
+ line_len = (long) edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer), 0,
+ edit_buffer_get_current_eol (&edit->buffer));
+ if (line_len < prev + edit->over_col)
+ {
+ edit->over_col = prev + over - line_len;
+ edit->prev_col = line_len;
+ edit->curs_col = line_len;
+ }
+ else
+ {
+ edit->curs_col = prev + over;
+ edit->prev_col = edit->curs_col;
+ edit->over_col = 0;
+ }
+ }
+ else
+ {
+ edit->over_col = 0;
+ if (edit_options.fake_half_tabs && is_in_indent (&edit->buffer))
+ {
+ long fake_half_tabs;
+
+ edit_update_curs_col (edit);
+
+ fake_half_tabs = HALF_TAB_SIZE * space_width;
+ if (fake_half_tabs != 0 && edit->curs_col % fake_half_tabs != 0)
+ {
+ long q;
+
+ q = edit->curs_col;
+ edit->curs_col -= (edit->curs_col % fake_half_tabs);
+ p = edit_buffer_get_current_bol (&edit->buffer);
+ edit_cursor_move (edit,
+ edit_move_forward3 (edit, p, edit->curs_col,
+ 0) - edit->buffer.curs1);
+ if (!left_of_four_spaces (edit))
+ edit_cursor_move (edit,
+ edit_move_forward3 (edit, p, q, 0) - edit->buffer.curs1);
+ }
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** check whether line in editor is blank or not
+ *
+ * @param edit editor object
+ * @param line number of line
+ *
+ * @return TRUE if line in blank, FALSE otherwise
+ */
+
+gboolean
+edit_line_is_blank (WEdit * edit, long line)
+{
+ return is_blank (&edit->buffer, edit_find_line (edit, line));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** move cursor to line 'line' */
+
+void
+edit_move_to_line (WEdit * e, long line)
+{
+ if (line < e->buffer.curs_line)
+ edit_move_up (e, e->buffer.curs_line - line, FALSE);
+ else
+ edit_move_down (e, line - e->buffer.curs_line, FALSE);
+ edit_scroll_screen_over_cursor (e);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** scroll window so that first visible line is 'line' */
+
+void
+edit_move_display (WEdit * e, long line)
+{
+ if (line < e->start_line)
+ edit_scroll_upward (e, e->start_line - line);
+ else
+ edit_scroll_downward (e, line - e->start_line);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** save markers onto undo stack */
+
+void
+edit_push_markers (WEdit * edit)
+{
+ edit_push_undo_action (edit, MARK_1 + edit->mark1);
+ edit_push_undo_action (edit, MARK_2 + edit->mark2);
+ edit_push_undo_action (edit, MARK_CURS + edit->end_mark_curs);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_set_markers (WEdit * edit, off_t m1, off_t m2, long c1, long c2)
+{
+ edit->mark1 = m1;
+ edit->mark2 = m2;
+ edit->column1 = c1;
+ edit->column2 = c2;
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/** highlight marker toggle */
+
+void
+edit_mark_cmd (WEdit * edit, gboolean unmark)
+{
+ edit_push_markers (edit);
+ if (unmark)
+ {
+ edit_set_markers (edit, 0, 0, 0, 0);
+ edit->force |= REDRAW_PAGE;
+ }
+ else if (edit->mark2 >= 0)
+ {
+ edit->end_mark_curs = -1;
+ edit_set_markers (edit, edit->buffer.curs1, -1, edit->curs_col + edit->over_col,
+ edit->curs_col + edit->over_col);
+ edit->force |= REDRAW_PAGE;
+ }
+ else
+ {
+ edit->end_mark_curs = edit->buffer.curs1;
+ edit_set_markers (edit, edit->mark1, edit->buffer.curs1, edit->column1,
+ edit->curs_col + edit->over_col);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** highlight the word under cursor */
+
+void
+edit_mark_current_word_cmd (WEdit * edit)
+{
+ long pos;
+
+ for (pos = edit->buffer.curs1; pos != 0; pos--)
+ {
+ int c1, c2;
+
+ c1 = edit_buffer_get_byte (&edit->buffer, pos);
+ c2 = edit_buffer_get_byte (&edit->buffer, pos - 1);
+ if (!isspace (c1) && isspace (c2))
+ break;
+ if ((my_type_of (c1) & my_type_of (c2)) == 0)
+ break;
+ }
+ edit->mark1 = pos;
+
+ for (; pos < edit->buffer.size; pos++)
+ {
+ int c1, c2;
+
+ c1 = edit_buffer_get_byte (&edit->buffer, pos);
+ c2 = edit_buffer_get_byte (&edit->buffer, pos + 1);
+ if (!isspace (c1) && isspace (c2))
+ break;
+ if ((my_type_of (c1) & my_type_of (c2)) == 0)
+ break;
+ }
+ edit->mark2 = MIN (pos + 1, edit->buffer.size);
+
+ edit->force |= REDRAW_LINE_ABOVE | REDRAW_AFTER_CURSOR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_mark_current_line_cmd (WEdit * edit)
+{
+ edit->mark1 = edit_buffer_get_current_bol (&edit->buffer);
+ edit->mark2 = edit_buffer_get_current_eol (&edit->buffer);
+
+ edit->force |= REDRAW_LINE_ABOVE | REDRAW_AFTER_CURSOR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_delete_line (WEdit * edit)
+{
+ /*
+ * Delete right part of the line.
+ * Note that edit_buffer_get_byte() returns '\n' when byte position is
+ * beyond EOF.
+ */
+ while (edit_buffer_get_current_byte (&edit->buffer) != '\n')
+ (void) edit_delete (edit, TRUE);
+
+ /*
+ * Delete '\n' char.
+ * Note that edit_delete() will not corrupt anything if called while
+ * cursor position is EOF.
+ */
+ (void) edit_delete (edit, TRUE);
+
+ /*
+ * Delete left part of the line.
+ * Note, that edit_buffer_get_byte() returns '\n' when byte position is < 0.
+ */
+ while (edit_buffer_get_previous_byte (&edit->buffer) != '\n')
+ (void) edit_backspace (edit, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_push_key_press (WEdit * edit)
+{
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+ if (edit->mark2 == -1)
+ {
+ edit_push_undo_action (edit, MARK_1 + edit->mark1);
+ edit_push_undo_action (edit, MARK_CURS + edit->end_mark_curs);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_find_bracket (WEdit * edit)
+{
+ edit->bracket = edit_get_bracket (edit, 1, 10000);
+ if (edit->last_bracket != edit->bracket)
+ edit->force |= REDRAW_PAGE;
+ edit->last_bracket = edit->bracket;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * This executes a command as though the user initiated it through a key
+ * press. Callback with MSG_KEY as a message calls this after
+ * translating the key press. This function can be used to pass any
+ * command to the editor. Note that the screen wouldn't update
+ * automatically. Either of command or char_for_insertion must be
+ * passed as -1. Commands are executed, and char_for_insertion is
+ * inserted at the cursor.
+ */
+
+void
+edit_execute_key_command (WEdit * edit, long command, int char_for_insertion)
+{
+ if (command == CK_MacroStartRecord || command == CK_RepeatStartRecord
+ || (macro_index < 0
+ && (command == CK_MacroStartStopRecord || command == CK_RepeatStartStopRecord)))
+ {
+ macro_index = 0;
+ edit->force |= REDRAW_CHAR_ONLY | REDRAW_LINE;
+ return;
+ }
+ if (macro_index != -1)
+ {
+ edit->force |= REDRAW_COMPLETELY;
+ if (command == CK_MacroStopRecord || command == CK_MacroStartStopRecord)
+ {
+ edit_store_macro_cmd (edit);
+ macro_index = -1;
+ return;
+ }
+ if (command == CK_RepeatStopRecord || command == CK_RepeatStartStopRecord)
+ {
+ edit_repeat_macro_cmd (edit);
+ macro_index = -1;
+ return;
+ }
+ }
+
+ if (macro_index >= 0 && macro_index < MAX_MACRO_LENGTH - 1)
+ {
+ record_macro_buf[macro_index].action = command;
+ record_macro_buf[macro_index++].ch = char_for_insertion;
+ }
+ /* record the beginning of a set of editing actions initiated by a key press */
+ if (command != CK_Undo && command != CK_ExtendedKeyMap)
+ edit_push_key_press (edit);
+
+ edit_execute_cmd (edit, command, char_for_insertion);
+ if (edit->column_highlight)
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ This executes a command at a lower level than macro recording.
+ It also does not push a key_press onto the undo stack. This means
+ that if it is called many times, a single undo command will undo
+ all of them. It also does not check for the Undo command.
+ */
+void
+edit_execute_cmd (WEdit * edit, long command, int char_for_insertion)
+{
+ WRect *w = &WIDGET (edit)->rect;
+
+ if (command == CK_WindowFullscreen)
+ {
+ edit_toggle_fullscreen (edit);
+ return;
+ }
+
+ /* handle window state */
+ if (edit_handle_move_resize (edit, command))
+ return;
+
+ edit->force |= REDRAW_LINE;
+
+ /* The next key press will unhighlight the found string, so update
+ * the whole page */
+ if (edit->found_len || edit->column_highlight)
+ edit->force |= REDRAW_PAGE;
+
+ switch (command)
+ {
+ /* a mark command with shift-arrow */
+ case CK_MarkLeft:
+ case CK_MarkRight:
+ case CK_MarkToWordBegin:
+ case CK_MarkToWordEnd:
+ case CK_MarkToHome:
+ case CK_MarkToEnd:
+ case CK_MarkUp:
+ case CK_MarkDown:
+ case CK_MarkPageUp:
+ case CK_MarkPageDown:
+ case CK_MarkToFileBegin:
+ case CK_MarkToFileEnd:
+ case CK_MarkToPageBegin:
+ case CK_MarkToPageEnd:
+ case CK_MarkScrollUp:
+ case CK_MarkScrollDown:
+ case CK_MarkParagraphUp:
+ case CK_MarkParagraphDown:
+ /* a mark command with alt-arrow */
+ case CK_MarkColumnPageUp:
+ case CK_MarkColumnPageDown:
+ case CK_MarkColumnLeft:
+ case CK_MarkColumnRight:
+ case CK_MarkColumnUp:
+ case CK_MarkColumnDown:
+ case CK_MarkColumnScrollUp:
+ case CK_MarkColumnScrollDown:
+ case CK_MarkColumnParagraphUp:
+ case CK_MarkColumnParagraphDown:
+ edit->column_highlight = 0;
+ if (edit->highlight == 0 || (edit->mark2 != -1 && edit->mark1 != edit->mark2))
+ {
+ edit_mark_cmd (edit, TRUE); /* clear */
+ edit_mark_cmd (edit, FALSE); /* marking on */
+ }
+ edit->highlight = 1;
+ break;
+
+ /* any other command */
+ default:
+ if (edit->highlight)
+ edit_mark_cmd (edit, FALSE); /* clear */
+ edit->highlight = 0;
+ }
+
+ /* first check for undo */
+ if (command == CK_Undo)
+ {
+ edit->redo_stack_reset = 0;
+ edit_group_undo (edit);
+ edit->found_len = 0;
+ edit->prev_col = edit_get_col (edit);
+ edit->search_start = edit->buffer.curs1;
+ return;
+ }
+ /* check for redo */
+ if (command == CK_Redo)
+ {
+ edit->redo_stack_reset = 0;
+ edit_do_redo (edit);
+ edit->found_len = 0;
+ edit->prev_col = edit_get_col (edit);
+ edit->search_start = edit->buffer.curs1;
+ return;
+ }
+
+ edit->redo_stack_reset = 1;
+
+ /* An ordinary key press */
+ if (char_for_insertion >= 0)
+ {
+ /* if non persistent selection and text selected */
+ if (!edit_options.persistent_selections && edit->mark1 != edit->mark2)
+ edit_block_delete_cmd (edit);
+
+ if (edit->overwrite)
+ {
+ /* remove char only one time, after input first byte, multibyte chars */
+#ifdef HAVE_CHARSET
+ if (!mc_global.utf8_display || edit->charpoint == 0)
+#endif
+ if (edit_buffer_get_current_byte (&edit->buffer) != '\n')
+
+ edit_delete (edit, FALSE);
+ }
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit_insert_over (edit);
+#ifdef HAVE_CHARSET
+ /**
+ Encode 8-bit input as UTF-8, if display (locale) is *not* UTF-8,
+ *but* source encoding *is* set to UTF-8; see ticket #3843 for the details.
+ */
+ if (char_for_insertion > 127 && str_isutf8 (get_codepage_id (mc_global.source_codepage))
+ && !mc_global.utf8_display)
+ {
+ unsigned char str[UTF8_CHAR_LEN + 1];
+ size_t i = 0;
+ int res;
+
+ res = g_unichar_to_utf8 (char_for_insertion, (char *) str);
+ if (res == 0)
+ {
+ str[0] = '.';
+ str[1] = '\0';
+ }
+ else
+ {
+ str[res] = '\0';
+ }
+ while (i <= UTF8_CHAR_LEN && str[i] != '\0')
+ {
+ char_for_insertion = str[i];
+ edit_insert (edit, char_for_insertion);
+ i++;
+ }
+ }
+ else
+#endif
+ edit_insert (edit, char_for_insertion);
+
+ if (edit_options.auto_para_formatting)
+ {
+ format_paragraph (edit, FALSE);
+ edit->force |= REDRAW_PAGE;
+ }
+ else
+ check_and_wrap_line (edit);
+ edit->found_len = 0;
+ edit->prev_col = edit_get_col (edit);
+ edit->search_start = edit->buffer.curs1;
+ edit_find_bracket (edit);
+ return;
+ }
+
+ switch (command)
+ {
+ case CK_TopOnScreen:
+ case CK_BottomOnScreen:
+ case CK_Top:
+ case CK_Bottom:
+ case CK_PageUp:
+ case CK_PageDown:
+ case CK_Home:
+ case CK_End:
+ case CK_Up:
+ case CK_Down:
+ case CK_Left:
+ case CK_Right:
+ case CK_WordLeft:
+ case CK_WordRight:
+ if (!edit_options.persistent_selections && edit->mark2 >= 0)
+ {
+ if (edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_ON);
+ edit->column_highlight = 0;
+ edit_mark_cmd (edit, TRUE);
+ }
+ break;
+ default:
+ break;
+ }
+
+ switch (command)
+ {
+ case CK_TopOnScreen:
+ case CK_BottomOnScreen:
+ case CK_MarkToPageBegin:
+ case CK_MarkToPageEnd:
+ case CK_Up:
+ case CK_Down:
+ case CK_WordLeft:
+ case CK_WordRight:
+ case CK_MarkToWordBegin:
+ case CK_MarkToWordEnd:
+ case CK_MarkUp:
+ case CK_MarkDown:
+ case CK_MarkColumnUp:
+ case CK_MarkColumnDown:
+ if (edit->mark2 == -1)
+ break; /*marking is following the cursor: may need to highlight a whole line */
+ MC_FALLTHROUGH;
+ case CK_Left:
+ case CK_Right:
+ case CK_MarkLeft:
+ case CK_MarkRight:
+ edit->force |= REDRAW_CHAR_ONLY;
+ break;
+ default:
+ break;
+ }
+
+ /* basic cursor key commands */
+ switch (command)
+ {
+ case CK_BackSpace:
+ /* if non persistent selection and text selected */
+ if (!edit_options.persistent_selections && edit->mark1 != edit->mark2)
+ edit_block_delete_cmd (edit);
+ else if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit->over_col--;
+ else if (edit_options.backspace_through_tabs && is_in_indent (&edit->buffer))
+ {
+ while (edit_buffer_get_previous_byte (&edit->buffer) != '\n' && edit->buffer.curs1 > 0)
+ edit_backspace (edit, TRUE);
+ }
+ else if (edit_options.fake_half_tabs && is_in_indent (&edit->buffer)
+ && right_of_four_spaces (edit))
+ {
+ int i;
+
+ for (i = 0; i < HALF_TAB_SIZE; i++)
+ edit_backspace (edit, TRUE);
+ }
+ else
+ edit_backspace (edit, FALSE);
+ break;
+ case CK_Delete:
+ /* if non persistent selection and text selected */
+ if (!edit_options.persistent_selections && edit->mark1 != edit->mark2)
+ edit_block_delete_cmd (edit);
+ else
+ {
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit_insert_over (edit);
+
+ if (edit_options.fake_half_tabs && is_in_indent (&edit->buffer)
+ && left_of_four_spaces (edit))
+ {
+ int i;
+
+ for (i = 1; i <= HALF_TAB_SIZE; i++)
+ edit_delete (edit, TRUE);
+ }
+ else
+ edit_delete (edit, FALSE);
+ }
+ break;
+ case CK_DeleteToWordBegin:
+ edit->over_col = 0;
+ edit_left_delete_word (edit);
+ break;
+ case CK_DeleteToWordEnd:
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit_insert_over (edit);
+
+ edit_right_delete_word (edit);
+ break;
+ case CK_DeleteLine:
+ edit_delete_line (edit);
+ break;
+ case CK_DeleteToHome:
+ edit_delete_to_line_begin (edit);
+ break;
+ case CK_DeleteToEnd:
+ edit_delete_to_line_end (edit);
+ break;
+ case CK_Enter:
+ edit->over_col = 0;
+ if (edit_options.auto_para_formatting)
+ {
+ edit_double_newline (edit);
+ if (edit_options.return_does_auto_indent && !bracketed_pasting_in_progress)
+ edit_auto_indent (edit);
+ format_paragraph (edit, FALSE);
+ }
+ else
+ {
+ edit_insert (edit, '\n');
+ if (edit_options.return_does_auto_indent && !bracketed_pasting_in_progress)
+ edit_auto_indent (edit);
+ }
+ break;
+ case CK_Return:
+ edit_insert (edit, '\n');
+ break;
+
+ case CK_MarkColumnPageUp:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_PageUp:
+ case CK_MarkPageUp:
+ edit_move_up (edit, w->lines - 1, TRUE);
+ break;
+ case CK_MarkColumnPageDown:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_PageDown:
+ case CK_MarkPageDown:
+ edit_move_down (edit, w->lines - 1, TRUE);
+ break;
+ case CK_MarkColumnLeft:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_Left:
+ case CK_MarkLeft:
+ if (edit_options.fake_half_tabs && is_in_indent (&edit->buffer)
+ && right_of_four_spaces (edit))
+ {
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit->over_col--;
+ else
+ edit_cursor_move (edit, -HALF_TAB_SIZE);
+ edit->force &= (0xFFF - REDRAW_CHAR_ONLY);
+ }
+ else
+ edit_left_char_move_cmd (edit);
+ break;
+ case CK_MarkColumnRight:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_Right:
+ case CK_MarkRight:
+ if (edit_options.fake_half_tabs && is_in_indent (&edit->buffer)
+ && left_of_four_spaces (edit))
+ {
+ edit_cursor_move (edit, HALF_TAB_SIZE);
+ edit->force &= (0xFFF - REDRAW_CHAR_ONLY);
+ }
+ else
+ edit_right_char_move_cmd (edit);
+ break;
+ case CK_TopOnScreen:
+ case CK_MarkToPageBegin:
+ edit_begin_page (edit);
+ break;
+ case CK_BottomOnScreen:
+ case CK_MarkToPageEnd:
+ edit_end_page (edit);
+ break;
+ case CK_WordLeft:
+ case CK_MarkToWordBegin:
+ edit->over_col = 0;
+ edit_left_word_move_cmd (edit);
+ break;
+ case CK_WordRight:
+ case CK_MarkToWordEnd:
+ edit->over_col = 0;
+ edit_right_word_move_cmd (edit);
+ break;
+ case CK_MarkColumnUp:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_Up:
+ case CK_MarkUp:
+ edit_move_up (edit, 1, FALSE);
+ break;
+ case CK_MarkColumnDown:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_Down:
+ case CK_MarkDown:
+ edit_move_down (edit, 1, FALSE);
+ break;
+ case CK_MarkColumnParagraphUp:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_ParagraphUp:
+ case CK_MarkParagraphUp:
+ edit_move_up_paragraph (edit, FALSE);
+ break;
+ case CK_MarkColumnParagraphDown:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_ParagraphDown:
+ case CK_MarkParagraphDown:
+ edit_move_down_paragraph (edit, FALSE);
+ break;
+ case CK_MarkColumnScrollUp:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_ScrollUp:
+ case CK_MarkScrollUp:
+ edit_move_up (edit, 1, TRUE);
+ break;
+ case CK_MarkColumnScrollDown:
+ edit->column_highlight = 1;
+ MC_FALLTHROUGH;
+ case CK_ScrollDown:
+ case CK_MarkScrollDown:
+ edit_move_down (edit, 1, TRUE);
+ break;
+ case CK_Home:
+ case CK_MarkToHome:
+ edit_cursor_to_bol (edit);
+ break;
+ case CK_End:
+ case CK_MarkToEnd:
+ edit_cursor_to_eol (edit);
+ break;
+ case CK_Tab:
+ /* if text marked shift block */
+ if (edit->mark1 != edit->mark2 && !edit_options.persistent_selections)
+ {
+ if (edit->mark2 < 0)
+ edit_mark_cmd (edit, FALSE);
+ edit_move_block_to_right (edit);
+ }
+ else
+ {
+ if (edit_options.cursor_beyond_eol)
+ edit_insert_over (edit);
+ edit_tab_cmd (edit);
+ if (edit_options.auto_para_formatting)
+ {
+ format_paragraph (edit, FALSE);
+ edit->force |= REDRAW_PAGE;
+ }
+ else
+ check_and_wrap_line (edit);
+ }
+ break;
+
+ case CK_InsertOverwrite:
+ edit->overwrite = !edit->overwrite;
+ break;
+
+ case CK_Mark:
+ if (edit->mark2 >= 0)
+ {
+ if (edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_ON);
+ edit->column_highlight = 0;
+ }
+ edit_mark_cmd (edit, FALSE);
+ break;
+ case CK_MarkColumn:
+ if (!edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_OFF);
+ edit->column_highlight = 1;
+ edit_mark_cmd (edit, FALSE);
+ break;
+ case CK_MarkAll:
+ edit_set_markers (edit, 0, edit->buffer.size, 0, 0);
+ edit->force |= REDRAW_PAGE;
+ break;
+ case CK_Unmark:
+ if (edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_ON);
+ edit->column_highlight = 0;
+ edit_mark_cmd (edit, TRUE);
+ break;
+ case CK_MarkWord:
+ if (edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_ON);
+ edit->column_highlight = 0;
+ edit_mark_current_word_cmd (edit);
+ break;
+ case CK_MarkLine:
+ if (edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_ON);
+ edit->column_highlight = 0;
+ edit_mark_current_line_cmd (edit);
+ break;
+
+ case CK_Bookmark:
+ book_mark_clear (edit, edit->buffer.curs_line, BOOK_MARK_FOUND_COLOR);
+ if (book_mark_query_color (edit, edit->buffer.curs_line, BOOK_MARK_COLOR))
+ book_mark_clear (edit, edit->buffer.curs_line, BOOK_MARK_COLOR);
+ else
+ book_mark_insert (edit, edit->buffer.curs_line, BOOK_MARK_COLOR);
+ break;
+ case CK_BookmarkFlush:
+ book_mark_flush (edit, BOOK_MARK_COLOR);
+ book_mark_flush (edit, BOOK_MARK_FOUND_COLOR);
+ edit->force |= REDRAW_PAGE;
+ break;
+ case CK_BookmarkNext:
+ if (edit->book_mark != NULL)
+ {
+ edit_book_mark_t *p;
+
+ p = book_mark_find (edit, edit->buffer.curs_line);
+ if (p->next != NULL)
+ {
+ p = p->next;
+ if (p->line >= edit->start_line + w->lines || p->line < edit->start_line)
+ edit_move_display (edit, p->line - w->lines / 2);
+ edit_move_to_line (edit, p->line);
+ }
+ }
+ break;
+ case CK_BookmarkPrev:
+ if (edit->book_mark != NULL)
+ {
+ edit_book_mark_t *p;
+
+ p = book_mark_find (edit, edit->buffer.curs_line);
+ while (p->line == edit->buffer.curs_line)
+ if (p->prev != NULL)
+ p = p->prev;
+ if (p->line >= 0)
+ {
+ if (p->line >= edit->start_line + w->lines || p->line < edit->start_line)
+ edit_move_display (edit, p->line - w->lines / 2);
+ edit_move_to_line (edit, p->line);
+ }
+ }
+ break;
+
+ case CK_Top:
+ case CK_MarkToFileBegin:
+ edit_move_to_top (edit);
+ break;
+ case CK_Bottom:
+ case CK_MarkToFileEnd:
+ edit_move_to_bottom (edit);
+ break;
+
+ case CK_Copy:
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit_insert_over (edit);
+ edit_block_copy_cmd (edit);
+ break;
+ case CK_Remove:
+ edit_block_delete_cmd (edit);
+ break;
+ case CK_Move:
+ edit_block_move_cmd (edit);
+ break;
+
+ case CK_BlockShiftLeft:
+ if (edit->mark1 != edit->mark2)
+ edit_move_block_to_left (edit);
+ break;
+ case CK_BlockShiftRight:
+ if (edit->mark1 != edit->mark2)
+ edit_move_block_to_right (edit);
+ break;
+ case CK_Store:
+ edit_copy_to_X_buf_cmd (edit);
+ break;
+ case CK_Cut:
+ edit_cut_to_X_buf_cmd (edit);
+ break;
+ case CK_Paste:
+ /* if non persistent selection and text selected */
+ if (!edit_options.persistent_selections && edit->mark1 != edit->mark2)
+ edit_block_delete_cmd (edit);
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit_insert_over (edit);
+ edit_paste_from_X_buf_cmd (edit);
+ if (!edit_options.persistent_selections && edit->mark2 >= 0)
+ {
+ if (edit->column_highlight)
+ edit_push_undo_action (edit, COLUMN_ON);
+ edit->column_highlight = 0;
+ edit_mark_cmd (edit, TRUE);
+ }
+ break;
+ case CK_History:
+ edit_paste_from_history (edit);
+ break;
+
+ case CK_SaveAs:
+ edit_save_as_cmd (edit);
+ break;
+ case CK_Save:
+ edit_save_confirm_cmd (edit);
+ break;
+ case CK_BlockSave:
+ edit_save_block_cmd (edit);
+ break;
+ case CK_InsertFile:
+ edit_insert_file_cmd (edit);
+ break;
+
+ case CK_FilePrev:
+ edit_load_back_cmd (edit);
+ break;
+ case CK_FileNext:
+ edit_load_forward_cmd (edit);
+ break;
+
+ case CK_SyntaxChoose:
+ edit_syntax_dialog (edit);
+ break;
+
+ case CK_Search:
+ edit_search_cmd (edit, FALSE);
+ break;
+ case CK_SearchContinue:
+ edit_search_cmd (edit, TRUE);
+ break;
+ case CK_Replace:
+ edit_replace_cmd (edit, FALSE);
+ break;
+ case CK_ReplaceContinue:
+ edit_replace_cmd (edit, TRUE);
+ break;
+ case CK_Complete:
+ /* if text marked shift block */
+ if (edit->mark1 != edit->mark2 && !edit_options.persistent_selections)
+ edit_move_block_to_left (edit);
+ else
+ edit_complete_word_cmd (edit);
+ break;
+ case CK_Find:
+ edit_get_match_keyword_cmd (edit);
+ break;
+
+#ifdef HAVE_ASPELL
+ case CK_SpellCheckCurrentWord:
+ edit_suggest_current_word (edit);
+ break;
+ case CK_SpellCheck:
+ edit_spellcheck_file (edit);
+ break;
+ case CK_SpellCheckSelectLang:
+ edit_set_spell_lang ();
+ break;
+#endif
+
+ case CK_Date:
+ {
+ char s[BUF_MEDIUM];
+ /* fool gcc to prevent a Y2K warning */
+ char time_format[] = "_c";
+ time_format[0] = '%';
+
+ FMT_LOCALTIME_CURRENT (s, sizeof (s), time_format);
+ edit_print_string (edit, s);
+ edit->force |= REDRAW_PAGE;
+ }
+ break;
+ case CK_Goto:
+ edit_goto_cmd (edit);
+ break;
+ case CK_ParagraphFormat:
+ format_paragraph (edit, TRUE);
+ edit->force |= REDRAW_PAGE;
+ break;
+ case CK_MacroDelete:
+ edit_delete_macro_cmd (edit);
+ break;
+ case CK_MatchBracket:
+ edit_goto_matching_bracket (edit);
+ break;
+ case CK_UserMenu:
+ user_menu (edit, NULL, -1);
+ break;
+ case CK_Sort:
+ edit_sort_cmd (edit);
+ break;
+ case CK_ExternalCommand:
+ edit_ext_cmd (edit);
+ break;
+ case CK_EditMail:
+ edit_mail_dialog (edit);
+ break;
+#ifdef HAVE_CHARSET
+ case CK_SelectCodepage:
+ edit_select_codepage_cmd (edit);
+ break;
+#endif
+ case CK_InsertLiteral:
+ edit_insert_literal_cmd (edit);
+ break;
+ case CK_MacroStartStopRecord:
+ edit_begin_end_macro_cmd (edit);
+ break;
+ case CK_RepeatStartStopRecord:
+ edit_begin_end_repeat_cmd (edit);
+ break;
+ case CK_ExtendedKeyMap:
+ WIDGET (edit)->ext_mode = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ /* CK_PipeBlock */
+ if ((command / CK_PipeBlock (0)) == 1)
+ edit_block_process_cmd (edit, command - CK_PipeBlock (0));
+
+ /* keys which must set the col position, and the search vars */
+ switch (command)
+ {
+ case CK_Search:
+ case CK_SearchContinue:
+ case CK_Replace:
+ case CK_ReplaceContinue:
+ case CK_Complete:
+ edit->prev_col = edit_get_col (edit);
+ break;
+ case CK_Up:
+ case CK_MarkUp:
+ case CK_MarkColumnUp:
+ case CK_Down:
+ case CK_MarkDown:
+ case CK_MarkColumnDown:
+ case CK_PageUp:
+ case CK_MarkPageUp:
+ case CK_MarkColumnPageUp:
+ case CK_PageDown:
+ case CK_MarkPageDown:
+ case CK_MarkColumnPageDown:
+ case CK_Top:
+ case CK_MarkToFileBegin:
+ case CK_Bottom:
+ case CK_MarkToFileEnd:
+ case CK_ParagraphUp:
+ case CK_MarkParagraphUp:
+ case CK_MarkColumnParagraphUp:
+ case CK_ParagraphDown:
+ case CK_MarkParagraphDown:
+ case CK_MarkColumnParagraphDown:
+ case CK_ScrollUp:
+ case CK_MarkScrollUp:
+ case CK_MarkColumnScrollUp:
+ case CK_ScrollDown:
+ case CK_MarkScrollDown:
+ case CK_MarkColumnScrollDown:
+ edit->search_start = edit->buffer.curs1;
+ edit->found_len = 0;
+ break;
+ default:
+ edit->found_len = 0;
+ edit->prev_col = edit_get_col (edit);
+ edit->search_start = edit->buffer.curs1;
+ }
+ edit_find_bracket (edit);
+
+ if (edit_options.auto_para_formatting)
+ {
+ switch (command)
+ {
+ case CK_BackSpace:
+ case CK_Delete:
+ case CK_DeleteToWordBegin:
+ case CK_DeleteToWordEnd:
+ case CK_DeleteToHome:
+ case CK_DeleteToEnd:
+ format_paragraph (edit, FALSE);
+ edit->force |= REDRAW_PAGE;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_stack_init (void)
+{
+ for (edit_stack_iterator = 0; edit_stack_iterator < MAX_HISTORY_MOVETO; edit_stack_iterator++)
+ {
+ edit_history_moveto[edit_stack_iterator].filename_vpath = NULL;
+ edit_history_moveto[edit_stack_iterator].line = -1;
+ }
+
+ edit_stack_iterator = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_stack_free (void)
+{
+ for (edit_stack_iterator = 0; edit_stack_iterator < MAX_HISTORY_MOVETO; edit_stack_iterator++)
+ vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** move i lines */
+
+void
+edit_move_up (WEdit * edit, long i, gboolean do_scroll)
+{
+ edit_move_updown (edit, i, do_scroll, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** move i lines */
+
+void
+edit_move_down (WEdit * edit, long i, gboolean do_scroll)
+{
+ edit_move_updown (edit, i, do_scroll, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/edit.h b/src/editor/edit.h
new file mode 100644
index 0000000..358aa3f
--- /dev/null
+++ b/src/editor/edit.h
@@ -0,0 +1,84 @@
+/*
+ Editor public API
+ */
+
+/** \file edit.h
+ * \brief Header: editor public API
+ * \author Paul Sheer
+ * \date 1996, 1997
+ * \author Andrew Borodin
+ * \date 2009, 2012
+ */
+
+#ifndef MC__EDIT_H
+#define MC__EDIT_H
+
+#include "lib/global.h" /* PATH_SEP_STR */
+#include "lib/vfs/vfs.h" /* vfs_path_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define DEFAULT_WRAP_LINE_LENGTH 72
+
+#define EDIT(x) ((WEdit *)(x))
+#define CONST_EDIT(x) ((const WEdit *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* Editor widget */
+struct WEdit;
+typedef struct WEdit WEdit;
+
+typedef struct
+{
+ int word_wrap_line_length;
+ gboolean typewriter_wrap;
+ gboolean auto_para_formatting;
+ gboolean fill_tabs_with_spaces;
+ gboolean return_does_auto_indent;
+ gboolean backspace_through_tabs;
+ gboolean fake_half_tabs;
+ gboolean persistent_selections;
+ gboolean drop_selection_on_copy; /* whether we need to drop selection on copy to buffer */
+ gboolean cursor_beyond_eol;
+ gboolean cursor_after_inserted_block;
+ gboolean state_full_filename;
+ gboolean line_state;
+ int line_state_width;
+ int save_mode;
+ gboolean confirm_save; /* queries on a save */
+ gboolean save_position;
+ gboolean syntax_highlighting;
+ gboolean group_undo;
+ char *backup_ext;
+ char *filesize_threshold;
+ char *stop_format_chars;
+ gboolean visible_tabs;
+ gboolean visible_tws;
+ gboolean show_right_margin;
+ gboolean simple_statusbar; /* statusbar draw style */
+ gboolean check_nl_at_eof;
+} edit_options_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern edit_options_t edit_options;
+
+/*** declarations of public functions ************************************************************/
+
+/* used in main() */
+void edit_stack_init (void);
+void edit_stack_free (void);
+
+gboolean edit_file (const vfs_path_t * file_vpath, long line);
+gboolean edit_files (const GList * files);
+
+const char *edit_get_file_name (const WEdit * edit);
+off_t edit_get_cursor_offset (const WEdit * edit);
+long edit_get_curs_col (const WEdit * edit);
+const char *edit_get_syntax_type (const WEdit * edit);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__EDIT_H */
diff --git a/src/editor/editbuffer.c b/src/editor/editbuffer.c
new file mode 100644
index 0000000..24bc7ee
--- /dev/null
+++ b/src/editor/editbuffer.c
@@ -0,0 +1,900 @@
+/*
+ Editor text keep buffer.
+
+ Copyright (C) 2013-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru> 2013
+
+ 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
+ * \brief Source: editor text keep buffer.
+ * \author Andrew Borodin
+ * \date 2013
+ */
+
+#include <config.h>
+
+#include <ctype.h> /* isdigit() */
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+
+#include "lib/vfs/vfs.h"
+
+#include "edit-impl.h"
+#include "editbuffer.h"
+
+/* --------------------------------------------------------------------------------------------- */
+/*-
+ *
+ * here's a quick sketch of the layout: (don't run this through indent.)
+ *
+ * |
+ * \0\0\0\0\0m e _ f i l e . \nf i n . \n|T h i s _ i s _ s o\0\0\0\0\0\0\0\0\0
+ * ______________________________________|______________________________________
+ * |
+ * ... | b2[2] | b2[1] | b2[0] | b1[0] | b1[1] | b1[2] | ...
+ * |-> |-> |-> |-> |-> |-> |
+ * |
+ * _<------------------------->|<----------------->_
+ * curs2 | curs1
+ * ^ | ^
+ * | ^|^ |
+ * cursor ||| cursor
+ * |||
+ * file end|||file beginning
+ * |
+ * |
+ *
+ * _
+ * This_is_some_file
+ * fin.
+ *
+ *
+ * This is called a "gap buffer".
+ * See also:
+ * http://en.wikipedia.org/wiki/Gap_buffer
+ * http://stackoverflow.com/questions/4199694/data-structure-for-text-editor
+ */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*
+ * The editor keeps data in two arrays of buffers.
+ * All buffers have the same size, which must be a power of 2.
+ */
+
+/* Configurable: log2 of the buffer size in bytes */
+#ifndef S_EDIT_BUF_SIZE
+#define S_EDIT_BUF_SIZE 16
+#endif
+
+/* Size of the buffer */
+#define EDIT_BUF_SIZE (((off_t) 1) << S_EDIT_BUF_SIZE)
+
+/* Buffer mask (used to find cursor position relative to the buffer) */
+#define M_EDIT_BUF_SIZE (EDIT_BUF_SIZE - 1)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get pointer to byte at specified index
+ *
+ * @param buf pointer to editor buffer
+ * @param byte_index byte index
+ *
+ * @return NULL if byte_index is negative or larger than file size; pointer to byte otherwise.
+ */
+static char *
+edit_buffer_get_byte_ptr (const edit_buffer_t * buf, off_t byte_index)
+{
+ void *b;
+
+ if (byte_index >= (buf->curs1 + buf->curs2) || byte_index < 0)
+ return NULL;
+
+ if (byte_index >= buf->curs1)
+ {
+ off_t p;
+
+ p = buf->curs1 + buf->curs2 - byte_index - 1;
+ b = g_ptr_array_index (buf->b2, p >> S_EDIT_BUF_SIZE);
+ return (char *) b + EDIT_BUF_SIZE - 1 - (p & M_EDIT_BUF_SIZE);
+ }
+
+ b = g_ptr_array_index (buf->b1, byte_index >> S_EDIT_BUF_SIZE);
+ return (char *) b + (byte_index & M_EDIT_BUF_SIZE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Initialize editor buffers.
+ *
+ * @param buf pointer to editor buffer
+ */
+
+void
+edit_buffer_init (edit_buffer_t * buf, off_t size)
+{
+ buf->b1 = g_ptr_array_new_full (32, g_free);
+ buf->b2 = g_ptr_array_new_full (32, g_free);
+
+ buf->curs1 = 0;
+ buf->curs2 = 0;
+
+ buf->size = size;
+ buf->lines = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Clean editor buffers.
+ *
+ * @param buf pointer to editor buffer
+ */
+
+void
+edit_buffer_clean (edit_buffer_t * buf)
+{
+ if (buf->b1 != NULL)
+ g_ptr_array_free (buf->b1, TRUE);
+
+ if (buf->b2 != NULL)
+ g_ptr_array_free (buf->b2, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get byte at specified index
+ *
+ * @param buf pointer to editor buffer
+ * @param byte_index byte index
+ *
+ * @return '\n' if byte_index is negative or larger than file size; byte at byte_index otherwise.
+ */
+
+int
+edit_buffer_get_byte (const edit_buffer_t * buf, off_t byte_index)
+{
+ char *p;
+
+ p = edit_buffer_get_byte_ptr (buf, byte_index);
+
+ return (p != NULL) ? *(unsigned char *) p : '\n';
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+/**
+ * Get utf-8 symbol at specified index
+ *
+ * @param buf pointer to editor buffer
+ * @param byte_index byte index
+ * @param char_length length of returned symbol
+ *
+ * @return '\n' if byte_index is negative or larger than file size;
+ * 0 if utf-8 symbol at specified index is invalid;
+ * utf-8 symbol otherwise
+ */
+
+int
+edit_buffer_get_utf (const edit_buffer_t * buf, off_t byte_index, int *char_length)
+{
+ gchar *str = NULL;
+ gunichar res;
+ gunichar ch;
+ gchar *next_ch = NULL;
+
+ if (byte_index >= (buf->curs1 + buf->curs2) || byte_index < 0)
+ {
+ *char_length = 0;
+ return '\n';
+ }
+
+ str = edit_buffer_get_byte_ptr (buf, byte_index);
+ if (str == NULL)
+ {
+ *char_length = 0;
+ return 0;
+ }
+
+ res = g_utf8_get_char_validated (str, -1);
+ if (res == (gunichar) (-2) || res == (gunichar) (-1))
+ {
+ /* Retry with explicit bytes to make sure it's not a buffer boundary */
+ size_t i;
+ gchar utf8_buf[UTF8_CHAR_LEN + 1];
+
+ for (i = 0; i < UTF8_CHAR_LEN; i++)
+ utf8_buf[i] = edit_buffer_get_byte (buf, byte_index + i);
+ utf8_buf[i] = '\0';
+ res = g_utf8_get_char_validated (utf8_buf, -1);
+ }
+
+ if (res == (gunichar) (-2) || res == (gunichar) (-1))
+ {
+ ch = *str;
+ *char_length = 0;
+ }
+ else
+ {
+ ch = res;
+ /* Calculate UTF-8 char length */
+ next_ch = g_utf8_next_char (str);
+ *char_length = next_ch - str;
+ }
+
+ return (int) ch;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get utf-8 symbol before specified index
+ *
+ * @param buf pointer to editor buffer
+ * @param byte_index byte index
+ * @param char_length length of returned symbol
+ *
+ * @return 0 if byte_index is negative or larger than file size;
+ * 1-byte value before specified index if utf-8 symbol before specified index is invalid;
+ * utf-8 symbol otherwise
+ */
+
+int
+edit_buffer_get_prev_utf (const edit_buffer_t * buf, off_t byte_index, int *char_length)
+{
+ size_t i;
+ gchar utf8_buf[3 * UTF8_CHAR_LEN + 1];
+ gchar *str;
+ gchar *cursor_buf_ptr;
+ gunichar res;
+
+ if (byte_index > (buf->curs1 + buf->curs2) || byte_index <= 0)
+ {
+ *char_length = 0;
+ return 0;
+ }
+
+ for (i = 0; i < (3 * UTF8_CHAR_LEN); i++)
+ utf8_buf[i] = edit_buffer_get_byte (buf, byte_index + i - (2 * UTF8_CHAR_LEN));
+ utf8_buf[i] = '\0';
+
+ cursor_buf_ptr = utf8_buf + (2 * UTF8_CHAR_LEN);
+ str = g_utf8_find_prev_char (utf8_buf, cursor_buf_ptr);
+
+ if (str == NULL || g_utf8_next_char (str) != cursor_buf_ptr)
+ {
+ *char_length = 1;
+ return *(cursor_buf_ptr - 1);
+ }
+
+ res = g_utf8_get_char_validated (str, -1);
+ if (res == (gunichar) (-2) || res == (gunichar) (-1))
+ {
+ *char_length = 1;
+ return *(cursor_buf_ptr - 1);
+ }
+
+ *char_length = cursor_buf_ptr - str;
+ return (int) res;
+}
+#endif /* HAVE_CHARSET */
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Count lines in editor buffer.
+ *
+ * @param buf editor buffer
+ * @param first start byte offset
+ * @param last finish byte offset
+ *
+ * @return line numbers between "first" and "last" bytes
+ */
+
+long
+edit_buffer_count_lines (const edit_buffer_t * buf, off_t first, off_t last)
+{
+ long lines = 0;
+
+ first = MAX (first, 0);
+ last = MIN (last, buf->size);
+
+ while (first < last)
+ if (edit_buffer_get_byte (buf, first++) == '\n')
+ lines++;
+
+ return lines;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get "begin-of-line" offset of line contained specified byte offset
+ *
+ * @param buf editor buffer
+ * @param current byte offset
+ *
+ * @return index of first char of line
+ */
+
+off_t
+edit_buffer_get_bol (const edit_buffer_t * buf, off_t current)
+{
+ if (current <= 0)
+ return 0;
+
+ for (; edit_buffer_get_byte (buf, current - 1) != '\n'; current--)
+ ;
+
+ return current;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get "end-of-line" offset of line contained specified byte offset
+ *
+ * @param buf editor buffer
+ * @param current byte offset
+ *
+ * @return index of last char of line + 1
+ */
+
+off_t
+edit_buffer_get_eol (const edit_buffer_t * buf, off_t current)
+{
+ if (current >= buf->size)
+ return buf->size;
+
+ for (; edit_buffer_get_byte (buf, current) != '\n'; current++)
+ ;
+
+ return current;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get word from specified offset.
+ *
+ * @param buf editor buffer
+ * @param current start_pos offset
+ * @param start actual start word ofset
+ * @param cut
+ *
+ * @return word as newly allocated object
+ */
+
+GString *
+edit_buffer_get_word_from_pos (const edit_buffer_t * buf, off_t start_pos, off_t * start,
+ gsize * cut)
+{
+ off_t word_start;
+ gsize cut_len = 0;
+ GString *match_expr;
+ int c1, c2;
+
+ for (word_start = start_pos; word_start != 0; word_start--, cut_len++)
+ {
+ c1 = edit_buffer_get_byte (buf, word_start);
+ c2 = edit_buffer_get_byte (buf, word_start - 1);
+
+ if (is_break_char (c1) != is_break_char (c2) || c1 == '\n' || c2 == '\n')
+ break;
+ }
+
+ match_expr = g_string_sized_new (16);
+
+ do
+ {
+ c1 = edit_buffer_get_byte (buf, word_start + match_expr->len);
+ c2 = edit_buffer_get_byte (buf, word_start + match_expr->len + 1);
+ g_string_append_c (match_expr, c1);
+ }
+ while (!(is_break_char (c1) != is_break_char (c2) || c1 == '\n' || c2 == '\n'));
+
+ *start = word_start;
+ *cut = cut_len;
+
+ return match_expr;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Find first character of current word
+ *
+ * @param buf editor buffer
+ * @param word_start position of first character of current word
+ * @param word_len length of current word
+ *
+ * @return TRUE if first character of word is found and this character is not 1) a digit and
+ * 2) a begin of file, FALSE otherwise
+ */
+
+gboolean
+edit_buffer_find_word_start (const edit_buffer_t * buf, off_t * word_start, gsize * word_len)
+{
+ int c;
+ off_t i;
+
+ /* return if at begin of file */
+ if (buf->curs1 <= 0)
+ return FALSE;
+
+ c = edit_buffer_get_previous_byte (buf);
+ /* return if not at end or in word */
+ if (is_break_char (c))
+ return FALSE;
+
+ /* search start of word */
+ for (i = 1;; i++)
+ {
+ int last;
+
+ last = c;
+ c = edit_buffer_get_byte (buf, buf->curs1 - i - 1);
+
+ if (is_break_char (c))
+ {
+ /* return if word starts with digit */
+ if (isdigit (last))
+ return FALSE;
+
+ break;
+ }
+ }
+
+ /* success */
+ *word_start = buf->curs1 - i; /* start found */
+ *word_len = (gsize) i;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Basic low level single character buffer alterations and movements at the cursor: insert character
+ * at the cursor position and move right.
+ *
+ * @param buf pointer to editor buffer
+ * @param c character to insert
+ */
+
+void
+edit_buffer_insert (edit_buffer_t * buf, int c)
+{
+ void *b;
+ off_t i;
+
+ i = buf->curs1 & M_EDIT_BUF_SIZE;
+
+ /* add a new buffer if we've reached the end of the last one */
+ if (i == 0)
+ g_ptr_array_add (buf->b1, g_malloc0 (EDIT_BUF_SIZE));
+
+ /* perform the insertion */
+ b = g_ptr_array_index (buf->b1, buf->curs1 >> S_EDIT_BUF_SIZE);
+ *((unsigned char *) b + i) = (unsigned char) c;
+
+ /* update cursor position */
+ buf->curs1++;
+
+ /* update file length */
+ buf->size++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Basic low level single character buffer alterations and movements at the cursor: insert character
+ * at the cursor position and move left.
+ *
+ * @param buf pointer to editor buffer
+ * @param c character to insert
+ */
+
+void
+edit_buffer_insert_ahead (edit_buffer_t * buf, int c)
+{
+ void *b;
+ off_t i;
+
+ i = buf->curs2 & M_EDIT_BUF_SIZE;
+
+ /* add a new buffer if we've reached the end of the last one */
+ if (i == 0)
+ g_ptr_array_add (buf->b2, g_malloc0 (EDIT_BUF_SIZE));
+
+ /* perform the insertion */
+ b = g_ptr_array_index (buf->b2, buf->curs2 >> S_EDIT_BUF_SIZE);
+ *((unsigned char *) b + EDIT_BUF_SIZE - 1 - i) = (unsigned char) c;
+
+ /* update cursor position */
+ buf->curs2++;
+
+ /* update file length */
+ buf->size++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Basic low level single character buffer alterations and movements at the cursor: delete character
+ * at the cursor position.
+ *
+ * @param buf pointer to editor buffer
+ * @param c character to insert
+ */
+
+int
+edit_buffer_delete (edit_buffer_t * buf)
+{
+ void *b;
+ unsigned char c;
+ off_t prev;
+ off_t i;
+
+ prev = buf->curs2 - 1;
+
+ b = g_ptr_array_index (buf->b2, prev >> S_EDIT_BUF_SIZE);
+ i = prev & M_EDIT_BUF_SIZE;
+ c = *((unsigned char *) b + EDIT_BUF_SIZE - 1 - i);
+
+ if (i == 0)
+ {
+ guint j;
+
+ j = buf->b2->len - 1;
+ b = g_ptr_array_index (buf->b2, j);
+ g_ptr_array_remove_index (buf->b2, j);
+ }
+
+ buf->curs2 = prev;
+
+ /* update file length */
+ buf->size--;
+
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Basic low level single character buffer alterations and movements at the cursor: delete character
+ * before the cursor position and move left.
+ *
+ * @param buf pointer to editor buffer
+ * @param c character to insert
+ */
+
+int
+edit_buffer_backspace (edit_buffer_t * buf)
+{
+ void *b;
+ unsigned char c;
+ off_t prev;
+ off_t i;
+
+ prev = buf->curs1 - 1;
+
+ b = g_ptr_array_index (buf->b1, prev >> S_EDIT_BUF_SIZE);
+ i = prev & M_EDIT_BUF_SIZE;
+ c = *((unsigned char *) b + i);
+
+ if (i == 0)
+ {
+ guint j;
+
+ j = buf->b1->len - 1;
+ b = g_ptr_array_index (buf->b1, j);
+ g_ptr_array_remove_index (buf->b1, j);
+ }
+
+ buf->curs1 = prev;
+
+ /* update file length */
+ buf->size--;
+
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculate forward offset with specified number of lines.
+ *
+ * @param buf editor buffer
+ * @param current current offset
+ * @param lines number of lines to move forward
+ * @param upto offset to count lines between current and upto.
+ *
+ * @return If lines is zero returns the count of lines from current to upto.
+ * If upto is zero returns offset of lines forward current.
+ * Else returns forward offset with specified number of lines
+ */
+
+off_t
+edit_buffer_get_forward_offset (const edit_buffer_t * buf, off_t current, long lines, off_t upto)
+{
+ if (upto != 0)
+ return (off_t) edit_buffer_count_lines (buf, current, upto);
+
+ lines = MAX (lines, 0);
+
+ while (lines-- != 0)
+ {
+ long next;
+
+ next = edit_buffer_get_eol (buf, current) + 1;
+ if (next > buf->size)
+ break;
+ current = next;
+ }
+
+ return current;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculate backward offset with specified number of lines.
+ *
+ * @param buf editor buffer
+ * @param current current offset
+ * @param lines number of lines to move backward
+ *
+ * @return backward offset with specified number of lines.
+ */
+
+off_t
+edit_buffer_get_backward_offset (const edit_buffer_t * buf, off_t current, long lines)
+{
+ lines = MAX (lines, 0);
+ current = edit_buffer_get_bol (buf, current);
+
+ while (lines-- != 0 && current != 0)
+ current = edit_buffer_get_bol (buf, current - 1);
+
+ return current;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load file into editor buffer
+ *
+ * @param buf pointer to editor buffer
+ * @param fd file descriptor
+ * @param size file size
+ *
+ * @return number of read bytes
+ */
+
+off_t
+edit_buffer_read_file (edit_buffer_t * buf, int fd, off_t size,
+ edit_buffer_read_file_status_msg_t * sm, gboolean * aborted)
+{
+ off_t ret = 0;
+ off_t i, j;
+ off_t data_size;
+ void *b;
+ status_msg_t *s = STATUS_MSG (sm);
+ unsigned short update_cnt = 0;
+
+ *aborted = FALSE;
+
+ buf->lines = 0;
+ buf->curs2 = size;
+ i = buf->curs2 >> S_EDIT_BUF_SIZE;
+
+ /* fill last part of b2 */
+ data_size = buf->curs2 & M_EDIT_BUF_SIZE;
+ if (data_size != 0)
+ {
+ b = g_malloc0 (EDIT_BUF_SIZE);
+ g_ptr_array_add (buf->b2, b);
+ b = (char *) b + EDIT_BUF_SIZE - data_size;
+ ret = mc_read (fd, b, data_size);
+
+ /* count lines */
+ for (j = 0; j < ret; j++)
+ if (*((char *) b + j) == '\n')
+ buf->lines++;
+
+ if (ret < 0 || ret != data_size)
+ return ret;
+ }
+
+ /* fulfill other parts of b2 from end to begin */
+ data_size = EDIT_BUF_SIZE;
+ for (--i; i >= 0; i--)
+ {
+ off_t sz;
+
+ b = g_malloc0 (data_size);
+ g_ptr_array_add (buf->b2, b);
+ sz = mc_read (fd, b, data_size);
+ if (sz >= 0)
+ ret += sz;
+
+ /* count lines */
+ for (j = 0; j < sz; j++)
+ if (*((char *) b + j) == '\n')
+ buf->lines++;
+
+ if (s != NULL && s->update != NULL)
+ {
+ update_cnt = (update_cnt + 1) & 0xf;
+ if (update_cnt == 0)
+ {
+ /* FIXME: overcare */
+ if (sm->buf == NULL)
+ sm->buf = buf;
+
+ sm->loaded = ret;
+ if (s->update (s) == B_CANCEL)
+ {
+ *aborted = TRUE;
+ return (-1);
+ }
+ }
+ }
+
+ if (sz != data_size)
+ break;
+ }
+
+ /* reverse buffer */
+ for (i = 0; i < (off_t) buf->b2->len / 2; i++)
+ {
+ void **b1, **b2;
+
+ b1 = &g_ptr_array_index (buf->b2, i);
+ b2 = &g_ptr_array_index (buf->b2, buf->b2->len - 1 - i);
+
+ b = *b1;
+ *b1 = *b2;
+ *b2 = b;
+
+ if (s != NULL && s->update != NULL)
+ {
+ update_cnt = (update_cnt + 1) & 0xf;
+ if (update_cnt == 0)
+ {
+ sm->loaded = ret;
+ if (s->update (s) == B_CANCEL)
+ {
+ *aborted = TRUE;
+ return (-1);
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Write editor buffer content to file
+ *
+ * @param buf pointer to editor buffer
+ * @param fd file descriptor
+ *
+ * @return number of written bytes
+ */
+
+off_t
+edit_buffer_write_file (edit_buffer_t * buf, int fd)
+{
+ off_t ret = 0;
+ off_t i;
+ off_t data_size, sz;
+ void *b;
+
+ /* write all fulfilled parts of b1 from begin to end */
+ if (buf->b1->len != 0)
+ {
+ data_size = EDIT_BUF_SIZE;
+ for (i = 0; i < (off_t) buf->b1->len - 1; i++)
+ {
+ b = g_ptr_array_index (buf->b1, i);
+ sz = mc_write (fd, b, data_size);
+ if (sz >= 0)
+ ret += sz;
+ else if (i == 0)
+ ret = sz;
+ if (sz != data_size)
+ return ret;
+ }
+
+ /* write last partially filled part of b1 */
+ data_size = ((buf->curs1 - 1) & M_EDIT_BUF_SIZE) + 1;
+ b = g_ptr_array_index (buf->b1, i);
+ sz = mc_write (fd, b, data_size);
+ if (sz >= 0)
+ ret += sz;
+ if (sz != data_size)
+ return ret;
+ }
+
+ /* write b2 from end to begin, if b2 contains some data */
+ if (buf->b2->len != 0)
+ {
+ /* write last partially filled part of b2 */
+ i = buf->b2->len - 1;
+ b = g_ptr_array_index (buf->b2, i);
+ data_size = ((buf->curs2 - 1) & M_EDIT_BUF_SIZE) + 1;
+ sz = mc_write (fd, (char *) b + EDIT_BUF_SIZE - data_size, data_size);
+ if (sz >= 0)
+ ret += sz;
+
+ if (sz == data_size)
+ {
+ /* write other fulfilled parts of b2 from end to begin */
+ data_size = EDIT_BUF_SIZE;
+ while (--i >= 0)
+ {
+ b = g_ptr_array_index (buf->b2, i);
+ sz = mc_write (fd, b, data_size);
+ if (sz >= 0)
+ ret += sz;
+ if (sz != data_size)
+ break;
+ }
+ }
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculate percentage of specified character offset
+ *
+ * @param buf pointer to editor buffer
+ * @param p character offset
+ *
+ * @return percentage of specified character offset
+ */
+
+int
+edit_buffer_calc_percent (const edit_buffer_t * buf, off_t offset)
+{
+ int percent;
+
+ if (buf->size == 0)
+ percent = 0;
+ else if (offset >= buf->size)
+ percent = 100;
+ else if (offset > (INT_MAX / 100))
+ percent = offset / (buf->size / 100);
+ else
+ percent = offset * 100 / buf->size;
+
+ return percent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editbuffer.h b/src/editor/editbuffer.h
new file mode 100644
index 0000000..def17ee
--- /dev/null
+++ b/src/editor/editbuffer.h
@@ -0,0 +1,117 @@
+/** \file
+ * \brief Header: text keep buffer for WEdit
+ */
+
+#ifndef MC__EDIT_BUFFER_H
+#define MC__EDIT_BUFFER_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct edit_buffer_struct
+{
+ off_t curs1; /* position of the cursor from the beginning of the file. */
+ off_t curs2; /* position from the end of the file */
+ GPtrArray *b1; /* all data up to curs1 */
+ GPtrArray *b2; /* all data from end of file down to curs2 */
+ off_t size; /* file size */
+ long lines; /* total lines in the file */
+ long curs_line; /* line number of the cursor. */
+} edit_buffer_t;
+
+typedef struct edit_buffer_read_file_status_msg_struct
+{
+ simple_status_msg_t status_msg; /* base class */
+
+ gboolean first;
+ edit_buffer_t *buf;
+ off_t loaded;
+} edit_buffer_read_file_status_msg_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void edit_buffer_init (edit_buffer_t * buf, off_t size);
+void edit_buffer_clean (edit_buffer_t * buf);
+
+int edit_buffer_get_byte (const edit_buffer_t * buf, off_t byte_index);
+#ifdef HAVE_CHARSET
+int edit_buffer_get_utf (const edit_buffer_t * buf, off_t byte_index, int *char_length);
+int edit_buffer_get_prev_utf (const edit_buffer_t * buf, off_t byte_index, int *char_length);
+#endif
+long edit_buffer_count_lines (const edit_buffer_t * buf, off_t first, off_t last);
+off_t edit_buffer_get_bol (const edit_buffer_t * buf, off_t current);
+off_t edit_buffer_get_eol (const edit_buffer_t * buf, off_t current);
+GString *edit_buffer_get_word_from_pos (const edit_buffer_t * buf, off_t start_pos, off_t * start,
+ gsize * cut);
+gboolean edit_buffer_find_word_start (const edit_buffer_t * buf, off_t * word_start,
+ gsize * word_len);
+
+void edit_buffer_insert (edit_buffer_t * buf, int c);
+void edit_buffer_insert_ahead (edit_buffer_t * buf, int c);
+int edit_buffer_delete (edit_buffer_t * buf);
+int edit_buffer_backspace (edit_buffer_t * buf);
+
+off_t edit_buffer_get_forward_offset (const edit_buffer_t * buf, off_t current, long lines,
+ off_t upto);
+off_t edit_buffer_get_backward_offset (const edit_buffer_t * buf, off_t current, long lines);
+
+off_t edit_buffer_read_file (edit_buffer_t * buf, int fd, off_t size,
+ edit_buffer_read_file_status_msg_t * sm, gboolean * aborted);
+off_t edit_buffer_write_file (edit_buffer_t * buf, int fd);
+
+int edit_buffer_calc_percent (const edit_buffer_t * buf, off_t offset);
+
+/*** inline functions ****************************************************************************/
+
+static inline int
+edit_buffer_get_current_byte (const edit_buffer_t * buf)
+{
+ return edit_buffer_get_byte (buf, buf->curs1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+edit_buffer_get_previous_byte (const edit_buffer_t * buf)
+{
+ return edit_buffer_get_byte (buf, buf->curs1 - 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get "begin-of-line" offset of current line
+ *
+ * @param buf editor buffer
+ *
+ * @return index of first char of current line
+ */
+
+static inline off_t
+edit_buffer_get_current_bol (const edit_buffer_t * buf)
+{
+ return edit_buffer_get_bol (buf, buf->curs1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get "end-of-line" offset of current line
+ *
+ * @param buf editor buffer
+ *
+ * @return index of first char of current line + 1
+ */
+
+static inline off_t
+edit_buffer_get_current_eol (const edit_buffer_t * buf)
+{
+ return edit_buffer_get_eol (buf, buf->curs1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC__EDIT_BUFFER_H */
diff --git a/src/editor/editcmd.c b/src/editor/editcmd.c
new file mode 100644
index 0000000..de624f2
--- /dev/null
+++ b/src/editor/editcmd.c
@@ -0,0 +1,2108 @@
+/*
+ Editor high level editing commands
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1996, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2012-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2012
+
+ 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
+ * \brief Source: editor high level editing commands
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+/* #define PIPE_BLOCKS_SO_READ_BYTE_BY_BYTE */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* XCTRL */
+#include "lib/strutil.h" /* utf string functions */
+#include "lib/fileloc.h"
+#include "lib/lock.h"
+#include "lib/util.h" /* tilde_expand() */
+#include "lib/vfs/vfs.h"
+#include "lib/widget.h"
+#include "lib/event.h" /* mc_event_raise() */
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "src/history.h"
+#include "src/file_history.h" /* show_file_history() */
+#ifdef HAVE_CHARSET
+#include "src/selcodepage.h"
+#endif
+#include "src/util.h" /* check_for_default() */
+
+#include "edit-impl.h"
+#include "editwidget.h"
+#include "editsearch.h"
+#include "etags.h"
+
+/*** global variables ****************************************************************************/
+
+/* search and replace: */
+int search_create_bookmark = FALSE;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define space_width 1
+
+#define TEMP_BUF_LEN 1024
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static unsigned long edit_save_mode_radio_id, edit_save_mode_input_id;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+edit_save_mode_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_CHANGED_FOCUS:
+ if (sender != NULL && sender->id == edit_save_mode_radio_id)
+ {
+ Widget *ww;
+
+ ww = widget_find_by_id (w, edit_save_mode_input_id);
+ widget_disable (ww, RADIO (sender)->sel != 2);
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* If 0 (quick save) then a) create/truncate <filename> file,
+ b) save to <filename>;
+ if 1 (safe save) then a) save to <tempnam>,
+ b) rename <tempnam> to <filename>;
+ if 2 (do backups) then a) save to <tempnam>,
+ b) rename <filename> to <filename.backup_ext>,
+ c) rename <tempnam> to <filename>. */
+
+/* returns 0 on error, -1 on abort */
+
+static int
+edit_save_file (WEdit * edit, const vfs_path_t * filename_vpath)
+{
+ char *p;
+ gchar *tmp;
+ off_t filelen = 0;
+ int this_save_mode, rv, fd = -1;
+ vfs_path_t *real_filename_vpath;
+ vfs_path_t *savename_vpath = NULL;
+ const char *start_filename;
+ const vfs_path_element_t *vpath_element;
+ struct stat sb;
+
+ vpath_element = vfs_path_get_by_index (filename_vpath, 0);
+ if (vpath_element == NULL)
+ return 0;
+
+ start_filename = vpath_element->path;
+ if (*start_filename == '\0')
+ return 0;
+
+ if (!IS_PATH_SEP (*start_filename) && edit->dir_vpath != NULL)
+ real_filename_vpath = vfs_path_append_vpath_new (edit->dir_vpath, filename_vpath, NULL);
+ else
+ real_filename_vpath = vfs_path_clone (filename_vpath);
+
+ this_save_mode = edit_options.save_mode;
+ if (this_save_mode != EDIT_QUICK_SAVE)
+ {
+ if (!vfs_file_is_local (real_filename_vpath))
+ /* The file does not exists yet, so no safe save or backup are necessary. */
+ this_save_mode = EDIT_QUICK_SAVE;
+ else
+ {
+ fd = mc_open (real_filename_vpath, O_RDONLY | O_BINARY);
+ if (fd == -1)
+ /* The file does not exists yet, so no safe save or backup are necessary. */
+ this_save_mode = EDIT_QUICK_SAVE;
+ }
+
+ if (fd != -1)
+ mc_close (fd);
+ }
+
+ rv = mc_stat (real_filename_vpath, &sb);
+ if (rv == 0)
+ {
+ if (this_save_mode == EDIT_QUICK_SAVE && !edit->skip_detach_prompt && sb.st_nlink > 1)
+ {
+ rv = edit_query_dialog3 (_("Warning"),
+ _("File has hard-links. Detach before saving?"),
+ _("&Yes"), _("&No"), _("&Cancel"));
+ switch (rv)
+ {
+ case 0:
+ this_save_mode = EDIT_SAFE_SAVE;
+ MC_FALLTHROUGH;
+ case 1:
+ edit->skip_detach_prompt = 1;
+ break;
+ default:
+ vfs_path_free (real_filename_vpath, TRUE);
+ return -1;
+ }
+ }
+
+ /* Prevent overwriting changes from other editor sessions. */
+ if (edit->stat1.st_mtime != 0 && edit->stat1.st_mtime != sb.st_mtime)
+ {
+ /* The default action is "Cancel". */
+ query_set_sel (1);
+
+ rv = edit_query_dialog2 (_("Warning"),
+ _("The file has been modified in the meantime. Save anyway?"),
+ _("&Yes"), _("&Cancel"));
+ if (rv != 0)
+ {
+ vfs_path_free (real_filename_vpath, TRUE);
+ return -1;
+ }
+ }
+ }
+
+ if (this_save_mode == EDIT_QUICK_SAVE)
+ savename_vpath = vfs_path_clone (real_filename_vpath);
+ else
+ {
+ char *savedir, *saveprefix;
+
+ savedir = vfs_path_tokens_get (real_filename_vpath, 0, -1);
+ if (savedir == NULL)
+ savedir = g_strdup (".");
+
+ /* Token-related function never return leading slash, so we need add it manually */
+ saveprefix = mc_build_filename (PATH_SEP_STR, savedir, "cooledit", (char *) NULL);
+ g_free (savedir);
+ fd = mc_mkstemps (&savename_vpath, saveprefix, NULL);
+ g_free (saveprefix);
+ if (savename_vpath == NULL)
+ {
+ vfs_path_free (real_filename_vpath, TRUE);
+ return 0;
+ }
+ /* FIXME:
+ * Close for now because mc_mkstemps use pure open system call
+ * to create temporary file and it needs to be reopened by
+ * VFS-aware mc_open().
+ */
+ close (fd);
+ }
+
+ (void) mc_chown (savename_vpath, edit->stat1.st_uid, edit->stat1.st_gid);
+ (void) mc_chmod (savename_vpath, edit->stat1.st_mode);
+
+ fd = mc_open (savename_vpath, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, edit->stat1.st_mode);
+ if (fd == -1)
+ goto error_save;
+
+ /* pipe save */
+ p = edit_get_write_filter (savename_vpath, real_filename_vpath);
+ if (p != NULL)
+ {
+ FILE *file;
+
+ mc_close (fd);
+ file = (FILE *) popen (p, "w");
+
+ if (file != NULL)
+ {
+ filelen = edit_write_stream (edit, file);
+#if 1
+ pclose (file);
+#else
+ if (pclose (file) != 0)
+ {
+ tmp = g_strdup_printf (_("Error writing to pipe: %s"), p);
+ edit_error_dialog (_("Error"), tmp);
+ g_free (tmp);
+ g_free (p);
+ goto error_save;
+ }
+#endif
+ }
+ else
+ {
+ tmp = g_strdup_printf (_("Cannot open pipe for writing: %s"), p);
+ edit_error_dialog (_("Error"), get_sys_error (tmp));
+ g_free (p);
+ g_free (tmp);
+ goto error_save;
+ }
+ g_free (p);
+ }
+ else if (edit->lb == LB_ASIS)
+ { /* do not change line breaks */
+ filelen = edit_buffer_write_file (&edit->buffer, fd);
+
+ if (filelen != edit->buffer.size)
+ {
+ mc_close (fd);
+ goto error_save;
+ }
+
+ if (mc_close (fd) != 0)
+ goto error_save;
+
+ /* Update the file information, especially the mtime. */
+ if (mc_stat (savename_vpath, &edit->stat1) == -1)
+ goto error_save;
+ }
+ else
+ { /* change line breaks */
+ FILE *file;
+ const char *savename;
+
+ mc_close (fd);
+
+ savename = vfs_path_get_last_path_str (savename_vpath);
+ file = (FILE *) fopen (savename, "w");
+ if (file != NULL)
+ {
+ filelen = edit_write_stream (edit, file);
+ fclose (file);
+ }
+ else
+ {
+ char *msg;
+
+ msg = g_strdup_printf (_("Cannot open file for writing: %s"), savename);
+ edit_error_dialog (_("Error"), msg);
+ g_free (msg);
+ goto error_save;
+ }
+ }
+
+ if (filelen != edit->buffer.size)
+ goto error_save;
+
+ if (this_save_mode == EDIT_DO_BACKUP)
+ {
+ char *tmp_store_filename;
+ vfs_path_element_t *last_vpath_element;
+ vfs_path_t *tmp_vpath;
+ gboolean ok;
+
+ g_assert (edit_options.backup_ext != NULL);
+
+ /* add backup extension to the path */
+ tmp_vpath = vfs_path_clone (real_filename_vpath);
+ last_vpath_element = (vfs_path_element_t *) vfs_path_get_by_index (tmp_vpath, -1);
+ tmp_store_filename = last_vpath_element->path;
+ last_vpath_element->path =
+ g_strdup_printf ("%s%s", tmp_store_filename, edit_options.backup_ext);
+ g_free (tmp_store_filename);
+
+ ok = (mc_rename (real_filename_vpath, tmp_vpath) != -1);
+ vfs_path_free (tmp_vpath, TRUE);
+ if (!ok)
+ goto error_save;
+ }
+
+ if (this_save_mode != EDIT_QUICK_SAVE && mc_rename (savename_vpath, real_filename_vpath) == -1)
+ goto error_save;
+
+ vfs_path_free (real_filename_vpath, TRUE);
+ vfs_path_free (savename_vpath, TRUE);
+ return 1;
+ error_save:
+ /* FIXME: Is this safe ?
+ * if (this_save_mode != EDIT_QUICK_SAVE)
+ * mc_unlink (savename);
+ */
+ vfs_path_free (real_filename_vpath, TRUE);
+ vfs_path_free (savename_vpath, TRUE);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+edit_check_newline (const edit_buffer_t * buf)
+{
+ return !(edit_options.check_nl_at_eof && buf->size > 0
+ && edit_buffer_get_byte (buf, buf->size - 1) != '\n'
+ && edit_query_dialog2 (_("Warning"),
+ _("The file you are saving does not end with a newline."),
+ _("C&ontinue"), _("&Cancel")) != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+edit_get_save_file_as (WEdit * edit)
+{
+ static LineBreaks cur_lb = LB_ASIS;
+ char *filename_res;
+ vfs_path_t *ret_vpath = NULL;
+
+ const char *lb_names[LB_NAMES] = {
+ N_("&Do not change"),
+ N_("&Unix format (LF)"),
+ N_("&Windows/DOS format (CR LF)"),
+ N_("&Macintosh format (CR)")
+ };
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (N_("Enter file name:"), input_label_above,
+ vfs_path_as_str (edit->filename_vpath), "save-as",
+ &filename_res, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_LABEL (N_("Change line breaks to:"), NULL),
+ QUICK_RADIO (LB_NAMES, lb_names, (int *) &cur_lb, NULL),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 64 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Save As"), "[Save File As]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ {
+ char *fname;
+
+ edit->lb = cur_lb;
+ fname = tilde_expand (filename_res);
+ g_free (filename_res);
+ ret_vpath = vfs_path_from_str (fname);
+ g_free (fname);
+ }
+
+ return ret_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** returns TRUE on success */
+
+static gboolean
+edit_save_cmd (WEdit * edit)
+{
+ int res, save_lock = 0;
+
+ if (!edit->locked && !edit->delete_file)
+ save_lock = lock_file (edit->filename_vpath);
+ res = edit_save_file (edit, edit->filename_vpath);
+
+ /* Maintain modify (not save) lock on failure */
+ if ((res > 0 && edit->locked) || save_lock)
+ edit->locked = unlock_file (edit->filename_vpath);
+
+ /* On failure try 'save as', it does locking on its own */
+ if (res == 0)
+ return edit_save_as_cmd (edit);
+
+ if (res > 0)
+ {
+ edit->delete_file = 0;
+ edit->modified = 0;
+ }
+
+ edit->force |= REDRAW_COMPLETELY;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_delete_column_of_text (WEdit * edit)
+{
+ off_t m1, m2;
+ off_t n;
+ long b, c, d;
+
+ eval_marks (edit, &m1, &m2);
+ n = edit_buffer_get_forward_offset (&edit->buffer, m1, 0, m2) + 1;
+ c = (long) edit_move_forward3 (edit, edit_buffer_get_bol (&edit->buffer, m1), 0, m1);
+ d = (long) edit_move_forward3 (edit, edit_buffer_get_bol (&edit->buffer, m2), 0, m2);
+ b = MAX (MIN (c, d), MIN (edit->column1, edit->column2));
+ c = MAX (c, MAX (edit->column1, edit->column2));
+
+ while (n-- != 0)
+ {
+ off_t r, p, q;
+
+ r = edit_buffer_get_current_bol (&edit->buffer);
+ p = edit_move_forward3 (edit, r, b, 0);
+ q = edit_move_forward3 (edit, r, c, 0);
+ p = MAX (p, m1);
+ q = MIN (q, m2);
+ edit_cursor_move (edit, p - edit->buffer.curs1);
+ /* delete line between margins */
+ for (; q > p; q--)
+ if (edit_buffer_get_current_byte (&edit->buffer) != '\n')
+ edit_delete (edit, TRUE);
+
+ /* move to next line except on the last delete */
+ if (n != 0)
+ edit_cursor_move (edit,
+ edit_buffer_get_forward_offset (&edit->buffer, edit->buffer.curs1, 1,
+ 0) - edit->buffer.curs1);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** if success return 0 */
+
+static int
+edit_block_delete (WEdit * edit)
+{
+ off_t start_mark, end_mark;
+ off_t curs_pos;
+ long curs_line, c1, c2;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return 0;
+
+ if (edit->column_highlight && edit->mark2 < 0)
+ edit_mark_cmd (edit, FALSE);
+
+ /* Warning message with a query to continue or cancel the operation */
+ if ((end_mark - start_mark) > max_undo / 2 &&
+ edit_query_dialog2 (_("Warning"),
+ ("Block is large, you may not be able to undo this action"),
+ _("C&ontinue"), _("&Cancel")) != 0)
+ return 1;
+
+ c1 = MIN (edit->column1, edit->column2);
+ c2 = MAX (edit->column1, edit->column2);
+ edit->column1 = c1;
+ edit->column2 = c2;
+
+ edit_push_markers (edit);
+
+ curs_line = edit->buffer.curs_line;
+
+ curs_pos = edit->curs_col + edit->over_col;
+
+ /* move cursor to start of selection */
+ edit_cursor_move (edit, start_mark - edit->buffer.curs1);
+ edit_scroll_screen_over_cursor (edit);
+
+ if (start_mark < end_mark)
+ {
+ if (edit->column_highlight)
+ {
+ off_t line_width;
+
+ if (edit->mark2 < 0)
+ edit_mark_cmd (edit, FALSE);
+ edit_delete_column_of_text (edit);
+ /* move cursor to the saved position */
+ edit_move_to_line (edit, curs_line);
+ /* calculate line width and cursor position before cut */
+ line_width = edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer), 0,
+ edit_buffer_get_current_eol (&edit->buffer));
+ if (edit_options.cursor_beyond_eol && curs_pos > line_width)
+ edit->over_col = curs_pos - line_width;
+ }
+ else
+ {
+ off_t count;
+
+ for (count = start_mark; count < end_mark; count++)
+ edit_delete (edit, TRUE);
+ }
+ }
+
+ edit_set_markers (edit, 0, 0, 0, 0);
+ edit->force |= REDRAW_PAGE;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Return a null terminated length of text. Result must be g_free'd */
+
+static unsigned char *
+edit_get_block (WEdit * edit, off_t start, off_t finish, off_t * l)
+{
+ unsigned char *s, *r;
+
+ r = s = g_malloc0 (finish - start + 1);
+
+ if (edit->column_highlight)
+ {
+ *l = 0;
+
+ /* copy from buffer, excluding chars that are out of the column 'margins' */
+ for (; start < finish; start++)
+ {
+ int c;
+ off_t x;
+
+ x = edit_move_forward3 (edit, edit_buffer_get_bol (&edit->buffer, start), 0, start);
+ c = edit_buffer_get_byte (&edit->buffer, start);
+ if ((x >= edit->column1 && x < edit->column2)
+ || (x >= edit->column2 && x < edit->column1) || c == '\n')
+ {
+ *s++ = c;
+ (*l)++;
+ }
+ }
+ }
+ else
+ {
+ *l = finish - start;
+
+ for (; start < finish; start++)
+ *s++ = edit_buffer_get_byte (&edit->buffer, start);
+ }
+
+ *s = '\0';
+
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** copies a block to clipboard file */
+
+static gboolean
+edit_save_block_to_clip_file (WEdit * edit, off_t start, off_t finish)
+{
+ gboolean ret;
+ gchar *tmp;
+
+ tmp = mc_config_get_full_path (EDIT_HOME_CLIP_FILE);
+ ret = edit_save_block (edit, tmp, start, finish);
+ g_free (tmp);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+pipe_mail (const edit_buffer_t * buf, char *to, char *subject, char *cc)
+{
+ FILE *p = 0;
+ char *s;
+
+ to = name_quote (to, FALSE);
+ subject = name_quote (subject, FALSE);
+ cc = name_quote (cc, FALSE);
+ s = g_strconcat ("mail -s ", subject, *cc ? " -c " : "", cc, " ", to, (char *) NULL);
+ g_free (to);
+ g_free (subject);
+ g_free (cc);
+
+ if (s != NULL)
+ {
+ p = popen (s, "w");
+ g_free (s);
+ }
+
+ if (p != NULL)
+ {
+ off_t i;
+
+ for (i = 0; i < buf->size; i++)
+ if (fputc (edit_buffer_get_byte (buf, i), p) < 0)
+ break;
+ pclose (p);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_insert_column_of_text (WEdit * edit, unsigned char *data, off_t size, long width,
+ off_t * start_pos, off_t * end_pos, long *col1, long *col2)
+{
+ off_t i, cursor;
+ long col;
+
+ cursor = edit->buffer.curs1;
+ col = edit_get_col (edit);
+
+ for (i = 0; i < size; i++)
+ {
+ if (data[i] != '\n')
+ edit_insert (edit, data[i]);
+ else
+ { /* fill in and move to next line */
+ long l;
+ off_t p;
+
+ if (edit_buffer_get_current_byte (&edit->buffer) != '\n')
+ {
+ for (l = width - (edit_get_col (edit) - col); l > 0; l -= space_width)
+ edit_insert (edit, ' ');
+ }
+ for (p = edit->buffer.curs1;; p++)
+ {
+ if (p == edit->buffer.size)
+ {
+ edit_cursor_move (edit, edit->buffer.size - edit->buffer.curs1);
+ edit_insert_ahead (edit, '\n');
+ p++;
+ break;
+ }
+ if (edit_buffer_get_byte (&edit->buffer, p) == '\n')
+ {
+ p++;
+ break;
+ }
+ }
+ edit_cursor_move (edit, edit_move_forward3 (edit, p, col, 0) - edit->buffer.curs1);
+
+ for (l = col - edit_get_col (edit); l >= space_width; l -= space_width)
+ edit_insert (edit, ' ');
+ }
+ }
+
+ *col1 = col;
+ *col2 = col + width;
+ *start_pos = cursor;
+ *end_pos = edit->buffer.curs1;
+ edit_cursor_move (edit, cursor - edit->buffer.curs1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for the iteration of objects in the 'editors' array.
+ * Toggle syntax highlighting in editor object.
+ *
+ * @param data probably WEdit object
+ * @param user_data unused
+ */
+
+static void
+edit_syntax_onoff_cb (void *data, void *user_data)
+{
+ (void) user_data;
+
+ if (edit_widget_is_editor (CONST_WIDGET (data)))
+ {
+ WEdit *edit = EDIT (data);
+
+ if (edit_options.syntax_highlighting)
+ edit_load_syntax (edit, NULL, edit->syntax_type);
+ edit->force |= REDRAW_PAGE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+editcmd_dialog_raw_key_query_cb (Widget * w, Widget * sender, widget_msg_t msg, int parm,
+ void *data)
+{
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_KEY:
+ h->ret_value = parm;
+ dlg_close (h);
+ return MSG_HANDLED;
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_refresh_cmd (void)
+{
+ tty_clear_screen ();
+ repaint_screen ();
+ tty_keypad (TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Toggle syntax highlighting in all editor windows.
+ *
+ * @param h root widget for all windows
+ */
+
+void
+edit_syntax_onoff_cmd (WDialog * h)
+{
+ edit_options.syntax_highlighting = !edit_options.syntax_highlighting;
+ g_list_foreach (GROUP (h)->widgets, edit_syntax_onoff_cb, NULL);
+ widget_draw (WIDGET (h));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Toggle tabs showing in all editor windows.
+ *
+ * @param h root widget for all windows
+ */
+
+void
+edit_show_tabs_tws_cmd (WDialog * h)
+{
+ enable_show_tabs_tws = !enable_show_tabs_tws;
+ widget_draw (WIDGET (h));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Toggle right margin showing in all editor windows.
+ *
+ * @param h root widget for all windows
+ */
+
+void
+edit_show_margin_cmd (WDialog * h)
+{
+ edit_options.show_right_margin = !edit_options.show_right_margin;
+ widget_draw (WIDGET (h));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Toggle line numbers showing in all editor windows.
+ *
+ * @param h root widget for all windows
+ */
+
+void
+edit_show_numbers_cmd (WDialog * h)
+{
+ edit_options.line_state = !edit_options.line_state;
+ edit_options.line_state_width = edit_options.line_state ? LINE_STATE_WIDTH : 0;
+ widget_draw (WIDGET (h));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_save_mode_cmd (void)
+{
+ char *str_result = NULL;
+
+ const char *str[] = {
+ N_("&Quick save"),
+ N_("&Safe save"),
+ N_("&Do backups with following extension:")
+ };
+
+#ifdef ENABLE_NLS
+ size_t i;
+
+ for (i = 0; i < 3; i++)
+ str[i] = _(str[i]);
+#endif
+
+ g_assert (edit_options.backup_ext != NULL);
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_RADIO (3, str, &edit_options.save_mode, &edit_save_mode_radio_id),
+ QUICK_INPUT (edit_options.backup_ext, "edit-backup-ext", &str_result,
+ &edit_save_mode_input_id, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_CHECKBOX (N_("Check &POSIX new line"), &edit_options.check_nl_at_eof, NULL),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 38 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Edit Save Mode"), "[Edit Save Mode]",
+ quick_widgets, edit_save_mode_callback, NULL
+ };
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ {
+ g_free (edit_options.backup_ext);
+ edit_options.backup_ext = str_result;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_set_filename (WEdit * edit, const vfs_path_t * name_vpath)
+{
+ vfs_path_free (edit->filename_vpath, TRUE);
+ edit->filename_vpath = vfs_path_clone (name_vpath);
+
+ if (edit->dir_vpath == NULL)
+ edit->dir_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Here we want to warn the users of overwriting an existing file,
+ but only if they have made a change to the filename */
+/* returns TRUE on success */
+gboolean
+edit_save_as_cmd (WEdit * edit)
+{
+ /* This heads the 'Save As' dialog box */
+ vfs_path_t *exp_vpath;
+ int save_lock = 0;
+ gboolean different_filename = FALSE;
+ gboolean ret = FALSE;
+
+ if (!edit_check_newline (&edit->buffer))
+ return FALSE;
+
+ exp_vpath = edit_get_save_file_as (edit);
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+
+ if (exp_vpath != NULL && vfs_path_len (exp_vpath) != 0)
+ {
+ int rv;
+
+ if (!vfs_path_equal (edit->filename_vpath, exp_vpath))
+ {
+ int file;
+ struct stat sb;
+
+ if (mc_stat (exp_vpath, &sb) == 0 && !S_ISREG (sb.st_mode))
+ {
+ edit_error_dialog (_("Save as"),
+ get_sys_error (_
+ ("Cannot save: destination is not a regular file")));
+ goto ret;
+ }
+
+ different_filename = TRUE;
+ file = mc_open (exp_vpath, O_RDONLY | O_BINARY);
+
+ if (file == -1)
+ edit->stat1.st_mode |= S_IWUSR;
+ else
+ {
+ /* the file exists */
+ mc_close (file);
+ /* Overwrite the current file or cancel the operation */
+ if (edit_query_dialog2
+ (_("Warning"),
+ _("A file already exists with this name"), _("&Overwrite"), _("&Cancel")))
+ goto ret;
+ }
+
+ save_lock = lock_file (exp_vpath);
+ }
+ else if (!edit->locked && !edit->delete_file)
+ /* filenames equal, check if already locked */
+ save_lock = lock_file (exp_vpath);
+
+ if (different_filename)
+ /* Allow user to write into saved (under another name) file
+ * even if original file had r/o user permissions. */
+ edit->stat1.st_mode |= S_IWUSR;
+
+ rv = edit_save_file (edit, exp_vpath);
+ switch (rv)
+ {
+ case 1:
+ /* Successful, so unlock both files */
+ if (different_filename)
+ {
+ if (save_lock)
+ unlock_file (exp_vpath);
+ if (edit->locked)
+ edit->locked = unlock_file (edit->filename_vpath);
+ }
+ else if (edit->locked || save_lock)
+ edit->locked = unlock_file (edit->filename_vpath);
+
+ edit_set_filename (edit, exp_vpath);
+ if (edit->lb != LB_ASIS)
+ edit_reload (edit, exp_vpath);
+ edit->modified = 0;
+ edit->delete_file = 0;
+ if (different_filename)
+ edit_load_syntax (edit, NULL, edit->syntax_type);
+ ret = TRUE;
+ break;
+
+ default:
+ edit_error_dialog (_("Save as"), get_sys_error (_("Cannot save file")));
+ MC_FALLTHROUGH;
+
+ case -1:
+ /* Failed, so maintain modify (not save) lock */
+ if (save_lock)
+ unlock_file (exp_vpath);
+ break;
+ }
+ }
+
+ ret:
+ vfs_path_free (exp_vpath, TRUE);
+ edit->force |= REDRAW_COMPLETELY;
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns TRUE on success */
+
+gboolean
+edit_save_confirm_cmd (WEdit * edit)
+{
+ if (edit->filename_vpath == NULL)
+ return edit_save_as_cmd (edit);
+
+ if (!edit_check_newline (&edit->buffer))
+ return FALSE;
+
+ if (edit_options.confirm_save)
+ {
+ char *f;
+ gboolean ok;
+
+ f = g_strdup_printf (_("Confirm save file: \"%s\""),
+ vfs_path_as_str (edit->filename_vpath));
+ ok = (edit_query_dialog2 (_("Save file"), f, _("&Save"), _("&Cancel")) == 0);
+ g_free (f);
+ if (!ok)
+ return FALSE;
+ }
+
+ return edit_save_cmd (edit);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Ask file to edit and load it.
+ *
+ * @return TRUE on success or cancel of ask.
+ */
+
+gboolean
+edit_load_cmd (WDialog * h)
+{
+ char *exp;
+ gboolean ret = TRUE; /* possible cancel */
+
+ exp = input_expand_dialog (_("Load"), _("Enter file name:"),
+ MC_HISTORY_EDIT_LOAD, INPUT_LAST_TEXT,
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
+
+ if (exp != NULL && *exp != '\0')
+ {
+ vfs_path_t *exp_vpath;
+
+ exp_vpath = vfs_path_from_str (exp);
+ ret = edit_load_file_from_filename (h, exp_vpath, 0);
+ vfs_path_free (exp_vpath, TRUE);
+ }
+
+ g_free (exp);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load file content
+ *
+ * @param h screen the owner of editor window
+ * @param vpath vfs file path
+ * @param line line number
+ *
+ * @return TRUE if file content was successfully loaded, FALSE otherwise
+ */
+
+gboolean
+edit_load_file_from_filename (WDialog * h, const vfs_path_t * vpath, long line)
+{
+ WRect r = WIDGET (h)->rect;
+
+ rect_grow (&r, -1, 0);
+
+ return edit_add_window (h, &r, vpath, line);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show history od edited or viewed files and open selected file.
+ *
+ * @return TRUE on success, FALSE otherwise.
+ */
+
+gboolean
+edit_load_file_from_history (WDialog * h)
+{
+ char *exp;
+ int action;
+ gboolean ret = TRUE; /* possible cancel */
+
+ exp = show_file_history (CONST_WIDGET (h), &action);
+ if (exp != NULL && (action == CK_Edit || action == CK_Enter))
+ {
+ vfs_path_t *exp_vpath;
+
+ exp_vpath = vfs_path_from_str (exp);
+ ret = edit_load_file_from_filename (h, exp_vpath, 0);
+ vfs_path_free (exp_vpath, TRUE);
+ }
+
+ g_free (exp);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load syntax file to edit.
+ *
+ * @return TRUE on success
+ */
+
+gboolean
+edit_load_syntax_file (WDialog * h)
+{
+ vfs_path_t *extdir_vpath;
+ int dir = 0;
+ gboolean ret = FALSE;
+
+ if (geteuid () == 0)
+ dir = query_dialog (_("Syntax file edit"),
+ _("Which syntax file you want to edit?"), D_NORMAL, 2,
+ _("&User"), _("&System wide"));
+
+ extdir_vpath =
+ vfs_path_build_filename (mc_global.sysconfig_dir, EDIT_SYNTAX_FILE, (char *) NULL);
+ if (!exist_file (vfs_path_get_last_path_str (extdir_vpath)))
+ {
+ vfs_path_free (extdir_vpath, TRUE);
+ extdir_vpath =
+ vfs_path_build_filename (mc_global.share_data_dir, EDIT_SYNTAX_FILE, (char *) NULL);
+ }
+
+ if (dir == 0)
+ {
+ vfs_path_t *user_syntax_file_vpath;
+
+ user_syntax_file_vpath = mc_config_get_full_vpath (EDIT_SYNTAX_FILE);
+ check_for_default (extdir_vpath, user_syntax_file_vpath);
+ ret = edit_load_file_from_filename (h, user_syntax_file_vpath, 0);
+ vfs_path_free (user_syntax_file_vpath, TRUE);
+ }
+ else if (dir == 1)
+ ret = edit_load_file_from_filename (h, extdir_vpath, 0);
+
+ vfs_path_free (extdir_vpath, TRUE);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load menu file to edit.
+ *
+ * @return TRUE on success
+ */
+
+gboolean
+edit_load_menu_file (WDialog * h)
+{
+ vfs_path_t *buffer_vpath;
+ vfs_path_t *menufile_vpath;
+ int dir;
+ gboolean ret;
+
+ query_set_sel (1);
+ dir = query_dialog (_("Menu edit"),
+ _("Which menu file do you want to edit?"), D_NORMAL,
+ geteuid () != 0 ? 2 : 3, _("&Local"), _("&User"), _("&System wide"));
+
+ menufile_vpath =
+ vfs_path_build_filename (mc_global.sysconfig_dir, EDIT_GLOBAL_MENU, (char *) NULL);
+ if (!exist_file (vfs_path_get_last_path_str (menufile_vpath)))
+ {
+ vfs_path_free (menufile_vpath, TRUE);
+ menufile_vpath =
+ vfs_path_build_filename (mc_global.share_data_dir, EDIT_GLOBAL_MENU, (char *) NULL);
+ }
+
+ switch (dir)
+ {
+ case 0:
+ buffer_vpath = vfs_path_from_str (EDIT_LOCAL_MENU);
+ check_for_default (menufile_vpath, buffer_vpath);
+ chmod (vfs_path_get_last_path_str (buffer_vpath), 0600);
+ break;
+
+ case 1:
+ buffer_vpath = mc_config_get_full_vpath (EDIT_HOME_MENU);
+ check_for_default (menufile_vpath, buffer_vpath);
+ break;
+
+ case 2:
+ buffer_vpath =
+ vfs_path_build_filename (mc_global.sysconfig_dir, EDIT_GLOBAL_MENU, (char *) NULL);
+ if (!exist_file (vfs_path_get_last_path_str (buffer_vpath)))
+ {
+ vfs_path_free (buffer_vpath, TRUE);
+ buffer_vpath =
+ vfs_path_build_filename (mc_global.share_data_dir, EDIT_GLOBAL_MENU, (char *) NULL);
+ }
+ break;
+
+ default:
+ vfs_path_free (menufile_vpath, TRUE);
+ return FALSE;
+ }
+
+ ret = edit_load_file_from_filename (h, buffer_vpath, 0);
+
+ vfs_path_free (buffer_vpath, TRUE);
+ vfs_path_free (menufile_vpath, TRUE);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Close window with opened file.
+ *
+ * @return TRUE if file was closed.
+ */
+
+gboolean
+edit_close_cmd (WEdit * edit)
+{
+ gboolean ret;
+
+ ret = (edit != NULL) && edit_ok_to_exit (edit);
+
+ if (ret)
+ {
+ Widget *w = WIDGET (edit);
+ WGroup *g = w->owner;
+
+ if (edit->locked != 0)
+ unlock_file (edit->filename_vpath);
+
+ group_remove_widget (w);
+ widget_destroy (w);
+
+ if (edit_widget_is_editor (CONST_WIDGET (g->current->data)))
+ edit = EDIT (g->current->data);
+ else
+ {
+ edit = edit_find_editor (DIALOG (g));
+ if (edit != NULL)
+ widget_select (WIDGET (edit));
+ }
+ }
+
+ if (edit != NULL)
+ edit->force |= REDRAW_COMPLETELY;
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ if mark2 is -1 then marking is from mark1 to the cursor.
+ Otherwise its between the markers. This handles this.
+ Returns FALSE if no text is marked.
+ */
+
+gboolean
+eval_marks (WEdit * edit, off_t * start_mark, off_t * end_mark)
+{
+ long end_mark_curs;
+
+ if (edit->mark1 == edit->mark2)
+ {
+ *start_mark = *end_mark = 0;
+ edit->column2 = edit->column1 = 0;
+ return FALSE;
+ }
+
+ if (edit->end_mark_curs < 0)
+ end_mark_curs = edit->buffer.curs1;
+ else
+ end_mark_curs = edit->end_mark_curs;
+
+ if (edit->mark2 >= 0)
+ {
+ *start_mark = MIN (edit->mark1, edit->mark2);
+ *end_mark = MAX (edit->mark1, edit->mark2);
+ }
+ else
+ {
+ *start_mark = MIN (edit->mark1, end_mark_curs);
+ *end_mark = MAX (edit->mark1, end_mark_curs);
+ edit->column2 = edit->curs_col + edit->over_col;
+ }
+
+ if (edit->column_highlight
+ && ((edit->mark1 > end_mark_curs && edit->column1 < edit->column2)
+ || (edit->mark1 < end_mark_curs && edit->column1 > edit->column2)))
+ {
+ off_t start_bol, start_eol;
+ off_t end_bol, end_eol;
+ long col1, col2;
+ off_t diff1, diff2;
+
+ start_bol = edit_buffer_get_bol (&edit->buffer, *start_mark);
+ start_eol = edit_buffer_get_eol (&edit->buffer, start_bol - 1) + 1;
+ end_bol = edit_buffer_get_bol (&edit->buffer, *end_mark);
+ end_eol = edit_buffer_get_eol (&edit->buffer, *end_mark);
+ col1 = MIN (edit->column1, edit->column2);
+ col2 = MAX (edit->column1, edit->column2);
+
+ diff1 = edit_move_forward3 (edit, start_bol, col2, 0) -
+ edit_move_forward3 (edit, start_bol, col1, 0);
+ diff2 = edit_move_forward3 (edit, end_bol, col2, 0) -
+ edit_move_forward3 (edit, end_bol, col1, 0);
+
+ *start_mark -= diff1;
+ *end_mark += diff2;
+ *start_mark = MAX (*start_mark, start_eol);
+ *end_mark = MIN (*end_mark, end_eol);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_block_copy_cmd (WEdit * edit)
+{
+ off_t start_mark, end_mark, current = edit->buffer.curs1;
+ off_t mark1 = 0, mark2 = 0;
+ long c1 = 0, c2 = 0;
+ off_t size;
+ unsigned char *copy_buf;
+
+ edit_update_curs_col (edit);
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return;
+
+ copy_buf = edit_get_block (edit, start_mark, end_mark, &size);
+
+ /* all that gets pushed are deletes hence little space is used on the stack */
+
+ edit_push_markers (edit);
+
+ if (edit->column_highlight)
+ {
+ long col_delta;
+
+ col_delta = labs (edit->column2 - edit->column1);
+ edit_insert_column_of_text (edit, copy_buf, size, col_delta, &mark1, &mark2, &c1, &c2);
+ }
+ else
+ {
+ int size_orig = size;
+
+ while (size-- != 0)
+ edit_insert_ahead (edit, copy_buf[size]);
+
+ /* Place cursor at the end of text selection */
+ if (edit_options.cursor_after_inserted_block)
+ edit_cursor_move (edit, size_orig);
+ }
+
+ g_free (copy_buf);
+ edit_scroll_screen_over_cursor (edit);
+
+ if (edit->column_highlight)
+ edit_set_markers (edit, edit->buffer.curs1, mark2, c1, c2);
+ else if (start_mark < current && end_mark > current)
+ edit_set_markers (edit, start_mark, end_mark + end_mark - start_mark, 0, 0);
+
+ edit->force |= REDRAW_PAGE;
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_block_move_cmd (WEdit * edit)
+{
+ off_t current;
+ unsigned char *copy_buf = NULL;
+ off_t start_mark, end_mark;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return;
+
+ if (!edit->column_highlight && edit->buffer.curs1 > start_mark && edit->buffer.curs1 < end_mark)
+ return;
+
+ if (edit->mark2 < 0)
+ edit_mark_cmd (edit, FALSE);
+ edit_push_markers (edit);
+
+ if (edit->column_highlight)
+ {
+ off_t mark1, mark2;
+ off_t size;
+ long c1, c2, b_width;
+ long x, x2;
+
+ c1 = MIN (edit->column1, edit->column2);
+ c2 = MAX (edit->column1, edit->column2);
+ b_width = c2 - c1;
+
+ edit_update_curs_col (edit);
+
+ x = edit->curs_col;
+ x2 = x + edit->over_col;
+
+ /* do nothing when cursor inside first line of selected area */
+ if ((edit_buffer_get_eol (&edit->buffer, edit->buffer.curs1) ==
+ edit_buffer_get_eol (&edit->buffer, start_mark)) && x2 > c1 && x2 <= c2)
+ return;
+
+ if (edit->buffer.curs1 > start_mark
+ && edit->buffer.curs1 < edit_buffer_get_eol (&edit->buffer, end_mark))
+ {
+ if (x > c2)
+ x -= b_width;
+ else if (x > c1 && x <= c2)
+ x = c1;
+ }
+ /* save current selection into buffer */
+ copy_buf = edit_get_block (edit, start_mark, end_mark, &size);
+
+ /* remove current selection */
+ edit_block_delete_cmd (edit);
+
+ edit->over_col = MAX (0, edit->over_col - b_width);
+ /* calculate the cursor pos after delete block */
+ current = edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer), x, 0);
+ edit_cursor_move (edit, current - edit->buffer.curs1);
+ edit_scroll_screen_over_cursor (edit);
+
+ /* add TWS if need before block insertion */
+ if (edit_options.cursor_beyond_eol && edit->over_col > 0)
+ edit_insert_over (edit);
+
+ edit_insert_column_of_text (edit, copy_buf, size, b_width, &mark1, &mark2, &c1, &c2);
+ edit_set_markers (edit, mark1, mark2, c1, c2);
+ }
+ else
+ {
+ off_t count, count_orig;
+
+ current = edit->buffer.curs1;
+ copy_buf = g_malloc0 (end_mark - start_mark);
+ edit_cursor_move (edit, start_mark - edit->buffer.curs1);
+ edit_scroll_screen_over_cursor (edit);
+
+ for (count = start_mark; count < end_mark; count++)
+ copy_buf[end_mark - count - 1] = edit_delete (edit, TRUE);
+
+ edit_scroll_screen_over_cursor (edit);
+ edit_cursor_move (edit,
+ current - edit->buffer.curs1 -
+ (((current - edit->buffer.curs1) > 0) ? end_mark - start_mark : 0));
+ edit_scroll_screen_over_cursor (edit);
+ count_orig = count;
+ while (count-- > start_mark)
+ edit_insert_ahead (edit, copy_buf[end_mark - count - 1]);
+
+ edit_set_markers (edit, edit->buffer.curs1, edit->buffer.curs1 + end_mark - start_mark, 0,
+ 0);
+
+ /* Place cursor at the end of text selection */
+ if (edit_options.cursor_after_inserted_block)
+ edit_cursor_move (edit, count_orig - start_mark);
+ }
+
+ edit_scroll_screen_over_cursor (edit);
+ g_free (copy_buf);
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns 1 if canceelled by user */
+
+int
+edit_block_delete_cmd (WEdit * edit)
+{
+ off_t start_mark, end_mark;
+
+ if (eval_marks (edit, &start_mark, &end_mark))
+ return edit_block_delete (edit);
+
+ edit_delete_line (edit);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check if it's OK to close the file. If there are unsaved changes, ask user.
+ *
+ * @return TRUE if it's OK to exit, FALSE to continue editing.
+ */
+
+gboolean
+edit_ok_to_exit (WEdit * edit)
+{
+ const char *fname = N_("[NoName]");
+ char *msg;
+ int act;
+
+ if (!edit->modified)
+ return TRUE;
+
+ if (edit->filename_vpath != NULL)
+ fname = vfs_path_as_str (edit->filename_vpath);
+#ifdef ENABLE_NLS
+ else
+ fname = _(fname);
+#endif
+
+ if (!mc_global.midnight_shutdown)
+ {
+ query_set_sel (2);
+
+ msg = g_strdup_printf (_("File %s was modified.\nSave before close?"), fname);
+ act = edit_query_dialog3 (_("Close file"), msg, _("&Yes"), _("&No"), _("&Cancel"));
+ }
+ else
+ {
+ msg = g_strdup_printf (_("Midnight Commander is being shut down.\nSave modified file %s?"),
+ fname);
+ act = edit_query_dialog2 (_("Quit"), msg, _("&Yes"), _("&No"));
+
+ /* Esc is No */
+ if (act == -1)
+ act = 1;
+ }
+
+ g_free (msg);
+
+ switch (act)
+ {
+ case 0: /* Yes */
+ if (!mc_global.midnight_shutdown && !edit_check_newline (&edit->buffer))
+ return FALSE;
+ edit_push_markers (edit);
+ edit_set_markers (edit, 0, 0, 0, 0);
+ if (!edit_save_cmd (edit) || mc_global.midnight_shutdown)
+ return mc_global.midnight_shutdown;
+ break;
+ case 1: /* No */
+ default:
+ break;
+ case 2: /* Cancel quit */
+ case -1: /* Esc */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** save block, returns TRUE on success */
+
+gboolean
+edit_save_block (WEdit * edit, const char *filename, off_t start, off_t finish)
+{
+ int file;
+ off_t len = 1;
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (filename);
+ file = mc_open (vpath, O_CREAT | O_WRONLY | O_TRUNC,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | O_BINARY);
+ vfs_path_free (vpath, TRUE);
+ if (file == -1)
+ return FALSE;
+
+ if (edit->column_highlight)
+ {
+ int r;
+
+ r = mc_write (file, VERTICAL_MAGIC, sizeof (VERTICAL_MAGIC));
+ if (r > 0)
+ {
+ unsigned char *block, *p;
+
+ p = block = edit_get_block (edit, start, finish, &len);
+ while (len != 0)
+ {
+ r = mc_write (file, p, len);
+ if (r < 0)
+ break;
+ p += r;
+ len -= r;
+ }
+ g_free (block);
+ }
+ }
+ else
+ {
+ unsigned char *buf;
+ off_t i = start;
+
+ len = finish - start;
+ buf = g_malloc0 (TEMP_BUF_LEN);
+ while (start != finish)
+ {
+ off_t end;
+
+ end = MIN (finish, start + TEMP_BUF_LEN);
+ for (; i < end; i++)
+ buf[i - start] = edit_buffer_get_byte (&edit->buffer, i);
+ len -= mc_write (file, (char *) buf, end - start);
+ start = end;
+ }
+ g_free (buf);
+ }
+ mc_close (file);
+
+ return (len == 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_paste_from_history (WEdit * edit)
+{
+ (void) edit;
+ edit_error_dialog (_("Error"), _("This function is not implemented"));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_copy_to_X_buf_cmd (WEdit * edit)
+{
+ off_t start_mark, end_mark;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return TRUE;
+
+ if (!edit_save_block_to_clip_file (edit, start_mark, end_mark))
+ {
+ edit_error_dialog (_("Copy to clipboard"), get_sys_error (_("Unable to save to file")));
+ return FALSE;
+ }
+ /* try use external clipboard utility */
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL);
+
+ if (edit_options.drop_selection_on_copy)
+ edit_mark_cmd (edit, TRUE);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_cut_to_X_buf_cmd (WEdit * edit)
+{
+ off_t start_mark, end_mark;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return TRUE;
+
+ if (!edit_save_block_to_clip_file (edit, start_mark, end_mark))
+ {
+ edit_error_dialog (_("Cut to clipboard"), _("Unable to save to file"));
+ return FALSE;
+ }
+ /* try use external clipboard utility */
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL);
+
+ edit_block_delete_cmd (edit);
+ edit_mark_cmd (edit, TRUE);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_paste_from_X_buf_cmd (WEdit * edit)
+{
+ vfs_path_t *tmp;
+ gboolean ret;
+
+ /* try use external clipboard utility */
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_from_ext_clip", NULL);
+ tmp = mc_config_get_full_vpath (EDIT_HOME_CLIP_FILE);
+ ret = (edit_insert_file (edit, tmp) >= 0);
+ vfs_path_free (tmp, TRUE);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Ask user for the line and go to that line.
+ * Negative numbers mean line from the end (i.e. -1 is the last line).
+ */
+
+void
+edit_goto_cmd (WEdit * edit)
+{
+ static gboolean first_run = TRUE;
+
+ char *f;
+ long l;
+ char *error;
+
+ f = input_dialog (_("Goto line"), _("Enter line:"), MC_HISTORY_EDIT_GOTO_LINE,
+ first_run ? NULL : INPUT_LAST_TEXT, INPUT_COMPLETE_NONE);
+ if (f == NULL || *f == '\0')
+ {
+ g_free (f);
+ return;
+ }
+
+ l = strtol (f, &error, 0);
+ if (*error != '\0')
+ {
+ g_free (f);
+ return;
+ }
+
+ if (l < 0)
+ l = edit->buffer.lines + l + 2;
+
+ edit_move_display (edit, l - WIDGET (edit)->rect.lines / 2 - 1);
+ edit_move_to_line (edit, l - 1);
+ edit->force |= REDRAW_COMPLETELY;
+
+ g_free (f);
+ first_run = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Return TRUE on success */
+
+gboolean
+edit_save_block_cmd (WEdit * edit)
+{
+ off_t start_mark, end_mark;
+ char *exp, *tmp;
+ gboolean ret = FALSE;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return TRUE;
+
+ tmp = mc_config_get_full_path (EDIT_HOME_CLIP_FILE);
+ exp =
+ input_expand_dialog (_("Save block"), _("Enter file name:"),
+ MC_HISTORY_EDIT_SAVE_BLOCK, tmp, INPUT_COMPLETE_FILENAMES);
+ g_free (tmp);
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+
+ if (exp != NULL && *exp != '\0')
+ {
+ if (edit_save_block (edit, exp, start_mark, end_mark))
+ ret = TRUE;
+ else
+ edit_error_dialog (_("Save block"), get_sys_error (_("Cannot save file")));
+
+ edit->force |= REDRAW_COMPLETELY;
+ }
+
+ g_free (exp);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** returns TRUE on success */
+gboolean
+edit_insert_file_cmd (WEdit * edit)
+{
+ char *tmp;
+ char *exp;
+ gboolean ret = FALSE;
+
+ tmp = mc_config_get_full_path (EDIT_HOME_CLIP_FILE);
+ exp = input_expand_dialog (_("Insert file"), _("Enter file name:"),
+ MC_HISTORY_EDIT_INSERT_FILE, tmp, INPUT_COMPLETE_FILENAMES);
+ g_free (tmp);
+
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+
+ if (exp != NULL && *exp != '\0')
+ {
+ vfs_path_t *exp_vpath;
+
+ exp_vpath = vfs_path_from_str (exp);
+ ret = (edit_insert_file (edit, exp_vpath) >= 0);
+ vfs_path_free (exp_vpath, TRUE);
+
+ if (!ret)
+ edit_error_dialog (_("Insert file"), get_sys_error (_("Cannot insert file")));
+ }
+
+ g_free (exp);
+
+ edit->force |= REDRAW_COMPLETELY;
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** sorts a block, returns -1 on system fail, 1 on cancel and 0 on success */
+
+int
+edit_sort_cmd (WEdit * edit)
+{
+ char *exp, *tmp, *tmp_edit_block_name, *tmp_edit_temp_name;
+ off_t start_mark, end_mark;
+ int e;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ {
+ edit_error_dialog (_("Sort block"), _("You must first highlight a block of text"));
+ return 0;
+ }
+
+ tmp = mc_config_get_full_path (EDIT_HOME_BLOCK_FILE);
+ edit_save_block (edit, tmp, start_mark, end_mark);
+ g_free (tmp);
+
+ exp = input_dialog (_("Run sort"),
+ _("Enter sort options (see sort(1) manpage) separated by whitespace:"),
+ MC_HISTORY_EDIT_SORT, INPUT_LAST_TEXT, INPUT_COMPLETE_NONE);
+
+ if (exp == NULL)
+ return 1;
+
+ tmp_edit_block_name = mc_config_get_full_path (EDIT_HOME_BLOCK_FILE);
+ tmp_edit_temp_name = mc_config_get_full_path (EDIT_HOME_TEMP_FILE);
+ tmp =
+ g_strconcat (" sort ", exp, " ", tmp_edit_block_name,
+ " > ", tmp_edit_temp_name, (char *) NULL);
+ g_free (tmp_edit_temp_name);
+ g_free (tmp_edit_block_name);
+ g_free (exp);
+
+ e = system (tmp);
+ g_free (tmp);
+ if (e != 0)
+ {
+ if (e == -1 || e == 127)
+ edit_error_dialog (_("Sort"), get_sys_error (_("Cannot execute sort command")));
+ else
+ {
+ char q[8];
+
+ sprintf (q, "%d ", e);
+ tmp = g_strdup_printf (_("Sort returned non-zero: %s"), q);
+ edit_error_dialog (_("Sort"), tmp);
+ g_free (tmp);
+ }
+
+ return -1;
+ }
+
+ edit->force |= REDRAW_COMPLETELY;
+
+ if (edit_block_delete_cmd (edit))
+ return 1;
+
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = mc_config_get_full_vpath (EDIT_HOME_TEMP_FILE);
+ edit_insert_file (edit, tmp_vpath);
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Ask user for a command, execute it and paste its output back to the
+ * editor.
+ */
+
+int
+edit_ext_cmd (WEdit * edit)
+{
+ char *exp, *tmp, *tmp_edit_temp_file;
+ int e;
+
+ exp =
+ input_dialog (_("Paste output of external command"),
+ _("Enter shell command(s):"), MC_HISTORY_EDIT_PASTE_EXTCMD, INPUT_LAST_TEXT,
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES
+ | INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_COMMANDS |
+ INPUT_COMPLETE_SHELL_ESC);
+
+ if (!exp)
+ return 1;
+
+ tmp_edit_temp_file = mc_config_get_full_path (EDIT_HOME_TEMP_FILE);
+ tmp = g_strconcat (exp, " > ", tmp_edit_temp_file, (char *) NULL);
+ g_free (tmp_edit_temp_file);
+ e = system (tmp);
+ g_free (tmp);
+ g_free (exp);
+
+ if (e != 0)
+ {
+ edit_error_dialog (_("External command"), get_sys_error (_("Cannot execute command")));
+ return -1;
+ }
+
+ edit->force |= REDRAW_COMPLETELY;
+
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = mc_config_get_full_vpath (EDIT_HOME_TEMP_FILE);
+ edit_insert_file (edit, tmp_vpath);
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** if block is 1, a block must be highlighted and the shell command
+ processes it. If block is 0 the shell command is a straight system
+ command, that just produces some output which is to be inserted */
+
+void
+edit_block_process_cmd (WEdit * edit, int macro_number)
+{
+ char *fname;
+ char *macros_fname = NULL;
+
+ fname = g_strdup_printf ("%s.%i.sh", EDIT_HOME_MACRO_FILE, macro_number);
+ macros_fname = g_build_filename (mc_config_get_data_path (), fname, (char *) NULL);
+ user_menu (edit, macros_fname, 0);
+ g_free (fname);
+ g_free (macros_fname);
+ edit->force |= REDRAW_COMPLETELY;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_mail_dialog (WEdit * edit)
+{
+ char *mail_to, *mail_subject, *mail_cc;
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABEL (N_("mail -s <subject> -c <cc> <to>"), NULL),
+ QUICK_LABELED_INPUT (N_("To"), input_label_above,
+ INPUT_LAST_TEXT, "mail-dlg-input-3",
+ &mail_to, NULL, FALSE, FALSE, INPUT_COMPLETE_USERNAMES),
+ QUICK_LABELED_INPUT (N_("Subject"), input_label_above,
+ INPUT_LAST_TEXT, "mail-dlg-input-2",
+ &mail_subject, NULL, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_LABELED_INPUT (N_("Copies to"), input_label_above,
+ INPUT_LAST_TEXT, "mail-dlg-input",
+ &mail_cc, NULL, FALSE, FALSE, INPUT_COMPLETE_USERNAMES),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 50 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Mail"), "[Input Line Keys]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ {
+ pipe_mail (&edit->buffer, mail_to, mail_subject, mail_cc);
+ g_free (mail_to);
+ g_free (mail_subject);
+ g_free (mail_cc);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+void
+edit_select_codepage_cmd (WEdit * edit)
+{
+ if (do_select_codepage ())
+ edit_set_codeset (edit);
+
+ edit->force = REDRAW_PAGE;
+ widget_draw (WIDGET (edit));
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_insert_literal_cmd (WEdit * edit)
+{
+ int char_for_insertion;
+
+ char_for_insertion = editcmd_dialog_raw_key_query (_("Insert literal"),
+ _("Press any key:"), FALSE);
+ edit_execute_key_command (edit, -1, ascii_alpha_to_cntrl (char_for_insertion));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_load_forward_cmd (WEdit * edit)
+{
+ if (edit->modified
+ && edit_query_dialog2 (_("Warning"),
+ _("Current text was modified without a file save.\n"
+ "Continue discards these changes."), _("C&ontinue"),
+ _("&Cancel")) == 1)
+ {
+ edit->force |= REDRAW_COMPLETELY;
+ return TRUE;
+ }
+
+ if (edit_stack_iterator + 1 >= MAX_HISTORY_MOVETO)
+ return FALSE;
+
+ if (edit_history_moveto[edit_stack_iterator + 1].line < 1)
+ return FALSE;
+
+ edit_stack_iterator++;
+ if (edit_history_moveto[edit_stack_iterator].filename_vpath != NULL)
+ return edit_reload_line (edit, edit_history_moveto[edit_stack_iterator].filename_vpath,
+ edit_history_moveto[edit_stack_iterator].line);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_load_back_cmd (WEdit * edit)
+{
+ if (edit->modified
+ && edit_query_dialog2 (_("Warning"),
+ _("Current text was modified without a file save.\n"
+ "Continue discards these changes."), _("C&ontinue"),
+ _("&Cancel")) == 1)
+ {
+ edit->force |= REDRAW_COMPLETELY;
+ return TRUE;
+ }
+
+ /* we are in the bottom of the stack, NO WAY! */
+ if (edit_stack_iterator == 0)
+ return FALSE;
+
+ edit_stack_iterator--;
+ if (edit_history_moveto[edit_stack_iterator].filename_vpath != NULL)
+ return edit_reload_line (edit, edit_history_moveto[edit_stack_iterator].filename_vpath,
+ edit_history_moveto[edit_stack_iterator].line);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* gets a raw key from the keyboard. Passing cancel = 1 draws
+ a cancel button thus allowing c-c etc. Alternatively, cancel = 0
+ will return the next key pressed. ctrl-a (=B_CANCEL), ctrl-g, ctrl-c,
+ and Esc are cannot returned */
+
+int
+editcmd_dialog_raw_key_query (const char *heading, const char *query, gboolean cancel)
+{
+ int w, wq;
+ int y = 2;
+ WDialog *raw_dlg;
+ WGroup *g;
+
+ w = str_term_width1 (heading) + 6;
+ wq = str_term_width1 (query);
+ w = MAX (w, wq + 3 * 2 + 1 + 2);
+
+ raw_dlg =
+ dlg_create (TRUE, 0, 0, cancel ? 7 : 5, w, WPOS_CENTER | WPOS_TRYUP, FALSE, dialog_colors,
+ editcmd_dialog_raw_key_query_cb, NULL, NULL, heading);
+ g = GROUP (raw_dlg);
+ widget_want_tab (WIDGET (raw_dlg), TRUE);
+
+ group_add_widget (g, label_new (y, 3, query));
+ group_add_widget (g,
+ input_new (y++, 3 + wq + 1, input_colors, w - (6 + wq + 1), "", 0,
+ INPUT_COMPLETE_NONE));
+ if (cancel)
+ {
+ group_add_widget (g, hline_new (y++, -1, -1));
+ /* Button w/o hotkey to allow use any key as raw or macro one */
+ group_add_widget_autopos (g, button_new (y, 1, B_CANCEL, NORMAL_BUTTON, _("Cancel"), NULL),
+ WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL);
+ }
+
+ w = dlg_run (raw_dlg);
+ widget_destroy (WIDGET (raw_dlg));
+
+ return (cancel && (w == ESC_CHAR || w == B_CANCEL)) ? 0 : w;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editcomplete.c b/src/editor/editcomplete.c
new file mode 100644
index 0000000..06f304d
--- /dev/null
+++ b/src/editor/editcomplete.c
@@ -0,0 +1,483 @@
+/*
+ Editor word completion engine
+
+ Copyright (C) 2021-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2021-2022
+
+ 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 <ctype.h> /* isspace() */
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/search.h"
+#include "lib/strutil.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h" /* str_convert_to_input() */
+#endif
+#include "lib/tty/tty.h" /* LINES, COLS */
+#include "lib/widget.h"
+
+#include "src/setup.h" /* verbose */
+
+#include "editwidget.h"
+#include "edit-impl.h"
+#include "editsearch.h"
+
+#include "editcomplete.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Get current word under cursor
+ *
+ * @param esm status message window
+ * @param srch mc_search object
+ * @param word_start start word position
+ *
+ * @return newly allocated string or NULL if no any words under cursor
+ */
+
+static GString *
+edit_collect_completions_get_current_word (edit_search_status_msg_t * esm, mc_search_t * srch,
+ off_t word_start)
+{
+ WEdit *edit = esm->edit;
+ gsize len = 0;
+ GString *temp = NULL;
+
+ if (mc_search_run (srch, (void *) esm, word_start, edit->buffer.size, &len))
+ {
+ off_t i;
+
+ for (i = 0; i < (off_t) len; i++)
+ {
+ int chr;
+
+ chr = edit_buffer_get_byte (&edit->buffer, word_start + i);
+ if (!isspace (chr))
+ {
+ if (temp == NULL)
+ temp = g_string_sized_new (len);
+
+ g_string_append_c (temp, chr);
+ }
+ }
+ }
+
+ return temp;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * collect the possible completions from one buffer
+ */
+
+static void
+edit_collect_completion_from_one_buffer (gboolean active_buffer, GQueue ** compl,
+ mc_search_t * srch, edit_search_status_msg_t * esm,
+ off_t word_start, gsize word_len, off_t last_byte,
+ GString * current_word, int *max_width)
+{
+ GString *temp = NULL;
+ gsize len = 0;
+ off_t start = -1;
+
+ while (mc_search_run (srch, (void *) esm, start + 1, last_byte, &len))
+ {
+ gsize i;
+ int width;
+
+ if (temp == NULL)
+ temp = g_string_sized_new (8);
+ else
+ g_string_set_size (temp, 0);
+
+ start = srch->normal_offset;
+
+ /* add matched completion if not yet added */
+ for (i = 0; i < len; i++)
+ {
+ int ch;
+
+ ch = edit_buffer_get_byte (&esm->edit->buffer, start + i);
+ if (isspace (ch))
+ continue;
+
+ /* skip current word */
+ if (start + (off_t) i == word_start)
+ break;
+
+ g_string_append_c (temp, ch);
+ }
+
+ if (temp->len == 0)
+ continue;
+
+ if (current_word != NULL && g_string_equal (current_word, temp))
+ continue;
+
+ if (*compl == NULL)
+ *compl = g_queue_new ();
+ else
+ {
+ GList *l;
+
+ for (l = g_queue_peek_head_link (*compl); l != NULL; l = g_list_next (l))
+ {
+ GString *s = (GString *) l->data;
+
+ /* skip if already added */
+ if (strncmp (s->str + word_len, temp->str + word_len,
+ MAX (len, s->len) - word_len) == 0)
+ break;
+ }
+
+ if (l != NULL)
+ {
+ /* resort completion in main buffer only:
+ * these completions must be at the top of list in the completion dialog */
+ if (!active_buffer && l != g_queue_peek_tail_link (*compl))
+ {
+ /* move to the end */
+ g_queue_unlink (*compl, l);
+ g_queue_push_tail_link (*compl, l);
+ }
+
+ continue;
+ }
+ }
+
+#ifdef HAVE_CHARSET
+ {
+ GString *recoded;
+
+ recoded = str_nconvert_to_display (temp->str, temp->len);
+ if (recoded != NULL)
+ {
+ if (recoded->len != 0)
+ mc_g_string_copy (temp, recoded);
+
+ g_string_free (recoded, TRUE);
+ }
+ }
+#endif
+
+ if (active_buffer)
+ g_queue_push_tail (*compl, temp);
+ else
+ g_queue_push_head (*compl, temp);
+
+ start += len;
+
+ /* note the maximal length needed for the completion dialog */
+ width = str_term_width1 (temp->str);
+ *max_width = MAX (*max_width, width);
+
+ temp = NULL;
+ }
+
+ if (temp != NULL)
+ g_string_free (temp, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * collect the possible completions from all buffers
+ */
+
+static GQueue *
+edit_collect_completions (WEdit * edit, off_t word_start, gsize word_len,
+ const char *match_expr, int *max_width)
+{
+ GQueue *compl = NULL;
+ mc_search_t *srch;
+ off_t last_byte;
+ GString *current_word;
+ gboolean entire_file, all_files;
+ edit_search_status_msg_t esm;
+
+#ifdef HAVE_CHARSET
+ srch = mc_search_new (match_expr, cp_source);
+#else
+ srch = mc_search_new (match_expr, NULL);
+#endif
+ if (srch == NULL)
+ return NULL;
+
+ entire_file =
+ mc_config_get_bool (mc_global.main_config, CONFIG_APP_SECTION,
+ "editor_wordcompletion_collect_entire_file", FALSE);
+
+ last_byte = entire_file ? edit->buffer.size : word_start;
+
+ srch->search_type = MC_SEARCH_T_REGEX;
+ srch->is_case_sensitive = TRUE;
+ srch->search_fn = edit_search_cmd_callback;
+ srch->update_fn = edit_search_update_callback;
+
+ esm.first = TRUE;
+ esm.edit = edit;
+ esm.offset = entire_file ? 0 : word_start;
+
+ status_msg_init (STATUS_MSG (&esm), _("Collect completions"), 1.0, simple_status_msg_init_cb,
+ edit_search_status_update_cb, NULL);
+
+ current_word = edit_collect_completions_get_current_word (&esm, srch, word_start);
+
+ *max_width = 0;
+
+ /* collect completions from current buffer at first */
+ edit_collect_completion_from_one_buffer (TRUE, &compl, srch, &esm, word_start, word_len,
+ last_byte, current_word, max_width);
+
+ /* collect completions from other buffers */
+ all_files =
+ mc_config_get_bool (mc_global.main_config, CONFIG_APP_SECTION,
+ "editor_wordcompletion_collect_all_files", TRUE);
+ if (all_files)
+ {
+ const WGroup *owner = CONST_GROUP (CONST_WIDGET (edit)->owner);
+ gboolean saved_verbose;
+ GList *w;
+
+ /* don't show incorrect percentage in edit_search_status_update_cb() */
+ saved_verbose = verbose;
+ verbose = FALSE;
+
+ for (w = owner->widgets; w != NULL; w = g_list_next (w))
+ {
+ Widget *ww = WIDGET (w->data);
+ WEdit *e;
+
+ if (!edit_widget_is_editor (ww))
+ continue;
+
+ e = EDIT (ww);
+
+ if (e == edit)
+ continue;
+
+ /* search in entire file */
+ word_start = 0;
+ last_byte = e->buffer.size;
+ esm.edit = e;
+ esm.offset = 0;
+
+ edit_collect_completion_from_one_buffer (FALSE, &compl, srch, &esm, word_start,
+ word_len, last_byte, current_word, max_width);
+ }
+
+ verbose = saved_verbose;
+ }
+
+ status_msg_deinit (STATUS_MSG (&esm));
+ mc_search_free (srch);
+ if (current_word != NULL)
+ g_string_free (current_word, TRUE);
+
+ return compl;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Insert autocompleted word into editor.
+ *
+ * @param edit editor object
+ * @param completion word for completion
+ * @param word_len offset from beginning for insert
+ */
+
+static void
+edit_complete_word_insert_recoded_completion (WEdit * edit, char *completion, gsize word_len)
+{
+#ifdef HAVE_CHARSET
+ GString *temp;
+
+ temp = str_convert_to_input (completion);
+ if (temp != NULL)
+ {
+ for (completion = temp->str + word_len; *completion != '\0'; completion++)
+ edit_insert (edit, *completion);
+ g_string_free (temp, TRUE);
+ }
+#else
+ for (completion += word_len; *completion != '\0'; completion++)
+ edit_insert (edit, *completion);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_completion_string_free (gpointer data)
+{
+ g_string_free ((GString *) data, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/* let the user select its preferred completion */
+
+/* Public function for unit tests */
+char *
+edit_completion_dialog_show (const WEdit * edit, GQueue * compl, int max_width)
+{
+ const WRect *we = &CONST_WIDGET (edit)->rect;
+ int start_x, start_y, offset;
+ char *curr = NULL;
+ WDialog *compl_dlg;
+ WListbox *compl_list;
+ int compl_dlg_h; /* completion dialog height */
+ int compl_dlg_w; /* completion dialog width */
+ GList *i;
+
+ /* calculate the dialog metrics */
+ compl_dlg_h = g_queue_get_length (compl) + 2;
+ compl_dlg_w = max_width + 4;
+ start_x = we->x + edit->curs_col + edit->start_col + EDIT_TEXT_HORIZONTAL_OFFSET +
+ (edit->fullscreen ? 0 : 1) + edit_options.line_state_width;
+ start_y = we->y + edit->curs_row + EDIT_TEXT_VERTICAL_OFFSET + (edit->fullscreen ? 0 : 1) + 1;
+
+ if (start_x < 0)
+ start_x = 0;
+ if (start_x < we->x + 1)
+ start_x = we->x + 1 + edit_options.line_state_width;
+ if (compl_dlg_w > COLS)
+ compl_dlg_w = COLS;
+ if (compl_dlg_h > LINES - 2)
+ compl_dlg_h = LINES - 2;
+
+ offset = start_x + compl_dlg_w - COLS;
+ if (offset > 0)
+ start_x -= offset;
+ offset = start_y + compl_dlg_h - LINES;
+ if (offset > 0)
+ start_y -= offset;
+
+ /* create the dialog */
+ compl_dlg =
+ dlg_create (TRUE, start_y, start_x, compl_dlg_h, compl_dlg_w, WPOS_KEEP_DEFAULT, TRUE,
+ dialog_colors, NULL, NULL, "[Completion]", NULL);
+
+ /* create the listbox */
+ compl_list = listbox_new (1, 1, compl_dlg_h - 2, compl_dlg_w - 2, FALSE, NULL);
+
+ /* fill the listbox with the completions in the reverse order */
+ for (i = g_queue_peek_tail_link (compl); i != NULL; i = g_list_previous (i))
+ listbox_add_item (compl_list, LISTBOX_APPEND_AT_END, 0, ((GString *) i->data)->str, NULL,
+ FALSE);
+
+ group_add_widget (GROUP (compl_dlg), compl_list);
+
+ /* pop up the dialog and apply the chosen completion */
+ if (dlg_run (compl_dlg) == B_ENTER)
+ {
+ listbox_get_current (compl_list, &curr, NULL);
+ curr = g_strdup (curr);
+ }
+
+ /* destroy dialog before return */
+ widget_destroy (WIDGET (compl_dlg));
+
+ return curr;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Complete current word using regular expression search
+ * backwards beginning at the current cursor position.
+ */
+
+void
+edit_complete_word_cmd (WEdit * edit)
+{
+ off_t word_start = 0;
+ gsize word_len = 0;
+ GString *match_expr;
+ gsize i;
+ GQueue *compl; /* completions: list of GString* */
+ int max_width;
+
+ /* search start of word to be completed */
+ if (!edit_buffer_find_word_start (&edit->buffer, &word_start, &word_len))
+ return;
+
+ /* prepare match expression */
+ /* match_expr = g_strdup_printf ("\\b%.*s[a-zA-Z_0-9]+", word_len, bufpos); */
+ match_expr = g_string_new ("(^|\\s+|\\b)");
+ for (i = 0; i < word_len; i++)
+ g_string_append_c (match_expr, edit_buffer_get_byte (&edit->buffer, word_start + i));
+ g_string_append (match_expr,
+ "[^\\s\\.=\\+\\[\\]\\(\\)\\,\\;\\:\\\"\\'\\-\\?\\/\\|\\\\\\{\\}\\*\\&\\^\\%%\\$#@\\!]+");
+
+ /* collect possible completions */
+ compl = edit_collect_completions (edit, word_start, word_len, match_expr->str, &max_width);
+
+ g_string_free (match_expr, TRUE);
+
+ if (compl == NULL)
+ return;
+
+ if (g_queue_get_length (compl) == 1)
+ {
+ /* insert completed word if there is only one match */
+
+ GString *curr_compl;
+
+ curr_compl = (GString *) g_queue_peek_head (compl);
+ edit_complete_word_insert_recoded_completion (edit, curr_compl->str, word_len);
+ }
+ else
+ {
+ /* more than one possible completion => ask the user */
+
+ char *curr_compl;
+
+ /* let the user select the preferred completion */
+ curr_compl = edit_completion_dialog_show (edit, compl, max_width);
+ if (curr_compl != NULL)
+ {
+ edit_complete_word_insert_recoded_completion (edit, curr_compl, word_len);
+ g_free (curr_compl);
+ }
+ }
+
+ g_queue_free_full (compl, edit_completion_string_free);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editcomplete.h b/src/editor/editcomplete.h
new file mode 100644
index 0000000..90ded8d
--- /dev/null
+++ b/src/editor/editcomplete.h
@@ -0,0 +1,21 @@
+#ifndef MC__EDIT_COMPLETE_H
+#define MC__EDIT_COMPLETE_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* Public function for unit tests */
+char *edit_completion_dialog_show (const WEdit * edit, GQueue * compl, int max_width);
+
+void edit_complete_word_cmd (WEdit * edit);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__EDIT_COMPLETE_H */
diff --git a/src/editor/editdraw.c b/src/editor/editdraw.c
new file mode 100644
index 0000000..fbd1e09
--- /dev/null
+++ b/src/editor/editdraw.c
@@ -0,0 +1,1122 @@
+/*
+ Editor text drawing.
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1996, 1997
+ Andrew Borodin <aborodin@vmail.ru> 2012-2022
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ 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
+ * \brief Source: editor text drawing
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h" /* tty_printf() */
+#include "lib/tty/key.h" /* is_idle() */
+#include "lib/skin.h"
+#include "lib/strutil.h" /* utf string functions */
+#include "lib/util.h" /* is_printable() */
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "edit-impl.h"
+#include "editwidget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define MAX_LINE_LEN 1024
+
+/* Text styles */
+#define MOD_ABNORMAL (1 << 8)
+#define MOD_BOLD (1 << 9)
+#define MOD_MARKED (1 << 10)
+#define MOD_CURSOR (1 << 11)
+#define MOD_WHITESPACE (1 << 12)
+
+#define edit_move(x,y) widget_gotoyx(edit, y, x);
+
+#define key_pending(x) (!is_idle())
+
+#define EDITOR_MINIMUM_TERMINAL_WIDTH 30
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ unsigned int ch;
+ unsigned int style;
+} line_s;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+static inline void
+printwstr (const char *s, int len)
+{
+ if (len > 0)
+ tty_printf ("%-*.*s", len, len, s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+status_string (WEdit * edit, char *s, int w)
+{
+ char byte_str[16];
+
+ /*
+ * If we are at the end of file, print <EOF>,
+ * otherwise print the current character as is (if printable),
+ * as decimal and as hex.
+ */
+ if (edit->buffer.curs1 >= edit->buffer.size)
+ strcpy (byte_str, "<EOF> ");
+#ifdef HAVE_CHARSET
+ else if (edit->utf8)
+ {
+ unsigned int cur_utf;
+ int char_length = 1;
+
+ cur_utf = edit_buffer_get_utf (&edit->buffer, edit->buffer.curs1, &char_length);
+ if (char_length > 0)
+ g_snprintf (byte_str, sizeof (byte_str), "%04u 0x%03X",
+ (unsigned) cur_utf, (unsigned) cur_utf);
+ else
+ {
+ cur_utf = edit_buffer_get_current_byte (&edit->buffer);
+ g_snprintf (byte_str, sizeof (byte_str), "%04d 0x%03X",
+ (int) cur_utf, (unsigned) cur_utf);
+ }
+ }
+#endif
+ else
+ {
+ unsigned char cur_byte;
+
+ cur_byte = edit_buffer_get_current_byte (&edit->buffer);
+ g_snprintf (byte_str, sizeof (byte_str), "%4d 0x%03X", (int) cur_byte, (unsigned) cur_byte);
+ }
+
+ /* The field lengths just prevent the status line from shortening too much */
+ if (edit_options.simple_statusbar)
+ g_snprintf (s, w,
+ "%c%c%c%c %3ld %5ld/%ld %6ld/%ld %s %s",
+ edit->mark1 != edit->mark2 ? (edit->column_highlight ? 'C' : 'B') : '-',
+ edit->modified ? 'M' : '-',
+ macro_index < 0 ? '-' : 'R',
+ edit->overwrite == 0 ? '-' : 'O',
+ edit->curs_col + edit->over_col,
+ edit->buffer.curs_line + 1,
+ edit->buffer.lines + 1, (long) edit->buffer.curs1, (long) edit->buffer.size,
+ byte_str,
+#ifdef HAVE_CHARSET
+ mc_global.source_codepage >= 0 ? get_codepage_id (mc_global.source_codepage) :
+#endif
+ "");
+ else
+ g_snprintf (s, w,
+ "[%c%c%c%c] %2ld L:[%3ld+%2ld %3ld/%3ld] *(%-4ld/%4ldb) %s %s",
+ edit->mark1 != edit->mark2 ? (edit->column_highlight ? 'C' : 'B') : '-',
+ edit->modified ? 'M' : '-',
+ macro_index < 0 ? '-' : 'R',
+ edit->overwrite == 0 ? '-' : 'O',
+ edit->curs_col + edit->over_col,
+ edit->start_line + 1,
+ edit->curs_row,
+ edit->buffer.curs_line + 1,
+ edit->buffer.lines + 1, (long) edit->buffer.curs1, (long) edit->buffer.size,
+ byte_str,
+#ifdef HAVE_CHARSET
+ mc_global.source_codepage >= 0 ? get_codepage_id (mc_global.source_codepage) :
+#endif
+ "");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Draw the status line at the top of the screen for fullscreen editor window.
+ *
+ * @param edit editor object
+ * @param color color pair
+ */
+
+static inline void
+edit_status_fullscreen (WEdit * edit, int color)
+{
+ Widget *h = WIDGET (WIDGET (edit)->owner);
+ const int w = h->rect.cols;
+ const int gap = 3; /* between the filename and the status */
+ const int right_gap = 5; /* at the right end of the screen */
+ const int preferred_fname_len = 16;
+ char *status;
+ size_t status_size;
+ int status_len;
+ const char *fname = "";
+ int fname_len;
+
+ status_size = w + 1;
+ status = g_malloc (status_size);
+ status_string (edit, status, status_size);
+ status_len = (int) str_term_width1 (status);
+
+ if (edit->filename_vpath != NULL)
+ {
+ fname = vfs_path_get_last_path_str (edit->filename_vpath);
+
+ if (!edit_options.state_full_filename)
+ fname = x_basename (fname);
+ }
+
+ fname_len = str_term_width1 (fname);
+ if (fname_len < preferred_fname_len)
+ fname_len = preferred_fname_len;
+
+ if (fname_len + gap + status_len + right_gap >= w)
+ {
+ if (preferred_fname_len + gap + status_len + right_gap >= w)
+ fname_len = preferred_fname_len;
+ else
+ fname_len = w - (gap + status_len + right_gap);
+ fname = str_trunc (fname, fname_len);
+ }
+
+ widget_gotoyx (h, 0, 0);
+ tty_setcolor (color);
+ printwstr (fname, fname_len + gap);
+ printwstr (status, w - (fname_len + gap));
+
+ if (edit_options.simple_statusbar && w > EDITOR_MINIMUM_TERMINAL_WIDTH)
+ {
+ int percent;
+
+ percent = edit_buffer_calc_percent (&edit->buffer, edit->buffer.curs1);
+ widget_gotoyx (h, 0, w - 6 - 6);
+ tty_printf (" %3d%%", percent);
+ }
+
+ g_free (status);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Draw status line for editor window if window is not in fullscreen mode.
+ *
+ * @param edit editor object
+ */
+
+static inline void
+edit_status_window (WEdit * edit)
+{
+ Widget *w = WIDGET (edit);
+ int y, x;
+ int cols = w->rect.cols;
+
+ tty_setcolor (STATUSBAR_COLOR);
+
+ if (cols > 5)
+ {
+ const char *fname = N_("NoName");
+
+ if (edit->filename_vpath != NULL)
+ {
+ fname = vfs_path_get_last_path_str (edit->filename_vpath);
+
+ if (!edit_options.state_full_filename)
+ fname = x_basename (fname);
+ }
+#ifdef ENABLE_NLS
+ else
+ fname = _(fname);
+#endif
+
+ edit_move (2, 0);
+ tty_printf ("[%s]", str_term_trim (fname, w->rect.cols - 8 - 6));
+ }
+
+ tty_getyx (&y, &x);
+ x -= w->rect.x;
+ x += 4;
+ if (x + 6 <= cols - 2 - 6)
+ {
+ edit_move (x, 0);
+ tty_printf ("[%c%c%c%c]",
+ edit->mark1 != edit->mark2 ? (edit->column_highlight ? 'C' : 'B') : '-',
+ edit->modified ? 'M' : '-',
+ macro_index < 0 ? '-' : 'R', edit->overwrite == 0 ? '-' : 'O');
+ }
+
+ if (cols > 30)
+ {
+ edit_move (2, w->rect.lines - 1);
+ tty_printf ("%3ld %5ld/%ld %6ld/%ld",
+ edit->curs_col + edit->over_col,
+ edit->buffer.curs_line + 1, edit->buffer.lines + 1, (long) edit->buffer.curs1,
+ (long) edit->buffer.size);
+ }
+
+ /*
+ * If we are at the end of file, print <EOF>,
+ * otherwise print the current character as is (if printable),
+ * as decimal and as hex.
+ */
+ if (cols > 46)
+ {
+ edit_move (32, w->rect.lines - 1);
+ if (edit->buffer.curs1 >= edit->buffer.size)
+ tty_print_string ("[<EOF> ]");
+#ifdef HAVE_CHARSET
+ else if (edit->utf8)
+ {
+ unsigned int cur_utf;
+ int char_length = 1;
+
+ cur_utf = edit_buffer_get_utf (&edit->buffer, edit->buffer.curs1, &char_length);
+ if (char_length <= 0)
+ cur_utf = edit_buffer_get_current_byte (&edit->buffer);
+ tty_printf ("[%05u 0x%04X]", cur_utf, cur_utf);
+ }
+#endif
+ else
+ {
+ unsigned char cur_byte;
+
+ cur_byte = edit_buffer_get_current_byte (&edit->buffer);
+ tty_printf ("[%05u 0x%04X]", (unsigned int) cur_byte, (unsigned int) cur_byte);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Draw a frame around edit area.
+ *
+ * @param edit editor object
+ * @param color color pair
+ * @param active TRUE if editor object is focused
+ */
+
+static inline void
+edit_draw_frame (const WEdit * edit, int color, gboolean active)
+{
+ const Widget *w = CONST_WIDGET (edit);
+
+ /* draw a frame around edit area */
+ tty_setcolor (color);
+ /* draw double frame for active window if skin supports that */
+ tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, !active);
+ /* draw a drag marker */
+ if (edit->drag_state == MCEDIT_DRAG_NONE)
+ {
+ tty_setcolor (EDITOR_FRAME_DRAG);
+ widget_gotoyx (w, w->rect.lines - 1, w->rect.cols - 1);
+ tty_print_alt_char (ACS_LRCORNER, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Draw a window control buttons.
+ *
+ * @param edit editor object
+ * @param color color pair
+ */
+
+static inline void
+edit_draw_window_icons (const WEdit * edit, int color)
+{
+ const Widget *w = CONST_WIDGET (edit);
+ char tmp[17];
+
+ tty_setcolor (color);
+ if (edit->fullscreen)
+ widget_gotoyx (w->owner, 0, WIDGET (w->owner)->rect.cols - 6);
+ else
+ widget_gotoyx (w, 0, w->rect.cols - 8);
+ g_snprintf (tmp, sizeof (tmp), "[%s][%s]", edit_window_state_char, edit_window_close_char);
+ tty_print_string (tmp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+print_to_widget (WEdit * edit, long row, int start_col, int start_col_real,
+ long end_col, line_s line[], char *status, int bookmarked)
+{
+ Widget *w = WIDGET (edit);
+ line_s *p;
+ int x, x1, y, cols_to_skip;
+ int i;
+ int wrap_start;
+ int len;
+
+ x = start_col_real;
+ x1 = start_col + EDIT_TEXT_HORIZONTAL_OFFSET + edit_options.line_state_width;
+ y = row + EDIT_TEXT_VERTICAL_OFFSET;
+ cols_to_skip = abs (x);
+
+ if (!edit->fullscreen)
+ {
+ x1++;
+ y++;
+ }
+
+ tty_setcolor (EDITOR_NORMAL_COLOR);
+ if (bookmarked != 0)
+ tty_setcolor (bookmarked);
+
+ len = end_col + 1 - start_col;
+ wrap_start = edit_options.word_wrap_line_length + edit->start_col;
+
+ if (len > 0 && w->rect.y + y >= 0)
+ {
+ if (!edit_options.show_right_margin || wrap_start > end_col)
+ tty_draw_hline (w->rect.y + y, w->rect.x + x1, ' ', len);
+ else if (wrap_start < 0)
+ {
+ tty_setcolor (EDITOR_RIGHT_MARGIN_COLOR);
+ tty_draw_hline (w->rect.y + y, w->rect.x + x1, ' ', len);
+ }
+ else
+ {
+ if (wrap_start > 0)
+ tty_draw_hline (w->rect.y + y, w->rect.x + x1, ' ', wrap_start);
+
+ len -= wrap_start;
+ if (len > 0)
+ {
+ tty_setcolor (EDITOR_RIGHT_MARGIN_COLOR);
+ tty_draw_hline (w->rect.y + y, w->rect.x + x1 + wrap_start, ' ', len);
+ }
+ }
+ }
+
+ if (edit_options.line_state)
+ {
+ tty_setcolor (LINE_STATE_COLOR);
+
+ for (i = 0; i < LINE_STATE_WIDTH; i++)
+ {
+ edit_move (x1 + i - edit_options.line_state_width, y);
+ if (status[i] == '\0')
+ status[i] = ' ';
+ tty_print_char (status[i]);
+ }
+ }
+
+ edit_move (x1, y);
+
+ i = 1;
+ for (p = line; p->ch != 0; p++)
+ {
+ int style;
+ unsigned int textchar;
+ int color;
+
+ if (cols_to_skip != 0)
+ {
+ cols_to_skip--;
+ continue;
+ }
+
+ style = p->style & 0xFF00;
+ textchar = p->ch;
+ /* If non-printable - use black background */
+ color = (style & MOD_ABNORMAL) != 0 ? 0 : p->style >> 16;
+
+ if ((style & MOD_WHITESPACE) != 0)
+ {
+ if ((style & MOD_MARKED) == 0)
+ tty_setcolor (EDITOR_WHITESPACE_COLOR);
+ else
+ {
+ textchar = ' ';
+ tty_setcolor (EDITOR_MARKED_COLOR);
+ }
+ }
+ else if ((style & MOD_BOLD) != 0)
+ tty_setcolor (EDITOR_BOLD_COLOR);
+ else if ((style & MOD_MARKED) != 0)
+ tty_setcolor (EDITOR_MARKED_COLOR);
+ else
+ tty_lowlevel_setcolor (color);
+
+ if (edit_options.show_right_margin)
+ {
+ if (i > edit_options.word_wrap_line_length + edit->start_col)
+ tty_setcolor (EDITOR_RIGHT_MARGIN_COLOR);
+ i++;
+ }
+
+ tty_print_anychar (textchar);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** b is a pointer to the beginning of the line */
+
+static void
+edit_draw_this_line (WEdit * edit, off_t b, long row, long start_col, long end_col)
+{
+ Widget *w = WIDGET (edit);
+ line_s line[MAX_LINE_LEN];
+ line_s *p = line;
+ off_t q;
+ int col, start_col_real;
+ int abn_style;
+ int book_mark = 0;
+ char line_stat[LINE_STATE_WIDTH + 1] = "\0";
+
+ if (row > w->rect.lines - 1 - EDIT_TEXT_VERTICAL_OFFSET - 2 * (edit->fullscreen ? 0 : 1))
+ return;
+
+ if (book_mark_query_color (edit, edit->start_line + row, BOOK_MARK_COLOR))
+ book_mark = BOOK_MARK_COLOR;
+ else if (book_mark_query_color (edit, edit->start_line + row, BOOK_MARK_FOUND_COLOR))
+ book_mark = BOOK_MARK_FOUND_COLOR;
+
+ if (book_mark != 0)
+ abn_style = book_mark << 16;
+ else
+ abn_style = MOD_ABNORMAL;
+
+ end_col -= EDIT_TEXT_HORIZONTAL_OFFSET + edit_options.line_state_width;
+ if (!edit->fullscreen)
+ {
+ end_col--;
+ if (w->rect.x + w->rect.cols <= WIDGET (w->owner)->rect.cols)
+ end_col--;
+ }
+
+ q = edit_move_forward3 (edit, b, start_col - edit->start_col, 0);
+ col = (int) edit_move_forward3 (edit, b, 0, q);
+ start_col_real = col + edit->start_col;
+
+ if (edit_options.line_state)
+ {
+ long cur_line;
+
+ cur_line = edit->start_line + row;
+ if (cur_line <= edit->buffer.lines)
+ g_snprintf (line_stat, sizeof (line_stat), "%7ld ", cur_line + 1);
+ else
+ {
+ memset (line_stat, ' ', LINE_STATE_WIDTH);
+ line_stat[LINE_STATE_WIDTH] = '\0';
+ }
+
+ if (book_mark_query_color (edit, cur_line, BOOK_MARK_COLOR))
+ g_snprintf (line_stat, 2, "*");
+ }
+
+ if (col <= -(edit->start_col + 16))
+ start_col_real = start_col = 0;
+ else
+ {
+ off_t m1 = 0, m2 = 0;
+
+ eval_marks (edit, &m1, &m2);
+
+ if (row <= edit->buffer.lines - edit->start_line)
+ {
+ off_t tws = 0;
+
+ if (edit_options.visible_tws && tty_use_colors ())
+ for (tws = edit_buffer_get_eol (&edit->buffer, b); tws > b; tws--)
+ {
+ unsigned int c;
+
+ c = edit_buffer_get_byte (&edit->buffer, tws - 1);
+ if (!whitespace (c))
+ break;
+ }
+
+ while (col <= end_col - edit->start_col)
+ {
+ int char_length = 1;
+ unsigned int c;
+ gboolean wide_width_char = FALSE;
+ gboolean control_char = FALSE;
+
+ p->ch = 0;
+ p->style = q == edit->buffer.curs1 ? MOD_CURSOR : 0;
+
+ if (q >= m1 && q < m2)
+ {
+ if (!edit->column_highlight)
+ p->style |= MOD_MARKED;
+ else
+ {
+ long x, cl;
+
+ x = (long) edit_move_forward3 (edit, b, 0, q);
+ cl = MIN (edit->column1, edit->column2);
+ if (x >= cl)
+ {
+ cl = MAX (edit->column1, edit->column2);
+ if (x < cl)
+ p->style |= MOD_MARKED;
+ }
+ }
+ }
+
+ if (q == edit->bracket)
+ p->style |= MOD_BOLD;
+ if (q >= edit->found_start && q < (off_t) (edit->found_start + edit->found_len))
+ p->style |= MOD_BOLD;
+
+#ifdef HAVE_CHARSET
+ if (edit->utf8)
+ c = edit_buffer_get_utf (&edit->buffer, q, &char_length);
+ else
+#endif
+ c = edit_buffer_get_byte (&edit->buffer, q);
+
+ /* we don't use bg for mc - fg contains both */
+ if (book_mark != 0)
+ p->style |= book_mark << 16;
+ else
+ {
+ int color;
+
+ color = edit_get_syntax_color (edit, q);
+ p->style |= color << 16;
+ }
+
+ switch (c)
+ {
+ case '\n':
+ col = end_col - edit->start_col + 1; /* quit */
+ break;
+
+ case '\t':
+ {
+ int tab_over;
+ int i;
+
+ i = TAB_SIZE - ((int) col % TAB_SIZE);
+ tab_over = (end_col - edit->start_col) - (col + i - 1);
+ if (tab_over < 0)
+ i += tab_over;
+ col += i;
+ if ((edit_options.visible_tabs || (edit_options.visible_tws && q >= tws))
+ && enable_show_tabs_tws && tty_use_colors ())
+ {
+ if ((p->style & MOD_MARKED) != 0)
+ c = p->style;
+ else if (book_mark != 0)
+ c |= book_mark << 16;
+ else
+ c = p->style | MOD_WHITESPACE;
+ if (i > 2)
+ {
+ p->ch = '<';
+ p->style = c;
+ p++;
+ while (--i > 1)
+ {
+ p->ch = '-';
+ p->style = c;
+ p++;
+ }
+ p->ch = '>';
+ p->style = c;
+ p++;
+ }
+ else if (i > 1)
+ {
+ p->ch = '<';
+ p->style = c;
+ p++;
+ p->ch = '>';
+ p->style = c;
+ p++;
+ }
+ else
+ {
+ p->ch = '>';
+ p->style = c;
+ p++;
+ }
+ }
+ else if (edit_options.visible_tws && q >= tws && enable_show_tabs_tws
+ && tty_use_colors ())
+ {
+ p->ch = '.';
+ p->style |= MOD_WHITESPACE;
+ c = p->style & ~MOD_CURSOR;
+ p++;
+ while (--i != 0)
+ {
+ p->ch = ' ';
+ p->style = c;
+ p++;
+ }
+ }
+ else
+ {
+ p->ch |= ' ';
+ c = p->style & ~MOD_CURSOR;
+ p++;
+ while (--i != 0)
+ {
+ p->ch = ' ';
+ p->style = c;
+ p++;
+ }
+ }
+ }
+ break;
+
+ case ' ':
+ if (edit_options.visible_tws && q >= tws && enable_show_tabs_tws
+ && tty_use_colors ())
+ {
+ p->ch = '.';
+ p->style |= MOD_WHITESPACE;
+ p++;
+ col++;
+ break;
+ }
+ MC_FALLTHROUGH;
+
+ default:
+#ifdef HAVE_CHARSET
+ if (mc_global.utf8_display)
+ {
+ if (!edit->utf8)
+ c = convert_from_8bit_to_utf_c ((unsigned char) c, edit->converter);
+ else if (g_unichar_iswide (c))
+ {
+ wide_width_char = TRUE;
+ col++;
+ }
+ }
+ else if (edit->utf8)
+ c = convert_from_utf_to_current_c (c, edit->converter);
+ else
+ c = convert_to_display_c (c);
+#endif
+
+ /* Caret notation for control characters */
+ if (c < 32)
+ {
+ p->ch = '^';
+ p->style = abn_style;
+ p++;
+ p->ch = c + 0x40;
+ p->style = abn_style;
+ p++;
+ col += 2;
+ control_char = TRUE;
+ break;
+ }
+ if (c == 127)
+ {
+ p->ch = '^';
+ p->style = abn_style;
+ p++;
+ p->ch = '?';
+ p->style = abn_style;
+ p++;
+ col += 2;
+ control_char = TRUE;
+ break;
+ }
+#ifdef HAVE_CHARSET
+ if (edit->utf8)
+ {
+ if (g_unichar_isprint (c))
+ p->ch = c;
+ else
+ {
+ p->ch = '.';
+ p->style = abn_style;
+ }
+ p++;
+ }
+ else
+#endif
+ {
+ if ((mc_global.utf8_display && g_unichar_isprint (c)) ||
+ (!mc_global.utf8_display && is_printable (c)))
+ {
+ p->ch = c;
+ p++;
+ }
+ else
+ {
+ p->ch = '.';
+ p->style = abn_style;
+ p++;
+ }
+ }
+ col++;
+ break;
+ } /* case */
+
+ q++;
+ if (char_length > 1)
+ q += char_length - 1;
+
+ if (col > (end_col - edit->start_col + 1))
+ {
+ if (wide_width_char)
+ {
+ p--;
+ break;
+ }
+ if (control_char)
+ {
+ p -= 2;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ p->ch = 0;
+
+ print_to_widget (edit, row, start_col, start_col_real, end_col, line, line_stat, book_mark);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_draw_this_char (WEdit * edit, off_t curs, long row, long start_column, long end_column)
+{
+ off_t b;
+
+ b = edit_buffer_get_bol (&edit->buffer, curs);
+ edit_draw_this_line (edit, b, row, start_column, end_column);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** cursor must be in screen for other than REDRAW_PAGE passed in force */
+
+static inline void
+render_edit_text (WEdit * edit, long start_row, long start_column, long end_row, long end_column)
+{
+ static long prev_curs_row = 0;
+ static off_t prev_curs = 0;
+
+ Widget *we = WIDGET (edit);
+ Widget *wh = WIDGET (we->owner);
+ WRect *w = &we->rect;
+
+ int force = edit->force;
+ int y1, x1, y2, x2;
+ int last_line, last_column;
+
+ /* draw only visible region */
+
+ last_line = wh->rect.y + wh->rect.lines - 1;
+
+ y1 = w->y;
+ if (y1 > last_line - 1 /* buttonbar */ )
+ return;
+
+ last_column = wh->rect.x + wh->rect.cols - 1;
+
+ x1 = w->x;
+ if (x1 > last_column)
+ return;
+
+ y2 = w->y + w->lines - 1;
+ if (y2 < wh->rect.y + 1 /* menubar */ )
+ return;
+
+ x2 = w->x + w->cols - 1;
+ if (x2 < wh->rect.x)
+ return;
+
+ if ((force & REDRAW_IN_BOUNDS) == 0)
+ {
+ /* !REDRAW_IN_BOUNDS means to ignore bounds and redraw whole rows */
+ /* draw only visible region */
+
+ if (y2 <= last_line - 1 /* buttonbar */ )
+ end_row = w->lines - 1;
+ else if (y1 >= wh->rect.y + 1 /* menubar */ )
+ end_row = wh->rect.lines - 1 - y1 - 1;
+ else
+ end_row = start_row + wh->rect.lines - 1 - 1;
+
+ if (x2 <= last_column)
+ end_column = w->cols - 1;
+ else if (x1 >= wh->rect.x)
+ end_column = wh->rect.cols - 1 - x1;
+ else
+ end_column = start_column + wh->rect.cols - 1;
+ }
+
+ /*
+ * If the position of the page has not moved then we can draw the cursor
+ * character only. This will prevent line flicker when using arrow keys.
+ */
+ if ((force & REDRAW_CHAR_ONLY) == 0 || (force & REDRAW_PAGE) != 0)
+ {
+ long row = 0;
+ long b;
+
+ if ((force & REDRAW_PAGE) != 0)
+ {
+ b = edit_buffer_get_forward_offset (&edit->buffer, edit->start_display, start_row, 0);
+ for (row = start_row; row <= end_row; row++)
+ {
+ if (key_pending (edit))
+ return;
+ edit_draw_this_line (edit, b, row, start_column, end_column);
+ b = edit_buffer_get_forward_offset (&edit->buffer, b, 1, 0);
+ }
+ }
+ else
+ {
+ long curs_row = edit->curs_row;
+
+ if ((force & REDRAW_BEFORE_CURSOR) != 0 && start_row < curs_row)
+ {
+ long upto;
+
+ b = edit->start_display;
+ upto = MIN (curs_row - 1, end_row);
+ for (row = start_row; row <= upto; row++)
+ {
+ if (key_pending (edit))
+ return;
+ edit_draw_this_line (edit, b, row, start_column, end_column);
+ b = edit_buffer_get_forward_offset (&edit->buffer, b, 1, 0);
+ }
+ }
+
+ /* if (force & REDRAW_LINE) ---> default */
+ b = edit_buffer_get_current_bol (&edit->buffer);
+ if (curs_row >= start_row && curs_row <= end_row)
+ {
+ if (key_pending (edit))
+ return;
+ edit_draw_this_line (edit, b, curs_row, start_column, end_column);
+ }
+
+ if ((force & REDRAW_AFTER_CURSOR) != 0 && end_row > curs_row)
+ {
+ b = edit_buffer_get_forward_offset (&edit->buffer, b, 1, 0);
+ for (row = MAX (curs_row + 1, start_row); row <= end_row; row++)
+ {
+ if (key_pending (edit))
+ return;
+ edit_draw_this_line (edit, b, row, start_column, end_column);
+ b = edit_buffer_get_forward_offset (&edit->buffer, b, 1, 0);
+ }
+ }
+
+ if ((force & REDRAW_LINE_ABOVE) != 0 && curs_row >= 1)
+ {
+ row = curs_row - 1;
+ b = edit_buffer_get_backward_offset (&edit->buffer,
+ edit_buffer_get_current_bol (&edit->buffer),
+ 1);
+ if (row >= start_row && row <= end_row)
+ {
+ if (key_pending (edit))
+ return;
+ edit_draw_this_line (edit, b, row, start_column, end_column);
+ }
+ }
+
+ if ((force & REDRAW_LINE_BELOW) != 0 && row < w->lines - 1)
+ {
+ row = curs_row + 1;
+ b = edit_buffer_get_current_bol (&edit->buffer);
+ b = edit_buffer_get_forward_offset (&edit->buffer, b, 1, 0);
+ if (row >= start_row && row <= end_row)
+ {
+ if (key_pending (edit))
+ return;
+ edit_draw_this_line (edit, b, row, start_column, end_column);
+ }
+ }
+ }
+ }
+ else if (prev_curs_row < edit->curs_row)
+ {
+ /* with the new text highlighting, we must draw from the top down */
+ edit_draw_this_char (edit, prev_curs, prev_curs_row, start_column, end_column);
+ edit_draw_this_char (edit, edit->buffer.curs1, edit->curs_row, start_column, end_column);
+ }
+ else
+ {
+ edit_draw_this_char (edit, edit->buffer.curs1, edit->curs_row, start_column, end_column);
+ edit_draw_this_char (edit, prev_curs, prev_curs_row, start_column, end_column);
+ }
+
+ edit->force = 0;
+
+ prev_curs_row = edit->curs_row;
+ prev_curs = edit->buffer.curs1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_render (WEdit * edit, int page, int row_start, int col_start, int row_end, int col_end)
+{
+ if (page != 0) /* if it was an expose event, 'page' would be set */
+ edit->force |= REDRAW_PAGE | REDRAW_IN_BOUNDS;
+
+ render_edit_text (edit, row_start, col_start, row_end, col_end);
+
+ /*
+ * edit->force != 0 means a key was pending and the redraw
+ * was halted, so next time we must redraw everything in case stuff
+ * was left undrawn from a previous key press.
+ */
+ if (edit->force != 0)
+ edit->force |= REDRAW_PAGE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_status (WEdit * edit, gboolean active)
+{
+ int color;
+
+ if (edit->fullscreen)
+ {
+ color = STATUSBAR_COLOR;
+ edit_status_fullscreen (edit, color);
+ }
+ else
+ {
+ color = edit->drag_state != MCEDIT_DRAG_NONE ? EDITOR_FRAME_DRAG : active ?
+ EDITOR_FRAME_ACTIVE : EDITOR_FRAME;
+ edit_draw_frame (edit, color, active);
+ edit_status_window (edit);
+ }
+
+ edit_draw_window_icons (edit, color);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** this scrolls the text so that cursor is on the screen */
+void
+edit_scroll_screen_over_cursor (WEdit * edit)
+{
+ WRect *w = &WIDGET (edit)->rect;
+
+ long p;
+ long outby;
+ int b_extreme, t_extreme, l_extreme, r_extreme;
+
+ if (w->lines <= 0 || w->cols <= 0)
+ return;
+
+ rect_resize (w, -EDIT_TEXT_VERTICAL_OFFSET,
+ -(EDIT_TEXT_HORIZONTAL_OFFSET + edit_options.line_state_width));
+
+ if (!edit->fullscreen)
+ rect_grow (w, -1, -1);
+
+ r_extreme = EDIT_RIGHT_EXTREME;
+ l_extreme = EDIT_LEFT_EXTREME;
+ b_extreme = EDIT_BOTTOM_EXTREME;
+ t_extreme = EDIT_TOP_EXTREME;
+ if (edit->found_len != 0)
+ {
+ b_extreme = MAX (w->lines / 4, b_extreme);
+ t_extreme = MAX (w->lines / 4, t_extreme);
+ }
+ if (b_extreme + t_extreme + 1 > w->lines)
+ {
+ int n;
+
+ n = b_extreme + t_extreme;
+ if (n == 0)
+ n = 1;
+ b_extreme = (b_extreme * (w->lines - 1)) / n;
+ t_extreme = (t_extreme * (w->lines - 1)) / n;
+ }
+ if (l_extreme + r_extreme + 1 > w->cols)
+ {
+ int n;
+
+ n = l_extreme + r_extreme;
+ if (n == 0)
+ n = 1;
+ l_extreme = (l_extreme * (w->cols - 1)) / n;
+ r_extreme = (r_extreme * (w->cols - 1)) / n;
+ }
+ p = edit_get_col (edit) + edit->over_col;
+ edit_update_curs_row (edit);
+ outby = p + edit->start_col - w->cols + 1 + (r_extreme + edit->found_len);
+ if (outby > 0)
+ edit_scroll_right (edit, outby);
+ outby = l_extreme - p - edit->start_col;
+ if (outby > 0)
+ edit_scroll_left (edit, outby);
+ p = edit->curs_row;
+ outby = p - w->lines + 1 + b_extreme;
+ if (outby > 0)
+ edit_scroll_downward (edit, outby);
+ outby = t_extreme - p;
+ if (outby > 0)
+ edit_scroll_upward (edit, outby);
+ edit_update_curs_row (edit);
+
+ rect_resize (w, EDIT_TEXT_VERTICAL_OFFSET,
+ EDIT_TEXT_HORIZONTAL_OFFSET + edit_options.line_state_width);
+ if (!edit->fullscreen)
+ rect_grow (w, 1, 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_render_keypress (WEdit * edit)
+{
+ edit_render (edit, 0, 0, 0, 0, 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editmacros.c b/src/editor/editmacros.c
new file mode 100644
index 0000000..8545d67
--- /dev/null
+++ b/src/editor/editmacros.c
@@ -0,0 +1,437 @@
+/*
+ Editor macros engine
+
+ Copyright (C) 2001-2023
+ Free Software Foundation, Inc.
+
+ 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 <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/mcconfig.h"
+#include "lib/tty/key.h" /* tty_keyname_to_keycode*() */
+#include "lib/keybind.h" /* keybind_lookup_actionname() */
+#include "lib/fileloc.h"
+
+#include "src/setup.h" /* macro_action_t */
+#include "src/history.h" /* MC_HISTORY_EDIT_REPEAT */
+
+#include "editwidget.h"
+
+#include "editmacros.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+edit_macro_comparator (gconstpointer * macro1, gconstpointer * macro2)
+{
+ const macros_t *m1 = (const macros_t *) macro1;
+ const macros_t *m2 = (const macros_t *) macro2;
+
+ return m1->hotkey - m2->hotkey;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_macro_sort_by_hotkey (void)
+{
+ if (macros_list != NULL && macros_list->len != 0)
+ g_array_sort (macros_list, (GCompareFunc) edit_macro_comparator);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+edit_get_macro (WEdit * edit, int hotkey)
+{
+ macros_t *array_start;
+ macros_t *result;
+ macros_t search_macro = {
+ .hotkey = hotkey
+ };
+
+ (void) edit;
+
+ result = bsearch (&search_macro, macros_list->data, macros_list->len,
+ sizeof (macros_t), (GCompareFunc) edit_macro_comparator);
+
+ if (result == NULL || result->macro == NULL)
+ return (-1);
+
+ array_start = &g_array_index (macros_list, struct macros_t, 0);
+
+ return (int) (result - array_start);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** returns FALSE on error */
+static gboolean
+edit_delete_macro (WEdit * edit, int hotkey)
+{
+ mc_config_t *macros_config = NULL;
+ const char *section_name = "editor";
+ gchar *macros_fname;
+ int indx;
+ char *skeyname;
+
+ /* clear array of actions for current hotkey */
+ while ((indx = edit_get_macro (edit, hotkey)) != -1)
+ {
+ macros_t *macros;
+
+ macros = &g_array_index (macros_list, struct macros_t, indx);
+ g_array_free (macros->macro, TRUE);
+ g_array_remove_index (macros_list, indx);
+ }
+
+ macros_fname = mc_config_get_full_path (MC_MACRO_FILE);
+ macros_config = mc_config_init (macros_fname, FALSE);
+ g_free (macros_fname);
+
+ if (macros_config == NULL)
+ return FALSE;
+
+ skeyname = tty_keycode_to_keyname (hotkey);
+ while (mc_config_del_key (macros_config, section_name, skeyname))
+ ;
+ g_free (skeyname);
+ mc_config_save_file (macros_config, NULL);
+ mc_config_deinit (macros_config);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** returns FALSE on error */
+gboolean
+edit_store_macro_cmd (WEdit * edit)
+{
+ int i;
+ int hotkey;
+ GString *macros_string = NULL;
+ const char *section_name = "editor";
+ gchar *macros_fname;
+ GArray *macros = NULL;
+ int tmp_act;
+ mc_config_t *macros_config;
+ char *skeyname;
+
+ hotkey =
+ editcmd_dialog_raw_key_query (_("Save macro"), _("Press the macro's new hotkey:"), TRUE);
+ if (hotkey == ESC_CHAR)
+ return FALSE;
+
+ tmp_act = keybind_lookup_keymap_command (WIDGET (edit)->keymap, hotkey);
+ /* return FALSE if try assign macro into restricted hotkeys */
+ if (tmp_act == CK_MacroStartRecord
+ || tmp_act == CK_MacroStopRecord || tmp_act == CK_MacroStartStopRecord)
+ return FALSE;
+
+ edit_delete_macro (edit, hotkey);
+
+ macros_fname = mc_config_get_full_path (MC_MACRO_FILE);
+ macros_config = mc_config_init (macros_fname, FALSE);
+ g_free (macros_fname);
+
+ if (macros_config == NULL)
+ return FALSE;
+
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+
+ skeyname = tty_keycode_to_keyname (hotkey);
+
+ for (i = 0; i < macro_index; i++)
+ {
+ macro_action_t m_act;
+ const char *action_name;
+
+ action_name = keybind_lookup_actionname (record_macro_buf[i].action);
+ if (action_name == NULL)
+ break;
+
+ if (macros == NULL)
+ {
+ macros = g_array_new (TRUE, FALSE, sizeof (macro_action_t));
+ macros_string = g_string_sized_new (250);
+ }
+
+ m_act.action = record_macro_buf[i].action;
+ m_act.ch = record_macro_buf[i].ch;
+ g_array_append_val (macros, m_act);
+ g_string_append_printf (macros_string, "%s:%i;", action_name, (int) record_macro_buf[i].ch);
+ }
+
+ if (macros == NULL)
+ mc_config_del_key (macros_config, section_name, skeyname);
+ else
+ {
+ macros_t macro;
+
+ macro.hotkey = hotkey;
+ macro.macro = macros;
+ g_array_append_val (macros_list, macro);
+ mc_config_set_string (macros_config, section_name, skeyname, macros_string->str);
+ }
+
+ g_free (skeyname);
+
+ edit_macro_sort_by_hotkey ();
+
+ if (macros_string != NULL)
+ g_string_free (macros_string, TRUE);
+ mc_config_save_file (macros_config, NULL);
+ mc_config_deinit (macros_config);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** return FALSE on error */
+
+gboolean
+edit_load_macro_cmd (WEdit * edit)
+{
+ mc_config_t *macros_config = NULL;
+ gchar **profile_keys, **keys;
+ gchar **values, **curr_values;
+ const char *section_name = "editor";
+ gchar *macros_fname;
+
+ (void) edit;
+
+ if (macros_list == NULL || macros_list->len != 0)
+ return FALSE;
+
+ macros_fname = mc_config_get_full_path (MC_MACRO_FILE);
+ macros_config = mc_config_init (macros_fname, TRUE);
+ g_free (macros_fname);
+
+ if (macros_config == NULL)
+ return FALSE;
+
+ keys = mc_config_get_keys (macros_config, section_name, NULL);
+
+ for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
+ {
+ int hotkey;
+ GArray *macros = NULL;
+
+ values = mc_config_get_string_list (macros_config, section_name, *profile_keys, NULL);
+ hotkey = tty_keyname_to_keycode (*profile_keys, NULL);
+
+ for (curr_values = values; *curr_values != NULL && *curr_values[0] != '\0'; curr_values++)
+ {
+ char **macro_pair;
+
+ macro_pair = g_strsplit (*curr_values, ":", 2);
+
+ if (macro_pair != NULL)
+ {
+ macro_action_t m_act = {
+ .action = 0,
+ .ch = -1
+ };
+
+ if (macro_pair[0] != NULL && macro_pair[0][0] != '\0')
+ m_act.action = keybind_lookup_action (macro_pair[0]);
+
+ if (macro_pair[1] != NULL && macro_pair[1][0] != '\0')
+ m_act.ch = strtol (macro_pair[1], NULL, 0);
+
+ if (m_act.action != 0)
+ {
+ /* a shell command */
+ if ((m_act.action / CK_PipeBlock (0)) == 1)
+ {
+ m_act.action = CK_PipeBlock (0);
+ if (m_act.ch > 0)
+ m_act.action += m_act.ch;
+ m_act.ch = -1;
+ }
+
+ if (macros == NULL)
+ macros = g_array_new (TRUE, FALSE, sizeof (m_act));
+
+ g_array_append_val (macros, m_act);
+ }
+
+ g_strfreev (macro_pair);
+ }
+ }
+
+ if (macros != NULL)
+ {
+ macros_t macro = {
+ .hotkey = hotkey,
+ .macro = macros
+ };
+
+ g_array_append_val (macros_list, macro);
+ }
+
+ g_strfreev (values);
+ }
+
+ g_strfreev (keys);
+ mc_config_deinit (macros_config);
+ edit_macro_sort_by_hotkey ();
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_delete_macro_cmd (WEdit * edit)
+{
+ int hotkey;
+
+ hotkey = editcmd_dialog_raw_key_query (_("Delete macro"), _("Press macro hotkey:"), TRUE);
+
+ if (hotkey != 0 && !edit_delete_macro (edit, hotkey))
+ message (D_ERROR, _("Delete macro"), _("Macro not deleted"));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_repeat_macro_cmd (WEdit * edit)
+{
+ gboolean ok;
+ char *f;
+ long count_repeat = 0;
+
+ f = input_dialog (_("Repeat last commands"), _("Repeat times:"), MC_HISTORY_EDIT_REPEAT, NULL,
+ INPUT_COMPLETE_NONE);
+ ok = (f != NULL && *f != '\0');
+
+ if (ok)
+ {
+ char *error = NULL;
+
+ count_repeat = strtol (f, &error, 0);
+
+ ok = (*error == '\0');
+ }
+
+ g_free (f);
+
+ if (ok)
+ {
+ int i, j;
+
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+ edit->force |= REDRAW_PAGE;
+
+ for (j = 0; j < count_repeat; j++)
+ for (i = 0; i < macro_index; i++)
+ edit_execute_cmd (edit, record_macro_buf[i].action, record_macro_buf[i].ch);
+
+ edit_update_screen (edit);
+ }
+
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** returns FALSE on error */
+gboolean
+edit_execute_macro (WEdit * edit, int hotkey)
+{
+ gboolean res = FALSE;
+
+ if (hotkey != 0)
+ {
+ int indx;
+
+ indx = edit_get_macro (edit, hotkey);
+ if (indx != -1)
+ {
+ const macros_t *macros;
+
+ macros = &g_array_index (macros_list, struct macros_t, indx);
+ if (macros->macro->len != 0)
+ {
+ guint i;
+
+ edit->force |= REDRAW_PAGE;
+
+ for (i = 0; i < macros->macro->len; i++)
+ {
+ const macro_action_t *m_act;
+
+ m_act = &g_array_index (macros->macro, struct macro_action_t, i);
+ edit_execute_cmd (edit, m_act->action, m_act->ch);
+ res = TRUE;
+ }
+ }
+ }
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_begin_end_macro_cmd (WEdit * edit)
+{
+ /* edit is a pointer to the widget */
+ if (edit != NULL)
+ {
+ long command = macro_index < 0 ? CK_MacroStartRecord : CK_MacroStopRecord;
+
+ edit_execute_key_command (edit, command, -1);
+ }
+}
+
+ /* --------------------------------------------------------------------------------------------- */
+
+void
+edit_begin_end_repeat_cmd (WEdit * edit)
+{
+ /* edit is a pointer to the widget */
+ if (edit != NULL)
+ {
+ long command = macro_index < 0 ? CK_RepeatStartRecord : CK_RepeatStopRecord;
+
+ edit_execute_key_command (edit, command, -1);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editmacros.h b/src/editor/editmacros.h
new file mode 100644
index 0000000..5d234e0
--- /dev/null
+++ b/src/editor/editmacros.h
@@ -0,0 +1,24 @@
+#ifndef MC__EDIT_MACROS_H
+#define MC__EDIT_MACROS_H 1
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+int edit_store_macro_cmd (WEdit * edit);
+gboolean edit_load_macro_cmd (WEdit * edit);
+void edit_delete_macro_cmd (WEdit * edit);
+gboolean edit_repeat_macro_cmd (WEdit * edit);
+gboolean edit_execute_macro (WEdit * edit, int hotkey);
+void edit_begin_end_macro_cmd (WEdit * edit);
+void edit_begin_end_repeat_cmd (WEdit * edit);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__EDIT_MACROS_H */
diff --git a/src/editor/editmenu.c b/src/editor/editmenu.c
new file mode 100644
index 0000000..3509fa2
--- /dev/null
+++ b/src/editor/editmenu.c
@@ -0,0 +1,338 @@
+/*
+ Editor menu definitions and initialisation
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1996, 1997
+ Andrew Borodin <aborodin@vmail.ru> 2012
+
+ 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
+ * \brief Source: editor menu definitions and initialisation
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* KEY_F */
+#include "lib/tty/key.h" /* XCTRL */
+#include "lib/widget.h"
+
+#include "src/setup.h" /* drop_menus */
+#include "src/keymap.h"
+
+#include "edit-impl.h"
+#include "editwidget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_file_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&Open file..."), CK_EditFile));
+ entries = g_list_prepend (entries, menu_entry_new (_("&New"), CK_EditNew));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Close"), CK_Close));
+ entries = g_list_prepend (entries, menu_entry_new (_("&History..."), CK_History));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Save"), CK_Save));
+ entries = g_list_prepend (entries, menu_entry_new (_("Save &as..."), CK_SaveAs));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Insert file..."), CK_InsertFile));
+ entries = g_list_prepend (entries, menu_entry_new (_("Cop&y to file..."), CK_BlockSave));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&User menu..."), CK_UserMenu));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("A&bout..."), CK_About));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Quit"), CK_Quit));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_edit_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&Undo"), CK_Undo));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Redo"), CK_Redo));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Toggle ins/overw"), CK_InsertOverwrite));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("To&ggle mark"), CK_Mark));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Mark columns"), CK_MarkColumn));
+ entries = g_list_prepend (entries, menu_entry_new (_("Mark &all"), CK_MarkAll));
+ entries = g_list_prepend (entries, menu_entry_new (_("Unmar&k"), CK_Unmark));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("Cop&y"), CK_Copy));
+ entries = g_list_prepend (entries, menu_entry_new (_("Mo&ve"), CK_Move));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Delete"), CK_Remove));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("Co&py to clipfile"), CK_Store));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Cut to clipfile"), CK_Cut));
+ entries = g_list_prepend (entries, menu_entry_new (_("Pa&ste from clipfile"), CK_Paste));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Beginning"), CK_Top));
+ entries = g_list_prepend (entries, menu_entry_new (_("&End"), CK_Bottom));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_search_replace_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&Search..."), CK_Search));
+ entries = g_list_prepend (entries, menu_entry_new (_("Search &again"), CK_SearchContinue));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Replace..."), CK_Replace));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Toggle bookmark"), CK_Bookmark));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Next bookmark"), CK_BookmarkNext));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Prev bookmark"), CK_BookmarkPrev));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Flush bookmarks"), CK_BookmarkFlush));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_command_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&Go to line..."), CK_Goto));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Toggle line state"), CK_ShowNumbers));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("Go to matching &bracket"), CK_MatchBracket));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("Toggle s&yntax highlighting"), CK_SyntaxOnOff));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Find declaration"), CK_Find));
+ entries = g_list_prepend (entries, menu_entry_new (_("Back from &declaration"), CK_FilePrev));
+ entries = g_list_prepend (entries, menu_entry_new (_("For&ward to declaration"), CK_FileNext));
+#ifdef HAVE_CHARSET
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("Encod&ing..."), CK_SelectCodepage));
+#endif
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Refresh screen"), CK_Refresh));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries =
+ g_list_prepend (entries,
+ menu_entry_new (_("&Start/Stop record macro"), CK_MacroStartStopRecord));
+ entries = g_list_prepend (entries, menu_entry_new (_("Delete macr&o..."), CK_MacroDelete));
+ entries =
+ g_list_prepend (entries,
+ menu_entry_new (_("Record/Repeat &actions"), CK_RepeatStartStopRecord));
+ entries = g_list_prepend (entries, menu_separator_new ());
+#ifdef HAVE_ASPELL
+ if (strcmp (spell_language, "NONE") != 0)
+ {
+ entries = g_list_prepend (entries, menu_entry_new (_("S&pell check"), CK_SpellCheck));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("C&heck word"), CK_SpellCheckCurrentWord));
+ entries =
+ g_list_prepend (entries,
+ menu_entry_new (_("Change spelling &language..."),
+ CK_SpellCheckSelectLang));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ }
+#endif /* HAVE_ASPELL */
+ entries = g_list_prepend (entries, menu_entry_new (_("&Mail..."), CK_EditMail));
+
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_format_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("Insert &literal..."), CK_InsertLiteral));
+ entries = g_list_prepend (entries, menu_entry_new (_("Insert &date/time"), CK_Date));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Format paragraph"), CK_ParagraphFormat));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Sort..."), CK_Sort));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("&Paste output of..."), CK_ExternalCommand));
+ entries = g_list_prepend (entries, menu_entry_new (_("&External formatter"), CK_PipeBlock (0)));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create the 'window' popup menu
+ */
+
+static GList *
+create_window_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&Move"), CK_WindowMove));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Resize"), CK_WindowResize));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("&Toggle fullscreen"), CK_WindowFullscreen));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Next"), CK_WindowNext));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Previous"), CK_WindowPrev));
+ entries = g_list_prepend (entries, menu_entry_new (_("&List..."), CK_WindowList));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_options_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&General..."), CK_Options));
+ entries = g_list_prepend (entries, menu_entry_new (_("Save &mode..."), CK_OptionsSaveMode));
+ entries = g_list_prepend (entries, menu_entry_new (_("Learn &keys..."), CK_LearnKeys));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("Syntax &highlighting..."), CK_SyntaxChoose));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("S&yntax file"), CK_EditSyntaxFile));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Menu file"), CK_EditUserMenu));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Save setup"), CK_SaveSetup));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_drop_menu_cmd (WDialog * h, int which)
+{
+ WMenuBar *menubar;
+
+ menubar = menubar_find (h);
+ menubar_activate (menubar, drop_menus, which);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_init_menu (WMenuBar * menubar)
+{
+ menubar_add_menu (menubar,
+ menu_new (_("&File"), create_file_menu (), "[Internal File Editor]"));
+ menubar_add_menu (menubar,
+ menu_new (_("&Edit"), create_edit_menu (), "[Internal File Editor]"));
+ menubar_add_menu (menubar,
+ menu_new (_("&Search"), create_search_replace_menu (),
+ "[Internal File Editor]"));
+ menubar_add_menu (menubar,
+ menu_new (_("&Command"), create_command_menu (), "[Internal File Editor]"));
+ menubar_add_menu (menubar,
+ menu_new (_("For&mat"), create_format_menu (), "[Internal File Editor]"));
+ menubar_add_menu (menubar,
+ menu_new (_("&Window"), create_window_menu (), "[Internal File Editor]"));
+ menubar_add_menu (menubar,
+ menu_new (_("&Options"), create_options_menu (), "[Internal File Editor]"));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_menu_cmd (WDialog * h)
+{
+ edit_drop_menu_cmd (h, -1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_drop_hotkey_menu (WDialog * h, int key)
+{
+ int m = 0;
+ switch (key)
+ {
+ case ALT ('f'):
+ m = 0;
+ break;
+ case ALT ('e'):
+ m = 1;
+ break;
+ case ALT ('s'):
+ m = 2;
+ break;
+ case ALT ('c'):
+ m = 3;
+ break;
+ case ALT ('m'):
+ m = 4;
+ break;
+ case ALT ('w'):
+ m = 5;
+ break;
+ case ALT ('o'):
+ m = 6;
+ break;
+ default:
+ return FALSE;
+ }
+
+ edit_drop_menu_cmd (h, m);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editoptions.c b/src/editor/editoptions.c
new file mode 100644
index 0000000..9e059f3
--- /dev/null
+++ b/src/editor/editoptions.c
@@ -0,0 +1,241 @@
+/*
+ Editor options dialog box
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1996, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2012-2022
+
+ 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
+ * \brief Source: editor options dialog box
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#include <config.h>
+
+#include <stdlib.h> /* atoi(), NULL */
+
+#include "lib/global.h"
+#include "lib/widget.h"
+
+#include "editwidget.h"
+#include "edit-impl.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static const char *wrap_str[] = {
+ N_("&None"),
+ N_("&Dynamic paragraphing"),
+ N_("Type &writer wrap"),
+ NULL
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_NLS
+static void
+i18n_translate_array (const char *array[])
+{
+ while (*array != NULL)
+ {
+ *array = _(*array);
+ array++;
+ }
+}
+#endif /* ENABLE_NLS */
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for the iteration of objects in the 'editors' array.
+ * Tear down 'over_col' property in all editors.
+ *
+ * @param data probably WEdit object
+ * @param user_data unused
+ */
+
+static void
+edit_reset_over_col (void *data, void *user_data)
+{
+ (void) user_data;
+
+ if (edit_widget_is_editor (CONST_WIDGET (data)))
+ EDIT (data)->over_col = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for the iteration of objects in the 'editors' array.
+ * Reload syntax lighlighting in all editors.
+ *
+ * @param data probably WEdit object
+ * @param user_data unused
+ */
+
+static void
+edit_reload_syntax (void *data, void *user_data)
+{
+ (void) user_data;
+
+ if (edit_widget_is_editor (CONST_WIDGET (data)))
+ {
+ WEdit *edit = EDIT (data);
+
+ edit_load_syntax (edit, NULL, edit->syntax_type);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_options_dialog (WDialog * h)
+{
+ char wrap_length[16], tab_spacing[16];
+ char *p, *q;
+ int wrap_mode = 0;
+ gboolean old_syntax_hl;
+
+#ifdef ENABLE_NLS
+ static gboolean i18n_flag = FALSE;
+
+ if (!i18n_flag)
+ {
+ i18n_translate_array (wrap_str);
+ i18n_flag = TRUE;
+ }
+#endif /* ENABLE_NLS */
+
+ g_snprintf (wrap_length, sizeof (wrap_length), "%d", edit_options.word_wrap_line_length);
+ g_snprintf (tab_spacing, sizeof (tab_spacing), "%d", TAB_SIZE);
+
+ if (edit_options.auto_para_formatting)
+ wrap_mode = 1;
+ else if (edit_options.typewriter_wrap)
+ wrap_mode = 2;
+ else
+ wrap_mode = 0;
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_COLUMNS,
+ QUICK_START_GROUPBOX (N_("Wrap mode")),
+ QUICK_RADIO (3, wrap_str, &wrap_mode, NULL),
+ QUICK_STOP_GROUPBOX,
+ QUICK_SEPARATOR (FALSE),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_START_GROUPBOX (N_("Tabulation")),
+ QUICK_CHECKBOX (N_("&Fake half tabs"), &edit_options.fake_half_tabs, NULL),
+ QUICK_CHECKBOX (N_("&Backspace through tabs"),
+ &edit_options.backspace_through_tabs, NULL),
+ QUICK_CHECKBOX (N_("Fill tabs with &spaces"),
+ &edit_options.fill_tabs_with_spaces, NULL),
+ QUICK_LABELED_INPUT (N_("Tab spacing:"), input_label_left, tab_spacing,
+ "edit-tab-spacing", &q, NULL, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_STOP_GROUPBOX,
+ QUICK_NEXT_COLUMN,
+ QUICK_START_GROUPBOX (N_("Other options")),
+ QUICK_CHECKBOX (N_("&Return does autoindent"), &edit_options.return_does_auto_indent, NULL),
+ QUICK_CHECKBOX (N_("Confir&m before saving"), &edit_options.confirm_save, NULL),
+ QUICK_CHECKBOX (N_("Save file &position"), &edit_options.save_position, NULL),
+ QUICK_CHECKBOX (N_("&Visible trailing spaces"), &edit_options.visible_tws, NULL),
+ QUICK_CHECKBOX (N_("Visible &tabs"), &edit_options.visible_tabs, NULL),
+ QUICK_CHECKBOX (N_("Synta&x highlighting"), &edit_options.syntax_highlighting, NULL),
+ QUICK_CHECKBOX (N_("C&ursor after inserted block"),
+ &edit_options.cursor_after_inserted_block, NULL),
+ QUICK_CHECKBOX (N_("Pers&istent selection"), &edit_options.persistent_selections, NULL),
+ QUICK_CHECKBOX (N_("Cursor be&yond end of line"), &edit_options.cursor_beyond_eol, NULL),
+ QUICK_CHECKBOX (N_("&Group undo"), &edit_options.group_undo, NULL),
+ QUICK_LABELED_INPUT (N_("Word wrap line length:"), input_label_left, wrap_length,
+ "edit-word-wrap", &p, NULL, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_STOP_GROUPBOX,
+ QUICK_STOP_COLUMNS,
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 74 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Editor options"), "[Editor options]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) == B_CANCEL)
+ return;
+ }
+
+ old_syntax_hl = edit_options.syntax_highlighting;
+
+ if (!edit_options.cursor_beyond_eol)
+ g_list_foreach (GROUP (h)->widgets, edit_reset_over_col, NULL);
+
+ if (*p != '\0')
+ {
+ edit_options.word_wrap_line_length = atoi (p);
+ if (edit_options.word_wrap_line_length <= 0)
+ edit_options.word_wrap_line_length = DEFAULT_WRAP_LINE_LENGTH;
+ g_free (p);
+ }
+
+ if (*q != '\0')
+ {
+ TAB_SIZE = atoi (q);
+ if (TAB_SIZE <= 0)
+ TAB_SIZE = DEFAULT_TAB_SPACING;
+ g_free (q);
+ }
+
+ if (wrap_mode == 1)
+ {
+ edit_options.auto_para_formatting = TRUE;
+ edit_options.typewriter_wrap = FALSE;
+ }
+ else if (wrap_mode == 2)
+ {
+ edit_options.auto_para_formatting = FALSE;
+ edit_options.typewriter_wrap = TRUE;
+ }
+ else
+ {
+ edit_options.auto_para_formatting = FALSE;
+ edit_options.typewriter_wrap = FALSE;
+ }
+
+ /* Load or unload syntax rules if the option has changed */
+ if (edit_options.syntax_highlighting != old_syntax_hl)
+ g_list_foreach (GROUP (h)->widgets, edit_reload_syntax, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editsearch.c b/src/editor/editsearch.c
new file mode 100644
index 0000000..1bdf883
--- /dev/null
+++ b/src/editor/editsearch.c
@@ -0,0 +1,1042 @@
+/*
+ Search & replace engine of MCEditor.
+
+ Copyright (C) 2021-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2021-2022
+
+ 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 <assert.h>
+
+#include "lib/global.h"
+#include "lib/search.h"
+#include "lib/mcconfig.h" /* mc_config_history_get */
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h" /* cp_source */
+#endif
+#include "lib/util.h"
+#include "lib/widget.h"
+#include "lib/skin.h" /* BOOK_MARK_FOUND_COLOR */
+
+#include "src/history.h" /* MC_HISTORY_SHARED_SEARCH */
+#include "src/setup.h" /* verbose */
+
+#include "edit-impl.h"
+#include "editwidget.h"
+
+#include "editsearch.h"
+
+/*** global variables ****************************************************************************/
+
+edit_search_options_t edit_search_options = {
+ .type = MC_SEARCH_T_NORMAL,
+ .case_sens = FALSE,
+ .backwards = FALSE,
+ .only_in_selection = FALSE,
+ .whole_words = FALSE,
+ .all_codepages = FALSE
+};
+
+/*** file scope macro definitions ****************************************************************/
+
+#define B_REPLACE_ALL (B_USER+1)
+#define B_REPLACE_ONE (B_USER+2)
+#define B_SKIP_REPLACE (B_USER+3)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+edit_dialog_search_show (WEdit * edit)
+{
+ char *search_text;
+ size_t num_of_types = 0;
+ gchar **list_of_types;
+ int dialog_result;
+
+ list_of_types = mc_search_get_types_strings_array (&num_of_types);
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (N_("Enter search string:"), input_label_above, INPUT_LAST_TEXT,
+ MC_HISTORY_SHARED_SEARCH, &search_text, NULL, FALSE, FALSE,
+ INPUT_COMPLETE_NONE),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_START_COLUMNS,
+ QUICK_RADIO (num_of_types, (const char **) list_of_types,
+ (int *) &edit_search_options.type, NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("Cas&e sensitive"), &edit_search_options.case_sens, NULL),
+ QUICK_CHECKBOX (N_("&Backwards"), &edit_search_options.backwards, NULL),
+ QUICK_CHECKBOX (N_("In se&lection"), &edit_search_options.only_in_selection, NULL),
+ QUICK_CHECKBOX (N_("&Whole words"), &edit_search_options.whole_words, NULL),
+#ifdef HAVE_CHARSET
+ QUICK_CHECKBOX (N_("&All charsets"), &edit_search_options.all_codepages, NULL),
+#endif
+ QUICK_STOP_COLUMNS,
+ QUICK_START_BUTTONS (TRUE, TRUE),
+ QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
+ QUICK_BUTTON (N_("&Find all"), B_USER, NULL, NULL),
+ QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 58 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Search"), "[Input Line Keys]",
+ quick_widgets, NULL, NULL
+ };
+
+ dialog_result = quick_dialog (&qdlg);
+ }
+
+ g_strfreev (list_of_types);
+
+ if (dialog_result == B_CANCEL || search_text[0] == '\0')
+ {
+ g_free (search_text);
+ return FALSE;
+ }
+
+ if (dialog_result == B_USER)
+ search_create_bookmark = TRUE;
+
+#ifdef HAVE_CHARSET
+ {
+ GString *tmp;
+
+ tmp = str_convert_to_input (search_text);
+ g_free (search_text);
+ if (tmp != NULL)
+ search_text = g_string_free (tmp, FALSE);
+ else
+ search_text = g_strdup ("");
+ }
+#endif
+
+ edit_search_deinit (edit);
+ edit->last_search_string = search_text;
+
+ return edit_search_init (edit, edit->last_search_string);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_dialog_replace_show (WEdit * edit, const char *search_default, const char *replace_default,
+ /*@out@ */ char **search_text, /*@out@ */ char **replace_text)
+{
+ size_t num_of_types = 0;
+ gchar **list_of_types;
+
+ if ((search_default == NULL) || (*search_default == '\0'))
+ search_default = INPUT_LAST_TEXT;
+
+ list_of_types = mc_search_get_types_strings_array (&num_of_types);
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (N_("Enter search string:"), input_label_above, search_default,
+ MC_HISTORY_SHARED_SEARCH, search_text, NULL, FALSE, FALSE,
+ INPUT_COMPLETE_NONE),
+ QUICK_LABELED_INPUT (N_("Enter replacement string:"), input_label_above, replace_default,
+ "replace", replace_text, NULL, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_START_COLUMNS,
+ QUICK_RADIO (num_of_types, (const char **) list_of_types,
+ (int *) &edit_search_options.type, NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("Cas&e sensitive"), &edit_search_options.case_sens, NULL),
+ QUICK_CHECKBOX (N_("&Backwards"), &edit_search_options.backwards, NULL),
+ QUICK_CHECKBOX (N_("In se&lection"), &edit_search_options.only_in_selection, NULL),
+ QUICK_CHECKBOX (N_("&Whole words"), &edit_search_options.whole_words, NULL),
+#ifdef HAVE_CHARSET
+ QUICK_CHECKBOX (N_("&All charsets"), &edit_search_options.all_codepages, NULL),
+#endif
+ QUICK_STOP_COLUMNS,
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 58 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Replace"), "[Input Line Keys]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ edit->replace_mode = 0;
+ else
+ {
+ *replace_text = NULL;
+ *search_text = NULL;
+ }
+ }
+
+ g_strfreev (list_of_types);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+edit_dialog_replace_prompt_show (WEdit * edit, char *from_text, char *to_text, int xpos, int ypos)
+{
+ Widget *w = WIDGET (edit);
+
+ /* dialog size */
+ int dlg_height = 10;
+ int dlg_width;
+
+ char tmp[BUF_MEDIUM];
+ char *repl_from, *repl_to;
+ int retval;
+
+ if (xpos == -1)
+ xpos = w->rect.x + edit_options.line_state_width + 1;
+ if (ypos == -1)
+ ypos = w->rect.y + w->rect.lines / 2;
+ /* Sometimes menu can hide replaced text. I don't like it */
+ if ((edit->curs_row >= ypos - 1) && (edit->curs_row <= ypos + dlg_height - 1))
+ ypos -= dlg_height;
+
+ dlg_width = WIDGET (w->owner)->rect.cols - xpos - 1;
+
+ g_snprintf (tmp, sizeof (tmp), "\"%s\"", from_text);
+ repl_from = g_strdup (str_trunc (tmp, dlg_width - 7));
+
+ g_snprintf (tmp, sizeof (tmp), "\"%s\"", to_text);
+ repl_to = g_strdup (str_trunc (tmp, dlg_width - 7));
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABEL (repl_from, NULL),
+ QUICK_LABEL (N_("Replace with:"), NULL),
+ QUICK_LABEL (repl_to, NULL),
+ QUICK_START_BUTTONS (TRUE, TRUE),
+ QUICK_BUTTON (N_("&Replace"), B_ENTER, NULL, NULL),
+ QUICK_BUTTON (N_("A&ll"), B_REPLACE_ALL, NULL, NULL),
+ QUICK_BUTTON (N_("&Skip"), B_SKIP_REPLACE, NULL, NULL),
+ QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { ypos, xpos, 0, -1 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Confirm replace"), NULL,
+ quick_widgets, NULL, NULL
+ };
+
+ retval = quick_dialog (&qdlg);
+ }
+
+ g_free (repl_from);
+ g_free (repl_to);
+
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Get EOL symbol for searching.
+ *
+ * @param edit editor object
+ * @return EOL symbol
+ */
+
+static inline char
+edit_search_get_current_end_line_char (const WEdit * edit)
+{
+ switch (edit->lb)
+ {
+ case LB_MAC:
+ return '\r';
+ default:
+ return '\n';
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Checking if search condition have BOL(^) or EOL ($) regexp special characters.
+ *
+ * @param search search object
+ * @return result of checks.
+ */
+
+static edit_search_line_t
+edit_get_search_line_type (mc_search_t * search)
+{
+ edit_search_line_t search_line_type = 0;
+
+ if (search->search_type != MC_SEARCH_T_REGEX)
+ return search_line_type;
+
+ if (search->original.str->str[0] == '^')
+ search_line_type |= AT_START_LINE;
+
+ if (search->original.str->str[search->original.str->len - 1] == '$')
+ search_line_type |= AT_END_LINE;
+ return search_line_type;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculating the start position of next line.
+ *
+ * @param buf editor buffer object
+ * @param current_pos current position
+ * @param max_pos max position
+ * @param end_string_symbol end of line symbol
+ * @return start position of next line
+ */
+
+static off_t
+edit_calculate_start_of_next_line (const edit_buffer_t * buf, off_t current_pos, off_t max_pos,
+ char end_string_symbol)
+{
+ off_t i;
+
+ for (i = current_pos; i < max_pos; i++)
+ {
+ current_pos++;
+ if (edit_buffer_get_byte (buf, i) == end_string_symbol)
+ break;
+ }
+
+ return current_pos;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculating the end position of previous line.
+ *
+ * @param buf editor buffer object
+ * @param current_pos current position
+ * @param end_string_symbol end of line symbol
+ * @return end position of previous line
+ */
+
+static off_t
+edit_calculate_end_of_previous_line (const edit_buffer_t * buf, off_t current_pos,
+ char end_string_symbol)
+{
+ off_t i;
+
+ for (i = current_pos - 1; i >= 0; i--)
+ if (edit_buffer_get_byte (buf, i) == end_string_symbol)
+ break;
+
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculating the start position of previous line.
+ *
+ * @param buf editor buffer object
+ * @param current_pos current position
+ * @param end_string_symbol end of line symbol
+ * @return start position of previous line
+ */
+
+static inline off_t
+edit_calculate_start_of_previous_line (const edit_buffer_t * buf, off_t current_pos,
+ char end_string_symbol)
+{
+ current_pos = edit_calculate_end_of_previous_line (buf, current_pos, end_string_symbol);
+ current_pos = edit_calculate_end_of_previous_line (buf, current_pos, end_string_symbol);
+
+ return (current_pos + 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculating the start position of current line.
+ *
+ * @param buf editor buffer object
+ * @param current_pos current position
+ * @param end_string_symbol end of line symbol
+ * @return start position of current line
+ */
+
+static inline off_t
+edit_calculate_start_of_current_line (const edit_buffer_t * buf, off_t current_pos,
+ char end_string_symbol)
+{
+ current_pos = edit_calculate_end_of_previous_line (buf, current_pos, end_string_symbol);
+
+ return (current_pos + 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Fixing (if needed) search start position if 'only in selection' option present.
+ *
+ * @param edit editor object
+ */
+
+static void
+edit_search_fix_search_start_if_selection (WEdit * edit)
+{
+ off_t start_mark = 0;
+ off_t end_mark = 0;
+
+ if (!edit_search_options.only_in_selection)
+ return;
+
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ return;
+
+ if (edit_search_options.backwards)
+ {
+ if (edit->search_start > end_mark || edit->search_start <= start_mark)
+ edit->search_start = end_mark;
+ }
+ else
+ {
+ if (edit->search_start < start_mark || edit->search_start >= end_mark)
+ edit->search_start = start_mark;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+edit_find (edit_search_status_msg_t * esm, gsize * len)
+{
+ WEdit *edit = esm->edit;
+ off_t search_start = edit->search_start;
+ off_t search_end;
+ off_t start_mark = 0;
+ off_t end_mark = edit->buffer.size;
+ char end_string_symbol;
+
+ end_string_symbol = edit_search_get_current_end_line_char (edit);
+
+ /* prepare for search */
+ if (edit_search_options.only_in_selection)
+ {
+ if (!eval_marks (edit, &start_mark, &end_mark))
+ {
+ mc_search_set_error (edit->search, MC_SEARCH_E_NOTFOUND, "%s", _(STR_E_NOTFOUND));
+ return FALSE;
+ }
+
+ /* fix the start and the end of search block positions */
+ if ((edit->search_line_type & AT_START_LINE) != 0
+ && (start_mark != 0
+ || edit_buffer_get_byte (&edit->buffer, start_mark - 1) != end_string_symbol))
+ start_mark =
+ edit_calculate_start_of_next_line (&edit->buffer, start_mark, edit->buffer.size,
+ end_string_symbol);
+
+ if ((edit->search_line_type & AT_END_LINE) != 0
+ && (end_mark - 1 != edit->buffer.size
+ || edit_buffer_get_byte (&edit->buffer, end_mark) != end_string_symbol))
+ end_mark =
+ edit_calculate_end_of_previous_line (&edit->buffer, end_mark, end_string_symbol);
+
+ if (start_mark >= end_mark)
+ {
+ mc_search_set_error (edit->search, MC_SEARCH_E_NOTFOUND, "%s", _(STR_E_NOTFOUND));
+ return FALSE;
+ }
+ }
+ else if (edit_search_options.backwards)
+ end_mark = MAX (1, edit->buffer.curs1) - 1;
+
+ /* search */
+ if (edit_search_options.backwards)
+ {
+ /* backward search */
+ search_end = end_mark;
+
+ if ((edit->search_line_type & AT_START_LINE) != 0)
+ search_start =
+ edit_calculate_start_of_current_line (&edit->buffer, search_start,
+ end_string_symbol);
+
+ while (search_start >= start_mark)
+ {
+ gboolean ok;
+
+ if (search_end > (off_t) (search_start + edit->search->original.str->len)
+ && mc_search_is_fixed_search_str (edit->search))
+ search_end = search_start + edit->search->original.str->len;
+
+ ok = mc_search_run (edit->search, (void *) esm, search_start, search_end, len);
+
+ if (ok && edit->search->normal_offset == search_start)
+ return TRUE;
+
+ /* We abort the search in case of a pattern error, or if the user aborts
+ the search. In other words: in all cases except "string not found". */
+ if (!ok && edit->search->error != MC_SEARCH_E_NOTFOUND)
+ return FALSE;
+
+ if ((edit->search_line_type & AT_START_LINE) != 0)
+ search_start =
+ edit_calculate_start_of_previous_line (&edit->buffer, search_start,
+ end_string_symbol);
+ else
+ search_start--;
+ }
+
+ mc_search_set_error (edit->search, MC_SEARCH_E_NOTFOUND, "%s", _(STR_E_NOTFOUND));
+ return FALSE;
+ }
+
+ /* forward search */
+ if ((edit->search_line_type & AT_START_LINE) != 0 && search_start != start_mark)
+ search_start =
+ edit_calculate_start_of_next_line (&edit->buffer, search_start, end_mark,
+ end_string_symbol);
+
+ return mc_search_run (edit->search, (void *) esm, search_start, end_mark, len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+edit_replace_cmd__conv_to_display (const char *str)
+{
+#ifdef HAVE_CHARSET
+ GString *tmp;
+
+ tmp = str_convert_to_display (str);
+ if (tmp != NULL)
+ {
+ if (tmp->len != 0)
+ return g_string_free (tmp, FALSE);
+ g_string_free (tmp, TRUE);
+ }
+#endif
+ return g_strdup (str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+edit_replace_cmd__conv_to_input (char *str)
+{
+#ifdef HAVE_CHARSET
+ GString *tmp;
+
+ tmp = str_convert_to_input (str);
+ if (tmp != NULL)
+ {
+ if (tmp->len != 0)
+ return g_string_free (tmp, FALSE);
+ g_string_free (tmp, TRUE);
+ }
+#endif
+ return g_strdup (str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_show_search_error (const WEdit * edit, const char *title)
+{
+ if (edit->search->error == MC_SEARCH_E_NOTFOUND)
+ edit_query_dialog (title, _(STR_E_NOTFOUND));
+ else if (edit->search->error_str != NULL)
+ edit_query_dialog (title, edit->search->error_str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_do_search (WEdit * edit)
+{
+ edit_search_status_msg_t esm;
+ gsize len = 0;
+
+ /* This shouldn't happen */
+ assert (edit->search != NULL);
+
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+
+ esm.first = TRUE;
+ esm.edit = edit;
+ esm.offset = edit->search_start;
+
+ status_msg_init (STATUS_MSG (&esm), _("Search"), 1.0, simple_status_msg_init_cb,
+ edit_search_status_update_cb, NULL);
+
+ if (search_create_bookmark)
+ {
+ gboolean found = FALSE;
+ long l = 0, l_last = -1;
+ long q = 0;
+
+ search_create_bookmark = FALSE;
+ book_mark_flush (edit, -1);
+
+ while (mc_search_run (edit->search, (void *) &esm, q, edit->buffer.size, &len))
+ {
+ if (!found)
+ edit->search_start = edit->search->normal_offset;
+ found = TRUE;
+
+ l += edit_buffer_count_lines (&edit->buffer, q, edit->search->normal_offset);
+ if (l != l_last)
+ book_mark_insert (edit, l, BOOK_MARK_FOUND_COLOR);
+ l_last = l;
+ q = edit->search->normal_offset + 1;
+ }
+
+ if (!found)
+ edit_error_dialog (_("Search"), _(STR_E_NOTFOUND));
+ else
+ edit_cursor_move (edit, edit->search_start - edit->buffer.curs1);
+ }
+ else
+ {
+ if (edit->found_len != 0 && edit->search_start == edit->found_start + 1
+ && edit_search_options.backwards)
+ edit->search_start--;
+
+ if (edit->found_len != 0 && edit->search_start == edit->found_start - 1
+ && !edit_search_options.backwards)
+ edit->search_start++;
+
+ if (edit_find (&esm, &len))
+ {
+ edit->found_start = edit->search_start = edit->search->normal_offset;
+ edit->found_len = len;
+ edit->over_col = 0;
+ edit_cursor_move (edit, edit->search_start - edit->buffer.curs1);
+ edit_scroll_screen_over_cursor (edit);
+ if (edit_search_options.backwards)
+ edit->search_start--;
+ else
+ edit->search_start++;
+ }
+ else
+ {
+ edit->search_start = edit->buffer.curs1;
+ edit_show_search_error (edit, _("Search"));
+ }
+ }
+
+ status_msg_deinit (STATUS_MSG (&esm));
+
+ edit->force |= REDRAW_COMPLETELY;
+ edit_scroll_screen_over_cursor (edit);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_search (WEdit * edit)
+{
+ if (edit_dialog_search_show (edit))
+ {
+ edit->search_line_type = edit_get_search_line_type (edit->search);
+ edit_search_fix_search_start_if_selection (edit);
+ edit_do_search (edit);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_search_init (WEdit * edit, const char *str)
+{
+#ifdef HAVE_CHARSET
+ edit->search = mc_search_new (str, cp_source);
+#else
+ edit->search = mc_search_new (str, NULL);
+#endif
+
+ if (edit->search == NULL)
+ return FALSE;
+
+ edit->search->search_type = edit_search_options.type;
+#ifdef HAVE_CHARSET
+ edit->search->is_all_charsets = edit_search_options.all_codepages;
+#endif
+ edit->search->is_case_sensitive = edit_search_options.case_sens;
+ edit->search->whole_words = edit_search_options.whole_words;
+ edit->search->search_fn = edit_search_cmd_callback;
+ edit->search->update_fn = edit_search_update_callback;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_search_deinit (WEdit * edit)
+{
+ mc_search_free (edit->search);
+ g_free (edit->last_search_string);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+mc_search_cbret_t
+edit_search_cmd_callback (const void *user_data, gsize char_offset, int *current_char)
+{
+ WEdit *edit = ((const edit_search_status_msg_t *) user_data)->edit;
+
+ *current_char = edit_buffer_get_byte (&edit->buffer, (off_t) char_offset);
+
+ return MC_SEARCH_CB_OK;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+mc_search_cbret_t
+edit_search_update_callback (const void *user_data, gsize char_offset)
+{
+ status_msg_t *sm = STATUS_MSG (user_data);
+
+ ((edit_search_status_msg_t *) sm)->offset = (off_t) char_offset;
+
+ return (sm->update (sm) == B_CANCEL ? MC_SEARCH_CB_ABORT : MC_SEARCH_CB_OK);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+edit_search_status_update_cb (status_msg_t * sm)
+{
+ simple_status_msg_t *ssm = SIMPLE_STATUS_MSG (sm);
+ edit_search_status_msg_t *esm = (edit_search_status_msg_t *) sm;
+ Widget *wd = WIDGET (sm->dlg);
+
+ if (verbose)
+ label_set_textv (ssm->label, _("Searching %s: %3d%%"), esm->edit->last_search_string,
+ edit_buffer_calc_percent (&esm->edit->buffer, esm->offset));
+ else
+ label_set_textv (ssm->label, _("Searching %s"), esm->edit->last_search_string);
+
+ if (esm->first)
+ {
+ Widget *lw = WIDGET (ssm->label);
+ WRect r;
+
+ r = wd->rect;
+ r.cols = MAX (r.cols, lw->rect.cols + 6);
+ widget_set_size_rect (wd, &r);
+ r = lw->rect;
+ r.x = wd->rect.x + (wd->rect.cols - r.cols) / 2;
+ widget_set_size_rect (lw, &r);
+ esm->first = FALSE;
+ }
+
+ return status_msg_common_update (sm);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_search_cmd (WEdit * edit, gboolean again)
+{
+ if (!again)
+ edit_search (edit);
+ else if (edit->last_search_string != NULL)
+ edit_do_search (edit);
+ else
+ {
+ /* find last search string in history */
+ GList *history;
+
+ history = mc_config_history_get (MC_HISTORY_SHARED_SEARCH);
+ if (history != NULL)
+ {
+ /* FIXME: is it possible that history->data == NULL? */
+ edit->last_search_string = (char *) history->data;
+ history->data = NULL;
+ history = g_list_first (history);
+ g_list_free_full (history, g_free);
+
+ if (edit_search_init (edit, edit->last_search_string))
+ {
+ edit_do_search (edit);
+ return;
+ }
+
+ /* found, but cannot init search */
+ MC_PTR_FREE (edit->last_search_string);
+ }
+
+ /* if not... then ask for an expression */
+ edit_search (edit);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** call with edit = 0 before shutdown to close memory leaks */
+
+void
+edit_replace_cmd (WEdit * edit, gboolean again)
+{
+ /* 1 = search string, 2 = replace with */
+ static char *saved1 = NULL; /* saved default[123] */
+ static char *saved2 = NULL;
+ char *input1 = NULL; /* user input from the dialog */
+ char *input2 = NULL;
+ GString *input2_str = NULL;
+ char *disp1 = NULL;
+ char *disp2 = NULL;
+ long times_replaced = 0;
+ gboolean once_found = FALSE;
+ edit_search_status_msg_t esm;
+
+ if (edit == NULL)
+ {
+ MC_PTR_FREE (saved1);
+ MC_PTR_FREE (saved2);
+ return;
+ }
+
+ edit->force |= REDRAW_COMPLETELY;
+
+ if (again && saved1 == NULL && saved2 == NULL)
+ again = FALSE;
+
+ if (again)
+ {
+ input1 = g_strdup (saved1 != NULL ? saved1 : "");
+ input2 = g_strdup (saved2 != NULL ? saved2 : "");
+ }
+ else
+ {
+ char *tmp_inp1, *tmp_inp2;
+
+ disp1 = edit_replace_cmd__conv_to_display (saved1 != NULL ? saved1 : "");
+ disp2 = edit_replace_cmd__conv_to_display (saved2 != NULL ? saved2 : "");
+
+ edit_push_undo_action (edit, KEY_PRESS + edit->start_display);
+
+ edit_dialog_replace_show (edit, disp1, disp2, &input1, &input2);
+
+ g_free (disp1);
+ g_free (disp2);
+
+ if (input1 == NULL || *input1 == '\0')
+ {
+ edit->force = REDRAW_COMPLETELY;
+ goto cleanup;
+ }
+
+ tmp_inp1 = input1;
+ tmp_inp2 = input2;
+ input1 = edit_replace_cmd__conv_to_input (input1);
+ input2 = edit_replace_cmd__conv_to_input (input2);
+ g_free (tmp_inp1);
+ g_free (tmp_inp2);
+
+ g_free (saved1);
+ saved1 = g_strdup (input1);
+ g_free (saved2);
+ saved2 = g_strdup (input2);
+
+ mc_search_free (edit->search);
+ edit->search = NULL;
+ }
+
+ input2_str = g_string_new (input2);
+
+ if (edit->search == NULL)
+ {
+ if (edit_search_init (edit, input1))
+ edit_search_fix_search_start_if_selection (edit);
+ else
+ {
+ edit->search_start = edit->buffer.curs1;
+ goto cleanup;
+ }
+ }
+
+ if (edit->found_len != 0 && edit->search_start == edit->found_start + 1
+ && edit_search_options.backwards)
+ edit->search_start--;
+
+ if (edit->found_len != 0 && edit->search_start == edit->found_start - 1
+ && !edit_search_options.backwards)
+ edit->search_start++;
+
+ esm.first = TRUE;
+ esm.edit = edit;
+ esm.offset = edit->search_start;
+
+ status_msg_init (STATUS_MSG (&esm), _("Search"), 1.0, simple_status_msg_init_cb,
+ edit_search_status_update_cb, NULL);
+
+ do
+ {
+ gsize len = 0;
+
+ if (!edit_find (&esm, &len))
+ {
+ if (!(edit->search->error == MC_SEARCH_E_OK ||
+ (once_found && edit->search->error == MC_SEARCH_E_NOTFOUND)))
+ edit_show_search_error (edit, _("Search"));
+ break;
+ }
+
+ once_found = TRUE;
+
+ edit->search_start = edit->search->normal_offset;
+ /* returns negative on not found or error in pattern */
+
+ if (edit->search_start >= 0 && edit->search_start < edit->buffer.size)
+ {
+ gsize i;
+ GString *repl_str;
+
+ edit->found_start = edit->search_start;
+ edit->found_len = len;
+
+ edit_cursor_move (edit, edit->search_start - edit->buffer.curs1);
+ edit_scroll_screen_over_cursor (edit);
+
+ if (edit->replace_mode == 0)
+ {
+ long l;
+ int prompt;
+
+ l = edit->curs_row - WIDGET (edit)->rect.lines / 3;
+ if (l > 0)
+ edit_scroll_downward (edit, l);
+ if (l < 0)
+ edit_scroll_upward (edit, -l);
+
+ edit_scroll_screen_over_cursor (edit);
+ edit->force |= REDRAW_PAGE;
+ edit_render_keypress (edit);
+
+ /*so that undo stops at each query */
+ edit_push_key_press (edit);
+ /* and prompt 2/3 down */
+ disp1 = edit_replace_cmd__conv_to_display (saved1);
+ disp2 = edit_replace_cmd__conv_to_display (saved2);
+ prompt = edit_dialog_replace_prompt_show (edit, disp1, disp2, -1, -1);
+ g_free (disp1);
+ g_free (disp2);
+
+ if (prompt == B_REPLACE_ALL)
+ edit->replace_mode = 1;
+ else if (prompt == B_SKIP_REPLACE)
+ {
+ if (edit_search_options.backwards)
+ edit->search_start--;
+ else
+ edit->search_start++;
+ continue; /* loop */
+ }
+ else if (prompt == B_CANCEL)
+ {
+ edit->replace_mode = -1;
+ break; /* loop */
+ }
+ }
+
+ repl_str = mc_search_prepare_replace_str (edit->search, input2_str);
+
+ if (edit->search->error != MC_SEARCH_E_OK)
+ {
+ edit_show_search_error (edit, _("Replace"));
+ if (repl_str != NULL)
+ g_string_free (repl_str, TRUE);
+ break;
+ }
+
+ /* delete then insert new */
+ for (i = 0; i < len; i++)
+ edit_delete (edit, TRUE);
+
+ for (i = 0; i < repl_str->len; i++)
+ edit_insert (edit, repl_str->str[i]);
+
+ edit->found_len = repl_str->len;
+ g_string_free (repl_str, TRUE);
+ times_replaced++;
+
+ /* so that we don't find the same string again */
+ if (edit_search_options.backwards)
+ edit->search_start--;
+ else
+ {
+ edit->search_start += edit->found_len + (len == 0 ? 1 : 0);
+
+ if (edit->search_start >= edit->buffer.size)
+ break;
+ }
+
+ edit_scroll_screen_over_cursor (edit);
+ }
+ else
+ {
+ /* try and find from right here for next search */
+ edit->search_start = edit->buffer.curs1;
+ edit_update_curs_col (edit);
+
+ edit->force |= REDRAW_PAGE;
+ edit_render_keypress (edit);
+
+ if (times_replaced == 0)
+ query_dialog (_("Replace"), _(STR_E_NOTFOUND), D_NORMAL, 1, _("&OK"));
+ break;
+ }
+ }
+ while (edit->replace_mode >= 0);
+
+ status_msg_deinit (STATUS_MSG (&esm));
+ edit_scroll_screen_over_cursor (edit);
+ edit->force |= REDRAW_COMPLETELY;
+ edit_render_keypress (edit);
+
+ if (edit->replace_mode == 1 && times_replaced != 0)
+ message (D_NORMAL, _("Replace"), _("%ld replacements made"), times_replaced);
+
+ cleanup:
+ g_free (input1);
+ g_free (input2);
+ if (input2_str != NULL)
+ g_string_free (input2_str, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editsearch.h b/src/editor/editsearch.h
new file mode 100644
index 0000000..5fc3932
--- /dev/null
+++ b/src/editor/editsearch.h
@@ -0,0 +1,36 @@
+#ifndef MC__EDIT_SEARCH_H
+#define MC__EDIT_SEARCH_H 1
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ simple_status_msg_t status_msg; /* base class */
+
+ gboolean first;
+ WEdit *edit;
+ off_t offset;
+} edit_search_status_msg_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+gboolean edit_search_init (WEdit * edit, const char *s);
+void edit_search_deinit (WEdit * edit);
+
+mc_search_cbret_t edit_search_cmd_callback (const void *user_data, gsize char_offset,
+ int *current_char);
+mc_search_cbret_t edit_search_update_callback (const void *user_data, gsize char_offset);
+int edit_search_status_update_cb (status_msg_t * sm);
+
+void edit_search_cmd (WEdit * edit, gboolean again);
+void edit_replace_cmd (WEdit * edit, gboolean again);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__EDIT_SEARCH_H */
diff --git a/src/editor/editwidget.c b/src/editor/editwidget.c
new file mode 100644
index 0000000..05f03e8
--- /dev/null
+++ b/src/editor/editwidget.c
@@ -0,0 +1,1550 @@
+/*
+ Editor initialisation and callback handler.
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1996, 1997
+ Andrew Borodin <aborodin@vmail.ru> 2012-2022
+
+ 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
+ * \brief Source: editor initialisation and callback handler
+ * \author Paul Sheer
+ * \date 1996, 1997
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* LINES, COLS */
+#include "lib/tty/key.h" /* is_idle() */
+#include "lib/tty/color.h" /* tty_setcolor() */
+#include "lib/skin.h"
+#include "lib/fileloc.h" /* EDIT_HOME_DIR */
+#include "lib/strutil.h" /* str_term_trim() */
+#include "lib/util.h" /* mc_build_filename() */
+#include "lib/widget.h"
+#include "lib/mcconfig.h"
+#include "lib/event.h" /* mc_event_raise() */
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "src/keymap.h" /* keybind_lookup_keymap_command() */
+#include "src/setup.h" /* home_dir */
+#include "src/execute.h" /* toggle_subshell() */
+#include "src/filemanager/cmd.h" /* save_setup_cmd() */
+#include "src/learn.h" /* learn_keys() */
+#include "src/args.h" /* mcedit_arg_t */
+
+#include "edit-impl.h"
+#include "editwidget.h"
+#include "editmacros.h" /* edit_execute_macro() */
+#ifdef HAVE_ASPELL
+#include "spell.h"
+#endif
+
+/*** global variables ****************************************************************************/
+
+char *edit_window_state_char = NULL;
+char *edit_window_close_char = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define WINDOW_MIN_LINES (2 + 2)
+#define WINDOW_MIN_COLS (2 + LINE_STATE_WIDTH + 2)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static unsigned int edit_dlg_init_refcounter = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Init the 'edit' subsystem
+ */
+
+static void
+edit_dlg_init (void)
+{
+ edit_dlg_init_refcounter++;
+
+ if (edit_dlg_init_refcounter == 1)
+ {
+ edit_window_state_char = mc_skin_get ("widget-editor", "window-state-char", "*");
+ edit_window_close_char = mc_skin_get ("widget-editor", "window-close-char", "X");
+
+#ifdef HAVE_ASPELL
+ aspell_init ();
+#endif
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Deinit the 'edit' subsystem
+ */
+
+static void
+edit_dlg_deinit (void)
+{
+ if (edit_dlg_init_refcounter == 1)
+ {
+ g_free (edit_window_state_char);
+ g_free (edit_window_close_char);
+
+#ifdef HAVE_ASPELL
+ aspell_clean ();
+#endif
+ }
+
+ if (edit_dlg_init_refcounter != 0)
+ edit_dlg_init_refcounter--;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show info about editor
+ */
+
+static void
+edit_about (void)
+{
+ char *ver;
+
+ ver = g_strdup_printf ("MCEdit %s", mc_global.mc_version);
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABEL (ver, NULL),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_LABEL (N_("A user friendly text editor\n"
+ "written for the Midnight Commander."), NULL),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_LABEL (N_("Copyright (C) 1996-2023 the Free Software Foundation"), NULL),
+ QUICK_START_BUTTONS (TRUE, TRUE),
+ QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 40 };
+
+ quick_dialog_t qdlg = {
+ r, N_("About"), "[Internal File Editor]",
+ quick_widgets, NULL, NULL
+ };
+
+ quick_widgets[0].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
+ quick_widgets[2].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
+ quick_widgets[4].pos_flags = WPOS_KEEP_TOP | WPOS_CENTER_HORZ;
+
+ (void) quick_dialog (&qdlg);
+ }
+
+ g_free (ver);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show a help window
+ */
+
+static void
+edit_help (void)
+{
+ ev_help_t event_data = { NULL, "[Internal File Editor]" };
+ mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Restore saved window size.
+ *
+ * @param edit editor object
+ */
+
+static void
+edit_restore_size (WEdit * edit)
+{
+ Widget *w = WIDGET (edit);
+
+ edit->drag_state = MCEDIT_DRAG_NONE;
+ w->mouse.forced_capture = FALSE;
+ widget_set_size_rect (w, &edit->loc_prev);
+ widget_draw (WIDGET (w->owner));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Move window by one row or column in any direction.
+ *
+ * @param edit editor object
+ * @param command direction (CK_Up, CK_Down, CK_Left, CK_Right)
+ */
+
+static void
+edit_window_move (WEdit * edit, long command)
+{
+ Widget *we = WIDGET (edit);
+ Widget *wo = WIDGET (we->owner);
+ WRect *w = &we->rect;
+ const WRect *wh = &wo->rect;
+
+ switch (command)
+ {
+ case CK_Up:
+ if (w->y > wh->y + 1) /* menubar */
+ w->y--;
+ break;
+ case CK_Down:
+ if (w->y < wh->y + wh->lines - 2) /* buttonbar */
+ w->y++;
+ break;
+ case CK_Left:
+ if (w->x + wh->cols > wh->x)
+ w->x--;
+ break;
+ case CK_Right:
+ if (w->x < wh->x + wh->cols)
+ w->x++;
+ break;
+ default:
+ return;
+ }
+
+ edit->force |= REDRAW_PAGE;
+ widget_draw (wo);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Resize window by one row or column in any direction.
+ *
+ * @param edit editor object
+ * @param command direction (CK_Up, CK_Down, CK_Left, CK_Right)
+ */
+
+static void
+edit_window_resize (WEdit * edit, long command)
+{
+ Widget *we = WIDGET (edit);
+ Widget *wo = WIDGET (we->owner);
+ WRect *w = &we->rect;
+ const WRect *wh = &wo->rect;
+
+ switch (command)
+ {
+ case CK_Up:
+ if (w->lines > WINDOW_MIN_LINES)
+ w->lines--;
+ break;
+ case CK_Down:
+ if (w->y + w->lines < wh->y + wh->lines - 1) /* buttonbar */
+ w->lines++;
+ break;
+ case CK_Left:
+ if (w->cols > WINDOW_MIN_COLS)
+ w->cols--;
+ break;
+ case CK_Right:
+ if (w->x + w->cols < wh->x + wh->cols)
+ w->cols++;
+ break;
+ default:
+ return;
+ }
+
+ edit->force |= REDRAW_COMPLETELY;
+ widget_draw (wo);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get hotkey by number.
+ *
+ * @param n number
+ * @return hotkey
+ */
+
+static unsigned char
+get_hotkey (int n)
+{
+ return (n <= 9) ? '0' + n : 'a' + n - 10;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_window_list (const WDialog * h)
+{
+ const WGroup *g = CONST_GROUP (h);
+ const size_t offset = 2; /* skip menu and buttonbar */
+ const size_t dlg_num = g_list_length (g->widgets) - offset;
+ int lines, cols;
+ Listbox *listbox;
+ GList *w;
+ WEdit *selected;
+ int i = 0;
+
+ lines = MIN ((size_t) (LINES * 2 / 3), dlg_num);
+ cols = COLS * 2 / 3;
+
+ listbox = listbox_window_new (lines, cols, _("Open files"), "[Open files]");
+
+ for (w = g->widgets; w != NULL; w = g_list_next (w))
+ if (edit_widget_is_editor (CONST_WIDGET (w->data)))
+ {
+ WEdit *e = EDIT (w->data);
+ char *fname;
+
+ if (e->filename_vpath == NULL)
+ fname = g_strdup_printf ("%c [%s]", e->modified ? '*' : ' ', _("NoName"));
+ else
+ fname =
+ g_strdup_printf ("%c%s", e->modified ? '*' : ' ',
+ vfs_path_as_str (e->filename_vpath));
+
+ listbox_add_item (listbox->list, LISTBOX_APPEND_AT_END, get_hotkey (i++),
+ str_term_trim (fname, WIDGET (listbox->list)->rect.cols - 2), e,
+ FALSE);
+ g_free (fname);
+ }
+
+ selected = listbox_run_with_data (listbox, g->current->data);
+ if (selected != NULL)
+ widget_select (WIDGET (selected));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+edit_get_shortcut (long command)
+{
+ const char *ext_map;
+ const char *shortcut = NULL;
+
+ shortcut = keybind_lookup_keymap_shortcut (editor_map, command);
+ if (shortcut != NULL)
+ return g_strdup (shortcut);
+
+ ext_map = keybind_lookup_keymap_shortcut (editor_map, CK_ExtendedKeyMap);
+ if (ext_map != NULL)
+ shortcut = keybind_lookup_keymap_shortcut (editor_x_map, command);
+ if (shortcut != NULL)
+ return g_strdup_printf ("%s %s", ext_map, shortcut);
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+edit_get_title (const WDialog * h, size_t len)
+{
+ const WEdit *edit;
+ const char *modified;
+ const char *file_label;
+ char *filename;
+
+ edit = edit_find_editor (h);
+ modified = edit->modified ? "(*) " : " ";
+
+ len -= 4;
+
+ if (edit->filename_vpath == NULL)
+ filename = g_strdup (_("[NoName]"));
+ else
+ filename = g_strdup (vfs_path_as_str (edit->filename_vpath));
+
+ file_label = str_term_trim (filename, len - str_term_width1 (_("Edit: ")));
+ g_free (filename);
+
+ return g_strconcat (_("Edit: "), modified, file_label, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+edit_dialog_command_execute (WDialog * h, long command)
+{
+ WGroup *g = GROUP (h);
+ cb_ret_t ret = MSG_HANDLED;
+
+ switch (command)
+ {
+ case CK_EditNew:
+ edit_load_file_from_filename (h, NULL, 0);
+ break;
+ case CK_EditFile:
+ edit_load_cmd (h);
+ break;
+ case CK_History:
+ edit_load_file_from_history (h);
+ break;
+ case CK_EditSyntaxFile:
+ edit_load_syntax_file (h);
+ break;
+ case CK_EditUserMenu:
+ edit_load_menu_file (h);
+ break;
+ case CK_Close:
+ /* if there are no opened files anymore, close MC editor */
+ if (edit_widget_is_editor (CONST_WIDGET (g->current->data)) &&
+ edit_close_cmd (EDIT (g->current->data)) && edit_find_editor (h) == NULL)
+ dlg_close (h);
+ break;
+ case CK_Help:
+ edit_help ();
+ /* edit->force |= REDRAW_COMPLETELY; */
+ break;
+ case CK_Menu:
+ edit_menu_cmd (h);
+ break;
+ case CK_Quit:
+ case CK_Cancel:
+ /* don't close editor due to SIGINT, but stop move/resize window */
+ {
+ Widget *w = WIDGET (g->current->data);
+
+ if (edit_widget_is_editor (w) && EDIT (w)->drag_state != MCEDIT_DRAG_NONE)
+ edit_restore_size (EDIT (w));
+ else if (command == CK_Quit)
+ dlg_close (h);
+ }
+ break;
+ case CK_About:
+ edit_about ();
+ break;
+ case CK_SyntaxOnOff:
+ edit_syntax_onoff_cmd (h);
+ break;
+ case CK_ShowTabTws:
+ edit_show_tabs_tws_cmd (h);
+ break;
+ case CK_ShowMargin:
+ edit_show_margin_cmd (h);
+ break;
+ case CK_ShowNumbers:
+ edit_show_numbers_cmd (h);
+ break;
+ case CK_Refresh:
+ edit_refresh_cmd ();
+ break;
+ case CK_Shell:
+ toggle_subshell ();
+ break;
+ case CK_LearnKeys:
+ learn_keys ();
+ break;
+ case CK_WindowMove:
+ case CK_WindowResize:
+ if (edit_widget_is_editor (CONST_WIDGET (g->current->data)))
+ edit_handle_move_resize (EDIT (g->current->data), command);
+ break;
+ case CK_WindowList:
+ edit_window_list (h);
+ break;
+ case CK_WindowNext:
+ group_select_next_widget (g);
+ break;
+ case CK_WindowPrev:
+ group_select_prev_widget (g);
+ break;
+ case CK_Options:
+ edit_options_dialog (h);
+ break;
+ case CK_OptionsSaveMode:
+ edit_save_mode_cmd ();
+ break;
+ case CK_SaveSetup:
+ save_setup_cmd ();
+ break;
+ default:
+ ret = MSG_NOT_HANDLED;
+ break;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Translate the keycode into either 'command' or 'char_for_insertion'.
+ * 'command' is one of the editor commands from lib/keybind.h.
+ */
+
+static gboolean
+edit_translate_key (WEdit * edit, long x_key, int *cmd, int *ch)
+{
+ Widget *w = WIDGET (edit);
+ long command = CK_InsertChar;
+ int char_for_insertion = -1;
+
+ /* an ordinary insertable character */
+ if (!w->ext_mode && x_key < 256)
+ {
+#ifndef HAVE_CHARSET
+ if (is_printable (x_key))
+ {
+ char_for_insertion = x_key;
+ goto fin;
+ }
+#else
+ int c;
+
+ if (edit->charpoint >= MB_LEN_MAX)
+ {
+ edit->charpoint = 0;
+ edit->charbuf[edit->charpoint] = '\0';
+ }
+ if (edit->charpoint < MB_LEN_MAX)
+ {
+ edit->charbuf[edit->charpoint++] = x_key;
+ edit->charbuf[edit->charpoint] = '\0';
+ }
+
+ /* input from 8-bit locale */
+ if (!mc_global.utf8_display)
+ {
+ /* source is in 8-bit codeset */
+ c = convert_from_input_c (x_key);
+
+ if (is_printable (c))
+ {
+ if (!edit->utf8)
+ char_for_insertion = c;
+ else
+ char_for_insertion = convert_from_8bit_to_utf_c2 ((char) x_key);
+ goto fin;
+ }
+ }
+ else
+ {
+ /* UTF-8 locale */
+ int res;
+
+ res = str_is_valid_char (edit->charbuf, edit->charpoint);
+ if (res < 0 && res != -2)
+ {
+ edit->charpoint = 0; /* broken multibyte char, skip */
+ goto fin;
+ }
+
+ if (edit->utf8)
+ {
+ /* source is in UTF-8 codeset */
+ if (res < 0)
+ {
+ char_for_insertion = x_key;
+ goto fin;
+ }
+
+ edit->charbuf[edit->charpoint] = '\0';
+ edit->charpoint = 0;
+ if (g_unichar_isprint (g_utf8_get_char (edit->charbuf)))
+ {
+ char_for_insertion = x_key;
+ goto fin;
+ }
+ }
+ else
+ {
+ /* 8-bit source */
+ if (res < 0)
+ {
+ /* not finished multibyte input (we're in the middle of multibyte utf-8 char) */
+ goto fin;
+ }
+
+ if (g_unichar_isprint (g_utf8_get_char (edit->charbuf)))
+ {
+ c = convert_from_utf_to_current (edit->charbuf);
+ edit->charbuf[0] = '\0';
+ edit->charpoint = 0;
+ char_for_insertion = c;
+ goto fin;
+ }
+
+ /* non-printable utf-8 input, skip it */
+ edit->charbuf[0] = '\0';
+ edit->charpoint = 0;
+ }
+ }
+#endif /* HAVE_CHARSET */
+ }
+
+ /* Commands specific to the key emulation */
+ command = widget_lookup_key (w, x_key);
+ if (command == CK_IgnoreKey)
+ command = CK_InsertChar;
+
+ fin:
+ *cmd = (int) command; /* FIXME */
+ *ch = char_for_insertion;
+
+ return !(command == CK_InsertChar && char_for_insertion == -1);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_quit (WDialog * h)
+{
+ GList *l;
+ WEdit *e = NULL;
+ GSList *m = NULL;
+ GSList *me;
+
+ /* don't stop the dialog before final decision */
+ widget_set_state (WIDGET (h), WST_ACTIVE, TRUE);
+
+ /* check window state and get modified files */
+ for (l = GROUP (h)->widgets; l != NULL; l = g_list_next (l))
+ if (edit_widget_is_editor (CONST_WIDGET (l->data)))
+ {
+ e = EDIT (l->data);
+
+ if (e->drag_state != MCEDIT_DRAG_NONE)
+ {
+ edit_restore_size (e);
+ g_slist_free (m);
+ return;
+ }
+
+ /* create separate list because widget_select()
+ changes the window position in Z order */
+ if (e->modified)
+ m = g_slist_prepend (m, l->data);
+ }
+
+ for (me = m; me != NULL; me = g_slist_next (me))
+ {
+ e = EDIT (me->data);
+
+ widget_select (WIDGET (e));
+
+ if (!edit_ok_to_exit (e))
+ break;
+ }
+
+ /* if all files were checked, quit editor */
+ if (me == NULL)
+ dlg_close (h);
+
+ g_slist_free (m);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+edit_set_buttonbar (WEdit * edit, WButtonBar * bb)
+{
+ Widget *w = WIDGET (edit);
+
+ buttonbar_set_label (bb, 1, Q_ ("ButtonBar|Help"), w->keymap, NULL);
+ buttonbar_set_label (bb, 2, Q_ ("ButtonBar|Save"), w->keymap, w);
+ buttonbar_set_label (bb, 3, Q_ ("ButtonBar|Mark"), w->keymap, w);
+ buttonbar_set_label (bb, 4, Q_ ("ButtonBar|Replac"), w->keymap, w);
+ buttonbar_set_label (bb, 5, Q_ ("ButtonBar|Copy"), w->keymap, w);
+ buttonbar_set_label (bb, 6, Q_ ("ButtonBar|Move"), w->keymap, w);
+ buttonbar_set_label (bb, 7, Q_ ("ButtonBar|Search"), w->keymap, w);
+ buttonbar_set_label (bb, 8, Q_ ("ButtonBar|Delete"), w->keymap, w);
+ buttonbar_set_label (bb, 9, Q_ ("ButtonBar|PullDn"), w->keymap, NULL);
+ buttonbar_set_label (bb, 10, Q_ ("ButtonBar|Quit"), w->keymap, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_total_update (WEdit * edit)
+{
+ edit_find_bracket (edit);
+ edit->force |= REDRAW_COMPLETELY;
+ edit_update_curs_row (edit);
+ edit_update_screen (edit);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+edit_update_cursor (WEdit * edit, const mouse_event_t * event)
+{
+ int x, y;
+ gboolean done;
+
+ x = event->x - (edit->fullscreen ? 0 : 1);
+ y = event->y - (edit->fullscreen ? 0 : 1);
+
+ if (edit->mark2 != -1 && event->msg == MSG_MOUSE_UP)
+ return TRUE; /* don't do anything */
+
+ if (event->msg == MSG_MOUSE_DOWN || event->msg == MSG_MOUSE_UP)
+ edit_push_key_press (edit);
+
+ if (!edit_options.cursor_beyond_eol)
+ edit->prev_col = x - edit->start_col - edit_options.line_state_width;
+ else
+ {
+ long line_len;
+
+ line_len =
+ edit_move_forward3 (edit, edit_buffer_get_current_bol (&edit->buffer), 0,
+ edit_buffer_get_current_eol (&edit->buffer));
+
+ if (x > line_len - 1)
+ {
+ edit->over_col = x - line_len - edit->start_col - edit_options.line_state_width;
+ edit->prev_col = line_len;
+ }
+ else
+ {
+ edit->over_col = 0;
+ edit->prev_col = x - edit_options.line_state_width - edit->start_col;
+ }
+ }
+
+ if (y > edit->curs_row)
+ edit_move_down (edit, y - edit->curs_row, FALSE);
+ else if (y < edit->curs_row)
+ edit_move_up (edit, edit->curs_row - y, FALSE);
+ else
+ edit_move_to_prev_col (edit, edit_buffer_get_current_bol (&edit->buffer));
+
+ if (event->msg == MSG_MOUSE_CLICK)
+ {
+ edit_mark_cmd (edit, TRUE); /* reset */
+ edit->highlight = 0;
+ }
+
+ done = (event->msg != MSG_MOUSE_DRAG);
+ if (done)
+ edit_mark_cmd (edit, FALSE);
+
+ return done;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Callback for the edit dialog */
+
+static cb_ret_t
+edit_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WGroup *g = GROUP (w);
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ edit_dlg_init ();
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ dlg_default_callback (w, NULL, MSG_RESIZE, 0, NULL);
+ menubar_arrange (menubar_find (h));
+ return MSG_HANDLED;
+
+ case MSG_ACTION:
+ {
+ /* Handle shortcuts, menu, and buttonbar. */
+
+ cb_ret_t result;
+
+ result = edit_dialog_command_execute (h, parm);
+
+ /* We forward any commands coming from the menu, and which haven't been
+ handled by the dialog, to the focused WEdit window. */
+ if (result == MSG_NOT_HANDLED && sender == WIDGET (menubar_find (h)))
+ result = send_message (g->current->data, NULL, MSG_ACTION, parm, NULL);
+
+ return result;
+ }
+
+ case MSG_KEY:
+ {
+ Widget *we = WIDGET (g->current->data);
+ cb_ret_t ret = MSG_NOT_HANDLED;
+
+ if (edit_widget_is_editor (we))
+ {
+ gboolean ext_mode;
+ long command;
+
+ /* keep and then extmod flag */
+ ext_mode = we->ext_mode;
+ command = widget_lookup_key (we, parm);
+ we->ext_mode = ext_mode;
+
+ if (command == CK_IgnoreKey)
+ we->ext_mode = FALSE;
+ else
+ {
+ ret = edit_dialog_command_execute (h, command);
+ /* if command was not handled, keep the extended mode
+ for the further key processing */
+ if (ret == MSG_HANDLED)
+ we->ext_mode = FALSE;
+ }
+ }
+
+ /*
+ * Due to the "end of bracket" escape the editor sees input with is_idle() == false
+ * (expects more characters) and hence doesn't yet refresh the screen, but then
+ * no further characters arrive (there's only an "end of bracket" which is swallowed
+ * by tty_get_event()), so you end up with a screen that's not refreshed after pasting.
+ * So let's trigger an IDLE signal.
+ */
+ if (!is_idle ())
+ widget_idle (w, TRUE);
+ return ret;
+ }
+
+ /* hardcoded menu hotkeys (see edit_drop_hotkey_menu) */
+ case MSG_UNHANDLED_KEY:
+ return edit_drop_hotkey_menu (h, parm) ? MSG_HANDLED : MSG_NOT_HANDLED;
+
+ case MSG_VALIDATE:
+ edit_quit (h);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ edit_dlg_deinit ();
+ return MSG_HANDLED;
+
+ case MSG_IDLE:
+ widget_idle (w, FALSE);
+ return send_message (g->current->data, NULL, MSG_IDLE, 0, NULL);
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Handle mouse events of editor screen.
+ *
+ * @param w Widget object (the editor)
+ * @param msg mouse event message
+ * @param event mouse event data
+ */
+static void
+edit_dialog_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ gboolean unhandled = TRUE;
+
+ if (msg == MSG_MOUSE_DOWN && event->y == 0)
+ {
+ WGroup *g = GROUP (w);
+ WDialog *h = DIALOG (w);
+ WMenuBar *b;
+
+ b = menubar_find (h);
+
+ if (!widget_get_state (WIDGET (b), WST_FOCUSED))
+ {
+ /* menubar */
+
+ GList *l;
+ GList *top = NULL;
+ int x;
+
+ /* Try find top fullscreen window */
+ for (l = g->widgets; l != NULL; l = g_list_next (l))
+ if (edit_widget_is_editor (CONST_WIDGET (l->data)) && EDIT (l->data)->fullscreen)
+ top = l;
+
+ /* Handle fullscreen/close buttons in the top line */
+ x = w->rect.cols - 6;
+
+ if (top != NULL && event->x >= x)
+ {
+ WEdit *e = EDIT (top->data);
+
+ if (top != g->current)
+ {
+ /* Window is not active. Activate it */
+ widget_select (WIDGET (e));
+ }
+
+ /* Handle buttons */
+ if (event->x - x <= 2)
+ edit_toggle_fullscreen (e);
+ else
+ send_message (h, NULL, MSG_ACTION, CK_Close, NULL);
+
+ unhandled = FALSE;
+ }
+
+ if (unhandled)
+ menubar_activate (b, drop_menus, -1);
+ }
+ }
+
+ /* Continue handling of unhandled event in window or menu */
+ event->result.abort = unhandled;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+edit_dialog_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_INIT:
+ w->rect = WIDGET (w->owner)->rect;
+ rect_grow (&w->rect, -1, 0);
+ w->pos_flags |= WPOS_KEEP_ALL;
+ return MSG_HANDLED;
+
+ default:
+ return background_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+edit_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WEdit *e = EDIT (w);
+
+ switch (msg)
+ {
+ case MSG_FOCUS:
+ edit_set_buttonbar (e, buttonbar_find (DIALOG (w->owner)));
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ e->force |= REDRAW_COMPLETELY;
+ edit_update_screen (e);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ {
+ int cmd, ch;
+ cb_ret_t ret = MSG_NOT_HANDLED;
+
+ /* The user may override the access-keys for the menu bar. */
+ if (macro_index == -1 && edit_execute_macro (e, parm))
+ {
+ edit_update_screen (e);
+ ret = MSG_HANDLED;
+ }
+ else if (edit_translate_key (e, parm, &cmd, &ch))
+ {
+ edit_execute_key_command (e, cmd, ch);
+ edit_update_screen (e);
+ ret = MSG_HANDLED;
+ }
+
+ return ret;
+ }
+
+ case MSG_ACTION:
+ /* command from menubar or buttonbar */
+ edit_execute_key_command (e, parm, -1);
+ edit_update_screen (e);
+ return MSG_HANDLED;
+
+ case MSG_CURSOR:
+ {
+ int y, x;
+
+ y = (e->fullscreen ? 0 : 1) + EDIT_TEXT_VERTICAL_OFFSET + e->curs_row;
+ x = (e->fullscreen ? 0 : 1) + EDIT_TEXT_HORIZONTAL_OFFSET +
+ edit_options.line_state_width + e->curs_col + e->start_col + e->over_col;
+
+ widget_gotoyx (w, y, x);
+ return MSG_HANDLED;
+ }
+
+ case MSG_IDLE:
+ edit_update_screen (e);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ edit_clean (e);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Handle move/resize mouse events.
+ */
+static void
+edit_mouse_handle_move_resize (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WEdit *edit = EDIT (w);
+ WRect *r = &w->rect;
+ const WRect *h = &CONST_WIDGET (w->owner)->rect;
+ int global_x, global_y;
+
+ if (msg == MSG_MOUSE_UP)
+ {
+ /* Exit move/resize mode. */
+ edit_execute_cmd (edit, CK_Enter, -1);
+ edit_update_screen (edit); /* Paint the buttonbar over our possibly overlapping frame. */
+ return;
+ }
+
+ if (msg != MSG_MOUSE_DRAG)
+ /**
+ * We ignore any other events. Specifically, MSG_MOUSE_DOWN.
+ *
+ * When the move/resize is initiated by the menu, we let the user
+ * stop it by clicking with the mouse. Which is why we don't want
+ * a mouse down to affect the window.
+ */
+ return;
+
+ /* Convert point to global coordinates for easier calculations. */
+ global_x = event->x + r->x;
+ global_y = event->y + r->y;
+
+ /* Clamp the point to the dialog's client area. */
+ global_y = CLAMP (global_y, h->y + 1, h->y + h->lines - 2); /* Status line, buttonbar */
+ global_x = CLAMP (global_x, h->x, h->x + h->cols - 1); /* Currently a no-op, as the dialog has no left/right margins. */
+
+ if (edit->drag_state == MCEDIT_DRAG_MOVE)
+ {
+ r->y = global_y;
+ r->x = global_x - edit->drag_state_start;
+ }
+ else if (edit->drag_state == MCEDIT_DRAG_RESIZE)
+ {
+ r->lines = MAX (WINDOW_MIN_LINES, global_y - r->y + 1);
+ r->cols = MAX (WINDOW_MIN_COLS, global_x - r->x + 1);
+ }
+
+ edit->force |= REDRAW_COMPLETELY; /* Not really needed as WEdit's MSG_DRAW already does this. */
+
+ /* We draw the whole dialog because dragging/resizing exposes area beneath. */
+ widget_draw (WIDGET (w->owner));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Handle mouse events of editor window
+ *
+ * @param w Widget object (the editor window)
+ * @param msg mouse event message
+ * @param event mouse event data
+ */
+static void
+edit_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WEdit *edit = EDIT (w);
+ /* buttons' distance from right edge */
+ int dx = edit->fullscreen ? 0 : 2;
+ /* location of 'Close' and 'Toggle fullscreen' pictograms */
+ int close_x, toggle_fullscreen_x;
+
+ close_x = (w->rect.cols - 1) - dx - 1;
+ toggle_fullscreen_x = close_x - 3;
+
+ if (edit->drag_state != MCEDIT_DRAG_NONE)
+ {
+ /* window is being resized/moved */
+ edit_mouse_handle_move_resize (w, msg, event);
+ return;
+ }
+
+ /* If it's the last line on the screen, we abort the event to make the
+ * system channel it to the overlapping buttonbar instead. We have to do
+ * this because a WEdit has the WOP_TOP_SELECT flag, which makes it above
+ * the buttonbar in Z-order. */
+ if (msg == MSG_MOUSE_DOWN && (event->y + w->rect.y == LINES - 1))
+ {
+ event->result.abort = TRUE;
+ return;
+ }
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ widget_select (w);
+ edit_update_curs_row (edit);
+ edit_update_curs_col (edit);
+
+ if (!edit->fullscreen)
+ {
+ if (event->y == 0)
+ {
+ if (event->x >= close_x - 1 && event->x <= close_x + 1)
+ ; /* do nothing (see MSG_MOUSE_CLICK) */
+ else if (event->x >= toggle_fullscreen_x - 1 && event->x <= toggle_fullscreen_x + 1)
+ ; /* do nothing (see MSG_MOUSE_CLICK) */
+ else
+ {
+ /* start window move */
+ edit_execute_cmd (edit, CK_WindowMove, -1);
+ edit_update_screen (edit); /* Paint the buttonbar over our possibly overlapping frame. */
+ edit->drag_state_start = event->x;
+ }
+ break;
+ }
+
+ if (event->y == w->rect.lines - 1 && event->x == w->rect.cols - 1)
+ {
+ /* bottom-right corner -- start window resize */
+ edit_execute_cmd (edit, CK_WindowResize, -1);
+ break;
+ }
+ }
+
+ MC_FALLTHROUGH; /* to start/stop text selection */
+
+ case MSG_MOUSE_UP:
+ edit_update_cursor (edit, event);
+ edit_total_update (edit);
+ break;
+
+ case MSG_MOUSE_CLICK:
+ if (event->y == 0)
+ {
+ if (event->x >= close_x - 1 && event->x <= close_x + 1)
+ send_message (w->owner, NULL, MSG_ACTION, CK_Close, NULL);
+ else if (event->x >= toggle_fullscreen_x - 1 && event->x <= toggle_fullscreen_x + 1)
+ edit_toggle_fullscreen (edit);
+ else if (!edit->fullscreen && event->count == GPM_DOUBLE)
+ /* double click on top line (toggle fullscreen) */
+ edit_toggle_fullscreen (edit);
+ }
+ else if (event->count == GPM_DOUBLE)
+ {
+ /* double click */
+ edit_mark_current_word_cmd (edit);
+ edit_total_update (edit);
+ }
+ else if (event->count == GPM_TRIPLE)
+ {
+ /* triple click: works in GPM only, not in xterm */
+ edit_mark_current_line_cmd (edit);
+ edit_total_update (edit);
+ }
+ break;
+
+ case MSG_MOUSE_DRAG:
+ edit_update_cursor (edit, event);
+ edit_total_update (edit);
+ break;
+
+ case MSG_MOUSE_SCROLL_UP:
+ edit_move_up (edit, 2, TRUE);
+ edit_total_update (edit);
+ break;
+
+ case MSG_MOUSE_SCROLL_DOWN:
+ edit_move_down (edit, 2, TRUE);
+ edit_total_update (edit);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Edit one file.
+ *
+ * @param file_vpath file object
+ * @param line line number
+ * @return TRUE if no errors was occurred, FALSE otherwise
+ */
+
+gboolean
+edit_file (const vfs_path_t * file_vpath, long line)
+{
+ mcedit_arg_t arg = { (vfs_path_t *) file_vpath, line };
+ GList *files;
+ gboolean ok;
+
+ files = g_list_prepend (NULL, &arg);
+ ok = edit_files (files);
+ g_list_free (files);
+
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+edit_files (const GList * files)
+{
+ static gboolean made_directory = FALSE;
+ WDialog *edit_dlg;
+ WGroup *g;
+ WMenuBar *menubar;
+ Widget *w, *wd;
+ const GList *file;
+ gboolean ok = FALSE;
+
+ if (!made_directory)
+ {
+ char *dir;
+
+ dir = mc_build_filename (mc_config_get_cache_path (), EDIT_HOME_DIR, (char *) NULL);
+ made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
+ g_free (dir);
+
+ dir = mc_build_filename (mc_config_get_path (), EDIT_HOME_DIR, (char *) NULL);
+ made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
+ g_free (dir);
+
+ dir = mc_build_filename (mc_config_get_data_path (), EDIT_HOME_DIR, (char *) NULL);
+ made_directory = (mkdir (dir, 0700) != -1 || errno == EEXIST);
+ g_free (dir);
+ }
+
+ /* Create a new dialog and add it widgets to it */
+ edit_dlg =
+ dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, NULL, edit_dialog_callback,
+ edit_dialog_mouse_callback, "[Internal File Editor]", NULL);
+ wd = WIDGET (edit_dlg);
+ widget_want_tab (wd, TRUE);
+ wd->keymap = editor_map;
+ wd->ext_keymap = editor_x_map;
+
+ edit_dlg->get_shortcut = edit_get_shortcut;
+ edit_dlg->get_title = edit_get_title;
+
+ g = GROUP (edit_dlg);
+
+ edit_dlg->bg =
+ WIDGET (background_new
+ (1, 0, wd->rect.lines - 2, wd->rect.cols, EDITOR_BACKGROUND, ' ',
+ edit_dialog_bg_callback));
+ group_add_widget (g, edit_dlg->bg);
+
+ menubar = menubar_new (NULL);
+ w = WIDGET (menubar);
+ group_add_widget_autopos (g, w, w->pos_flags, NULL);
+ edit_init_menu (menubar);
+
+ w = WIDGET (buttonbar_new ());
+ group_add_widget_autopos (g, w, w->pos_flags, NULL);
+
+ for (file = files; file != NULL; file = g_list_next (file))
+ {
+ mcedit_arg_t *f = (mcedit_arg_t *) file->data;
+ gboolean f_ok;
+
+ f_ok = edit_load_file_from_filename (edit_dlg, f->file_vpath, f->line_number);
+ /* at least one file has been opened succefully */
+ ok = ok || f_ok;
+ }
+
+ if (ok)
+ dlg_run (edit_dlg);
+
+ if (!ok || widget_get_state (wd, WST_CLOSED))
+ widget_destroy (wd);
+
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+edit_get_file_name (const WEdit * edit)
+{
+ return vfs_path_as_str (edit->filename_vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WEdit *
+edit_find_editor (const WDialog * h)
+{
+ const WGroup *g = CONST_GROUP (h);
+
+ if (edit_widget_is_editor (CONST_WIDGET (g->current->data)))
+ return EDIT (g->current->data);
+ return EDIT (widget_find_by_type (CONST_WIDGET (h), edit_callback));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check if widget is an WEdit class.
+ *
+ * @param w probably editor object
+ * @return TRUE if widget is an WEdit class, FALSE otherwise
+ */
+
+gboolean
+edit_widget_is_editor (const Widget * w)
+{
+ return (w != NULL && w->callback == edit_callback);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_update_screen (WEdit * e)
+{
+ edit_scroll_screen_over_cursor (e);
+ edit_update_curs_col (e);
+ edit_status (e, widget_get_state (WIDGET (e), WST_FOCUSED));
+
+ /* pop all events for this window for internal handling */
+ if (!is_idle ())
+ e->force |= REDRAW_PAGE;
+ else
+ {
+ if ((e->force & REDRAW_COMPLETELY) != 0)
+ e->force |= REDRAW_PAGE;
+ edit_render_keypress (e);
+ }
+
+ widget_draw (WIDGET (buttonbar_find (DIALOG (WIDGET (e)->owner))));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Save current window size.
+ *
+ * @param edit editor object
+ */
+
+void
+edit_save_size (WEdit * edit)
+{
+ edit->loc_prev = WIDGET (edit)->rect;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create new editor window and insert it into editor screen.
+ *
+ * @param h editor dialog (screen)
+ * @param y y coordinate
+ * @param x x coordinate
+ * @param lines window height
+ * @param cols window width
+ * @param f file object
+ * @param fline line number in file
+ * @return TRUE if new window was successfully created and inserted into editor screen,
+ * FALSE otherwise
+ */
+
+gboolean
+edit_add_window (WDialog * h, const WRect * r, const vfs_path_t * f, long fline)
+{
+ WEdit *edit;
+ Widget *w;
+
+ edit = edit_init (NULL, r, f, fline);
+ if (edit == NULL)
+ return FALSE;
+
+ w = WIDGET (edit);
+ w->callback = edit_callback;
+ w->mouse_callback = edit_mouse_callback;
+
+ group_add_widget_autopos (GROUP (h), w, WPOS_KEEP_ALL, NULL);
+ edit_set_buttonbar (edit, buttonbar_find (h));
+ widget_draw (WIDGET (h));
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Handle move/resize events.
+ *
+ * @param edit editor object
+ * @param command action id
+ * @return TRUE if the action was handled, FALSE otherwise
+ */
+
+gboolean
+edit_handle_move_resize (WEdit * edit, long command)
+{
+ Widget *w = WIDGET (edit);
+ gboolean ret = FALSE;
+
+ if (edit->fullscreen)
+ {
+ edit->drag_state = MCEDIT_DRAG_NONE;
+ w->mouse.forced_capture = FALSE;
+ return ret;
+ }
+
+ switch (edit->drag_state)
+ {
+ case MCEDIT_DRAG_NONE:
+ /* possible start move/resize */
+ switch (command)
+ {
+ case CK_WindowMove:
+ edit->drag_state = MCEDIT_DRAG_MOVE;
+ edit_save_size (edit);
+ edit_status (edit, TRUE); /* redraw frame and status */
+ /**
+ * If a user initiates a move by the menu, not by the mouse, we
+ * make a subsequent mouse drag pull the frame from its middle.
+ * (We can instead choose '0' to pull it from the corner.)
+ */
+ edit->drag_state_start = w->rect.cols / 2;
+ ret = TRUE;
+ break;
+ case CK_WindowResize:
+ edit->drag_state = MCEDIT_DRAG_RESIZE;
+ edit_save_size (edit);
+ edit_status (edit, TRUE); /* redraw frame and status */
+ ret = TRUE;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case MCEDIT_DRAG_MOVE:
+ switch (command)
+ {
+ case CK_WindowResize:
+ edit->drag_state = MCEDIT_DRAG_RESIZE;
+ ret = TRUE;
+ break;
+ case CK_Up:
+ case CK_Down:
+ case CK_Left:
+ case CK_Right:
+ edit_window_move (edit, command);
+ ret = TRUE;
+ break;
+ case CK_Enter:
+ case CK_WindowMove:
+ edit->drag_state = MCEDIT_DRAG_NONE;
+ edit_status (edit, TRUE); /* redraw frame and status */
+ MC_FALLTHROUGH;
+ default:
+ ret = TRUE;
+ break;
+ }
+ break;
+
+ case MCEDIT_DRAG_RESIZE:
+ switch (command)
+ {
+ case CK_WindowMove:
+ edit->drag_state = MCEDIT_DRAG_MOVE;
+ ret = TRUE;
+ break;
+ case CK_Up:
+ case CK_Down:
+ case CK_Left:
+ case CK_Right:
+ edit_window_resize (edit, command);
+ ret = TRUE;
+ break;
+ case CK_Enter:
+ case CK_WindowResize:
+ edit->drag_state = MCEDIT_DRAG_NONE;
+ edit_status (edit, TRUE); /* redraw frame and status */
+ MC_FALLTHROUGH;
+ default:
+ ret = TRUE;
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /**
+ * - We let the user stop a resize/move operation by clicking with the
+ * mouse anywhere. ("clicking" = pressing and releasing a button.)
+ * - We let the user perform a resize/move operation by a mouse drag
+ * initiated anywhere.
+ *
+ * "Anywhere" means: inside or outside the window. We make this happen
+ * with the 'forced_capture' flag.
+ */
+ w->mouse.forced_capture = (edit->drag_state != MCEDIT_DRAG_NONE);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Toggle window fuulscreen mode.
+ *
+ * @param edit editor object
+ */
+
+void
+edit_toggle_fullscreen (WEdit * edit)
+{
+ Widget *w = WIDGET (edit);
+
+ edit->fullscreen = !edit->fullscreen;
+ edit->force = REDRAW_COMPLETELY;
+
+ if (!edit->fullscreen)
+ {
+ edit_restore_size (edit);
+ /* do not follow screen size on resize */
+ w->pos_flags = WPOS_KEEP_DEFAULT;
+ }
+ else
+ {
+ WRect r;
+
+ edit_save_size (edit);
+ r = WIDGET (w->owner)->rect;
+ rect_grow (&r, -1, 0);
+ widget_set_size_rect (w, &r);
+ /* follow screen size on resize */
+ w->pos_flags = WPOS_KEEP_ALL;
+ edit->force |= REDRAW_PAGE;
+ edit_update_screen (edit);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/editwidget.h b/src/editor/editwidget.h
new file mode 100644
index 0000000..769b91a
--- /dev/null
+++ b/src/editor/editwidget.h
@@ -0,0 +1,173 @@
+/** \file
+ * \brief Header: editor widget WEdit
+ */
+
+#ifndef MC__EDIT_WIDGET_H
+#define MC__EDIT_WIDGET_H
+
+#include <limits.h> /* MB_LEN_MAX */
+
+#include "lib/search.h" /* mc_search_t */
+#include "lib/widget.h" /* Widget */
+
+#include "edit-impl.h"
+#include "editbuffer.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define N_LINE_CACHES 32
+
+/*** enums ***************************************************************************************/
+
+/**
+ enum for store the search conditions check results.
+ (if search condition have BOL(^) or EOL ($) regexp checial characters).
+*/
+typedef enum
+{
+ AT_START_LINE = (1 << 0),
+ AT_END_LINE = (1 << 1)
+} edit_search_line_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct edit_book_mark_t edit_book_mark_t;
+struct edit_book_mark_t
+{
+ long line; /* line number */
+ int c; /* color */
+ edit_book_mark_t *next;
+ edit_book_mark_t *prev;
+};
+
+typedef struct edit_syntax_rule_t edit_syntax_rule_t;
+struct edit_syntax_rule_t
+{
+ unsigned short keyword;
+ off_t end;
+ unsigned char context;
+ unsigned char _context;
+ unsigned char border;
+};
+
+/*
+ * State of WEdit window
+ * MCEDIT_DRAG_NONE - window is in normal mode
+ * MCEDIT_DRAG_MOVE - window is being moved
+ * MCEDIT_DRAG_RESIZE - window is being resized
+ */
+typedef enum
+{
+ MCEDIT_DRAG_NONE = 0,
+ MCEDIT_DRAG_MOVE,
+ MCEDIT_DRAG_RESIZE
+} mcedit_drag_state_t;
+
+struct WEdit
+{
+ Widget widget;
+ mcedit_drag_state_t drag_state;
+ int drag_state_start; /* save cursor position before window moving */
+
+ /* save location before move/resize or toggle to fullscreen */
+ WRect loc_prev;
+
+ vfs_path_t *filename_vpath; /* Name of the file */
+ vfs_path_t *dir_vpath; /* NULL if filename is absolute */
+
+ /* dynamic buffers and cursor position for editor: */
+ edit_buffer_t buffer;
+
+#ifdef HAVE_CHARSET
+ /* multibyte support */
+ gboolean utf8; /* It's multibyte file codeset */
+ GIConv converter;
+ char charbuf[MB_LEN_MAX + 1];
+ int charpoint;
+#endif
+
+ /* search handler */
+ mc_search_t *search;
+ int replace_mode;
+ /* is search conditions should be started from BOL(^) or ended with EOL($) */
+ edit_search_line_t search_line_type;
+
+ char *last_search_string; /* String that have been searched */
+ off_t search_start; /* First character to start searching from */
+ unsigned long found_len; /* Length of found string or 0 if none was found */
+ off_t found_start; /* the found word from a search - start position */
+
+ /* display information */
+ long start_display; /* First char displayed */
+ long start_col; /* First displayed column, negative */
+ long max_column; /* The maximum cursor position ever reached used to calc hori scroll bar */
+ long curs_row; /* row position of cursor on the screen */
+ long curs_col; /* column position on screen */
+ long over_col; /* pos after '\n' */
+ int force; /* how much of the screen do we redraw? */
+ unsigned int overwrite:1; /* Overwrite on type mode (as opposed to insert) */
+ unsigned int modified:1; /* File has been modified and needs saving */
+ unsigned int loading_done:1; /* File has been loaded into the editor */
+ unsigned int locked:1; /* We hold lock on current file */
+ unsigned int delete_file:1; /* New file, needs to be deleted unless modified */
+ unsigned int highlight:1; /* There is a selected block */
+ unsigned int column_highlight:1;
+ unsigned int fullscreen:1; /* Is window fullscreen or not */
+ long prev_col; /* recent column position of the cursor - used when moving
+ up or down past lines that are shorter than the current line */
+ long start_line; /* line number of the top of the page */
+
+ /* file info */
+ off_t mark1; /* position of highlight start */
+ off_t mark2; /* position of highlight end */
+ off_t end_mark_curs; /* position of cursor after end of highlighting */
+ long column1; /* position of column highlight start */
+ long column2; /* position of column highlight end */
+ off_t bracket; /* position of a matching bracket */
+ off_t last_bracket; /* previous position of a matching bracket */
+
+ /* cache speedup for line lookups */
+ gboolean caches_valid;
+ long line_numbers[N_LINE_CACHES];
+ off_t line_offsets[N_LINE_CACHES];
+
+ edit_book_mark_t *book_mark;
+ GArray *serialized_bookmarks;
+
+ /* undo stack and pointers */
+ unsigned long undo_stack_pointer;
+ long *undo_stack;
+ unsigned long undo_stack_size;
+ unsigned long undo_stack_size_mask;
+ unsigned long undo_stack_bottom;
+ unsigned int undo_stack_disable:1; /* If not 0, don't save events in the undo stack */
+
+ unsigned long redo_stack_pointer;
+ long *redo_stack;
+ unsigned long redo_stack_size;
+ unsigned long redo_stack_size_mask;
+ unsigned long redo_stack_bottom;
+ unsigned int redo_stack_reset:1; /* If 1, need clear redo stack */
+
+ struct stat stat1; /* Result of mc_fstat() on the file */
+ unsigned int skip_detach_prompt:1; /* Do not prompt whether to detach a file anymore */
+
+ /* syntax highlighting */
+ GSList *syntax_marker;
+ GPtrArray *rules;
+ off_t last_get_rule;
+ edit_syntax_rule_t rule;
+ char *syntax_type; /* description of syntax highlighting type being used */
+ GTree *defines; /* List of defines */
+ gboolean is_case_insensitive; /* selects language case sensitivity */
+
+ /* line break */
+ LineBreaks lb;
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__EDIT_WIDGET_H */
diff --git a/src/editor/etags.c b/src/editor/etags.c
new file mode 100644
index 0000000..7b570d6
--- /dev/null
+++ b/src/editor/etags.c
@@ -0,0 +1,468 @@
+/*
+ Editor C-code navigation via tags.
+ make TAGS file via command:
+ $ find . -type f -name "*.[ch]" | etags -l c --declarations -
+
+ or, if etags utility not installed:
+ $ find . -type f -name "*.[ch]" | ctags --c-kinds=+p --fields=+iaS --extra=+q -e -L-
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+ Slava Zanko <slavazanko@gmail.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2010-2022
+
+ 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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/fileloc.h" /* TAGS_NAME */
+#include "lib/tty/tty.h" /* LINES, COLS */
+#include "lib/strutil.h"
+#include "lib/util.h"
+
+#include "editwidget.h"
+
+#include "etags.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static int def_max_width;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+etags_hash_free (gpointer data)
+{
+ etags_hash_t *hash = (etags_hash_t *) data;
+
+ g_free (hash->filename);
+ g_free (hash->fullpath);
+ g_free (hash->short_define);
+ g_free (hash);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+parse_define (const char *buf, char **long_name, char **short_name, long *line)
+{
+ /* *INDENT-OFF* */
+ enum
+ {
+ in_longname,
+ in_shortname,
+ in_shortname_first_char,
+ in_line,
+ finish
+ } def_state = in_longname;
+ /* *INDENT-ON* */
+
+ GString *longdef = NULL;
+ GString *shortdef = NULL;
+ GString *linedef = NULL;
+
+ char c = *buf;
+
+ while (!(c == '\0' || c == '\n'))
+ {
+ switch (def_state)
+ {
+ case in_longname:
+ if (c == 0x01)
+ def_state = in_line;
+ else if (c == 0x7F)
+ def_state = in_shortname;
+ else
+ {
+ if (longdef == NULL)
+ longdef = g_string_sized_new (32);
+
+ g_string_append_c (longdef, c);
+ }
+ break;
+
+ case in_shortname_first_char:
+ if (isdigit (c))
+ {
+ if (shortdef == NULL)
+ shortdef = g_string_sized_new (32);
+ else
+ g_string_set_size (shortdef, 0);
+
+ buf--;
+ def_state = in_line;
+ }
+ else if (c == 0x01)
+ def_state = in_line;
+ else
+ {
+ if (shortdef == NULL)
+ shortdef = g_string_sized_new (32);
+
+ g_string_append_c (shortdef, c);
+ def_state = in_shortname;
+ }
+ break;
+
+ case in_shortname:
+ if (c == 0x01)
+ def_state = in_line;
+ else if (c == '\n')
+ def_state = finish;
+ else
+ {
+ if (shortdef == NULL)
+ shortdef = g_string_sized_new (32);
+
+ g_string_append_c (shortdef, c);
+ }
+ break;
+
+ case in_line:
+ if (c == ',' || c == '\n')
+ def_state = finish;
+ else if (isdigit (c))
+ {
+ if (linedef == NULL)
+ linedef = g_string_sized_new (32);
+
+ g_string_append_c (linedef, c);
+ }
+ break;
+
+ case finish:
+ *long_name = longdef == NULL ? NULL : g_string_free (longdef, FALSE);
+ *short_name = shortdef == NULL ? NULL : g_string_free (shortdef, FALSE);
+
+ if (linedef == NULL)
+ *line = 0;
+ else
+ {
+ *line = atol (linedef->str);
+ g_string_free (linedef, TRUE);
+ }
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ buf++;
+ c = *buf;
+ }
+
+ *long_name = NULL;
+ *short_name = NULL;
+ *line = 0;
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GPtrArray *
+etags_set_definition_hash (const char *tagfile, const char *start_path, const char *match_func)
+{
+ /* *INDENT-OFF* */
+ enum
+ {
+ start,
+ in_filename,
+ in_define
+ } state = start;
+ /* *INDENT-ON* */
+
+ FILE *f;
+ char buf[BUF_LARGE];
+ char *filename = NULL;
+ GPtrArray *ret = NULL;
+
+ if (match_func == NULL || tagfile == NULL)
+ return NULL;
+
+ /* open file with positions */
+ f = fopen (tagfile, "r");
+ if (f == NULL)
+ return NULL;
+
+ while (fgets (buf, sizeof (buf), f) != NULL)
+ switch (state)
+ {
+ case start:
+ if (buf[0] == 0x0C)
+ state = in_filename;
+ break;
+
+ case in_filename:
+ {
+ size_t pos;
+
+ pos = strcspn (buf, ",");
+ g_free (filename);
+ filename = g_strndup (buf, pos);
+ state = in_define;
+ break;
+ }
+
+ case in_define:
+ if (buf[0] == 0x0C)
+ state = in_filename;
+ else
+ {
+ char *chekedstr;
+
+ /* check if the filename matches the define pos */
+ chekedstr = strstr (buf, match_func);
+ if (chekedstr != NULL)
+ {
+ char *longname = NULL;
+ char *shortname = NULL;
+ etags_hash_t *def_hash;
+
+ def_hash = g_new (etags_hash_t, 1);
+
+ def_hash->fullpath = mc_build_filename (start_path, filename, (char *) NULL);
+ def_hash->filename = g_strdup (filename);
+
+ def_hash->line = 0;
+
+ parse_define (chekedstr, &longname, &shortname, &def_hash->line);
+
+ if (shortname != NULL && *shortname != '\0')
+ {
+ def_hash->short_define = shortname;
+ g_free (longname);
+ }
+ else
+ {
+ def_hash->short_define = longname;
+ g_free (shortname);
+ }
+
+ if (ret == NULL)
+ ret = g_ptr_array_new_with_free_func (etags_hash_free);
+
+ g_ptr_array_add (ret, def_hash);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ g_free (filename);
+ fclose (f);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+editcmd_dialog_select_definition_add (gpointer data, gpointer user_data)
+{
+ etags_hash_t *def_hash = (etags_hash_t *) data;
+ WListbox *def_list = (WListbox *) user_data;
+ char *label_def;
+ int def_width;
+
+ label_def =
+ g_strdup_printf ("%s -> %s:%ld", def_hash->short_define, def_hash->filename,
+ def_hash->line);
+ listbox_add_item (def_list, LISTBOX_APPEND_AT_END, 0, label_def, def_hash, FALSE);
+ def_width = str_term_width1 (label_def);
+ g_free (label_def);
+ def_max_width = MAX (def_max_width, def_width);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* let the user select where function definition */
+
+static void
+editcmd_dialog_select_definition_show (WEdit * edit, char *match_expr, GPtrArray * def_hash)
+{
+ const WRect *w = &CONST_WIDGET (edit)->rect;
+ int start_x, start_y, offset;
+ char *curr = NULL;
+ WDialog *def_dlg;
+ WListbox *def_list;
+ int def_dlg_h; /* dialog height */
+ int def_dlg_w; /* dialog width */
+
+ /* calculate the dialog metrics */
+ def_dlg_h = def_hash->len + 2;
+ def_dlg_w = COLS - 2; /* will be clarified later */
+ start_x = w->x + edit->curs_col + edit->start_col + EDIT_TEXT_HORIZONTAL_OFFSET +
+ (edit->fullscreen ? 0 : 1) + edit_options.line_state_width;
+ start_y = w->y + edit->curs_row + EDIT_TEXT_VERTICAL_OFFSET + (edit->fullscreen ? 0 : 1) + 1;
+
+ if (start_x < 0)
+ start_x = 0;
+ if (start_x < w->x + 1)
+ start_x = w->x + 1 + edit_options.line_state_width;
+
+ if (def_dlg_h > LINES - 2)
+ def_dlg_h = LINES - 2;
+
+ offset = start_y + def_dlg_h - LINES;
+ if (offset > 0)
+ start_y -= (offset + 1);
+
+ def_dlg = dlg_create (TRUE, start_y, start_x, def_dlg_h, def_dlg_w, WPOS_KEEP_DEFAULT, TRUE,
+ dialog_colors, NULL, NULL, "[Definitions]", match_expr);
+ def_list = listbox_new (1, 1, def_dlg_h - 2, def_dlg_w - 2, FALSE, NULL);
+ group_add_widget_autopos (GROUP (def_dlg), def_list, WPOS_KEEP_ALL, NULL);
+
+ /* fill the listbox with the completions and get the maximum width */
+ def_max_width = 0;
+ g_ptr_array_foreach (def_hash, editcmd_dialog_select_definition_add, def_list);
+
+ /* adjust dialog width */
+ def_dlg_w = def_max_width + 4;
+ offset = start_x + def_dlg_w - COLS;
+ if (offset > 0)
+ start_x -= offset;
+
+ widget_set_size (WIDGET (def_dlg), start_y, start_x, def_dlg_h, def_dlg_w);
+
+ /* pop up the dialog and apply the chosen completion */
+ if (dlg_run (def_dlg) == B_ENTER)
+ {
+ etags_hash_t *curr_def = NULL;
+ gboolean do_moveto = FALSE;
+
+ listbox_get_current (def_list, &curr, (void **) &curr_def);
+
+ if (!edit->modified)
+ do_moveto = TRUE;
+ else if (!edit_query_dialog2
+ (_("Warning"),
+ _("Current text was modified without a file save.\n"
+ "Continue discards these changes."), _("C&ontinue"), _("&Cancel")))
+ {
+ edit->force |= REDRAW_COMPLETELY;
+ do_moveto = TRUE;
+ }
+
+ if (curr != NULL && do_moveto && edit_stack_iterator + 1 < MAX_HISTORY_MOVETO)
+ {
+ vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath, TRUE);
+
+ /* Is file path absolute? Prepend with dir_vpath if necessary */
+ if (edit->filename_vpath != NULL && edit->filename_vpath->relative
+ && edit->dir_vpath != NULL)
+ edit_history_moveto[edit_stack_iterator].filename_vpath =
+ vfs_path_append_vpath_new (edit->dir_vpath, edit->filename_vpath, NULL);
+ else
+ edit_history_moveto[edit_stack_iterator].filename_vpath =
+ vfs_path_clone (edit->filename_vpath);
+
+ edit_history_moveto[edit_stack_iterator].line = edit->start_line + edit->curs_row + 1;
+ edit_stack_iterator++;
+ vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath, TRUE);
+ edit_history_moveto[edit_stack_iterator].filename_vpath =
+ vfs_path_from_str ((char *) curr_def->fullpath);
+ edit_history_moveto[edit_stack_iterator].line = curr_def->line;
+ edit_reload_line (edit, edit_history_moveto[edit_stack_iterator].filename_vpath,
+ edit_history_moveto[edit_stack_iterator].line);
+ }
+ }
+
+ /* destroy dialog before return */
+ widget_destroy (WIDGET (def_dlg));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_get_match_keyword_cmd (WEdit * edit)
+{
+ gsize word_len = 0;
+ gsize i;
+ off_t word_start = 0;
+ GString *match_expr;
+ char *path = NULL;
+ char *ptr = NULL;
+ char *tagfile = NULL;
+ GPtrArray *def_hash = NULL;
+
+ /* search start of word to be completed */
+ if (!edit_buffer_find_word_start (&edit->buffer, &word_start, &word_len))
+ return;
+
+ /* prepare match expression */
+ match_expr = g_string_sized_new (word_len);
+ for (i = 0; i < word_len; i++)
+ g_string_append_c (match_expr, edit_buffer_get_byte (&edit->buffer, word_start + i));
+
+ ptr = g_get_current_dir ();
+ path = g_strconcat (ptr, PATH_SEP_STR, (char *) NULL);
+ g_free (ptr);
+
+ /* Recursive search file 'TAGS' in parent dirs */
+ do
+ {
+ ptr = g_path_get_dirname (path);
+ g_free (path);
+ path = ptr;
+ g_free (tagfile);
+ tagfile = mc_build_filename (path, TAGS_NAME, (char *) NULL);
+ if (tagfile != NULL && exist_file (tagfile))
+ break;
+ }
+ while (strcmp (path, PATH_SEP_STR) != 0);
+
+ if (tagfile != NULL)
+ {
+ def_hash = etags_set_definition_hash (tagfile, path, match_expr->str);
+ g_free (tagfile);
+ }
+ g_free (path);
+
+ if (def_hash != NULL)
+ {
+ editcmd_dialog_select_definition_show (edit, match_expr->str, def_hash);
+
+ g_ptr_array_free (def_hash, TRUE);
+ }
+
+ g_string_free (match_expr, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/etags.h b/src/editor/etags.h
new file mode 100644
index 0000000..23813e9
--- /dev/null
+++ b/src/editor/etags.h
@@ -0,0 +1,26 @@
+#ifndef MC__EDIT_ETAGS_H
+#define MC__EDIT_ETAGS_H 1
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct etags_hash_struct
+{
+ char *filename;
+ char *fullpath;
+ char *short_define;
+ long line;
+} etags_hash_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void edit_get_match_keyword_cmd (WEdit * edit);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__EDIT_ETAGS_H */
diff --git a/src/editor/format.c b/src/editor/format.c
new file mode 100644
index 0000000..3193067
--- /dev/null
+++ b/src/editor/format.c
@@ -0,0 +1,538 @@
+/*
+ Dynamic paragraph formatting.
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Copyright (C) 1996 Paul Sheer
+
+ Written by:
+ Paul Sheer, 1996
+ Andrew Borodin <aborodin@vmail.ru>, 2013, 2014
+
+ 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
+ * \brief Source: Dynamic paragraph formatting
+ * \author Paul Sheer
+ * \date 1996
+ * \author Andrew Borodin
+ * \date 2013, 2014
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/util.h" /* whitespace() */
+
+#include "edit-impl.h"
+#include "editwidget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define FONT_MEAN_WIDTH 1
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+line_start (const edit_buffer_t * buf, long line)
+{
+ off_t p;
+ long l;
+
+ l = buf->curs_line;
+ p = buf->curs1;
+
+ if (line < l)
+ p = edit_buffer_get_backward_offset (buf, p, l - line);
+ else if (line > l)
+ p = edit_buffer_get_forward_offset (buf, p, line - l, 0);
+
+ p = edit_buffer_get_bol (buf, p);
+ while (strchr ("\t ", edit_buffer_get_byte (buf, p)) != NULL)
+ p++;
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+bad_line_start (const edit_buffer_t * buf, off_t p)
+{
+ int c;
+
+ c = edit_buffer_get_byte (buf, p);
+ if (c == '.')
+ {
+ /* `...' is acceptable */
+ return !(edit_buffer_get_byte (buf, p + 1) == '.'
+ && edit_buffer_get_byte (buf, p + 2) == '.');
+ }
+ if (c == '-')
+ {
+ /* `---' is acceptable */
+ return !(edit_buffer_get_byte (buf, p + 1) == '-'
+ && edit_buffer_get_byte (buf, p + 2) == '-');
+ }
+
+ return (edit_options.stop_format_chars != NULL
+ && strchr (edit_options.stop_format_chars, c) != NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Find the start of the current paragraph for the purpose of formatting.
+ * Return position in the file.
+ */
+
+static off_t
+begin_paragraph (WEdit * edit, gboolean force, long *lines)
+{
+ long i;
+
+ for (i = edit->buffer.curs_line - 1; i >= 0; i--)
+ if (edit_line_is_blank (edit, i) ||
+ (force && bad_line_start (&edit->buffer, line_start (&edit->buffer, i))))
+ {
+ i++;
+ break;
+ }
+
+ *lines = edit->buffer.curs_line - i;
+
+ return edit_buffer_get_backward_offset (&edit->buffer,
+ edit_buffer_get_current_bol (&edit->buffer), *lines);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Find the end of the current paragraph for the purpose of formatting.
+ * Return position in the file.
+ */
+
+static off_t
+end_paragraph (WEdit * edit, gboolean force)
+{
+ long i;
+
+ for (i = edit->buffer.curs_line + 1; i <= edit->buffer.lines; i++)
+ if (edit_line_is_blank (edit, i) ||
+ (force && bad_line_start (&edit->buffer, line_start (&edit->buffer, i))))
+ {
+ i--;
+ break;
+ }
+
+ return edit_buffer_get_eol (&edit->buffer,
+ edit_buffer_get_forward_offset (&edit->buffer,
+ edit_buffer_get_current_bol
+ (&edit->buffer),
+ i - edit->buffer.curs_line, 0));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GString *
+get_paragraph (const edit_buffer_t * buf, off_t p, off_t q, gboolean indent)
+{
+ GString *t;
+
+ t = g_string_sized_new (128);
+
+ for (; p < q; p++)
+ {
+ if (indent && edit_buffer_get_byte (buf, p - 1) == '\n')
+ while (strchr ("\t ", edit_buffer_get_byte (buf, p)) != NULL)
+ p++;
+
+ g_string_append_c (t, edit_buffer_get_byte (buf, p));
+ }
+
+ g_string_append_c (t, '\n');
+
+ return t;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+strip_newlines (unsigned char *t, off_t size)
+{
+ unsigned char *p;
+
+ for (p = t; size-- != 0; p++)
+ if (*p == '\n')
+ *p = ' ';
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ This function calculates the number of chars in a line specified to length l in pixels
+ */
+
+static inline off_t
+next_tab_pos (off_t x)
+{
+ x += TAB_SIZE - x % TAB_SIZE;
+ return x;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline off_t
+line_pixel_length (unsigned char *t, off_t b, off_t l, gboolean utf8)
+{
+ off_t xn, x; /* position counters */
+ off_t char_length = 0; /* character length in bytes */
+
+#ifndef HAVE_CHARSET
+ (void) utf8;
+#endif
+
+ for (xn = 0, x = 0; xn <= l; x = xn)
+ {
+ char *tb;
+
+ b += char_length;
+ tb = (char *) t + b;
+ char_length = 1;
+
+ switch (tb[0])
+ {
+ case '\n':
+ return b;
+ case '\t':
+ xn = next_tab_pos (x);
+ break;
+ default:
+#ifdef HAVE_CHARSET
+ if (utf8)
+ {
+ gunichar ch;
+
+ ch = g_utf8_get_char_validated (tb, -1);
+ if (ch != (gunichar) (-2) && ch != (gunichar) (-1))
+ {
+ char *next_ch;
+
+ /* Calculate UTF-8 char length */
+ next_ch = g_utf8_next_char (tb);
+ char_length = next_ch - tb;
+
+ if (g_unichar_iswide (ch))
+ x++;
+ }
+ }
+#endif
+
+ xn = x + 1;
+ break;
+ }
+ }
+
+ return b;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+next_word_start (unsigned char *t, off_t q, off_t size)
+{
+ off_t i;
+ gboolean saw_ws = FALSE;
+
+ for (i = q; i < size; i++)
+ {
+ switch (t[i])
+ {
+ case '\n':
+ return -1;
+ case '\t':
+ case ' ':
+ saw_ws = TRUE;
+ break;
+ default:
+ if (saw_ws)
+ return i;
+ break;
+ }
+ }
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** find the start of a word */
+
+static inline int
+word_start (unsigned char *t, off_t q, off_t size)
+{
+ off_t i;
+
+ if (whitespace (t[q]))
+ return next_word_start (t, q, size);
+
+ for (i = q;; i--)
+ {
+ unsigned char c;
+
+ if (i == 0)
+ return (-1);
+ c = t[i - 1];
+ if (c == '\n')
+ return (-1);
+ if (whitespace (c))
+ return i;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** replaces ' ' with '\n' to properly format a paragraph */
+
+static inline void
+format_this (unsigned char *t, off_t size, long indent, gboolean utf8)
+{
+ off_t q = 0, ww;
+
+ strip_newlines (t, size);
+ ww = edit_options.word_wrap_line_length * FONT_MEAN_WIDTH - indent;
+ if (ww < FONT_MEAN_WIDTH * 2)
+ ww = FONT_MEAN_WIDTH * 2;
+
+ while (TRUE)
+ {
+ off_t p;
+
+ q = line_pixel_length (t, q, ww, utf8);
+ if (q > size)
+ break;
+ if (t[q] == '\n')
+ break;
+ p = word_start (t, q, size);
+ if (p == -1)
+ q = next_word_start (t, q, size); /* Return the end of the word if the beginning
+ of the word is at the beginning of a line
+ (i.e. a very long word) */
+ else
+ q = p;
+ if (q == -1) /* end of paragraph */
+ break;
+ if (q != 0)
+ t[q - 1] = '\n';
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+replace_at (WEdit * edit, off_t q, int c)
+{
+ edit_cursor_move (edit, q - edit->buffer.curs1);
+ edit_delete (edit, TRUE);
+ edit_insert_ahead (edit, c);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static long
+edit_indent_width (const WEdit * edit, off_t p)
+{
+ off_t q = p;
+
+ /* move to the end of the leading whitespace of the line */
+ while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, q)) != NULL
+ && q < edit->buffer.size - 1)
+ q++;
+ /* count the number of columns of indentation */
+ return (long) edit_move_forward3 (edit, p, 0, q);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_insert_indent (WEdit * edit, long indent)
+{
+ if (!edit_options.fill_tabs_with_spaces)
+ while (indent >= TAB_SIZE)
+ {
+ edit_insert (edit, '\t');
+ indent -= TAB_SIZE;
+ }
+
+ while (indent-- > 0)
+ edit_insert (edit, ' ');
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** replaces a block of text */
+
+static inline void
+put_paragraph (WEdit * edit, unsigned char *t, off_t p, long indent, off_t size)
+{
+ off_t cursor;
+ off_t i;
+ int c = '\0';
+
+ cursor = edit->buffer.curs1;
+ if (indent != 0)
+ while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
+ p++;
+ for (i = 0; i < size; i++, p++)
+ {
+ if (i != 0 && indent != 0)
+ {
+ if (t[i - 1] == '\n' && c == '\n')
+ {
+ while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
+ p++;
+ }
+ else if (t[i - 1] == '\n')
+ {
+ off_t curs;
+
+ edit_cursor_move (edit, p - edit->buffer.curs1);
+ curs = edit->buffer.curs1;
+ edit_insert_indent (edit, indent);
+ if (cursor >= curs)
+ cursor += edit->buffer.curs1 - p;
+ p = edit->buffer.curs1;
+ }
+ else if (c == '\n')
+ {
+ edit_cursor_move (edit, p - edit->buffer.curs1);
+ while (strchr ("\t ", edit_buffer_get_byte (&edit->buffer, p)) != NULL)
+ {
+ edit_delete (edit, TRUE);
+ if (cursor > edit->buffer.curs1)
+ cursor--;
+ }
+ p = edit->buffer.curs1;
+ }
+ }
+
+ c = edit_buffer_get_byte (&edit->buffer, p);
+ if (c != t[i])
+ replace_at (edit, p, t[i]);
+ }
+ edit_cursor_move (edit, cursor - edit->buffer.curs1); /* restore cursor position */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline long
+test_indent (const WEdit * edit, off_t p, off_t q)
+{
+ long indent;
+
+ indent = edit_indent_width (edit, p++);
+ if (indent == 0)
+ return 0;
+
+ for (; p < q; p++)
+ if (edit_buffer_get_byte (&edit->buffer, p - 1) == '\n'
+ && indent != edit_indent_width (edit, p))
+ return 0;
+ return indent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+format_paragraph (WEdit * edit, gboolean force)
+{
+ off_t p, q;
+ long lines;
+ off_t size;
+ GString *t;
+ long indent;
+ unsigned char *t2;
+ gboolean utf8 = FALSE;
+
+ if (edit_options.word_wrap_line_length < 2)
+ return;
+ if (edit_line_is_blank (edit, edit->buffer.curs_line))
+ return;
+
+ p = begin_paragraph (edit, force, &lines);
+ q = end_paragraph (edit, force);
+ indent = test_indent (edit, p, q);
+
+ t = get_paragraph (&edit->buffer, p, q, indent != 0);
+ size = t->len - 1;
+
+ if (!force)
+ {
+ off_t i;
+ char *stop_format_chars;
+
+ if (edit_options.stop_format_chars != NULL
+ && strchr (edit_options.stop_format_chars, t->str[0]) != NULL)
+ {
+ g_string_free (t, TRUE);
+ return;
+ }
+
+ if (edit_options.stop_format_chars == NULL || *edit_options.stop_format_chars == '\0')
+ stop_format_chars = g_strdup ("\t");
+ else
+ stop_format_chars = g_strconcat (edit_options.stop_format_chars, "\t", (char *) NULL);
+
+ for (i = 0; i < size - 1; i++)
+ if (t->str[i] == '\n' && strchr (stop_format_chars, t->str[i + 1]) != NULL)
+ {
+ g_free (stop_format_chars);
+ g_string_free (t, TRUE);
+ return;
+ }
+
+ g_free (stop_format_chars);
+ }
+
+ t2 = (unsigned char *) g_string_free (t, FALSE);
+#ifdef HAVE_CHARSET
+ utf8 = edit->utf8;
+#endif
+ format_this (t2, q - p, indent, utf8);
+ put_paragraph (edit, t2, p, indent, size);
+ g_free ((char *) t2);
+
+ /* Scroll left as much as possible to show the formatted paragraph */
+ edit_scroll_left (edit, -edit->start_col);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/spell.c b/src/editor/spell.c
new file mode 100644
index 0000000..aeb0884
--- /dev/null
+++ b/src/editor/spell.c
@@ -0,0 +1,834 @@
+/*
+ Editor spell checker
+
+ Copyright (C) 2012-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2012
+ Andrew Borodin <aborodin@vmail.ru>, 2013, 2021
+
+
+ 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 <stdlib.h>
+#include <string.h>
+#include <gmodule.h>
+#include <aspell.h>
+
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+#include "lib/strutil.h"
+#include "lib/util.h" /* MC_PTR_FREE() */
+#include "lib/tty/tty.h" /* COLS, LINES */
+
+#include "src/setup.h"
+
+#include "editwidget.h"
+
+#include "spell.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define B_SKIP_WORD (B_USER+3)
+#define B_ADD_WORD (B_USER+4)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct aspell_struct
+{
+ AspellConfig *config;
+ AspellSpeller *speller;
+} spell_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static GModule *spell_module = NULL;
+static spell_t *global_speller = NULL;
+
+static AspellConfig *(*mc_new_aspell_config) (void);
+static int (*mc_aspell_config_replace) (AspellConfig * ths, const char *key, const char *value);
+static AspellCanHaveError *(*mc_new_aspell_speller) (AspellConfig * config);
+static unsigned int (*mc_aspell_error_number) (const AspellCanHaveError * ths);
+static const char *(*mc_aspell_speller_error_message) (const AspellSpeller * ths);
+static const AspellError *(*mc_aspell_speller_error) (const AspellSpeller * ths);
+
+static AspellSpeller *(*mc_to_aspell_speller) (AspellCanHaveError * obj);
+static int (*mc_aspell_speller_check) (AspellSpeller * ths, const char *word, int word_size);
+static const AspellWordList *(*mc_aspell_speller_suggest) (AspellSpeller * ths,
+ const char *word, int word_size);
+static AspellStringEnumeration *(*mc_aspell_word_list_elements) (const struct AspellWordList * ths);
+static const char *(*mc_aspell_config_retrieve) (AspellConfig * ths, const char *key);
+static void (*mc_delete_aspell_speller) (AspellSpeller * ths);
+static void (*mc_delete_aspell_config) (AspellConfig * ths);
+static void (*mc_delete_aspell_can_have_error) (AspellCanHaveError * ths);
+static const char *(*mc_aspell_error_message) (const AspellCanHaveError * ths);
+static void (*mc_delete_aspell_string_enumeration) (AspellStringEnumeration * ths);
+static AspellDictInfoEnumeration *(*mc_aspell_dict_info_list_elements)
+ (const AspellDictInfoList * ths);
+static AspellDictInfoList *(*mc_get_aspell_dict_info_list) (AspellConfig * config);
+static const AspellDictInfo *(*mc_aspell_dict_info_enumeration_next)
+ (AspellDictInfoEnumeration * ths);
+static const char *(*mc_aspell_string_enumeration_next) (AspellStringEnumeration * ths);
+static void (*mc_delete_aspell_dict_info_enumeration) (AspellDictInfoEnumeration * ths);
+static unsigned int (*mc_aspell_word_list_size) (const AspellWordList * ths);
+static const AspellError *(*mc_aspell_error) (const AspellCanHaveError * ths);
+static int (*mc_aspell_speller_add_to_personal) (AspellSpeller * ths, const char *word,
+ int word_size);
+static int (*mc_aspell_speller_save_all_word_lists) (AspellSpeller * ths);
+
+static struct
+{
+ const char *code;
+ const char *name;
+} spell_codes_map[] =
+{
+ /* *INDENT-OFF* */
+ {"br", N_("Breton")},
+ {"cs", N_("Czech")},
+ {"cy", N_("Welsh")},
+ {"da", N_("Danish")},
+ {"de", N_("German")},
+ {"el", N_("Greek")},
+ {"en", N_("English")},
+ {"en_GB", N_("British English")},
+ {"en_CA", N_("Canadian English")},
+ {"en_US", N_("American English")},
+ {"eo", N_("Esperanto")},
+ {"es", N_("Spanish")},
+ {"fo", N_("Faroese")},
+ {"fr", N_("French")},
+ {"it", N_("Italian")},
+ {"nl", N_("Dutch")},
+ {"no", N_("Norwegian")},
+ {"pl", N_("Polish")},
+ {"pt", N_("Portuguese")},
+ {"ro", N_("Romanian")},
+ {"ru", N_("Russian")},
+ {"sk", N_("Slovak")},
+ {"sv", N_("Swedish")},
+ {"uk", N_("Ukrainian")},
+ {NULL, NULL}
+ /* *INDENT-ON* */
+};
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Found the language name by language code. For example: en_US -> American English.
+ *
+ * @param code Short name of the language (ru, en, pl, uk, etc...)
+ * @return language name
+ */
+
+static const char *
+spell_decode_lang (const char *code)
+{
+ size_t i;
+
+ for (i = 0; spell_codes_map[i].code != NULL; i++)
+ {
+ if (strcmp (spell_codes_map[i].code, code) == 0)
+ return _(spell_codes_map[i].name);
+ }
+
+ return code;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Checks if aspell library and symbols are available.
+ *
+ * @return FALSE or error
+ */
+
+static gboolean
+spell_available (void)
+{
+ gchar *spell_module_fname;
+ gboolean ret = FALSE;
+
+ if (spell_module != NULL)
+ return TRUE;
+
+ spell_module_fname = g_module_build_path (NULL, "libaspell");
+ spell_module = g_module_open (spell_module_fname, G_MODULE_BIND_LAZY);
+
+ g_free (spell_module_fname);
+
+ if (spell_module == NULL)
+ return FALSE;
+
+ if (!g_module_symbol (spell_module, "new_aspell_config", (void *) &mc_new_aspell_config))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_dict_info_list_elements",
+ (void *) &mc_aspell_dict_info_list_elements))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_dict_info_enumeration_next",
+ (void *) &mc_aspell_dict_info_enumeration_next))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "new_aspell_speller", (void *) &mc_new_aspell_speller))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_error_number", (void *) &mc_aspell_error_number))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_speller_error_message",
+ (void *) &mc_aspell_speller_error_message))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_speller_error", (void *) &mc_aspell_speller_error))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_error", (void *) &mc_aspell_error))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "to_aspell_speller", (void *) &mc_to_aspell_speller))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_speller_check", (void *) &mc_aspell_speller_check))
+ goto error_ret;
+
+ if (!g_module_symbol
+ (spell_module, "aspell_speller_suggest", (void *) &mc_aspell_speller_suggest))
+ goto error_ret;
+
+ if (!g_module_symbol
+ (spell_module, "aspell_word_list_elements", (void *) &mc_aspell_word_list_elements))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_string_enumeration_next",
+ (void *) &mc_aspell_string_enumeration_next))
+ goto error_ret;
+
+ if (!g_module_symbol
+ (spell_module, "aspell_config_replace", (void *) &mc_aspell_config_replace))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_error_message", (void *) &mc_aspell_error_message))
+ goto error_ret;
+
+ if (!g_module_symbol
+ (spell_module, "delete_aspell_speller", (void *) &mc_delete_aspell_speller))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "delete_aspell_config", (void *) &mc_delete_aspell_config))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "delete_aspell_string_enumeration",
+ (void *) &mc_delete_aspell_string_enumeration))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "get_aspell_dict_info_list",
+ (void *) &mc_get_aspell_dict_info_list))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "delete_aspell_can_have_error",
+ (void *) &mc_delete_aspell_can_have_error))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "delete_aspell_dict_info_enumeration",
+ (void *) &mc_delete_aspell_dict_info_enumeration))
+ goto error_ret;
+
+ if (!g_module_symbol
+ (spell_module, "aspell_config_retrieve", (void *) &mc_aspell_config_retrieve))
+ goto error_ret;
+
+ if (!g_module_symbol
+ (spell_module, "aspell_word_list_size", (void *) &mc_aspell_word_list_size))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_speller_add_to_personal",
+ (void *) &mc_aspell_speller_add_to_personal))
+ goto error_ret;
+
+ if (!g_module_symbol (spell_module, "aspell_speller_save_all_word_lists",
+ (void *) &mc_aspell_speller_save_all_word_lists))
+ goto error_ret;
+
+ ret = TRUE;
+
+ error_ret:
+ if (!ret)
+ {
+ g_module_close (spell_module);
+ spell_module = NULL;
+ }
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Initialization of Aspell support.
+ */
+
+void
+aspell_init (void)
+{
+ AspellCanHaveError *error = NULL;
+
+ if (strcmp (spell_language, "NONE") == 0)
+ return;
+
+ if (global_speller != NULL)
+ return;
+
+ global_speller = g_try_malloc (sizeof (spell_t));
+ if (global_speller == NULL)
+ return;
+
+ if (!spell_available ())
+ {
+ MC_PTR_FREE (global_speller);
+ return;
+ }
+
+ global_speller->config = mc_new_aspell_config ();
+ global_speller->speller = NULL;
+
+ if (spell_language != NULL)
+ mc_aspell_config_replace (global_speller->config, "lang", spell_language);
+
+ error = mc_new_aspell_speller (global_speller->config);
+
+ if (mc_aspell_error_number (error) == 0)
+ global_speller->speller = mc_to_aspell_speller (error);
+ else
+ {
+ edit_error_dialog (_("Error"), mc_aspell_error_message (error));
+ mc_delete_aspell_can_have_error (error);
+ aspell_clean ();
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Deinitialization of Aspell support.
+ */
+
+void
+aspell_clean (void)
+{
+ if (global_speller == NULL)
+ return;
+
+ if (global_speller->speller != NULL)
+ mc_delete_aspell_speller (global_speller->speller);
+
+ if (global_speller->config != NULL)
+ mc_delete_aspell_config (global_speller->config);
+
+ MC_PTR_FREE (global_speller);
+
+ g_module_close (spell_module);
+ spell_module = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get array of available languages.
+ *
+ * @param lang_list Array of languages. Must be cleared before use
+ * @return language list length
+ */
+
+unsigned int
+aspell_get_lang_list (GPtrArray * lang_list)
+{
+ AspellDictInfoList *dlist;
+ AspellDictInfoEnumeration *elem;
+ const AspellDictInfo *entry;
+ unsigned int i = 0;
+
+ if (spell_module == NULL)
+ return 0;
+
+ /* the returned pointer should _not_ need to be deleted */
+ dlist = mc_get_aspell_dict_info_list (global_speller->config);
+ elem = mc_aspell_dict_info_list_elements (dlist);
+
+ while ((entry = mc_aspell_dict_info_enumeration_next (elem)) != NULL)
+ if (entry->name != NULL)
+ {
+ g_ptr_array_add (lang_list, g_strdup (entry->name));
+ i++;
+ }
+
+ mc_delete_aspell_dict_info_enumeration (elem);
+
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Clear the array of languages.
+ *
+ * @param array Array of languages
+ */
+
+void
+aspell_array_clean (GPtrArray * array)
+{
+ if (array != NULL)
+ g_ptr_array_free (array, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get the current language name.
+ *
+ * @return language name
+ */
+
+const char *
+aspell_get_lang (void)
+{
+ const char *code;
+
+ code = mc_aspell_config_retrieve (global_speller->config, "lang");
+ return spell_decode_lang (code);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Set the language.
+ *
+ * @param lang Language name
+ * @return FALSE or error
+ */
+
+gboolean
+aspell_set_lang (const char *lang)
+{
+ if (lang != NULL)
+ {
+ AspellCanHaveError *error;
+ const char *spell_codeset;
+
+ g_free (spell_language);
+ spell_language = g_strdup (lang);
+
+#ifdef HAVE_CHARSET
+ if (mc_global.source_codepage > 0)
+ spell_codeset = get_codepage_id (mc_global.source_codepage);
+ else
+#endif
+ spell_codeset = str_detect_termencoding ();
+
+ mc_aspell_config_replace (global_speller->config, "lang", lang);
+ mc_aspell_config_replace (global_speller->config, "encoding", spell_codeset);
+
+ /* the returned pointer should _not_ need to be deleted */
+ if (global_speller->speller != NULL)
+ mc_delete_aspell_speller (global_speller->speller);
+
+ global_speller->speller = NULL;
+
+ error = mc_new_aspell_speller (global_speller->config);
+ if (mc_aspell_error (error) != 0)
+ {
+ mc_delete_aspell_can_have_error (error);
+ return FALSE;
+ }
+
+ global_speller->speller = mc_to_aspell_speller (error);
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check word.
+ *
+ * @param word Word for spell check
+ * @param word_size Word size (in bytes)
+ * @return FALSE if word is not in the dictionary
+ */
+
+gboolean
+aspell_check (const char *word, const int word_size)
+{
+ int res = 0;
+
+ if (word != NULL && global_speller != NULL && global_speller->speller != NULL)
+ res = mc_aspell_speller_check (global_speller->speller, word, word_size);
+
+ return (res == 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Examine dictionaries and suggest possible words that may repalce the incorrect word.
+ *
+ * @param suggest array of words to iterate through
+ * @param word Word for spell check
+ * @param word_size Word size (in bytes)
+ * @return count of suggests for the word
+ */
+
+unsigned int
+aspell_suggest (GPtrArray * suggest, const char *word, const int word_size)
+{
+ unsigned int size = 0;
+
+ if (word != NULL && global_speller != NULL && global_speller->speller != NULL)
+ {
+ const AspellWordList *wordlist;
+
+ wordlist = mc_aspell_speller_suggest (global_speller->speller, word, word_size);
+ if (wordlist != NULL)
+ {
+ AspellStringEnumeration *elements = NULL;
+ unsigned int i;
+
+ elements = mc_aspell_word_list_elements (wordlist);
+ size = mc_aspell_word_list_size (wordlist);
+
+ for (i = 0; i < size; i++)
+ {
+ const char *cur_sugg_word;
+
+ cur_sugg_word = mc_aspell_string_enumeration_next (elements);
+ if (cur_sugg_word != NULL)
+ g_ptr_array_add (suggest, g_strdup (cur_sugg_word));
+ }
+
+ mc_delete_aspell_string_enumeration (elements);
+ }
+ }
+
+ return size;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Add word to personal dictionary.
+ *
+ * @param word Word for spell check
+ * @param word_size Word size (in bytes)
+ * @return FALSE or error
+ */
+gboolean
+aspell_add_to_dict (const char *word, int word_size)
+{
+ mc_aspell_speller_add_to_personal (global_speller->speller, word, word_size);
+
+ if (mc_aspell_speller_error (global_speller->speller) != 0)
+ {
+ edit_error_dialog (_("Error"), mc_aspell_speller_error_message (global_speller->speller));
+ return FALSE;
+ }
+
+ mc_aspell_speller_save_all_word_lists (global_speller->speller);
+
+ if (mc_aspell_speller_error (global_speller->speller) != 0)
+ {
+ edit_error_dialog (_("Error"), mc_aspell_speller_error_message (global_speller->speller));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+edit_suggest_current_word (WEdit * edit)
+{
+ gsize cut_len = 0;
+ gsize word_len = 0;
+ off_t word_start = 0;
+ int retval = B_SKIP_WORD;
+ GString *match_word;
+
+ /* search start of word to spell check */
+ match_word = edit_buffer_get_word_from_pos (&edit->buffer, edit->buffer.curs1, &word_start,
+ &cut_len);
+ word_len = match_word->len;
+
+#ifdef HAVE_CHARSET
+ if (mc_global.source_codepage >= 0 && mc_global.source_codepage != mc_global.display_codepage)
+ {
+ GString *tmp_word;
+
+ tmp_word = str_convert_to_display (match_word->str);
+ g_string_free (match_word, TRUE);
+ match_word = tmp_word;
+ }
+#endif
+ if (match_word != NULL)
+ {
+ if (!aspell_check (match_word->str, (int) word_len))
+ {
+ GPtrArray *suggest;
+ unsigned int res;
+ guint i;
+
+ suggest = g_ptr_array_new_with_free_func (g_free);
+
+ res = aspell_suggest (suggest, match_word->str, (int) word_len);
+ if (res != 0)
+ {
+ char *new_word = NULL;
+
+ edit->found_start = word_start;
+ edit->found_len = word_len;
+ edit->force |= REDRAW_PAGE;
+ edit_scroll_screen_over_cursor (edit);
+ edit_render_keypress (edit);
+
+ retval =
+ spell_dialog_spell_suggest_show (edit, match_word->str, &new_word, suggest);
+ edit_cursor_move (edit, word_len - cut_len);
+
+ if (retval == B_ENTER && new_word != NULL)
+ {
+#ifdef HAVE_CHARSET
+ if (mc_global.source_codepage >= 0 &&
+ (mc_global.source_codepage != mc_global.display_codepage))
+ {
+ GString *tmp_word;
+
+ tmp_word = str_convert_to_input (new_word);
+ MC_PTR_FREE (new_word);
+ if (tmp_word != NULL)
+ new_word = g_string_free (tmp_word, FALSE);
+ }
+#endif
+ for (i = 0; i < word_len; i++)
+ edit_backspace (edit, TRUE);
+ if (new_word != NULL)
+ {
+ for (i = 0; new_word[i] != '\0'; i++)
+ edit_insert (edit, new_word[i]);
+ g_free (new_word);
+ }
+ }
+ else if (retval == B_ADD_WORD)
+ aspell_add_to_dict (match_word->str, (int) word_len);
+ }
+
+ g_ptr_array_free (suggest, TRUE);
+ edit->found_start = 0;
+ edit->found_len = 0;
+ }
+
+ g_string_free (match_word, TRUE);
+ }
+
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_spellcheck_file (WEdit * edit)
+{
+ if (edit->buffer.curs_line > 0)
+ {
+ edit_cursor_move (edit, -edit->buffer.curs1);
+ edit_move_to_prev_col (edit, 0);
+ edit_update_curs_row (edit);
+ }
+
+ do
+ {
+ int c1, c2;
+
+ c2 = edit_buffer_get_current_byte (&edit->buffer);
+
+ do
+ {
+ if (edit->buffer.curs1 >= edit->buffer.size)
+ return;
+
+ c1 = c2;
+ edit_cursor_move (edit, 1);
+ c2 = edit_buffer_get_current_byte (&edit->buffer);
+ }
+ while (is_break_char (c1) || is_break_char (c2));
+ }
+ while (edit_suggest_current_word (edit) != B_CANCEL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_set_spell_lang (void)
+{
+ GPtrArray *lang_list;
+
+ lang_list = g_ptr_array_new_with_free_func (g_free);
+ if (aspell_get_lang_list (lang_list) != 0)
+ {
+ const char *lang;
+
+ lang = spell_dialog_lang_list_show (lang_list);
+ if (lang != NULL)
+ (void) aspell_set_lang (lang);
+ }
+ aspell_array_clean (lang_list);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show suggests for the current word.
+ *
+ * @param edit Editor object
+ * @param word Word for spell check
+ * @param new_word Word to replace the incorrect word
+ * @param suggest Array of suggests for current word
+ * @return code of pressed button
+ */
+
+int
+spell_dialog_spell_suggest_show (WEdit * edit, const char *word, char **new_word,
+ const GPtrArray * suggest)
+{
+
+ int sug_dlg_h = 14; /* dialog height */
+ int sug_dlg_w = 29; /* dialog width */
+ int xpos, ypos;
+ char *lang_label;
+ char *word_label;
+ unsigned int i;
+ int res;
+ char *curr = NULL;
+ WDialog *sug_dlg;
+ WGroup *g;
+ WListbox *sug_list;
+ int max_btn_len = 0;
+ int replace_len;
+ int skip_len;
+ int cancel_len;
+ WButton *add_btn;
+ WButton *replace_btn;
+ WButton *skip_btn;
+ WButton *cancel_button;
+ int word_label_len;
+
+ /* calculate the dialog metrics */
+ xpos = (COLS - sug_dlg_w) / 2;
+ ypos = (LINES - sug_dlg_h) * 2 / 3;
+
+ /* Sometimes menu can hide replaced text. I don't like it */
+ if ((edit->curs_row >= ypos - 1) && (edit->curs_row <= ypos + sug_dlg_h - 1))
+ ypos -= sug_dlg_h;
+
+ add_btn = button_new (5, 28, B_ADD_WORD, NORMAL_BUTTON, _("&Add word"), 0);
+ replace_btn = button_new (7, 28, B_ENTER, NORMAL_BUTTON, _("&Replace"), 0);
+ replace_len = button_get_len (replace_btn);
+ skip_btn = button_new (9, 28, B_SKIP_WORD, NORMAL_BUTTON, _("&Skip"), 0);
+ skip_len = button_get_len (skip_btn);
+ cancel_button = button_new (11, 28, B_CANCEL, NORMAL_BUTTON, _("&Cancel"), 0);
+ cancel_len = button_get_len (cancel_button);
+
+ max_btn_len = MAX (replace_len, skip_len);
+ max_btn_len = MAX (max_btn_len, cancel_len);
+
+ lang_label = g_strdup_printf ("%s: %s", _("Language"), aspell_get_lang ());
+ word_label = g_strdup_printf ("%s: %s", _("Misspelled"), word);
+ word_label_len = str_term_width1 (word_label) + 5;
+
+ sug_dlg_w += max_btn_len;
+ sug_dlg_w = MAX (sug_dlg_w, word_label_len) + 1;
+
+ sug_dlg = dlg_create (TRUE, ypos, xpos, sug_dlg_h, sug_dlg_w, WPOS_KEEP_DEFAULT, TRUE,
+ dialog_colors, NULL, NULL, "[ASpell]", _("Check word"));
+ g = GROUP (sug_dlg);
+
+ group_add_widget (g, label_new (1, 2, lang_label));
+ group_add_widget (g, label_new (3, 2, word_label));
+
+ group_add_widget (g, groupbox_new (4, 2, sug_dlg_h - 5, 25, _("Suggest")));
+
+ sug_list = listbox_new (5, 2, sug_dlg_h - 7, 24, FALSE, NULL);
+ for (i = 0; i < suggest->len; i++)
+ listbox_add_item (sug_list, LISTBOX_APPEND_AT_END, 0, g_ptr_array_index (suggest, i), NULL,
+ FALSE);
+ group_add_widget (g, sug_list);
+
+ group_add_widget (g, add_btn);
+ group_add_widget (g, replace_btn);
+ group_add_widget (g, skip_btn);
+ group_add_widget (g, cancel_button);
+
+ res = dlg_run (sug_dlg);
+ if (res == B_ENTER)
+ {
+ char *tmp = NULL;
+ listbox_get_current (sug_list, &curr, NULL);
+
+ if (curr != NULL)
+ tmp = g_strdup (curr);
+ *new_word = tmp;
+ }
+
+ widget_destroy (WIDGET (sug_dlg));
+ g_free (lang_label);
+ g_free (word_label);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show dialog to select language for spell check.
+ *
+ * @param languages Array of available languages
+ * @return name of chosen language
+ */
+
+const char *
+spell_dialog_lang_list_show (const GPtrArray * languages)
+{
+
+ int lang_dlg_h = 12; /* dialog height */
+ int lang_dlg_w = 30; /* dialog width */
+ const char *selected_lang = NULL;
+ unsigned int i;
+ int res;
+ Listbox *lang_list;
+
+ /* Create listbox */
+ lang_list = listbox_window_centered_new (-1, -1, lang_dlg_h, lang_dlg_w,
+ _("Select language"), "[ASpell]");
+
+ for (i = 0; i < languages->len; i++)
+ LISTBOX_APPEND_TEXT (lang_list, 0, g_ptr_array_index (languages, i), NULL, FALSE);
+
+ res = listbox_run (lang_list);
+ if (res >= 0)
+ selected_lang = g_ptr_array_index (languages, (unsigned int) res);
+
+ return selected_lang;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/editor/spell.h b/src/editor/spell.h
new file mode 100644
index 0000000..005a2f5
--- /dev/null
+++ b/src/editor/spell.h
@@ -0,0 +1,36 @@
+#ifndef MC__EDIT_ASPELL_H
+#define MC__EDIT_ASPELL_H
+
+#include "lib/global.h" /* include <glib.h> */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void aspell_init (void);
+void aspell_clean (void);
+gboolean aspell_check (const char *word, const int word_size);
+unsigned int aspell_suggest (GPtrArray * suggest, const char *word, const int word_size);
+void aspell_array_clean (GPtrArray * array);
+unsigned int aspell_get_lang_list (GPtrArray * lang_list);
+const char *aspell_get_lang (void);
+gboolean aspell_set_lang (const char *lang);
+gboolean aspell_add_to_dict (const char *word, const int word_size);
+
+int edit_suggest_current_word (WEdit * edit);
+void edit_spellcheck_file (WEdit * edit);
+void edit_set_spell_lang (void);
+
+int spell_dialog_spell_suggest_show (WEdit * edit, const char *word, char **new_word,
+ const GPtrArray * suggest);
+const char *spell_dialog_lang_list_show (const GPtrArray * languages);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__EDIT_ASPELL_H */
diff --git a/src/editor/syntax.c b/src/editor/syntax.c
new file mode 100644
index 0000000..f95ad2b
--- /dev/null
+++ b/src/editor/syntax.c
@@ -0,0 +1,1606 @@
+/*
+ Editor syntax highlighting.
+
+ Copyright (C) 1996-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Paul Sheer, 1998
+ Leonard den Ottolander <leonard den ottolander nl>, 2005, 2006
+ Egmont Koblinger <egmont@gmail.com>, 2010
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013, 2014, 2021
+
+ 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
+ * \brief Source: editor syntax highlighting
+ * \author Paul Sheer
+ * \date 1996, 1997
+ * \author Mikhail Pobolovets
+ * \date 2010
+ *
+ * Misspelled words are flushed from the syntax highlighting rules
+ * when they have been around longer than
+ * TRANSIENT_WORD_TIME_OUT seconds. At a cursor rate of 30
+ * chars per second and say 3 chars + a space per word, we can
+ * accumulate 450 words absolute max with a value of 60. This is
+ * below this limit of 1024 words in a context.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/search.h" /* search engine */
+#include "lib/skin.h"
+#include "lib/fileloc.h" /* EDIT_SYNTAX_DIR, EDIT_SYNTAX_FILE */
+#include "lib/strutil.h" /* utf string functions */
+#include "lib/util.h"
+#include "lib/widget.h" /* Listbox, message() */
+
+#include "edit-impl.h"
+#include "editwidget.h"
+
+/*** global variables ****************************************************************************/
+
+gboolean auto_syntax = TRUE;
+
+/*** file scope macro definitions ****************************************************************/
+
+/* bytes */
+#define SYNTAX_MARKER_DENSITY 512
+
+#define RULE_ON_LEFT_BORDER 1
+#define RULE_ON_RIGHT_BORDER 2
+
+#define SYNTAX_TOKEN_STAR '\001'
+#define SYNTAX_TOKEN_PLUS '\002'
+#define SYNTAX_TOKEN_BRACKET '\003'
+#define SYNTAX_TOKEN_BRACE '\004'
+
+#define break_a { result = line; break; }
+#define check_a { if (*a == NULL) { result = line; break; } }
+#define check_not_a { if (*a != NULL) { result = line ;break; } }
+
+#define SYNTAX_KEYWORD(x) ((syntax_keyword_t *) (x))
+#define CONTEXT_RULE(x) ((context_rule_t *) (x))
+
+#define ARGS_LEN 1024
+
+#define MAX_ENTRY_LEN 40
+#define LIST_LINES 14
+#define N_DFLT_ENTRIES 2
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ GString *keyword;
+ char *whole_word_chars_left;
+ char *whole_word_chars_right;
+ gboolean line_start;
+ int color;
+} syntax_keyword_t;
+
+typedef struct
+{
+ GString *left;
+ unsigned char first_left;
+ GString *right;
+ unsigned char first_right;
+ gboolean line_start_left;
+ gboolean line_start_right;
+ gboolean between_delimiters;
+ char *whole_word_chars_left;
+ char *whole_word_chars_right;
+ char *keyword_first_chars;
+ gboolean spelling;
+ /* first word is word[1] */
+ GPtrArray *keyword;
+} context_rule_t;
+
+typedef struct
+{
+ off_t offset;
+ edit_syntax_rule_t rule;
+} syntax_marker_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static char *error_file_name = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+syntax_keyword_free (gpointer keyword)
+{
+ syntax_keyword_t *k = SYNTAX_KEYWORD (keyword);
+
+ g_string_free (k->keyword, TRUE);
+ g_free (k->whole_word_chars_left);
+ g_free (k->whole_word_chars_right);
+ g_free (k);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+context_rule_free (gpointer rule)
+{
+ context_rule_t *r = CONTEXT_RULE (rule);
+
+ g_string_free (r->left, TRUE);
+ g_string_free (r->right, TRUE);
+ g_free (r->whole_word_chars_left);
+ g_free (r->whole_word_chars_right);
+ g_free (r->keyword_first_chars);
+
+ if (r->keyword != NULL)
+ g_ptr_array_free (r->keyword, TRUE);
+
+ g_free (r);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gint
+mc_defines_destroy (gpointer key, gpointer value, gpointer data)
+{
+ (void) data;
+
+ g_free (key);
+ g_strfreev ((char **) value);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Completely destroys the defines tree */
+
+static void
+destroy_defines (GTree ** defines)
+{
+ g_tree_foreach (*defines, mc_defines_destroy, NULL);
+ g_tree_destroy (*defines);
+ *defines = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Wrapper for case insensitive mode */
+inline static int
+xx_tolower (const WEdit * edit, int c)
+{
+ return edit->is_case_insensitive ? tolower (c) : c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+subst_defines (GTree * defines, char **argv, char **argv_end)
+{
+ for (; *argv != NULL && argv < argv_end; argv++)
+ {
+ char **t;
+
+ t = g_tree_lookup (defines, *argv);
+ if (t != NULL)
+ {
+ int argc, count;
+ char **p;
+
+ /* Count argv array members */
+ argc = g_strv_length (argv + 1);
+
+ /* Count members of definition array */
+ count = g_strv_length (t);
+
+ p = argv + count + argc;
+ /* Buffer overflow or infinitive loop in define */
+ if (p >= argv_end)
+ break;
+
+ /* Move rest of argv after definition members */
+ while (argc >= 0)
+ *p-- = argv[argc-- + 1];
+
+ /* Copy definition members to argv */
+ for (p = argv; *t != NULL; *p++ = *t++)
+ ;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+compare_word_to_right (const WEdit * edit, off_t i, const GString * text,
+ const char *whole_left, const char *whole_right, gboolean line_start)
+{
+ const unsigned char *p, *q;
+ int c, d, j;
+
+ c = xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i - 1));
+ if ((line_start && c != '\n') || (whole_left != NULL && strchr (whole_left, c) != NULL))
+ return -1;
+
+ for (p = (const unsigned char *) text->str, q = p + text->len; p < q; p++, i++)
+ {
+ switch (*p)
+ {
+ case SYNTAX_TOKEN_STAR:
+ if (++p > q)
+ return -1;
+ while (TRUE)
+ {
+ c = xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i));
+ if (*p == '\0' && whole_right != NULL && strchr (whole_right, c) == NULL)
+ break;
+ if (c == *p)
+ break;
+ if (c == '\n')
+ return -1;
+ i++;
+ }
+ break;
+ case SYNTAX_TOKEN_PLUS:
+ if (++p > q)
+ return -1;
+ j = 0;
+ while (TRUE)
+ {
+ c = xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i));
+ if (c == *p)
+ {
+ j = i;
+ if (p[0] == text->str[0] && p[1] == '\0') /* handle eg '+' and @+@ keywords properly */
+ break;
+ }
+ if (j != 0 && strchr ((const char *) p + 1, c) != NULL) /* c exists further down, so it will get matched later */
+ break;
+ if (whiteness (c) || (whole_right != NULL && strchr (whole_right, c) == NULL))
+ {
+ if (*p == '\0')
+ {
+ i--;
+ break;
+ }
+ if (j == 0)
+ return -1;
+ i = j;
+ break;
+ }
+ i++;
+ }
+ break;
+ case SYNTAX_TOKEN_BRACKET:
+ if (++p > q)
+ return -1;
+ c = -1;
+ while (TRUE)
+ {
+ d = c;
+ c = xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i));
+ for (j = 0; p[j] != SYNTAX_TOKEN_BRACKET && p[j] != '\0'; j++)
+ if (c == p[j])
+ goto found_char2;
+ break;
+ found_char2:
+ i++;
+ }
+ i--;
+ while (*p != SYNTAX_TOKEN_BRACKET && p <= q)
+ p++;
+ if (p > q)
+ return -1;
+ if (p[1] == d)
+ i--;
+ break;
+ case SYNTAX_TOKEN_BRACE:
+ if (++p > q)
+ return -1;
+ c = xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i));
+ for (; *p != SYNTAX_TOKEN_BRACE && *p != '\0'; p++)
+ if (c == *p)
+ goto found_char3;
+ return -1;
+ found_char3:
+ while (*p != SYNTAX_TOKEN_BRACE && p < q)
+ p++;
+ break;
+ default:
+ if (*p != xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i)))
+ return -1;
+ }
+ }
+ return (whole_right != NULL &&
+ strchr (whole_right,
+ xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i))) != NULL) ? -1 : i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+xx_strchr (const WEdit * edit, const unsigned char *s, int char_byte)
+{
+ while (*s >= '\005' && xx_tolower (edit, *s) != char_byte)
+ s++;
+
+ return (const char *) s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+apply_rules_going_right (WEdit * edit, off_t i)
+{
+ context_rule_t *r;
+ int c;
+ gboolean contextchanged = FALSE;
+ gboolean found_left = FALSE, found_right = FALSE;
+ gboolean keyword_foundleft = FALSE, keyword_foundright = FALSE;
+ gboolean is_end;
+ off_t end = 0;
+ edit_syntax_rule_t _rule = edit->rule;
+
+ c = xx_tolower (edit, edit_buffer_get_byte (&edit->buffer, i));
+ if (c == 0)
+ return;
+
+ is_end = (edit->rule.end == i);
+
+ /* check to turn off a keyword */
+ if (_rule.keyword != 0)
+ {
+ if (edit_buffer_get_byte (&edit->buffer, i - 1) == '\n')
+ _rule.keyword = 0;
+ if (is_end)
+ {
+ _rule.keyword = 0;
+ keyword_foundleft = TRUE;
+ }
+ }
+
+ /* check to turn off a context */
+ if (_rule.context != 0 && _rule.keyword == 0)
+ {
+ off_t e;
+
+ r = CONTEXT_RULE (g_ptr_array_index (edit->rules, _rule.context));
+ if (r->first_right == c && (edit->rule.border & RULE_ON_RIGHT_BORDER) == 0
+ && r->right->len != 0 && (e =
+ compare_word_to_right (edit, i, r->right,
+ r->whole_word_chars_left,
+ r->whole_word_chars_right,
+ r->line_start_right)) > 0)
+ {
+ _rule.end = e;
+ found_right = TRUE;
+ _rule.border = RULE_ON_RIGHT_BORDER;
+ if (r->between_delimiters)
+ _rule.context = 0;
+ }
+ else if (is_end && (edit->rule.border & RULE_ON_RIGHT_BORDER) != 0)
+ {
+ /* always turn off a context at 4 */
+ found_left = TRUE;
+ _rule.border = 0;
+ if (!keyword_foundleft)
+ _rule.context = 0;
+ }
+ else if (is_end && (edit->rule.border & RULE_ON_LEFT_BORDER) != 0)
+ {
+ /* never turn off a context at 2 */
+ found_left = TRUE;
+ _rule.border = 0;
+ }
+ }
+
+ /* check to turn on a keyword */
+ if (_rule.keyword == 0)
+ {
+ const char *p;
+
+ r = CONTEXT_RULE (g_ptr_array_index (edit->rules, _rule.context));
+ p = r->keyword_first_chars;
+
+ if (p != NULL)
+ while (*(p = xx_strchr (edit, (const unsigned char *) p + 1, c)) != '\0')
+ {
+ syntax_keyword_t *k;
+ int count;
+ off_t e = -1;
+
+ count = p - r->keyword_first_chars;
+ k = SYNTAX_KEYWORD (g_ptr_array_index (r->keyword, count));
+ if (k->keyword != 0)
+ e = compare_word_to_right (edit, i, k->keyword, k->whole_word_chars_left,
+ k->whole_word_chars_right, k->line_start);
+ if (e > 0)
+ {
+ /* when both context and keyword terminate with a newline,
+ the context overflows to the next line and colorizes it incorrectly */
+ if (e > i + 1 && _rule._context != 0
+ && k->keyword->str[k->keyword->len - 1] == '\n')
+ {
+ r = CONTEXT_RULE (g_ptr_array_index (edit->rules, _rule._context));
+ if (r->right != NULL && r->right->len != 0
+ && r->right->str[r->right->len - 1] == '\n')
+ e--;
+ }
+
+ end = e;
+ _rule.end = e;
+ _rule.keyword = count;
+ keyword_foundright = TRUE;
+ break;
+ }
+ }
+ }
+
+ /* check to turn on a context */
+ if (_rule.context == 0)
+ {
+ if (!found_left && is_end)
+ {
+ if ((edit->rule.border & RULE_ON_RIGHT_BORDER) != 0)
+ {
+ _rule.border = 0;
+ _rule.context = 0;
+ contextchanged = TRUE;
+ _rule.keyword = 0;
+
+ }
+ else if ((edit->rule.border & RULE_ON_LEFT_BORDER) != 0)
+ {
+ r = CONTEXT_RULE (g_ptr_array_index (edit->rules, _rule._context));
+ _rule.border = 0;
+ if (r->between_delimiters)
+ {
+ _rule.context = _rule._context;
+ contextchanged = TRUE;
+ _rule.keyword = 0;
+
+ if (r->first_right == c)
+ {
+ off_t e = -1;
+
+ if (r->right->len != 0)
+ e = compare_word_to_right (edit, i, r->right, r->whole_word_chars_left,
+ r->whole_word_chars_right,
+ r->line_start_right);
+ if (e >= end)
+ {
+ _rule.end = e;
+ found_right = TRUE;
+ _rule.border = RULE_ON_RIGHT_BORDER;
+ _rule.context = 0;
+ }
+ }
+ }
+ }
+ }
+
+ if (!found_right)
+ {
+ size_t count;
+
+ for (count = 1; count < edit->rules->len; count++)
+ {
+ r = CONTEXT_RULE (g_ptr_array_index (edit->rules, count));
+ if (r->first_left == c)
+ {
+ off_t e = -1;
+
+ if (r->left->len != 0)
+ e = compare_word_to_right (edit, i, r->left, r->whole_word_chars_left,
+ r->whole_word_chars_right, r->line_start_left);
+ if (e >= end && (_rule.keyword == 0 || keyword_foundright))
+ {
+ _rule.end = e;
+ _rule.border = RULE_ON_LEFT_BORDER;
+ _rule._context = count;
+ if (!r->between_delimiters && _rule.keyword == 0)
+ {
+ _rule.context = count;
+ contextchanged = TRUE;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /* check again to turn on a keyword if the context switched */
+ if (contextchanged && _rule.keyword == 0)
+ {
+ const char *p;
+
+ r = CONTEXT_RULE (g_ptr_array_index (edit->rules, _rule.context));
+ p = r->keyword_first_chars;
+
+ while (*(p = xx_strchr (edit, (const unsigned char *) p + 1, c)) != '\0')
+ {
+ syntax_keyword_t *k;
+ int count;
+ off_t e = -1;
+
+ count = p - r->keyword_first_chars;
+ k = SYNTAX_KEYWORD (g_ptr_array_index (r->keyword, count));
+
+ if (k->keyword->len != 0)
+ e = compare_word_to_right (edit, i, k->keyword, k->whole_word_chars_left,
+ k->whole_word_chars_right, k->line_start);
+ if (e > 0)
+ {
+ _rule.end = e;
+ _rule.keyword = count;
+ break;
+ }
+ }
+ }
+
+ edit->rule = _rule;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edit_get_rule (WEdit * edit, off_t byte_index)
+{
+ off_t i;
+
+ if (byte_index > edit->last_get_rule)
+ {
+ for (i = edit->last_get_rule + 1; i <= byte_index; i++)
+ {
+ off_t d = SYNTAX_MARKER_DENSITY;
+
+ apply_rules_going_right (edit, i);
+
+ if (edit->syntax_marker != NULL)
+ d += ((syntax_marker_t *) edit->syntax_marker->data)->offset;
+
+ if (i > d)
+ {
+ syntax_marker_t *s;
+
+ s = g_new (syntax_marker_t, 1);
+ s->offset = i;
+ s->rule = edit->rule;
+ edit->syntax_marker = g_slist_prepend (edit->syntax_marker, s);
+ }
+ }
+ }
+ else if (byte_index < edit->last_get_rule)
+ {
+ while (TRUE)
+ {
+ syntax_marker_t *s;
+
+ if (edit->syntax_marker == NULL)
+ {
+ memset (&edit->rule, 0, sizeof (edit->rule));
+ for (i = -1; i <= byte_index; i++)
+ apply_rules_going_right (edit, i);
+ break;
+ }
+
+ s = (syntax_marker_t *) edit->syntax_marker->data;
+
+ if (byte_index >= s->offset)
+ {
+ edit->rule = s->rule;
+ for (i = s->offset + 1; i <= byte_index; i++)
+ apply_rules_going_right (edit, i);
+ break;
+ }
+
+ g_free (s);
+ edit->syntax_marker = g_slist_delete_link (edit->syntax_marker, edit->syntax_marker);
+ }
+ }
+ edit->last_get_rule = byte_index;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+translate_rule_to_color (const WEdit * edit, const edit_syntax_rule_t * rule)
+{
+ syntax_keyword_t *k;
+ context_rule_t *r;
+
+ r = CONTEXT_RULE (g_ptr_array_index (edit->rules, rule->context));
+ k = SYNTAX_KEYWORD (g_ptr_array_index (r->keyword, rule->keyword));
+
+ return k->color;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ Returns 0 on error/eof or a count of the number of bytes read
+ including the newline. Result must be free'd.
+ In case of an error, *line will not be modified.
+ */
+
+static size_t
+read_one_line (char **line, FILE * f)
+{
+ GString *p;
+ size_t r = 0;
+
+ /* not reallocate string too often */
+ p = g_string_sized_new (64);
+
+ while (TRUE)
+ {
+ int c;
+
+ c = fgetc (f);
+ if (c == EOF)
+ {
+ if (ferror (f))
+ {
+ if (errno == EINTR)
+ continue;
+ r = 0;
+ }
+ break;
+ }
+ r++;
+
+ /* handle all of \r\n, \r, \n correctly. */
+ if (c == '\n')
+ break;
+ if (c == '\r')
+ {
+ c = fgetc (f);
+ if (c == '\n')
+ r++;
+ else
+ ungetc (c, f);
+ break;
+ }
+
+ g_string_append_c (p, c);
+ }
+ if (r != 0)
+ *line = g_string_free (p, FALSE);
+ else
+ g_string_free (p, TRUE);
+
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+convert (char *s)
+{
+ char *r, *p;
+
+ p = r = s;
+ while (*s)
+ {
+ switch (*s)
+ {
+ case '\\':
+ s++;
+ switch (*s)
+ {
+ case ' ':
+ *p = ' ';
+ s--;
+ break;
+ case 'n':
+ *p = '\n';
+ break;
+ case 'r':
+ *p = '\r';
+ break;
+ case 't':
+ *p = '\t';
+ break;
+ case 's':
+ *p = ' ';
+ break;
+ case '*':
+ *p = '*';
+ break;
+ case '\\':
+ *p = '\\';
+ break;
+ case '[':
+ case ']':
+ *p = SYNTAX_TOKEN_BRACKET;
+ break;
+ case '{':
+ case '}':
+ *p = SYNTAX_TOKEN_BRACE;
+ break;
+ case 0:
+ *p = *s;
+ return r;
+ default:
+ *p = *s;
+ break;
+ }
+ break;
+ case '*':
+ *p = SYNTAX_TOKEN_STAR;
+ break;
+ case '+':
+ *p = SYNTAX_TOKEN_PLUS;
+ break;
+ default:
+ *p = *s;
+ break;
+ }
+ s++;
+ p++;
+ }
+ *p = '\0';
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+get_args (char *l, char **args, int args_size)
+{
+ int argc = 0;
+
+ while (argc < args_size)
+ {
+ char *p = l;
+
+ while (*p != '\0' && whiteness (*p))
+ p++;
+ if (*p == '\0')
+ break;
+ for (l = p + 1; *l != '\0' && !whiteness (*l); l++)
+ ;
+ if (*l != '\0')
+ *l++ = '\0';
+ args[argc++] = convert (p);
+ }
+ args[argc] = (char *) NULL;
+ return argc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+this_try_alloc_color_pair (const char *fg, const char *bg, const char *attrs)
+{
+ char f[80], b[80], a[80], *p;
+
+ if (bg != NULL && *bg == '\0')
+ bg = NULL;
+ if (fg != NULL && *fg == '\0')
+ fg = NULL;
+ if (attrs != NULL && *attrs == '\0')
+ attrs = NULL;
+
+ if ((fg == NULL) && (bg == NULL))
+ return EDITOR_NORMAL_COLOR;
+
+ if (fg != NULL)
+ {
+ g_strlcpy (f, fg, sizeof (f));
+ p = strchr (f, '/');
+ if (p != NULL)
+ *p = '\0';
+ fg = f;
+ }
+ if (bg != NULL)
+ {
+ g_strlcpy (b, bg, sizeof (b));
+ p = strchr (b, '/');
+ if (p != NULL)
+ *p = '\0';
+ bg = b;
+ }
+ if ((fg == NULL) || (bg == NULL))
+ {
+ /* get colors from skin */
+ char *editnormal;
+
+ editnormal = mc_skin_get ("editor", "_default_", "default;default");
+
+ if (fg == NULL)
+ {
+ g_strlcpy (f, editnormal, sizeof (f));
+ p = strchr (f, ';');
+ if (p != NULL)
+ *p = '\0';
+ if (f[0] == '\0')
+ g_strlcpy (f, "default", sizeof (f));
+ fg = f;
+ }
+ if (bg == NULL)
+ {
+ p = strchr (editnormal, ';');
+ if ((p != NULL) && (*(++p) != '\0'))
+ g_strlcpy (b, p, sizeof (b));
+ else
+ g_strlcpy (b, "default", sizeof (b));
+ bg = b;
+ }
+
+ g_free (editnormal);
+ }
+
+ if (attrs != NULL)
+ {
+ g_strlcpy (a, attrs, sizeof (a));
+ p = strchr (a, '/');
+ if (p != NULL)
+ *p = '\0';
+ /* get_args() mangles the + signs, unmangle 'em */
+ p = a;
+ while ((p = strchr (p, SYNTAX_TOKEN_PLUS)) != NULL)
+ *p++ = '+';
+ attrs = a;
+ }
+ return tty_try_alloc_color_pair (fg, bg, attrs);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FILE *
+open_include_file (const char *filename)
+{
+ FILE *f;
+
+ g_free (error_file_name);
+ error_file_name = g_strdup (filename);
+ if (g_path_is_absolute (filename))
+ return fopen (filename, "r");
+
+ g_free (error_file_name);
+ error_file_name =
+ g_build_filename (mc_config_get_data_path (), EDIT_SYNTAX_DIR, filename, (char *) NULL);
+ f = fopen (error_file_name, "r");
+ if (f != NULL)
+ return f;
+
+ g_free (error_file_name);
+ error_file_name =
+ g_build_filename (mc_global.share_data_dir, EDIT_SYNTAX_DIR, filename, (char *) NULL);
+
+ return fopen (error_file_name, "r");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+inline static void
+xx_lowerize_line (WEdit * edit, char *line, size_t len)
+{
+ if (edit->is_case_insensitive)
+ {
+ size_t i;
+
+ for (i = 0; i < len; ++i)
+ line[i] = tolower (line[i]);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns line number on error */
+
+static int
+edit_read_syntax_rules (WEdit * edit, FILE * f, char **args, int args_size)
+{
+ FILE *g = NULL;
+ char *fg, *bg, *attrs;
+ char last_fg[32] = "", last_bg[32] = "", last_attrs[64] = "";
+ char whole_right[512];
+ char whole_left[512];
+ char *l = NULL;
+ int save_line = 0, line = 0;
+ context_rule_t *c = NULL;
+ gboolean no_words = TRUE;
+ int result = 0;
+
+ args[0] = NULL;
+ edit->is_case_insensitive = FALSE;
+
+ strcpy (whole_left, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_01234567890");
+ strcpy (whole_right, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_01234567890");
+
+ edit->rules = g_ptr_array_new_with_free_func (context_rule_free);
+
+ if (edit->defines == NULL)
+ edit->defines = g_tree_new ((GCompareFunc) strcmp);
+
+ while (TRUE)
+ {
+ char **a;
+ size_t len;
+ int argc;
+
+ line++;
+ l = NULL;
+
+ len = read_one_line (&l, f);
+ if (len == 0)
+ {
+ if (g == NULL)
+ break;
+
+ fclose (f);
+ f = g;
+ g = NULL;
+ line = save_line + 1;
+ MC_PTR_FREE (error_file_name);
+ MC_PTR_FREE (l);
+ len = read_one_line (&l, f);
+ if (len == 0)
+ break;
+ }
+
+ xx_lowerize_line (edit, l, len);
+
+ argc = get_args (l, args, args_size);
+ a = args + 1;
+ if (args[0] == NULL)
+ {
+ /* do nothing */
+ }
+ else if (strcmp (args[0], "include") == 0)
+ {
+ if (g != NULL || argc != 2)
+ {
+ result = line;
+ break;
+ }
+ g = f;
+ f = open_include_file (args[1]);
+ if (f == NULL)
+ {
+ MC_PTR_FREE (error_file_name);
+ result = line;
+ break;
+ }
+ save_line = line;
+ line = 0;
+ }
+ else if (strcmp (args[0], "caseinsensitive") == 0)
+ {
+ edit->is_case_insensitive = TRUE;
+ }
+ else if (strcmp (args[0], "wholechars") == 0)
+ {
+ check_a;
+ if (strcmp (*a, "left") == 0)
+ {
+ a++;
+ g_strlcpy (whole_left, *a, sizeof (whole_left));
+ }
+ else if (strcmp (*a, "right") == 0)
+ {
+ a++;
+ g_strlcpy (whole_right, *a, sizeof (whole_right));
+ }
+ else
+ {
+ g_strlcpy (whole_left, *a, sizeof (whole_left));
+ g_strlcpy (whole_right, *a, sizeof (whole_right));
+ }
+ a++;
+ check_not_a;
+ }
+ else if (strcmp (args[0], "context") == 0)
+ {
+ syntax_keyword_t *k;
+
+ check_a;
+ if (edit->rules->len == 0)
+ {
+ /* first context is the default */
+ if (strcmp (*a, "default") != 0)
+ break_a;
+
+ a++;
+ c = g_new0 (context_rule_t, 1);
+ g_ptr_array_add (edit->rules, c);
+ c->left = g_string_new (" ");
+ c->right = g_string_new (" ");
+ }
+ else
+ {
+ /* Start new context. */
+ c = g_new0 (context_rule_t, 1);
+ g_ptr_array_add (edit->rules, c);
+ if (strcmp (*a, "exclusive") == 0)
+ {
+ a++;
+ c->between_delimiters = TRUE;
+ }
+ check_a;
+ if (strcmp (*a, "whole") == 0)
+ {
+ a++;
+ c->whole_word_chars_left = g_strdup (whole_left);
+ c->whole_word_chars_right = g_strdup (whole_right);
+ }
+ else if (strcmp (*a, "wholeleft") == 0)
+ {
+ a++;
+ c->whole_word_chars_left = g_strdup (whole_left);
+ }
+ else if (strcmp (*a, "wholeright") == 0)
+ {
+ a++;
+ c->whole_word_chars_right = g_strdup (whole_right);
+ }
+ check_a;
+ if (strcmp (*a, "linestart") == 0)
+ {
+ a++;
+ c->line_start_left = TRUE;
+ }
+ check_a;
+ c->left = g_string_new (*a++);
+ check_a;
+ if (strcmp (*a, "linestart") == 0)
+ {
+ a++;
+ c->line_start_right = TRUE;
+ }
+ check_a;
+ c->right = g_string_new (*a++);
+ c->first_left = c->left->str[0];
+ c->first_right = c->right->str[0];
+ }
+ c->keyword = g_ptr_array_new_with_free_func (syntax_keyword_free);
+ k = g_new0 (syntax_keyword_t, 1);
+ g_ptr_array_add (c->keyword, k);
+ no_words = FALSE;
+ subst_defines (edit->defines, a, &args[ARGS_LEN]);
+ fg = *a;
+ if (*a != NULL)
+ a++;
+ bg = *a;
+ if (*a != NULL)
+ a++;
+ attrs = *a;
+ if (*a != NULL)
+ a++;
+ g_strlcpy (last_fg, fg != NULL ? fg : "", sizeof (last_fg));
+ g_strlcpy (last_bg, bg != NULL ? bg : "", sizeof (last_bg));
+ g_strlcpy (last_attrs, attrs != NULL ? attrs : "", sizeof (last_attrs));
+ k->color = this_try_alloc_color_pair (fg, bg, attrs);
+ k->keyword = g_string_new (" ");
+ check_not_a;
+ }
+ else if (strcmp (args[0], "spellcheck") == 0)
+ {
+ if (c == NULL)
+ {
+ result = line;
+ break;
+ }
+ c->spelling = TRUE;
+ }
+ else if (strcmp (args[0], "keyword") == 0)
+ {
+ context_rule_t *last_rule;
+ syntax_keyword_t *k;
+
+ if (no_words)
+ break_a;
+ check_a;
+ last_rule = CONTEXT_RULE (g_ptr_array_index (edit->rules, edit->rules->len - 1));
+ k = g_new0 (syntax_keyword_t, 1);
+ g_ptr_array_add (last_rule->keyword, k);
+ if (strcmp (*a, "whole") == 0)
+ {
+ a++;
+ k->whole_word_chars_left = g_strdup (whole_left);
+ k->whole_word_chars_right = g_strdup (whole_right);
+ }
+ else if (strcmp (*a, "wholeleft") == 0)
+ {
+ a++;
+ k->whole_word_chars_left = g_strdup (whole_left);
+ }
+ else if (strcmp (*a, "wholeright") == 0)
+ {
+ a++;
+ k->whole_word_chars_right = g_strdup (whole_right);
+ }
+ check_a;
+ if (strcmp (*a, "linestart") == 0)
+ {
+ a++;
+ k->line_start = TRUE;
+ }
+ check_a;
+ if (strcmp (*a, "whole") == 0)
+ break_a;
+
+ k->keyword = g_string_new (*a++);
+ subst_defines (edit->defines, a, &args[ARGS_LEN]);
+ fg = *a;
+ if (*a != NULL)
+ a++;
+ bg = *a;
+ if (*a != NULL)
+ a++;
+ attrs = *a;
+ if (*a != NULL)
+ a++;
+ if (fg == NULL)
+ fg = last_fg;
+ if (bg == NULL)
+ bg = last_bg;
+ if (attrs == NULL)
+ attrs = last_attrs;
+ k->color = this_try_alloc_color_pair (fg, bg, attrs);
+ check_not_a;
+ }
+ else if (*(args[0]) == '#')
+ {
+ /* do nothing for comment */
+ }
+ else if (strcmp (args[0], "file") == 0)
+ {
+ break;
+ }
+ else if (strcmp (args[0], "define") == 0)
+ {
+ char *key = *a++;
+ char **argv;
+
+ if (argc < 3)
+ break_a;
+ argv = g_tree_lookup (edit->defines, key);
+ if (argv != NULL)
+ mc_defines_destroy (NULL, argv, NULL);
+ else
+ key = g_strdup (key);
+
+ argv = g_new (char *, argc - 1);
+ g_tree_insert (edit->defines, key, argv);
+ while (*a != NULL)
+ *argv++ = g_strdup (*a++);
+ *argv = NULL;
+ }
+ else
+ {
+ /* anything else is an error */
+ break_a;
+ }
+ MC_PTR_FREE (l);
+ }
+ MC_PTR_FREE (l);
+
+ if (edit->rules->len == 0)
+ {
+ g_ptr_array_free (edit->rules, TRUE);
+ edit->rules = NULL;
+ }
+
+ if (result == 0)
+ {
+ size_t i;
+ GString *first_chars;
+
+ if (edit->rules == NULL)
+ return line;
+
+ first_chars = g_string_sized_new (32);
+
+ /* collect first character of keywords */
+ for (i = 0; i < edit->rules->len; i++)
+ {
+ size_t j;
+
+ g_string_set_size (first_chars, 0);
+ c = CONTEXT_RULE (g_ptr_array_index (edit->rules, i));
+
+ g_string_append_c (first_chars, (char) 1);
+ for (j = 1; j < c->keyword->len; j++)
+ {
+ syntax_keyword_t *k;
+
+ k = SYNTAX_KEYWORD (g_ptr_array_index (c->keyword, j));
+ g_string_append_c (first_chars, k->keyword->str[0]);
+ }
+
+ c->keyword_first_chars = g_strndup (first_chars->str, first_chars->len);
+ }
+
+ g_string_free (first_chars, TRUE);
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* returns -1 on file error, line number on error in file syntax */
+static int
+edit_read_syntax_file (WEdit * edit, GPtrArray * pnames, const char *syntax_file,
+ const char *editor_file, const char *first_line, const char *type)
+{
+ FILE *f, *g = NULL;
+ char *args[ARGS_LEN], *l = NULL;
+ long line = 0;
+ int result = 0;
+ gboolean found = FALSE;
+
+ f = fopen (syntax_file, "r");
+ if (f == NULL)
+ {
+ char *global_syntax_file;
+
+ global_syntax_file =
+ g_build_filename (mc_global.share_data_dir, EDIT_SYNTAX_FILE, (char *) NULL);
+ f = fopen (global_syntax_file, "r");
+ g_free (global_syntax_file);
+ if (f == NULL)
+ return -1;
+ }
+
+ args[0] = NULL;
+ while (TRUE)
+ {
+ line++;
+ MC_PTR_FREE (l);
+ if (read_one_line (&l, f) == 0)
+ break;
+ (void) get_args (l, args, ARGS_LEN - 1); /* Final NULL */
+ if (args[0] == NULL)
+ continue;
+
+ /* Looking for 'include ...' lines before first 'file ...' ones */
+ if (!found && strcmp (args[0], "include") == 0)
+ {
+ if (args[1] == NULL || (g = open_include_file (args[1])) == NULL)
+ {
+ result = line;
+ break;
+ }
+ goto found_type;
+ }
+
+ /* looking for 'file ...' lines only */
+ if (strcmp (args[0], "file") != 0)
+ continue;
+
+ found = TRUE;
+
+ /* must have two args or report error */
+ if (args[1] == NULL || args[2] == NULL)
+ {
+ result = line;
+ break;
+ }
+
+ if (pnames != NULL)
+ {
+ /* 1: just collecting a list of names of rule sets */
+ g_ptr_array_add (pnames, g_strdup (args[2]));
+ }
+ else if (type != NULL)
+ {
+ /* 2: rule set was explicitly specified by the caller */
+ if (strcmp (type, args[2]) == 0)
+ goto found_type;
+ }
+ else if (editor_file != NULL && edit != NULL)
+ {
+ /* 3: auto-detect rule set from regular expressions */
+ gboolean q;
+
+ q = mc_search (args[1], DEFAULT_CHARSET, editor_file, MC_SEARCH_T_REGEX);
+ /* does filename match arg 1 ? */
+ if (!q && args[3] != NULL)
+ {
+ /* does first line match arg 3 ? */
+ q = mc_search (args[3], DEFAULT_CHARSET, first_line, MC_SEARCH_T_REGEX);
+ }
+ if (q)
+ {
+ int line_error;
+ char *syntax_type;
+
+ found_type:
+ syntax_type = args[2];
+ line_error = edit_read_syntax_rules (edit, g ? g : f, args, ARGS_LEN - 1);
+ if (line_error != 0)
+ {
+ if (error_file_name == NULL) /* an included file */
+ result = line + line_error;
+ else
+ result = line_error;
+ }
+ else
+ {
+ g_free (edit->syntax_type);
+ edit->syntax_type = g_strdup (syntax_type);
+ /* if there are no rules then turn off syntax highlighting for speed */
+ if (g == NULL && edit->rules->len == 1)
+ {
+ context_rule_t *r0;
+
+ r0 = CONTEXT_RULE (g_ptr_array_index (edit->rules, 0));
+ if (r0->keyword->len == 1 && !r0->spelling)
+ {
+ edit_free_syntax_rules (edit);
+ break;
+ }
+ }
+ }
+
+ if (g == NULL)
+ break;
+
+ fclose (g);
+ g = NULL;
+ }
+ }
+ }
+ g_free (l);
+ fclose (f);
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+get_first_editor_line (WEdit * edit)
+{
+ static char s[256];
+
+ s[0] = '\0';
+
+ if (edit != NULL)
+ {
+ size_t i;
+
+ for (i = 0; i < sizeof (s) - 1; i++)
+ {
+ s[i] = edit_buffer_get_byte (&edit->buffer, i);
+ if (s[i] == '\n')
+ {
+ s[i] = '\0';
+ break;
+ }
+ }
+
+ s[sizeof (s) - 1] = '\0';
+ }
+
+ return s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+pstrcmp (const void *p1, const void *p2)
+{
+ return strcmp (*(char *const *) p1, *(char *const *) p2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+exec_edit_syntax_dialog (const GPtrArray * names, const char *current_syntax)
+{
+ size_t i;
+ Listbox *syntaxlist;
+
+ syntaxlist = listbox_window_new (LIST_LINES, MAX_ENTRY_LEN,
+ _("Choose syntax highlighting"), NULL);
+ LISTBOX_APPEND_TEXT (syntaxlist, 'A', _("< Auto >"), NULL, FALSE);
+ LISTBOX_APPEND_TEXT (syntaxlist, 'R', _("< Reload Current Syntax >"), NULL, FALSE);
+
+ for (i = 0; i < names->len; i++)
+ {
+ const char *name;
+
+ name = g_ptr_array_index (names, i);
+ LISTBOX_APPEND_TEXT (syntaxlist, 0, name, NULL, FALSE);
+ if (current_syntax != NULL && strcmp (name, current_syntax) == 0)
+ listbox_set_current (syntaxlist->list, i + N_DFLT_ENTRIES);
+ }
+
+ return listbox_run (syntaxlist);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+edit_get_syntax_color (WEdit * edit, off_t byte_index)
+{
+ if (!tty_use_colors ())
+ return 0;
+
+ if (edit_options.syntax_highlighting && edit->rules != NULL && byte_index < edit->buffer.size)
+ {
+ edit_get_rule (edit, byte_index);
+ return translate_rule_to_color (edit, &edit->rule);
+ }
+
+ return EDITOR_NORMAL_COLOR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_free_syntax_rules (WEdit * edit)
+{
+ if (edit == NULL)
+ return;
+
+ if (edit->defines != NULL)
+ destroy_defines (&edit->defines);
+
+ if (edit->rules == NULL)
+ return;
+
+ edit_get_rule (edit, -1);
+ MC_PTR_FREE (edit->syntax_type);
+
+ g_ptr_array_free (edit->rules, TRUE);
+ edit->rules = NULL;
+ g_clear_slist (&edit->syntax_marker, g_free);
+ tty_color_free_all_tmp ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load rules into edit struct. Either edit or *pnames must be NULL. If
+ * edit is NULL, a list of types will be stored into names. If type is
+ * NULL, then the type will be selected according to the filename.
+ * type must be edit->syntax_type or NULL
+ */
+void
+edit_load_syntax (WEdit * edit, GPtrArray * pnames, const char *type)
+{
+ int r;
+ char *f = NULL;
+
+ if (auto_syntax)
+ type = NULL;
+
+ if (edit != NULL)
+ {
+ char *saved_type;
+
+ saved_type = g_strdup (type); /* save edit->syntax_type */
+ edit_free_syntax_rules (edit);
+ edit->syntax_type = saved_type; /* restore edit->syntax_type */
+ }
+
+ if (!tty_use_colors ())
+ return;
+
+ if (!edit_options.syntax_highlighting && (pnames == NULL || pnames->len == 0))
+ return;
+
+ if (edit != NULL && edit->filename_vpath == NULL)
+ return;
+
+ f = mc_config_get_full_path (EDIT_SYNTAX_FILE);
+ if (edit != NULL)
+ r = edit_read_syntax_file (edit, pnames, f, vfs_path_as_str (edit->filename_vpath),
+ get_first_editor_line (edit),
+ auto_syntax ? NULL : edit->syntax_type);
+ else
+ r = edit_read_syntax_file (NULL, pnames, f, NULL, "", NULL);
+ if (r == -1)
+ {
+ edit_free_syntax_rules (edit);
+ message (D_ERROR, _("Load syntax file"),
+ _("Cannot open file %s\n%s"), f, unix_error_string (errno));
+ }
+ else if (r != 0)
+ {
+ edit_free_syntax_rules (edit);
+ message (D_ERROR, _("Load syntax file"),
+ _("Error in file %s on line %d"), error_file_name != NULL ? error_file_name : f,
+ r);
+ MC_PTR_FREE (error_file_name);
+ }
+
+ g_free (f);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+edit_get_syntax_type (const WEdit * edit)
+{
+ return edit->syntax_type;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_syntax_dialog (WEdit * edit)
+{
+ GPtrArray *names;
+ int syntax;
+
+ names = g_ptr_array_new_with_free_func (g_free);
+
+ /* We fill the list of syntax files every time the editor is invoked.
+ Instead we could save the list to a file and update it once the syntax
+ file gets updated (either by testing or by explicit user command). */
+ edit_load_syntax (NULL, names, NULL);
+ g_ptr_array_sort (names, pstrcmp);
+
+ syntax = exec_edit_syntax_dialog (names, edit->syntax_type);
+ if (syntax >= 0)
+ {
+ gboolean force_reload = FALSE;
+ char *current_syntax;
+ gboolean old_auto_syntax;
+
+ current_syntax = g_strdup (edit->syntax_type);
+ old_auto_syntax = auto_syntax;
+
+ switch (syntax)
+ {
+ case 0: /* auto syntax */
+ auto_syntax = TRUE;
+ break;
+ case 1: /* reload current syntax */
+ force_reload = TRUE;
+ break;
+ default:
+ auto_syntax = FALSE;
+ g_free (edit->syntax_type);
+ edit->syntax_type = g_strdup (g_ptr_array_index (names, syntax - N_DFLT_ENTRIES));
+ }
+
+ /* Load or unload syntax rules if the option has changed */
+ if (force_reload || (auto_syntax && !old_auto_syntax) || old_auto_syntax ||
+ (current_syntax != NULL && edit->syntax_type != NULL &&
+ strcmp (current_syntax, edit->syntax_type) != 0))
+ edit_load_syntax (edit, NULL, edit->syntax_type);
+
+ g_free (current_syntax);
+ }
+
+ g_ptr_array_free (names, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/events_init.c b/src/events_init.c
new file mode 100644
index 0000000..53473e5
--- /dev/null
+++ b/src/events_init.c
@@ -0,0 +1,86 @@
+/*
+ Event callbacks initialization
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2011.
+
+ 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 "lib/global.h"
+
+#include "lib/event.h"
+
+#ifdef ENABLE_BACKGROUND
+#include "background.h" /* (background_parent_call), background_parent_call_string() */
+#endif /* ENABLE_BACKGROUND */
+#include "clipboard.h" /* clipboard events */
+#include "execute.h" /* execute_suspend() */
+#include "help.h" /* help_interactive_display() */
+
+#include "events_init.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+events_init (GError ** mcerror)
+{
+ /* *INDENT-OFF* */
+ static const event_init_t standard_events[] =
+ {
+ {MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", clipboard_file_to_ext_clip, NULL},
+ {MCEVENT_GROUP_CORE, "clipboard_file_from_ext_clip", clipboard_file_from_ext_clip, NULL},
+ {MCEVENT_GROUP_CORE, "clipboard_text_to_file", clipboard_text_to_file, NULL},
+ {MCEVENT_GROUP_CORE, "clipboard_text_from_file", clipboard_text_from_file, NULL},
+
+ {MCEVENT_GROUP_CORE, "help", help_interactive_display, NULL},
+ {MCEVENT_GROUP_CORE, "suspend", execute_suspend, NULL},
+
+#ifdef ENABLE_BACKGROUND
+ {MCEVENT_GROUP_CORE, "background_parent_call", background_parent_call, NULL},
+ {MCEVENT_GROUP_CORE, "background_parent_call_string", background_parent_call_string, NULL},
+#endif /* ENABLE_BACKGROUND */
+
+ {NULL, NULL, NULL, NULL}
+ };
+ /* *INDENT-ON* */
+
+ if (!mc_event_init (mcerror))
+ return FALSE;
+
+ return mc_event_mass_add (standard_events, mcerror);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/events_init.h b/src/events_init.h
new file mode 100644
index 0000000..f2d447e
--- /dev/null
+++ b/src/events_init.h
@@ -0,0 +1,19 @@
+#ifndef MC__EVENTS_INIT_H
+#define MC__EVENTS_INIT_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+
+/*** declarations of public functions ************************************************************/
+
+gboolean events_init (GError ** mcerror);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__EVENTS_INIT_H */
diff --git a/src/execute.c b/src/execute.c
new file mode 100644
index 0000000..c220774
--- /dev/null
+++ b/src/execute.c
@@ -0,0 +1,670 @@
+/*
+ Execution routines for GNU Midnight Commander
+
+ Copyright (C) 2003-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ 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 execute.c
+ * \brief Source: execution routines
+ */
+
+#include <config.h>
+
+#include <signal.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h"
+#include "lib/tty/win.h"
+#include "lib/vfs/vfs.h"
+#include "lib/mcconfig.h"
+#include "lib/util.h"
+#include "lib/strutil.h" /* str_replace_all_substrings() */
+#include "lib/widget.h"
+
+#include "filemanager/filemanager.h"
+#include "filemanager/layout.h" /* use_dash() */
+#include "consaver/cons.saver.h"
+#ifdef ENABLE_SUBSHELL
+#include "subshell/subshell.h"
+#endif
+#include "setup.h" /* clear_before_exec */
+
+#include "execute.h"
+
+/*** global variables ****************************************************************************/
+
+int pause_after_run = pause_on_dumb_terminals;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+void do_execute (const char *shell, const char *command, int flags);
+void do_executev (const char *shell, int flags, char *const argv[]);
+char *execute_get_external_cmd_opts_from_config (const char *command,
+ const vfs_path_t * filename_vpath,
+ long start_line);
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edition_post_exec (void)
+{
+ tty_enter_ca_mode ();
+
+ /* FIXME: Missing on slang endwin? */
+ tty_reset_prog_mode ();
+ tty_flush_input ();
+
+ tty_keypad (TRUE);
+ tty_raw_mode ();
+ channels_up ();
+ enable_mouse ();
+ enable_bracketed_paste ();
+ if (mc_global.tty.alternate_plus_minus)
+ application_keypad_mode ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+edition_pre_exec (void)
+{
+ if (clear_before_exec)
+ tty_clear_screen ();
+ else
+ {
+ if (!(mc_global.tty.console_flag != '\0' || mc_global.tty.xterm_flag))
+ printf ("\n\n");
+ }
+
+ channels_down ();
+ disable_mouse ();
+ disable_bracketed_paste ();
+
+ tty_reset_shell_mode ();
+ tty_keypad (FALSE);
+ tty_reset_screen ();
+
+ numeric_keypad_mode ();
+
+ /* on xterms: maybe endwin did not leave the terminal on the shell
+ * screen page: do it now.
+ *
+ * Do not move this before endwin: in some systems rmcup includes
+ * a call to clear screen, so it will end up clearing the shell screen.
+ */
+ tty_exit_ca_mode ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_SUBSHELL
+static void
+do_possible_cd (const vfs_path_t * new_dir_vpath)
+{
+ if (!panel_cd (current_panel, new_dir_vpath, cd_exact))
+ message (D_ERROR, _("Warning"), "%s",
+ _("The Commander can't change to the directory that\n"
+ "the subshell claims you are in. Perhaps you have\n"
+ "deleted your working directory, or given yourself\n"
+ "extra access permissions with the \"su\" command?"));
+}
+#endif /* ENABLE_SUBSHELL */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+do_suspend_cmd (void)
+{
+ pre_exec ();
+
+ if (mc_global.tty.console_flag != '\0' && !mc_global.tty.use_subshell)
+ handle_console (CONSOLE_RESTORE);
+
+#ifdef SIGTSTP
+ {
+ struct sigaction sigtstp_action;
+
+ memset (&sigtstp_action, 0, sizeof (sigtstp_action));
+ /* Make sure that the SIGTSTP below will suspend us directly,
+ without calling ncurses' SIGTSTP handler; we *don't* want
+ ncurses to redraw the screen immediately after the SIGCONT */
+ sigaction (SIGTSTP, &startup_handler, &sigtstp_action);
+
+ kill (getpid (), SIGTSTP);
+
+ /* Restore previous SIGTSTP action */
+ sigaction (SIGTSTP, &sigtstp_action, NULL);
+ }
+#endif /* SIGTSTP */
+
+ if (mc_global.tty.console_flag != '\0' && !mc_global.tty.use_subshell)
+ handle_console (CONSOLE_SAVE);
+
+ edition_post_exec ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+execute_prepare_with_vfs_arg (const vfs_path_t * filename_vpath, vfs_path_t ** localcopy_vpath,
+ time_t * mtime)
+{
+ struct stat st;
+
+ /* Simplest case, this file is local */
+ if ((filename_vpath == NULL && vfs_file_is_local (vfs_get_raw_current_dir ()))
+ || vfs_file_is_local (filename_vpath))
+ return TRUE;
+
+ /* FIXME: Creation of new files on VFS is not supported */
+ if (filename_vpath == NULL)
+ return FALSE;
+
+ *localcopy_vpath = mc_getlocalcopy (filename_vpath);
+ if (*localcopy_vpath == NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot fetch a local copy of %s"),
+ vfs_path_as_str (filename_vpath));
+ return FALSE;
+ }
+
+ mc_stat (*localcopy_vpath, &st);
+ *mtime = st.st_mtime;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+execute_cleanup_with_vfs_arg (const vfs_path_t * filename_vpath, vfs_path_t ** localcopy_vpath,
+ time_t * mtime)
+{
+ if (*localcopy_vpath != NULL)
+ {
+ struct stat st;
+
+ /*
+ * filename can be an entry on panel, it can be changed by executing
+ * the command, so make a copy. Smarter VFS code would make the code
+ * below unnecessary.
+ */
+ mc_stat (*localcopy_vpath, &st);
+ mc_ungetlocalcopy (filename_vpath, *localcopy_vpath, *mtime != st.st_mtime);
+ vfs_path_free (*localcopy_vpath, TRUE);
+ *localcopy_vpath = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+execute_get_opts_from_cfg (const char *command, const char *default_str)
+{
+ char *str_from_config;
+
+ str_from_config =
+ mc_config_get_string_raw (mc_global.main_config, CONFIG_EXT_EDITOR_VIEWER_SECTION, command,
+ NULL);
+
+ if (str_from_config == NULL)
+ {
+ mc_config_t *cfg;
+
+ cfg = mc_config_init (mc_global.profile_name, TRUE);
+ if (cfg == NULL)
+ return g_strdup (default_str);
+
+ str_from_config =
+ mc_config_get_string_raw (cfg, CONFIG_EXT_EDITOR_VIEWER_SECTION, command, default_str);
+
+ mc_config_deinit (cfg);
+ }
+
+ return str_from_config;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+execute_get_external_cmd_opts_from_config (const char *command, const vfs_path_t * filename_vpath,
+ long start_line)
+{
+ char *str_from_config, *return_str;
+ char *parameter;
+
+ if (filename_vpath == NULL)
+ return g_strdup ("");
+
+ parameter = g_shell_quote (vfs_path_get_last_path_str (filename_vpath));
+
+ if (start_line <= 0)
+ return parameter;
+
+ str_from_config = execute_get_opts_from_cfg (command, "%filename");
+
+ return_str = str_replace_all (str_from_config, "%filename", parameter);
+ g_free (parameter);
+ g_free (str_from_config);
+ str_from_config = return_str;
+
+ parameter = g_strdup_printf ("%ld", start_line);
+ return_str = str_replace_all (str_from_config, "%lineno", parameter);
+ g_free (parameter);
+ g_free (str_from_config);
+
+ return return_str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+do_executev (const char *shell, int flags, char *const argv[])
+{
+#ifdef ENABLE_SUBSHELL
+ vfs_path_t *new_dir_vpath = NULL;
+#endif /* ENABLE_SUBSHELL */
+
+ vfs_path_t *old_vfs_dir_vpath = NULL;
+
+ if (!vfs_current_is_local ())
+ old_vfs_dir_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
+
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ save_cwds_stat ();
+ pre_exec ();
+ if (mc_global.tty.console_flag != '\0')
+ handle_console (CONSOLE_RESTORE);
+
+ if (!mc_global.tty.use_subshell && *argv != NULL && (flags & EXECUTE_INTERNAL) == 0)
+ {
+ printf ("%s%s\n", mc_prompt, *argv);
+ fflush (stdout);
+ }
+#ifdef ENABLE_SUBSHELL
+ if (mc_global.tty.use_subshell && (flags & EXECUTE_INTERNAL) == 0)
+ {
+ do_update_prompt ();
+
+ /* We don't care if it died, higher level takes care of this */
+ invoke_subshell (*argv, VISIBLY, old_vfs_dir_vpath != NULL ? NULL : &new_dir_vpath);
+ }
+ else
+#endif /* ENABLE_SUBSHELL */
+ my_systemv_flags (flags, shell, argv);
+
+ if ((flags & EXECUTE_INTERNAL) == 0)
+ {
+ if ((pause_after_run == pause_always
+ || (pause_after_run == pause_on_dumb_terminals && !mc_global.tty.xterm_flag
+ && mc_global.tty.console_flag == '\0')) && quit == 0
+#ifdef ENABLE_SUBSHELL
+ && subshell_state != RUNNING_COMMAND
+#endif /* ENABLE_SUBSHELL */
+ )
+ {
+ printf ("%s", _("Press any key to continue..."));
+ fflush (stdout);
+ tty_raw_mode ();
+ get_key_code (0);
+ printf ("\r\n");
+ fflush (stdout);
+ }
+ if (mc_global.tty.console_flag != '\0' && output_lines != 0 && mc_global.keybar_visible)
+ {
+ putchar ('\n');
+ fflush (stdout);
+ }
+ }
+
+ if (mc_global.tty.console_flag != '\0')
+ handle_console (CONSOLE_SAVE);
+ edition_post_exec ();
+
+#ifdef ENABLE_SUBSHELL
+ if (new_dir_vpath != NULL)
+ {
+ do_possible_cd (new_dir_vpath);
+ vfs_path_free (new_dir_vpath, TRUE);
+ }
+
+#endif /* ENABLE_SUBSHELL */
+
+ if (old_vfs_dir_vpath != NULL)
+ {
+ mc_chdir (old_vfs_dir_vpath);
+ vfs_path_free (old_vfs_dir_vpath, TRUE);
+ }
+
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ {
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ update_xterm_title_path ();
+ }
+
+ do_refresh ();
+ use_dash (TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+do_execute (const char *shell, const char *command, int flags)
+{
+ GPtrArray *args_array;
+
+ args_array = g_ptr_array_new ();
+ g_ptr_array_add (args_array, (char *) command);
+ g_ptr_array_add (args_array, NULL);
+
+ do_executev (shell, flags, (char *const *) args_array->pdata);
+
+ g_ptr_array_free (args_array, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Set up the terminal before executing a program */
+
+void
+pre_exec (void)
+{
+ use_dash (FALSE);
+ edition_pre_exec ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Hide the terminal after executing a program */
+void
+post_exec (void)
+{
+ edition_post_exec ();
+ use_dash (TRUE);
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Executes a command */
+
+void
+shell_execute (const char *command, int flags)
+{
+ char *cmd = NULL;
+
+ if (flags & EXECUTE_HIDE)
+ {
+ cmd = g_strconcat (" ", command, (char *) NULL);
+ flags ^= EXECUTE_HIDE;
+ }
+
+#ifdef ENABLE_SUBSHELL
+ if (mc_global.tty.use_subshell)
+ {
+ if (subshell_state == INACTIVE)
+ do_execute (mc_global.shell->path, cmd ? cmd : command, flags | EXECUTE_AS_SHELL);
+ else
+ message (D_ERROR, MSG_ERROR, "%s", _("The shell is already running a command"));
+ }
+ else
+#endif /* ENABLE_SUBSHELL */
+ do_execute (mc_global.shell->path, cmd ? cmd : command, flags | EXECUTE_AS_SHELL);
+
+ g_free (cmd);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+toggle_subshell (void)
+{
+ static gboolean message_flag = TRUE;
+
+#ifdef ENABLE_SUBSHELL
+ vfs_path_t *new_dir_vpath = NULL;
+#endif /* ENABLE_SUBSHELL */
+
+ SIG_ATOMIC_VOLATILE_T was_sigwinch = 0;
+
+ if (!(mc_global.tty.xterm_flag || mc_global.tty.console_flag != '\0'
+ || mc_global.tty.use_subshell || output_starts_shell))
+ {
+ if (message_flag)
+ message (D_ERROR, MSG_ERROR,
+ _("Not an xterm or Linux console;\nthe subshell cannot be toggled."));
+ message_flag = FALSE;
+ return;
+ }
+
+ channels_down ();
+ disable_mouse ();
+ disable_bracketed_paste ();
+ if (clear_before_exec)
+ tty_clear_screen ();
+ if (mc_global.tty.alternate_plus_minus)
+ numeric_keypad_mode ();
+#ifndef HAVE_SLANG
+ /* With slang we don't want any of this, since there
+ * is no raw_mode supported
+ */
+ tty_reset_shell_mode ();
+#endif /* !HAVE_SLANG */
+ tty_noecho ();
+ tty_keypad (FALSE);
+ tty_reset_screen ();
+ tty_exit_ca_mode ();
+ tty_raw_mode ();
+ if (mc_global.tty.console_flag != '\0')
+ handle_console (CONSOLE_RESTORE);
+
+#ifdef ENABLE_SUBSHELL
+ if (mc_global.tty.use_subshell)
+ {
+ vfs_path_t **new_dir_p;
+
+ new_dir_p = vfs_current_is_local ()? &new_dir_vpath : NULL;
+ invoke_subshell (NULL, VISIBLY, new_dir_p);
+ }
+ else
+#endif /* ENABLE_SUBSHELL */
+ {
+ if (output_starts_shell)
+ {
+ fputs (_("Type 'exit' to return to the Midnight Commander"), stderr);
+ fputs ("\n\r\n\r", stderr);
+
+ my_system (EXECUTE_INTERNAL, mc_global.shell->path, NULL);
+ }
+ else
+ get_key_code (0);
+ }
+
+ if (mc_global.tty.console_flag != '\0')
+ handle_console (CONSOLE_SAVE);
+
+ tty_enter_ca_mode ();
+
+ tty_reset_prog_mode ();
+ tty_keypad (TRUE);
+
+ /* Prevent screen flash when user did 'exit' or 'logout' within
+ subshell */
+ if ((quit & SUBSHELL_EXIT) != 0)
+ {
+ /* User did 'exit' or 'logout': quit MC */
+ if (quiet_quit_cmd ())
+ return;
+
+ quit = 0;
+#ifdef ENABLE_SUBSHELL
+ /* restart subshell */
+ if (mc_global.tty.use_subshell)
+ init_subshell ();
+#endif /* ENABLE_SUBSHELL */
+ }
+
+ enable_mouse ();
+ enable_bracketed_paste ();
+ channels_up ();
+ if (mc_global.tty.alternate_plus_minus)
+ application_keypad_mode ();
+
+ /* HACK:
+ * Save sigwinch flag that will be reset in mc_refresh() called via update_panels().
+ * There is some problem with screen redraw in ncurses-based mc in this situation.
+ */
+ was_sigwinch = tty_got_winch ();
+ tty_flush_winch ();
+
+#ifdef ENABLE_SUBSHELL
+ if (mc_global.tty.use_subshell)
+ {
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ {
+ if (new_dir_vpath != NULL)
+ do_possible_cd (new_dir_vpath);
+ }
+ else if (new_dir_vpath != NULL && mc_chdir (new_dir_vpath) != -1)
+ vfs_setup_cwd ();
+ }
+
+ vfs_path_free (new_dir_vpath, TRUE);
+#endif /* ENABLE_SUBSHELL */
+
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ {
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ update_xterm_title_path ();
+ }
+
+ if (was_sigwinch != 0 || tty_got_winch ())
+ dialog_change_screen_size ();
+ else
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+gboolean
+execute_suspend (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+ (void) data;
+
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ save_cwds_stat ();
+ do_suspend_cmd ();
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ do_refresh ();
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Execute command on a filename that can be on VFS.
+ * Errors are reported to the user.
+ */
+
+void
+execute_with_vfs_arg (const char *command, const vfs_path_t * filename_vpath)
+{
+ vfs_path_t *localcopy_vpath = NULL;
+ const vfs_path_t *do_execute_vpath;
+ time_t mtime;
+
+ if (!execute_prepare_with_vfs_arg (filename_vpath, &localcopy_vpath, &mtime))
+ return;
+
+ do_execute_vpath = (localcopy_vpath == NULL) ? filename_vpath : localcopy_vpath;
+
+ do_execute (command, vfs_path_get_last_path_str (do_execute_vpath), EXECUTE_INTERNAL);
+
+ execute_cleanup_with_vfs_arg (filename_vpath, &localcopy_vpath, &mtime);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Execute external editor or viewer.
+ *
+ * @param command editor/viewer to run
+ * @param filename_vpath path for edit/view
+ * @param start_line cursor will be placed at the 'start_line' position after opening file
+ * if start_line is 0 or negative, no start line will be passed to editor/viewer
+ */
+
+void
+execute_external_editor_or_viewer (const char *command, const vfs_path_t * filename_vpath,
+ long start_line)
+{
+ vfs_path_t *localcopy_vpath = NULL;
+ const vfs_path_t *do_execute_vpath;
+ char *extern_cmd_options;
+ time_t mtime = 0;
+
+ if (!execute_prepare_with_vfs_arg (filename_vpath, &localcopy_vpath, &mtime))
+ return;
+
+ do_execute_vpath = (localcopy_vpath == NULL) ? filename_vpath : localcopy_vpath;
+
+ extern_cmd_options =
+ execute_get_external_cmd_opts_from_config (command, do_execute_vpath, start_line);
+
+ if (extern_cmd_options != NULL)
+ {
+ char **argv_cmd_options;
+ int argv_count;
+
+ if (g_shell_parse_argv (extern_cmd_options, &argv_count, &argv_cmd_options, NULL))
+ {
+ do_executev (command, EXECUTE_INTERNAL, argv_cmd_options);
+ g_strfreev (argv_cmd_options);
+ }
+ else
+ do_executev (command, EXECUTE_INTERNAL, NULL);
+
+ g_free (extern_cmd_options);
+ }
+
+ execute_cleanup_with_vfs_arg (filename_vpath, &localcopy_vpath, &mtime);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/execute.h b/src/execute.h
new file mode 100644
index 0000000..a326806
--- /dev/null
+++ b/src/execute.h
@@ -0,0 +1,51 @@
+/** \file execute.h
+ * \brief Header: execution routines
+ */
+
+#ifndef MC__EXECUTE_H
+#define MC__EXECUTE_H
+
+#include "lib/utilunix.h"
+#include "lib/vfs/vfs.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/* If true, after executing a command, wait for a keystroke */
+enum
+{
+ pause_never,
+ pause_on_dumb_terminals,
+ pause_always
+};
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern int pause_after_run;
+
+/*** declarations of public functions ************************************************************/
+
+/* Execute functions that use the shell to execute */
+void shell_execute (const char *command, int flags);
+
+/* Handle toggling panels by Ctrl-O */
+void toggle_subshell (void);
+
+/* Handle toggling panels by Ctrl-Z */
+gboolean execute_suspend (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data);
+
+/* Execute command on a filename that can be on VFS */
+void execute_with_vfs_arg (const char *command, const vfs_path_t * filename_vpath);
+void execute_external_editor_or_viewer (const char *command, const vfs_path_t * filename_vpath,
+ long start_line);
+
+void post_exec (void);
+void pre_exec (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__EXECUTE_H */
diff --git a/src/file_history.c b/src/file_history.c
new file mode 100644
index 0000000..e46985e
--- /dev/null
+++ b/src/file_history.c
@@ -0,0 +1,246 @@
+/*
+ Load and show history of edited and viewed files
+
+ Copyright (C) 2020-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2019-2022
+
+ 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 <stdio.h> /* file functions */
+
+#include "lib/global.h"
+
+#include "lib/fileloc.h" /* MC_FILEPOS_FILE */
+#include "lib/mcconfig.h" /* mc_config_get_full_path() */
+#include "lib/strutil.h" /* str_term_width1() */
+#include "lib/util.h" /* backup functions */
+
+#include "file_history.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define TMP_SUFFIX ".tmp"
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct file_history_data_t
+{
+ char *file_name;
+ char *file_pos;
+} file_history_data_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+file_history_list_read (void)
+{
+ char *fn;
+ FILE *f;
+ char buf[MC_MAXPATHLEN + 100];
+ GList *file_list = NULL;
+
+ /* open file with positions */
+ fn = mc_config_get_full_path (MC_FILEPOS_FILE);
+ if (fn == NULL)
+ return NULL;
+
+ f = fopen (fn, "r");
+ g_free (fn);
+ if (f == NULL)
+ return NULL;
+
+ while (fgets (buf, sizeof (buf), f) != NULL)
+ {
+ char *s;
+ file_history_data_t *fhd;
+ size_t len;
+
+ s = strrchr (buf, ' ');
+ /* FIXME: saved file position info is present in filepos file */
+ fhd = g_new (file_history_data_t, 1);
+ fhd->file_name = g_strndup (buf, s - buf);
+ len = strlen (s + 1);
+ fhd->file_pos = g_strndup (s + 1, len - 1); /* ignore '\n' */
+ file_list = g_list_prepend (file_list, fhd);
+ }
+
+ fclose (f);
+
+ return file_list;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+file_history_list_write (const GList * file_list)
+{
+ char *fn;
+ FILE *f;
+ gboolean write_error = FALSE;
+
+ fn = mc_config_get_full_path (MC_FILEPOS_FILE);
+ if (fn == NULL)
+ return;
+
+ mc_util_make_backup_if_possible (fn, TMP_SUFFIX);
+
+ f = fopen (fn, "w");
+ if (f != NULL)
+ {
+ GString *s;
+
+ s = g_string_sized_new (128);
+
+ for (; file_list != NULL && !write_error; file_list = g_list_next (file_list))
+ {
+ file_history_data_t *fhd = (file_history_data_t *) file_list->data;
+
+ g_string_append (s, fhd->file_name);
+ if (fhd->file_pos != NULL)
+ {
+ g_string_append_c (s, ' ');
+ g_string_append (s, fhd->file_pos);
+ }
+
+ write_error = (fprintf (f, "%s\n", s->str) < 0);
+ g_string_truncate (s, 0);
+ }
+
+ g_string_free (s, TRUE);
+
+ fclose (f);
+ }
+
+ if (write_error)
+ mc_util_restore_from_backup_if_possible (fn, TMP_SUFFIX);
+ else
+ mc_util_unlink_backup_if_possible (fn, TMP_SUFFIX);
+
+ g_free (fn);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+file_history_create_item (history_descriptor_t * hd, void *data)
+{
+ file_history_data_t *fhd = (file_history_data_t *) data;
+ size_t width;
+
+ width = str_term_width1 (fhd->file_name);
+ hd->max_width = MAX (width, hd->max_width);
+
+ listbox_add_item (hd->listbox, LISTBOX_APPEND_AT_END, 0, fhd->file_name, fhd->file_pos, TRUE);
+ /* fhd->file_pos is not copied, NULLize it to prevent double free */
+ fhd->file_pos = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+file_history_release_item (history_descriptor_t * hd, WLEntry * le)
+{
+ file_history_data_t *fhd;
+
+ (void) hd;
+
+ fhd = g_new (file_history_data_t, 1);
+ fhd->file_name = le->text;
+ le->text = NULL;
+ fhd->file_pos = (char *) le->data;
+ le->data = NULL;
+
+ return fhd;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+file_history_free_item (void *data)
+{
+ file_history_data_t *fhd = (file_history_data_t *) data;
+
+ g_free (fhd->file_name);
+ g_free (fhd->file_pos);
+ g_free (fhd);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Show file history and return the selected file
+ *
+ * @param w widget used for positioning of history window
+ * @param action to do with file (edit, view, etc)
+ *
+ * @return name of selected file, A newly allocated string.
+ */
+char *
+show_file_history (const Widget * w, int *action)
+{
+ GList *file_list;
+ size_t len;
+ history_descriptor_t hd;
+
+ file_list = file_history_list_read ();
+ if (file_list == NULL)
+ return NULL;
+
+ len = g_list_length (file_list);
+
+ file_list = g_list_last (file_list);
+
+ history_descriptor_init (&hd, w->rect.y, w->rect.x, file_list, 0);
+ /* redefine list-specific functions */
+ hd.create = file_history_create_item;
+ hd.release = file_history_release_item;
+ hd.free = file_history_free_item;
+
+ history_show (&hd);
+
+ hd.list = g_list_first (hd.list);
+
+ /* Has history cleaned up or not? */
+ if (len != g_list_length (hd.list))
+ {
+ hd.list = g_list_reverse (hd.list);
+ file_history_list_write (hd.list);
+ }
+
+ g_list_free_full (hd.list, (GDestroyNotify) file_history_free_item);
+
+ *action = hd.action;
+
+ return hd.text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/file_history.h b/src/file_history.h
new file mode 100644
index 0000000..0156207
--- /dev/null
+++ b/src/file_history.h
@@ -0,0 +1,20 @@
+#ifndef MC__FILE_HISTORY_H
+#define MC__FILE_HISTORY_H
+
+#include "lib/widget.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+char *show_file_history (const Widget * w, int *action);
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__FILE_HISTORY_H */
diff --git a/src/filemanager/Makefile.am b/src/filemanager/Makefile.am
new file mode 100644
index 0000000..534d8dc
--- /dev/null
+++ b/src/filemanager/Makefile.am
@@ -0,0 +1,40 @@
+
+noinst_LTLIBRARIES = libmcfilemanager.la
+
+libmcfilemanager_la_SOURCES = \
+ achown.c \
+ boxes.c boxes.h \
+ cd.c cd.h \
+ chmod.c \
+ chown.c \
+ cmd.c cmd.h \
+ command.c command.h \
+ dir.c dir.h \
+ ext.c ext.h \
+ file.c file.h \
+ filegui.c filegui.h \
+ filemanager.h filemanager.c \
+ filenot.c filenot.h \
+ fileopctx.c fileopctx.h \
+ find.c \
+ hotlist.c hotlist.h \
+ info.c info.h \
+ ioblksize.h \
+ layout.c layout.h \
+ mountlist.c mountlist.h \
+ panelize.c panelize.h \
+ panel.c panel.h \
+ tree.c tree.h \
+ treestore.c treestore.h
+
+# Unmaintained, unsupported, etc
+# listmode.c listmode.h
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
+
+if ENABLE_EXT2FS_ATTR
+libmcfilemanager_la_SOURCES += \
+ chattr.c
+
+AM_CPPFLAGS += @EXT2FS_CFLAGS@ @E2P_CFLAGS@
+endif
diff --git a/src/filemanager/Makefile.in b/src/filemanager/Makefile.in
new file mode 100644
index 0000000..2e1300b
--- /dev/null
+++ b/src/filemanager/Makefile.in
@@ -0,0 +1,839 @@
+# 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@
+@ENABLE_EXT2FS_ATTR_TRUE@am__append_1 = \
+@ENABLE_EXT2FS_ATTR_TRUE@ chattr.c
+
+@ENABLE_EXT2FS_ATTR_TRUE@am__append_2 = @EXT2FS_CFLAGS@ @E2P_CFLAGS@
+subdir = src/filemanager
+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)
+libmcfilemanager_la_LIBADD =
+am__libmcfilemanager_la_SOURCES_DIST = achown.c boxes.c boxes.h cd.c \
+ cd.h chmod.c chown.c cmd.c cmd.h command.c command.h dir.c \
+ dir.h ext.c ext.h file.c file.h filegui.c filegui.h \
+ filemanager.h filemanager.c filenot.c filenot.h fileopctx.c \
+ fileopctx.h find.c hotlist.c hotlist.h info.c info.h \
+ ioblksize.h layout.c layout.h mountlist.c mountlist.h \
+ panelize.c panelize.h panel.c panel.h tree.c tree.h \
+ treestore.c treestore.h chattr.c
+@ENABLE_EXT2FS_ATTR_TRUE@am__objects_1 = chattr.lo
+am_libmcfilemanager_la_OBJECTS = achown.lo boxes.lo cd.lo chmod.lo \
+ chown.lo cmd.lo command.lo dir.lo ext.lo file.lo filegui.lo \
+ filemanager.lo filenot.lo fileopctx.lo find.lo hotlist.lo \
+ info.lo layout.lo mountlist.lo panelize.lo panel.lo tree.lo \
+ treestore.lo $(am__objects_1)
+libmcfilemanager_la_OBJECTS = $(am_libmcfilemanager_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)/achown.Plo ./$(DEPDIR)/boxes.Plo \
+ ./$(DEPDIR)/cd.Plo ./$(DEPDIR)/chattr.Plo \
+ ./$(DEPDIR)/chmod.Plo ./$(DEPDIR)/chown.Plo \
+ ./$(DEPDIR)/cmd.Plo ./$(DEPDIR)/command.Plo \
+ ./$(DEPDIR)/dir.Plo ./$(DEPDIR)/ext.Plo ./$(DEPDIR)/file.Plo \
+ ./$(DEPDIR)/filegui.Plo ./$(DEPDIR)/filemanager.Plo \
+ ./$(DEPDIR)/filenot.Plo ./$(DEPDIR)/fileopctx.Plo \
+ ./$(DEPDIR)/find.Plo ./$(DEPDIR)/hotlist.Plo \
+ ./$(DEPDIR)/info.Plo ./$(DEPDIR)/layout.Plo \
+ ./$(DEPDIR)/mountlist.Plo ./$(DEPDIR)/panel.Plo \
+ ./$(DEPDIR)/panelize.Plo ./$(DEPDIR)/tree.Plo \
+ ./$(DEPDIR)/treestore.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 = $(libmcfilemanager_la_SOURCES)
+DIST_SOURCES = $(am__libmcfilemanager_la_SOURCES_DIST)
+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 = libmcfilemanager.la
+libmcfilemanager_la_SOURCES = achown.c boxes.c boxes.h cd.c cd.h \
+ chmod.c chown.c cmd.c cmd.h command.c command.h dir.c dir.h \
+ ext.c ext.h file.c file.h filegui.c filegui.h filemanager.h \
+ filemanager.c filenot.c filenot.h fileopctx.c fileopctx.h \
+ find.c hotlist.c hotlist.h info.c info.h ioblksize.h layout.c \
+ layout.h mountlist.c mountlist.h panelize.c panelize.h panel.c \
+ panel.h tree.c tree.h treestore.c treestore.h $(am__append_1)
+
+# Unmaintained, unsupported, etc
+# listmode.c listmode.h
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS) $(am__append_2)
+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/filemanager/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/filemanager/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}; \
+ }
+
+libmcfilemanager.la: $(libmcfilemanager_la_OBJECTS) $(libmcfilemanager_la_DEPENDENCIES) $(EXTRA_libmcfilemanager_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmcfilemanager_la_OBJECTS) $(libmcfilemanager_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/achown.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/boxes.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chattr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chmod.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chown.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dir.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filegui.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filemanager.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filenot.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fileopctx.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/find.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hotlist.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/info.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/layout.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mountlist.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/panel.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/panelize.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tree.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/treestore.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)/achown.Plo
+ -rm -f ./$(DEPDIR)/boxes.Plo
+ -rm -f ./$(DEPDIR)/cd.Plo
+ -rm -f ./$(DEPDIR)/chattr.Plo
+ -rm -f ./$(DEPDIR)/chmod.Plo
+ -rm -f ./$(DEPDIR)/chown.Plo
+ -rm -f ./$(DEPDIR)/cmd.Plo
+ -rm -f ./$(DEPDIR)/command.Plo
+ -rm -f ./$(DEPDIR)/dir.Plo
+ -rm -f ./$(DEPDIR)/ext.Plo
+ -rm -f ./$(DEPDIR)/file.Plo
+ -rm -f ./$(DEPDIR)/filegui.Plo
+ -rm -f ./$(DEPDIR)/filemanager.Plo
+ -rm -f ./$(DEPDIR)/filenot.Plo
+ -rm -f ./$(DEPDIR)/fileopctx.Plo
+ -rm -f ./$(DEPDIR)/find.Plo
+ -rm -f ./$(DEPDIR)/hotlist.Plo
+ -rm -f ./$(DEPDIR)/info.Plo
+ -rm -f ./$(DEPDIR)/layout.Plo
+ -rm -f ./$(DEPDIR)/mountlist.Plo
+ -rm -f ./$(DEPDIR)/panel.Plo
+ -rm -f ./$(DEPDIR)/panelize.Plo
+ -rm -f ./$(DEPDIR)/tree.Plo
+ -rm -f ./$(DEPDIR)/treestore.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)/achown.Plo
+ -rm -f ./$(DEPDIR)/boxes.Plo
+ -rm -f ./$(DEPDIR)/cd.Plo
+ -rm -f ./$(DEPDIR)/chattr.Plo
+ -rm -f ./$(DEPDIR)/chmod.Plo
+ -rm -f ./$(DEPDIR)/chown.Plo
+ -rm -f ./$(DEPDIR)/cmd.Plo
+ -rm -f ./$(DEPDIR)/command.Plo
+ -rm -f ./$(DEPDIR)/dir.Plo
+ -rm -f ./$(DEPDIR)/ext.Plo
+ -rm -f ./$(DEPDIR)/file.Plo
+ -rm -f ./$(DEPDIR)/filegui.Plo
+ -rm -f ./$(DEPDIR)/filemanager.Plo
+ -rm -f ./$(DEPDIR)/filenot.Plo
+ -rm -f ./$(DEPDIR)/fileopctx.Plo
+ -rm -f ./$(DEPDIR)/find.Plo
+ -rm -f ./$(DEPDIR)/hotlist.Plo
+ -rm -f ./$(DEPDIR)/info.Plo
+ -rm -f ./$(DEPDIR)/layout.Plo
+ -rm -f ./$(DEPDIR)/mountlist.Plo
+ -rm -f ./$(DEPDIR)/panel.Plo
+ -rm -f ./$(DEPDIR)/panelize.Plo
+ -rm -f ./$(DEPDIR)/tree.Plo
+ -rm -f ./$(DEPDIR)/treestore.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/filemanager/achown.c b/src/filemanager/achown.c
new file mode 100644
index 0000000..dca3eca
--- /dev/null
+++ b/src/filemanager/achown.c
@@ -0,0 +1,1107 @@
+/*
+ Chown-advanced command -- for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ 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 achown.c
+ * \brief Source: Contains functions for advanced chowning
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* XCTRL and ALT macros */
+#include "lib/skin.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+#include "cmd.h" /* advanced_chown_cmd() */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define BX 5
+#define BY 5
+
+#define BUTTONS 9
+#define BUTTONS_PERM 5
+
+#define B_SETALL B_USER
+#define B_SKIP (B_USER + 1)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct
+{
+ unsigned long id;
+ int ret_cmd;
+ button_flags_t flags;
+ int x;
+ int len;
+ const char *text;
+} advanced_chown_but[BUTTONS] =
+{
+ /* *INDENT-OFF* */
+ { 0, B_ENTER, NARROW_BUTTON, 3, 0, " " },
+ { 0, B_ENTER, NARROW_BUTTON, 11, 0, " " },
+ { 0, B_ENTER, NARROW_BUTTON, 19, 0, " " },
+ { 0, B_ENTER, NARROW_BUTTON, 29, 0, "" },
+ { 0, B_ENTER, NARROW_BUTTON, 47, 0, "" },
+
+ { 0, B_SETALL, NORMAL_BUTTON, 0, 0, N_("Set &all") },
+ { 0, B_SKIP, NORMAL_BUTTON, 0, 0, N_("S&kip") },
+ { 0, B_ENTER, DEFPUSH_BUTTON, 0, 0, N_("&Set") },
+ { 0, B_CANCEL, NORMAL_BUTTON, 0, 0, N_("&Cancel") }
+ /* *INDENT-ON* */
+};
+
+static int current_file;
+static gboolean ignore_all;
+
+static WButton *b_att[3]; /* permission */
+static WButton *b_user, *b_group; /* owner */
+static WLabel *l_filename;
+static WLabel *l_mode;
+
+static int flag_pos;
+static int x_toggle;
+static char ch_flags[11];
+static const char ch_perm[] = "rwx";
+static mode_t ch_cmode;
+static struct stat sf_stat;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+advanced_chown_init (void)
+{
+ static gboolean i18n = FALSE;
+ int i;
+
+ if (i18n)
+ return;
+
+ i18n = TRUE;
+
+ for (i = BUTTONS_PERM; i < BUTTONS; i++)
+ {
+#ifdef ENABLE_NLS
+ advanced_chown_but[i].text = _(advanced_chown_but[i].text);
+#endif /* ENABLE_NLS */
+
+ advanced_chown_but[i].len = str_term_width1 (advanced_chown_but[i].text) + 3;
+ if (advanced_chown_but[i].flags == DEFPUSH_BUTTON)
+ advanced_chown_but[i].len += 2; /* "<>" */
+ }
+
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+inc_flag_pos (void)
+{
+ if (flag_pos == 10)
+ {
+ flag_pos = 0;
+ return MSG_NOT_HANDLED;
+ }
+
+ flag_pos++;
+
+ return flag_pos % 3 == 0 ? MSG_NOT_HANDLED : MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+dec_flag_pos (void)
+{
+ if (flag_pos == 0)
+ {
+ flag_pos = 10;
+ return MSG_NOT_HANDLED;
+ }
+
+ flag_pos--;
+
+ return (flag_pos + 1) % 3 == 0 ? MSG_NOT_HANDLED : MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+set_perm_by_flags (char *s, int f_p)
+{
+ int i;
+
+ for (i = 0; i < 3; i++)
+ {
+ if (ch_flags[f_p + i] == '+')
+ s[i] = ch_perm[i];
+ else if (ch_flags[f_p + i] == '-')
+ s[i] = '-';
+ else
+ s[i] = (ch_cmode & (1 << (8 - f_p - i))) != 0 ? ch_perm[i] : '-';
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mode_t
+get_perm (char *s, int base)
+{
+ mode_t m = 0;
+
+ m |= (s[0] == '-') ? 0 :
+ ((s[0] == '+') ? (mode_t) (1 << (base + 2)) : (1 << (base + 2)) & ch_cmode);
+
+ m |= (s[1] == '-') ? 0 :
+ ((s[1] == '+') ? (mode_t) (1 << (base + 1)) : (1 << (base + 1)) & ch_cmode);
+
+ m |= (s[2] == '-') ? 0 : ((s[2] == '+') ? (mode_t) (1 << base) : (1 << base) & ch_cmode);
+
+ return m;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mode_t
+get_mode (void)
+{
+ mode_t m;
+
+ m = ch_cmode ^ (ch_cmode & 0777);
+ m |= get_perm (ch_flags, 6);
+ m |= get_perm (ch_flags + 3, 3);
+ m |= get_perm (ch_flags + 6, 0);
+
+ return m;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+update_permissions (void)
+{
+ set_perm_by_flags (b_att[0]->text.start, 0);
+ set_perm_by_flags (b_att[1]->text.start, 3);
+ set_perm_by_flags (b_att[2]->text.start, 6);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+update_ownership (void)
+{
+ button_set_text (b_user, get_owner (sf_stat.st_uid));
+ button_set_text (b_group, get_group (sf_stat.st_gid));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+print_flags (const WDialog * h)
+{
+ int i;
+
+ tty_setcolor (COLOR_NORMAL);
+
+ for (i = 0; i < 3; i++)
+ {
+ widget_gotoyx (h, BY + 1, advanced_chown_but[0].x + 6 + i);
+ tty_print_char (ch_flags[i]);
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ widget_gotoyx (h, BY + 1, advanced_chown_but[1].x + 6 + i);
+ tty_print_char (ch_flags[i + 3]);
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ widget_gotoyx (h, BY + 1, advanced_chown_but[2].x + 6 + i);
+ tty_print_char (ch_flags[i + 6]);
+ }
+
+ update_permissions ();
+
+ for (i = 0; i < 15; i++)
+ {
+ widget_gotoyx (h, BY + 1, advanced_chown_but[3].x + 6 + i);
+ tty_print_char (ch_flags[9]);
+ }
+ for (i = 0; i < 15; i++)
+ {
+ widget_gotoyx (h, BY + 1, advanced_chown_but[4].x + 6 + i);
+ tty_print_char (ch_flags[10]);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+advanced_chown_refresh (const WDialog * h)
+{
+ tty_setcolor (COLOR_NORMAL);
+
+ widget_gotoyx (h, BY - 1, advanced_chown_but[0].x + 5);
+ tty_print_string (_("owner"));
+ widget_gotoyx (h, BY - 1, advanced_chown_but[1].x + 5);
+ tty_print_string (_("group"));
+ widget_gotoyx (h, BY - 1, advanced_chown_but[2].x + 5);
+ tty_print_string (_("other"));
+
+ widget_gotoyx (h, BY - 1, advanced_chown_but[3].x + 5);
+ tty_print_string (_("owner"));
+ widget_gotoyx (h, BY - 1, advanced_chown_but[4].x + 5);
+ tty_print_string (_("group"));
+
+ widget_gotoyx (h, BY + 1, 3);
+ tty_print_string (_("Flag"));
+ print_flags (h);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+advanced_chown_info_update (void)
+{
+ /* mode */
+ label_set_textv (l_mode, _("Permissions (octal): %o"), get_mode ());
+
+ /* permissions */
+ update_permissions ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+update_mode (WGroup * g)
+{
+ print_flags (DIALOG (g));
+ advanced_chown_info_update ();
+ widget_set_state (WIDGET (g->current->data), WST_FOCUSED, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+perm_button_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WButton *b = BUTTON (w);
+ WGroup *g = w->owner;
+ int i = 0;
+ int f_pos;
+
+ /* one of permission buttons */
+ if (b == b_att[0])
+ f_pos = 0;
+ else if (b == b_att[1])
+ f_pos = 1;
+ else /* if (w == b_att [1] */
+ f_pos = 2;
+
+ switch (msg)
+ {
+ case MSG_FOCUS:
+ if (b->hotpos == -1)
+ b->hotpos = 0;
+
+ flag_pos = f_pos * 3 + b->hotpos;
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ switch (parm)
+ {
+ case '*':
+ parm = '=';
+ MC_FALLTHROUGH;
+
+ case '-':
+ case '=':
+ case '+':
+ flag_pos = f_pos * 3 + b->hotpos;
+ ch_flags[flag_pos] = parm;
+ update_mode (g);
+ send_message (w, NULL, MSG_KEY, KEY_RIGHT, NULL);
+ if (b->hotpos == 2)
+ group_select_next_widget (g);
+ break;
+
+ case XCTRL ('f'):
+ case KEY_RIGHT:
+ {
+ cb_ret_t ret;
+
+ ret = inc_flag_pos ();
+ b->hotpos = flag_pos % 3;
+ return ret;
+ }
+
+ case XCTRL ('b'):
+ case KEY_LEFT:
+ {
+ cb_ret_t ret;
+
+ ret = dec_flag_pos ();
+ b->hotpos = flag_pos % 3;
+ return ret;
+ }
+
+ case 'x':
+ i++;
+ MC_FALLTHROUGH;
+
+ case 'w':
+ i++;
+ MC_FALLTHROUGH;
+
+ case 'r':
+ b->hotpos = i;
+ MC_FALLTHROUGH;
+
+ case ' ':
+ i = b->hotpos;
+
+ flag_pos = f_pos * 3 + i;
+ if (b->text.start[flag_pos % 3] == '-')
+ ch_flags[flag_pos] = '+';
+ else
+ ch_flags[flag_pos] = '-';
+ update_mode (w->owner);
+ break;
+
+ case '4':
+ i++;
+ MC_FALLTHROUGH;
+
+ case '2':
+ i++;
+ MC_FALLTHROUGH;
+
+ case '1':
+ b->hotpos = i;
+ flag_pos = f_pos * 3 + i;
+ ch_flags[flag_pos] = '=';
+ update_mode (g);
+ break;
+
+ default:
+ break;
+ }
+ /* continue key handling in the dialog level */
+ return MSG_NOT_HANDLED;
+
+ default:
+ return button_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+perm_button_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ /* place cursor on flag that is being modified */
+ BUTTON (w)->hotpos = CLAMP (event->x - 1, 0, 2);
+ MC_FALLTHROUGH;
+
+ default:
+ button_mouse_default_callback (w, msg, event);
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WButton *
+perm_button_new (int y, int x, int action, button_flags_t flags, const char *text,
+ bcback_fn callback)
+{
+ WButton *b;
+ Widget *w;
+
+ /* create base button using native API */
+ b = button_new (y, x, action, flags, text, callback);
+ w = WIDGET (b);
+
+ /* we don't want HOTKEY */
+ widget_want_hotkey (w, FALSE);
+
+ w->callback = perm_button_callback;
+ w->mouse_callback = perm_button_mouse_callback;
+
+ return b;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chl_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_KEY:
+ switch (parm)
+ {
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ {
+ WDialog *h = DIALOG (w);
+
+ h->ret_value = parm;
+ dlg_close (h);
+ }
+ break;
+ default:
+ break;
+ }
+ MC_FALLTHROUGH;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+user_group_button_cb (WButton * button, int action)
+{
+ Widget *w = WIDGET (button);
+ int f_pos;
+ gboolean chl_end;
+
+ (void) action;
+
+ if (button == b_user)
+ f_pos = BUTTONS_PERM - 2;
+ else if (button == b_group)
+ f_pos = BUTTONS_PERM - 1;
+ else
+ return 0; /* do nothing */
+
+ do
+ {
+ WGroup *g = w->owner;
+ WDialog *h = DIALOG (g);
+ Widget *wh = WIDGET (h);
+
+ gboolean is_owner = (f_pos == BUTTONS_PERM - 2);
+ const char *title;
+ int lxx, b_current;
+ WDialog *chl_dlg;
+ WListbox *chl_list;
+ int result;
+ int fe;
+ struct passwd *chl_pass;
+ struct group *chl_grp;
+
+ chl_end = FALSE;
+
+ if (is_owner)
+ {
+ title = _("owner");
+ lxx = WIDGET (b_user)->rect.x + 1;
+ }
+ else
+ {
+ title = _("group");
+ lxx = WIDGET (b_group)->rect.x + 1;
+ }
+
+ chl_dlg =
+ dlg_create (TRUE, wh->rect.y - 1, lxx, wh->rect.lines + 2, 17, WPOS_KEEP_DEFAULT, TRUE,
+ dialog_colors, chl_callback, NULL, "[Advanced Chown]", title);
+
+ /* get new listboxes */
+ chl_list =
+ listbox_new (1, 1, WIDGET (chl_dlg)->rect.lines - 2, WIDGET (chl_dlg)->rect.cols - 2,
+ FALSE, NULL);
+ listbox_add_item (chl_list, LISTBOX_APPEND_AT_END, 0, "<Unknown>", NULL, FALSE);
+ if (is_owner)
+ {
+ /* get and put user names in the listbox */
+ setpwent ();
+ while ((chl_pass = getpwent ()) != NULL)
+ listbox_add_item (chl_list, LISTBOX_APPEND_SORTED, 0, chl_pass->pw_name, NULL,
+ FALSE);
+ endpwent ();
+ fe = listbox_search_text (chl_list, get_owner (sf_stat.st_uid));
+ }
+ else
+ {
+ /* get and put group names in the listbox */
+ setgrent ();
+ while ((chl_grp = getgrent ()) != NULL)
+ listbox_add_item (chl_list, LISTBOX_APPEND_SORTED, 0, chl_grp->gr_name, NULL,
+ FALSE);
+ endgrent ();
+ fe = listbox_search_text (chl_list, get_group (sf_stat.st_gid));
+ }
+
+ listbox_set_current (chl_list, fe);
+
+ b_current = chl_list->current;
+ group_add_widget (GROUP (chl_dlg), chl_list);
+
+ result = dlg_run (chl_dlg);
+
+ if (result != B_CANCEL)
+ {
+ if (b_current != chl_list->current)
+ {
+ gboolean ok = FALSE;
+ char *text;
+
+ listbox_get_current (chl_list, &text, NULL);
+ if (is_owner)
+ {
+ chl_pass = getpwnam (text);
+ if (chl_pass != NULL)
+ {
+ sf_stat.st_uid = chl_pass->pw_uid;
+ ok = TRUE;
+ }
+ }
+ else
+ {
+ chl_grp = getgrnam (text);
+ if (chl_grp != NULL)
+ {
+ sf_stat.st_gid = chl_grp->gr_gid;
+ ok = TRUE;
+ }
+ }
+
+ if (!ok)
+ group_select_current_widget (g);
+ else
+ {
+ ch_flags[f_pos + 6] = '+';
+ update_ownership ();
+ group_select_current_widget (g);
+ print_flags (h);
+ }
+ }
+
+ if (result == KEY_LEFT)
+ {
+ if (!is_owner)
+ chl_end = TRUE;
+ group_select_prev_widget (g);
+ f_pos--;
+ }
+ else if (result == KEY_RIGHT)
+ {
+ if (is_owner)
+ chl_end = TRUE;
+ group_select_next_widget (g);
+ f_pos++;
+ }
+ }
+
+ /* Here we used to redraw the window */
+ widget_destroy (WIDGET (chl_dlg));
+ }
+ while (chl_end);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+advanced_chown_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_DRAW:
+ frame_callback (w, NULL, MSG_DRAW, 0, NULL);
+ advanced_chown_refresh (DIALOG (w->owner));
+ advanced_chown_info_update ();
+ return MSG_HANDLED;
+
+ default:
+ return frame_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+advanced_chown_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WGroup *g = GROUP (w);
+ int i = 0;
+
+ switch (msg)
+ {
+ case MSG_KEY:
+ switch (parm)
+ {
+ case ALT ('x'):
+ i++;
+ MC_FALLTHROUGH;
+
+ case ALT ('w'):
+ i++;
+ MC_FALLTHROUGH;
+
+ case ALT ('r'):
+ parm = i + 3;
+ for (i = 0; i < 3; i++)
+ ch_flags[i * 3 + parm - 3] = (x_toggle & (1 << parm)) ? '-' : '+';
+ x_toggle ^= (1 << parm);
+ update_mode (g);
+ widget_draw (w);
+ break;
+
+ case XCTRL ('x'):
+ i++;
+ MC_FALLTHROUGH;
+
+ case XCTRL ('w'):
+ i++;
+ MC_FALLTHROUGH;
+
+ case XCTRL ('r'):
+ parm = i;
+ for (i = 0; i < 3; i++)
+ ch_flags[i * 3 + parm] = (x_toggle & (1 << parm)) ? '-' : '+';
+ x_toggle ^= (1 << parm);
+ update_mode (g);
+ widget_draw (w);
+ break;
+
+ default:
+ break;
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WDialog *
+advanced_chown_dlg_create (WPanel * panel)
+{
+ gboolean single_set;
+ WDialog *ch_dlg;
+ WGroup *ch_grp;
+ int lines = 12;
+ int cols = 74;
+ int i;
+ int y;
+
+ memset (ch_flags, '=', 11);
+ flag_pos = 0;
+ x_toggle = 070;
+
+ single_set = (panel->marked < 2);
+ if (!single_set)
+ lines += 2;
+
+ ch_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors,
+ advanced_chown_callback, NULL, "[Advanced Chown]", _("Chown advanced command"));
+ ch_grp = GROUP (ch_dlg);
+
+ /* draw background */
+ ch_dlg->bg->callback = advanced_chown_bg_callback;
+
+ l_filename = label_new (2, 3, NULL);
+ group_add_widget (ch_grp, l_filename);
+
+ group_add_widget (ch_grp, hline_new (3, -1, -1));
+
+#define XTRACT(i,y,cb) y, BX+advanced_chown_but[i].x, \
+ advanced_chown_but[i].ret_cmd, advanced_chown_but[i].flags, \
+ (advanced_chown_but[i].text), cb
+ b_att[0] = perm_button_new (XTRACT (0, BY, NULL));
+ advanced_chown_but[0].id = group_add_widget (ch_grp, b_att[0]);
+ b_att[1] = perm_button_new (XTRACT (1, BY, NULL));
+ advanced_chown_but[1].id = group_add_widget (ch_grp, b_att[1]);
+ b_att[2] = perm_button_new (XTRACT (2, BY, NULL));
+ advanced_chown_but[2].id = group_add_widget (ch_grp, b_att[2]);
+ b_user = button_new (XTRACT (3, BY, user_group_button_cb));
+ advanced_chown_but[3].id = group_add_widget (ch_grp, b_user);
+ b_group = button_new (XTRACT (4, BY, user_group_button_cb));
+ advanced_chown_but[4].id = group_add_widget (ch_grp, b_group);
+
+ l_mode = label_new (BY + 2, 3, NULL);
+ group_add_widget (ch_grp, l_mode);
+
+ y = BY + 3;
+ if (!single_set)
+ {
+ i = BUTTONS_PERM;
+ group_add_widget (ch_grp, hline_new (y++, -1, -1));
+ advanced_chown_but[i].id = group_add_widget (ch_grp,
+ button_new (y,
+ WIDGET (ch_dlg)->rect.cols / 2 -
+ advanced_chown_but[i].len,
+ advanced_chown_but[i].ret_cmd,
+ advanced_chown_but[i].flags,
+ advanced_chown_but[i].text, NULL));
+ i++;
+ advanced_chown_but[i].id = group_add_widget (ch_grp,
+ button_new (y,
+ WIDGET (ch_dlg)->rect.cols / 2 + 1,
+ advanced_chown_but[i].ret_cmd,
+ advanced_chown_but[i].flags,
+ advanced_chown_but[i].text, NULL));
+ y++;
+ }
+
+ i = BUTTONS_PERM + 2;
+ group_add_widget (ch_grp, hline_new (y++, -1, -1));
+ advanced_chown_but[i].id = group_add_widget (ch_grp,
+ button_new (y,
+ WIDGET (ch_dlg)->rect.cols / 2 -
+ advanced_chown_but[i].len,
+ advanced_chown_but[i].ret_cmd,
+ advanced_chown_but[i].flags,
+ advanced_chown_but[i].text, NULL));
+ i++;
+ advanced_chown_but[i].id = group_add_widget (ch_grp,
+ button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1,
+ advanced_chown_but[i].ret_cmd,
+ advanced_chown_but[i].flags,
+ advanced_chown_but[i].text, NULL));
+
+ widget_select (WIDGET (b_att[0]));
+
+ return ch_dlg;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+advanced_chown_done (gboolean need_update)
+{
+ if (need_update)
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const GString *
+next_file (const WPanel * panel)
+{
+ while (panel->dir.list[current_file].f.marked == 0)
+ current_file++;
+
+ return panel->dir.list[current_file].fname;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+try_advanced_chown (const vfs_path_t * p, mode_t m, uid_t u, gid_t g)
+{
+ int chmod_result;
+ const char *fname = NULL;
+
+ while ((chmod_result = mc_chmod (p, m)) == -1 && !ignore_all)
+ {
+ int my_errno = errno;
+ int result;
+ char *msg;
+
+ if (fname == NULL)
+ fname = x_basename (vfs_path_as_str (p));
+ msg = g_strdup_printf (_("Cannot chmod \"%s\"\n%s"), fname, unix_error_string (my_errno));
+ result =
+ query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
+ _("&Cancel"));
+ g_free (msg);
+
+ switch (result)
+ {
+ case 0:
+ /* call mc_chown() only, if mc_chmod() didn't fail */
+ return TRUE;
+
+ case 1:
+ ignore_all = TRUE;
+ /* call mc_chown() only, if mc_chmod() didn't fail */
+ return TRUE;
+
+ case 2:
+ /* retry chmod of this file */
+ break;
+
+ case 3:
+ default:
+ /* stop remain files processing */
+ return FALSE;
+ }
+ }
+
+ /* call mc_chown() only, if mc_chmod didn't fail */
+ while (chmod_result != -1 && mc_chown (p, u, g) == -1 && !ignore_all)
+ {
+ int my_errno = errno;
+ int result;
+ char *msg;
+
+ if (fname == NULL)
+ fname = x_basename (vfs_path_as_str (p));
+ msg = g_strdup_printf (_("Cannot chown \"%s\"\n%s"), fname, unix_error_string (my_errno));
+ result =
+ query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
+ _("&Cancel"));
+ g_free (msg);
+
+ switch (result)
+ {
+ case 0:
+ /* try next file */
+ return TRUE;
+
+ case 1:
+ ignore_all = TRUE;
+ /* try next file */
+ return TRUE;
+
+ case 2:
+ /* retry chown of this file */
+ break;
+
+ case 3:
+ default:
+ /* stop remain files processing */
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+do_advanced_chown (WPanel * panel, const vfs_path_t * p, mode_t m, uid_t u, gid_t g)
+{
+ gboolean ret;
+
+ ret = try_advanced_chown (p, m, u, g);
+
+ do_file_mark (panel, current_file, 0);
+
+ return ret;
+}
+
+ /* --------------------------------------------------------------------------------------------- */
+
+static void
+apply_advanced_chowns (WPanel * panel, vfs_path_t * vpath, struct stat *sf)
+{
+ gid_t a_gid = sf->st_gid;
+ uid_t a_uid = sf->st_uid;
+ gboolean ok;
+
+ if (!do_advanced_chown (panel, vpath, get_mode (),
+ (ch_flags[9] == '+') ? a_uid : (uid_t) (-1),
+ (ch_flags[10] == '+') ? a_gid : (gid_t) (-1)))
+ return;
+
+ do
+ {
+ const GString *fname;
+
+ fname = next_file (panel);
+ vpath = vfs_path_from_str (fname->str);
+ ok = (mc_stat (vpath, sf) == 0);
+
+ if (!ok)
+ {
+ /* if current file was deleted outside mc -- try next file */
+ /* decrease panel->marked */
+ do_file_mark (panel, current_file, 0);
+
+ /* try next file */
+ ok = TRUE;
+ }
+ else
+ {
+ ch_cmode = sf->st_mode;
+
+ ok = do_advanced_chown (panel, vpath, get_mode (),
+ (ch_flags[9] == '+') ? a_uid : (uid_t) (-1),
+ (ch_flags[10] == '+') ? a_gid : (gid_t) (-1));
+ }
+
+ vfs_path_free (vpath, TRUE);
+ }
+ while (ok && panel->marked != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+advanced_chown_cmd (WPanel * panel)
+{
+ gboolean need_update;
+ gboolean end_chown;
+
+ /* Number of files at startup */
+ int files_on_begin;
+
+ files_on_begin = MAX (1, panel->marked);
+
+ advanced_chown_init ();
+
+ current_file = 0;
+ ignore_all = FALSE;
+
+ do
+ { /* do while any files remaining */
+ vfs_path_t *vpath;
+ WDialog *ch_dlg;
+ const GString *fname;
+ int result;
+ int file_idx;
+
+ do_refresh ();
+
+ need_update = FALSE;
+ end_chown = FALSE;
+
+ if (panel->marked != 0)
+ fname = next_file (panel); /* next marked file */
+ else
+ fname = panel_current_entry (panel)->fname; /* single file */
+
+ vpath = vfs_path_from_str (fname->str);
+
+ if (mc_stat (vpath, &sf_stat) != 0)
+ {
+ vfs_path_free (vpath, TRUE);
+ break;
+ }
+
+ ch_cmode = sf_stat.st_mode;
+
+ ch_dlg = advanced_chown_dlg_create (panel);
+
+ file_idx = files_on_begin == 1 ? 1 : (files_on_begin - panel->marked + 1);
+ label_set_textv (l_filename, "%s (%d/%d)",
+ str_fit_to_term (fname->str, WIDGET (ch_dlg)->rect.cols - 20, J_LEFT_FIT),
+ file_idx, files_on_begin);
+ update_ownership ();
+
+ result = dlg_run (ch_dlg);
+
+ switch (result)
+ {
+ case B_CANCEL:
+ end_chown = TRUE;
+ break;
+
+ case B_ENTER:
+ {
+ uid_t uid = ch_flags[9] == '+' ? sf_stat.st_uid : (uid_t) (-1);
+ gid_t gid = ch_flags[10] == '+' ? sf_stat.st_gid : (gid_t) (-1);
+
+ if (panel->marked <= 1)
+ {
+ /* single or last file */
+ if (mc_chmod (vpath, get_mode ()) == -1)
+ message (D_ERROR, MSG_ERROR, _("Cannot chmod \"%s\"\n%s"),
+ fname->str, unix_error_string (errno));
+ /* call mc_chown only, if mc_chmod didn't fail */
+ else if (mc_chown (vpath, uid, gid) == -1)
+ message (D_ERROR, MSG_ERROR, _("Cannot chown \"%s\"\n%s"), fname->str,
+ unix_error_string (errno));
+
+ end_chown = TRUE;
+ }
+ else if (!try_advanced_chown (vpath, get_mode (), uid, gid))
+ {
+ /* stop multiple files processing */
+ result = B_CANCEL;
+ end_chown = TRUE;
+ }
+
+ need_update = TRUE;
+ break;
+ }
+
+ case B_SETALL:
+ apply_advanced_chowns (panel, vpath, &sf_stat);
+ need_update = TRUE;
+ end_chown = TRUE;
+ break;
+
+ case B_SKIP:
+ default:
+ break;
+ }
+
+ if (panel->marked != 0 && result != B_CANCEL)
+ {
+ do_file_mark (panel, current_file, 0);
+ need_update = TRUE;
+ }
+
+ vfs_path_free (vpath, TRUE);
+
+ widget_destroy (WIDGET (ch_dlg));
+ }
+ while (panel->marked != 0 && !end_chown);
+
+ advanced_chown_done (need_update);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/boxes.c b/src/filemanager/boxes.c
new file mode 100644
index 0000000..e091c95
--- /dev/null
+++ b/src/filemanager/boxes.c
@@ -0,0 +1,1343 @@
+/*
+ Some misc dialog boxes for the program.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ 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 boxes.c
+ * \brief Source: Some misc dialog boxes for the program
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h" /* tty_use_colors() */
+#include "lib/tty/key.h" /* XCTRL and ALT macros */
+#include "lib/skin.h" /* INPUT_COLOR */
+#include "lib/mcconfig.h" /* Load/save user formats */
+#include "lib/strutil.h"
+
+#include "lib/vfs/vfs.h"
+#ifdef ENABLE_VFS_FTP
+#include "src/vfs/ftpfs/ftpfs.h"
+#endif /* ENABLE_VFS_FTP */
+
+#include "lib/util.h" /* Q_() */
+#include "lib/widget.h"
+
+#include "src/setup.h"
+#include "src/history.h" /* MC_HISTORY_ESC_TIMEOUT */
+#include "src/execute.h" /* pause_after_run */
+#ifdef ENABLE_BACKGROUND
+#include "src/background.h" /* task_list */
+#endif
+
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#include "src/selcodepage.h"
+#endif
+
+#include "command.h" /* For cmdline */
+#include "dir.h"
+#include "tree.h"
+#include "layout.h" /* for get_nth_panel_name proto */
+#include "filemanager.h"
+
+#include "boxes.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifdef ENABLE_BACKGROUND
+#define B_STOP (B_USER+1)
+#define B_RESUME (B_USER+2)
+#define B_KILL (B_USER+3)
+#endif /* ENABLE_BACKGROUND */
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static unsigned long configure_old_esc_mode_id, configure_time_out_id;
+
+/* Index in list_formats[] for "brief" */
+static const int panel_list_brief_idx = 1;
+/* Index in list_formats[] for "user defined" */
+static const int panel_list_user_idx = 3;
+
+static char **status_format;
+static unsigned long panel_list_formats_id, panel_user_format_id, panel_brief_cols_id;
+static unsigned long user_mini_status_id, mini_user_format_id;
+
+#ifdef HAVE_CHARSET
+static int new_display_codepage;
+#endif /* HAVE_CHARSET */
+
+#if defined(ENABLE_VFS) && defined(ENABLE_VFS_FTP)
+static unsigned long ftpfs_always_use_proxy_id, ftpfs_proxy_host_id;
+#endif /* ENABLE_VFS && ENABLE_VFS_FTP */
+
+static GPtrArray *skin_names;
+static gchar *current_skin_name;
+
+#ifdef ENABLE_BACKGROUND
+static WListbox *bg_list = NULL;
+#endif /* ENABLE_BACKGROUND */
+
+static unsigned long shadows_id;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+configure_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_NOTIFY:
+ /* message from "Single press" checkbutton */
+ if (sender != NULL && sender->id == configure_old_esc_mode_id)
+ {
+ const gboolean not_single = !CHECK (sender)->state;
+ Widget *ww;
+
+ /* input line */
+ ww = widget_find_by_id (w, configure_time_out_id);
+ widget_disable (ww, not_single);
+
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+skin_apply (const gchar * skin_override)
+{
+ GError *mcerror = NULL;
+
+ mc_skin_deinit ();
+ mc_skin_init (skin_override, &mcerror);
+ mc_fhl_free (&mc_filehighlight);
+ mc_filehighlight = mc_fhl_new (TRUE);
+ dlg_set_default_colors ();
+ input_set_default_colors ();
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ command_set_default_colors ();
+ panel_deinit ();
+ panel_init ();
+ repaint_screen ();
+
+ mc_error_message (&mcerror, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const gchar *
+skin_name_to_label (const gchar * name)
+{
+ if (strcmp (name, "default") == 0)
+ return _("< Default >");
+ return name;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+skin_dlg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_RESIZE:
+ {
+ WDialog *d = DIALOG (w);
+ const WRect *wd = &WIDGET (d->data.p)->rect;
+ WRect r = w->rect;
+
+ r.y = wd->y + (wd->lines - r.lines) / 2;
+ r.x = wd->x + wd->cols / 2;
+
+ return dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
+ }
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sel_skin_button (WButton * button, int action)
+{
+ int result;
+ WListbox *skin_list;
+ WDialog *skin_dlg;
+ const gchar *skin_name;
+ unsigned int i;
+ unsigned int pos = 1;
+
+ (void) action;
+
+ skin_dlg =
+ dlg_create (TRUE, 0, 0, 13, 24, WPOS_KEEP_DEFAULT, TRUE, dialog_colors, skin_dlg_callback,
+ NULL, "[Appearance]", _("Skins"));
+ /* use Appearance dialog for positioning */
+ skin_dlg->data.p = WIDGET (button)->owner;
+
+ /* set dialog location before all */
+ send_message (skin_dlg, NULL, MSG_RESIZE, 0, NULL);
+
+ skin_list = listbox_new (1, 1, 11, 22, FALSE, NULL);
+ skin_name = "default";
+ listbox_add_item (skin_list, LISTBOX_APPEND_AT_END, 0, skin_name_to_label (skin_name),
+ (void *) skin_name, FALSE);
+
+ if (strcmp (skin_name, current_skin_name) == 0)
+ listbox_set_current (skin_list, 0);
+
+ for (i = 0; i < skin_names->len; i++)
+ {
+ skin_name = g_ptr_array_index (skin_names, i);
+ if (strcmp (skin_name, "default") != 0)
+ {
+ listbox_add_item (skin_list, LISTBOX_APPEND_AT_END, 0, skin_name_to_label (skin_name),
+ (void *) skin_name, FALSE);
+ if (strcmp (skin_name, current_skin_name) == 0)
+ listbox_set_current (skin_list, pos);
+ pos++;
+ }
+ }
+
+ /* make list stick to all sides of dialog, effectively make it be resized with dialog */
+ group_add_widget_autopos (GROUP (skin_dlg), skin_list, WPOS_KEEP_ALL, NULL);
+
+ result = dlg_run (skin_dlg);
+ if (result == B_ENTER)
+ {
+ gchar *skin_label;
+
+ listbox_get_current (skin_list, &skin_label, (void **) &skin_name);
+ g_free (current_skin_name);
+ current_skin_name = g_strdup (skin_name);
+ skin_apply (skin_name);
+
+ button_set_text (button, str_fit_to_term (skin_label, 20, J_LEFT_FIT));
+ }
+ widget_destroy (WIDGET (skin_dlg));
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+appearance_box_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_INIT:
+#ifdef ENABLE_SHADOWS
+ if (!tty_use_colors ())
+#endif
+ {
+ Widget *shadow;
+
+ shadow = widget_find_by_id (w, shadows_id);
+ CHECK (shadow)->state = FALSE;
+ widget_disable (shadow, TRUE);
+ }
+ return MSG_HANDLED;
+
+ case MSG_NOTIFY:
+ if (sender != NULL && sender->id == shadows_id)
+ {
+ mc_global.tty.shadows = CHECK (sender)->state;
+ repaint_screen ();
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+panel_listing_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_NOTIFY:
+ if (sender != NULL && sender->id == panel_list_formats_id)
+ {
+ WCheck *ch;
+ WInput *in1, *in2, *in3;
+
+ in1 = INPUT (widget_find_by_id (w, panel_user_format_id));
+ in2 = INPUT (widget_find_by_id (w, panel_brief_cols_id));
+ ch = CHECK (widget_find_by_id (w, user_mini_status_id));
+ in3 = INPUT (widget_find_by_id (w, mini_user_format_id));
+
+ if (!ch->state)
+ input_assign_text (in3, status_format[RADIO (sender)->sel]);
+ input_update (in1, FALSE);
+ input_update (in2, FALSE);
+ input_update (in3, FALSE);
+ widget_disable (WIDGET (in1), RADIO (sender)->sel != panel_list_user_idx);
+ widget_disable (WIDGET (in2), RADIO (sender)->sel != panel_list_brief_idx);
+ return MSG_HANDLED;
+ }
+
+ if (sender != NULL && sender->id == user_mini_status_id)
+ {
+ WInput *in;
+
+ in = INPUT (widget_find_by_id (w, mini_user_format_id));
+
+ if (CHECK (sender)->state)
+ {
+ widget_disable (WIDGET (in), FALSE);
+ input_assign_text (in, status_format[3]);
+ }
+ else
+ {
+ WRadio *r;
+
+ r = RADIO (widget_find_by_id (w, panel_list_formats_id));
+ widget_disable (WIDGET (in), TRUE);
+ input_assign_text (in, status_format[r->sel]);
+ }
+ /* input_update (in, FALSE); */
+ return MSG_HANDLED;
+ }
+
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+static int
+sel_charset_button (WButton * button, int action)
+{
+ int new_dcp;
+
+ (void) action;
+
+ new_dcp = select_charset (-1, -1, new_display_codepage, TRUE);
+
+ if (new_dcp != SELECT_CHARSET_CANCEL)
+ {
+ const char *cpname;
+
+ new_display_codepage = new_dcp;
+ cpname = (new_display_codepage == SELECT_CHARSET_OTHER_8BIT) ?
+ _("Other 8 bit") :
+ ((codepage_desc *) g_ptr_array_index (codepages, new_display_codepage))->name;
+ if (cpname != NULL)
+ mc_global.utf8_display = str_isutf8 (cpname);
+ else
+ cpname = _("7-bit ASCII"); /* FIXME */
+
+ button_set_text (button, cpname);
+ widget_draw (WIDGET (WIDGET (button)->owner));
+ }
+
+ return 0;
+}
+#endif /* HAVE_CHARSET */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+tree_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_RESIZE:
+ {
+ WRect r = w->rect;
+ Widget *bar;
+
+ r.lines = LINES - 9;
+ r.cols = COLS - 20;
+ dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
+
+ bar = WIDGET (buttonbar_find (h));
+ bar->rect.x = 0;
+ bar->rect.y = LINES - 1;
+ return MSG_HANDLED;
+ }
+
+ case MSG_ACTION:
+ return send_message (find_tree (h), NULL, MSG_ACTION, parm, NULL);
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if defined(ENABLE_VFS) && defined (ENABLE_VFS_FTP)
+static cb_ret_t
+confvfs_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_NOTIFY:
+ /* message from "Always use ftp proxy" checkbutton */
+ if (sender != NULL && sender->id == ftpfs_always_use_proxy_id)
+ {
+ const gboolean not_use = !CHECK (sender)->state;
+ Widget *wi;
+
+ /* input */
+ wi = widget_find_by_id (w, ftpfs_proxy_host_id);
+ widget_disable (wi, not_use);
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+#endif /* ENABLE_VFS && ENABLE_VFS_FTP */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_BACKGROUND
+static void
+jobs_fill_listbox (WListbox * list)
+{
+ static const char *state_str[2] = { "", "" };
+ TaskList *tl;
+
+ if (state_str[0][0] == '\0')
+ {
+ state_str[0] = _("Running");
+ state_str[1] = _("Stopped");
+ }
+
+ for (tl = task_list; tl != NULL; tl = tl->next)
+ {
+ char *s;
+
+ s = g_strconcat (state_str[tl->state], " ", tl->info, (char *) NULL);
+ listbox_add_item (list, LISTBOX_APPEND_AT_END, 0, s, (void *) tl, FALSE);
+ g_free (s);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+task_cb (WButton * button, int action)
+{
+ TaskList *tl;
+ int sig = 0;
+
+ (void) button;
+
+ if (bg_list->list == NULL)
+ return 0;
+
+ /* Get this instance information */
+ listbox_get_current (bg_list, NULL, (void **) &tl);
+
+#ifdef SIGTSTP
+ if (action == B_STOP)
+ {
+ sig = SIGSTOP;
+ tl->state = Task_Stopped;
+ }
+ else if (action == B_RESUME)
+ {
+ sig = SIGCONT;
+ tl->state = Task_Running;
+ }
+ else
+#endif
+ if (action == B_KILL)
+ sig = SIGKILL;
+
+ if (sig == SIGKILL)
+ unregister_task_running (tl->pid, tl->fd);
+
+ kill (tl->pid, sig);
+ listbox_remove_list (bg_list);
+ jobs_fill_listbox (bg_list);
+
+ /* This can be optimized to just redraw this widget :-) */
+ widget_draw (WIDGET (WIDGET (button)->owner));
+
+ return 0;
+}
+#endif /* ENABLE_BACKGROUND */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+configure_box (void)
+{
+ const char *pause_options[] = {
+ N_("&Never"),
+ N_("On dum&b terminals"),
+ N_("Alwa&ys")
+ };
+
+ int pause_options_num;
+
+ pause_options_num = G_N_ELEMENTS (pause_options);
+
+ {
+ char time_out[BUF_TINY] = "";
+ char *time_out_new;
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_COLUMNS,
+ QUICK_START_GROUPBOX (N_("File operations")),
+ QUICK_CHECKBOX (N_("&Verbose operation"), &verbose, NULL),
+ QUICK_CHECKBOX (N_("Compute tota&ls"), &file_op_compute_totals, NULL),
+ QUICK_CHECKBOX (N_("Classic pro&gressbar"), &classic_progressbar, NULL),
+ QUICK_CHECKBOX (N_("Mkdi&r autoname"), &auto_fill_mkdir_name, NULL),
+ QUICK_CHECKBOX (N_("&Preallocate space"), &mc_global.vfs.preallocate_space,
+ NULL),
+ QUICK_STOP_GROUPBOX,
+ QUICK_START_GROUPBOX (N_("Esc key mode")),
+ QUICK_CHECKBOX (N_("S&ingle press"), &old_esc_mode, &configure_old_esc_mode_id),
+ QUICK_LABELED_INPUT (N_("Timeout:"), input_label_left,
+ (const char *) time_out, MC_HISTORY_ESC_TIMEOUT,
+ &time_out_new, &configure_time_out_id, FALSE, FALSE,
+ INPUT_COMPLETE_NONE),
+ QUICK_STOP_GROUPBOX,
+ QUICK_START_GROUPBOX (N_("Pause after run")),
+ QUICK_RADIO (pause_options_num, pause_options, &pause_after_run, NULL),
+ QUICK_STOP_GROUPBOX,
+ QUICK_NEXT_COLUMN,
+ QUICK_START_GROUPBOX (N_("Other options")),
+ QUICK_CHECKBOX (N_("Use internal edi&t"), &use_internal_edit, NULL),
+ QUICK_CHECKBOX (N_("Use internal vie&w"), &use_internal_view, NULL),
+ QUICK_CHECKBOX (N_("A&sk new file name"),
+ &editor_ask_filename_before_edit, NULL),
+ QUICK_CHECKBOX (N_("Auto m&enus"), &auto_menu, NULL),
+ QUICK_CHECKBOX (N_("&Drop down menus"), &drop_menus, NULL),
+ QUICK_CHECKBOX (N_("S&hell patterns"), &easy_patterns, NULL),
+ QUICK_CHECKBOX (N_("Co&mplete: show all"),
+ &mc_global.widget.show_all_if_ambiguous, NULL),
+ QUICK_CHECKBOX (N_("Rotating d&ash"), &nice_rotating_dash, NULL),
+ QUICK_CHECKBOX (N_("Cd follows lin&ks"), &mc_global.vfs.cd_symlinks, NULL),
+ QUICK_CHECKBOX (N_("Sa&fe delete"), &safe_delete, NULL),
+ QUICK_CHECKBOX (N_("Safe overwrite"), &safe_overwrite, NULL), /* w/o hotkey */
+ QUICK_CHECKBOX (N_("A&uto save setup"), &auto_save_setup, NULL),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_STOP_GROUPBOX,
+ QUICK_STOP_COLUMNS,
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 60 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Configure options"), "[Configuration]",
+ quick_widgets, configure_callback, NULL
+ };
+
+ g_snprintf (time_out, sizeof (time_out), "%d", old_esc_mode_timeout);
+
+#ifndef USE_INTERNAL_EDIT
+ quick_widgets[17].state = WST_DISABLED;
+#endif
+
+ if (!old_esc_mode)
+ quick_widgets[10].state = quick_widgets[11].state = WST_DISABLED;
+
+#ifndef HAVE_POSIX_FALLOCATE
+ mc_global.vfs.preallocate_space = FALSE;
+ quick_widgets[7].state = WST_DISABLED;
+#endif
+
+ if (quick_dialog (&qdlg) == B_ENTER)
+ {
+ if (time_out_new[0] == '\0')
+ old_esc_mode_timeout = 0;
+ else
+ old_esc_mode_timeout = atoi (time_out_new);
+ }
+
+ g_free (time_out_new);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+appearance_box (void)
+{
+ gboolean shadows = mc_global.tty.shadows;
+
+ current_skin_name = g_strdup (mc_skin__default.name);
+ skin_names = mc_skin_list ();
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_COLUMNS,
+ QUICK_LABEL (N_("Skin:"), NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_BUTTON (str_fit_to_term (skin_name_to_label (current_skin_name), 20, J_LEFT_FIT),
+ B_USER, sel_skin_button, NULL),
+ QUICK_STOP_COLUMNS,
+ QUICK_SEPARATOR (TRUE),
+ QUICK_CHECKBOX (N_("&Shadows"), &mc_global.tty.shadows, &shadows_id),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 54 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Appearance"), "[Appearance]",
+ quick_widgets, appearance_box_callback, NULL
+ };
+
+ if (quick_dialog (&qdlg) == B_ENTER)
+ mc_config_set_string (mc_global.main_config, CONFIG_APP_SECTION, "skin",
+ current_skin_name);
+ else
+ {
+ skin_apply (NULL);
+ mc_global.tty.shadows = shadows;
+ }
+ }
+
+ g_free (current_skin_name);
+ g_ptr_array_free (skin_names, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_options_box (void)
+{
+ gboolean simple_swap;
+
+ simple_swap = mc_config_get_bool (mc_global.main_config, CONFIG_PANELS_SECTION,
+ "simple_swap", FALSE);
+ {
+ const char *qsearch_options[] = {
+ N_("Case &insensitive"),
+ N_("Cas&e sensitive"),
+ N_("Use panel sort mo&de")
+ };
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_COLUMNS,
+ QUICK_START_GROUPBOX (N_("Main options")),
+ QUICK_CHECKBOX (N_("Show mi&ni-status"), &panels_options.show_mini_info, NULL),
+ QUICK_CHECKBOX (N_("Use SI si&ze units"), &panels_options.kilobyte_si, NULL),
+ QUICK_CHECKBOX (N_("Mi&x all files"), &panels_options.mix_all_files, NULL),
+ QUICK_CHECKBOX (N_("Show &backup files"), &panels_options.show_backups, NULL),
+ QUICK_CHECKBOX (N_("Show &hidden files"), &panels_options.show_dot_files, NULL),
+ QUICK_CHECKBOX (N_("&Fast dir reload"), &panels_options.fast_reload, NULL),
+ QUICK_CHECKBOX (N_("Ma&rk moves down"), &panels_options.mark_moves_down, NULL),
+ QUICK_CHECKBOX (N_("Re&verse files only"), &panels_options.reverse_files_only,
+ NULL),
+ QUICK_CHECKBOX (N_("Simple s&wap"), &simple_swap, NULL),
+ QUICK_CHECKBOX (N_("A&uto save panels setup"), &panels_options.auto_save_setup,
+ NULL),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_STOP_GROUPBOX,
+ QUICK_NEXT_COLUMN,
+ QUICK_START_GROUPBOX (N_("Navigation")),
+ QUICK_CHECKBOX (N_("L&ynx-like motion"), &panels_options.navigate_with_arrows,
+ NULL),
+ QUICK_CHECKBOX (N_("Pa&ge scrolling"), &panels_options.scroll_pages, NULL),
+ QUICK_CHECKBOX (N_("Center &scrolling"), &panels_options.scroll_center, NULL),
+ QUICK_CHECKBOX (N_("&Mouse page scrolling"), &panels_options.mouse_move_pages,
+ NULL),
+ QUICK_STOP_GROUPBOX,
+ QUICK_START_GROUPBOX (N_("File highlight")),
+ QUICK_CHECKBOX (N_("File &types"), &panels_options.filetype_mode, NULL),
+ QUICK_CHECKBOX (N_("&Permissions"), &panels_options.permission_mode, NULL),
+ QUICK_STOP_GROUPBOX,
+ QUICK_START_GROUPBOX (N_("Quick search")),
+ QUICK_RADIO (QSEARCH_NUM, qsearch_options, (int *) &panels_options.qsearch_mode,
+ NULL),
+ QUICK_STOP_GROUPBOX,
+ QUICK_STOP_COLUMNS,
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 60 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Panel options"), "[Panel options]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) != B_ENTER)
+ return;
+ }
+
+ mc_config_set_bool (mc_global.main_config, CONFIG_PANELS_SECTION, "simple_swap", simple_swap);
+
+ if (!panels_options.fast_reload_msg_shown && panels_options.fast_reload)
+ {
+ message (D_NORMAL, _("Information"),
+ _("Using the fast reload option may not reflect the exact\n"
+ "directory contents. In this case you'll need to do a\n"
+ "manual reload of the directory. See the man page for\n" "the details."));
+ panels_options.fast_reload_msg_shown = TRUE;
+ }
+
+ update_panels (UP_RELOAD, UP_KEEPSEL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* return list type */
+int
+panel_listing_box (WPanel * panel, int num, char **userp, char **minip, gboolean * use_msformat,
+ int *brief_cols)
+{
+ int result = -1;
+ const char *p = NULL;
+
+ if (panel == NULL)
+ {
+ p = get_nth_panel_name (num);
+ panel = panel_empty_new (p);
+ }
+
+ {
+ gboolean user_mini_status;
+ char panel_brief_cols_in[BUF_TINY];
+ char *panel_brief_cols_out = NULL;
+ char *panel_user_format = NULL;
+ char *mini_user_format = NULL;
+
+ /* Controls whether the array strings have been translated */
+ const char *list_formats[LIST_FORMATS] = {
+ N_("&Full file list"),
+ N_("&Brief file list:"),
+ N_("&Long file list"),
+ N_("&User defined:")
+ };
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_COLUMNS,
+ QUICK_RADIO (LIST_FORMATS, list_formats, &result, &panel_list_formats_id),
+ QUICK_NEXT_COLUMN,
+ QUICK_SEPARATOR (FALSE),
+ QUICK_LABELED_INPUT (_ ("columns"), input_label_right, panel_brief_cols_in,
+ "panel-brief-cols-input", &panel_brief_cols_out,
+ &panel_brief_cols_id, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_STOP_COLUMNS,
+ QUICK_INPUT (panel->user_format, "user-fmt-input", &panel_user_format,
+ &panel_user_format_id, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_CHECKBOX (N_("User &mini status"), &user_mini_status, &user_mini_status_id),
+ QUICK_INPUT (panel->user_status_format[panel->list_format], "mini_input",
+ &mini_user_format, &mini_user_format_id, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 48 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Listing format"), "[Listing Format...]",
+ quick_widgets, panel_listing_callback, NULL
+ };
+
+ user_mini_status = panel->user_mini_status;
+ result = panel->list_format;
+ status_format = panel->user_status_format;
+
+ g_snprintf (panel_brief_cols_in, sizeof (panel_brief_cols_in), "%d", panel->brief_cols);
+
+ if ((int) panel->list_format != panel_list_brief_idx)
+ quick_widgets[4].state = WST_DISABLED;
+
+ if ((int) panel->list_format != panel_list_user_idx)
+ quick_widgets[6].state = WST_DISABLED;
+
+ if (!user_mini_status)
+ quick_widgets[9].state = WST_DISABLED;
+
+ if (quick_dialog (&qdlg) == B_CANCEL)
+ result = -1;
+ else
+ {
+ int cols;
+ char *error = NULL;
+
+ *userp = panel_user_format;
+ *minip = mini_user_format;
+ *use_msformat = user_mini_status;
+
+ cols = strtol (panel_brief_cols_out, &error, 10);
+ if (*error == '\0')
+ *brief_cols = cols;
+ else
+ *brief_cols = panel->brief_cols;
+
+ g_free (panel_brief_cols_out);
+ }
+ }
+
+ if (p != NULL)
+ {
+ int i;
+
+ g_free (panel->user_format);
+ for (i = 0; i < LIST_FORMATS; i++)
+ g_free (panel->user_status_format[i]);
+ g_free (panel);
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const panel_field_t *
+sort_box (dir_sort_options_t * op, const panel_field_t * sort_field)
+{
+ char **sort_orders_names;
+ gsize i;
+ gsize sort_names_num = 0;
+ int sort_idx = 0;
+ const panel_field_t *result = NULL;
+
+ sort_orders_names = panel_get_sortable_fields (&sort_names_num);
+
+ for (i = 0; i < sort_names_num; i++)
+ if (strcmp (sort_orders_names[i], _(sort_field->title_hotkey)) == 0)
+ {
+ sort_idx = i;
+ break;
+ }
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_COLUMNS,
+ QUICK_RADIO (sort_names_num, (const char **) sort_orders_names, &sort_idx, NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("Executable &first"), &op->exec_first, NULL),
+ QUICK_CHECKBOX (N_("Cas&e sensitive"), &op->case_sensitive, NULL),
+ QUICK_CHECKBOX (N_("&Reverse"), &op->reverse, NULL),
+ QUICK_STOP_COLUMNS,
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 40 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Sort order"), "[Sort Order...]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ result = panel_get_field_by_title_hotkey (sort_orders_names[sort_idx]);
+
+ if (result == NULL)
+ result = sort_field;
+ }
+
+ g_strfreev (sort_orders_names);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+confirm_box (void)
+{
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ /* TRANSLATORS: no need to translate 'Confirmation', it's just a context prefix */
+ QUICK_CHECKBOX (Q_("Confirmation|&Delete"), &confirm_delete, NULL),
+ QUICK_CHECKBOX (Q_("Confirmation|O&verwrite"), &confirm_overwrite, NULL),
+ QUICK_CHECKBOX (Q_("Confirmation|&Execute"), &confirm_execute, NULL),
+ QUICK_CHECKBOX (Q_("Confirmation|E&xit"), &confirm_exit, NULL),
+ QUICK_CHECKBOX (Q_("Confirmation|Di&rectory hotlist delete"),
+ &confirm_directory_hotlist_delete, NULL),
+ QUICK_CHECKBOX (Q_("Confirmation|&History cleanup"),
+ &mc_global.widget.confirm_history_cleanup, NULL),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 46 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Confirmation"), "[Confirmation]",
+ quick_widgets, NULL, NULL
+ };
+
+ (void) quick_dialog (&qdlg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifndef HAVE_CHARSET
+void
+display_bits_box (void)
+{
+ gboolean new_meta;
+ int current_mode;
+
+ const char *display_bits_str[] = {
+ N_("&UTF-8 output"),
+ N_("&Full 8 bits output"),
+ N_("&ISO 8859-1"),
+ N_("7 &bits")
+ };
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_RADIO (4, display_bits_str, &current_mode, NULL),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_CHECKBOX (N_("F&ull 8 bits input"), &new_meta, NULL),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 46 };
+
+ quick_dialog_t qdlg = {
+ r, _("Display bits"), "[Display bits]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (mc_global.full_eight_bits)
+ current_mode = 0;
+ else if (mc_global.eight_bit_clean)
+ current_mode = 1;
+ else
+ current_mode = 2;
+
+ new_meta = !use_8th_bit_as_meta;
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ {
+ mc_global.eight_bit_clean = current_mode < 3;
+ mc_global.full_eight_bits = current_mode < 2;
+#ifndef HAVE_SLANG
+ tty_display_8bit (mc_global.eight_bit_clean);
+#else
+ tty_display_8bit (mc_global.full_eight_bits);
+#endif
+ use_8th_bit_as_meta = !new_meta;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+#else /* HAVE_CHARSET */
+
+void
+display_bits_box (void)
+{
+ const char *cpname;
+
+ new_display_codepage = mc_global.display_codepage;
+
+ cpname = (new_display_codepage < 0) ? _("Other 8 bit")
+ : ((codepage_desc *) g_ptr_array_index (codepages, new_display_codepage))->name;
+
+ {
+ gboolean new_meta;
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_COLUMNS,
+ QUICK_LABEL (N_("Input / display codepage:"), NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_BUTTON (cpname, B_USER, sel_charset_button, NULL),
+ QUICK_STOP_COLUMNS,
+ QUICK_SEPARATOR (TRUE),
+ QUICK_CHECKBOX (N_("F&ull 8 bits input"), &new_meta, NULL),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 46 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Display bits"), "[Display bits]",
+ quick_widgets, NULL, NULL
+ };
+
+ new_meta = !use_8th_bit_as_meta;
+ application_keypad_mode ();
+
+ if (quick_dialog (&qdlg) == B_ENTER)
+ {
+ char *errmsg;
+
+ mc_global.display_codepage = new_display_codepage;
+
+ errmsg = init_translation_table (mc_global.source_codepage, mc_global.display_codepage);
+ if (errmsg != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, "%s", errmsg);
+ g_free (errmsg);
+ }
+
+#ifdef HAVE_SLANG
+ tty_display_8bit (mc_global.display_codepage != 0 && mc_global.display_codepage != 1);
+#else
+ tty_display_8bit (mc_global.display_codepage != 0);
+#endif
+ use_8th_bit_as_meta = !new_meta;
+
+ repaint_screen ();
+ }
+ }
+}
+#endif /* HAVE_CHARSET */
+
+/* --------------------------------------------------------------------------------------------- */
+/** Show tree in a box, not on a panel */
+
+char *
+tree_box (const char *current_dir)
+{
+ WTree *mytree;
+ WDialog *dlg;
+ WGroup *g;
+ Widget *wd;
+ char *val = NULL;
+ WButtonBar *bar;
+
+ (void) current_dir;
+
+ /* Create the components */
+ dlg = dlg_create (TRUE, 0, 0, LINES - 9, COLS - 20, WPOS_CENTER, FALSE, dialog_colors,
+ tree_callback, NULL, "[Directory Tree]", _("Directory tree"));
+ g = GROUP (dlg);
+ wd = WIDGET (dlg);
+
+ mytree = tree_new (2, 2, wd->rect.lines - 6, wd->rect.cols - 5, FALSE);
+ group_add_widget_autopos (g, mytree, WPOS_KEEP_ALL, NULL);
+ group_add_widget_autopos (g, hline_new (wd->rect.lines - 4, 1, -1), WPOS_KEEP_BOTTOM, NULL);
+ bar = buttonbar_new ();
+ group_add_widget (g, bar);
+ /* restore ButtonBar coordinates after add_widget() */
+ WIDGET (bar)->rect.x = 0;
+ WIDGET (bar)->rect.y = LINES - 1;
+
+ if (dlg_run (dlg) == B_ENTER)
+ {
+ const vfs_path_t *selected_name;
+
+ selected_name = tree_selected_name (mytree);
+ val = g_strdup (vfs_path_as_str (selected_name));
+ }
+
+ widget_destroy (wd);
+ return val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS
+void
+configure_vfs_box (void)
+{
+ char buffer2[BUF_TINY];
+#ifdef ENABLE_VFS_FTP
+ char buffer3[BUF_TINY];
+
+ g_snprintf (buffer3, sizeof (buffer3), "%i", ftpfs_directory_timeout);
+#endif
+
+ g_snprintf (buffer2, sizeof (buffer2), "%i", vfs_timeout);
+
+ {
+ char *ret_timeout;
+#ifdef ENABLE_VFS_FTP
+ char *ret_passwd;
+ char *ret_ftp_proxy;
+ char *ret_directory_timeout;
+#endif /* ENABLE_VFS_FTP */
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (N_("Timeout for freeing VFSs (sec):"), input_label_left,
+ buffer2, "input-timo-vfs", &ret_timeout, NULL, FALSE, FALSE,
+ INPUT_COMPLETE_NONE),
+#ifdef ENABLE_VFS_FTP
+ QUICK_SEPARATOR (TRUE),
+ QUICK_LABELED_INPUT (N_("FTP anonymous password:"), input_label_left,
+ ftpfs_anonymous_passwd, "input-passwd", &ret_passwd, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_LABELED_INPUT (N_("FTP directory cache timeout (sec):"), input_label_left,
+ buffer3, "input-timeout", &ret_directory_timeout, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_CHECKBOX (N_("&Always use ftp proxy:"), &ftpfs_always_use_proxy,
+ &ftpfs_always_use_proxy_id),
+ QUICK_INPUT (ftpfs_proxy_host, "input-ftp-proxy", &ret_ftp_proxy,
+ &ftpfs_proxy_host_id, FALSE, FALSE, INPUT_COMPLETE_HOSTNAMES),
+ QUICK_CHECKBOX (N_("&Use ~/.netrc"), &ftpfs_use_netrc, NULL),
+ QUICK_CHECKBOX (N_("Use &passive mode"), &ftpfs_use_passive_connections, NULL),
+ QUICK_CHECKBOX (N_("Use passive mode over pro&xy"),
+ &ftpfs_use_passive_connections_over_proxy, NULL),
+#endif /* ENABLE_VFS_FTP */
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 56 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Virtual File System Setting"), "[Virtual FS]",
+ quick_widgets,
+#ifdef ENABLE_VFS_FTP
+ confvfs_callback,
+#else
+ NULL,
+#endif
+ NULL,
+ };
+
+#ifdef ENABLE_VFS_FTP
+ if (!ftpfs_always_use_proxy)
+ quick_widgets[5].state = WST_DISABLED;
+#endif
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ {
+ /* cppcheck-suppress uninitvar */
+ if (ret_timeout[0] == '\0')
+ vfs_timeout = 0;
+ else
+ vfs_timeout = atoi (ret_timeout);
+ g_free (ret_timeout);
+
+ if (vfs_timeout < 0 || vfs_timeout > 10000)
+ vfs_timeout = 10;
+#ifdef ENABLE_VFS_FTP
+ g_free (ftpfs_anonymous_passwd);
+ /* cppcheck-suppress uninitvar */
+ ftpfs_anonymous_passwd = ret_passwd;
+ g_free (ftpfs_proxy_host);
+ /* cppcheck-suppress uninitvar */
+ ftpfs_proxy_host = ret_ftp_proxy;
+ /* cppcheck-suppress uninitvar */
+ if (ret_directory_timeout[0] == '\0')
+ ftpfs_directory_timeout = 0;
+ else
+ ftpfs_directory_timeout = atoi (ret_directory_timeout);
+ g_free (ret_directory_timeout);
+#endif
+ }
+ }
+}
+
+#endif /* ENABLE_VFS */
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+cd_box (const WPanel * panel)
+{
+ const Widget *w = CONST_WIDGET (panel);
+ char *my_str;
+
+ quick_widget_t quick_widgets[] = {
+ QUICK_LABELED_INPUT (N_("cd"), input_label_left, "", "input", &my_str, NULL, FALSE, TRUE,
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD),
+ QUICK_END
+ };
+
+ WRect r = { w->rect.y + w->rect.lines - 6, w->rect.x, 0, w->rect.cols };
+
+ quick_dialog_t qdlg = {
+ r, N_("Quick cd"), "[Quick cd]",
+ quick_widgets, NULL, NULL
+ };
+
+ return (quick_dialog (&qdlg) != B_CANCEL) ? my_str : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+symlink_box (const vfs_path_t * existing_vpath, const vfs_path_t * new_vpath,
+ char **ret_existing, char **ret_new)
+{
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (N_("Existing filename (filename symlink will point to):"),
+ input_label_above, vfs_path_as_str (existing_vpath), "input-2",
+ ret_existing, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_LABELED_INPUT (N_("Symbolic link filename:"), input_label_above,
+ vfs_path_as_str (new_vpath), "input-1",
+ ret_new, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 64 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Symbolic link"), "[File Menu]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) == B_CANCEL)
+ {
+ *ret_new = NULL;
+ *ret_existing = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_BACKGROUND
+void
+jobs_box (void)
+{
+ struct
+ {
+ const char *name;
+ int flags;
+ int value;
+ int len;
+ bcback_fn callback;
+ }
+ job_but[] =
+ {
+ /* *INDENT-OFF* */
+ { N_("&Stop"), NORMAL_BUTTON, B_STOP, 0, task_cb },
+ { N_("&Resume"), NORMAL_BUTTON, B_RESUME, 0, task_cb },
+ { N_("&Kill"), NORMAL_BUTTON, B_KILL, 0, task_cb },
+ { N_("&OK"), DEFPUSH_BUTTON, B_CANCEL, 0, NULL }
+ /* *INDENT-ON* */
+ };
+
+ size_t i;
+ const size_t n_but = G_N_ELEMENTS (job_but);
+
+ WDialog *jobs_dlg;
+ WGroup *g;
+ int cols = 60;
+ int lines = 15;
+ int x = 0;
+
+ for (i = 0; i < n_but; i++)
+ {
+#ifdef ENABLE_NLS
+ job_but[i].name = _(job_but[i].name);
+#endif /* ENABLE_NLS */
+
+ job_but[i].len = str_term_width1 (job_but[i].name) + 3;
+ if (job_but[i].flags == DEFPUSH_BUTTON)
+ job_but[i].len += 2;
+ x += job_but[i].len;
+ }
+
+ x += (int) n_but - 1;
+ cols = MAX (cols, x + 6);
+
+ jobs_dlg = dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, NULL, NULL,
+ "[Background jobs]", _("Background jobs"));
+ g = GROUP (jobs_dlg);
+
+ bg_list = listbox_new (2, 2, lines - 6, cols - 6, FALSE, NULL);
+ jobs_fill_listbox (bg_list);
+ group_add_widget (g, bg_list);
+
+ group_add_widget (g, hline_new (lines - 4, -1, -1));
+
+ x = (cols - x) / 2;
+ for (i = 0; i < n_but; i++)
+ {
+ group_add_widget (g, button_new (lines - 3, x, job_but[i].value, job_but[i].flags,
+ job_but[i].name, job_but[i].callback));
+ x += job_but[i].len + 1;
+ }
+
+ (void) dlg_run (jobs_dlg);
+ widget_destroy (WIDGET (jobs_dlg));
+}
+#endif /* ENABLE_BACKGROUND */
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/boxes.h b/src/filemanager/boxes.h
new file mode 100644
index 0000000..6cb115e
--- /dev/null
+++ b/src/filemanager/boxes.h
@@ -0,0 +1,37 @@
+/** \file boxes.h
+ * \brief Header: Some misc dialog boxes for the program
+ */
+
+#ifndef MC__BOXES_H
+#define MC__BOXES_H
+
+#include "dir.h"
+#include "panel.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void configure_box (void);
+void appearance_box (void);
+void panel_options_box (void);
+int panel_listing_box (WPanel * p, int num, char **user, char **mini, gboolean * use_msformat,
+ int *brief_cols);
+const panel_field_t *sort_box (dir_sort_options_t * op, const panel_field_t * sort_field);
+void confirm_box (void);
+void display_bits_box (void);
+void configure_vfs_box (void);
+void jobs_box (void);
+char *cd_box (const WPanel * panel);
+void symlink_box (const vfs_path_t * existing_vpath, const vfs_path_t * new_vpath,
+ char **ret_existing, char **ret_new);
+char *tree_box (const char *current_dir);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__BOXES_H */
diff --git a/src/filemanager/cd.c b/src/filemanager/cd.c
new file mode 100644
index 0000000..564a605
--- /dev/null
+++ b/src/filemanager/cd.c
@@ -0,0 +1,310 @@
+/*
+ cd_to() function.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2020
+
+ 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 cd.c
+ * \brief Source: cd_to() function
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strescape.h" /* strutils_shell_unescape() */
+#include "lib/util.h" /* whitespace() */
+#include "lib/widget.h" /* message() */
+
+#include "filemanager.h" /* current_panel, panel.h, layout.h */
+#include "tree.h" /* sync_tree() */
+
+#include "cd.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Expand the argument to "cd" and change directory. First try tilde
+ * expansion, then variable substitution. If the CDPATH variable is set
+ * (e.g. CDPATH=".:~:/usr"), try all the paths contained there.
+ * We do not support such rare substitutions as ${var:-value} etc.
+ * No quoting is implemented here, so ${VAR} and $VAR will be always
+ * substituted. Wildcards are not supported either.
+ * Advanced users should be encouraged to use "\cd" instead of "cd" if
+ * they want the behavior they are used to in the shell.
+ *
+ * @param _path string to examine
+ * @return newly allocated string
+ */
+
+static GString *
+examine_cd (const char *_path)
+{
+ /* *INDENT-OFF* */
+ typedef enum
+ {
+ copy_sym,
+ subst_var
+ } state_t;
+ /* *INDENT-ON* */
+
+ state_t state = copy_sym;
+ GString *q;
+ char *path_tilde, *path;
+ char *p;
+
+ /* Tilde expansion */
+ path = strutils_shell_unescape (_path);
+ path_tilde = tilde_expand (path);
+ g_free (path);
+
+ q = g_string_sized_new (32);
+
+ /* Variable expansion */
+ for (p = path_tilde; *p != '\0';)
+ {
+ switch (state)
+ {
+ case copy_sym:
+ if (p[0] == '\\' && p[1] == '$')
+ {
+ g_string_append_c (q, '$');
+ p += 2;
+ }
+ else if (p[0] != '$' || p[1] == '[' || p[1] == '(')
+ {
+ g_string_append_c (q, *p);
+ p++;
+ }
+ else
+ state = subst_var;
+ break;
+
+ case subst_var:
+ {
+ char *s = NULL;
+ char c;
+ const char *t = NULL;
+
+ /* skip dollar */
+ p++;
+
+ if (p[0] == '{')
+ {
+ p++;
+ s = strchr (p, '}');
+ }
+ if (s == NULL)
+ s = strchr (p, PATH_SEP);
+ if (s == NULL)
+ s = strchr (p, '\0');
+ c = *s;
+ *s = '\0';
+ t = getenv (p);
+ *s = c;
+ if (t == NULL)
+ {
+ g_string_append_c (q, '$');
+ if (p[-1] != '$')
+ g_string_append_c (q, '{');
+ }
+ else
+ {
+ g_string_append (q, t);
+ p = s;
+ if (*s == '}')
+ p++;
+ }
+
+ state = copy_sym;
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ g_free (path_tilde);
+
+ return q;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* CDPATH handling */
+static gboolean
+handle_cdpath (const char *path)
+{
+ gboolean result = FALSE;
+
+ /* CDPATH handling */
+ if (!IS_PATH_SEP (*path))
+ {
+ char *cdpath, *p;
+ char c;
+
+ cdpath = g_strdup (getenv ("CDPATH"));
+ p = cdpath;
+ c = (p == NULL) ? '\0' : ':';
+
+ while (!result && c == ':')
+ {
+ char *s;
+
+ s = strchr (p, ':');
+ if (s == NULL)
+ s = strchr (p, '\0');
+ c = *s;
+ *s = '\0';
+ if (*p != '\0')
+ {
+ vfs_path_t *r_vpath;
+
+ r_vpath = vfs_path_build_filename (p, path, (char *) NULL);
+ result = panel_cd (current_panel, r_vpath, cd_parse_command);
+ vfs_path_free (r_vpath, TRUE);
+ }
+ *s = c;
+ p = s + 1;
+ }
+ g_free (cdpath);
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** Execute the cd command to specified path
+ *
+ * @param path path to cd
+ */
+
+void
+cd_to (const char *path)
+{
+ char *p;
+
+ /* Remove leading whitespaces. */
+ /* Any final whitespace should be removed here (to see why, try "cd fred "). */
+ /* NOTE: I think we should not remove the extra space,
+ that way, we can cd into hidden directories */
+ /* FIXME: what about interpreting quoted strings like the shell.
+ so one could type "cd <tab> M-a <enter>" and it would work. */
+ p = g_strstrip (g_strdup (path));
+
+ if (get_current_type () == view_tree)
+ {
+ vfs_path_t *new_vpath = NULL;
+
+ if (p[0] == '\0')
+ {
+ new_vpath = vfs_path_from_str (mc_config_get_home_dir ());
+ sync_tree (new_vpath);
+ }
+ else if (DIR_IS_DOTDOT (p))
+ {
+ if (vfs_path_elements_count (current_panel->cwd_vpath) != 1 ||
+ strlen (vfs_path_get_by_index (current_panel->cwd_vpath, 0)->path) > 1)
+ {
+ vfs_path_t *tmp_vpath = current_panel->cwd_vpath;
+
+ current_panel->cwd_vpath =
+ vfs_path_vtokens_get (tmp_vpath, 0, vfs_path_tokens_count (tmp_vpath) - 1);
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ sync_tree (current_panel->cwd_vpath);
+ }
+ else
+ {
+ if (IS_PATH_SEP (*p))
+ new_vpath = vfs_path_from_str (p);
+ else
+ new_vpath = vfs_path_append_new (current_panel->cwd_vpath, p, (char *) NULL);
+
+ sync_tree (new_vpath);
+ }
+
+ vfs_path_free (new_vpath, TRUE);
+ }
+ else
+ {
+ GString *s_path;
+ vfs_path_t *q_vpath;
+ gboolean ok;
+
+ s_path = examine_cd (p);
+
+ if (s_path->len == 0)
+ q_vpath = vfs_path_from_str (mc_config_get_home_dir ());
+ else
+ q_vpath = vfs_path_from_str_flags (s_path->str, VPF_NO_CANON);
+
+ ok = panel_cd (current_panel, q_vpath, cd_parse_command);
+ if (!ok)
+ ok = handle_cdpath (s_path->str);
+ if (!ok)
+ {
+ char *d;
+
+ d = vfs_path_to_str_flags (q_vpath, 0, VPF_STRIP_PASSWORD);
+ cd_error_message (d);
+ g_free (d);
+ }
+
+ vfs_path_free (q_vpath, TRUE);
+ g_string_free (s_path, TRUE);
+ }
+
+ g_free (p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+cd_error_message (const char *path)
+{
+ message (D_ERROR, MSG_ERROR, _("Cannot change directory to\n%s\n%s"), path,
+ unix_error_string (errno));
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/cd.h b/src/filemanager/cd.h
new file mode 100644
index 0000000..13e7718
--- /dev/null
+++ b/src/filemanager/cd.h
@@ -0,0 +1,23 @@
+/** \file cd.h
+ * \brief Header: cd function
+ */
+
+#ifndef MC__CD_H
+#define MC__CD_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void cd_to (const char *path);
+void cd_error_message (const char *path);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__CD_H */
diff --git a/src/filemanager/chattr.c b/src/filemanager/chattr.c
new file mode 100644
index 0000000..08a5a99
--- /dev/null
+++ b/src/filemanager/chattr.c
@@ -0,0 +1,1358 @@
+/*
+ Chattr command -- for the Midnight Commander
+
+ Copyright (C) 2020-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2020-2023
+
+ 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 chattr.c
+ * \brief Source: chattr command
+ */
+
+/* TODO: change attributes recursively (ticket #3109) */
+
+#include <config.h>
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ext2fs/ext2_fs.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* tty_print*() */
+#include "lib/tty/color.h" /* tty_setcolor() */
+#include "lib/skin.h" /* COLOR_NORMAL, DISABLED_COLOR */
+#include "lib/vfs/vfs.h"
+#include "lib/widget.h"
+#include "lib/util.h" /* x_basename() */
+
+#include "src/keymap.h" /* chattr_map */
+
+#include "cmd.h" /* chattr_cmd(), chattr_get_as_str() */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define B_MARKED B_USER
+#define B_SETALL (B_USER + 1)
+#define B_SETMRK (B_USER + 2)
+#define B_CLRMRK (B_USER + 3)
+
+#define BUTTONS 6
+
+#define CHATTRBOXES(x) ((WChattrBoxes *)(x))
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct WFileAttrText WFileAttrText;
+
+struct WFileAttrText
+{
+ Widget widget; /* base class */
+
+ char *filename;
+ int filename_width; /* cached width of file name */
+ char attrs[32 + 1]; /* 32 bits in attributes (unsigned long) */
+};
+
+typedef struct WChattrBoxes WChattrBoxes;
+
+struct WChattrBoxes
+{
+ WGroup base; /* base class */
+
+ int pos; /* The current checkbox selected */
+ int top; /* The first flag displayed */
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* see /usr/include/ext2fs/ext2_fs.h
+ *
+ * EXT2_SECRM_FL 0x00000001 -- Secure deletion
+ * EXT2_UNRM_FL 0x00000002 -- Undelete
+ * EXT2_COMPR_FL 0x00000004 -- Compress file
+ * EXT2_SYNC_FL 0x00000008 -- Synchronous updates
+ * EXT2_IMMUTABLE_FL 0x00000010 -- Immutable file
+ * EXT2_APPEND_FL 0x00000020 -- writes to file may only append
+ * EXT2_NODUMP_FL 0x00000040 -- do not dump file
+ * EXT2_NOATIME_FL 0x00000080 -- do not update atime
+ * * Reserved for compression usage...
+ * EXT2_DIRTY_FL 0x00000100
+ * EXT2_COMPRBLK_FL 0x00000200 -- One or more compressed clusters
+ * EXT2_NOCOMPR_FL 0x00000400 -- Access raw compressed data
+ * * nb: was previously EXT2_ECOMPR_FL
+ * EXT4_ENCRYPT_FL 0x00000800 -- encrypted inode
+ * * End compression flags --- maybe not all used
+ * EXT2_BTREE_FL 0x00001000 -- btree format dir
+ * EXT2_INDEX_FL 0x00001000 -- hash-indexed directory
+ * EXT2_IMAGIC_FL 0x00002000
+ * EXT3_JOURNAL_DATA_FL 0x00004000 -- file data should be journaled
+ * EXT2_NOTAIL_FL 0x00008000 -- file tail should not be merged
+ * EXT2_DIRSYNC_FL 0x00010000 -- Synchronous directory modifications
+ * EXT2_TOPDIR_FL 0x00020000 -- Top of directory hierarchies
+ * EXT4_HUGE_FILE_FL 0x00040000 -- Set to each huge file
+ * EXT4_EXTENTS_FL 0x00080000 -- Inode uses extents
+ * EXT4_VERITY_FL 0x00100000 -- Verity protected inode
+ * EXT4_EA_INODE_FL 0x00200000 -- Inode used for large EA
+ * EXT4_EOFBLOCKS_FL 0x00400000 was here, unused
+ * FS_NOCOW_FL 0x00800000 -- Do not cow file
+ * EXT4_SNAPFILE_FL 0x01000000 -- Inode is a snapshot
+ * FS_DAX_FL 0x02000000 -- Inode is DAX
+ * EXT4_SNAPFILE_DELETED_FL 0x04000000 -- Snapshot is being deleted
+ * EXT4_SNAPFILE_SHRUNK_FL 0x08000000 -- Snapshot shrink has completed
+ * EXT4_INLINE_DATA_FL 0x10000000 -- Inode has inline data
+ * EXT4_PROJINHERIT_FL 0x20000000 -- Create with parents projid
+ * EXT4_CASEFOLD_FL 0x40000000 -- Casefolded file
+ * 0x80000000 -- unused yet
+ */
+
+static struct
+{
+ unsigned long flags;
+ char attr;
+ const char *text;
+ gboolean selected;
+ gboolean state; /* state of checkboxes */
+} check_attr[] =
+{
+ /* *INDENT-OFF* */
+ { EXT2_SECRM_FL, 's', N_("Secure deletion"), FALSE, FALSE },
+ { EXT2_UNRM_FL, 'u', N_("Undelete"), FALSE, FALSE },
+ { EXT2_SYNC_FL, 'S', N_("Synchronous updates"), FALSE, FALSE },
+ { EXT2_DIRSYNC_FL, 'D', N_("Synchronous directory updates"), FALSE, FALSE },
+ { EXT2_IMMUTABLE_FL, 'i', N_("Immutable"), FALSE, FALSE },
+ { EXT2_APPEND_FL, 'a', N_("Append only"), FALSE, FALSE },
+ { EXT2_NODUMP_FL, 'd', N_("No dump"), FALSE, FALSE },
+ { EXT2_NOATIME_FL, 'A', N_("No update atime"), FALSE, FALSE },
+ { EXT2_COMPR_FL, 'c', N_("Compress"), FALSE, FALSE },
+#ifdef EXT2_COMPRBLK_FL
+ /* removed in v1.43-WIP-2015-05-18
+ ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
+ { EXT2_COMPRBLK_FL, 'B', N_("Compressed clusters"), FALSE, FALSE },
+#endif
+#ifdef EXT2_DIRTY_FL
+ /* removed in v1.43-WIP-2015-05-18
+ ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
+ { EXT2_DIRTY_FL, 'Z', N_("Compressed dirty file"), FALSE, FALSE },
+#endif
+#ifdef EXT2_NOCOMPR_FL
+ /* removed in v1.43-WIP-2015-05-18
+ ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
+ { EXT2_NOCOMPR_FL, 'X', N_("Compression raw access"), FALSE, FALSE },
+#endif
+#ifdef EXT4_ENCRYPT_FL
+ { EXT4_ENCRYPT_FL, 'E', N_("Encrypted inode"), FALSE, FALSE },
+#endif
+ { EXT3_JOURNAL_DATA_FL, 'j', N_("Journaled data"), FALSE, FALSE },
+ { EXT2_INDEX_FL, 'I', N_("Indexed directory"), FALSE, FALSE },
+ { EXT2_NOTAIL_FL, 't', N_("No tail merging"), FALSE, FALSE },
+ { EXT2_TOPDIR_FL, 'T', N_("Top of directory hierarchies"), FALSE, FALSE },
+ { EXT4_EXTENTS_FL, 'e', N_("Inode uses extents"), FALSE, FALSE },
+#ifdef EXT4_HUGE_FILE_FL
+ /* removed in v1.43.9
+ ext2fsprogs 4825daeb0228e556444d199274b08c499ac3706c 2018-02-06 */
+ { EXT4_HUGE_FILE_FL, 'h', N_("Huge_file"), FALSE, FALSE },
+#endif
+ { FS_NOCOW_FL, 'C', N_("No COW"), FALSE, FALSE },
+#ifdef FS_DAX_FL
+ /* added in v1.45.7
+ ext2fsprogs 1dd48bc23c3776df76459aff0c7723fff850ea45 2020-07-28 */
+ { FS_DAX_FL, 'x', N_("Direct access for files"), FALSE, FALSE },
+#endif
+#ifdef EXT4_CASEFOLD_FL
+ /* added in v1.45.0
+ ext2fsprogs 1378bb6515e98a27f0f5c220381d49d20544204e 2018-12-01 */
+ { EXT4_CASEFOLD_FL, 'F', N_("Casefolded file"), FALSE, FALSE },
+#endif
+#ifdef EXT4_INLINE_DATA_FL
+ { EXT4_INLINE_DATA_FL, 'N', N_("Inode has inline data"), FALSE, FALSE },
+#endif
+#ifdef EXT4_PROJINHERIT_FL
+ /* added in v1.43-WIP-2016-05-12
+ ext2fsprogs e1cec4464bdaf93ea609de43c5cdeb6a1f553483 2016-03-07
+ 97d7e2fdb2ebec70c3124c1a6370d28ec02efad0 2016-05-09 */
+ { EXT4_PROJINHERIT_FL, 'P', N_("Project hierarchy"), FALSE, FALSE },
+#endif
+#ifdef EXT4_VERITY_FL
+ /* added in v1.44.4
+ ext2fsprogs faae7aa00df0abe7c6151fc4947aa6501b981ee1 2018-08-14
+ v1.44.5
+ ext2fsprogs 7e5a95e3d59719361661086ec7188ca6e674f139 2018-08-21 */
+ { EXT4_VERITY_FL, 'V', N_("Verity protected inode"), FALSE, FALSE }
+#endif
+ /* *INDENT-ON* */
+};
+
+/* number of attributes */
+static const size_t check_attr_num = G_N_ELEMENTS (check_attr);
+
+/* modifiable attribute numbers */
+static int check_attr_mod[32];
+static int check_attr_mod_num = 0; /* 0..31 */
+
+/* maximum width of attribute text */
+static int check_attr_width = 0;
+
+static struct
+{
+ int ret_cmd;
+ button_flags_t flags;
+ int width;
+ const char *text;
+ Widget *button;
+} chattr_but[BUTTONS] =
+{
+ /* *INDENT-OFF* */
+ /* 0 */ { B_SETALL, NORMAL_BUTTON, 0, N_("Set &all"), NULL },
+ /* 1 */ { B_MARKED, NORMAL_BUTTON, 0, N_("&Marked all"), NULL },
+ /* 2 */ { B_SETMRK, NORMAL_BUTTON, 0, N_("S&et marked"), NULL },
+ /* 3 */ { B_CLRMRK, NORMAL_BUTTON, 0, N_("C&lear marked"), NULL },
+ /* 4 */ { B_ENTER, DEFPUSH_BUTTON, 0, N_("&Set"), NULL },
+ /* 5 */ { B_CANCEL, NORMAL_BUTTON, 0, N_("&Cancel"), NULL }
+ /* *INDENT-ON* */
+};
+
+static gboolean flags_changed;
+static int current_file;
+static gboolean ignore_all;
+
+static unsigned long and_mask, or_mask, flags;
+
+static WFileAttrText *file_attr;
+
+/* x-coord of widget in the dialog */
+static const int wx = 3;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+chattr_is_modifiable (size_t i)
+{
+ return ((check_attr[i].flags & EXT2_FL_USER_MODIFIABLE) != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattr_fill_str (unsigned long attr, char *str)
+{
+ size_t i;
+
+ for (i = 0; i < check_attr_num; i++)
+ str[i] = (attr & check_attr[i].flags) != 0 ? check_attr[i].attr : '-';
+
+ str[check_attr_num] = '\0';
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fileattrtext_fill (WFileAttrText * fat, unsigned long attr)
+{
+ chattr_fill_str (attr, fat->attrs);
+ widget_draw (WIDGET (fat));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+fileattrtext_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WFileAttrText *fat = (WFileAttrText *) w;
+
+ switch (msg)
+ {
+ case MSG_DRAW:
+ {
+ int color;
+ size_t i;
+
+ color = COLOR_NORMAL;
+ tty_setcolor (color);
+
+ if (w->rect.cols > fat->filename_width)
+ {
+ widget_gotoyx (w, 0, (w->rect.cols - fat->filename_width) / 2);
+ tty_print_string (fat->filename);
+ }
+ else
+ {
+ widget_gotoyx (w, 0, 0);
+ tty_print_string (str_trunc (fat->filename, w->rect.cols));
+ }
+
+ /* hope that w->cols is greater than check_attr_num */
+ widget_gotoyx (w, 1, (w->rect.cols - check_attr_num) / 2);
+ for (i = 0; i < check_attr_num; i++)
+ {
+ /* Do not set new color for each symbol. Try to use previous color. */
+ if (chattr_is_modifiable (i))
+ {
+ if (color == DISABLED_COLOR)
+ {
+ color = COLOR_NORMAL;
+ tty_setcolor (color);
+ }
+ }
+ else
+ {
+ if (color != DISABLED_COLOR)
+ {
+ color = DISABLED_COLOR;
+ tty_setcolor (color);
+ }
+ }
+
+ tty_print_char (fat->attrs[i]);
+ }
+ return MSG_HANDLED;
+ }
+
+ case MSG_RESIZE:
+ {
+ const WRect *wo = &CONST_WIDGET (w->owner)->rect;
+
+ widget_default_callback (w, sender, msg, parm, data);
+ /* initially file name may be wider than screen */
+ if (fat->filename_width > wo->cols - wx * 2)
+ {
+ w->rect.x = wo->x + wx;
+ w->rect.cols = wo->cols - wx * 2;
+ }
+ return MSG_HANDLED;
+ }
+
+ case MSG_DESTROY:
+ g_free (fat->filename);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WFileAttrText *
+fileattrtext_new (int y, int x, const char *filename, unsigned long attr)
+{
+ WRect r = { y, x, 2, 1 };
+ WFileAttrText *fat;
+ int width;
+
+ width = str_term_width1 (filename);
+ r.cols = MAX (width, (int) check_attr_num);
+
+ fat = g_new (WFileAttrText, 1);
+ widget_init (WIDGET (fat), &r, fileattrtext_callback, NULL);
+
+ fat->filename = g_strdup (filename);
+ fat->filename_width = width;
+ fileattrtext_fill (fat, attr);
+
+ return fat;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattr_draw_select (const Widget * w, gboolean selected)
+{
+ widget_gotoyx (w, 0, -1);
+ tty_print_char (selected ? '*' : ' ');
+ widget_gotoyx (w, 0, 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattr_toggle_select (const WChattrBoxes * cb, int Id)
+{
+ Widget *w;
+
+ /* find checkbox */
+ w = WIDGET (g_list_nth_data (CONST_GROUP (cb)->widgets, Id - cb->top));
+
+ check_attr[Id].selected = !check_attr[Id].selected;
+
+ tty_setcolor (COLOR_NORMAL);
+ chattr_draw_select (w, check_attr[Id].selected);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+chattrboxes_draw_scrollbar (const WChattrBoxes * cb)
+{
+ const Widget *w = CONST_WIDGET (cb);
+ int max_line;
+ int line;
+ int i;
+
+ /* Are we at the top? */
+ widget_gotoyx (w, 0, w->rect.cols);
+ if (cb->top == 0)
+ tty_print_one_vline (TRUE);
+ else
+ tty_print_char ('^');
+
+ max_line = w->rect.lines - 1;
+
+ /* Are we at the bottom? */
+ widget_gotoyx (w, max_line, w->rect.cols);
+ if (cb->top + w->rect.lines == check_attr_mod_num || w->rect.lines >= check_attr_mod_num)
+ tty_print_one_vline (TRUE);
+ else
+ tty_print_char ('v');
+
+ /* Now draw the nice relative pointer */
+ line = 1 + (cb->pos * (w->rect.lines - 2)) / check_attr_mod_num;
+
+ for (i = 1; i < max_line; i++)
+ {
+ widget_gotoyx (w, i, w->rect.cols);
+ if (i != line)
+ tty_print_one_vline (TRUE);
+ else
+ tty_print_char ('*');
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattrboxes_draw (WChattrBoxes * cb)
+{
+ Widget *w = WIDGET (cb);
+ int i;
+ GList *l;
+ const int *colors;
+
+ colors = widget_get_colors (w);
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ tty_fill_region (w->rect.y, w->rect.x - 1, w->rect.lines, w->rect.cols + 1, ' ');
+
+ /* redraw checkboxes */
+ group_default_callback (w, NULL, MSG_DRAW, 0, NULL);
+
+ /* draw scrollbar */
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ if (!mc_global.tty.slow_terminal && check_attr_mod_num > w->rect.lines)
+ chattrboxes_draw_scrollbar (cb);
+
+ /* mark selected checkboxes */
+ for (i = cb->top, l = GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
+ chattr_draw_select (WIDGET (l->data), check_attr[i].selected);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattrboxes_rename (WChattrBoxes * cb)
+{
+ Widget *w = WIDGET (cb);
+ gboolean active;
+ int i;
+ GList *l;
+ char btext[BUF_SMALL]; /* FIXME: is 128 bytes enough? */
+
+ active = widget_get_state (w, WST_ACTIVE);
+
+ /* lock the group to avoid redraw of checkboxes individually */
+ if (active)
+ widget_set_state (w, WST_SUSPENDED, TRUE);
+
+ for (i = cb->top, l = GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
+ {
+ WCheck *c = CHECK (l->data);
+ int m;
+
+ m = check_attr_mod[i];
+ g_snprintf (btext, sizeof (btext), "(%c) %s", check_attr[m].attr, check_attr[m].text);
+ check_set_text (c, btext);
+ c->state = check_attr[m].state;
+ }
+
+ /* unlock */
+ if (active)
+ widget_set_state (w, WST_ACTIVE, TRUE);
+
+ widget_draw (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+checkboxes_save_state (const WChattrBoxes * cb)
+{
+ int i;
+ GList *l;
+
+ for (i = cb->top, l = CONST_GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
+ {
+ int m;
+
+ m = check_attr_mod[i];
+ check_attr[m].state = CHECK (l->data)->state;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_down (WChattrBoxes * cb)
+{
+ if (cb->pos == cb->top + WIDGET (cb)->rect.lines - 1)
+ {
+ /* We are on the last checkbox.
+ Keep this position. */
+
+ if (cb->pos == check_attr_mod_num - 1)
+ /* get out of widget */
+ return MSG_NOT_HANDLED;
+
+ /* emulate scroll of checkboxes */
+ checkboxes_save_state (cb);
+ cb->pos++;
+ cb->top++;
+ chattrboxes_rename (cb);
+ }
+ else /* cb->pos > cb-top */
+ {
+ GList *l;
+
+ /* select next checkbox */
+ cb->pos++;
+ l = g_list_next (GROUP (cb)->current);
+ widget_select (WIDGET (l->data));
+ }
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_page_down (WChattrBoxes * cb)
+{
+ WGroup *g = GROUP (cb);
+ GList *l;
+
+ if (cb->pos == check_attr_mod_num - 1)
+ {
+ /* We are on the last checkbox.
+ Keep this position.
+ Do nothing. */
+ l = g_list_last (g->widgets);
+ }
+ else
+ {
+ int i = WIDGET (cb)->rect.lines;
+
+ checkboxes_save_state (cb);
+
+ if (cb->top > check_attr_mod_num - 2 * i)
+ i = check_attr_mod_num - i - cb->top;
+ if (cb->top + i < 0)
+ i = -cb->top;
+ if (i == 0)
+ {
+ cb->pos = check_attr_mod_num - 1;
+ cb->top += i;
+ l = g_list_last (g->widgets);
+ }
+ else
+ {
+ cb->pos += i;
+ cb->top += i;
+ l = g_list_nth (g->widgets, cb->pos - cb->top);
+ }
+
+ chattrboxes_rename (cb);
+ }
+
+ widget_select (WIDGET (l->data));
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_end (WChattrBoxes * cb)
+{
+ GList *l;
+
+ checkboxes_save_state (cb);
+ cb->pos = check_attr_mod_num - 1;
+ cb->top = cb->pos - WIDGET (cb)->rect.lines + 1;
+ l = g_list_last (GROUP (cb)->widgets);
+ chattrboxes_rename (cb);
+ widget_select (WIDGET (l->data));
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_up (WChattrBoxes * cb)
+{
+ if (cb->pos == cb->top)
+ {
+ /* We are on the first checkbox.
+ Keep this position. */
+
+ if (cb->top == 0)
+ /* get out of widget */
+ return MSG_NOT_HANDLED;
+
+ /* emulate scroll of checkboxes */
+ checkboxes_save_state (cb);
+ cb->pos--;
+ cb->top--;
+ chattrboxes_rename (cb);
+ }
+ else /* cb->pos > cb-top */
+ {
+ GList *l;
+
+ /* select previous checkbox */
+ cb->pos--;
+ l = g_list_previous (GROUP (cb)->current);
+ widget_select (WIDGET (l->data));
+ }
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_page_up (WChattrBoxes * cb)
+{
+ WGroup *g = GROUP (cb);
+ GList *l;
+
+ if (cb->pos == 0 && cb->top == 0)
+ {
+ /* We are on the first checkbox.
+ Keep this position.
+ Do nothing. */
+ l = g_list_first (g->widgets);
+ }
+ else
+ {
+ int i = WIDGET (cb)->rect.lines;
+
+ checkboxes_save_state (cb);
+
+ if (cb->top < i)
+ i = cb->top;
+ if (i == 0)
+ {
+ cb->pos = 0;
+ cb->top -= i;
+ l = g_list_first (g->widgets);
+ }
+ else
+ {
+ cb->pos -= i;
+ cb->top -= i;
+ l = g_list_nth (g->widgets, cb->pos - cb->top);
+ }
+
+ chattrboxes_rename (cb);
+ }
+
+ widget_select (WIDGET (l->data));
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_home (WChattrBoxes * cb)
+{
+ GList *l;
+
+ checkboxes_save_state (cb);
+ cb->pos = 0;
+ cb->top = 0;
+ l = g_list_first (GROUP (cb)->widgets);
+ chattrboxes_rename (cb);
+ widget_select (WIDGET (l->data));
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_execute_cmd (WChattrBoxes * cb, long command)
+{
+ switch (command)
+ {
+ case CK_Down:
+ return chattrboxes_down (cb);
+
+ case CK_PageDown:
+ return chattrboxes_page_down (cb);
+
+ case CK_Bottom:
+ return chattrboxes_end (cb);
+
+ case CK_Up:
+ return chattrboxes_up (cb);
+
+ case CK_PageUp:
+ return chattrboxes_page_up (cb);
+
+ case CK_Top:
+ return chattrboxes_home (cb);
+
+ case CK_Mark:
+ case CK_MarkAndDown:
+ {
+ chattr_toggle_select (cb, cb->pos); /* FIXME */
+ if (command == CK_MarkAndDown)
+ chattrboxes_down (cb);
+
+ return MSG_HANDLED;
+ }
+
+ default:
+ return MSG_NOT_HANDLED;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_key (WChattrBoxes * cb, int key)
+{
+ long command;
+
+ command = widget_lookup_key (WIDGET (cb), key);
+ if (command == CK_IgnoreKey)
+ return MSG_NOT_HANDLED;
+ return chattrboxes_execute_cmd (cb, command);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WChattrBoxes *cb = CHATTRBOXES (w);
+ WGroup *g = GROUP (w);
+
+ switch (msg)
+ {
+ case MSG_DRAW:
+ chattrboxes_draw (cb);
+ return MSG_HANDLED;
+
+ case MSG_NOTIFY:
+ {
+ /* handle checkboxes */
+ int i;
+
+ i = g_list_index (g->widgets, sender);
+ if (i >= 0)
+ {
+ int m;
+
+ i += cb->top;
+ m = check_attr_mod[i];
+ flags ^= check_attr[m].flags;
+ fileattrtext_fill (file_attr, flags);
+ chattr_toggle_select (cb, i);
+ flags_changed = TRUE;
+ return MSG_HANDLED;
+ }
+ }
+ return MSG_NOT_HANDLED;
+
+ case MSG_CHANGED_FOCUS:
+ /* sender is one of chattr checkboxes */
+ if (widget_get_state (sender, WST_FOCUSED))
+ {
+ int i;
+
+ i = g_list_index (g->widgets, sender);
+ cb->pos = cb->top + i;
+ }
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ {
+ cb_ret_t ret;
+
+ ret = chattrboxes_key (cb, parm);
+ if (ret != MSG_HANDLED)
+ ret = group_default_callback (w, NULL, MSG_KEY, parm, NULL);
+
+ return ret;
+ }
+
+ case MSG_ACTION:
+ return chattrboxes_execute_cmd (cb, parm);
+
+ case MSG_DESTROY:
+ /* save all states */
+ checkboxes_save_state (cb);
+ MC_FALLTHROUGH;
+
+ default:
+ return group_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+chattrboxes_handle_mouse_event (Widget * w, Gpm_Event * event)
+{
+ int mou;
+
+ mou = mouse_handle_event (w, event);
+ if (mou == MOU_UNHANDLED)
+ mou = group_handle_mouse_event (w, event);
+
+ return mou;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattrboxes_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WChattrBoxes *cb = CHATTRBOXES (w);
+
+ (void) event;
+
+ switch (msg)
+ {
+ case MSG_MOUSE_SCROLL_UP:
+ chattrboxes_up (cb);
+ break;
+
+ case MSG_MOUSE_SCROLL_DOWN:
+ chattrboxes_down (cb);
+ break;
+
+ default:
+ /* return MOU_UNHANDLED */
+ event->result.abort = TRUE;
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WChattrBoxes *
+chattrboxes_new (const WRect * r)
+{
+ WChattrBoxes *cb;
+ Widget *w;
+ WGroup *cbg;
+ int i;
+
+ cb = g_new0 (WChattrBoxes, 1);
+ w = WIDGET (cb);
+ cbg = GROUP (cb);
+ group_init (cbg, r, chattrboxes_callback, chattrboxes_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR;
+ w->mouse_handler = chattrboxes_handle_mouse_event;
+ w->keymap = chattr_map;
+
+ /* create checkboxes */
+ for (i = 0; i < r->lines; i++)
+ {
+ int m;
+ WCheck *check;
+
+ m = check_attr_mod[i];
+
+ check = check_new (i, 0, check_attr[m].state, NULL);
+ group_add_widget (cbg, check);
+ }
+
+ chattrboxes_rename (cb);
+
+ /* select first checkbox */
+ cbg->current = cbg->widgets;
+
+ return cb;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattr_init (void)
+{
+ static gboolean i18n = FALSE;
+ size_t i;
+
+ for (i = 0; i < check_attr_num; i++)
+ check_attr[i].selected = FALSE;
+
+ if (i18n)
+ return;
+
+ i18n = TRUE;
+
+ for (i = 0; i < check_attr_num; i++)
+ if (chattr_is_modifiable (i))
+ {
+ int width;
+
+#ifdef ENABLE_NLS
+ check_attr[i].text = _(check_attr[i].text);
+#endif
+
+ check_attr_mod[check_attr_mod_num++] = i;
+
+ width = 4 + str_term_width1 (check_attr[i].text); /* "(Q) text " */
+ check_attr_width = MAX (check_attr_width, width);
+ }
+
+ check_attr_width += 1 + 3 + 1; /* mark, [x] and space */
+
+ for (i = 0; i < BUTTONS; i++)
+ {
+#ifdef ENABLE_NLS
+ chattr_but[i].text = _(chattr_but[i].text);
+#endif
+
+ chattr_but[i].width = str_term_width1 (chattr_but[i].text) + 3; /* [], spaces and w/o & */
+ if (chattr_but[i].flags == DEFPUSH_BUTTON)
+ chattr_but[i].width += 2; /* <> */
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WDialog *
+chattr_dlg_create (WPanel * panel, const char *fname, unsigned long attr)
+{
+ Widget *mw = WIDGET (WIDGET (panel)->owner);
+ gboolean single_set;
+ WDialog *ch_dlg;
+ int lines, cols;
+ int checkboxes_lines = check_attr_mod_num;
+ size_t i;
+ int y;
+ Widget *dw;
+ WGroup *dg;
+ WChattrBoxes *cb;
+ const int cb_scrollbar_width = 1;
+ WRect r;
+
+ /* prepare to set up checkbox states */
+ for (i = 0; i < check_attr_num; i++)
+ check_attr[i].state = chattr_is_modifiable (i) && (attr & check_attr[i].flags) != 0;
+
+ cols = check_attr_width + cb_scrollbar_width;
+
+ single_set = (panel->marked < 2);
+
+ lines = 5 + checkboxes_lines + 4;
+ if (!single_set)
+ lines += 3;
+
+ if (lines >= mw->rect.lines - 2)
+ {
+ int dl;
+
+ dl = lines - (mw->rect.lines - 2);
+ lines -= dl;
+ checkboxes_lines -= dl;
+ }
+
+ ch_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols + wx * 2, WPOS_CENTER, FALSE, dialog_colors,
+ dlg_default_callback, NULL, "[Chattr]", _("Chattr command"));
+ dg = GROUP (ch_dlg);
+ dw = WIDGET (ch_dlg);
+
+ y = 2;
+ file_attr = fileattrtext_new (y, wx, fname, attr);
+ group_add_widget_autopos (dg, file_attr, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL);
+ y += WIDGET (file_attr)->rect.lines;
+ group_add_widget (dg, hline_new (y++, -1, -1));
+
+ if (cols < WIDGET (file_attr)->rect.cols)
+ {
+ r = dw->rect;
+ cols = WIDGET (file_attr)->rect.cols;
+ cols = MIN (cols, mw->rect.cols - wx * 2);
+ r.cols = cols + wx * 2;
+ r.lines = lines;
+ widget_set_size_rect (dw, &r);
+ }
+
+ checkboxes_lines = MIN (check_attr_mod_num, checkboxes_lines);
+ rect_init (&r, y++, wx, checkboxes_lines > 0 ? checkboxes_lines : 1, cols);
+ cb = chattrboxes_new (&r);
+ group_add_widget_autopos (dg, cb, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
+
+ y += checkboxes_lines - 1;
+ cols = 0;
+
+ for (i = single_set ? (BUTTONS - 2) : 0; i < BUTTONS; i++)
+ {
+ if (i == 0 || i == BUTTONS - 2)
+ group_add_widget (dg, hline_new (y++, -1, -1));
+
+ chattr_but[i].button = WIDGET (button_new (y, dw->rect.cols / 2 + 1 - chattr_but[i].width,
+ chattr_but[i].ret_cmd, chattr_but[i].flags,
+ chattr_but[i].text, NULL));
+ group_add_widget (dg, chattr_but[i].button);
+
+ i++;
+ chattr_but[i].button =
+ WIDGET (button_new (y++, dw->rect.cols / 2 + 2, chattr_but[i].ret_cmd,
+ chattr_but[i].flags, chattr_but[i].text, NULL));
+ group_add_widget (dg, chattr_but[i].button);
+
+ /* two buttons in a row */
+ cols =
+ MAX (cols, chattr_but[i - 1].button->rect.cols + 1 + chattr_but[i].button->rect.cols);
+ }
+
+ /* adjust dialog size and button positions */
+ cols += 6;
+ if (cols > dw->rect.cols)
+ {
+ r = dw->rect;
+ r.lines = lines;
+ r.cols = cols;
+ widget_set_size_rect (dw, &r);
+
+ /* dialog center */
+ cols = dw->rect.x + dw->rect.cols / 2 + 1;
+
+ for (i = single_set ? (BUTTONS - 2) : 0; i < BUTTONS; i++)
+ {
+ Widget *b;
+
+ b = chattr_but[i++].button;
+ r = b->rect;
+ r.x = cols - r.cols;
+ widget_set_size_rect (b, &r);
+
+ b = chattr_but[i].button;
+ r = b->rect;
+ r.x = cols + 1;
+ widget_set_size_rect (b, &r);
+ }
+ }
+
+ widget_select (WIDGET (cb));
+
+ return ch_dlg;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattr_done (gboolean need_update)
+{
+ if (need_update)
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const GString *
+next_file (const WPanel * panel)
+{
+ while (panel->dir.list[current_file].f.marked == 0)
+ current_file++;
+
+ return panel->dir.list[current_file].fname;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+try_chattr (const vfs_path_t * p, unsigned long m)
+{
+ const char *fname = NULL;
+
+ while (mc_fsetflags (p, m) == -1 && !ignore_all)
+ {
+ int my_errno = errno;
+ int result;
+ char *msg;
+
+ if (fname == NULL)
+ fname = x_basename (vfs_path_as_str (p));
+ msg = g_strdup_printf (_("Cannot chattr \"%s\"\n%s"), fname, unix_error_string (my_errno));
+ result =
+ query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
+ _("&Cancel"));
+ g_free (msg);
+
+ switch (result)
+ {
+ case 0:
+ /* try next file */
+ return TRUE;
+
+ case 1:
+ ignore_all = TRUE;
+ /* try next file */
+ return TRUE;
+
+ case 2:
+ /* retry this file */
+ break;
+
+ case 3:
+ default:
+ /* stop remain files processing */
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+do_chattr (WPanel * panel, const vfs_path_t * p, unsigned long m)
+{
+ gboolean ret;
+
+ m &= and_mask;
+ m |= or_mask;
+
+ ret = try_chattr (p, m);
+
+ do_file_mark (panel, current_file, 0);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattr_apply_mask (WPanel * panel, vfs_path_t * vpath, unsigned long m)
+{
+ gboolean ok;
+
+ if (!do_chattr (panel, vpath, m))
+ return;
+
+ do
+ {
+ const GString *fname;
+
+ fname = next_file (panel);
+ vpath = vfs_path_from_str (fname->str);
+ ok = (mc_fgetflags (vpath, &m) == 0);
+
+ if (!ok)
+ {
+ /* if current file was deleted outside mc -- try next file */
+ /* decrease panel->marked */
+ do_file_mark (panel, current_file, 0);
+
+ /* try next file */
+ ok = TRUE;
+ }
+ else
+ {
+ flags = m;
+ ok = do_chattr (panel, vpath, m);
+ vfs_path_free (vpath, TRUE);
+ }
+ }
+ while (ok && panel->marked != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+chattr_cmd (WPanel * panel)
+{
+ gboolean need_update = FALSE;
+ gboolean end_chattr = FALSE;
+
+ chattr_init ();
+
+ current_file = 0;
+ ignore_all = FALSE;
+
+ do
+ { /* do while any files remaining */
+ vfs_path_t *vpath;
+ WDialog *ch_dlg;
+ const GString *fname;
+ size_t i;
+ int result;
+
+ do_refresh ();
+
+ need_update = FALSE;
+ end_chattr = FALSE;
+
+ if (panel->marked != 0)
+ fname = next_file (panel); /* next marked file */
+ else
+ fname = panel_current_entry (panel)->fname; /* single file */
+
+ vpath = vfs_path_from_str (fname->str);
+
+ if (mc_fgetflags (vpath, &flags) != 0)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot get flags of \"%s\"\n%s"), fname->str,
+ unix_error_string (errno));
+ vfs_path_free (vpath, TRUE);
+ break;
+ }
+
+ flags_changed = FALSE;
+
+ ch_dlg = chattr_dlg_create (panel, fname->str, flags);
+ result = dlg_run (ch_dlg);
+ widget_destroy (WIDGET (ch_dlg));
+
+ switch (result)
+ {
+ case B_CANCEL:
+ end_chattr = TRUE;
+ break;
+
+ case B_ENTER:
+ if (flags_changed)
+ {
+ if (panel->marked <= 1)
+ {
+ /* single or last file */
+ if (mc_fsetflags (vpath, flags) == -1 && !ignore_all)
+ message (D_ERROR, MSG_ERROR, _("Cannot chattr \"%s\"\n%s"), fname->str,
+ unix_error_string (errno));
+ end_chattr = TRUE;
+ }
+ else if (!try_chattr (vpath, flags))
+ {
+ /* stop multiple files processing */
+ result = B_CANCEL;
+ end_chattr = TRUE;
+ }
+ }
+
+ need_update = TRUE;
+ break;
+
+ case B_SETALL:
+ case B_MARKED:
+ or_mask = 0;
+ and_mask = ~0;
+
+ for (i = 0; i < check_attr_num; i++)
+ if (chattr_is_modifiable (i) && (check_attr[i].selected || result == B_SETALL))
+ {
+ if (check_attr[i].state)
+ or_mask |= check_attr[i].flags;
+ else
+ and_mask &= ~check_attr[i].flags;
+ }
+
+ chattr_apply_mask (panel, vpath, flags);
+ need_update = TRUE;
+ end_chattr = TRUE;
+ break;
+
+ case B_SETMRK:
+ or_mask = 0;
+ and_mask = ~0;
+
+ for (i = 0; i < check_attr_num; i++)
+ if (chattr_is_modifiable (i) && check_attr[i].selected)
+ or_mask |= check_attr[i].flags;
+
+ chattr_apply_mask (panel, vpath, flags);
+ need_update = TRUE;
+ end_chattr = TRUE;
+ break;
+
+ case B_CLRMRK:
+ or_mask = 0;
+ and_mask = ~0;
+
+ for (i = 0; i < check_attr_num; i++)
+ if (chattr_is_modifiable (i) && check_attr[i].selected)
+ and_mask &= ~check_attr[i].flags;
+
+ chattr_apply_mask (panel, vpath, flags);
+ need_update = TRUE;
+ end_chattr = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (panel->marked != 0 && result != B_CANCEL)
+ {
+ do_file_mark (panel, current_file, 0);
+ need_update = TRUE;
+ }
+
+ vfs_path_free (vpath, TRUE);
+
+ }
+ while (panel->marked != 0 && !end_chattr);
+
+ chattr_done (need_update);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+chattr_get_as_str (unsigned long attr)
+{
+ static char str[32 + 1]; /* 32 bits in attributes (unsigned long) */
+
+ chattr_fill_str (attr, str);
+
+ return str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/chmod.c b/src/filemanager/chmod.c
new file mode 100644
index 0000000..c93bcbc
--- /dev/null
+++ b/src/filemanager/chmod.c
@@ -0,0 +1,664 @@
+/*
+ Chmod command -- for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ 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 chmod.c
+ * \brief Source: chmod command
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+#include "cmd.h" /* chmod_cmd() */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define PX 3
+#define PY 2
+
+#define B_MARKED B_USER
+#define B_SETALL (B_USER + 1)
+#define B_SETMRK (B_USER + 2)
+#define B_CLRMRK (B_USER + 3)
+
+#define BUTTONS 6
+#define BUTTONS_PERM 12
+#define LABELS 4
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct
+{
+ mode_t mode;
+ const char *text;
+ gboolean selected;
+ WCheck *check;
+} check_perm[BUTTONS_PERM] =
+{
+ /* *INDENT-OFF* */
+ { S_ISUID, N_("set &user ID on execution"), FALSE, NULL },
+ { S_ISGID, N_("set &group ID on execution"), FALSE, NULL },
+ { S_ISVTX, N_("stick&y bit"), FALSE, NULL },
+ { S_IRUSR, N_("&read by owner"), FALSE, NULL },
+ { S_IWUSR, N_("&write by owner"), FALSE, NULL },
+ { S_IXUSR, N_("e&xecute/search by owner"), FALSE, NULL },
+ { S_IRGRP, N_("rea&d by group"), FALSE, NULL },
+ { S_IWGRP, N_("write by grou&p"), FALSE, NULL },
+ { S_IXGRP, N_("execu&te/search by group"), FALSE, NULL },
+ { S_IROTH, N_("read &by others"), FALSE, NULL },
+ { S_IWOTH, N_("wr&ite by others"), FALSE, NULL },
+ { S_IXOTH, N_("execute/searc&h by others"), FALSE, NULL }
+ /* *INDENT-ON* */
+};
+
+static int check_perm_len = 0;
+
+static const char *file_info_labels[LABELS] = {
+ N_("Name:"),
+ N_("Permissions (octal):"),
+ N_("Owner name:"),
+ N_("Group name:")
+};
+
+static int file_info_labels_len = 0;
+
+static struct
+{
+ int ret_cmd;
+ button_flags_t flags;
+ int y; /* vertical position relatively to dialog bottom boundary */
+ int len;
+ const char *text;
+} chmod_but[BUTTONS] =
+{
+ /* *INDENT-OFF* */
+ { B_SETALL, NORMAL_BUTTON, 6, 0, N_("Set &all") },
+ { B_MARKED, NORMAL_BUTTON, 6, 0, N_("&Marked all") },
+ { B_SETMRK, NORMAL_BUTTON, 5, 0, N_("S&et marked") },
+ { B_CLRMRK, NORMAL_BUTTON, 5, 0, N_("C&lear marked") },
+ { B_ENTER, DEFPUSH_BUTTON, 3, 0, N_("&Set") },
+ { B_CANCEL, NORMAL_BUTTON, 3, 0, N_("&Cancel") }
+ /* *INDENT-ON* */
+};
+
+static gboolean mode_change;
+static int current_file;
+static gboolean ignore_all;
+
+static mode_t and_mask, or_mask, ch_mode;
+
+static WLabel *statl;
+static WGroupbox *file_gb;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chmod_init (void)
+{
+ static gboolean i18n = FALSE;
+ int i, len;
+
+ for (i = 0; i < BUTTONS_PERM; i++)
+ check_perm[i].selected = FALSE;
+
+ if (i18n)
+ return;
+
+ i18n = TRUE;
+
+#ifdef ENABLE_NLS
+ for (i = 0; i < BUTTONS_PERM; i++)
+ check_perm[i].text = _(check_perm[i].text);
+
+ for (i = 0; i < LABELS; i++)
+ file_info_labels[i] = _(file_info_labels[i]);
+
+ for (i = 0; i < BUTTONS; i++)
+ chmod_but[i].text = _(chmod_but[i].text);
+#endif /* ENABLE_NLS */
+
+ for (i = 0; i < BUTTONS_PERM; i++)
+ {
+ len = str_term_width1 (check_perm[i].text);
+ check_perm_len = MAX (check_perm_len, len);
+ }
+
+ check_perm_len += 1 + 3 + 1; /* mark, [x] and space */
+
+ for (i = 0; i < LABELS; i++)
+ {
+ len = str_term_width1 (file_info_labels[i]) + 2; /* spaces around */
+ file_info_labels_len = MAX (file_info_labels_len, len);
+ }
+
+ for (i = 0; i < BUTTONS; i++)
+ {
+ chmod_but[i].len = str_term_width1 (chmod_but[i].text) + 3; /* [], spaces and w/o & */
+ if (chmod_but[i].flags == DEFPUSH_BUTTON)
+ chmod_but[i].len += 2; /* <> */
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chmod_draw_select (const WDialog * h, int Id)
+{
+ widget_gotoyx (h, PY + Id + 1, PX + 1);
+ tty_print_char (check_perm[Id].selected ? '*' : ' ');
+ widget_gotoyx (h, PY + Id + 1, PX + 3);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chmod_toggle_select (const WDialog * h, int Id)
+{
+ check_perm[Id].selected = !check_perm[Id].selected;
+ tty_setcolor (COLOR_NORMAL);
+ chmod_draw_select (h, Id);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chmod_refresh (const WDialog * h)
+{
+ int i;
+ int y, x;
+
+ tty_setcolor (COLOR_NORMAL);
+
+ for (i = 0; i < BUTTONS_PERM; i++)
+ chmod_draw_select (h, i);
+
+ y = WIDGET (file_gb)->rect.y + 1;
+ x = WIDGET (file_gb)->rect.x + 2;
+
+ tty_gotoyx (y, x);
+ tty_print_string (file_info_labels[0]);
+ tty_gotoyx (y + 2, x);
+ tty_print_string (file_info_labels[1]);
+ tty_gotoyx (y + 4, x);
+ tty_print_string (file_info_labels[2]);
+ tty_gotoyx (y + 6, x);
+ tty_print_string (file_info_labels[3]);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chmod_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_DRAW:
+ frame_callback (w, NULL, MSG_DRAW, 0, NULL);
+ chmod_refresh (CONST_DIALOG (w->owner));
+ return MSG_HANDLED;
+
+ default:
+ return frame_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chmod_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WGroup *g = GROUP (w);
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_NOTIFY:
+ {
+ /* handle checkboxes */
+ int i;
+
+ /* whether notification was sent by checkbox? */
+ for (i = 0; i < BUTTONS_PERM; i++)
+ if (sender == WIDGET (check_perm[i].check))
+ break;
+
+ if (i < BUTTONS_PERM)
+ {
+ ch_mode ^= check_perm[i].mode;
+ label_set_textv (statl, "%o", (unsigned int) ch_mode);
+ chmod_toggle_select (h, i);
+ mode_change = TRUE;
+ return MSG_HANDLED;
+ }
+ }
+
+ return MSG_NOT_HANDLED;
+
+ case MSG_KEY:
+ if (parm == 'T' || parm == 't' || parm == KEY_IC)
+ {
+ int i;
+ unsigned long id;
+
+ id = group_get_current_widget_id (g);
+ for (i = 0; i < BUTTONS_PERM; i++)
+ if (id == WIDGET (check_perm[i].check)->id)
+ break;
+
+ if (i < BUTTONS_PERM)
+ {
+ chmod_toggle_select (h, i);
+ if (parm == KEY_IC)
+ group_select_next_widget (g);
+ return MSG_HANDLED;
+ }
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WDialog *
+chmod_dlg_create (WPanel * panel, const char *fname, const struct stat *sf_stat)
+{
+ gboolean single_set;
+ WDialog *ch_dlg;
+ WGroup *g;
+ int lines, cols;
+ int i, y;
+ int perm_gb_len;
+ int file_gb_len;
+ const char *c_fname, *c_fown, *c_fgrp;
+ char buffer[BUF_TINY];
+
+ mode_change = FALSE;
+
+ single_set = (panel->marked < 2);
+ perm_gb_len = check_perm_len + 2;
+ file_gb_len = file_info_labels_len + 2;
+ cols = str_term_width1 (fname) + 2 + 1;
+ file_gb_len = MAX (file_gb_len, cols);
+
+ lines = single_set ? 20 : 23;
+ cols = perm_gb_len + file_gb_len + 1 + 6;
+
+ if (cols > COLS)
+ {
+ /* shrink the right groupbox */
+ cols = COLS;
+ file_gb_len = cols - (perm_gb_len + 1 + 6);
+ }
+
+ ch_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors,
+ chmod_callback, NULL, "[Chmod]", _("Chmod command"));
+ g = GROUP (ch_dlg);
+
+ /* draw background */
+ ch_dlg->bg->callback = chmod_bg_callback;
+
+ group_add_widget (g, groupbox_new (PY, PX, BUTTONS_PERM + 2, perm_gb_len, _("Permission")));
+
+ for (i = 0; i < BUTTONS_PERM; i++)
+ {
+ check_perm[i].check = check_new (PY + i + 1, PX + 2, (ch_mode & check_perm[i].mode) != 0,
+ check_perm[i].text);
+ group_add_widget (g, check_perm[i].check);
+ }
+
+ file_gb = groupbox_new (PY, PX + perm_gb_len + 1, BUTTONS_PERM + 2, file_gb_len, _("File"));
+ group_add_widget (g, file_gb);
+
+ /* Set the labels */
+ y = PY + 2;
+ cols = PX + perm_gb_len + 3;
+ c_fname = str_trunc (fname, file_gb_len - 3);
+ group_add_widget (g, label_new (y, cols, c_fname));
+ g_snprintf (buffer, sizeof (buffer), "%o", (unsigned int) ch_mode);
+ statl = label_new (y + 2, cols, buffer);
+ group_add_widget (g, statl);
+ c_fown = str_trunc (get_owner (sf_stat->st_uid), file_gb_len - 3);
+ group_add_widget (g, label_new (y + 4, cols, c_fown));
+ c_fgrp = str_trunc (get_group (sf_stat->st_gid), file_gb_len - 3);
+ group_add_widget (g, label_new (y + 6, cols, c_fgrp));
+
+ if (!single_set)
+ {
+ i = 0;
+
+ group_add_widget (g, hline_new (lines - chmod_but[i].y - 1, -1, -1));
+
+ for (; i < BUTTONS - 2; i++)
+ {
+ y = lines - chmod_but[i].y;
+ group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 - chmod_but[i].len,
+ chmod_but[i].ret_cmd, chmod_but[i].flags,
+ chmod_but[i].text, NULL));
+ i++;
+ group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1,
+ chmod_but[i].ret_cmd, chmod_but[i].flags,
+ chmod_but[i].text, NULL));
+ }
+ }
+
+ i = BUTTONS - 2;
+ y = lines - chmod_but[i].y;
+ group_add_widget (g, hline_new (y - 1, -1, -1));
+ group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 - chmod_but[i].len,
+ chmod_but[i].ret_cmd, chmod_but[i].flags, chmod_but[i].text,
+ NULL));
+ i++;
+ group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1, chmod_but[i].ret_cmd,
+ chmod_but[i].flags, chmod_but[i].text, NULL));
+
+ /* select first checkbox */
+ widget_select (WIDGET (check_perm[0].check));
+
+ return ch_dlg;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chmod_done (gboolean need_update)
+{
+ if (need_update)
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const GString *
+next_file (const WPanel * panel)
+{
+ while (panel->dir.list[current_file].f.marked == 0)
+ current_file++;
+
+ return panel->dir.list[current_file].fname;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+try_chmod (const vfs_path_t * p, mode_t m)
+{
+ const char *fname = NULL;
+
+ while (mc_chmod (p, m) == -1 && !ignore_all)
+ {
+ int my_errno = errno;
+ int result;
+ char *msg;
+
+ if (fname == NULL)
+ fname = x_basename (vfs_path_as_str (p));
+ msg = g_strdup_printf (_("Cannot chmod \"%s\"\n%s"), fname, unix_error_string (my_errno));
+ result =
+ query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
+ _("&Cancel"));
+ g_free (msg);
+
+ switch (result)
+ {
+ case 0:
+ /* try next file */
+ return TRUE;
+
+ case 1:
+ ignore_all = TRUE;
+ /* try next file */
+ return TRUE;
+
+ case 2:
+ /* retry this file */
+ break;
+
+ case 3:
+ default:
+ /* stop remain files processing */
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+do_chmod (WPanel * panel, const vfs_path_t * p, struct stat *sf)
+{
+ gboolean ret;
+
+ sf->st_mode &= and_mask;
+ sf->st_mode |= or_mask;
+
+ ret = try_chmod (p, sf->st_mode);
+
+ do_file_mark (panel, current_file, 0);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+apply_mask (WPanel * panel, vfs_path_t * vpath, struct stat *sf)
+{
+ gboolean ok;
+
+ if (!do_chmod (panel, vpath, sf))
+ return;
+
+ do
+ {
+ const GString *fname;
+
+ fname = next_file (panel);
+ vpath = vfs_path_from_str (fname->str);
+ ok = (mc_stat (vpath, sf) == 0);
+
+ if (!ok)
+ {
+ /* if current file was deleted outside mc -- try next file */
+ /* decrease panel->marked */
+ do_file_mark (panel, current_file, 0);
+
+ /* try next file */
+ ok = TRUE;
+ }
+ else
+ {
+ ch_mode = sf->st_mode;
+
+ ok = do_chmod (panel, vpath, sf);
+ }
+
+ vfs_path_free (vpath, TRUE);
+ }
+ while (ok && panel->marked != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+chmod_cmd (WPanel * panel)
+{
+ gboolean need_update;
+ gboolean end_chmod;
+
+ chmod_init ();
+
+ current_file = 0;
+ ignore_all = FALSE;
+
+ do
+ { /* do while any files remaining */
+ vfs_path_t *vpath;
+ WDialog *ch_dlg;
+ struct stat sf_stat;
+ const GString *fname;
+ int i, result;
+
+ do_refresh ();
+
+ need_update = FALSE;
+ end_chmod = FALSE;
+
+ if (panel->marked != 0)
+ fname = next_file (panel); /* next marked file */
+ else
+ fname = panel_current_entry (panel)->fname; /* single file */
+
+ vpath = vfs_path_from_str (fname->str);
+
+ if (mc_stat (vpath, &sf_stat) != 0)
+ {
+ vfs_path_free (vpath, TRUE);
+ break;
+ }
+
+ ch_mode = sf_stat.st_mode;
+
+ ch_dlg = chmod_dlg_create (panel, fname->str, &sf_stat);
+ result = dlg_run (ch_dlg);
+
+ switch (result)
+ {
+ case B_CANCEL:
+ end_chmod = TRUE;
+ break;
+
+ case B_ENTER:
+ if (mode_change)
+ {
+ if (panel->marked <= 1)
+ {
+ /* single or last file */
+ if (mc_chmod (vpath, ch_mode) == -1 && !ignore_all)
+ message (D_ERROR, MSG_ERROR, _("Cannot chmod \"%s\"\n%s"), fname->str,
+ unix_error_string (errno));
+ end_chmod = TRUE;
+ }
+ else if (!try_chmod (vpath, ch_mode))
+ {
+ /* stop multiple files processing */
+ result = B_CANCEL;
+ end_chmod = TRUE;
+ }
+ }
+
+ need_update = TRUE;
+ break;
+
+ case B_SETALL:
+ case B_MARKED:
+ and_mask = or_mask = 0;
+ and_mask = ~and_mask;
+
+ for (i = 0; i < BUTTONS_PERM; i++)
+ if (check_perm[i].selected || result == B_SETALL)
+ {
+ if (check_perm[i].check->state)
+ or_mask |= check_perm[i].mode;
+ else
+ and_mask &= ~check_perm[i].mode;
+ }
+
+ apply_mask (panel, vpath, &sf_stat);
+ need_update = TRUE;
+ end_chmod = TRUE;
+ break;
+
+ case B_SETMRK:
+ and_mask = or_mask = 0;
+ and_mask = ~and_mask;
+
+ for (i = 0; i < BUTTONS_PERM; i++)
+ if (check_perm[i].selected)
+ or_mask |= check_perm[i].mode;
+
+ apply_mask (panel, vpath, &sf_stat);
+ need_update = TRUE;
+ end_chmod = TRUE;
+ break;
+
+ case B_CLRMRK:
+ and_mask = or_mask = 0;
+ and_mask = ~and_mask;
+
+ for (i = 0; i < BUTTONS_PERM; i++)
+ if (check_perm[i].selected)
+ and_mask &= ~check_perm[i].mode;
+
+ apply_mask (panel, vpath, &sf_stat);
+ need_update = TRUE;
+ end_chmod = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (panel->marked != 0 && result != B_CANCEL)
+ {
+ do_file_mark (panel, current_file, 0);
+ need_update = TRUE;
+ }
+
+ vfs_path_free (vpath, TRUE);
+
+ widget_destroy (WIDGET (ch_dlg));
+ }
+ while (panel->marked != 0 && !end_chmod);
+
+ chmod_done (need_update);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/chown.c b/src/filemanager/chown.c
new file mode 100644
index 0000000..1ce769f
--- /dev/null
+++ b/src/filemanager/chown.c
@@ -0,0 +1,552 @@
+/*
+ Chown command -- for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ 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 chown.c
+ * \brief Source: chown command
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+#include "src/setup.h" /* panels_options */
+
+#include "cmd.h" /* chown_cmd() */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define GH 12
+#define GW 21
+
+#define BUTTONS 5
+
+#define B_SETALL B_USER
+#define B_SETUSR (B_USER + 1)
+#define B_SETGRP (B_USER + 2)
+
+#define LABELS 5
+
+#define chown_label(n,txt) label_set_text (chown_label [n].l, txt)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct
+{
+ int ret_cmd;
+ button_flags_t flags;
+ int y;
+ int len;
+ const char *text;
+} chown_but[BUTTONS] =
+{
+ /* *INDENT-OFF* */
+ { B_SETALL, NORMAL_BUTTON, 5, 0, N_("Set &all") },
+ { B_SETGRP, NORMAL_BUTTON, 5, 0, N_("Set &groups") },
+ { B_SETUSR, NORMAL_BUTTON, 5, 0, N_("Set &users") },
+ { B_ENTER, DEFPUSH_BUTTON, 3, 0, N_("&Set") },
+ { B_CANCEL, NORMAL_BUTTON, 3, 0, N_("&Cancel") }
+ /* *INDENT-ON* */
+};
+
+/* summary length of three buttons */
+static int blen = 0;
+
+static struct
+{
+ int y;
+ WLabel *l;
+} chown_label[LABELS] =
+{
+ /* *INDENT-OFF* */
+ { 4, NULL },
+ { 6, NULL },
+ { 8, NULL },
+ { 10, NULL },
+ { 12, NULL }
+ /* *INDENT-ON* */
+};
+
+static int current_file;
+static gboolean ignore_all;
+
+static WListbox *l_user, *l_group;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chown_init (void)
+{
+ static gboolean i18n = FALSE;
+ int i;
+
+ if (i18n)
+ return;
+
+ i18n = TRUE;
+
+#ifdef ENABLE_NLS
+ for (i = 0; i < BUTTONS; i++)
+ chown_but[i].text = _(chown_but[i].text);
+#endif /* ENABLE_NLS */
+
+ for (i = 0; i < BUTTONS; i++)
+ {
+ chown_but[i].len = str_term_width1 (chown_but[i].text) + 3; /* [], spaces and w/o & */
+ if (chown_but[i].flags == DEFPUSH_BUTTON)
+ chown_but[i].len += 2; /* <> */
+
+ if (i < BUTTONS - 2)
+ blen += chown_but[i].len;
+ }
+
+ blen += 2;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chown_refresh (const Widget * h)
+{
+ int y = 3;
+ int x = 7 + GW * 2;
+
+ tty_setcolor (COLOR_NORMAL);
+
+ widget_gotoyx (h, y + 0, x);
+ tty_print_string (_("Name"));
+ widget_gotoyx (h, y + 2, x);
+ tty_print_string (_("Owner name"));
+ widget_gotoyx (h, y + 4, x);
+ tty_print_string (_("Group name"));
+ widget_gotoyx (h, y + 6, x);
+ tty_print_string (_("Size"));
+ widget_gotoyx (h, y + 8, x);
+ tty_print_string (_("Permission"));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chown_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_DRAW:
+ frame_callback (w, NULL, MSG_DRAW, 0, NULL);
+ chown_refresh (WIDGET (w->owner));
+ return MSG_HANDLED;
+
+ default:
+ return frame_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WDialog *
+chown_dlg_create (WPanel * panel)
+{
+ int single_set;
+ WDialog *ch_dlg;
+ WGroup *g;
+ int lines, cols;
+ int i, y;
+ struct passwd *l_pass;
+ struct group *l_grp;
+
+ single_set = (panel->marked < 2) ? 3 : 0;
+ lines = GH + 4 + (single_set != 0 ? 2 : 4);
+ cols = GW * 3 + 2 + 6;
+
+ ch_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, NULL, NULL,
+ "[Chown]", _("Chown command"));
+ g = GROUP (ch_dlg);
+
+ /* draw background */
+ ch_dlg->bg->callback = chown_bg_callback;
+
+ group_add_widget (g, groupbox_new (2, 3, GH, GW, _("User name")));
+ l_user = listbox_new (3, 4, GH - 2, GW - 2, FALSE, NULL);
+ group_add_widget (g, l_user);
+ /* add field for unknown names (numbers) */
+ listbox_add_item (l_user, LISTBOX_APPEND_AT_END, 0, _("<Unknown user>"), NULL, FALSE);
+ /* get and put user names in the listbox */
+ setpwent ();
+ while ((l_pass = getpwent ()) != NULL)
+ listbox_add_item (l_user, LISTBOX_APPEND_SORTED, 0, l_pass->pw_name, NULL, FALSE);
+ endpwent ();
+
+ group_add_widget (g, groupbox_new (2, 4 + GW, GH, GW, _("Group name")));
+ l_group = listbox_new (3, 5 + GW, GH - 2, GW - 2, FALSE, NULL);
+ group_add_widget (g, l_group);
+ /* add field for unknown names (numbers) */
+ listbox_add_item (l_group, LISTBOX_APPEND_AT_END, 0, _("<Unknown group>"), NULL, FALSE);
+ /* get and put group names in the listbox */
+ setgrent ();
+ while ((l_grp = getgrent ()) != NULL)
+ listbox_add_item (l_group, LISTBOX_APPEND_SORTED, 0, l_grp->gr_name, NULL, FALSE);
+ endgrent ();
+
+ group_add_widget (g, groupbox_new (2, 5 + GW * 2, GH, GW, _("File")));
+ /* add widgets for the file information */
+ for (i = 0; i < LABELS; i++)
+ {
+ chown_label[i].l = label_new (chown_label[i].y, 7 + GW * 2, NULL);
+ group_add_widget (g, chown_label[i].l);
+ }
+
+ if (single_set == 0)
+ {
+ int x;
+
+ group_add_widget (g, hline_new (lines - chown_but[0].y - 1, -1, -1));
+
+ y = lines - chown_but[0].y;
+ x = (cols - blen) / 2;
+
+ for (i = 0; i < BUTTONS - 2; i++)
+ {
+ group_add_widget (g, button_new (y, x, chown_but[i].ret_cmd, chown_but[i].flags,
+ chown_but[i].text, NULL));
+ x += chown_but[i].len + 1;
+ }
+ }
+
+ i = BUTTONS - 2;
+ y = lines - chown_but[i].y;
+ group_add_widget (g, hline_new (y - 1, -1, -1));
+ group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 - chown_but[i].len,
+ chown_but[i].ret_cmd, chown_but[i].flags, chown_but[i].text,
+ NULL));
+ i++;
+ group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1, chown_but[i].ret_cmd,
+ chown_but[i].flags, chown_but[i].text, NULL));
+
+ /* select first listbox */
+ widget_select (WIDGET (l_user));
+
+ return ch_dlg;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chown_done (gboolean need_update)
+{
+ if (need_update)
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const GString *
+next_file (const WPanel * panel)
+{
+ while (panel->dir.list[current_file].f.marked == 0)
+ current_file++;
+
+ return panel->dir.list[current_file].fname;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+try_chown (const vfs_path_t * p, uid_t u, gid_t g)
+{
+ const char *fname = NULL;
+
+ while (mc_chown (p, u, g) == -1 && !ignore_all)
+ {
+ int my_errno = errno;
+ int result;
+ char *msg;
+
+ if (fname == NULL)
+ fname = x_basename (vfs_path_as_str (p));
+ msg = g_strdup_printf (_("Cannot chown \"%s\"\n%s"), fname, unix_error_string (my_errno));
+ result =
+ query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
+ _("&Cancel"));
+ g_free (msg);
+
+ switch (result)
+ {
+ case 0:
+ /* try next file */
+ return TRUE;
+
+ case 1:
+ ignore_all = TRUE;
+ /* try next file */
+ return TRUE;
+
+ case 2:
+ /* retry this file */
+ break;
+
+ case 3:
+ default:
+ /* stop remain files processing */
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+do_chown (WPanel * panel, const vfs_path_t * p, uid_t u, gid_t g)
+{
+ gboolean ret;
+
+ ret = try_chown (p, u, g);
+
+ do_file_mark (panel, current_file, 0);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+apply_chowns (WPanel * panel, vfs_path_t * vpath, uid_t u, gid_t g)
+{
+ gboolean ok;
+
+ if (!do_chown (panel, vpath, u, g))
+ return;
+
+ do
+ {
+ const GString *fname;
+ struct stat sf;
+
+ fname = next_file (panel);
+ vpath = vfs_path_from_str (fname->str);
+ ok = (mc_stat (vpath, &sf) == 0);
+
+ if (!ok)
+ {
+ /* if current file was deleted outside mc -- try next file */
+ /* decrease panel->marked */
+ do_file_mark (panel, current_file, 0);
+
+ /* try next file */
+ ok = TRUE;
+ }
+ else
+ ok = do_chown (panel, vpath, u, g);
+
+ vfs_path_free (vpath, TRUE);
+ }
+ while (ok && panel->marked != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+chown_cmd (WPanel * panel)
+{
+ gboolean need_update;
+ gboolean end_chown;
+
+ chown_init ();
+
+ current_file = 0;
+ ignore_all = FALSE;
+
+ do
+ { /* do while any files remaining */
+ vfs_path_t *vpath;
+ WDialog *ch_dlg;
+ struct stat sf_stat;
+ const GString *fname;
+ int result;
+ char buffer[BUF_TINY];
+ uid_t new_user = (uid_t) (-1);
+ gid_t new_group = (gid_t) (-1);
+
+ do_refresh ();
+
+ need_update = FALSE;
+ end_chown = FALSE;
+
+ if (panel->marked != 0)
+ fname = next_file (panel); /* next marked file */
+ else
+ fname = panel_current_entry (panel)->fname; /* single file */
+
+ vpath = vfs_path_from_str (fname->str);
+
+ if (mc_stat (vpath, &sf_stat) != 0)
+ {
+ vfs_path_free (vpath, TRUE);
+ break;
+ }
+
+ ch_dlg = chown_dlg_create (panel);
+
+ /* select in listboxes */
+ listbox_set_current (l_user, listbox_search_text (l_user, get_owner (sf_stat.st_uid)));
+ listbox_set_current (l_group, listbox_search_text (l_group, get_group (sf_stat.st_gid)));
+
+ chown_label (0, str_trunc (fname->str, GW - 4));
+ chown_label (1, str_trunc (get_owner (sf_stat.st_uid), GW - 4));
+ chown_label (2, str_trunc (get_group (sf_stat.st_gid), GW - 4));
+ size_trunc_len (buffer, GW - 4, sf_stat.st_size, 0, panels_options.kilobyte_si);
+ chown_label (3, buffer);
+ chown_label (4, string_perm (sf_stat.st_mode));
+
+ result = dlg_run (ch_dlg);
+
+ switch (result)
+ {
+ case B_CANCEL:
+ end_chown = TRUE;
+ break;
+
+ case B_ENTER:
+ case B_SETALL:
+ {
+ struct group *grp;
+ struct passwd *user;
+ char *text;
+
+ listbox_get_current (l_group, &text, NULL);
+ grp = getgrnam (text);
+ if (grp != NULL)
+ new_group = grp->gr_gid;
+ listbox_get_current (l_user, &text, NULL);
+ user = getpwnam (text);
+ if (user != NULL)
+ new_user = user->pw_uid;
+ if (result == B_ENTER)
+ {
+ if (panel->marked <= 1)
+ {
+ /* single or last file */
+ if (mc_chown (vpath, new_user, new_group) == -1)
+ message (D_ERROR, MSG_ERROR, _("Cannot chown \"%s\"\n%s"),
+ fname->str, unix_error_string (errno));
+ end_chown = TRUE;
+ }
+ else if (!try_chown (vpath, new_user, new_group))
+ {
+ /* stop multiple files processing */
+ result = B_CANCEL;
+ end_chown = TRUE;
+ }
+ }
+ else
+ {
+ apply_chowns (panel, vpath, new_user, new_group);
+ end_chown = TRUE;
+ }
+
+ need_update = TRUE;
+ break;
+ }
+
+ case B_SETUSR:
+ {
+ struct passwd *user;
+ char *text;
+
+ listbox_get_current (l_user, &text, NULL);
+ user = getpwnam (text);
+ if (user != NULL)
+ {
+ new_user = user->pw_uid;
+ apply_chowns (panel, vpath, new_user, new_group);
+ need_update = TRUE;
+ end_chown = TRUE;
+ }
+ break;
+ }
+
+ case B_SETGRP:
+ {
+ struct group *grp;
+ char *text;
+
+ listbox_get_current (l_group, &text, NULL);
+ grp = getgrnam (text);
+ if (grp != NULL)
+ {
+ new_group = grp->gr_gid;
+ apply_chowns (panel, vpath, new_user, new_group);
+ need_update = TRUE;
+ end_chown = TRUE;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (panel->marked != 0 && result != B_CANCEL)
+ {
+ do_file_mark (panel, current_file, 0);
+ need_update = TRUE;
+ }
+
+ vfs_path_free (vpath, TRUE);
+
+ widget_destroy (WIDGET (ch_dlg));
+ }
+ while (panel->marked != 0 && !end_chown);
+
+ chown_done (need_update);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/cmd.c b/src/filemanager/cmd.c
new file mode 100644
index 0000000..8c33fd8
--- /dev/null
+++ b/src/filemanager/cmd.c
@@ -0,0 +1,1470 @@
+/*
+ Routines invoked by a function key
+ They normally operate on the current panel.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2022
+
+ 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 cmd.c
+ * \brief Source: routines invoked by a function key
+ *
+ * They normally operate on the current panel.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_MMAP
+#include <sys/mman.h>
+#endif
+#ifdef ENABLE_VFS_NET
+#include <netdb.h>
+#endif
+#include <unistd.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* LINES, tty_touch_screen() */
+#include "lib/tty/key.h" /* ALT() macro */
+#include "lib/mcconfig.h"
+#include "lib/filehighlight.h" /* MC_FHL_INI_FILE */
+#include "lib/vfs/vfs.h"
+#include "lib/fileloc.h"
+#include "lib/strutil.h"
+#include "lib/file-entry.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+#include "lib/keybind.h" /* CK_Down, CK_History */
+#include "lib/event.h" /* mc_event_raise() */
+
+#include "src/setup.h"
+#include "src/execute.h" /* toggle_panels() */
+#include "src/history.h"
+#include "src/usermenu.h" /* MC_GLOBAL_MENU */
+#include "src/util.h" /* check_for_default() */
+
+#include "src/viewer/mcviewer.h"
+
+#ifdef USE_INTERNAL_EDIT
+#include "src/editor/edit.h"
+#endif
+
+#ifdef USE_DIFF_VIEW
+#include "src/diffviewer/ydiff.h"
+#endif
+
+#include "fileopctx.h"
+#include "filenot.h"
+#include "hotlist.h" /* hotlist_show() */
+#include "tree.h" /* tree_chdir() */
+#include "filemanager.h" /* change_panel() */
+#include "command.h" /* cmdline */
+#include "layout.h" /* get_current_type() */
+#include "ext.h" /* regex_command() */
+#include "boxes.h" /* cd_box() */
+#include "dir.h"
+#include "cd.h"
+
+#include "cmd.h" /* Our definitions */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifdef HAVE_MMAP
+#ifndef MAP_FILE
+#define MAP_FILE 0
+#endif
+#endif /* HAVE_MMAP */
+
+/*** file scope type declarations ****************************************************************/
+
+enum CompareMode
+{
+ compare_quick = 0,
+ compare_size_only,
+ compare_thourough
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+#ifdef ENABLE_VFS_NET
+static const char *machine_str = N_("Enter machine name (F1 for details):");
+#endif /* ENABLE_VFS_NET */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Run viewer (internal or external) on the current file.
+ * If @plain_view is TRUE, force internal viewer and raw mode (used for F13).
+ */
+static void
+do_view_cmd (WPanel * panel, gboolean plain_view)
+{
+ const file_entry_t *fe;
+
+ fe = panel_current_entry (panel);
+
+ /* Directories are viewed by changing to them */
+ if (S_ISDIR (fe->st.st_mode) || link_isdir (fe))
+ {
+ vfs_path_t *fname_vpath;
+
+ if (confirm_view_dir && (panel->marked != 0 || panel->dirs_marked != 0) &&
+ query_dialog (_("Confirmation"), _("Files tagged, want to cd?"), D_NORMAL, 2,
+ _("&Yes"), _("&No")) != 0)
+ return;
+
+ fname_vpath = vfs_path_from_str (fe->fname->str);
+ if (!panel_cd (panel, fname_vpath, cd_exact))
+ cd_error_message (fe->fname->str);
+ vfs_path_free (fname_vpath, TRUE);
+ }
+ else
+ {
+ vfs_path_t *filename_vpath;
+
+ filename_vpath = vfs_path_from_str (fe->fname->str);
+ view_file (filename_vpath, plain_view, use_internal_view);
+ vfs_path_free (filename_vpath, TRUE);
+ }
+
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+do_edit (const vfs_path_t * what_vpath)
+{
+ edit_file_at_line (what_vpath, use_internal_edit, 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+compare_files (const vfs_path_t * vpath1, const vfs_path_t * vpath2, off_t size)
+{
+ int file1;
+ int result = -1; /* Different by default */
+
+ if (size == 0)
+ return 0;
+
+ file1 = open (vfs_path_as_str (vpath1), O_RDONLY);
+ if (file1 >= 0)
+ {
+ int file2;
+
+ file2 = open (vfs_path_as_str (vpath2), O_RDONLY);
+ if (file2 >= 0)
+ {
+#ifdef HAVE_MMAP
+ char *data1;
+
+ /* Ugly if jungle */
+ data1 = mmap (0, size, PROT_READ, MAP_FILE | MAP_PRIVATE, file1, 0);
+ if (data1 != (char *) -1)
+ {
+ char *data2;
+
+ data2 = mmap (0, size, PROT_READ, MAP_FILE | MAP_PRIVATE, file2, 0);
+ if (data2 != (char *) -1)
+ {
+ rotate_dash (TRUE);
+ result = memcmp (data1, data2, size);
+ munmap (data2, size);
+ }
+ munmap (data1, size);
+ }
+#else
+ /* Don't have mmap() :( Even more ugly :) */
+ char buf1[BUFSIZ], buf2[BUFSIZ];
+ int n1, n2;
+
+ rotate_dash (TRUE);
+ do
+ {
+ while ((n1 = read (file1, buf1, sizeof (buf1))) == -1 && errno == EINTR)
+ ;
+ while ((n2 = read (file2, buf2, sizeof (buf2))) == -1 && errno == EINTR)
+ ;
+ }
+ while (n1 == n2 && n1 == sizeof (buf1) && memcmp (buf1, buf2, sizeof (buf1)) == 0);
+ result = (n1 != n2) || memcmp (buf1, buf2, n1);
+#endif /* !HAVE_MMAP */
+ close (file2);
+ }
+ close (file1);
+ }
+ rotate_dash (FALSE);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+compare_dir (WPanel * panel, const WPanel * other, enum CompareMode mode)
+{
+ int i, j;
+
+ /* No marks by default */
+ panel->marked = 0;
+ panel->total = 0;
+ panel->dirs_marked = 0;
+
+ /* Handle all files in the panel */
+ for (i = 0; i < panel->dir.len; i++)
+ {
+ file_entry_t *source = &panel->dir.list[i];
+ const char *source_fname;
+
+ /* Default: unmarked */
+ file_mark (panel, i, 0);
+
+ /* Skip directories */
+ if (S_ISDIR (source->st.st_mode))
+ continue;
+
+ source_fname = source->fname->str;
+ if (panel->is_panelized)
+ source_fname = x_basename (source_fname);
+
+ /* Search the corresponding entry from the other panel */
+ for (j = 0; j < other->dir.len; j++)
+ {
+ const char *other_fname;
+
+ other_fname = other->dir.list[j].fname->str;
+ if (other->is_panelized)
+ other_fname = x_basename (other_fname);
+
+ if (strcmp (source_fname, other_fname) == 0)
+ break;
+ }
+
+ if (j >= other->dir.len)
+ /* Not found -> mark */
+ do_file_mark (panel, i, 1);
+ else
+ {
+ /* Found */
+ file_entry_t *target = &other->dir.list[j];
+
+ if (mode != compare_size_only)
+ /* Older version is not marked */
+ if (source->st.st_mtime < target->st.st_mtime)
+ continue;
+
+ /* Newer version with different size is marked */
+ if (source->st.st_size != target->st.st_size)
+ {
+ do_file_mark (panel, i, 1);
+ continue;
+ }
+
+ if (mode == compare_size_only)
+ continue;
+
+ if (mode == compare_quick)
+ {
+ /* Thorough compare off, compare only time stamps */
+ /* Mark newer version, don't mark version with the same date */
+ if (source->st.st_mtime > target->st.st_mtime)
+ do_file_mark (panel, i, 1);
+
+ continue;
+ }
+
+ /* Thorough compare on, do byte-by-byte comparison */
+ {
+ vfs_path_t *src_name, *dst_name;
+
+ src_name =
+ vfs_path_append_new (panel->cwd_vpath, source->fname->str, (char *) NULL);
+ dst_name =
+ vfs_path_append_new (other->cwd_vpath, target->fname->str, (char *) NULL);
+ if (compare_files (src_name, dst_name, source->st.st_size))
+ do_file_mark (panel, i, 1);
+ vfs_path_free (src_name, TRUE);
+ vfs_path_free (dst_name, TRUE);
+ }
+ }
+ } /* for (i ...) */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+do_link (link_type_t link_type, const char *fname)
+{
+ char *dest = NULL, *src = NULL;
+ vfs_path_t *dest_vpath = NULL;
+
+ if (link_type == LINK_HARDLINK)
+ {
+ vfs_path_t *fname_vpath;
+
+ src = g_strdup_printf (_("Link %s to:"), str_trunc (fname, 46));
+ dest =
+ input_expand_dialog (_("Link"), src, MC_HISTORY_FM_LINK, "", INPUT_COMPLETE_FILENAMES);
+ if (dest == NULL || *dest == '\0')
+ goto cleanup;
+
+ save_cwds_stat ();
+
+ fname_vpath = vfs_path_from_str (fname);
+ dest_vpath = vfs_path_from_str (dest);
+ if (mc_link (fname_vpath, dest_vpath) == -1)
+ message (D_ERROR, MSG_ERROR, _("link: %s"), unix_error_string (errno));
+ vfs_path_free (fname_vpath, TRUE);
+ }
+ else
+ {
+ vfs_path_t *s, *d;
+
+ /* suggest the full path for symlink, and either the full or
+ relative path to the file it points to */
+ s = vfs_path_append_new (current_panel->cwd_vpath, fname, (char *) NULL);
+
+ if (get_other_type () == view_listing)
+ d = vfs_path_append_new (other_panel->cwd_vpath, fname, (char *) NULL);
+ else
+ d = vfs_path_from_str (fname);
+
+ if (link_type == LINK_SYMLINK_RELATIVE)
+ {
+ char *s_str;
+
+ s_str = diff_two_paths (other_panel->cwd_vpath, s);
+ vfs_path_free (s, TRUE);
+ s = vfs_path_from_str_flags (s_str, VPF_NO_CANON);
+ g_free (s_str);
+ }
+
+ symlink_box (s, d, &dest, &src);
+ vfs_path_free (d, TRUE);
+ vfs_path_free (s, TRUE);
+
+ if (dest == NULL || *dest == '\0' || src == NULL || *src == '\0')
+ goto cleanup;
+
+ save_cwds_stat ();
+
+ dest_vpath = vfs_path_from_str_flags (dest, VPF_NO_CANON);
+
+ s = vfs_path_from_str (src);
+ if (mc_symlink (dest_vpath, s) == -1)
+ message (D_ERROR, MSG_ERROR, _("symlink: %s"), unix_error_string (errno));
+ vfs_path_free (s, TRUE);
+ }
+
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ repaint_screen ();
+
+ cleanup:
+ vfs_path_free (dest_vpath, TRUE);
+ g_free (src);
+ g_free (dest);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if defined(ENABLE_VFS_UNDELFS) || defined(ENABLE_VFS_NET)
+static void
+nice_cd (const char *text, const char *xtext, const char *help,
+ const char *history_name, const char *prefix, int to_home, gboolean strip_password)
+{
+ char *machine;
+ char *cd_path;
+
+ machine =
+ input_dialog_help (text, xtext, help, history_name, INPUT_LAST_TEXT, strip_password,
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_HOSTNAMES |
+ INPUT_COMPLETE_USERNAMES);
+ if (machine == NULL)
+ return;
+
+ to_home = 0; /* FIXME: how to solve going to home nicely? /~/ is
+ ugly as hell and leads to problems in vfs layer */
+
+ if (strncmp (prefix, machine, strlen (prefix)) == 0)
+ cd_path = g_strconcat (machine, to_home ? "/~/" : (char *) NULL, (char *) NULL);
+ else
+ cd_path = g_strconcat (prefix, machine, to_home ? "/~/" : (char *) NULL, (char *) NULL);
+
+ g_free (machine);
+
+ if (!IS_PATH_SEP (*cd_path))
+ {
+ char *tmp = cd_path;
+
+ cd_path = g_strconcat (PATH_SEP_STR, tmp, (char *) NULL);
+ g_free (tmp);
+ }
+
+ {
+ panel_view_mode_t save_type;
+ vfs_path_t *cd_vpath;
+
+ save_type = get_panel_type (MENU_PANEL_IDX);
+
+ if (save_type != view_listing)
+ create_panel (MENU_PANEL_IDX, view_listing);
+
+ cd_vpath = vfs_path_from_str_flags (cd_path, VPF_NO_CANON);
+ if (!panel_do_cd (MENU_PANEL, cd_vpath, cd_parse_command))
+ {
+ cd_error_message (cd_path);
+
+ if (save_type != view_listing)
+ create_panel (MENU_PANEL_IDX, save_type);
+ }
+ vfs_path_free (cd_vpath, TRUE);
+ }
+ g_free (cd_path);
+
+ /* In case of passive panel, restore current VFS directory that was changed in panel_do_cd() */
+ if (MENU_PANEL != current_panel)
+ (void) mc_chdir (current_panel->cwd_vpath);
+}
+#endif /* ENABLE_VFS_UNDELFS || ENABLE_VFS_NET */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+configure_panel_listing (WPanel * p, int list_format, int brief_cols, gboolean use_msformat,
+ char **user, char **status)
+{
+ p->user_mini_status = use_msformat;
+ p->list_format = list_format;
+
+ if (list_format == list_brief)
+ p->brief_cols = brief_cols;
+
+ if (list_format == list_user || use_msformat)
+ {
+ g_free (p->user_format);
+ p->user_format = *user;
+ *user = NULL;
+
+ g_free (p->user_status_format[list_format]);
+ p->user_status_format[list_format] = *status;
+ *status = NULL;
+
+ set_panel_formats (p);
+ }
+
+ set_panel_formats (p);
+ do_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+switch_to_listing (int panel_index)
+{
+ if (get_panel_type (panel_index) != view_listing)
+ create_panel (panel_index, view_listing);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+view_file_at_line (const vfs_path_t * filename_vpath, gboolean plain_view, gboolean internal,
+ long start_line, off_t search_start, off_t search_end)
+{
+ gboolean ret = TRUE;
+
+ if (plain_view)
+ {
+ mcview_mode_flags_t changed_flags;
+
+ mcview_clear_mode_flags (&changed_flags);
+ mcview_altered_flags.hex = FALSE;
+ mcview_altered_flags.magic = FALSE;
+ mcview_altered_flags.nroff = FALSE;
+ if (mcview_global_flags.hex)
+ changed_flags.hex = TRUE;
+ if (mcview_global_flags.magic)
+ changed_flags.magic = TRUE;
+ if (mcview_global_flags.nroff)
+ changed_flags.nroff = TRUE;
+ mcview_global_flags.hex = FALSE;
+ mcview_global_flags.magic = FALSE;
+ mcview_global_flags.nroff = FALSE;
+
+ ret = mcview_viewer (NULL, filename_vpath, start_line, search_start, search_end);
+
+ if (changed_flags.hex && !mcview_altered_flags.hex)
+ mcview_global_flags.hex = TRUE;
+ if (changed_flags.magic && !mcview_altered_flags.magic)
+ mcview_global_flags.magic = TRUE;
+ if (changed_flags.nroff && !mcview_altered_flags.nroff)
+ mcview_global_flags.nroff = TRUE;
+
+ dialog_switch_process_pending ();
+ }
+ else if (internal)
+ {
+ char view_entry[BUF_TINY];
+
+ if (start_line > 0)
+ g_snprintf (view_entry, sizeof (view_entry), "View:%ld", start_line);
+ else
+ strcpy (view_entry, "View");
+
+ ret = (regex_command (filename_vpath, view_entry) == 0);
+ if (ret)
+ {
+ ret = mcview_viewer (NULL, filename_vpath, start_line, search_start, search_end);
+ dialog_switch_process_pending ();
+ }
+ }
+ else
+ {
+ static const char *viewer = NULL;
+
+ if (viewer == NULL)
+ {
+ viewer = getenv ("VIEWER");
+ if (viewer == NULL)
+ viewer = getenv ("PAGER");
+ if (viewer == NULL)
+ viewer = "view";
+ }
+
+ execute_external_editor_or_viewer (viewer, filename_vpath, start_line);
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** view_file (filename, plain_view, internal)
+ *
+ * Inputs:
+ * filename_vpath: The file name to view
+ * plain_view: If set does not do any fancy pre-processing (no filtering) and
+ * always invokes the internal viewer.
+ * internal: If set uses the internal viewer, otherwise an external viewer.
+ */
+
+gboolean
+view_file (const vfs_path_t * filename_vpath, gboolean plain_view, gboolean internal)
+{
+ return view_file_at_line (filename_vpath, plain_view, internal, 0, 0, 0);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/** Run user's preferred viewer on the current file */
+
+void
+view_cmd (WPanel * panel)
+{
+ do_view_cmd (panel, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Ask for file and run user's preferred viewer on it */
+
+void
+view_file_cmd (const WPanel * panel)
+{
+ char *filename;
+ vfs_path_t *vpath;
+
+ filename =
+ input_expand_dialog (_("View file"), _("Filename:"),
+ MC_HISTORY_FM_VIEW_FILE, panel_current_entry (panel)->fname->str,
+ INPUT_COMPLETE_FILENAMES);
+ if (filename == NULL)
+ return;
+
+ vpath = vfs_path_from_str (filename);
+ g_free (filename);
+ view_file (vpath, FALSE, use_internal_view);
+ vfs_path_free (vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Run plain internal viewer on the current file */
+void
+view_raw_cmd (WPanel * panel)
+{
+ do_view_cmd (panel, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+view_filtered_cmd (const WPanel * panel)
+{
+ char *command;
+ const char *initial_command;
+
+ if (input_is_empty (cmdline))
+ initial_command = panel_current_entry (panel)->fname->str;
+ else
+ initial_command = input_get_ctext (cmdline);
+
+ command =
+ input_dialog (_("Filtered view"),
+ _("Filter command and arguments:"),
+ MC_HISTORY_FM_FILTERED_VIEW, initial_command,
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
+
+ if (command != NULL)
+ {
+ mcview_viewer (command, NULL, 0, 0, 0);
+ g_free (command);
+ dialog_switch_process_pending ();
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_file_at_line (const vfs_path_t * what_vpath, gboolean internal, long start_line)
+{
+
+#ifdef USE_INTERNAL_EDIT
+ if (internal)
+ edit_file (what_vpath, start_line);
+ else
+#endif /* USE_INTERNAL_EDIT */
+ {
+ static const char *editor = NULL;
+
+ (void) internal;
+
+ if (editor == NULL)
+ {
+ editor = getenv ("EDITOR");
+ if (editor == NULL)
+ editor = get_default_editor ();
+ }
+
+ execute_external_editor_or_viewer (editor, what_vpath, start_line);
+ }
+
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+
+#ifdef USE_INTERNAL_EDIT
+ if (use_internal_edit)
+ dialog_switch_process_pending ();
+ else
+#endif /* USE_INTERNAL_EDIT */
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_cmd (const WPanel * panel)
+{
+ vfs_path_t *fname;
+
+ fname = vfs_path_from_str (panel_current_entry (panel)->fname->str);
+ if (regex_command (fname, "Edit") == 0)
+ do_edit (fname);
+ vfs_path_free (fname, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef USE_INTERNAL_EDIT
+void
+edit_cmd_force_internal (const WPanel * panel)
+{
+ vfs_path_t *fname;
+
+ fname = vfs_path_from_str (panel_current_entry (panel)->fname->str);
+ if (regex_command (fname, "Edit") == 0)
+ edit_file_at_line (fname, TRUE, 1);
+ vfs_path_free (fname, TRUE);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_cmd_new (void)
+{
+ vfs_path_t *fname_vpath = NULL;
+
+ if (editor_ask_filename_before_edit)
+ {
+ char *fname;
+
+ fname = input_expand_dialog (_("Edit file"), _("Enter file name:"),
+ MC_HISTORY_EDIT_LOAD, "", INPUT_COMPLETE_FILENAMES);
+ if (fname == NULL)
+ return;
+
+ if (*fname != '\0')
+ fname_vpath = vfs_path_from_str (fname);
+
+ g_free (fname);
+ }
+
+#ifdef HAVE_CHARSET
+ mc_global.source_codepage = default_source_codepage;
+#endif
+ do_edit (fname_vpath);
+
+ vfs_path_free (fname_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mkdir_cmd (WPanel * panel)
+{
+ const file_entry_t *fe;
+ char *dir;
+ const char *name = "";
+
+ fe = panel_current_entry (panel);
+
+ /* If 'on' then automatically fills name with current item name */
+ if (auto_fill_mkdir_name && !DIR_IS_DOTDOT (fe->fname->str))
+ name = fe->fname->str;
+
+ dir =
+ input_expand_dialog (_("Create a new Directory"),
+ _("Enter directory name:"), MC_HISTORY_FM_MKDIR, name,
+ INPUT_COMPLETE_FILENAMES);
+
+ if (dir != NULL && *dir != '\0')
+ {
+ vfs_path_t *absdir;
+
+ if (IS_PATH_SEP (dir[0]) || dir[0] == '~')
+ absdir = vfs_path_from_str (dir);
+ else
+ {
+ /* possible escaped '~' */
+ /* allow create directory with name '~' */
+ char *tmpdir = dir;
+
+ if (dir[0] == '\\' && dir[1] == '~')
+ tmpdir = dir + 1;
+
+ absdir = vfs_path_append_new (panel->cwd_vpath, tmpdir, (char *) NULL);
+ }
+
+ save_cwds_stat ();
+
+ if (my_mkdir (absdir, 0777) != 0)
+ message (D_ERROR, MSG_ERROR, "%s", unix_error_string (errno));
+ else
+ {
+ update_panels (UP_OPTIMIZE, dir);
+ repaint_screen ();
+ select_item (panel);
+ }
+
+ vfs_path_free (absdir, TRUE);
+ }
+ g_free (dir);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+reread_cmd (void)
+{
+ panel_update_flags_t flag = UP_ONLY_CURRENT;
+
+ if (get_current_type () == view_listing && get_other_type () == view_listing &&
+ vfs_path_equal (current_panel->cwd_vpath, other_panel->cwd_vpath))
+ flag = UP_OPTIMIZE;
+
+ update_panels (UP_RELOAD | flag, UP_KEEPSEL);
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+ext_cmd (void)
+{
+ vfs_path_t *extdir_vpath;
+ int dir = 0;
+
+ if (geteuid () == 0)
+ dir = query_dialog (_("Extension file edit"),
+ _("Which extension file you want to edit?"), D_NORMAL, 2,
+ _("&User"), _("&System Wide"));
+
+ extdir_vpath = vfs_path_build_filename (mc_global.sysconfig_dir, MC_EXT_FILE, (char *) NULL);
+
+ if (dir == 0)
+ {
+ vfs_path_t *buffer_vpath;
+
+ buffer_vpath = mc_config_get_full_vpath (MC_EXT_FILE);
+ check_for_default (extdir_vpath, buffer_vpath);
+ do_edit (buffer_vpath);
+ vfs_path_free (buffer_vpath, TRUE);
+ }
+ else if (dir == 1)
+ {
+ if (!exist_file (vfs_path_get_last_path_str (extdir_vpath)))
+ {
+ vfs_path_free (extdir_vpath, TRUE);
+ extdir_vpath =
+ vfs_path_build_filename (mc_global.share_data_dir, MC_EXT_FILE, (char *) NULL);
+ }
+ do_edit (extdir_vpath);
+ }
+
+ vfs_path_free (extdir_vpath, TRUE);
+ flush_extension_file ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** edit file menu for mc */
+
+void
+edit_mc_menu_cmd (void)
+{
+ vfs_path_t *buffer_vpath;
+ vfs_path_t *menufile_vpath;
+ int dir = 0;
+
+ query_set_sel (1);
+ dir = query_dialog (_("Menu edit"),
+ _("Which menu file do you want to edit?"),
+ D_NORMAL, geteuid ()? 2 : 3, _("&Local"), _("&User"), _("&System Wide"));
+
+ menufile_vpath =
+ vfs_path_build_filename (mc_global.sysconfig_dir, MC_GLOBAL_MENU, (char *) NULL);
+
+ if (!exist_file (vfs_path_get_last_path_str (menufile_vpath)))
+ {
+ vfs_path_free (menufile_vpath, TRUE);
+ menufile_vpath =
+ vfs_path_build_filename (mc_global.share_data_dir, MC_GLOBAL_MENU, (char *) NULL);
+ }
+
+ switch (dir)
+ {
+ case 0:
+ buffer_vpath = vfs_path_from_str (MC_LOCAL_MENU);
+ check_for_default (menufile_vpath, buffer_vpath);
+ chmod (vfs_path_get_last_path_str (buffer_vpath), 0600);
+ break;
+
+ case 1:
+ buffer_vpath = mc_config_get_full_vpath (MC_USERMENU_FILE);
+ check_for_default (menufile_vpath, buffer_vpath);
+ break;
+
+ case 2:
+ buffer_vpath =
+ vfs_path_build_filename (mc_global.sysconfig_dir, MC_GLOBAL_MENU, (char *) NULL);
+ if (!exist_file (vfs_path_get_last_path_str (buffer_vpath)))
+ {
+ vfs_path_free (buffer_vpath, TRUE);
+ buffer_vpath =
+ vfs_path_build_filename (mc_global.share_data_dir, MC_GLOBAL_MENU, (char *) NULL);
+ }
+ break;
+
+ default:
+ vfs_path_free (menufile_vpath, TRUE);
+ return;
+ }
+
+ do_edit (buffer_vpath);
+
+ vfs_path_free (buffer_vpath, TRUE);
+ vfs_path_free (menufile_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_fhl_cmd (void)
+{
+ vfs_path_t *fhlfile_vpath = NULL;
+ int dir = 0;
+
+ if (geteuid () == 0)
+ dir = query_dialog (_("Highlighting groups file edit"),
+ _("Which highlighting file you want to edit?"), D_NORMAL, 2,
+ _("&User"), _("&System Wide"));
+
+ fhlfile_vpath =
+ vfs_path_build_filename (mc_global.sysconfig_dir, MC_FHL_INI_FILE, (char *) NULL);
+
+ if (dir == 0)
+ {
+ vfs_path_t *buffer_vpath;
+
+ buffer_vpath = mc_config_get_full_vpath (MC_FHL_INI_FILE);
+ check_for_default (fhlfile_vpath, buffer_vpath);
+ do_edit (buffer_vpath);
+ vfs_path_free (buffer_vpath, TRUE);
+ }
+ else if (dir == 1)
+ {
+ if (!exist_file (vfs_path_get_last_path_str (fhlfile_vpath)))
+ {
+ vfs_path_free (fhlfile_vpath, TRUE);
+ fhlfile_vpath =
+ vfs_path_build_filename (mc_global.sysconfig_dir, MC_FHL_INI_FILE, (char *) NULL);
+ }
+ do_edit (fhlfile_vpath);
+ }
+
+ vfs_path_free (fhlfile_vpath, TRUE);
+ /* refresh highlighting rules */
+ mc_fhl_free (&mc_filehighlight);
+ mc_filehighlight = mc_fhl_new (TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+hotlist_cmd (WPanel * panel)
+{
+ char *target;
+
+ target = hotlist_show (LIST_HOTLIST, panel);
+ if (target == NULL)
+ return;
+
+ if (get_current_type () == view_tree)
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (target);
+ tree_chdir (the_tree, vpath);
+ vfs_path_free (vpath, TRUE);
+ }
+ else
+ {
+ vfs_path_t *deprecated_vpath;
+ const char *deprecated_path;
+
+ deprecated_vpath = vfs_path_from_str_flags (target, VPF_USE_DEPRECATED_PARSER);
+ deprecated_path = vfs_path_as_str (deprecated_vpath);
+ cd_to (deprecated_path);
+ vfs_path_free (deprecated_vpath, TRUE);
+ }
+
+ g_free (target);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS
+void
+vfs_list (WPanel * panel)
+{
+ char *target;
+ vfs_path_t *target_vpath;
+
+ target = hotlist_show (LIST_VFSLIST, panel);
+ if (target == NULL)
+ return;
+
+ target_vpath = vfs_path_from_str (target);
+ if (!panel_cd (current_panel, target_vpath, cd_exact))
+ cd_error_message (target);
+ vfs_path_free (target_vpath, TRUE);
+ g_free (target);
+}
+#endif /* ENABLE_VFS */
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+compare_dirs_cmd (void)
+{
+ int choice;
+ enum CompareMode thorough_flag;
+
+ choice =
+ query_dialog (_("Compare directories"),
+ _("Select compare method:"), D_NORMAL, 4,
+ _("&Quick"), _("&Size only"), _("&Thorough"), _("&Cancel"));
+
+ if (choice < 0 || choice > 2)
+ return;
+
+ thorough_flag = choice;
+
+ if (get_current_type () == view_listing && get_other_type () == view_listing)
+ {
+ compare_dir (current_panel, other_panel, thorough_flag);
+ compare_dir (other_panel, current_panel, thorough_flag);
+ }
+ else
+ message (D_ERROR, MSG_ERROR,
+ _("Both panels should be in the listing mode\nto use this command"));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef USE_DIFF_VIEW
+void
+diff_view_cmd (void)
+{
+ /* both panels must be in the list mode */
+ if (get_current_type () == view_listing && get_other_type () == view_listing)
+ {
+ if (get_current_index () == 0)
+ dview_diff_cmd (current_panel, other_panel);
+ else
+ dview_diff_cmd (other_panel, current_panel);
+
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+
+ dialog_switch_process_pending ();
+ }
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+swap_cmd (void)
+{
+ swap_panels ();
+ tty_touch_screen ();
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+link_cmd (link_type_t link_type)
+{
+ const char *filename;
+
+ filename = panel_current_entry (current_panel)->fname->str;
+ if (filename != NULL)
+ do_link (link_type, filename);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_symlink_cmd (void)
+{
+ const file_entry_t *fe;
+ const char *p;
+
+ fe = panel_current_entry (current_panel);
+ p = fe->fname->str;
+
+ if (!S_ISLNK (fe->st.st_mode))
+ message (D_ERROR, MSG_ERROR, _("'%s' is not a symbolic link"), p);
+ else
+ {
+ char buffer[MC_MAXPATHLEN];
+ int i;
+
+ i = readlink (p, buffer, sizeof (buffer) - 1);
+ if (i > 0)
+ {
+ char *q, *dest;
+
+ buffer[i] = '\0';
+
+ q = g_strdup_printf (_("Symlink '%s\' points to:"), str_trunc (p, 32));
+ dest =
+ input_expand_dialog (_("Edit symlink"), q, MC_HISTORY_FM_EDIT_LINK, buffer,
+ INPUT_COMPLETE_FILENAMES);
+ g_free (q);
+
+ if (dest != NULL && *dest != '\0' && strcmp (buffer, dest) != 0)
+ {
+ vfs_path_t *p_vpath;
+
+ p_vpath = vfs_path_from_str (p);
+
+ save_cwds_stat ();
+
+ if (mc_unlink (p_vpath) == -1)
+ message (D_ERROR, MSG_ERROR, _("edit symlink, unable to remove %s: %s"), p,
+ unix_error_string (errno));
+ else
+ {
+ vfs_path_t *dest_vpath;
+
+ dest_vpath = vfs_path_from_str_flags (dest, VPF_NO_CANON);
+ if (mc_symlink (dest_vpath, p_vpath) == -1)
+ message (D_ERROR, MSG_ERROR, _("edit symlink: %s"),
+ unix_error_string (errno));
+ vfs_path_free (dest_vpath, TRUE);
+ }
+
+ vfs_path_free (p_vpath, TRUE);
+
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ repaint_screen ();
+ }
+
+ g_free (dest);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+help_cmd (void)
+{
+ ev_help_t event_data = { NULL, NULL };
+
+ if (current_panel->quick_search.active)
+ event_data.node = "[Quick search]";
+ else
+ event_data.node = "[main]";
+
+ mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+user_file_menu_cmd (void)
+{
+ (void) user_menu_cmd (NULL, NULL, -1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS_FTP
+void
+ftplink_cmd (void)
+{
+ nice_cd (_("FTP to machine"), _(machine_str),
+ "[FTP File System]", ":ftplink_cmd: FTP to machine ", "ftp://", 1, TRUE);
+}
+#endif /* ENABLE_VFS_FTP */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS_SFTP
+void
+sftplink_cmd (void)
+{
+ nice_cd (_("SFTP to machine"), _(machine_str),
+ "[SFTP (SSH File Transfer Protocol) filesystem]",
+ ":sftplink_cmd: SFTP to machine ", "sftp://", 1, TRUE);
+}
+#endif /* ENABLE_VFS_SFTP */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS_FISH
+void
+fishlink_cmd (void)
+{
+ nice_cd (_("Shell link to machine"), _(machine_str),
+ "[FIle transfer over SHell filesystem]", ":fishlink_cmd: Shell link to machine ",
+ "sh://", 1, TRUE);
+}
+#endif /* ENABLE_VFS_FISH */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS_UNDELFS
+void
+undelete_cmd (void)
+{
+ nice_cd (_("Undelete files on an ext2 file system"),
+ _("Enter device (without /dev/) to undelete\nfiles on: (F1 for details)"),
+ "[Undelete File System]", ":undelete_cmd: Undel on ext2 fs ", "undel://", 0, FALSE);
+}
+#endif /* ENABLE_VFS_UNDELFS */
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+quick_cd_cmd (WPanel * panel)
+{
+ char *p;
+
+ p = cd_box (panel);
+ if (p != NULL && *p != '\0')
+ cd_to (p);
+ g_free (p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*!
+ \brief calculate dirs sizes
+
+ calculate dirs sizes and resort panel:
+ dirs_selected = show size for selected dirs,
+ otherwise = show size for dir under cursor:
+ dir under cursor ".." = show size for all dirs,
+ otherwise = show size for dir under cursor
+ */
+
+void
+smart_dirsize_cmd (WPanel * panel)
+{
+ const file_entry_t *entry;
+
+ entry = panel_current_entry (panel);
+ if ((S_ISDIR (entry->st.st_mode) && DIR_IS_DOTDOT (entry->fname->str)) || panel->dirs_marked)
+ dirsizes_cmd (panel);
+ else
+ single_dirsize_cmd (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+single_dirsize_cmd (WPanel * panel)
+{
+ file_entry_t *entry;
+
+ entry = panel_current_entry (panel);
+
+ if (S_ISDIR (entry->st.st_mode) && !DIR_IS_DOTDOT (entry->fname->str))
+ {
+ size_t dir_count = 0;
+ size_t count = 0;
+ uintmax_t total = 0;
+ dirsize_status_msg_t dsm;
+ vfs_path_t *p;
+
+ p = vfs_path_from_str (entry->fname->str);
+
+ memset (&dsm, 0, sizeof (dsm));
+ status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb,
+ dirsize_status_update_cb, dirsize_status_deinit_cb);
+
+ if (compute_dir_size (p, &dsm, &dir_count, &count, &total, FALSE) == FILE_CONT)
+ {
+ entry->st.st_size = (off_t) total;
+ entry->f.dir_size_computed = 1;
+ }
+
+ vfs_path_free (p, TRUE);
+
+ status_msg_deinit (STATUS_MSG (&dsm));
+ }
+
+ if (panels_options.mark_moves_down)
+ send_message (panel, NULL, MSG_ACTION, CK_Down, NULL);
+
+ recalculate_panel_summary (panel);
+
+ if (panel->sort_field->sort_routine == (GCompareFunc) sort_size)
+ panel_re_sort (panel);
+
+ panel->dirty = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dirsizes_cmd (WPanel * panel)
+{
+ int i;
+ dirsize_status_msg_t dsm;
+
+ memset (&dsm, 0, sizeof (dsm));
+ status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb,
+ dirsize_status_update_cb, dirsize_status_deinit_cb);
+
+ for (i = 0; i < panel->dir.len; i++)
+ if (S_ISDIR (panel->dir.list[i].st.st_mode)
+ && ((panel->dirs_marked != 0 && panel->dir.list[i].f.marked != 0)
+ || panel->dirs_marked == 0) && !DIR_IS_DOTDOT (panel->dir.list[i].fname->str))
+ {
+ vfs_path_t *p;
+ size_t dir_count = 0;
+ size_t count = 0;
+ uintmax_t total = 0;
+ gboolean ok;
+
+ p = vfs_path_from_str (panel->dir.list[i].fname->str);
+ ok = compute_dir_size (p, &dsm, &dir_count, &count, &total, FALSE) != FILE_CONT;
+ vfs_path_free (p, TRUE);
+ if (ok)
+ break;
+
+ panel->dir.list[i].st.st_size = (off_t) total;
+ panel->dir.list[i].f.dir_size_computed = 1;
+ }
+
+ status_msg_deinit (STATUS_MSG (&dsm));
+
+ recalculate_panel_summary (panel);
+
+ if (panel->sort_field->sort_routine == (GCompareFunc) sort_size)
+ panel_re_sort (panel);
+
+ panel->dirty = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+save_setup_cmd (void)
+{
+ vfs_path_t *vpath;
+ const char *path;
+
+ vpath = vfs_path_from_str_flags (mc_config_get_path (), VPF_STRIP_HOME);
+ path = vfs_path_as_str (vpath);
+
+ if (save_setup (TRUE, TRUE))
+ message (D_NORMAL, _("Setup"), _("Setup saved to %s"), path);
+ else
+ message (D_ERROR, _("Setup"), _("Unable to save setup to %s"), path);
+
+ vfs_path_free (vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+info_cmd_no_menu (void)
+{
+ if (get_panel_type (0) == view_info)
+ create_panel (0, view_listing);
+ else if (get_panel_type (1) == view_info)
+ create_panel (1, view_listing);
+ else
+ create_panel (current_panel == left_panel ? 1 : 0, view_info);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+quick_cmd_no_menu (void)
+{
+ if (get_panel_type (0) == view_quick)
+ create_panel (0, view_listing);
+ else if (get_panel_type (1) == view_quick)
+ create_panel (1, view_listing);
+ else
+ create_panel (current_panel == left_panel ? 1 : 0, view_quick);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+listing_cmd (void)
+{
+ WPanel *p;
+
+ switch_to_listing (MENU_PANEL_IDX);
+
+ p = PANEL (get_panel_widget (MENU_PANEL_IDX));
+
+ p->is_panelized = FALSE;
+ panel_set_filter (p, NULL); /* including panel reload */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+setup_listing_format_cmd (void)
+{
+ int list_format;
+ gboolean use_msformat;
+ int brief_cols;
+ char *user, *status;
+ WPanel *p = NULL;
+
+ if (SELECTED_IS_PANEL)
+ p = MENU_PANEL_IDX == 0 ? left_panel : right_panel;
+
+ list_format = panel_listing_box (p, MENU_PANEL_IDX, &user, &status, &use_msformat, &brief_cols);
+ if (list_format != -1)
+ {
+ switch_to_listing (MENU_PANEL_IDX);
+ p = MENU_PANEL_IDX == 0 ? left_panel : right_panel;
+ configure_panel_listing (p, list_format, brief_cols, use_msformat, &user, &status);
+ g_free (user);
+ g_free (status);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_tree_cmd (void)
+{
+ create_panel (MENU_PANEL_IDX, view_tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+info_cmd (void)
+{
+ create_panel (MENU_PANEL_IDX, view_info);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+quick_view_cmd (void)
+{
+ if (PANEL (get_panel_widget (MENU_PANEL_IDX)) == current_panel)
+ (void) change_panel ();
+ create_panel (MENU_PANEL_IDX, view_quick);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+void
+encoding_cmd (void)
+{
+ if (SELECTED_IS_PANEL)
+ panel_change_encoding (MENU_PANEL);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/cmd.h b/src/filemanager/cmd.h
new file mode 100644
index 0000000..26bfdb7
--- /dev/null
+++ b/src/filemanager/cmd.h
@@ -0,0 +1,172 @@
+/** \file cmd.h
+ * \brief Header: routines invoked by a function key
+ *
+ * They normally operate on the current panel.
+ */
+
+#ifndef MC__CMD_H
+#define MC__CMD_H
+
+#include "lib/global.h"
+
+#include "file.h" /* panel_operate() */
+#include "panel.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ LINK_HARDLINK = 0,
+ LINK_SYMLINK_ABSOLUTE,
+ LINK_SYMLINK_RELATIVE
+} link_type_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+#ifdef ENABLE_VFS_FTP
+void ftplink_cmd (void);
+#endif
+#ifdef ENABLE_VFS_SFTP
+void sftplink_cmd (void);
+#endif
+#ifdef ENABLE_VFS_FISH
+void fishlink_cmd (void);
+#endif
+void undelete_cmd (void);
+void help_cmd (void);
+void smart_dirsize_cmd (WPanel * panel);
+void single_dirsize_cmd (WPanel * panel);
+void dirsizes_cmd (WPanel * panel);
+gboolean view_file_at_line (const vfs_path_t * filename_vpath, gboolean plain_view,
+ gboolean internal, long start_line, off_t search_start,
+ off_t search_end);
+gboolean view_file (const vfs_path_t * filename_vpath, gboolean plain_view, gboolean internal);
+void view_cmd (WPanel * panel);
+void view_file_cmd (const WPanel * panel);
+void view_raw_cmd (WPanel * panel);
+void view_filtered_cmd (const WPanel * panel);
+void edit_file_at_line (const vfs_path_t * what_vpath, gboolean internal, long start_line);
+void edit_cmd (const WPanel * panel);
+void edit_cmd_new (void);
+#ifdef USE_INTERNAL_EDIT
+void edit_cmd_force_internal (const WPanel * panel);
+#endif
+void mkdir_cmd (WPanel * panel);
+void reread_cmd (void);
+void vfs_list (WPanel * panel);
+void ext_cmd (void);
+void edit_mc_menu_cmd (void);
+void edit_fhl_cmd (void);
+void hotlist_cmd (WPanel * panel);
+void compare_dirs_cmd (void);
+#ifdef USE_DIFF_VIEW
+void diff_view_cmd (void);
+#endif
+void panel_tree_cmd (void);
+void link_cmd (link_type_t link_type);
+void edit_symlink_cmd (void);
+void swap_cmd (void);
+void quick_cd_cmd (WPanel * panel);
+void save_setup_cmd (void);
+void user_file_menu_cmd (void);
+void info_cmd (void);
+void listing_cmd (void);
+void setup_listing_format_cmd (void);
+void quick_cmd_no_menu (void);
+void info_cmd_no_menu (void);
+void quick_view_cmd (void);
+#ifdef HAVE_CHARSET
+void encoding_cmd (void);
+#endif
+/* achown.c */
+void advanced_chown_cmd (WPanel * panel);
+/* chmod.c */
+void chmod_cmd (WPanel * panel);
+/* chown.c */
+void chown_cmd (WPanel * panel);
+#ifdef ENABLE_EXT2FS_ATTR
+/* chattr.c */
+void chattr_cmd (WPanel * panel);
+const char *chattr_get_as_str (unsigned long attr);
+#endif
+/* find.c */
+void find_cmd (WPanel * panel);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Copy, default to the other panel.
+ */
+
+static inline void
+copy_cmd (WPanel * panel)
+{
+ panel_operate (panel, OP_COPY, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Copy, default to the same panel, ignore marks.
+ */
+
+static inline void
+copy_cmd_local (WPanel * panel)
+{
+ panel_operate (panel, OP_COPY, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Move/rename, default to the other panel.
+ */
+
+static inline void
+rename_cmd (WPanel * panel)
+{
+ panel_operate (panel, OP_MOVE, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Move/rename, default to the same panel, ignore marks.
+ */
+
+static inline void
+rename_cmd_local (WPanel * panel)
+{
+ panel_operate (panel, OP_MOVE, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Remove.
+ */
+
+static inline void
+delete_cmd (WPanel * panel)
+{
+ panel_operate (panel, OP_DELETE, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Remove, ignore marks.
+ */
+
+static inline void
+delete_cmd_local (WPanel * panel)
+{
+ panel_operate (panel, OP_DELETE, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC__CMD_H */
diff --git a/src/filemanager/command.c b/src/filemanager/command.c
new file mode 100644
index 0000000..47d2d75
--- /dev/null
+++ b/src/filemanager/command.c
@@ -0,0 +1,255 @@
+/*
+ Command line widget.
+ This widget is derived from the WInput widget, it's used to cope
+ with all the magic of the command input line, we depend on some
+ help from the program's callback.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2011-2022
+
+ 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 command.c
+ * \brief Source: command line widget
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/vfs/vfs.h" /* vfs_current_is_local() */
+#include "lib/skin.h" /* DEFAULT_COLOR */
+#include "lib/util.h" /* whitespace() */
+#include "lib/widget.h"
+
+#include "src/setup.h" /* quit */
+#ifdef ENABLE_SUBSHELL
+#include "src/subshell/subshell.h"
+#endif
+#include "src/execute.h" /* shell_execute() */
+#include "src/usermenu.h" /* expand_format() */
+
+#include "filemanager.h" /* quiet_quit_cmd(), layout.h */
+#include "cd.h" /* cd_to() */
+
+#include "command.h"
+
+/*** global variables ****************************************************************************/
+
+/* This holds the command line */
+WInput *cmdline;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* Color styles command line */
+static input_colors_t command_colors;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** Handle Enter on the command line
+ *
+ * @param lc_cmdline string for handling
+ * @return MSG_HANDLED on success else MSG_NOT_HANDLED
+ */
+
+static cb_ret_t
+enter (WInput * lc_cmdline)
+{
+ const char *cmd;
+
+ if (!command_prompt)
+ return MSG_HANDLED;
+
+ cmd = input_get_ctext (lc_cmdline);
+
+ /* Any initial whitespace should be removed at this point */
+ while (whiteness (*cmd))
+ cmd++;
+
+ if (*cmd == '\0')
+ return MSG_HANDLED;
+
+ if (strncmp (cmd, "cd", 2) == 0 && (cmd[2] == '\0' || whitespace (cmd[2])))
+ {
+ cd_to (cmd + 2);
+ input_clean (lc_cmdline);
+ return MSG_HANDLED;
+ }
+ else if (strcmp (cmd, "exit") == 0)
+ {
+ input_assign_text (lc_cmdline, "");
+ if (!quiet_quit_cmd ())
+ return MSG_NOT_HANDLED;
+ }
+ else
+ {
+ GString *command;
+ size_t i;
+
+ if (!vfs_current_is_local ())
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot execute commands on non-local filesystems"));
+ return MSG_NOT_HANDLED;
+ }
+#ifdef ENABLE_SUBSHELL
+ /* Check this early before we clean command line
+ * (will be checked again by shell_execute) */
+ if (mc_global.tty.use_subshell && subshell_state != INACTIVE)
+ {
+ message (D_ERROR, MSG_ERROR, _("The shell is already running a command"));
+ return MSG_NOT_HANDLED;
+ }
+#endif
+ command = g_string_sized_new (32);
+
+ for (i = 0; cmd[i] != '\0'; i++)
+ {
+ if (cmd[i] != '%')
+ g_string_append_c (command, cmd[i]);
+ else
+ {
+ char *s;
+
+ s = expand_format (NULL, cmd[++i], TRUE);
+ g_string_append (command, s);
+ g_free (s);
+ }
+ }
+
+ input_clean (lc_cmdline);
+ shell_execute (command->str, 0);
+ g_string_free (command, TRUE);
+
+#ifdef ENABLE_SUBSHELL
+ if ((quit & SUBSHELL_EXIT) != 0)
+ {
+ if (quiet_quit_cmd ())
+ return MSG_HANDLED;
+
+ quit = 0;
+ /* restart subshell */
+ if (mc_global.tty.use_subshell)
+ init_subshell ();
+ }
+
+ if (mc_global.tty.use_subshell)
+ do_load_prompt ();
+#endif
+ }
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default command line callback
+ *
+ * @param w Widget object
+ * @param msg message for handling
+ * @param parm extra parameter such as key code
+ *
+ * @return MSG_NOT_HANDLED on fail else MSG_HANDLED
+ */
+
+static cb_ret_t
+command_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_KEY:
+ /* Special case: we handle the enter key */
+ if (parm == '\n')
+ return enter (INPUT (w));
+ MC_FALLTHROUGH;
+
+ default:
+ return input_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WInput *
+command_new (int y, int x, int cols)
+{
+ WInput *cmd;
+ Widget *w;
+
+ cmd = input_new (y, x, command_colors, cols, "", "cmdline",
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES
+ | INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_COMMANDS |
+ INPUT_COMPLETE_SHELL_ESC);
+ w = WIDGET (cmd);
+ /* Don't set WOP_SELECTABLE up, otherwise panels will be unselected */
+ widget_set_options (w, WOP_SELECTABLE, FALSE);
+ /* Add our hooks */
+ w->callback = command_callback;
+
+ return cmd;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Set colors for the command line.
+ */
+
+void
+command_set_default_colors (void)
+{
+ command_colors[WINPUTC_MAIN] = DEFAULT_COLOR;
+ command_colors[WINPUTC_MARK] = COMMAND_MARK_COLOR;
+ command_colors[WINPUTC_UNCHANGED] = DEFAULT_COLOR;
+ command_colors[WINPUTC_HISTORY] = COMMAND_HISTORY_COLOR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Insert quoted text in input line. The function is meant for the
+ * command line, so the percent sign is quoted as well.
+ *
+ * @param in WInput object
+ * @param text string for insertion
+ * @param insert_extra_space add extra space
+ */
+
+void
+command_insert (WInput * in, const char *text, gboolean insert_extra_space)
+{
+ char *quoted_text;
+
+ quoted_text = name_quote (text, TRUE);
+ input_insert (in, quoted_text, insert_extra_space);
+ g_free (quoted_text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/command.h b/src/filemanager/command.h
new file mode 100644
index 0000000..9aacca4
--- /dev/null
+++ b/src/filemanager/command.h
@@ -0,0 +1,27 @@
+/** \file command.h
+ * \brief Header: command line widget
+ */
+
+#ifndef MC__COMMAND_H
+#define MC__COMMAND_H
+
+#include "lib/widget.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern WInput *cmdline;
+
+/*** declarations of public functions ************************************************************/
+
+WInput *command_new (int y, int x, int len);
+void command_set_default_colors (void);
+void command_insert (WInput * in, const char *text, gboolean insert_extra_space);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__COMMAND_H */
diff --git a/src/filemanager/dir.c b/src/filemanager/dir.c
new file mode 100644
index 0000000..0931819
--- /dev/null
+++ b/src/filemanager/dir.c
@@ -0,0 +1,839 @@
+/*
+ Directory routines
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2022
+
+ 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 src/filemanager/dir.c
+ * \brief Source: directory routines
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/search.h"
+#include "lib/vfs/vfs.h"
+#include "lib/fs.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+
+#include "src/setup.h" /* panels_options */
+
+#include "treestore.h"
+#include "file.h" /* file_is_symlink_to_dir() */
+#include "dir.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define MY_ISDIR(x) (\
+ (is_exe (x->st.st_mode) && !(S_ISDIR (x->st.st_mode) || link_isdir (x)) && exec_first) \
+ ? 1 \
+ : ( (S_ISDIR (x->st.st_mode) || link_isdir (x)) ? 2 : 0) )
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* Reverse flag */
+static int reverse = 1;
+
+/* Are the files sorted case sensitively? */
+static gboolean case_sensitive = OS_SORT_CASE_SENSITIVE_DEFAULT;
+
+/* Are the exec_bit files top in list */
+static gboolean exec_first = TRUE;
+
+static dir_list dir_copy = { NULL, 0, 0, NULL };
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+key_collate (const char *t1, const char *t2)
+{
+ int dotdot = 0;
+ int ret;
+
+ dotdot = (t1[0] == '.' ? 1 : 0) | ((t2[0] == '.' ? 1 : 0) << 1);
+
+ switch (dotdot)
+ {
+ case 0:
+ case 3:
+ ret = str_key_collate (t1, t2, case_sensitive) * reverse;
+ break;
+ case 1:
+ ret = -1; /* t1 < t2 */
+ break;
+ case 2:
+ ret = 1; /* t1 > t2 */
+ break;
+ default:
+ ret = 0; /* it must not happen */
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+compare_by_names (file_entry_t * a, file_entry_t * b)
+{
+ /* create key if does not exist, key will be freed after sorting */
+ if (a->name_sort_key == NULL)
+ a->name_sort_key = str_create_key_for_filename (a->fname->str, case_sensitive);
+ if (b->name_sort_key == NULL)
+ b->name_sort_key = str_create_key_for_filename (b->fname->str, case_sensitive);
+
+ return key_collate (a->name_sort_key, b->name_sort_key);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * clear keys, should be call after sorting is finished.
+ */
+
+static void
+clean_sort_keys (dir_list * list, int start, int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++)
+ {
+ file_entry_t *fentry;
+
+ fentry = &list->list[i + start];
+ str_release_key (fentry->name_sort_key, case_sensitive);
+ fentry->name_sort_key = NULL;
+ str_release_key (fentry->extension_sort_key, case_sensitive);
+ fentry->extension_sort_key = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * If you change handle_dirent then check also handle_path.
+ * @return FALSE = don't add, TRUE = add to the list
+ */
+
+static gboolean
+handle_dirent (struct vfs_dirent *dp, const file_filter_t * filter, struct stat *buf1,
+ gboolean * link_to_dir, gboolean * stale_link)
+{
+ vfs_path_t *vpath;
+ gboolean ok = TRUE;
+
+ if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
+ return FALSE;
+ if (!panels_options.show_dot_files && (dp->d_name[0] == '.'))
+ return FALSE;
+ if (!panels_options.show_backups && dp->d_name[strlen (dp->d_name) - 1] == '~')
+ return FALSE;
+
+ vpath = vfs_path_from_str (dp->d_name);
+ if (mc_lstat (vpath, buf1) == -1)
+ {
+ /*
+ * lstat() fails - such entries should be identified by
+ * buf1->st_mode being 0.
+ * It happens on QNX Neutrino for /fs/cd0 if no CD is inserted.
+ */
+ memset (buf1, 0, sizeof (*buf1));
+ }
+
+ if (S_ISDIR (buf1->st_mode))
+ tree_store_mark_checked (dp->d_name);
+
+ /* A link to a file or a directory? */
+ *link_to_dir = file_is_symlink_to_dir (vpath, buf1, stale_link);
+
+ vfs_path_free (vpath, TRUE);
+
+ if (filter != NULL && filter->handler != NULL)
+ {
+ gboolean files_only = (filter->flags & SELECT_FILES_ONLY) != 0;
+
+ ok = ((S_ISDIR (buf1->st_mode) || *link_to_dir) && files_only)
+ || mc_search_run (filter->handler, dp->d_name, 0, strlen (dp->d_name), NULL);
+ }
+
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** get info about ".." */
+
+static gboolean
+dir_get_dotdot_stat (const vfs_path_t * vpath, struct stat *st)
+{
+ gboolean ret = FALSE;
+
+ if ((vpath != NULL) && (st != NULL))
+ {
+ const char *path;
+
+ path = vfs_path_get_by_index (vpath, 0)->path;
+ if (path != NULL && *path != '\0')
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_append_new (vpath, "..", (char *) NULL);
+ ret = mc_stat (tmp_vpath, st) == 0;
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+alloc_dir_copy (int size)
+{
+ if (dir_copy.size < size)
+ {
+ if (dir_copy.list != NULL)
+ dir_list_free_list (&dir_copy);
+
+ dir_copy.list = g_new0 (file_entry_t, size);
+ dir_copy.size = size;
+ dir_copy.len = 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Increase or decrease directory list size.
+ *
+ * @param list directory list
+ * @param delta value by increase (if positive) or decrease (if negative) list size
+ *
+ * @return FALSE on failure, TRUE on success
+ */
+
+gboolean
+dir_list_grow (dir_list * list, int delta)
+{
+ int size;
+ gboolean clear_flag = FALSE;
+
+ if (list == NULL)
+ return FALSE;
+
+ if (delta == 0)
+ return TRUE;
+
+ size = list->size + delta;
+ if (size <= 0)
+ {
+ size = DIR_LIST_MIN_SIZE;
+ clear_flag = TRUE;
+ }
+
+ if (size != list->size)
+ {
+ file_entry_t *fe;
+
+ fe = g_try_renew (file_entry_t, list->list, size);
+ if (fe == NULL)
+ return FALSE;
+
+ list->list = fe;
+ list->size = size;
+ }
+
+ list->len = clear_flag ? 0 : MIN (list->len, size);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Append file info to the directory list.
+ *
+ * @param list directory list
+ * @param fname file name
+ * @param st file stat info
+ * @param link_to_dir is file link to directory
+ * @param stale_link is file stale elink
+ *
+ * @return FALSE on failure, TRUE on success
+ */
+
+gboolean
+dir_list_append (dir_list * list, const char *fname, const struct stat * st,
+ gboolean link_to_dir, gboolean stale_link)
+{
+ file_entry_t *fentry;
+
+ /* Need to grow the *list? */
+ if (list->len == list->size && !dir_list_grow (list, DIR_LIST_RESIZE_STEP))
+ return FALSE;
+
+ fentry = &list->list[list->len];
+ fentry->fname = g_string_new (fname);
+ fentry->f.marked = 0;
+ fentry->f.link_to_dir = link_to_dir ? 1 : 0;
+ fentry->f.stale_link = stale_link ? 1 : 0;
+ fentry->f.dir_size_computed = 0;
+ fentry->st = *st;
+ fentry->name_sort_key = NULL;
+ fentry->extension_sort_key = NULL;
+
+ list->len++;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+unsorted (file_entry_t * a, file_entry_t * b)
+{
+ (void) a;
+ (void) b;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_name (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ return compare_by_names (a, b);
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_vers (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ {
+ int result;
+
+ result = filevercmp (a->fname->str, b->fname->str);
+ if (result != 0)
+ return result * reverse;
+
+ return compare_by_names (a, b);
+ }
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_ext (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ {
+ int r;
+
+ if (a->extension_sort_key == NULL)
+ a->extension_sort_key = str_create_key (extension (a->fname->str), case_sensitive);
+ if (b->extension_sort_key == NULL)
+ b->extension_sort_key = str_create_key (extension (b->fname->str), case_sensitive);
+
+ r = str_key_collate (a->extension_sort_key, b->extension_sort_key, case_sensitive);
+ if (r != 0)
+ return r * reverse;
+
+ return compare_by_names (a, b);
+ }
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_time (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ {
+ int result = _GL_CMP (a->st.st_mtime, b->st.st_mtime);
+
+ if (result != 0)
+ return result * reverse;
+
+ return compare_by_names (a, b);
+ }
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_ctime (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ {
+ int result = _GL_CMP (a->st.st_ctime, b->st.st_ctime);
+
+ if (result != 0)
+ return result * reverse;
+
+ return compare_by_names (a, b);
+ }
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_atime (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ {
+ int result = _GL_CMP (a->st.st_atime, b->st.st_atime);
+
+ if (result != 0)
+ return result * reverse;
+
+ return compare_by_names (a, b);
+ }
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_inode (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ return (a->st.st_ino - b->st.st_ino) * reverse;
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_size (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ {
+ int result = _GL_CMP (a->st.st_size, b->st.st_size);
+
+ if (result != 0)
+ return result * reverse;
+
+ return compare_by_names (a, b);
+ }
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dir_list_sort (dir_list * list, GCompareFunc sort, const dir_sort_options_t * sort_op)
+{
+ if (list->len > 1 && sort != (GCompareFunc) unsorted)
+ {
+ file_entry_t *fentry = &list->list[0];
+ int dot_dot_found;
+
+ /* If there is an ".." entry the caller must take care to
+ ensure that it occupies the first list element. */
+ dot_dot_found = DIR_IS_DOTDOT (fentry->fname->str) ? 1 : 0;
+ reverse = sort_op->reverse ? -1 : 1;
+ case_sensitive = sort_op->case_sensitive ? 1 : 0;
+ exec_first = sort_op->exec_first;
+ qsort (&(list->list)[dot_dot_found], list->len - dot_dot_found, sizeof (file_entry_t),
+ sort);
+
+ clean_sort_keys (list, dot_dot_found, list->len - dot_dot_found);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dir_list_clean (dir_list * list)
+{
+ int i;
+
+ for (i = 0; i < list->len; i++)
+ {
+ file_entry_t *fentry;
+
+ fentry = &list->list[i];
+ g_string_free (fentry->fname, TRUE);
+ fentry->fname = NULL;
+ }
+
+ list->len = 0;
+ /* reduce memory usage */
+ dir_list_grow (list, DIR_LIST_MIN_SIZE - list->size);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dir_list_free_list (dir_list * list)
+{
+ int i;
+
+ for (i = 0; i < list->len; i++)
+ {
+ file_entry_t *fentry;
+
+ fentry = &list->list[i];
+ g_string_free (fentry->fname, TRUE);
+ }
+
+ MC_PTR_FREE (list->list);
+ list->len = 0;
+ list->size = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Used to set up a directory list when there is no access to a directory */
+
+gboolean
+dir_list_init (dir_list * list)
+{
+ file_entry_t *fentry;
+
+ /* Need to grow the *list? */
+ if (list->size == 0 && !dir_list_grow (list, DIR_LIST_RESIZE_STEP))
+ {
+ list->len = 0;
+ return FALSE;
+ }
+
+ fentry = &list->list[0];
+ memset (fentry, 0, sizeof (*fentry));
+ fentry->fname = g_string_new ("..");
+ fentry->f.link_to_dir = 0;
+ fentry->f.stale_link = 0;
+ fentry->f.dir_size_computed = 0;
+ fentry->f.marked = 0;
+ fentry->st.st_mode = 040755;
+ list->len = 1;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ handle_path is a simplified handle_dirent. The difference is that
+ handle_path doesn't pay attention to panels_options.show_dot_files
+ and panels_options.show_backups.
+ Moreover handle_path can't be used with a filemask.
+ If you change handle_path then check also handle_dirent. */
+/* Return values: FALSE = don't add, TRUE = add to the list */
+
+gboolean
+handle_path (const char *path, struct stat * buf1, gboolean * link_to_dir, gboolean * stale_link)
+{
+ vfs_path_t *vpath;
+
+ if (DIR_IS_DOT (path) || DIR_IS_DOTDOT (path))
+ return FALSE;
+
+ vpath = vfs_path_from_str (path);
+ if (mc_lstat (vpath, buf1) == -1)
+ {
+ vfs_path_free (vpath, TRUE);
+ return FALSE;
+ }
+
+ if (S_ISDIR (buf1->st_mode))
+ tree_store_mark_checked (path);
+
+ /* A link to a file or a directory? */
+ *link_to_dir = FALSE;
+ *stale_link = FALSE;
+ if (S_ISLNK (buf1->st_mode))
+ {
+ struct stat buf2;
+
+ if (mc_stat (vpath, &buf2) == 0)
+ *link_to_dir = S_ISDIR (buf2.st_mode) != 0;
+ else
+ *stale_link = TRUE;
+ }
+
+ vfs_path_free (vpath, TRUE);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+dir_list_load (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
+ const dir_sort_options_t * sort_op, const file_filter_t * filter)
+{
+ DIR *dirp;
+ struct vfs_dirent *dp;
+ struct stat st;
+ file_entry_t *fentry;
+ const char *vpath_str;
+ gboolean ret = TRUE;
+
+ /* ".." (if any) must be the first entry in the list */
+ if (!dir_list_init (list))
+ return FALSE;
+
+ fentry = &list->list[0];
+ if (dir_get_dotdot_stat (vpath, &st))
+ fentry->st = st;
+
+ if (list->callback != NULL)
+ list->callback (DIR_OPEN, (void *) vpath);
+ dirp = mc_opendir (vpath);
+ if (dirp == NULL)
+ return FALSE;
+
+ tree_store_start_check (vpath);
+
+ vpath_str = vfs_path_as_str (vpath);
+ /* Do not add a ".." entry to the root directory */
+ if (IS_PATH_SEP (vpath_str[0]) && vpath_str[1] == '\0')
+ dir_list_clean (list);
+
+ while (ret && (dp = mc_readdir (dirp)) != NULL)
+ {
+ gboolean link_to_dir, stale_link;
+
+ if (list->callback != NULL)
+ list->callback (DIR_READ, dp);
+
+ if (!handle_dirent (dp, filter, &st, &link_to_dir, &stale_link))
+ continue;
+
+ if (!dir_list_append (list, dp->d_name, &st, link_to_dir, stale_link))
+ ret = FALSE;
+ }
+
+ if (ret)
+ dir_list_sort (list, sort, sort_op);
+
+ if (list->callback != NULL)
+ list->callback (DIR_CLOSE, NULL);
+ mc_closedir (dirp);
+ tree_store_end_check ();
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+if_link_is_exe (const vfs_path_t * full_name_vpath, const file_entry_t * file)
+{
+ struct stat b;
+
+ if (S_ISLNK (file->st.st_mode) && mc_stat (full_name_vpath, &b) == 0)
+ return is_exe (b.st_mode);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** If filter is null, then it is a match */
+
+gboolean
+dir_list_reload (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
+ const dir_sort_options_t * sort_op, const file_filter_t * filter)
+{
+ DIR *dirp;
+ struct vfs_dirent *dp;
+ int i;
+ struct stat st;
+ int marked_cnt;
+ GHashTable *marked_files;
+ const char *tmp_path;
+ gboolean ret = TRUE;
+
+ if (list->callback != NULL)
+ list->callback (DIR_OPEN, (void *) vpath);
+ dirp = mc_opendir (vpath);
+ if (dirp == NULL)
+ {
+ dir_list_clean (list);
+ dir_list_init (list);
+ return FALSE;
+ }
+
+ tree_store_start_check (vpath);
+
+ marked_files = g_hash_table_new (g_str_hash, g_str_equal);
+ alloc_dir_copy (list->len);
+ for (marked_cnt = i = 0; i < list->len; i++)
+ {
+ file_entry_t *fentry, *dfentry;
+
+ fentry = &list->list[i];
+ dfentry = &dir_copy.list[i];
+
+ dfentry->fname = mc_g_string_dup (fentry->fname);
+ dfentry->f.marked = fentry->f.marked;
+ dfentry->f.dir_size_computed = fentry->f.dir_size_computed;
+ dfentry->f.link_to_dir = fentry->f.link_to_dir;
+ dfentry->f.stale_link = fentry->f.stale_link;
+ dfentry->name_sort_key = NULL;
+ dfentry->extension_sort_key = NULL;
+ if (fentry->f.marked != 0)
+ {
+ g_hash_table_insert (marked_files, dfentry->fname->str, dfentry);
+ marked_cnt++;
+ }
+ }
+
+ /* save len for later dir_list_clean() */
+ dir_copy.len = list->len;
+
+ /* Add ".." except to the root directory. The ".." entry
+ (if any) must be the first in the list. */
+ tmp_path = vfs_path_get_by_index (vpath, 0)->path;
+ if (vfs_path_elements_count (vpath) == 1 && IS_PATH_SEP (tmp_path[0]) && tmp_path[1] == '\0')
+ {
+ /* root directory */
+ dir_list_clean (list);
+ }
+ else
+ {
+ dir_list_clean (list);
+ if (!dir_list_init (list))
+ {
+ dir_list_free_list (&dir_copy);
+ mc_closedir (dirp);
+ return FALSE;
+ }
+
+ if (dir_get_dotdot_stat (vpath, &st))
+ {
+ file_entry_t *fentry;
+
+ fentry = &list->list[0];
+ fentry->st = st;
+ }
+ }
+
+ while (ret && (dp = mc_readdir (dirp)) != NULL)
+ {
+ gboolean link_to_dir, stale_link;
+
+ if (list->callback != NULL)
+ list->callback (DIR_READ, dp);
+
+ if (!handle_dirent (dp, filter, &st, &link_to_dir, &stale_link))
+ continue;
+
+ if (!dir_list_append (list, dp->d_name, &st, link_to_dir, stale_link))
+ ret = FALSE;
+ else
+ {
+ file_entry_t *fentry;
+
+ fentry = &list->list[list->len - 1];
+
+ /*
+ * If we have marked files in the copy, scan through the copy
+ * to find matching file. Decrease number of remaining marks if
+ * we copied one.
+ */
+ fentry->f.marked = (marked_cnt > 0
+ && g_hash_table_lookup (marked_files, dp->d_name) != NULL) ? 1 : 0;
+ if (fentry->f.marked != 0)
+ marked_cnt--;
+ }
+ }
+
+ if (ret)
+ dir_list_sort (list, sort, sort_op);
+
+ if (list->callback != NULL)
+ list->callback (DIR_CLOSE, NULL);
+ mc_closedir (dirp);
+ tree_store_end_check ();
+
+ g_hash_table_destroy (marked_files);
+ dir_list_free_list (&dir_copy);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_filter_clear (file_filter_t * filter)
+{
+ MC_PTR_FREE (filter->value);
+ mc_search_free (filter->handler);
+ filter->handler = NULL;
+ /* keep filter->flags */
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/dir.h b/src/filemanager/dir.h
new file mode 100644
index 0000000..80a19df
--- /dev/null
+++ b/src/filemanager/dir.h
@@ -0,0 +1,115 @@
+/** \file dir.h
+ * \brief Header: directory routines
+ */
+
+#ifndef MC__DIR_H
+#define MC__DIR_H
+
+#include <sys/stat.h>
+
+#include "lib/global.h"
+#include "lib/search.h"
+#include "lib/file-entry.h"
+#include "lib/vfs/vfs.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define DIR_LIST_MIN_SIZE 128
+#define DIR_LIST_RESIZE_STEP 128
+
+typedef enum
+{
+ DIR_OPEN = 0,
+ DIR_READ,
+ DIR_CLOSE
+} dir_list_cb_state_t;
+
+/* selection flags */
+typedef enum
+{
+ SELECT_FILES_ONLY = 1 << 0,
+ SELECT_MATCH_CASE = 1 << 1,
+ SELECT_SHELL_PATTERNS = 1 << 2
+} select_flags_t;
+
+#define FILE_FILTER_DEFAULT_FLAGS (SELECT_FILES_ONLY | SELECT_MATCH_CASE | SELECT_SHELL_PATTERNS)
+
+/* dir_list callback */
+typedef void (*dir_list_cb_fn) (dir_list_cb_state_t state, void *data);
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/**
+ * A structure to represent directory content
+ */
+typedef struct
+{
+ file_entry_t *list; /**< list of file_entry_t objects */
+ int size; /**< number of allocated elements in list (capacity) */
+ int len; /**< number of used elements in list */
+ dir_list_cb_fn callback; /**< callback to visualize of directory read */
+} dir_list;
+
+/**
+ * A structure to represent sort options for directory content
+ */
+typedef struct dir_sort_options_struct
+{
+ gboolean reverse; /**< sort is reverse */
+ gboolean case_sensitive; /**< sort is case sensitive */
+ gboolean exec_first; /**< executables are at top of list */
+} dir_sort_options_t;
+
+/* filter */
+typedef struct
+{
+ char *value;
+ mc_search_t *handler;
+ select_flags_t flags;
+} file_filter_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+gboolean dir_list_grow (dir_list * list, int delta);
+gboolean dir_list_append (dir_list * list, const char *fname, const struct stat *st,
+ gboolean link_to_dir, gboolean stale_link);
+
+gboolean dir_list_load (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
+ const dir_sort_options_t * sort_op, const file_filter_t * filter);
+gboolean dir_list_reload (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
+ const dir_sort_options_t * sort_op, const file_filter_t * filter);
+void dir_list_sort (dir_list * list, GCompareFunc sort, const dir_sort_options_t * sort_op);
+gboolean dir_list_init (dir_list * list);
+void dir_list_clean (dir_list * list);
+void dir_list_free_list (dir_list * list);
+gboolean handle_path (const char *path, struct stat *buf1, gboolean * link_to_dir,
+ gboolean * stale_link);
+
+/* Sorting functions */
+int unsorted (file_entry_t * a, file_entry_t * b);
+int sort_name (file_entry_t * a, file_entry_t * b);
+int sort_vers (file_entry_t * a, file_entry_t * b);
+int sort_ext (file_entry_t * a, file_entry_t * b);
+int sort_time (file_entry_t * a, file_entry_t * b);
+int sort_atime (file_entry_t * a, file_entry_t * b);
+int sort_ctime (file_entry_t * a, file_entry_t * b);
+int sort_size (file_entry_t * a, file_entry_t * b);
+int sort_inode (file_entry_t * a, file_entry_t * b);
+
+gboolean if_link_is_exe (const vfs_path_t * full_name, const file_entry_t * file);
+
+void file_filter_clear (file_filter_t * filter);
+
+/*** inline functions ****************************************************************************/
+
+static inline gboolean
+link_isdir (const file_entry_t * file)
+{
+ return (file->f.link_to_dir != 0);
+}
+
+#endif /* MC__DIR_H */
diff --git a/src/filemanager/ext.c b/src/filemanager/ext.c
new file mode 100644
index 0000000..b21c4d0
--- /dev/null
+++ b/src/filemanager/ext.c
@@ -0,0 +1,1089 @@
+/*
+ Extension dependent execution.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Jakub Jelinek, 1995
+ Miguel de Icaza, 1994
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ 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 ext.c
+ * \brief Source: extension dependent execution
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/search.h"
+#include "lib/fileloc.h"
+#include "lib/mcconfig.h"
+#include "lib/util.h"
+#include "lib/vfs/vfs.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h" /* get_codepage_index */
+#endif
+
+#ifdef USE_FILE_CMD
+#include "src/setup.h" /* use_file_to_check_type */
+#endif
+#include "src/execute.h"
+#include "src/history.h"
+#include "src/usermenu.h"
+
+#include "src/consaver/cons.saver.h"
+#include "src/viewer/mcviewer.h"
+
+#ifdef HAVE_CHARSET
+#include "src/selcodepage.h" /* do_set_codepage */
+#endif
+
+#include "filemanager.h" /* current_panel */
+#include "panel.h" /* panel_cd */
+
+#include "ext.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifdef USE_FILE_CMD
+#ifdef FILE_B
+#define FILE_CMD "file -z " FILE_B FILE_S FILE_L
+#else
+#define FILE_CMD "file -z " FILE_S FILE_L
+#endif
+#endif
+
+/*** file scope type declarations ****************************************************************/
+
+typedef char *(*quote_func_t) (const char *name, gboolean quote_percent);
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* This variable points to a copy of the mc.ext file in memory
+ * With this we avoid loading/parsing the file each time we
+ * need it
+ */
+static mc_config_t *ext_ini = NULL;
+static gchar **ext_ini_groups = NULL;
+static vfs_path_t *localfilecopy_vpath = NULL;
+static char buffer[BUF_1K];
+
+static char *pbuffer = NULL;
+static time_t localmtime = 0;
+static quote_func_t quote_func = name_quote;
+static gboolean run_view = FALSE;
+static gboolean is_cd = FALSE;
+static gboolean written_nonspace = FALSE;
+static gboolean do_local_copy = FALSE;
+
+static const char *descr_group = "mc.ext.ini";
+static const char *default_group = "Default";
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+exec_cleanup_script (vfs_path_t * script_vpath)
+{
+ if (script_vpath != NULL)
+ {
+ (void) mc_unlink (script_vpath);
+ vfs_path_free (script_vpath, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+exec_cleanup_file_name (const vfs_path_t * filename_vpath, gboolean has_changed)
+{
+ if (localfilecopy_vpath == NULL)
+ return;
+
+ if (has_changed)
+ {
+ struct stat mystat;
+
+ mc_stat (localfilecopy_vpath, &mystat);
+ has_changed = localmtime != mystat.st_mtime;
+ }
+ mc_ungetlocalcopy (filename_vpath, localfilecopy_vpath, has_changed);
+ vfs_path_free (localfilecopy_vpath, TRUE);
+ localfilecopy_vpath = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+exec_get_file_name (const vfs_path_t * filename_vpath)
+{
+ if (!do_local_copy)
+ return quote_func (vfs_path_get_last_path_str (filename_vpath), FALSE);
+
+ if (localfilecopy_vpath == NULL)
+ {
+ struct stat mystat;
+ localfilecopy_vpath = mc_getlocalcopy (filename_vpath);
+ if (localfilecopy_vpath == NULL)
+ return NULL;
+
+ mc_stat (localfilecopy_vpath, &mystat);
+ localmtime = mystat.st_mtime;
+ }
+
+ return quote_func (vfs_path_get_last_path_str (localfilecopy_vpath), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+exec_expand_format (char symbol, gboolean is_result_quoted)
+{
+ char *text;
+
+ text = expand_format (NULL, symbol, TRUE);
+ if (is_result_quoted && text != NULL)
+ {
+ char *quoted_text;
+
+ quoted_text = g_strdup_printf ("\"%s\"", text);
+ g_free (text);
+ text = quoted_text;
+ }
+ return text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GString *
+exec_get_export_variables (const vfs_path_t * filename_vpath)
+{
+ char *text;
+ GString *export_vars_string;
+ size_t i;
+
+ /* *INDENT-OFF* */
+ struct
+ {
+ const char symbol;
+ const char *name;
+ const gboolean is_result_quoted;
+ } export_variables[] = {
+ {'p', "MC_EXT_BASENAME", FALSE},
+ {'d', "MC_EXT_CURRENTDIR", FALSE},
+ {'s', "MC_EXT_SELECTED", TRUE},
+ {'t', "MC_EXT_ONLYTAGGED", TRUE},
+ {'\0', NULL, FALSE}
+ };
+ /* *INDENT-ON* */
+
+ text = exec_get_file_name (filename_vpath);
+ if (text == NULL)
+ return NULL;
+
+ export_vars_string = g_string_new ("MC_EXT_FILENAME=");
+ g_string_append_printf (export_vars_string, "%s\nexport MC_EXT_FILENAME\n", text);
+ g_free (text);
+
+ for (i = 0; export_variables[i].name != NULL; i++)
+ {
+ text =
+ exec_expand_format (export_variables[i].symbol, export_variables[i].is_result_quoted);
+ if (text != NULL)
+ {
+ g_string_append_printf (export_vars_string,
+ "%s=%s\nexport %s\n", export_variables[i].name, text,
+ export_variables[i].name);
+ g_free (text);
+ }
+ }
+
+ return export_vars_string;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GString *
+exec_make_shell_string (const char *lc_data, const vfs_path_t * filename_vpath)
+{
+ GString *shell_string;
+ char lc_prompt[80] = "\0";
+ gboolean parameter_found = FALSE;
+ gboolean expand_prefix_found = FALSE;
+
+ shell_string = g_string_new ("");
+
+ for (; *lc_data != '\0' && *lc_data != '\n'; lc_data++)
+ {
+ if (parameter_found)
+ {
+ if (*lc_data == '}')
+ {
+ char *parameter;
+
+ parameter_found = FALSE;
+ parameter =
+ input_dialog (_("Parameter"), lc_prompt, MC_HISTORY_EXT_PARAMETER, "",
+ INPUT_COMPLETE_NONE);
+ if (parameter == NULL)
+ {
+ /* User canceled */
+ g_string_free (shell_string, TRUE);
+ exec_cleanup_file_name (filename_vpath, FALSE);
+ return NULL;
+ }
+ g_string_append (shell_string, parameter);
+ written_nonspace = TRUE;
+ g_free (parameter);
+ }
+ else
+ {
+ size_t len = strlen (lc_prompt);
+
+ if (len < sizeof (lc_prompt) - 1)
+ {
+ lc_prompt[len] = *lc_data;
+ lc_prompt[len + 1] = '\0';
+ }
+ }
+ }
+ else if (expand_prefix_found)
+ {
+ expand_prefix_found = FALSE;
+ if (*lc_data == '{')
+ parameter_found = TRUE;
+ else
+ {
+ int i;
+
+ i = check_format_view (lc_data);
+ if (i != 0)
+ {
+ lc_data += i - 1;
+ run_view = TRUE;
+ }
+ else
+ {
+ i = check_format_cd (lc_data);
+ if (i > 0)
+ {
+ is_cd = TRUE;
+ quote_func = fake_name_quote;
+ do_local_copy = FALSE;
+ pbuffer = buffer;
+ lc_data += i - 1;
+ }
+ else
+ {
+ char *v;
+
+ i = check_format_var (lc_data, &v);
+ if (i > 0)
+ {
+ g_string_append (shell_string, v);
+ g_free (v);
+ lc_data += i;
+ }
+ else
+ {
+ char *text;
+
+ if (*lc_data != 'f')
+ text = expand_format (NULL, *lc_data, !is_cd);
+ else
+ {
+ text = exec_get_file_name (filename_vpath);
+ if (text == NULL)
+ {
+ g_string_free (shell_string, TRUE);
+ return NULL;
+ }
+ }
+
+ if (!is_cd)
+ g_string_append (shell_string, text);
+ else
+ {
+ strcpy (pbuffer, text);
+ pbuffer = strchr (pbuffer, 0);
+ }
+
+ g_free (text);
+ written_nonspace = TRUE;
+ }
+ }
+ }
+ }
+ }
+ else if (*lc_data == '%')
+ expand_prefix_found = TRUE;
+ else
+ {
+ if (!whitespace (*lc_data))
+ written_nonspace = TRUE;
+ if (is_cd)
+ *(pbuffer++) = *lc_data;
+ else
+ g_string_append_c (shell_string, *lc_data);
+ }
+ } /* for */
+
+ return shell_string;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+exec_extension_view (void *target, char *cmd, const vfs_path_t * filename_vpath, int start_line)
+{
+ mcview_mode_flags_t def_flags = {
+ /* *INDENT-OFF* */
+ .wrap = FALSE,
+ .hex = mcview_global_flags.hex,
+ .magic = FALSE,
+ .nroff = mcview_global_flags.nroff
+ /* *INDENT-ON* */
+ };
+
+ mcview_mode_flags_t changed_flags;
+
+ mcview_clear_mode_flags (&changed_flags);
+ mcview_altered_flags.hex = FALSE;
+ mcview_altered_flags.nroff = FALSE;
+ if (def_flags.hex != mcview_global_flags.hex)
+ changed_flags.hex = TRUE;
+ if (def_flags.nroff != mcview_global_flags.nroff)
+ changed_flags.nroff = TRUE;
+
+ if (target == NULL)
+ mcview_viewer (cmd, filename_vpath, start_line, 0, 0);
+ else
+ mcview_load ((WView *) target, cmd, vfs_path_as_str (filename_vpath), start_line, 0, 0);
+
+ if (changed_flags.hex && !mcview_altered_flags.hex)
+ mcview_global_flags.hex = def_flags.hex;
+ if (changed_flags.nroff && !mcview_altered_flags.nroff)
+ mcview_global_flags.nroff = def_flags.nroff;
+
+ dialog_switch_process_pending ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+exec_extension_cd (WPanel * panel)
+{
+ char *q;
+ vfs_path_t *p_vpath;
+
+ *pbuffer = '\0';
+ pbuffer = buffer;
+ /* Search last non-space character. Start search at the end in order
+ not to short filenames containing spaces. */
+ q = pbuffer + strlen (pbuffer) - 1;
+ while (q >= pbuffer && whitespace (*q))
+ q--;
+ q[1] = 0;
+
+ p_vpath = vfs_path_from_str_flags (pbuffer, VPF_NO_CANON);
+ panel_cd (panel, p_vpath, cd_parse_command);
+ vfs_path_free (p_vpath, TRUE);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+exec_extension (WPanel * panel, void *target, const vfs_path_t * filename_vpath,
+ const char *lc_data, int start_line)
+{
+ GString *shell_string, *export_variables;
+ vfs_path_t *script_vpath = NULL;
+ int cmd_file_fd;
+ FILE *cmd_file;
+ char *cmd = NULL;
+
+ pbuffer = NULL;
+ localmtime = 0;
+ quote_func = name_quote;
+ run_view = FALSE;
+ is_cd = FALSE;
+ written_nonspace = FALSE;
+
+ /* Avoid making a local copy if we are doing a cd */
+ do_local_copy = !vfs_file_is_local (filename_vpath);
+
+ shell_string = exec_make_shell_string (lc_data, filename_vpath);
+ if (shell_string == NULL)
+ goto ret;
+
+ if (is_cd)
+ {
+ exec_extension_cd (panel);
+ g_string_free (shell_string, TRUE);
+ goto ret;
+ }
+
+ /*
+ * All commands should be run in /bin/sh regardless of user shell.
+ * To do that, create temporary shell script and run it.
+ * Sometimes it's not needed (e.g. for %cd and %view commands),
+ * but it's easier to create it anyway.
+ */
+ cmd_file_fd = mc_mkstemps (&script_vpath, "mcext", SCRIPT_SUFFIX);
+
+ if (cmd_file_fd == -1)
+ {
+ message (D_ERROR, MSG_ERROR,
+ _("Cannot create temporary command file\n%s"), unix_error_string (errno));
+ g_string_free (shell_string, TRUE);
+ goto ret;
+ }
+
+ cmd_file = fdopen (cmd_file_fd, "w");
+ fputs ("#! /bin/sh\n\n", cmd_file);
+
+ export_variables = exec_get_export_variables (filename_vpath);
+ if (export_variables != NULL)
+ {
+ fputs (export_variables->str, cmd_file);
+ g_string_free (export_variables, TRUE);
+ }
+
+ fputs (shell_string->str, cmd_file);
+ g_string_free (shell_string, TRUE);
+
+ /*
+ * Make the script remove itself when it finishes.
+ * Don't do it for the viewer - it may need to rerun the script,
+ * so we clean up after calling view().
+ */
+ if (!run_view)
+ fprintf (cmd_file, "\n/bin/rm -f %s\n", vfs_path_as_str (script_vpath));
+
+ fclose (cmd_file);
+
+ if ((run_view && !written_nonspace) || is_cd)
+ {
+ exec_cleanup_script (script_vpath);
+ script_vpath = NULL;
+ }
+ else
+ {
+ /* Set executable flag on the command file ... */
+ mc_chmod (script_vpath, S_IRWXU);
+ /* ... but don't rely on it - run /bin/sh explicitly */
+ cmd = g_strconcat ("/bin/sh ", vfs_path_as_str (script_vpath), (char *) NULL);
+ }
+
+ if (run_view)
+ {
+ /* If we've written whitespace only, then just load filename into view */
+ if (!written_nonspace)
+ exec_extension_view (target, NULL, filename_vpath, start_line);
+ else
+ exec_extension_view (target, cmd, filename_vpath, start_line);
+ }
+ else
+ {
+ shell_execute (cmd, EXECUTE_INTERNAL);
+ if (mc_global.tty.console_flag != '\0')
+ {
+ handle_console (CONSOLE_SAVE);
+ if (output_lines != 0 && mc_global.keybar_visible)
+ {
+ unsigned char end_line;
+
+ end_line = LINES - (mc_global.keybar_visible ? 1 : 0) - 1;
+ show_console_contents (output_start_y, end_line - output_lines, end_line);
+ }
+ }
+ }
+
+ g_free (cmd);
+
+ exec_cleanup_file_name (filename_vpath, TRUE);
+ ret:
+ return script_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Run cmd_file with args, put result into buf.
+ * If error, put '\0' into buf[0]
+ * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors.
+ *
+ * NOTES: buf is null-terminated string.
+ */
+
+#ifdef USE_FILE_CMD
+static int
+get_popen_information (const char *cmd_file, const char *args, char *buf, int buflen)
+{
+ gboolean read_bytes = FALSE;
+ char *command;
+ FILE *f;
+
+ command = g_strconcat (cmd_file, args, " 2>/dev/null", (char *) NULL);
+ f = popen (command, "r");
+ g_free (command);
+
+ if (f != NULL)
+ {
+#ifdef __QNXNTO__
+ if (setvbuf (f, NULL, _IOFBF, 0) != 0)
+ {
+ (void) pclose (f);
+ return -1;
+ }
+#endif
+ read_bytes = (fgets (buf, buflen, f) != NULL);
+ if (!read_bytes)
+ buf[0] = '\0'; /* Paranoid termination */
+ pclose (f);
+ }
+ else
+ {
+ buf[0] = '\0'; /* Paranoid termination */
+ return -1;
+ }
+
+ buf[buflen - 1] = '\0';
+
+ return read_bytes ? 1 : 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Run the "file" command on the local file.
+ * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors.
+ */
+
+static int
+get_file_type_local (const vfs_path_t * filename_vpath, char *buf, int buflen)
+{
+ char *tmp;
+ int ret;
+
+ tmp = name_quote (vfs_path_get_last_path_str (filename_vpath), FALSE);
+ ret = get_popen_information (FILE_CMD, tmp, buf, buflen);
+ g_free (tmp);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Run the "enca" command on the local file.
+ * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors.
+ */
+
+#ifdef HAVE_CHARSET
+static int
+get_file_encoding_local (const vfs_path_t * filename_vpath, char *buf, int buflen)
+{
+ char *tmp, *lang, *args;
+ int ret;
+
+ tmp = name_quote (vfs_path_get_last_path_str (filename_vpath), FALSE);
+ lang = name_quote (autodetect_codeset, FALSE);
+ args = g_strconcat (" -L", lang, " -i ", tmp, (char *) NULL);
+
+ ret = get_popen_information ("enca", args, buf, buflen);
+
+ g_free (args);
+ g_free (lang);
+ g_free (tmp);
+
+ return ret;
+}
+#endif /* HAVE_CHARSET */
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Invoke the "file" command on the file and match its output against PTR.
+ * have_type is a flag that is set if we already have tried to determine
+ * the type of that file.
+ * Return TRUE for match, FALSE otherwise.
+ */
+
+static gboolean
+regex_check_type (const vfs_path_t * filename_vpath, const char *ptr, gboolean case_insense,
+ gboolean * have_type, GError ** mcerror)
+{
+ gboolean found = FALSE;
+
+ /* Following variables are valid if *have_type is TRUE */
+ static char content_string[2048];
+ static size_t content_shift = 0;
+ static int got_data = 0;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if (!*have_type)
+ {
+ vfs_path_t *localfile_vpath;
+
+#ifdef HAVE_CHARSET
+ static char encoding_id[21]; /* CSISO51INISCYRILLIC -- 20 */
+ int got_encoding_data;
+#endif /* HAVE_CHARSET */
+
+ /* Don't repeate even unsuccessful checks */
+ *have_type = TRUE;
+
+ localfile_vpath = mc_getlocalcopy (filename_vpath);
+ if (localfile_vpath == NULL)
+ {
+ mc_propagate_error (mcerror, 0, _("Cannot fetch a local copy of %s"),
+ vfs_path_as_str (filename_vpath));
+ return FALSE;
+ }
+
+
+#ifdef HAVE_CHARSET
+ got_encoding_data = is_autodetect_codeset_enabled
+ ? get_file_encoding_local (localfile_vpath, encoding_id, sizeof (encoding_id)) : 0;
+
+ if (got_encoding_data > 0)
+ {
+ char *pp;
+ int cp_id;
+
+ pp = strchr (encoding_id, '\n');
+ if (pp != NULL)
+ *pp = '\0';
+
+ cp_id = get_codepage_index (encoding_id);
+ if (cp_id == -1)
+ cp_id = default_source_codepage;
+
+ do_set_codepage (cp_id);
+ }
+#endif /* HAVE_CHARSET */
+
+ got_data = get_file_type_local (localfile_vpath, content_string, sizeof (content_string));
+
+ mc_ungetlocalcopy (filename_vpath, localfile_vpath, FALSE);
+
+ if (got_data > 0)
+ {
+ char *pp;
+
+ pp = strchr (content_string, '\n');
+ if (pp != NULL)
+ *pp = '\0';
+
+#ifndef FILE_B
+ {
+ const char *real_name; /* name used with "file" */
+ size_t real_len;
+
+ real_name = vfs_path_get_last_path_str (localfile_vpath);
+ real_len = strlen (real_name);
+
+ if (strncmp (content_string, real_name, real_len) == 0)
+ {
+ /* Skip "real_name: " */
+ content_shift = real_len;
+
+ /* Solaris' file prints tab(s) after ':' */
+ if (content_string[content_shift] == ':')
+ for (content_shift++; whitespace (content_string[content_shift]);
+ content_shift++)
+ ;
+ }
+ }
+#endif /* FILE_B */
+ }
+ else
+ {
+ /* No data */
+ content_string[0] = '\0';
+ }
+ vfs_path_free (localfile_vpath, TRUE);
+ }
+
+ if (got_data == -1)
+ {
+ mc_propagate_error (mcerror, 0, "%s", _("Pipe failed"));
+ return FALSE;
+ }
+
+ if (content_string[0] != '\0')
+ {
+ mc_search_t *search;
+
+ search = mc_search_new (ptr, DEFAULT_CHARSET);
+ if (search != NULL)
+ {
+ search->search_type = MC_SEARCH_T_REGEX;
+ search->is_case_sensitive = !case_insense;
+ found = mc_search_run (search, content_string + content_shift, 0, -1, NULL);
+ mc_search_free (search);
+ }
+ else
+ {
+ mc_propagate_error (mcerror, 0, "%s", _("Regular expression error"));
+ }
+ }
+
+ return found;
+}
+#endif /* USE_FILE_CMD */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+check_old_extension_file (void)
+{
+ char *extension_old_file;
+
+ extension_old_file = mc_config_get_full_path (MC_EXT_OLD_FILE);
+ if (exist_file (extension_old_file))
+ message (D_ERROR, _("Warning"),
+ _("You have an outdated %s file.\nMidnight Commander now uses %s file.\n"
+ "Please copy your modifications of the old file to the new one."),
+ extension_old_file, MC_EXT_FILE);
+ g_free (extension_old_file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+load_extension_file (void)
+{
+ char *extension_file;
+ gboolean mc_user_ext = TRUE;
+ gboolean home_error = FALSE;
+
+ extension_file = mc_config_get_full_path (MC_EXT_FILE);
+ if (!exist_file (extension_file))
+ {
+ g_free (extension_file);
+
+ check_old_extension_file ();
+
+ check_stock_mc_ext:
+ extension_file = mc_build_filename (mc_global.sysconfig_dir, MC_EXT_FILE, (char *) NULL);
+ if (!exist_file (extension_file))
+ {
+ g_free (extension_file);
+ extension_file =
+ mc_build_filename (mc_global.share_data_dir, MC_EXT_FILE, (char *) NULL);
+ if (!exist_file (extension_file))
+ MC_PTR_FREE (extension_file);
+ }
+ mc_user_ext = FALSE;
+ }
+
+ if (extension_file != NULL)
+ {
+ ext_ini = mc_config_init (extension_file, TRUE);
+ g_free (extension_file);
+ }
+ if (ext_ini == NULL)
+ return FALSE;
+
+ /* Check version */
+ if (!mc_config_has_group (ext_ini, descr_group))
+ {
+ flush_extension_file ();
+
+ if (!mc_user_ext)
+ {
+ message (D_ERROR, MSG_ERROR,
+ _("The format of the\n%s%s\nfile has changed with version 4.0.\n"
+ "It seems that the installation has failed.\nPlease fetch a fresh copy "
+ "from the Midnight Commander package."),
+ mc_global.sysconfig_dir, MC_EXT_FILE);
+ return FALSE;
+ }
+
+ home_error = TRUE;
+ goto check_stock_mc_ext;
+ }
+
+ if (home_error)
+ {
+ extension_file = mc_config_get_full_path (MC_EXT_FILE);
+ message (D_ERROR, MSG_ERROR,
+ _("The format of the\n%s\nfile has changed with version 4.0.\nYou may either want "
+ "to copy it from\n%s%s\nor use that file as an example of how to write it."),
+ extension_file, mc_global.sysconfig_dir, MC_EXT_FILE);
+ g_free (extension_file);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+flush_extension_file (void)
+{
+ g_strfreev (ext_ini_groups);
+ ext_ini_groups = NULL;
+
+ mc_config_deinit (ext_ini);
+ ext_ini = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * The second argument is action, i.e. Open, View or Edit
+ * Use target object to open file in.
+ *
+ * This function returns:
+ *
+ * -1 for a failure or user interrupt
+ * 0 if no command was run
+ * 1 if some command was run
+ *
+ * If action == "View" then a parameter is checked in the form of "View:%d",
+ * if the value for %d exists, then the viewer is started up at that line number.
+ */
+
+int
+regex_command_for (void *target, const vfs_path_t * filename_vpath, const char *action,
+ vfs_path_t ** script_vpath)
+{
+ const char *filename;
+ size_t filename_len;
+ gboolean found = FALSE;
+ gboolean error_flag = FALSE;
+ int ret = 0;
+ struct stat mystat;
+ int view_at_line_number = 0;
+#ifdef USE_FILE_CMD
+ gboolean have_type = FALSE; /* Flag used by regex_check_type() */
+#endif
+ char **group_iter;
+ char *include_group = NULL;
+ const char *current_group;
+
+ if (filename_vpath == NULL)
+ return 0;
+
+ if (script_vpath != NULL)
+ *script_vpath = NULL;
+
+ /* Check for the special View:%d parameter */
+ if (strncmp (action, "View:", 5) == 0)
+ {
+ view_at_line_number = atoi (action + 5);
+ action = "View";
+ }
+
+ if (ext_ini == NULL && !load_extension_file ())
+ return 0;
+
+ mc_stat (filename_vpath, &mystat);
+
+ filename = vfs_path_get_last_path_str (filename_vpath);
+ filename = x_basename (filename);
+ filename_len = strlen (filename);
+
+ if (ext_ini_groups == NULL)
+ ext_ini_groups = mc_config_get_groups (ext_ini, NULL);
+
+ /* find matched type, regex or shell pattern */
+ for (group_iter = ext_ini_groups; *group_iter != NULL && !found; group_iter++)
+ {
+ enum
+ {
+ TYPE_UNUSED,
+ TYPE_NOT_FOUND,
+ TYPE_FOUND
+ } type_state = TYPE_UNUSED;
+
+ const gchar *g = *group_iter;
+ gchar *pattern;
+ gboolean ignore_case;
+
+ if (strcmp (g, descr_group) == 0 || strncmp (g, "Include/", 8) == 0
+ || strcmp (g, default_group) == 0)
+ continue;
+
+ /* The "Directory" parameter is a special case: if it's present then
+ "Type", "Regex", and "Shell" parameters are ignored */
+ pattern = mc_config_get_string_raw (ext_ini, g, "Directory", NULL);
+ if (pattern != NULL)
+ {
+ found = S_ISDIR (mystat.st_mode)
+ && mc_search (pattern, DEFAULT_CHARSET, vfs_path_as_str (filename_vpath),
+ MC_SEARCH_T_REGEX);
+ g_free (pattern);
+
+ continue; /* stop if found */
+ }
+
+#ifdef USE_FILE_CMD
+ if (use_file_to_check_type)
+ {
+ pattern = mc_config_get_string_raw (ext_ini, g, "Type", NULL);
+ if (pattern != NULL)
+ {
+ GError *mcerror = NULL;
+
+ ignore_case = mc_config_get_bool (ext_ini, g, "TypeIgnoreCase", FALSE);
+ type_state =
+ regex_check_type (filename_vpath, pattern, ignore_case, &have_type, &mcerror)
+ ? TYPE_FOUND : TYPE_NOT_FOUND;
+ g_free (pattern);
+
+ if (mc_error_message (&mcerror, NULL))
+ error_flag = TRUE; /* leave it if file cannot be opened */
+
+ if (type_state == TYPE_NOT_FOUND)
+ continue;
+ }
+ }
+#endif /* USE_FILE_CMD */
+
+ pattern = mc_config_get_string_raw (ext_ini, g, "Regex", NULL);
+ if (pattern != NULL)
+ {
+ mc_search_t *search;
+
+ ignore_case = mc_config_get_bool (ext_ini, g, "RegexIgnoreCase", FALSE);
+ search = mc_search_new (pattern, DEFAULT_CHARSET);
+ g_free (pattern);
+
+ if (search != NULL)
+ {
+ search->search_type = MC_SEARCH_T_REGEX;
+ search->is_case_sensitive = !ignore_case;
+ found = mc_search_run (search, filename, 0, filename_len, NULL);
+ mc_search_free (search);
+ }
+
+ found = found && (type_state == TYPE_UNUSED || type_state == TYPE_FOUND);
+ }
+ else
+ {
+ pattern = mc_config_get_string_raw (ext_ini, g, "Shell", NULL);
+ if (pattern != NULL)
+ {
+ int (*cmp_func) (const char *s1, const char *s2, size_t n);
+ size_t pattern_len;
+
+ ignore_case = mc_config_get_bool (ext_ini, g, "ShellIgnoreCase", FALSE);
+ cmp_func = ignore_case ? strncasecmp : strncmp;
+ pattern_len = strlen (pattern);
+
+ if (*pattern == '.' && filename_len >= pattern_len)
+ found =
+ cmp_func (pattern, filename + filename_len - pattern_len, pattern_len) == 0;
+ else
+ found = pattern_len == filename_len
+ && cmp_func (pattern, filename, filename_len) == 0;
+
+ g_free (pattern);
+
+ found = found && (type_state == TYPE_UNUSED || type_state == TYPE_FOUND);
+ }
+ else
+ found = type_state == TYPE_FOUND;
+ }
+ }
+
+ /* group is found, process actions */
+ if (found)
+ {
+ char *include_value;
+
+ group_iter--;
+
+ /* "Include" parameter has the highest priority over any actions */
+ include_value = mc_config_get_string_raw (ext_ini, *group_iter, "Include", NULL);
+ if (include_value != NULL)
+ {
+ /* find "Include/include_value" group */
+ include_group = g_strconcat ("Include/", include_value, (char *) NULL);
+ g_free (include_value);
+ found = mc_config_has_group (ext_ini, include_group);
+ }
+ }
+
+ if (found)
+ current_group = include_group != NULL ? include_group : *group_iter;
+ else
+ {
+ current_group = default_group;
+ found = mc_config_has_group (ext_ini, current_group);
+ }
+
+ if (found && !error_flag)
+ {
+ gchar *action_value;
+
+ action_value = mc_config_get_string_raw (ext_ini, current_group, action, NULL);
+ if (action_value == NULL)
+ {
+ /* Not found, try the action from default section */
+ action_value = mc_config_get_string_raw (ext_ini, default_group, action, NULL);
+ found = (action_value != NULL && *action_value != '\0');
+ }
+ else
+ {
+ /* If action's value is empty, ignore action from default section */
+ found = (*action_value != '\0');
+ }
+
+ if (found)
+ {
+ vfs_path_t *sv;
+
+ sv = exec_extension (current_panel, target, filename_vpath, action_value,
+ view_at_line_number);
+ if (script_vpath != NULL)
+ *script_vpath = sv;
+ else
+ exec_cleanup_script (sv);
+
+ ret = 1;
+ }
+
+ g_free (action_value);
+ }
+
+ g_free (include_group);
+
+ return (error_flag ? -1 : ret);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/ext.h b/src/filemanager/ext.h
new file mode 100644
index 0000000..771ca18
--- /dev/null
+++ b/src/filemanager/ext.h
@@ -0,0 +1,33 @@
+/** \file ext.h
+ * \brief Header: extension dependent execution
+ */
+
+#ifndef MC__EXT_H
+#define MC__EXT_H
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+int regex_command_for (void *target, const vfs_path_t * filename_vpath, const char *action,
+ vfs_path_t ** script_vpath);
+
+/* Call it after the user has edited the mc.ext file,
+ * to flush the cached mc.ext file
+ */
+void flush_extension_file (void);
+
+/*** inline functions ****************************************************************************/
+
+static inline int
+regex_command (const vfs_path_t * filename_vpath, const char *action)
+{
+ return regex_command_for (NULL, filename_vpath, action, NULL);
+}
+
+#endif /* MC__EXT_H */
diff --git a/src/filemanager/file.c b/src/filemanager/file.c
new file mode 100644
index 0000000..fa2ef44
--- /dev/null
+++ b/src/filemanager/file.c
@@ -0,0 +1,3562 @@
+/*
+ File management.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Janne Kukonlehto, 1994, 1995
+ Fred Leeflang, 1994, 1995
+ Miguel de Icaza, 1994, 1995, 1996
+ Jakub Jelinek, 1995, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Andrew Borodin <aborodin@vmail.ru>, 2011-2022
+
+ The copy code was based in GNU's cp, and was written by:
+ Torbjorn Granlund, David MacKenzie, and Jim Meyering.
+
+ The move code was based in GNU's mv, and was written by:
+ Mike Parker and David MacKenzie.
+
+ Janne Kukonlehto added much error recovery to them for being used
+ in an interactive program.
+
+ 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/>.
+ */
+
+/*
+ * Please note that all dialogs used here must be safe for background
+ * operations.
+ */
+
+/** \file src/filemanager/file.c
+ * \brief Source: file management
+ */
+
+/* {{{ Include files */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h"
+#include "lib/search.h"
+#include "lib/strescape.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/vfs/vfs.h"
+#include "lib/widget.h"
+
+#include "src/setup.h"
+#ifdef ENABLE_BACKGROUND
+#include "src/background.h" /* do_background() */
+#endif
+
+/* Needed for other_panel and WTree */
+#include "dir.h"
+#include "filegui.h"
+#include "filenot.h"
+#include "tree.h"
+#include "filemanager.h" /* other_panel */
+#include "layout.h" /* rotate_dash() */
+#include "ioblksize.h" /* io_blksize() */
+
+#include "file.h"
+
+/* }}} */
+
+/*** global variables ****************************************************************************/
+
+/* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
+const char *op_names[3] = {
+ N_("DialogTitle|Copy"),
+ N_("DialogTitle|Move"),
+ N_("DialogTitle|Delete")
+};
+
+/*** file scope macro definitions ****************************************************************/
+
+#define FILEOP_UPDATE_INTERVAL 2
+#define FILEOP_STALLING_INTERVAL 4
+#define FILEOP_UPDATE_INTERVAL_US (FILEOP_UPDATE_INTERVAL * G_USEC_PER_SEC)
+#define FILEOP_STALLING_INTERVAL_US (FILEOP_STALLING_INTERVAL * G_USEC_PER_SEC)
+
+/*** file scope type declarations ****************************************************************/
+
+/* This is a hard link cache */
+struct link
+{
+ const struct vfs_class *vfs;
+ dev_t dev;
+ ino_t ino;
+ short linkcount;
+ mode_t st_mode;
+ vfs_path_t *src_vpath;
+ vfs_path_t *dst_vpath;
+};
+
+/* Status of the destination file */
+typedef enum
+{
+ DEST_NONE = 0, /**< Not created */
+ DEST_SHORT_QUERY, /**< Created, not fully copied, query to do */
+ DEST_SHORT_KEEP, /**< Created, not fully copied, keep it */
+ DEST_SHORT_DELETE, /**< Created, not fully copied, delete it */
+ DEST_FULL /**< Created, fully copied */
+} dest_status_t;
+
+/* Status of hard link creation */
+typedef enum
+{
+ HARDLINK_OK = 0, /**< Hardlink was created successfully */
+ HARDLINK_CACHED, /**< Hardlink was added to the cache */
+ HARDLINK_NOTLINK, /**< This is not a hard link */
+ HARDLINK_UNSUPPORTED, /**< VFS doesn't support hard links */
+ HARDLINK_ERROR, /**< Hard link creation error */
+ HARDLINK_ABORT /**< Stop file operation after hardlink creation error */
+} hardlink_status_t;
+
+/*
+ * This array introduced to avoid translation problems. The former (op_names)
+ * is assumed to be nouns, suitable in dialog box titles; this one should
+ * contain whatever is used in prompt itself (i.e. in russian, it's verb).
+ * (I don't use spaces around the words, because someday they could be
+ * dropped, when widgets get smarter)
+ */
+
+/* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
+static const char *op_names1[] = {
+ N_("FileOperation|Copy"),
+ N_("FileOperation|Move"),
+ N_("FileOperation|Delete")
+};
+
+/*
+ * These are formats for building a prompt. Parts encoded as follows:
+ * %o - operation from op_names1
+ * %f - file/files or files/directories, as appropriate
+ * %m - "with source mask" or question mark for delete
+ * %s - source name (truncated)
+ * %d - number of marked files
+ * %n - the '\n' symbol to form two-line prompt for delete or space for other operations
+ */
+/* xgettext:no-c-format */
+static const char *one_format = N_("%o %f%n\"%s\"%m");
+/* xgettext:no-c-format */
+static const char *many_format = N_("%o %d %f%m");
+
+static const char *prompt_parts[] = {
+ N_("file"),
+ N_("files"),
+ N_("directory"),
+ N_("directories"),
+ N_("files/directories"),
+ /* TRANSLATORS: keep leading space here to split words in Copy/Move dialog */
+ N_(" with source mask:")
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* the hard link cache */
+static GSList *linklist = NULL;
+
+/* the files-to-be-erased list */
+static GQueue *erase_list = NULL;
+
+/*
+ * In copy_dir_dir we use two additional single linked lists: The first -
+ * variable name 'parent_dirs' - holds information about already copied
+ * directories and is used to detect cyclic symbolic links.
+ * The second ('dest_dirs' below) holds information about just created
+ * target directories and is used to detect when an directory is copied
+ * into itself (we don't want to copy infinitely).
+ * Both lists don't use the linkcount and name structure members of struct
+ * link.
+ */
+static GSList *dest_dirs = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dirsize_status_locate_buttons (dirsize_status_msg_t * dsm)
+{
+ status_msg_t *sm = STATUS_MSG (dsm);
+ Widget *wd = WIDGET (sm->dlg);
+ int y, x;
+ WRect r;
+
+ y = wd->rect.y + 5;
+ x = wd->rect.x;
+
+ if (!dsm->allow_skip)
+ {
+ /* single button: "Abort" */
+ x += (wd->rect.cols - dsm->abort_button->rect.cols) / 2;
+ r = dsm->abort_button->rect;
+ r.y = y;
+ r.x = x;
+ widget_set_size_rect (dsm->abort_button, &r);
+ }
+ else
+ {
+ /* two buttons: "Abort" and "Skip" */
+ int cols;
+
+ cols = dsm->abort_button->rect.cols + dsm->skip_button->rect.cols + 1;
+ x += (wd->rect.cols - cols) / 2;
+ r = dsm->abort_button->rect;
+ r.y = y;
+ r.x = x;
+ widget_set_size_rect (dsm->abort_button, &r);
+ x += dsm->abort_button->rect.cols + 1;
+ r = dsm->skip_button->rect;
+ r.y = y;
+ r.x = x;
+ widget_set_size_rect (dsm->skip_button, &r);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+build_dest (file_op_context_t * ctx, const char *src, const char *dest, FileProgressStatus * status)
+{
+ char *s, *q;
+ const char *fnsource;
+
+ *status = FILE_CONT;
+
+ s = g_strdup (src);
+
+ /* We remove \n from the filename since regex routines would use \n as an anchor */
+ /* this is just to be allowed to maniupulate file names with \n on it */
+ for (q = s; *q != '\0'; q++)
+ if (*q == '\n')
+ *q = ' ';
+
+ fnsource = x_basename (s);
+
+ if (!mc_search_run (ctx->search_handle, fnsource, 0, strlen (fnsource), NULL))
+ {
+ q = NULL;
+ *status = FILE_SKIP;
+ }
+ else
+ {
+ q = mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask);
+ if (ctx->search_handle->error != MC_SEARCH_E_OK)
+ {
+ if (ctx->search_handle->error_str != NULL)
+ message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
+
+ *status = FILE_ABORT;
+ }
+ }
+
+ MC_PTR_FREE (s);
+
+ if (*status == FILE_CONT)
+ {
+ char *repl_dest;
+
+ repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
+ if (ctx->search_handle->error == MC_SEARCH_E_OK)
+ s = mc_build_filename (repl_dest, q, (char *) NULL);
+ else
+ {
+ if (ctx->search_handle->error_str != NULL)
+ message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
+
+ *status = FILE_ABORT;
+ }
+
+ g_free (repl_dest);
+ }
+
+ g_free (q);
+
+ return s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+free_link (void *data)
+{
+ struct link *lp = (struct link *) data;
+
+ vfs_path_free (lp->src_vpath, TRUE);
+ vfs_path_free (lp->dst_vpath, TRUE);
+ g_free (lp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void *
+free_erase_list (GQueue * lp)
+{
+ if (lp != NULL)
+ g_queue_free_full (lp, free_link);
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void *
+free_linklist (GSList * lp)
+{
+ g_slist_free_full (lp, free_link);
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const struct link *
+is_in_linklist (const GSList * lp, const vfs_path_t * vpath, const struct stat *sb)
+{
+ const struct vfs_class *class;
+ ino_t ino = sb->st_ino;
+ dev_t dev = sb->st_dev;
+
+ class = vfs_path_get_last_path_vfs (vpath);
+
+ for (; lp != NULL; lp = (const GSList *) g_slist_next (lp))
+ {
+ const struct link *lnk = (const struct link *) lp->data;
+
+ if (lnk->vfs == class && lnk->ino == ino && lnk->dev == dev)
+ return lnk;
+ }
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check and made hardlink
+ *
+ * @return FALSE if the inode wasn't found in the cache and TRUE if it was found
+ * and a hardlink was successfully made
+ */
+
+static hardlink_status_t
+check_hardlinks (const vfs_path_t * src_vpath, const struct stat *src_stat,
+ const vfs_path_t * dst_vpath, gboolean * skip_all)
+{
+ struct link *lnk;
+ ino_t ino = src_stat->st_ino;
+ dev_t dev = src_stat->st_dev;
+
+ if (src_stat->st_nlink < 2)
+ return HARDLINK_NOTLINK;
+ if ((vfs_file_class_flags (src_vpath) & VFSF_NOLINKS) != 0)
+ return HARDLINK_UNSUPPORTED;
+
+ lnk = (struct link *) is_in_linklist (linklist, src_vpath, src_stat);
+ if (lnk != NULL)
+ {
+ int stat_result;
+ struct stat link_stat;
+
+ stat_result = mc_stat (lnk->src_vpath, &link_stat);
+
+ if (stat_result == 0 && link_stat.st_ino == ino && link_stat.st_dev == dev)
+ {
+ const struct vfs_class *lp_name_class;
+ const struct vfs_class *my_vfs;
+
+ lp_name_class = vfs_path_get_last_path_vfs (lnk->src_vpath);
+ my_vfs = vfs_path_get_last_path_vfs (src_vpath);
+
+ if (lp_name_class == my_vfs)
+ {
+ const struct vfs_class *p_class, *dst_name_class;
+
+ dst_name_class = vfs_path_get_last_path_vfs (dst_vpath);
+ p_class = vfs_path_get_last_path_vfs (lnk->dst_vpath);
+
+ if (dst_name_class == p_class)
+ {
+ gboolean ok;
+
+ while (!(ok = (mc_stat (lnk->dst_vpath, &link_stat) == 0)) && !*skip_all)
+ {
+ FileProgressStatus status;
+
+ status =
+ file_error (TRUE, _("Cannot stat hardlink source file \"%s\"\n%s"),
+ vfs_path_as_str (lnk->dst_vpath));
+ if (status == FILE_ABORT)
+ return HARDLINK_ABORT;
+ if (status == FILE_RETRY)
+ continue;
+ if (status == FILE_SKIPALL)
+ *skip_all = TRUE;
+ break;
+ }
+
+ /* if stat() finished unsuccessfully, don't try to create link */
+ if (!ok)
+ return HARDLINK_ERROR;
+
+ while (!(ok = (mc_link (lnk->dst_vpath, dst_vpath) == 0)) && !*skip_all)
+ {
+ FileProgressStatus status;
+
+ status =
+ file_error (TRUE, _("Cannot create target hardlink \"%s\"\n%s"),
+ vfs_path_as_str (dst_vpath));
+ if (status == FILE_ABORT)
+ return HARDLINK_ABORT;
+ if (status == FILE_RETRY)
+ continue;
+ if (status == FILE_SKIPALL)
+ *skip_all = TRUE;
+ break;
+ }
+
+ /* Success? */
+ return (ok ? HARDLINK_OK : HARDLINK_ERROR);
+ }
+ }
+ }
+
+ if (!*skip_all)
+ {
+ FileProgressStatus status;
+
+ /* Message w/o "Retry" action.
+ *
+ * FIXME: Can't say what errno is here. Define it and don't display.
+ *
+ * file_error() displays a message with text representation of errno
+ * and the string passed to file_error() should provide the format "%s"
+ * for that at end (see previous file_error() call for the reference).
+ * But if format for errno isn't provided, it is safe, because C standard says:
+ * "If the format is exhausted while arguments remain, the excess arguments
+ * are evaluated (as always) but are otherwise ignored" (ISO/IEC 9899:1999,
+ * section 7.19.6.1, paragraph 2).
+ *
+ */
+ errno = 0;
+ status =
+ file_error (FALSE, _("Cannot create target hardlink \"%s\""),
+ vfs_path_as_str (dst_vpath));
+
+ if (status == FILE_ABORT)
+ return HARDLINK_ABORT;
+
+ if (status == FILE_SKIPALL)
+ *skip_all = TRUE;
+ }
+
+ return HARDLINK_ERROR;
+ }
+
+ lnk = g_try_new (struct link, 1);
+ if (lnk != NULL)
+ {
+ lnk->vfs = vfs_path_get_last_path_vfs (src_vpath);
+ lnk->ino = ino;
+ lnk->dev = dev;
+ lnk->linkcount = 0;
+ lnk->st_mode = 0;
+ lnk->src_vpath = vfs_path_clone (src_vpath);
+ lnk->dst_vpath = vfs_path_clone (dst_vpath);
+
+ linklist = g_slist_prepend (linklist, lnk);
+ }
+
+ return HARDLINK_CACHED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Duplicate the contents of the symbolic link src_vpath in dst_vpath.
+ * Try to make a stable symlink if the option "stable symlink" was
+ * set in the file mask dialog.
+ * If dst_path is an existing symlink it will be deleted silently
+ * (upper levels take already care of existing files at dst_vpath).
+ */
+
+static FileProgressStatus
+make_symlink (file_op_context_t * ctx, const vfs_path_t * src_vpath, const vfs_path_t * dst_vpath)
+{
+ const char *src_path;
+ const char *dst_path;
+ char link_target[MC_MAXPATHLEN];
+ int len;
+ FileProgressStatus return_status;
+ struct stat dst_stat;
+ gboolean dst_is_symlink;
+ vfs_path_t *link_target_vpath = NULL;
+
+ src_path = vfs_path_as_str (src_vpath);
+ dst_path = vfs_path_as_str (dst_vpath);
+
+ dst_is_symlink = (mc_lstat (dst_vpath, &dst_stat) == 0) && S_ISLNK (dst_stat.st_mode);
+
+ retry_src_readlink:
+ len = mc_readlink (src_vpath, link_target, sizeof (link_target) - 1);
+ if (len < 0)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot read source link \"%s\"\n%s"), src_path);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_RETRY)
+ goto retry_src_readlink;
+ }
+ goto ret;
+ }
+
+ link_target[len] = '\0';
+
+ if (ctx->stable_symlinks && !(vfs_file_is_local (src_vpath) && vfs_file_is_local (dst_vpath)))
+ {
+ message (D_ERROR, MSG_ERROR,
+ _("Cannot make stable symlinks across "
+ "non-local filesystems:\n\nOption Stable Symlinks will be disabled"));
+ ctx->stable_symlinks = FALSE;
+ }
+
+ if (ctx->stable_symlinks && !g_path_is_absolute (link_target))
+ {
+ const char *r;
+
+ r = strrchr (src_path, PATH_SEP);
+ if (r != NULL)
+ {
+ size_t slen;
+ GString *p;
+ vfs_path_t *q;
+
+ slen = r - src_path + 1;
+
+ p = g_string_sized_new (slen + len);
+ g_string_append_len (p, src_path, slen);
+
+ if (g_path_is_absolute (dst_path))
+ q = vfs_path_from_str_flags (dst_path, VPF_NO_CANON);
+ else
+ q = vfs_path_build_filename (p->str, dst_path, (char *) NULL);
+
+ if (vfs_path_tokens_count (q) > 1)
+ {
+ char *s = NULL;
+ vfs_path_t *tmp_vpath1, *tmp_vpath2;
+
+ g_string_append_len (p, link_target, len);
+ tmp_vpath1 = vfs_path_vtokens_get (q, -1, 1);
+ tmp_vpath2 = vfs_path_from_str (p->str);
+ s = diff_two_paths (tmp_vpath1, tmp_vpath2);
+ vfs_path_free (tmp_vpath2, TRUE);
+ vfs_path_free (tmp_vpath1, TRUE);
+ g_strlcpy (link_target, s != NULL ? s : p->str, sizeof (link_target));
+ g_free (s);
+ }
+
+ g_string_free (p, TRUE);
+ vfs_path_free (q, TRUE);
+ }
+ }
+ link_target_vpath = vfs_path_from_str_flags (link_target, VPF_NO_CANON);
+
+ retry_dst_symlink:
+ if (mc_symlink (link_target_vpath, dst_vpath) == 0)
+ {
+ /* Success */
+ return_status = FILE_CONT;
+ goto ret;
+ }
+ /*
+ * if dst_exists, it is obvious that this had failed.
+ * We can delete the old symlink and try again...
+ */
+ if (dst_is_symlink && mc_unlink (dst_vpath) == 0
+ && mc_symlink (link_target_vpath, dst_vpath) == 0)
+ {
+ /* Success */
+ return_status = FILE_CONT;
+ goto ret;
+ }
+
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot create target symlink \"%s\"\n%s"), dst_path);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_RETRY)
+ goto retry_dst_symlink;
+ }
+
+ ret:
+ vfs_path_free (link_target_vpath, TRUE);
+ return return_status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * do_compute_dir_size:
+ *
+ * Computes the number of bytes used by the files in a directory
+ */
+
+static FileProgressStatus
+do_compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * dsm,
+ size_t * dir_count, size_t * ret_marked, uintmax_t * ret_total,
+ mc_stat_fn stat_func)
+{
+ static gint64 timestamp = 0;
+ /* update with 25 FPS rate */
+ static const gint64 delay = G_USEC_PER_SEC / 25;
+
+ status_msg_t *sm = STATUS_MSG (dsm);
+ int res;
+ struct stat s;
+ DIR *dir;
+ struct vfs_dirent *dirent;
+ FileProgressStatus ret = FILE_CONT;
+
+ (*dir_count)++;
+
+ dir = mc_opendir (dirname_vpath);
+ if (dir == NULL)
+ return ret;
+
+ while (ret == FILE_CONT && (dirent = mc_readdir (dir)) != NULL)
+ {
+ vfs_path_t *tmp_vpath;
+
+ if (DIR_IS_DOT (dirent->d_name) || DIR_IS_DOTDOT (dirent->d_name))
+ continue;
+
+ tmp_vpath = vfs_path_append_new (dirname_vpath, dirent->d_name, (char *) NULL);
+
+ res = stat_func (tmp_vpath, &s);
+ if (res == 0)
+ {
+ if (S_ISDIR (s.st_mode))
+ ret =
+ do_compute_dir_size (tmp_vpath, dsm, dir_count, ret_marked, ret_total,
+ stat_func);
+ else
+ {
+ ret = FILE_CONT;
+
+ (*ret_marked)++;
+ *ret_total += (uintmax_t) s.st_size;
+ }
+
+ if (ret == FILE_CONT && sm->update != NULL && mc_time_elapsed (&timestamp, delay))
+ {
+ dsm->dirname_vpath = tmp_vpath;
+ dsm->dir_count = *dir_count;
+ dsm->total_size = *ret_total;
+ ret = sm->update (sm);
+ }
+ }
+
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+
+ mc_closedir (dir);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * panel_compute_totals:
+ *
+ * compute the number of files and the number of bytes
+ * used up by the whole selection, recursing directories
+ * as required. In addition, it checks to see if it will
+ * overwrite any files by doing the copy.
+ */
+
+static FileProgressStatus
+panel_compute_totals (const WPanel * panel, dirsize_status_msg_t * sm, size_t * ret_count,
+ uintmax_t * ret_total, gboolean follow_symlinks)
+{
+ int i;
+ size_t dir_count = 0;
+ mc_stat_fn stat_func = follow_symlinks ? mc_stat : mc_lstat;
+
+ for (i = 0; i < panel->dir.len; i++)
+ {
+ const file_entry_t *fe = &panel->dir.list[i];
+ const struct stat *s;
+
+ if (fe->f.marked == 0)
+ continue;
+
+ s = &fe->st;
+
+ if (S_ISDIR (s->st_mode) || (follow_symlinks && link_isdir (fe) && fe->f.stale_link == 0))
+ {
+ vfs_path_t *p;
+ FileProgressStatus status;
+
+ p = vfs_path_append_new (panel->cwd_vpath, fe->fname->str, (char *) NULL);
+ status = do_compute_dir_size (p, sm, &dir_count, ret_count, ret_total, stat_func);
+ vfs_path_free (p, TRUE);
+
+ if (status != FILE_CONT)
+ return status;
+ }
+ else
+ {
+ (*ret_count)++;
+ *ret_total += (uintmax_t) s->st_size;
+ }
+ }
+
+ return FILE_CONT;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Initialize variables for progress bars */
+static FileProgressStatus
+panel_operate_init_totals (const WPanel * panel, const vfs_path_t * source,
+ const struct stat *source_stat, file_op_context_t * ctx,
+ gboolean compute_totals, filegui_dialog_type_t dialog_type)
+{
+ FileProgressStatus status;
+
+#ifdef ENABLE_BACKGROUND
+ if (mc_global.we_are_background)
+ return FILE_CONT;
+#endif
+
+ if (verbose && compute_totals)
+ {
+ dirsize_status_msg_t dsm;
+ gboolean stale_link = FALSE;
+
+ memset (&dsm, 0, sizeof (dsm));
+ dsm.allow_skip = TRUE;
+ status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb,
+ dirsize_status_update_cb, dirsize_status_deinit_cb);
+
+ ctx->progress_count = 0;
+ ctx->progress_bytes = 0;
+
+ if (source == NULL)
+ status = panel_compute_totals (panel, &dsm, &ctx->progress_count, &ctx->progress_bytes,
+ ctx->follow_links);
+ else if (S_ISDIR (source_stat->st_mode)
+ || (ctx->follow_links
+ && file_is_symlink_to_dir (source, (struct stat *) source_stat, &stale_link)
+ && !stale_link))
+ {
+ size_t dir_count = 0;
+
+ status = do_compute_dir_size (source, &dsm, &dir_count, &ctx->progress_count,
+ &ctx->progress_bytes, ctx->stat_func);
+ }
+ else
+ {
+ ctx->progress_count++;
+ ctx->progress_bytes += (uintmax_t) source_stat->st_size;
+ status = FILE_CONT;
+ }
+
+ status_msg_deinit (STATUS_MSG (&dsm));
+
+ ctx->progress_totals_computed = (status == FILE_CONT);
+
+ if (status == FILE_SKIP)
+ status = FILE_CONT;
+ }
+ else
+ {
+ status = FILE_CONT;
+ ctx->progress_count = panel->marked;
+ ctx->progress_bytes = panel->total;
+ ctx->progress_totals_computed = verbose && dialog_type == FILEGUI_DIALOG_ONE_ITEM;
+ }
+
+ /* destroy already created UI for single file rename operation */
+ file_op_context_destroy_ui (ctx);
+
+ file_op_context_create_ui (ctx, TRUE, dialog_type);
+
+ return status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+progress_update_one (file_op_total_context_t * tctx, file_op_context_t * ctx, off_t add)
+{
+ gint64 tv_current;
+ static gint64 tv_start = -1;
+
+ tctx->progress_count++;
+ tctx->progress_bytes += (uintmax_t) add;
+
+ tv_current = g_get_monotonic_time ();
+
+ if (tv_start < 0)
+ tv_start = tv_current;
+
+ if (tv_current - tv_start > FILEOP_UPDATE_INTERVAL_US)
+ {
+ if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
+ {
+ file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
+ file_progress_show_total (tctx, ctx, tctx->progress_bytes, TRUE);
+ }
+
+ tv_start = tv_current;
+ }
+
+ return check_progress_buttons (ctx);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+real_warn_same_file (enum OperationMode mode, const char *fmt, const char *a, const char *b)
+{
+ char *msg;
+ int result = 0;
+ const char *head_msg;
+ int width_a, width_b, width;
+
+ head_msg = mode == Foreground ? MSG_ERROR : _("Background process error");
+
+ width_a = str_term_width1 (a);
+ width_b = str_term_width1 (b);
+ width = COLS - 8;
+
+ if (width_a > width)
+ {
+ if (width_b > width)
+ {
+ char *s;
+
+ s = g_strndup (str_trunc (a, width), width);
+ b = str_trunc (b, width);
+ msg = g_strdup_printf (fmt, s, b);
+ g_free (s);
+ }
+ else
+ {
+ a = str_trunc (a, width);
+ msg = g_strdup_printf (fmt, a, b);
+ }
+ }
+ else
+ {
+ if (width_b > width)
+ b = str_trunc (b, width);
+
+ msg = g_strdup_printf (fmt, a, b);
+ }
+
+ result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
+ g_free (msg);
+ do_refresh ();
+
+ return (result == 1) ? FILE_ABORT : FILE_SKIP;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+warn_same_file (const char *fmt, const char *a, const char *b)
+{
+#ifdef ENABLE_BACKGROUND
+/* *INDENT-OFF* */
+ union
+ {
+ void *p;
+ FileProgressStatus (*f) (enum OperationMode, const char *fmt, const char *a, const char *b);
+ } pntr;
+/* *INDENT-ON* */
+
+ pntr.f = real_warn_same_file;
+
+ if (mc_global.we_are_background)
+ return parent_call (pntr.p, NULL, 3, strlen (fmt), fmt, strlen (a), a, strlen (b), b);
+#endif
+ return real_warn_same_file (Foreground, fmt, a, b);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+check_same_file (const char *a, const struct stat *ast, const char *b, const struct stat *bst,
+ FileProgressStatus * status)
+{
+ if (ast->st_dev != bst->st_dev || ast->st_ino != bst->st_ino)
+ return FALSE;
+
+ if (S_ISDIR (ast->st_mode))
+ *status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same directory"), a, b);
+ else
+ *status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), a, b);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+get_times (const struct stat *sb, mc_timesbuf_t * times)
+{
+#ifdef HAVE_UTIMENSAT
+ (*times)[0] = sb->st_atim;
+ (*times)[1] = sb->st_mtim;
+#else
+ times->actime = sb->st_atime;
+ times->modtime = sb->st_mtime;
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ Query/status report routines */
+
+static FileProgressStatus
+real_do_file_error (enum OperationMode mode, gboolean allow_retry, const char *error)
+{
+ int result;
+ const char *msg;
+
+ msg = mode == Foreground ? MSG_ERROR : _("Background process error");
+
+ if (allow_retry)
+ result =
+ query_dialog (msg, error, D_ERROR, 4, _("&Skip"), _("Ski&p all"), _("&Retry"),
+ _("&Abort"));
+ else
+ result = query_dialog (msg, error, D_ERROR, 3, _("&Skip"), _("Ski&p all"), _("&Abort"));
+
+ switch (result)
+ {
+ case 0:
+ do_refresh ();
+ return FILE_SKIP;
+
+ case 1:
+ do_refresh ();
+ return FILE_SKIPALL;
+
+ case 2:
+ if (allow_retry)
+ {
+ do_refresh ();
+ return FILE_RETRY;
+ }
+ MC_FALLTHROUGH;
+
+ case 3:
+ default:
+ return FILE_ABORT;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+real_query_recursive (file_op_context_t * ctx, enum OperationMode mode, const char *s)
+{
+ if (ctx->recursive_result < RECURSIVE_ALWAYS)
+ {
+ const char *msg;
+ char *text;
+
+ msg = mode == Foreground
+ ? _("Directory \"%s\" not empty.\nDelete it recursively?")
+ : _("Background process:\nDirectory \"%s\" not empty.\nDelete it recursively?");
+ text = g_strdup_printf (msg, path_trunc (s, 30));
+
+ if (safe_delete)
+ query_set_sel (1);
+
+ ctx->recursive_result =
+ query_dialog (op_names[OP_DELETE], text, D_ERROR, 5, _("&Yes"), _("&No"), _("A&ll"),
+ _("Non&e"), _("&Abort"));
+ g_free (text);
+
+ if (ctx->recursive_result != RECURSIVE_ABORT)
+ do_refresh ();
+ }
+
+ switch (ctx->recursive_result)
+ {
+ case RECURSIVE_YES:
+ case RECURSIVE_ALWAYS:
+ return FILE_CONT;
+
+ case RECURSIVE_NO:
+ case RECURSIVE_NEVER:
+ return FILE_SKIP;
+
+ case RECURSIVE_ABORT:
+ default:
+ return FILE_ABORT;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_BACKGROUND
+static FileProgressStatus
+do_file_error (gboolean allow_retry, const char *str)
+{
+/* *INDENT-OFF* */
+ union
+ {
+ void *p;
+ FileProgressStatus (*f) (enum OperationMode, gboolean, const char *);
+ } pntr;
+/* *INDENT-ON* */
+
+ pntr.f = real_do_file_error;
+
+ if (mc_global.we_are_background)
+ return parent_call (pntr.p, NULL, 2, sizeof (allow_retry), allow_retry, strlen (str), str);
+ else
+ return real_do_file_error (Foreground, allow_retry, str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+query_recursive (file_op_context_t * ctx, const char *s)
+{
+/* *INDENT-OFF* */
+ union
+ {
+ void *p;
+ FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *);
+ } pntr;
+/* *INDENT-ON* */
+
+ pntr.f = real_query_recursive;
+
+ if (mc_global.we_are_background)
+ return parent_call (pntr.p, ctx, 1, strlen (s), s);
+ else
+ return real_query_recursive (ctx, Foreground, s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+query_replace (file_op_context_t * ctx, const char *src, struct stat *src_stat, const char *dst,
+ struct stat *dst_stat)
+{
+/* *INDENT-OFF* */
+ union
+ {
+ void *p;
+ FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *,
+ struct stat *, const char *, struct stat *);
+ } pntr;
+/* *INDENT-ON* */
+
+ pntr.f = file_progress_real_query_replace;
+
+ if (mc_global.we_are_background)
+ return parent_call (pntr.p, ctx, 4, strlen (src), src, sizeof (struct stat), src_stat,
+ strlen (dst), dst, sizeof (struct stat), dst_stat);
+ else
+ return file_progress_real_query_replace (ctx, Foreground, src, src_stat, dst, dst_stat);
+}
+
+#else
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+do_file_error (gboolean allow_retry, const char *str)
+{
+ return real_do_file_error (Foreground, allow_retry, str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+query_recursive (file_op_context_t * ctx, const char *s)
+{
+ return real_query_recursive (ctx, Foreground, s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+query_replace (file_op_context_t * ctx, const char *src, struct stat *src_stat, const char *dst,
+ struct stat *dst_stat)
+{
+ return file_progress_real_query_replace (ctx, Foreground, src, src_stat, dst, dst_stat);
+}
+
+#endif /* !ENABLE_BACKGROUND */
+
+/* --------------------------------------------------------------------------------------------- */
+/** Report error with two files */
+
+static FileProgressStatus
+files_error (const char *format, const char *file1, const char *file2)
+{
+ char buf[BUF_MEDIUM];
+ char *nfile1 = g_strdup (path_trunc (file1, 15));
+ char *nfile2 = g_strdup (path_trunc (file2, 15));
+
+ g_snprintf (buf, sizeof (buf), format, nfile1, nfile2, unix_error_string (errno));
+
+ g_free (nfile1);
+ g_free (nfile2);
+
+ return do_file_error (TRUE, buf);
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+copy_file_file_display_progress (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ gint64 tv_current, gint64 tv_transfer_start, off_t file_size,
+ off_t file_part)
+{
+ gint64 dt;
+
+ /* Update rotating dash after some time */
+ rotate_dash (TRUE);
+
+ /* Compute ETA */
+ dt = (tv_current - tv_transfer_start) / G_USEC_PER_SEC;
+
+ if (file_part == 0)
+ ctx->eta_secs = 0.0;
+ else
+ ctx->eta_secs = ((dt / (double) file_part) * file_size) - dt;
+
+ /* Compute BPS rate */
+ ctx->bps_time = MAX (1, dt);
+ ctx->bps = file_part / ctx->bps_time;
+
+ /* Compute total ETA and BPS */
+ if (ctx->progress_bytes != 0)
+ {
+ uintmax_t remain_bytes;
+
+ remain_bytes = ctx->progress_bytes - tctx->copied_bytes;
+#if 1
+ {
+ gint64 total_secs;
+
+ total_secs = (tv_current - tctx->transfer_start) / G_USEC_PER_SEC;
+ total_secs = MAX (1, total_secs);
+
+ tctx->bps = tctx->copied_bytes / total_secs;
+ tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0;
+ }
+#else
+ /* broken on lot of little files */
+ tctx->bps_count++;
+ tctx->bps = (tctx->bps * (tctx->bps_count - 1) + ctx->bps) / tctx->bps_count;
+ tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0;
+#endif
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+try_remove_file (file_op_context_t * ctx, const vfs_path_t * vpath, FileProgressStatus * status)
+{
+ while (mc_unlink (vpath) != 0 && !ctx->skip_all)
+ {
+ *status = file_error (TRUE, _("Cannot remove file \"%s\"\n%s"), vfs_path_as_str (vpath));
+ if (*status == FILE_RETRY)
+ continue;
+ if (*status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* {{{ Move routines */
+
+/**
+ * Move single file or one of many files from one location to another.
+ *
+ * @panel pointer to panel in case of single file, NULL otherwise
+ * @tctx file operation total context object
+ * @ctx file operation context object
+ * @s source file name
+ * @d destination file name
+ *
+ * @return operation result
+ */
+static FileProgressStatus
+move_file_file (const WPanel * panel, file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *s, const char *d)
+{
+ struct stat src_stat, dst_stat;
+ FileProgressStatus return_status = FILE_CONT;
+ gboolean copy_done = FALSE;
+ gboolean old_ask_overwrite;
+ vfs_path_t *src_vpath, *dst_vpath;
+
+ src_vpath = vfs_path_from_str (s);
+ dst_vpath = vfs_path_from_str (d);
+
+ file_progress_show_source (ctx, src_vpath);
+ file_progress_show_target (ctx, dst_vpath);
+
+ /* FIXME: do we really need to check buttons in case of single file? */
+ if (check_progress_buttons (ctx) == FILE_ABORT)
+ {
+ return_status = FILE_ABORT;
+ goto ret;
+ }
+
+ mc_refresh ();
+
+ while (mc_lstat (src_vpath, &src_stat) != 0)
+ {
+ /* Source doesn't exist */
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot stat file \"%s\"\n%s"), s);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ }
+
+ if (return_status != FILE_RETRY)
+ goto ret;
+ }
+
+ if (mc_lstat (dst_vpath, &dst_stat) == 0)
+ {
+ if (check_same_file (s, &src_stat, d, &dst_stat, &return_status))
+ goto ret;
+
+ if (S_ISDIR (dst_stat.st_mode))
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot overwrite directory \"%s\""), d);
+ do_refresh ();
+ return_status = FILE_SKIP;
+ goto ret;
+ }
+
+ if (confirm_overwrite)
+ {
+ return_status = query_replace (ctx, s, &src_stat, d, &dst_stat);
+ if (return_status != FILE_CONT)
+ goto ret;
+ }
+ /* Ok to overwrite */
+ }
+
+ if (!ctx->do_append)
+ {
+ if (S_ISLNK (src_stat.st_mode) && ctx->stable_symlinks)
+ {
+ return_status = make_symlink (ctx, src_vpath, dst_vpath);
+ if (return_status == FILE_CONT)
+ {
+ if (ctx->preserve)
+ {
+ mc_timesbuf_t times;
+
+ get_times (&src_stat, &times);
+ mc_utime (dst_vpath, &times);
+ }
+ goto retry_src_remove;
+ }
+ goto ret;
+ }
+
+ if (mc_rename (src_vpath, dst_vpath) == 0)
+ {
+ /* FIXME: do we really need to update progress in case of single file? */
+ return_status = progress_update_one (tctx, ctx, src_stat.st_size);
+ goto ret;
+ }
+ }
+#if 0
+ /* Comparison to EXDEV seems not to work in nfs if you're moving from
+ one nfs to the same, but on the server it is on two different
+ filesystems. Then nfs returns EIO instead of EXDEV.
+ Hope it will not hurt if we always in case of error try to copy/delete. */
+ else
+ errno = EXDEV; /* Hack to copy (append) the file and then delete it */
+
+ if (errno != EXDEV)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s, d);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_RETRY)
+ goto retry_rename;
+ }
+
+ goto ret;
+ }
+#endif
+
+ /* Failed rename -> copy the file instead */
+ if (panel != NULL)
+ {
+ /* In case of single file, calculate totals. In case of many files,
+ totals are calculated already. */
+ return_status =
+ panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
+ FILEGUI_DIALOG_ONE_ITEM);
+ if (return_status != FILE_CONT)
+ goto ret;
+ }
+
+ old_ask_overwrite = tctx->ask_overwrite;
+ tctx->ask_overwrite = FALSE;
+ return_status = copy_file_file (tctx, ctx, s, d);
+ tctx->ask_overwrite = old_ask_overwrite;
+ if (return_status != FILE_CONT)
+ goto ret;
+
+ copy_done = TRUE;
+
+ /* FIXME: there is no need to update progress and check buttons
+ at the finish of single file operation. */
+ if (panel == NULL)
+ {
+ file_progress_show_source (ctx, NULL);
+ file_progress_show (ctx, 0, 0, "", FALSE);
+
+ return_status = check_progress_buttons (ctx);
+ if (return_status != FILE_CONT)
+ goto ret;
+ }
+
+ mc_refresh ();
+
+ retry_src_remove:
+ if (!try_remove_file (ctx, src_vpath, &return_status) && panel == NULL)
+ goto ret;
+
+ if (!copy_done)
+ return_status = progress_update_one (tctx, ctx, src_stat.st_size);
+
+ ret:
+ vfs_path_free (src_vpath, TRUE);
+ vfs_path_free (dst_vpath, TRUE);
+
+ return return_status;
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ Erase routines */
+/** Don't update progress status if progress_count==NULL */
+
+static FileProgressStatus
+erase_file (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath)
+{
+ struct stat buf;
+ FileProgressStatus return_status;
+
+ /* check buttons if deleting info was changed */
+ if (file_progress_show_deleting (ctx, vfs_path_as_str (vpath), &tctx->progress_count))
+ {
+ file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
+ if (check_progress_buttons (ctx) == FILE_ABORT)
+ return FILE_ABORT;
+
+ mc_refresh ();
+ }
+
+ if (tctx->progress_count != 0 && mc_lstat (vpath, &buf) != 0)
+ {
+ /* ignore, most likely the mc_unlink fails, too */
+ buf.st_size = 0;
+ }
+
+ if (!try_remove_file (ctx, vpath, &return_status) && return_status == FILE_ABORT)
+ return FILE_ABORT;
+
+ if (tctx->progress_count == 0)
+ return FILE_CONT;
+
+ return check_progress_buttons (ctx);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+try_erase_dir (file_op_context_t * ctx, const char *dir)
+{
+ FileProgressStatus return_status = FILE_CONT;
+
+ while (my_rmdir (dir) != 0 && !ctx->skip_all)
+ {
+ return_status = file_error (TRUE, _("Cannot remove directory \"%s\"\n%s"), dir);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status != FILE_RETRY)
+ break;
+ }
+
+ return return_status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ Recursive remove of files
+ abort->cancel stack
+ skip ->warn every level, gets default
+ skipall->remove as much as possible
+*/
+static FileProgressStatus
+recursive_erase (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath)
+{
+ struct vfs_dirent *next;
+ DIR *reading;
+ const char *s;
+ FileProgressStatus return_status = FILE_CONT;
+
+ reading = mc_opendir (vpath);
+ if (reading == NULL)
+ return FILE_RETRY;
+
+ while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
+ {
+ vfs_path_t *tmp_vpath;
+ struct stat buf;
+
+ if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
+ continue;
+
+ tmp_vpath = vfs_path_append_new (vpath, next->d_name, (char *) NULL);
+ if (mc_lstat (tmp_vpath, &buf) != 0)
+ {
+ mc_closedir (reading);
+ vfs_path_free (tmp_vpath, TRUE);
+ return FILE_RETRY;
+ }
+ if (S_ISDIR (buf.st_mode))
+ return_status = recursive_erase (tctx, ctx, tmp_vpath);
+ else
+ return_status = erase_file (tctx, ctx, tmp_vpath);
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ mc_closedir (reading);
+
+ if (return_status == FILE_ABORT)
+ return FILE_ABORT;
+
+ s = vfs_path_as_str (vpath);
+
+ file_progress_show_deleting (ctx, s, NULL);
+ file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
+ if (check_progress_buttons (ctx) == FILE_ABORT)
+ return FILE_ABORT;
+
+ mc_refresh ();
+
+ return try_erase_dir (ctx, s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check if directory is empty or not.
+ *
+ * @param vpath directory handler
+ *
+ * @returns -1 on error,
+ * 1 if there are no entries besides "." and ".." in the directory path points to,
+ * 0 else.
+ *
+ * ATTENTION! Be careful when modifying this function (like commit 25e419ba0886f)!
+ * Some implementations of readdir() in MC VFS (for example, vfs_s_readdir(), which is used
+ * in FISH) don't return "." and ".." entries.
+ */
+static int
+check_dir_is_empty (const vfs_path_t * vpath)
+{
+ DIR *dir;
+ struct vfs_dirent *d;
+ int i = 1;
+
+ dir = mc_opendir (vpath);
+ if (dir == NULL)
+ return -1;
+
+ for (d = mc_readdir (dir); d != NULL; d = mc_readdir (dir))
+ if (!DIR_IS_DOT (d->d_name) && !DIR_IS_DOTDOT (d->d_name))
+ {
+ i = 0;
+ break;
+ }
+
+ mc_closedir (dir);
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+erase_dir_iff_empty (file_op_context_t * ctx, const vfs_path_t * vpath, size_t count)
+{
+ const char *s;
+
+ s = vfs_path_as_str (vpath);
+
+ file_progress_show_deleting (ctx, s, NULL);
+ file_progress_show_count (ctx, count, ctx->progress_count);
+ if (check_progress_buttons (ctx) == FILE_ABORT)
+ return FILE_ABORT;
+
+ mc_refresh ();
+
+ if (check_dir_is_empty (vpath) != 1)
+ return FILE_CONT;
+
+ /* not empty or error */
+ return try_erase_dir (ctx, s);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+erase_dir_after_copy (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const vfs_path_t * vpath, FileProgressStatus * status)
+{
+ if (ctx->erase_at_end && erase_list != NULL)
+ {
+ /* Reset progress count before delete to avoid counting files twice */
+ tctx->progress_count = tctx->prev_progress_count;
+
+ while (!g_queue_is_empty (erase_list) && *status != FILE_ABORT)
+ {
+ struct link *lp;
+
+ lp = (struct link *) g_queue_pop_head (erase_list);
+
+ if (S_ISDIR (lp->st_mode))
+ *status = erase_dir_iff_empty (ctx, lp->src_vpath, tctx->progress_count);
+ else
+ *status = erase_file (tctx, ctx, lp->src_vpath);
+
+ free_link (lp);
+ }
+
+ /* Save progress counter before move next directory */
+ tctx->prev_progress_count = tctx->progress_count;
+ }
+
+ erase_dir_iff_empty (ctx, vpath, tctx->progress_count);
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Move single directory or one of many directories from one location to another.
+ *
+ * @panel pointer to panel in case of single directory, NULL otherwise
+ * @tctx file operation total context object
+ * @ctx file operation context object
+ * @s source directory name
+ * @d destination directory name
+ *
+ * @return operation result
+ */
+static FileProgressStatus
+do_move_dir_dir (const WPanel * panel, file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *s, const char *d)
+{
+ struct stat src_stat, dst_stat;
+ FileProgressStatus return_status = FILE_CONT;
+ gboolean move_over = FALSE;
+ gboolean dstat_ok;
+ vfs_path_t *src_vpath, *dst_vpath;
+
+ src_vpath = vfs_path_from_str (s);
+ dst_vpath = vfs_path_from_str (d);
+
+ file_progress_show_source (ctx, src_vpath);
+ file_progress_show_target (ctx, dst_vpath);
+
+ /* FIXME: do we really need to check buttons in case of single directory? */
+ if (panel != NULL && check_progress_buttons (ctx) == FILE_ABORT)
+ {
+ return_status = FILE_ABORT;
+ goto ret_fast;
+ }
+
+ mc_refresh ();
+
+ mc_stat (src_vpath, &src_stat);
+
+ dstat_ok = (mc_stat (dst_vpath, &dst_stat) == 0);
+
+ if (dstat_ok && check_same_file (s, &src_stat, d, &dst_stat, &return_status))
+ goto ret_fast;
+
+ if (!dstat_ok)
+ ; /* destination doesn't exist */
+ else if (!ctx->dive_into_subdirs)
+ move_over = TRUE;
+ else
+ {
+ vfs_path_t *tmp;
+
+ tmp = dst_vpath;
+ dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
+ vfs_path_free (tmp, TRUE);
+ }
+
+ d = vfs_path_as_str (dst_vpath);
+
+ /* Check if the user inputted an existing dir */
+ retry_dst_stat:
+ if (mc_stat (dst_vpath, &dst_stat) == 0)
+ {
+ if (move_over)
+ {
+ if (panel != NULL)
+ {
+ /* In case of single directory, calculate totals. In case of many directories,
+ totals are calculated already. */
+ return_status =
+ panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
+ FILEGUI_DIALOG_MULTI_ITEM);
+ if (return_status != FILE_CONT)
+ goto ret;
+ }
+
+ return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, TRUE, TRUE, NULL);
+
+ if (return_status != FILE_CONT)
+ goto ret;
+ goto oktoret;
+ }
+ else if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ if (S_ISDIR (dst_stat.st_mode))
+ return_status = file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), d);
+ else
+ return_status = file_error (TRUE, _("Cannot overwrite file \"%s\"\n%s"), d);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_RETRY)
+ goto retry_dst_stat;
+ }
+
+ goto ret_fast;
+ }
+
+ retry_rename:
+ if (mc_rename (src_vpath, dst_vpath) == 0)
+ {
+ return_status = FILE_CONT;
+ goto ret;
+ }
+
+ if (errno != EXDEV)
+ {
+ if (!ctx->skip_all)
+ {
+ return_status = files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s, d);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_RETRY)
+ goto retry_rename;
+ }
+ goto ret;
+ }
+
+ /* Failed because of filesystem boundary -> copy dir instead */
+ if (panel != NULL)
+ {
+ /* In case of single directory, calculate totals. In case of many directories,
+ totals are calculated already. */
+ return_status =
+ panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
+ FILEGUI_DIALOG_MULTI_ITEM);
+ if (return_status != FILE_CONT)
+ goto ret;
+ }
+
+ return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, FALSE, TRUE, NULL);
+
+ if (return_status != FILE_CONT)
+ goto ret;
+
+ oktoret:
+ /* FIXME: there is no need to update progress and check buttons
+ at the finish of single directory operation. */
+ if (panel == NULL)
+ {
+ file_progress_show_source (ctx, NULL);
+ file_progress_show_target (ctx, NULL);
+ file_progress_show (ctx, 0, 0, "", FALSE);
+
+ return_status = check_progress_buttons (ctx);
+ if (return_status != FILE_CONT)
+ goto ret;
+ }
+
+ mc_refresh ();
+
+ erase_dir_after_copy (tctx, ctx, src_vpath, &return_status);
+
+ ret:
+ erase_list = free_erase_list (erase_list);
+ ret_fast:
+ vfs_path_free (src_vpath, TRUE);
+ vfs_path_free (dst_vpath, TRUE);
+ return return_status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* {{{ Panel operate routines */
+
+/**
+ * Return currently selected entry name or the name of the first marked
+ * entry if there is one.
+ */
+
+static const char *
+panel_get_file (const WPanel * panel)
+{
+ if (get_current_type () == view_tree)
+ {
+ WTree *tree;
+ const vfs_path_t *selected_name;
+
+ tree = (WTree *) get_panel_widget (get_current_index ());
+ selected_name = tree_selected_name (tree);
+ return vfs_path_as_str (selected_name);
+ }
+
+ if (panel->marked != 0)
+ {
+ int i;
+
+ for (i = 0; i < panel->dir.len; i++)
+ if (panel->dir.list[i].f.marked != 0)
+ return panel->dir.list[i].fname->str;
+ }
+
+ return panel_current_entry (panel)->fname->str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+check_single_entry (const WPanel * panel, gboolean force_single, struct stat *src_stat)
+{
+ const char *source;
+ gboolean ok;
+
+ if (force_single)
+ source = panel_current_entry (panel)->fname->str;
+ else
+ source = panel_get_file (panel);
+
+ ok = !DIR_IS_DOTDOT (source);
+
+ if (!ok)
+ message (D_ERROR, MSG_ERROR, _("Cannot operate on \"..\"!"));
+ else
+ {
+ vfs_path_t *source_vpath;
+
+ source_vpath = vfs_path_from_str (source);
+
+ /* Update stat to get actual info */
+ ok = mc_lstat (source_vpath, src_stat) == 0;
+ if (!ok)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot stat \"%s\"\n%s"),
+ path_trunc (source, 30), unix_error_string (errno));
+
+ /* Directory was changed outside MC. Reload it forced */
+ if (!panel->is_panelized)
+ {
+ panel_update_flags_t flags = UP_RELOAD;
+
+ /* don't update panelized panel */
+ if (get_other_type () == view_listing && other_panel->is_panelized)
+ flags |= UP_ONLY_CURRENT;
+
+ update_panels (flags, UP_KEEPSEL);
+ }
+ }
+
+ vfs_path_free (source_vpath, TRUE);
+ }
+
+ return ok ? source : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Generate user prompt for panel operation.
+ * src_stat must be not NULL for single source, and NULL for multiple sources
+ */
+
+static char *
+panel_operate_generate_prompt (const WPanel * panel, FileOperation operation,
+ const struct stat *src_stat)
+{
+ char *sp;
+ char *format_string;
+ const char *cp;
+
+ static gboolean i18n_flag = FALSE;
+ if (!i18n_flag)
+ {
+ size_t i;
+
+ for (i = G_N_ELEMENTS (op_names1); i-- != 0;)
+ op_names1[i] = Q_ (op_names1[i]);
+
+#ifdef ENABLE_NLS
+ for (i = G_N_ELEMENTS (prompt_parts); i-- != 0;)
+ prompt_parts[i] = _(prompt_parts[i]);
+
+ one_format = _(one_format);
+ many_format = _(many_format);
+#endif /* ENABLE_NLS */
+ i18n_flag = TRUE;
+ }
+
+ /* Possible prompts:
+ * OP_COPY:
+ * "Copy file \"%s\" with source mask:"
+ * "Copy %d files with source mask:"
+ * "Copy directory \"%s\" with source mask:"
+ * "Copy %d directories with source mask:"
+ * "Copy %d files/directories with source mask:"
+ * OP_MOVE:
+ * "Move file \"%s\" with source mask:"
+ * "Move %d files with source mask:"
+ * "Move directory \"%s\" with source mask:"
+ * "Move %d directories with source mask:"
+ * "Move %d files/directories with source mask:"
+ * OP_DELETE:
+ * "Delete file \"%s\"?"
+ * "Delete %d files?"
+ * "Delete directory \"%s\"?"
+ * "Delete %d directories?"
+ * "Delete %d files/directories?"
+ */
+
+ cp = (src_stat != NULL ? one_format : many_format);
+
+ /* 1. Substitute %o */
+ format_string = str_replace_all (cp, "%o", op_names1[(int) operation]);
+
+ /* 2. Substitute %n */
+ cp = operation == OP_DELETE ? "\n" : " ";
+ sp = format_string;
+ format_string = str_replace_all (sp, "%n", cp);
+ g_free (sp);
+
+ /* 3. Substitute %f */
+ if (src_stat != NULL)
+ cp = S_ISDIR (src_stat->st_mode) ? prompt_parts[2] : prompt_parts[0];
+ else if (panel->marked == panel->dirs_marked)
+ cp = prompt_parts[3];
+ else
+ cp = panel->dirs_marked != 0 ? prompt_parts[4] : prompt_parts[1];
+
+ sp = format_string;
+ format_string = str_replace_all (sp, "%f", cp);
+ g_free (sp);
+
+ /* 4. Substitute %m */
+ cp = operation == OP_DELETE ? "?" : prompt_parts[5];
+ sp = format_string;
+ format_string = str_replace_all (sp, "%m", cp);
+ g_free (sp);
+
+ return format_string;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+do_confirm_copy_move (const WPanel * panel, gboolean force_single, const char *source,
+ struct stat *src_stat, file_op_context_t * ctx, gboolean * do_bg)
+{
+ const char *tmp_dest_dir;
+ char *dest_dir;
+ char *format;
+ char *ret;
+
+ /* Forced single operations default to the original name */
+ if (force_single)
+ tmp_dest_dir = source;
+ else if (get_other_type () == view_listing)
+ tmp_dest_dir = vfs_path_as_str (other_panel->cwd_vpath);
+ else
+ tmp_dest_dir = vfs_path_as_str (panel->cwd_vpath);
+
+ /*
+ * Add trailing backslash only when do non-local ops.
+ * It saves user from occasional file renames (when destination
+ * dir is deleted)
+ */
+ if (!force_single && tmp_dest_dir != NULL && tmp_dest_dir[0] != '\0'
+ && !IS_PATH_SEP (tmp_dest_dir[strlen (tmp_dest_dir) - 1]))
+ {
+ /* add trailing separator */
+ dest_dir = g_strconcat (tmp_dest_dir, PATH_SEP_STR, (char *) NULL);
+ }
+ else
+ {
+ /* just copy */
+ dest_dir = g_strdup (tmp_dest_dir);
+ }
+
+ if (dest_dir == NULL)
+ return NULL;
+
+ if (source == NULL)
+ src_stat = NULL;
+
+ /* Generate confirmation prompt */
+ format = panel_operate_generate_prompt (panel, ctx->operation, src_stat);
+
+ ret = file_mask_dialog (ctx, source != NULL, format,
+ source != NULL ? source : (const void *) &panel->marked, dest_dir,
+ do_bg);
+
+ g_free (format);
+ g_free (dest_dir);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+do_confirm_erase (const WPanel * panel, const char *source, struct stat *src_stat)
+{
+ int i;
+ char *format;
+ char fmd_buf[BUF_MEDIUM];
+
+ if (source == NULL)
+ src_stat = NULL;
+
+ /* Generate confirmation prompt */
+ format = panel_operate_generate_prompt (panel, OP_DELETE, src_stat);
+
+ if (source == NULL)
+ g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
+ else
+ {
+ const int fmd_xlen = 64;
+
+ i = fmd_xlen - str_term_width1 (format) - 4;
+ g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc (source, i));
+ }
+
+ g_free (format);
+
+ if (safe_delete)
+ query_set_sel (1);
+
+ i = query_dialog (op_names[OP_DELETE], fmd_buf, D_ERROR, 2, _("&Yes"), _("&No"));
+
+ return (i == 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+operate_single_file (const WPanel * panel, file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *src, struct stat *src_stat, const char *dest,
+ filegui_dialog_type_t dialog_type)
+{
+ FileProgressStatus value;
+ vfs_path_t *src_vpath;
+ gboolean is_file;
+
+ if (g_path_is_absolute (src))
+ src_vpath = vfs_path_from_str (src);
+ else
+ src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
+
+ is_file = !S_ISDIR (src_stat->st_mode);
+ /* Is link to directory? */
+ if (is_file)
+ {
+ gboolean is_link;
+
+ is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
+ is_file = !(is_link && ctx->follow_links);
+ }
+
+ if (ctx->operation == OP_DELETE)
+ {
+ value = panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file, dialog_type);
+ if (value == FILE_CONT)
+ {
+ if (is_file)
+ value = erase_file (tctx, ctx, src_vpath);
+ else
+ value = erase_dir (tctx, ctx, src_vpath);
+ }
+ }
+ else
+ {
+ char *temp;
+
+ src = vfs_path_as_str (src_vpath);
+
+ temp = build_dest (ctx, src, dest, &value);
+ if (temp != NULL)
+ {
+ dest = temp;
+
+ switch (ctx->operation)
+ {
+ case OP_COPY:
+ /* we use file_mask_op_follow_links only with OP_COPY */
+ ctx->stat_func (src_vpath, src_stat);
+
+ value =
+ panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file,
+ dialog_type);
+ if (value == FILE_CONT)
+ {
+ is_file = !S_ISDIR (src_stat->st_mode);
+ /* Is link to directory? */
+ if (is_file)
+ {
+ gboolean is_link;
+
+ is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
+ is_file = !(is_link && ctx->follow_links);
+ }
+
+ if (is_file)
+ value = copy_file_file (tctx, ctx, src, dest);
+ else
+ value = copy_dir_dir (tctx, ctx, src, dest, TRUE, FALSE, FALSE, NULL);
+ }
+ break;
+
+ case OP_MOVE:
+#ifdef ENABLE_BACKGROUND
+ if (!mc_global.we_are_background)
+#endif
+ /* create UI to show confirmation dialog */
+ file_op_context_create_ui (ctx, TRUE, FILEGUI_DIALOG_ONE_ITEM);
+
+ if (is_file)
+ value = move_file_file (panel, tctx, ctx, src, dest);
+ else
+ value = do_move_dir_dir (panel, tctx, ctx, src, dest);
+ break;
+
+ default:
+ /* Unknown file operation */
+ abort ();
+ }
+
+ g_free (temp);
+ }
+ }
+
+ vfs_path_free (src_vpath, TRUE);
+
+ return value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+operate_one_file (const WPanel * panel, file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *src, struct stat *src_stat, const char *dest)
+{
+ FileProgressStatus value = FILE_CONT;
+ vfs_path_t *src_vpath;
+ gboolean is_file;
+
+ if (g_path_is_absolute (src))
+ src_vpath = vfs_path_from_str (src);
+ else
+ src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
+
+ is_file = !S_ISDIR (src_stat->st_mode);
+
+ if (ctx->operation == OP_DELETE)
+ {
+ if (is_file)
+ value = erase_file (tctx, ctx, src_vpath);
+ else
+ value = erase_dir (tctx, ctx, src_vpath);
+ }
+ else
+ {
+ char *temp;
+
+ src = vfs_path_as_str (src_vpath);
+
+ temp = build_dest (ctx, src, dest, &value);
+ if (temp != NULL)
+ {
+ dest = temp;
+
+ switch (ctx->operation)
+ {
+ case OP_COPY:
+ /* we use file_mask_op_follow_links only with OP_COPY */
+ ctx->stat_func (src_vpath, src_stat);
+ is_file = !S_ISDIR (src_stat->st_mode);
+
+ if (is_file)
+ value = copy_file_file (tctx, ctx, src, dest);
+ else
+ value = copy_dir_dir (tctx, ctx, src, dest, TRUE, FALSE, FALSE, NULL);
+ dest_dirs = free_linklist (dest_dirs);
+ break;
+
+ case OP_MOVE:
+ if (is_file)
+ value = move_file_file (NULL, tctx, ctx, src, dest);
+ else
+ value = do_move_dir_dir (NULL, tctx, ctx, src, dest);
+ break;
+
+ default:
+ /* Unknown file operation */
+ abort ();
+ }
+
+ g_free (temp);
+ }
+ }
+
+ vfs_path_free (src_vpath, TRUE);
+
+ return value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_BACKGROUND
+static int
+end_bg_process (file_op_context_t * ctx, enum OperationMode mode)
+{
+ int pid = ctx->pid;
+
+ (void) mode;
+ ctx->pid = 0;
+
+ unregister_task_with_pid (pid);
+ /* file_op_context_destroy(ctx); */
+ return 1;
+}
+#endif
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Is file symlink to directory or not.
+ *
+ * @param path file or directory
+ * @param st result of mc_lstat(vpath). If NULL, mc_lstat(vpath) is performed here
+ * @param stale_link TRUE if file is stale link to directory
+ *
+ * @return TRUE if file symlink to directory, ELSE otherwise.
+ */
+gboolean
+file_is_symlink_to_dir (const vfs_path_t * vpath, struct stat * st, gboolean * stale_link)
+{
+ struct stat st2;
+ gboolean stale = FALSE;
+ gboolean res = FALSE;
+
+ if (st == NULL)
+ {
+ st = &st2;
+
+ if (mc_lstat (vpath, st) != 0)
+ goto ret;
+ }
+
+ if (S_ISLNK (st->st_mode))
+ {
+ struct stat st3;
+
+ stale = (mc_stat (vpath, &st3) != 0);
+
+ if (!stale)
+ res = (S_ISDIR (st3.st_mode) != 0);
+ }
+
+ ret:
+ if (stale_link != NULL)
+ *stale_link = stale;
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+FileProgressStatus
+copy_file_file (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *src_path, const char *dst_path)
+{
+ uid_t src_uid = (uid_t) (-1);
+ gid_t src_gid = (gid_t) (-1);
+
+ int src_desc, dest_desc = -1;
+ mode_t src_mode = 0; /* The mode of the source file */
+ struct stat src_stat, dst_stat;
+ mc_timesbuf_t times;
+ gboolean dst_exists = FALSE, appending = FALSE;
+ off_t file_size = -1;
+ FileProgressStatus return_status, temp_status;
+ gint64 tv_transfer_start;
+ dest_status_t dst_status = DEST_NONE;
+ int open_flags;
+ vfs_path_t *src_vpath = NULL, *dst_vpath = NULL;
+ char *buf = NULL;
+
+ /* FIXME: We should not be using global variables! */
+ ctx->do_reget = 0;
+ return_status = FILE_RETRY;
+
+ dst_vpath = vfs_path_from_str (dst_path);
+ src_vpath = vfs_path_from_str (src_path);
+
+ file_progress_show_source (ctx, src_vpath);
+ file_progress_show_target (ctx, dst_vpath);
+
+ if (check_progress_buttons (ctx) == FILE_ABORT)
+ {
+ return_status = FILE_ABORT;
+ goto ret_fast;
+ }
+
+ mc_refresh ();
+
+ while (mc_stat (dst_vpath, &dst_stat) == 0)
+ {
+ if (S_ISDIR (dst_stat.st_mode))
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status =
+ file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), dst_path);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_RETRY)
+ continue;
+ }
+ goto ret_fast;
+ }
+
+ dst_exists = TRUE;
+ break;
+ }
+
+ while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot stat source file \"%s\"\n%s"), src_path);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ }
+
+ if (return_status != FILE_RETRY)
+ goto ret_fast;
+ }
+
+ if (dst_exists)
+ {
+ /* Destination already exists */
+ if (check_same_file (src_path, &src_stat, dst_path, &dst_stat, &return_status))
+ goto ret_fast;
+
+ /* Should we replace destination? */
+ if (tctx->ask_overwrite)
+ {
+ ctx->do_reget = 0;
+ return_status = query_replace (ctx, src_path, &src_stat, dst_path, &dst_stat);
+ if (return_status != FILE_CONT)
+ goto ret_fast;
+ }
+ }
+
+ get_times (&src_stat, &times);
+
+ if (!ctx->do_append)
+ {
+ /* Check the hardlinks */
+ if (!ctx->follow_links)
+ {
+ switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->skip_all))
+ {
+ case HARDLINK_OK:
+ /* We have made a hardlink - no more processing is necessary */
+ return_status = FILE_CONT;
+ goto ret_fast;
+
+ case HARDLINK_ABORT:
+ return_status = FILE_ABORT;
+ goto ret_fast;
+
+ default:
+ break;
+ }
+ }
+
+ if (S_ISLNK (src_stat.st_mode))
+ {
+ return_status = make_symlink (ctx, src_vpath, dst_vpath);
+ if (return_status == FILE_CONT && ctx->preserve)
+ mc_utime (dst_vpath, &times);
+ goto ret_fast;
+ }
+
+ if (S_ISCHR (src_stat.st_mode) || S_ISBLK (src_stat.st_mode) || S_ISFIFO (src_stat.st_mode)
+ || S_ISNAM (src_stat.st_mode) || S_ISSOCK (src_stat.st_mode))
+ {
+ dev_t rdev = 0;
+
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ rdev = src_stat.st_rdev;
+#endif
+
+ while (mc_mknod (dst_vpath, src_stat.st_mode & ctx->umask_kill, rdev) < 0
+ && !ctx->skip_all)
+ {
+ return_status =
+ file_error (TRUE, _("Cannot create special file \"%s\"\n%s"), dst_path);
+ if (return_status == FILE_RETRY)
+ continue;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ goto ret_fast;
+ }
+ /* Success */
+
+ while (ctx->preserve_uidgid
+ && mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0 && !ctx->skip_all)
+ {
+ temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
+ if (temp_status == FILE_SKIP)
+ break;
+ if (temp_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (temp_status != FILE_RETRY)
+ {
+ return_status = temp_status;
+ goto ret_fast;
+ }
+ }
+
+ while (ctx->preserve && mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill) != 0
+ && !ctx->skip_all)
+ {
+ temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
+ if (temp_status == FILE_SKIP)
+ break;
+ if (temp_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (temp_status != FILE_RETRY)
+ {
+ return_status = temp_status;
+ goto ret_fast;
+ }
+ }
+
+ return_status = FILE_CONT;
+ mc_utime (dst_vpath, &times);
+ goto ret_fast;
+ }
+ }
+
+ tv_transfer_start = g_get_monotonic_time ();
+
+ while ((src_desc = mc_open (src_vpath, O_RDONLY | O_LINEAR)) < 0 && !ctx->skip_all)
+ {
+ return_status = file_error (TRUE, _("Cannot open source file \"%s\"\n%s"), src_path);
+ if (return_status == FILE_RETRY)
+ continue;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_SKIP)
+ break;
+ ctx->do_append = FALSE;
+ goto ret_fast;
+ }
+
+ if (ctx->do_reget != 0 && mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget)
+ {
+ message (D_ERROR, _("Warning"), _("Reget failed, about to overwrite file"));
+ ctx->do_reget = 0;
+ ctx->do_append = FALSE;
+ }
+
+ while (mc_fstat (src_desc, &src_stat) != 0)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot fstat source file \"%s\"\n%s"), src_path);
+ if (return_status == FILE_RETRY)
+ continue;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ ctx->do_append = FALSE;
+ }
+ goto ret;
+ }
+
+ src_mode = src_stat.st_mode;
+ src_uid = src_stat.st_uid;
+ src_gid = src_stat.st_gid;
+ file_size = src_stat.st_size;
+
+ open_flags = O_WRONLY;
+ if (!dst_exists)
+ open_flags |= O_CREAT | O_EXCL;
+ else if (ctx->do_append)
+ open_flags |= O_APPEND;
+ else
+ open_flags |= O_CREAT | O_TRUNC;
+
+ while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0)
+ {
+ if (errno != EEXIST)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status =
+ file_error (TRUE, _("Cannot create target file \"%s\"\n%s"), dst_path);
+ if (return_status == FILE_RETRY)
+ continue;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ ctx->do_append = FALSE;
+ }
+ }
+ goto ret;
+ }
+
+ /* file opened, but not fully copied */
+ dst_status = DEST_SHORT_QUERY;
+
+ appending = ctx->do_append;
+ ctx->do_append = FALSE;
+
+ /* Try clone the file first. */
+ if (vfs_clone_file (dest_desc, src_desc) == 0)
+ {
+ dst_status = DEST_FULL;
+ return_status = FILE_CONT;
+ goto ret;
+ }
+
+ /* Find out the optimal buffer size. */
+ while (mc_fstat (dest_desc, &dst_stat) != 0)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot fstat target file \"%s\"\n%s"), dst_path);
+ if (return_status == FILE_RETRY)
+ continue;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ }
+ goto ret;
+ }
+
+ /* try preallocate space; if fail, try copy anyway */
+ while (mc_global.vfs.preallocate_space &&
+ vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0)
+ {
+ if (ctx->skip_all)
+ {
+ /* cannot allocate, start the file copying anyway */
+ return_status = FILE_CONT;
+ break;
+ }
+
+ return_status =
+ file_error (TRUE, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path);
+
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+
+ if (ctx->skip_all || return_status == FILE_SKIP)
+ {
+ /* skip the space allocation error, start file copying */
+ return_status = FILE_CONT;
+ break;
+ }
+
+ if (return_status == FILE_ABORT)
+ {
+ mc_close (dest_desc);
+ dest_desc = -1;
+ mc_unlink (dst_vpath);
+ dst_status = DEST_NONE;
+ goto ret;
+ }
+
+ /* return_status == FILE_RETRY -- try allocate space again */
+ }
+
+ ctx->eta_secs = 0.0;
+ ctx->bps = 0;
+
+ if (tctx->bps == 0 || (file_size / tctx->bps) > FILEOP_UPDATE_INTERVAL)
+ file_progress_show (ctx, 0, file_size, "", TRUE);
+ else
+ file_progress_show (ctx, 1, 1, "", TRUE);
+ return_status = check_progress_buttons (ctx);
+ mc_refresh ();
+
+ if (return_status == FILE_CONT)
+ {
+ size_t bufsize;
+ off_t file_part = 0;
+ gint64 tv_current, tv_last_update;
+ gint64 tv_last_input = 0;
+ gint64 usecs, update_usecs;
+ const char *stalled_msg = "";
+ gboolean is_first_time = TRUE;
+
+ tv_last_update = tv_transfer_start;
+
+ bufsize = io_blksize (dst_stat);
+ buf = g_malloc (bufsize);
+
+ while (TRUE)
+ {
+ ssize_t n_read = -1, n_written;
+ gboolean force_update;
+
+ /* src_read */
+ if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0)
+ while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->skip_all)
+ {
+ return_status =
+ file_error (TRUE, _("Cannot read source file \"%s\"\n%s"), src_path);
+ if (return_status == FILE_RETRY)
+ continue;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ goto ret;
+ }
+
+ if (n_read == 0)
+ break;
+
+ tv_current = g_get_monotonic_time ();
+
+ if (n_read > 0)
+ {
+ char *t = buf;
+
+ file_part += n_read;
+
+ tv_last_input = tv_current;
+
+ /* dst_write */
+ while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read)
+ {
+ gboolean write_errno_nospace;
+
+ if (n_written > 0)
+ {
+ n_read -= n_written;
+ t += n_written;
+ continue;
+ }
+
+ write_errno_nospace = (n_written < 0 && errno == ENOSPC);
+
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ return_status =
+ file_error (TRUE, _("Cannot write target file \"%s\"\n%s"), dst_path);
+
+ if (return_status == FILE_SKIP)
+ {
+ if (write_errno_nospace)
+ goto ret;
+ break;
+ }
+ if (return_status == FILE_SKIPALL)
+ {
+ ctx->skip_all = TRUE;
+ if (write_errno_nospace)
+ goto ret;
+ }
+ if (return_status != FILE_RETRY)
+ goto ret;
+ }
+ }
+
+ tctx->copied_bytes = tctx->progress_bytes + file_part + ctx->do_reget;
+
+ usecs = tv_current - tv_last_update;
+ update_usecs = tv_current - tv_last_input;
+
+ if (is_first_time || usecs > FILEOP_UPDATE_INTERVAL_US)
+ {
+ copy_file_file_display_progress (tctx, ctx, tv_current, tv_transfer_start,
+ file_size, file_part);
+ tv_last_update = tv_current;
+ }
+
+ is_first_time = FALSE;
+
+ if (update_usecs > FILEOP_STALLING_INTERVAL_US)
+ stalled_msg = _("(stalled)");
+
+ force_update = (tv_current - tctx->transfer_start) > FILEOP_UPDATE_INTERVAL_US;
+
+ if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
+ {
+ file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
+ file_progress_show_total (tctx, ctx, tctx->copied_bytes, force_update);
+ }
+
+ file_progress_show (ctx, file_part + ctx->do_reget, file_size, stalled_msg,
+ force_update);
+ mc_refresh ();
+
+ return_status = check_progress_buttons (ctx);
+ if (return_status != FILE_CONT)
+ {
+ int query_res;
+
+ query_res =
+ query_dialog (Q_ ("DialogTitle|Copy"),
+ _("Incomplete file was retrieved"), D_ERROR, 3,
+ _("&Delete"), _("&Keep"), _("&Continue copy"));
+
+ switch (query_res)
+ {
+ case 0:
+ /* delete */
+ dst_status = DEST_SHORT_DELETE;
+ goto ret;
+
+ case 1:
+ /* keep */
+ dst_status = DEST_SHORT_KEEP;
+ goto ret;
+
+ default:
+ /* continue copy */
+ break;
+ }
+ }
+ }
+
+ /* copy successful */
+ dst_status = DEST_FULL;
+ }
+
+ ret:
+ g_free (buf);
+
+ rotate_dash (FALSE);
+ while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->skip_all)
+ {
+ temp_status = file_error (TRUE, _("Cannot close source file \"%s\"\n%s"), src_path);
+ if (temp_status == FILE_RETRY)
+ continue;
+ if (temp_status == FILE_ABORT)
+ return_status = temp_status;
+ if (temp_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ break;
+ }
+
+ while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->skip_all)
+ {
+ temp_status = file_error (TRUE, _("Cannot close target file \"%s\"\n%s"), dst_path);
+ if (temp_status == FILE_RETRY)
+ continue;
+ if (temp_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ return_status = temp_status;
+ break;
+ }
+
+ if (dst_status == DEST_SHORT_QUERY)
+ {
+ /* Query to remove short file */
+ if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved"),
+ D_ERROR, 2, _("&Delete"), _("&Keep")) == 0)
+ mc_unlink (dst_vpath);
+ }
+ else if (dst_status == DEST_SHORT_DELETE)
+ mc_unlink (dst_vpath);
+ else if (dst_status == DEST_FULL && !appending)
+ {
+ /* Copy has succeeded */
+
+ while (ctx->preserve_uidgid && mc_chown (dst_vpath, src_uid, src_gid) != 0
+ && !ctx->skip_all)
+ {
+ temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
+ if (temp_status == FILE_RETRY)
+ continue;
+ if (temp_status == FILE_SKIPALL)
+ {
+ ctx->skip_all = TRUE;
+ return_status = FILE_CONT;
+ }
+ if (temp_status == FILE_SKIP)
+ return_status = FILE_CONT;
+ break;
+ }
+
+ while (ctx->preserve && mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0
+ && !ctx->skip_all)
+ {
+ temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
+ if (temp_status == FILE_RETRY)
+ continue;
+ if (temp_status == FILE_SKIPALL)
+ {
+ ctx->skip_all = TRUE;
+ return_status = FILE_CONT;
+ }
+ if (temp_status == FILE_SKIP)
+ return_status = FILE_CONT;
+ break;
+ }
+
+ if (!ctx->preserve && !dst_exists)
+ {
+ src_mode = umask (-1);
+ umask (src_mode);
+ src_mode = 0100666 & ~src_mode;
+ mc_chmod (dst_vpath, (src_mode & ctx->umask_kill));
+ }
+
+ mc_utime (dst_vpath, &times);
+ }
+
+ if (return_status == FILE_CONT)
+ return_status = progress_update_one (tctx, ctx, file_size);
+
+ ret_fast:
+ vfs_path_free (src_vpath, TRUE);
+ vfs_path_free (dst_vpath, TRUE);
+ return return_status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * I think these copy_*_* functions should have a return type.
+ * anyway, this function *must* have two directories as arguments.
+ */
+/* FIXME: This function needs to check the return values of the
+ function calls */
+
+FileProgressStatus
+copy_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s, const char *d,
+ gboolean toplevel, gboolean move_over, gboolean do_delete, GSList * parent_dirs)
+{
+ struct vfs_dirent *next;
+ struct stat dst_stat, src_stat;
+ DIR *reading;
+ FileProgressStatus return_status = FILE_CONT;
+ struct link *lp;
+ vfs_path_t *src_vpath, *dst_vpath;
+ gboolean do_mkdir = TRUE;
+
+ src_vpath = vfs_path_from_str (s);
+ dst_vpath = vfs_path_from_str (d);
+
+ /* First get the mode of the source dir */
+
+ retry_src_stat:
+ if ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot stat source directory \"%s\"\n%s"), s);
+ if (return_status == FILE_RETRY)
+ goto retry_src_stat;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ }
+ goto ret_fast;
+ }
+
+ if (is_in_linklist (dest_dirs, src_vpath, &src_stat) != NULL)
+ {
+ /* Don't copy a directory we created before (we don't want to copy
+ infinitely if a directory is copied into itself) */
+ /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
+ return_status = FILE_CONT;
+ goto ret_fast;
+ }
+
+ /* Hmm, hardlink to directory??? - Norbert */
+ /* FIXME: In this step we should do something in case the destination already exist */
+ /* Check the hardlinks */
+ if (ctx->preserve)
+ {
+ switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->skip_all))
+ {
+ case HARDLINK_OK:
+ /* We have made a hardlink - no more processing is necessary */
+ goto ret_fast;
+
+ case HARDLINK_ABORT:
+ return_status = FILE_ABORT;
+ goto ret_fast;
+
+ default:
+ break;
+ }
+ }
+
+ if (!S_ISDIR (src_stat.st_mode))
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Source \"%s\" is not a directory\n%s"), s);
+ if (return_status == FILE_RETRY)
+ goto retry_src_stat;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ }
+ goto ret_fast;
+ }
+
+ if (is_in_linklist (parent_dirs, src_vpath, &src_stat) != NULL)
+ {
+ /* we found a cyclic symbolic link */
+ message (D_ERROR, MSG_ERROR, _("Cannot copy cyclic symbolic link\n\"%s\""), s);
+ return_status = FILE_SKIP;
+ goto ret_fast;
+ }
+
+ lp = g_new0 (struct link, 1);
+ lp->vfs = vfs_path_get_last_path_vfs (src_vpath);
+ lp->ino = src_stat.st_ino;
+ lp->dev = src_stat.st_dev;
+ parent_dirs = g_slist_prepend (parent_dirs, lp);
+
+ retry_dst_stat:
+ /* Now, check if the dest dir exists, if not, create it. */
+ if (mc_stat (dst_vpath, &dst_stat) != 0)
+ {
+ /* Here the dir doesn't exist : make it ! */
+ if (move_over && mc_rename (src_vpath, dst_vpath) == 0)
+ {
+ return_status = FILE_CONT;
+ goto ret;
+ }
+ }
+ else
+ {
+ /*
+ * If the destination directory exists, we want to copy the whole
+ * directory, but we only want this to happen once.
+ *
+ * Escape sequences added to the * to compiler warnings.
+ * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
+ * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
+ */
+ if (!S_ISDIR (dst_stat.st_mode))
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status =
+ file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), d);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_RETRY)
+ goto retry_dst_stat;
+ }
+ goto ret;
+ }
+ /* Dive into subdir if exists */
+ if (toplevel && ctx->dive_into_subdirs)
+ {
+ vfs_path_t *tmp;
+
+ tmp = dst_vpath;
+ dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
+ vfs_path_free (tmp, TRUE);
+
+ }
+ else
+ do_mkdir = FALSE;
+ }
+
+ d = vfs_path_as_str (dst_vpath);
+
+ if (do_mkdir)
+ {
+ while (my_mkdir (dst_vpath, (src_stat.st_mode & ctx->umask_kill) | S_IRWXU) != 0)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status =
+ file_error (TRUE, _("Cannot create target directory \"%s\"\n%s"), d);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ }
+ if (return_status != FILE_RETRY)
+ goto ret;
+ }
+
+ lp = g_new0 (struct link, 1);
+ mc_stat (dst_vpath, &dst_stat);
+ lp->vfs = vfs_path_get_last_path_vfs (dst_vpath);
+ lp->ino = dst_stat.st_ino;
+ lp->dev = dst_stat.st_dev;
+ dest_dirs = g_slist_prepend (dest_dirs, lp);
+ }
+
+ if (ctx->preserve_uidgid)
+ {
+ while (mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot chown target directory \"%s\"\n%s"), d);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ }
+ if (return_status != FILE_RETRY)
+ goto ret;
+ }
+ }
+
+ /* open the source dir for reading */
+ reading = mc_opendir (src_vpath);
+ if (reading == NULL)
+ goto ret;
+
+ while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
+ {
+ char *path;
+ vfs_path_t *tmp_vpath;
+
+ /*
+ * Now, we don't want '.' and '..' to be created / copied at any time
+ */
+ if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
+ continue;
+
+ /* get the filename and add it to the src directory */
+ path = mc_build_filename (s, next->d_name, (char *) NULL);
+ tmp_vpath = vfs_path_from_str (path);
+
+ (*ctx->stat_func) (tmp_vpath, &dst_stat);
+ if (S_ISDIR (dst_stat.st_mode))
+ {
+ char *mdpath;
+
+ mdpath = mc_build_filename (d, next->d_name, (char *) NULL);
+ /*
+ * From here, we just intend to recursively copy subdirs, not
+ * the double functionality of copying different when the target
+ * dir already exists. So, we give the recursive call the flag 0
+ * meaning no toplevel.
+ */
+ return_status =
+ copy_dir_dir (tctx, ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
+ g_free (mdpath);
+ }
+ else
+ {
+ char *dest_file;
+
+ dest_file = mc_build_filename (d, x_basename (path), (char *) NULL);
+ return_status = copy_file_file (tctx, ctx, path, dest_file);
+ g_free (dest_file);
+ }
+
+ g_free (path);
+
+ if (do_delete && return_status == FILE_CONT)
+ {
+ if (ctx->erase_at_end)
+ {
+ if (erase_list == NULL)
+ erase_list = g_queue_new ();
+
+ lp = g_new0 (struct link, 1);
+ lp->src_vpath = tmp_vpath;
+ lp->st_mode = dst_stat.st_mode;
+ g_queue_push_tail (erase_list, lp);
+ tmp_vpath = NULL;
+ }
+ else if (S_ISDIR (dst_stat.st_mode))
+ return_status = erase_dir_iff_empty (ctx, tmp_vpath, tctx->progress_count);
+ else
+ return_status = erase_file (tctx, ctx, tmp_vpath);
+ }
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ mc_closedir (reading);
+
+ if (ctx->preserve)
+ {
+ mc_timesbuf_t times;
+
+ mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
+ get_times (&src_stat, &times);
+ mc_utime (dst_vpath, &times);
+ }
+ else
+ {
+ src_stat.st_mode = umask (-1);
+ umask (src_stat.st_mode);
+ src_stat.st_mode = 0100777 & ~src_stat.st_mode;
+ mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
+ }
+
+ ret:
+ free_link (parent_dirs->data);
+ g_slist_free_1 (parent_dirs);
+ ret_fast:
+ vfs_path_free (src_vpath, TRUE);
+ vfs_path_free (dst_vpath, TRUE);
+ return return_status;
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ Move routines */
+
+FileProgressStatus
+move_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s, const char *d)
+{
+ return do_move_dir_dir (NULL, tctx, ctx, s, d);
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ Erase routines */
+
+FileProgressStatus
+erase_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath)
+{
+ file_progress_show_deleting (ctx, vfs_path_as_str (vpath), NULL);
+ file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
+ if (check_progress_buttons (ctx) == FILE_ABORT)
+ return FILE_ABORT;
+
+ mc_refresh ();
+
+ /* The old way to detect a non empty directory was:
+ error = my_rmdir (s);
+ if (error && (errno == ENOTEMPTY || errno == EEXIST))){
+ For the linux user space nfs server (nfs-server-2.2beta29-2)
+ we would have to check also for EIO. I hope the new way is
+ fool proof. (Norbert)
+ */
+ if (check_dir_is_empty (vpath) == 0)
+ { /* not empty */
+ FileProgressStatus error;
+
+ error = query_recursive (ctx, vfs_path_as_str (vpath));
+ if (error == FILE_CONT)
+ error = recursive_erase (tctx, ctx, vpath);
+ return error;
+ }
+
+ return try_erase_dir (ctx, vfs_path_as_str (vpath));
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ Panel operate routines */
+
+void
+dirsize_status_init_cb (status_msg_t * sm)
+{
+ dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
+ WGroup *gd = GROUP (sm->dlg);
+ Widget *wd = WIDGET (sm->dlg);
+ WRect r = wd->rect;
+
+ const char *b1_name = N_("&Abort");
+ const char *b2_name = N_("&Skip");
+ int b_width, ui_width;
+
+#ifdef ENABLE_NLS
+ b1_name = _(b1_name);
+ b2_name = _(b2_name);
+#endif
+
+ b_width = str_term_width1 (b1_name) + 4;
+ if (dsm->allow_skip)
+ b_width += str_term_width1 (b2_name) + 4 + 1;
+
+ ui_width = MAX (COLS / 2, b_width + 6);
+ dsm->dirname = label_new (2, 3, NULL);
+ group_add_widget (gd, dsm->dirname);
+ dsm->count_size = label_new (3, 3, NULL);
+ group_add_widget (gd, dsm->count_size);
+ group_add_widget (gd, hline_new (4, -1, -1));
+
+ dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL));
+ group_add_widget (gd, dsm->abort_button);
+ if (dsm->allow_skip)
+ {
+ dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL));
+ group_add_widget (gd, dsm->skip_button);
+ widget_select (dsm->skip_button);
+ }
+
+ r.lines = 8;
+ r.cols = ui_width;
+ widget_set_size_rect (wd, &r);
+ dirsize_status_locate_buttons (dsm);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+dirsize_status_update_cb (status_msg_t * sm)
+{
+ dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
+ Widget *wd = WIDGET (sm->dlg);
+ WRect r = wd->rect;
+
+ /* update second (longer label) */
+ label_set_textv (dsm->count_size, _("Directories: %zu, total size: %s"),
+ dsm->dir_count, size_trunc_sep (dsm->total_size, panels_options.kilobyte_si));
+
+ /* enlarge dialog if required */
+ if (WIDGET (dsm->count_size)->rect.cols + 6 > r.cols)
+ {
+ r.cols = WIDGET (dsm->count_size)->rect.cols + 6;
+ widget_set_size_rect (wd, &r);
+ dirsize_status_locate_buttons (dsm);
+ widget_draw (wd);
+ /* TODO: ret rid of double redraw */
+ }
+
+ /* adjust first label */
+ label_set_text (dsm->dirname,
+ str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->rect.cols - 6));
+
+ switch (status_msg_common_update (sm))
+ {
+ case B_CANCEL:
+ case FILE_ABORT:
+ return FILE_ABORT;
+ case FILE_SKIP:
+ return FILE_SKIP;
+ default:
+ return FILE_CONT;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dirsize_status_deinit_cb (status_msg_t * sm)
+{
+ (void) sm;
+
+ /* schedule to update passive panel */
+ if (get_other_type () == view_listing)
+ other_panel->dirty = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * compute_dir_size:
+ *
+ * Computes the number of bytes used by the files in a directory
+ */
+
+FileProgressStatus
+compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * sm,
+ size_t * ret_dir_count, size_t * ret_marked_count, uintmax_t * ret_total,
+ gboolean follow_symlinks)
+{
+ return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total,
+ follow_symlinks ? mc_stat : mc_lstat);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * panel_operate:
+ *
+ * Performs one of the operations on the current on the source_panel
+ * (copy, delete, move).
+ *
+ * Returns TRUE if did change the directory
+ * structure, Returns FALSE if user aborted
+ *
+ * force_single forces operation on the current entry and affects
+ * default destination. Current filename is used as default.
+ */
+
+gboolean
+panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
+{
+ WPanel *panel = PANEL (source_panel);
+ const gboolean single_entry = force_single || (panel->marked <= 1)
+ || (get_current_type () == view_tree);
+
+ const char *source = NULL;
+ char *dest = NULL;
+ vfs_path_t *dest_vpath = NULL;
+ vfs_path_t *save_cwd = NULL, *save_dest = NULL;
+ struct stat src_stat;
+ gboolean ret_val = TRUE;
+ int i;
+ FileProgressStatus value;
+ file_op_context_t *ctx;
+ file_op_total_context_t *tctx;
+ filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM;
+
+ gboolean do_bg = FALSE; /* do background operation? */
+
+ static gboolean i18n_flag = FALSE;
+ if (!i18n_flag)
+ {
+ for (i = G_N_ELEMENTS (op_names); i-- != 0;)
+ op_names[i] = Q_ (op_names[i]);
+ i18n_flag = TRUE;
+ }
+
+ linklist = free_linklist (linklist);
+ dest_dirs = free_linklist (dest_dirs);
+
+ save_cwds_stat ();
+
+ if (single_entry)
+ {
+ source = check_single_entry (panel, force_single, &src_stat);
+
+ if (source == NULL)
+ return FALSE;
+ }
+
+ ctx = file_op_context_new (operation);
+
+ /* Show confirmation dialog */
+ if (operation != OP_DELETE)
+ {
+ dest = do_confirm_copy_move (panel, force_single, source, &src_stat, ctx, &do_bg);
+ if (dest == NULL)
+ {
+ ret_val = FALSE;
+ goto ret_fast;
+ }
+
+ dest_vpath = vfs_path_from_str (dest);
+ }
+ else if (confirm_delete && !do_confirm_erase (panel, source, &src_stat))
+ {
+ ret_val = FALSE;
+ goto ret_fast;
+ }
+
+ tctx = file_op_total_context_new ();
+ tctx->transfer_start = g_get_monotonic_time ();
+
+#ifdef ENABLE_BACKGROUND
+ /* Did the user select to do a background operation? */
+ if (do_bg)
+ {
+ int v;
+
+ v = do_background (ctx,
+ g_strconcat (op_names[operation], ": ",
+ vfs_path_as_str (panel->cwd_vpath), (char *) NULL));
+ if (v == -1)
+ message (D_ERROR, MSG_ERROR, _("Sorry, I could not put the job in background"));
+
+ /* If we are the parent */
+ if (v == 1)
+ {
+ mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL);
+
+ mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL);
+ vfs_path_free (dest_vpath, TRUE);
+ g_free (dest);
+ /* file_op_context_destroy (ctx); */
+ return FALSE;
+ }
+ }
+ else
+#endif /* ENABLE_BACKGROUND */
+ {
+ if (operation == OP_DELETE)
+ dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
+ else if (single_entry && S_ISDIR (panel_current_entry (panel)->st.st_mode))
+ dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
+ else if (single_entry || force_single)
+ dialog_type = FILEGUI_DIALOG_ONE_ITEM;
+ else
+ dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
+ }
+
+ /* Initialize things */
+ /* We do not want to trash cache every time file is
+ created/touched. However, this will make our cache contain
+ invalid data. */
+ if ((dest != NULL)
+ && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
+ save_dest = vfs_path_from_str (dest);
+
+ if ((vfs_path_tokens_count (panel->cwd_vpath) != 0)
+ && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
+ save_cwd = vfs_path_clone (panel->cwd_vpath);
+
+ /* Now, let's do the job */
+
+ /* This code is only called by the tree and panel code */
+ if (single_entry)
+ {
+ /* We now have ETA in all cases */
+
+ /* One file: FIXME mc_chdir will take user out of any vfs */
+ if ((operation != OP_COPY) && (get_current_type () == view_tree))
+ {
+ vfs_path_t *vpath;
+ int chdir_retcode;
+
+ vpath = vfs_path_from_str (PATH_SEP_STR);
+ chdir_retcode = mc_chdir (vpath);
+ vfs_path_free (vpath, TRUE);
+ if (chdir_retcode < 0)
+ {
+ ret_val = FALSE;
+ goto clean_up;
+ }
+ }
+
+ value = operate_single_file (panel, tctx, ctx, source, &src_stat, dest, dialog_type);
+ if ((value == FILE_CONT) && !force_single)
+ unmark_files (panel);
+ }
+ else
+ {
+ /* Many files */
+
+ /* Check destination for copy or move operation */
+ while (operation != OP_DELETE)
+ {
+ int dst_result;
+ struct stat dst_stat;
+
+ dst_result = mc_stat (dest_vpath, &dst_stat);
+
+ if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
+ break;
+
+ if (ctx->skip_all
+ || file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"),
+ dest) != FILE_RETRY)
+ goto clean_up;
+ }
+
+ /* TODO: the good way is required to skip directories scanning in case of rename/move
+ * of several directories. Since reqular expression can be used for destination,
+ * some directory movements can be a cross-filesystem and directory scanning is useful
+ * for those directories only. */
+
+ if (panel_operate_init_totals (panel, NULL, NULL, ctx, file_op_compute_totals, dialog_type)
+ == FILE_CONT)
+ {
+ /* Loop for every file, perform the actual copy operation */
+ for (i = 0; i < panel->dir.len; i++)
+ {
+ const char *source2;
+
+ if (panel->dir.list[i].f.marked == 0)
+ continue; /* Skip the unmarked ones */
+
+ source2 = panel->dir.list[i].fname->str;
+ src_stat = panel->dir.list[i].st;
+
+ value = operate_one_file (panel, tctx, ctx, source2, &src_stat, dest);
+
+ if (value == FILE_ABORT)
+ break;
+
+ if (value == FILE_CONT)
+ do_file_mark (panel, i, 0);
+
+ if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
+ {
+ file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
+ file_progress_show_total (tctx, ctx, tctx->progress_bytes, FALSE);
+ }
+
+ if (operation != OP_DELETE)
+ file_progress_show (ctx, 0, 0, "", FALSE);
+
+ if (check_progress_buttons (ctx) == FILE_ABORT)
+ break;
+
+ mc_refresh ();
+ } /* Loop for every file */
+ }
+ } /* Many entries */
+
+ clean_up:
+ /* Clean up */
+ if (save_cwd != NULL)
+ {
+ mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
+ vfs_path_free (save_cwd, TRUE);
+ }
+
+ if (save_dest != NULL)
+ {
+ mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
+ vfs_path_free (save_dest, TRUE);
+ }
+
+ linklist = free_linklist (linklist);
+ dest_dirs = free_linklist (dest_dirs);
+ g_free (dest);
+ vfs_path_free (dest_vpath, TRUE);
+ MC_PTR_FREE (ctx->dest_mask);
+
+#ifdef ENABLE_BACKGROUND
+ /* Let our parent know we are saying bye bye */
+ if (mc_global.we_are_background)
+ {
+ int cur_pid = getpid ();
+ /* Send pid to parent with child context, it is fork and
+ don't modify real parent ctx */
+ ctx->pid = cur_pid;
+ parent_call ((void *) end_bg_process, ctx, 0);
+
+ vfs_shut ();
+ my_exit (EXIT_SUCCESS);
+ }
+#endif /* ENABLE_BACKGROUND */
+
+ file_op_total_context_destroy (tctx);
+ ret_fast:
+ file_op_context_destroy (ctx);
+
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ repaint_screen ();
+
+ return ret_val;
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ Query/status report routines */
+/** Report error with one file */
+FileProgressStatus
+file_error (gboolean allow_retry, const char *format, const char *file)
+{
+ char buf[BUF_MEDIUM];
+
+ g_snprintf (buf, sizeof (buf), format, path_trunc (file, 30), unix_error_string (errno));
+
+ return do_file_error (allow_retry, buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ Cause emacs to enter folding mode for this file:
+ Local variables:
+ end:
+ */
diff --git a/src/filemanager/file.h b/src/filemanager/file.h
new file mode 100644
index 0000000..ae12e15
--- /dev/null
+++ b/src/filemanager/file.h
@@ -0,0 +1,72 @@
+/** \file file.h
+ * \brief Header: File and directory operation routines
+ */
+
+#ifndef MC__FILE_H
+#define MC__FILE_H
+
+#include <inttypes.h> /* off_t, uintmax_t */
+
+#include "lib/global.h"
+#include "lib/widget.h"
+
+#include "fileopctx.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+typedef struct dirsize_status_msg_t dirsize_status_msg_t;
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* status dialog of directory size computing */
+struct dirsize_status_msg_t
+{
+ status_msg_t status_msg; /* base class */
+
+ gboolean allow_skip;
+ WLabel *dirname;
+ WLabel *count_size;
+ Widget *abort_button;
+ Widget *skip_button;
+ const vfs_path_t *dirname_vpath;
+ size_t dir_count;
+ uintmax_t total_size;
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+gboolean file_is_symlink_to_dir (const vfs_path_t * path, struct stat *st, gboolean * stale_link);
+
+FileProgressStatus copy_file_file (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *src_path, const char *dst_path);
+FileProgressStatus move_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *s, const char *d);
+FileProgressStatus copy_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *s, const char *d,
+ gboolean toplevel, gboolean move_over, gboolean do_delete,
+ GSList * parent_dirs);
+FileProgressStatus erase_dir (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const vfs_path_t * vpath);
+
+gboolean panel_operate (void *source_panel, FileOperation op, gboolean force_single);
+
+/* Error reporting routines */
+
+/* Report error with one file */
+FileProgressStatus file_error (gboolean allow_retry, const char *format, const char *file);
+
+/* return value is FILE_CONT or FILE_ABORT */
+FileProgressStatus compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * sm,
+ size_t * ret_dir_count, size_t * ret_marked_count,
+ uintmax_t * ret_total, gboolean follow_symlinks);
+
+void dirsize_status_init_cb (status_msg_t * sm);
+int dirsize_status_update_cb (status_msg_t * sm);
+void dirsize_status_deinit_cb (status_msg_t * sm);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__FILE_H */
diff --git a/src/filemanager/filegui.c b/src/filemanager/filegui.c
new file mode 100644
index 0000000..abca598
--- /dev/null
+++ b/src/filemanager/filegui.c
@@ -0,0 +1,1498 @@
+/*
+ File management GUI for the text mode edition
+
+ The copy code was based in GNU's cp, and was written by:
+ Torbjorn Granlund, David MacKenzie, and Jim Meyering.
+
+ The move code was based in GNU's mv, and was written by:
+ Mike Parker and David MacKenzie.
+
+ Janne Kukonlehto added much error recovery to them for being used
+ in an interactive program.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Janne Kukonlehto, 1994, 1995
+ Fred Leeflang, 1994, 1995
+ Miguel de Icaza, 1994, 1995, 1996
+ Jakub Jelinek, 1995, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Slava Zanko, 2009, 2010, 2011, 2012, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2023
+
+ 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/>.
+ */
+
+/*
+ * Please note that all dialogs used here must be safe for background
+ * operations.
+ */
+
+/** \file filegui.c
+ * \brief Source: file management GUI for the text mode edition
+ */
+
+/* {{{ Include files */
+
+#include <config.h>
+
+#if ((defined STAT_STATVFS || defined STAT_STATVFS64) \
+ && (defined HAVE_STRUCT_STATVFS_F_BASETYPE || defined HAVE_STRUCT_STATVFS_F_FSTYPENAME \
+ || (! defined HAVE_STRUCT_STATFS_F_FSTYPENAME)))
+#define USE_STATVFS 1
+#else
+#define USE_STATVFS 0
+#endif
+
+#include <errno.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if USE_STATVFS
+#include <sys/statvfs.h>
+#elif defined HAVE_SYS_VFS_H
+#include <sys/vfs.h>
+#elif defined HAVE_SYS_MOUNT_H && defined HAVE_SYS_PARAM_H
+/* NOTE: freebsd5.0 needs sys/param.h and sys/mount.h for statfs.
+ It does have statvfs.h, but shouldn't use it, since it doesn't
+ HAVE_STRUCT_STATVFS_F_BASETYPE. So find a clean way to fix it. */
+/* NetBSD 1.5.2 needs these, for the declaration of struct statfs. */
+#include <sys/param.h>
+#include <sys/mount.h>
+#elif defined HAVE_OS_H /* Haiku, also (obsolete) BeOS */
+#include <fs_info.h>
+#endif
+
+#if USE_STATVFS
+#if ! defined STAT_STATVFS && defined STAT_STATVFS64
+#define STRUCT_STATVFS struct statvfs64
+#define STATFS statvfs64
+#else
+#define STRUCT_STATVFS struct statvfs
+#define STATFS statvfs
+
+#if defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__)
+#include <sys/utsname.h>
+#include <sys/statfs.h>
+#define STAT_STATFS2_BSIZE 1
+#endif
+#endif
+
+#else
+#define STATFS statfs
+#define STRUCT_STATVFS struct statfs
+#ifdef HAVE_OS_H /* Haiku, also (obsolete) BeOS */
+/* BeOS has a statvfs function, but it does not return sensible values
+ for f_files, f_ffree and f_favail, and lacks f_type, f_basetype and
+ f_fstypename. Use 'struct fs_info' instead. */
+static int
+statfs (char const *filename, struct fs_info *buf)
+{
+ dev_t device;
+
+ device = dev_for_path (filename);
+
+ if (device < 0)
+ {
+ errno = (device == B_ENTRY_NOT_FOUND ? ENOENT
+ : device == B_BAD_VALUE ? EINVAL
+ : device == B_NAME_TOO_LONG ? ENAMETOOLONG
+ : device == B_NO_MEMORY ? ENOMEM : device == B_FILE_ERROR ? EIO : 0);
+ return -1;
+ }
+ /* If successful, buf->dev will be == device. */
+ return fs_stat_dev (device, buf);
+}
+
+#define STRUCT_STATVFS struct fs_info
+#else
+#define STRUCT_STATVFS struct statfs
+#endif
+#endif
+
+#ifdef HAVE_STRUCT_STATVFS_F_BASETYPE
+#define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype
+#else
+#if defined HAVE_STRUCT_STATVFS_F_FSTYPENAME || defined HAVE_STRUCT_STATFS_F_FSTYPENAME
+#define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_fstypename
+#elif defined HAVE_OS_H /* Haiku, also (obsolete) BeOS */
+#define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME fsh_name
+#endif
+#endif
+
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/key.h" /* tty_get_event */
+#include "lib/mcconfig.h"
+#include "lib/search.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strescape.h"
+#include "lib/strutil.h"
+#include "lib/timefmt.h" /* file_date() */
+#include "lib/util.h"
+#include "lib/widget.h"
+
+#include "src/setup.h" /* verbose, safe_overwrite */
+
+#include "filemanager.h"
+#include "fileopctx.h" /* FILE_CONT */
+
+#include "filegui.h"
+
+/* }}} */
+
+/*** global variables ****************************************************************************/
+
+gboolean classic_progressbar = TRUE;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define truncFileString(dlg, s) str_trunc (s, WIDGET (dlg)->rect.cols - 10)
+#define truncFileStringSecure(dlg, s) path_trunc (s, WIDGET (dlg)->rect.cols - 10)
+
+/*** file scope type declarations ****************************************************************/
+
+/* *INDENT-OFF* */
+typedef enum {
+ MSDOS_SUPER_MAGIC = 0x4d44,
+ NTFS_SB_MAGIC = 0x5346544e,
+ FUSE_MAGIC = 0x65735546,
+ PROC_SUPER_MAGIC = 0x9fa0,
+ SMB_SUPER_MAGIC = 0x517B,
+ NCP_SUPER_MAGIC = 0x564c,
+ USBDEVICE_SUPER_MAGIC = 0x9fa2
+} filegui_nonattrs_fs_t;
+/* *INDENT-ON* */
+
+/* Used for button result values */
+typedef enum
+{
+ REPLACE_YES = B_USER,
+ REPLACE_NO,
+ REPLACE_APPEND,
+ REPLACE_REGET,
+ REPLACE_ALL,
+ REPLACE_OLDER,
+ REPLACE_NONE,
+ REPLACE_SMALLER,
+ REPLACE_SIZE,
+ REPLACE_ABORT
+} replace_action_t;
+
+/* This structure describes the UI and internal data required by a file
+ * operation context.
+ */
+typedef struct
+{
+ /* ETA and bps */
+ gboolean showing_eta;
+ gboolean showing_bps;
+
+ /* Dialog and widgets for the operation progress window */
+ WDialog *op_dlg;
+ /* Source file: label and name */
+ WLabel *src_file_label;
+ WLabel *src_file;
+ /* Target file: label and name */
+ WLabel *tgt_file_label;
+ WLabel *tgt_file;
+
+ WGauge *progress_file_gauge;
+ WLabel *progress_file_label;
+
+ WGauge *progress_total_gauge;
+
+ WLabel *total_files_processed_label;
+ WLabel *time_label;
+ WHLine *total_bytes_label;
+
+ /* Query replace dialog */
+ WDialog *replace_dlg;
+ const char *src_filename;
+ const char *tgt_filename;
+ replace_action_t replace_result;
+ gboolean dont_overwrite_with_zero;
+
+ struct stat *src_stat, *dst_stat;
+} file_op_context_ui_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct
+{
+ Widget *w;
+ FileProgressStatus action;
+ const char *text;
+ button_flags_t flags;
+ int len;
+} progress_buttons[] =
+{
+ /* *INDENT-OFF* */
+ { NULL, FILE_SKIP, N_("&Skip"), NORMAL_BUTTON, -1 },
+ { NULL, FILE_SUSPEND, N_("S&uspend"), NORMAL_BUTTON, -1 },
+ { NULL, FILE_SUSPEND, N_("Con&tinue"), NORMAL_BUTTON, -1 },
+ { NULL, FILE_ABORT, N_("&Abort"), NORMAL_BUTTON, -1 }
+ /* *INDENT-ON* */
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Return true if statvfs works. This is false for statvfs on systems
+ with GNU libc on Linux kernels before 2.6.36, which stats all
+ preceding entries in /proc/mounts; that makes df hang if even one
+ of the corresponding file systems is hard-mounted but not available. */
+
+#if USE_STATVFS && ! (! defined STAT_STATVFS && defined STAT_STATVFS64)
+static int
+statvfs_works (void)
+{
+#if ! (defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
+ return 1;
+#else
+ static int statvfs_works_cache = -1;
+ struct utsname name;
+
+ if (statvfs_works_cache < 0)
+ statvfs_works_cache = (uname (&name) == 0 && 0 <= str_verscmp (name.release, "2.6.36"));
+ return statvfs_works_cache;
+#endif
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+filegui__check_attrs_on_fs (const char *fs_path)
+{
+ STRUCT_STATVFS stfs;
+
+#if USE_STATVFS && defined(STAT_STATVFS)
+ if (statvfs_works () && statvfs (fs_path, &stfs) != 0)
+ return TRUE;
+#else
+ if (STATFS (fs_path, &stfs) != 0)
+ return TRUE;
+#endif
+
+#if (USE_STATVFS && defined(HAVE_STRUCT_STATVFS_F_TYPE)) || \
+ (!USE_STATVFS && defined(HAVE_STRUCT_STATFS_F_TYPE))
+ switch ((filegui_nonattrs_fs_t) stfs.f_type)
+ {
+ case MSDOS_SUPER_MAGIC:
+ case NTFS_SB_MAGIC:
+ case PROC_SUPER_MAGIC:
+ case SMB_SUPER_MAGIC:
+ case NCP_SUPER_MAGIC:
+ case USBDEVICE_SUPER_MAGIC:
+ return FALSE;
+ default:
+ break;
+ }
+#elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
+ if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdos") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdosfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "procfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
+ || strstr (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fusefs") != NULL)
+ return FALSE;
+#elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
+ if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "pcfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "proc") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fuse") == 0)
+ return FALSE;
+#endif
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+file_frmt_time (char *buffer, double eta_secs)
+{
+ int eta_hours, eta_mins, eta_s;
+
+ eta_hours = (int) (eta_secs / (60 * 60));
+ eta_mins = (int) ((eta_secs - (eta_hours * 60 * 60)) / 60);
+ eta_s = (int) (eta_secs - (eta_hours * 60 * 60 + eta_mins * 60));
+ g_snprintf (buffer, BUF_TINY, _("%d:%02d:%02d"), eta_hours, eta_mins, eta_s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show)
+{
+ char _fmt_buff[BUF_TINY];
+
+ if (eta_secs <= 0.5 && !always_show)
+ {
+ *buffer = '\0';
+ return;
+ }
+
+ if (eta_secs <= 0.5)
+ eta_secs = 1;
+ file_frmt_time (_fmt_buff, eta_secs);
+ g_snprintf (buffer, BUF_TINY, _("ETA %s"), _fmt_buff);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+file_bps_prepare_for_show (char *buffer, long bps)
+{
+ if (bps > 1024 * 1024)
+ g_snprintf (buffer, BUF_TINY, _("%.2f MB/s"), bps / (1024 * 1024.0));
+ else if (bps > 1024)
+ g_snprintf (buffer, BUF_TINY, _("%.2f KB/s"), bps / 1024.0);
+ else if (bps > 1)
+ g_snprintf (buffer, BUF_TINY, _("%ld B/s"), bps);
+ else
+ *buffer = '\0';
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+file_ui_op_dlg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_ACTION:
+ /* Do not close the dialog because the query dialog will be shown */
+ if (parm == CK_Cancel)
+ {
+ DIALOG (w)->ret_value = FILE_ABORT; /* for check_progress_buttons() */
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* The dialog layout:
+ *
+ * +---------------------- File exists -----------------------+
+ * | New : /path/to/original_file_name | // 0, 1
+ * | 1234567 feb 4 2017 13:38 | // 2, 3
+ * | Existing: /path/to/target_file_name | // 4, 5
+ * | 1234567890 feb 4 2017 13:37 | // 6, 7
+ * +----------------------------------------------------------+
+ * | Overwrite this file? | // 8
+ * | [ Yes ] [ No ] [ Append ] [ Reget ] | // 9, 10, 11, 12
+ * +----------------------------------------------------------+
+ * | Overwrite all files? | // 13
+ * | [ ] Don't overwrite with zero length file | // 14
+ * | [ All ] [ Older ] [None] [ Smaller ] [ Size differs ] | // 15, 16, 17, 18, 19
+ * +----------------------------------------------------------|
+ * | [ Abort ] | // 20
+ * +----------------------------------------------------------+
+ */
+
+static replace_action_t
+overwrite_query_dialog (file_op_context_t * ctx, enum OperationMode mode)
+{
+#define W(i) dlg_widgets[i].widget
+#define WX(i) W(i)->rect.x
+#define WY(i) W(i)->rect.y
+#define WCOLS(i) W(i)->rect.cols
+
+#define NEW_LABEL(i, text) \
+ W(i) = WIDGET (label_new (dlg_widgets[i].y, dlg_widgets[i].x, text))
+
+#define ADD_LABEL(i) \
+ group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, \
+ g->current != NULL ? g->current->data : NULL)
+
+#define NEW_BUTTON(i) \
+ W(i) = WIDGET (button_new (dlg_widgets[i].y, dlg_widgets[i].x, \
+ dlg_widgets[i].value, NORMAL_BUTTON, dlg_widgets[i].text, NULL))
+
+#define ADD_BUTTON(i) \
+ group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, g->current->data)
+
+ /* dialog sizes */
+ const int dlg_height = 17;
+ int dlg_width = 60;
+
+ struct
+ {
+ Widget *widget;
+ const char *text;
+ int y;
+ int x;
+ widget_pos_flags_t pos_flags;
+ int value; /* 0 for labels and checkbox */
+ } dlg_widgets[] =
+ {
+ /* *INDENT-OFF* */
+ /* 0 - label */
+ { NULL, N_("New :"), 2, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 1 - label - name */
+ { NULL, NULL, 2, 14, WPOS_KEEP_DEFAULT, 0 },
+ /* 2 - label - size */
+ { NULL, NULL, 3, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 3 - label - date & time */
+ { NULL, NULL, 3, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
+ /* 4 - label */
+ { NULL, N_("Existing:"), 4, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 5 - label - name */
+ { NULL, NULL, 4, 14, WPOS_KEEP_DEFAULT, 0 },
+ /* 6 - label - size */
+ { NULL, NULL, 5, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 7 - label - date & time */
+ { NULL, NULL, 5, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
+ /* --------------------------------------------------- */
+ /* 8 - label */
+ { NULL, N_("Overwrite this file?"), 7, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
+ /* 9 - button */
+ { NULL, N_("&Yes"), 8, 14, WPOS_KEEP_DEFAULT, REPLACE_YES },
+ /* 10 - button */
+ { NULL, N_("&No"), 8, 22, WPOS_KEEP_DEFAULT, REPLACE_NO },
+ /* 11 - button */
+ { NULL, N_("A&ppend"), 8, 29, WPOS_KEEP_DEFAULT, REPLACE_APPEND },
+ /* 12 - button */
+ { NULL, N_("&Reget"), 8, 40, WPOS_KEEP_DEFAULT, REPLACE_REGET },
+ /* --------------------------------------------------- */
+ /* 13 - label */
+ { NULL, N_("Overwrite all files?"), 10, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
+ /* 14 - checkbox */
+ { NULL, N_("Don't overwrite with &zero length file"), 11, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 15 - button */
+ { NULL, N_("A&ll"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_ALL },
+ /* 16 - button */
+ { NULL, N_("&Older"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_OLDER },
+ /* 17 - button */
+ { NULL, N_("Non&e"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_NONE },
+ /* 18 - button */
+ { NULL, N_("S&maller"), 12, 25, WPOS_KEEP_DEFAULT, REPLACE_SMALLER },
+ /* 19 - button */
+ { NULL, N_("&Size differs"), 12, 40, WPOS_KEEP_DEFAULT, REPLACE_SIZE },
+ /* --------------------------------------------------- */
+ /* 20 - button */
+ { NULL, N_("&Abort"), 14, 27, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, REPLACE_ABORT }
+ /* *INDENT-ON* */
+ };
+
+ const int gap = 1;
+
+ file_op_context_ui_t *ui = ctx->ui;
+ Widget *wd;
+ WGroup *g;
+ const char *title;
+
+ vfs_path_t *p;
+ char *s1;
+ const char *cs1;
+ char s2[BUF_SMALL];
+ int w, bw1, bw2;
+ unsigned short i;
+
+ gboolean do_append = FALSE, do_reget = FALSE;
+ unsigned long yes_id, no_id;
+ int result;
+
+ if (mode == Foreground)
+ title = _("File exists");
+ else
+ title = _("Background process: File exists");
+
+#ifdef ENABLE_NLS
+ {
+ const unsigned short num = G_N_ELEMENTS (dlg_widgets);
+
+ for (i = 0; i < num; i++)
+ if (dlg_widgets[i].text != NULL)
+ dlg_widgets[i].text = _(dlg_widgets[i].text);
+ }
+#endif /* ENABLE_NLS */
+
+ /* create widgets to get their real widths */
+ /* new file */
+ NEW_LABEL (0, dlg_widgets[0].text);
+ /* new file name */
+ p = vfs_path_from_str (ui->src_filename);
+ s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
+ NEW_LABEL (1, s1);
+ vfs_path_free (p, TRUE);
+ g_free (s1);
+ /* new file size */
+ size_trunc_len (s2, sizeof (s2), ui->src_stat->st_size, 0, panels_options.kilobyte_si);
+ NEW_LABEL (2, s2);
+ /* new file modification date & time */
+ cs1 = file_date (ui->src_stat->st_mtime);
+ NEW_LABEL (3, cs1);
+
+ /* existing file */
+ NEW_LABEL (4, dlg_widgets[4].text);
+ /* existing file name */
+ p = vfs_path_from_str (ui->tgt_filename);
+ s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
+ NEW_LABEL (5, s1);
+ vfs_path_free (p, TRUE);
+ g_free (s1);
+ /* existing file size */
+ size_trunc_len (s2, sizeof (s2), ui->dst_stat->st_size, 0, panels_options.kilobyte_si);
+ NEW_LABEL (6, s2);
+ /* existing file modification date & time */
+ cs1 = file_date (ui->dst_stat->st_mtime);
+ NEW_LABEL (7, cs1);
+
+ /* will "Append" and "Reget" buttons be in the dialog? */
+ do_append = !S_ISDIR (ui->dst_stat->st_mode);
+ do_reget = do_append && ctx->operation == OP_COPY && ui->dst_stat->st_size != 0
+ && ui->src_stat->st_size > ui->dst_stat->st_size;
+
+ NEW_LABEL (8, dlg_widgets[8].text);
+ NEW_BUTTON (9);
+ NEW_BUTTON (10);
+ if (do_append)
+ NEW_BUTTON (11);
+ if (do_reget)
+ NEW_BUTTON (12);
+
+ NEW_LABEL (13, dlg_widgets[13].text);
+ dlg_widgets[14].widget =
+ WIDGET (check_new (dlg_widgets[14].y, dlg_widgets[14].x, FALSE, dlg_widgets[14].text));
+ for (i = 15; i <= 20; i++)
+ NEW_BUTTON (i);
+
+ /* place widgets */
+ dlg_width -= 2 * (2 + gap); /* inside frame */
+
+ /* perhaps longest line is buttons */
+ bw1 = WCOLS (9) + gap + WCOLS (10);
+ if (do_append)
+ bw1 += gap + WCOLS (11);
+ if (do_reget)
+ bw1 += gap + WCOLS (12);
+ dlg_width = MAX (dlg_width, bw1);
+
+ bw2 = WCOLS (15);
+ for (i = 16; i <= 19; i++)
+ bw2 += gap + WCOLS (i);
+ dlg_width = MAX (dlg_width, bw2);
+
+ dlg_width = MAX (dlg_width, WCOLS (8));
+ dlg_width = MAX (dlg_width, WCOLS (13));
+ dlg_width = MAX (dlg_width, WCOLS (14));
+
+ /* truncate file names */
+ w = WCOLS (0) + gap + WCOLS (1);
+ if (w > dlg_width)
+ {
+ WLabel *l = LABEL (W (1));
+
+ w = dlg_width - gap - WCOLS (0);
+ label_set_text (l, str_trunc (l->text, w));
+ }
+
+ w = WCOLS (4) + gap + WCOLS (5);
+ if (w > dlg_width)
+ {
+ WLabel *l = LABEL (W (5));
+
+ w = dlg_width - gap - WCOLS (4);
+ label_set_text (l, str_trunc (l->text, w));
+ }
+
+ /* real dlalog width */
+ dlg_width += 2 * (2 + gap);
+
+ WX (1) = WX (0) + WCOLS (0) + gap;
+ WX (5) = WX (4) + WCOLS (4) + gap;
+
+ /* sizes: right alignment */
+ WX (2) = dlg_width / 2 - WCOLS (2);
+ WX (6) = dlg_width / 2 - WCOLS (6);
+
+ w = dlg_width - (2 + gap); /* right bound */
+
+ /* date & time */
+ WX (3) = w - WCOLS (3);
+ WX (7) = w - WCOLS (7);
+
+ /* buttons: center alignment */
+ WX (9) = dlg_width / 2 - bw1 / 2;
+ WX (10) = WX (9) + WCOLS (9) + gap;
+ if (do_append)
+ WX (11) = WX (10) + WCOLS (10) + gap;
+ if (do_reget)
+ WX (12) = WX (11) + WCOLS (11) + gap;
+
+ WX (15) = dlg_width / 2 - bw2 / 2;
+ for (i = 16; i <= 19; i++)
+ WX (i) = WX (i - 1) + WCOLS (i - 1) + gap;
+
+ /* TODO: write help (ticket #3970) */
+ ui->replace_dlg =
+ dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, alarm_colors, NULL, NULL,
+ "[Replace]", title);
+ wd = WIDGET (ui->replace_dlg);
+ g = GROUP (ui->replace_dlg);
+
+ /* file info */
+ for (i = 0; i <= 7; i++)
+ ADD_LABEL (i);
+ group_add_widget (g, hline_new (WY (7) - wd->rect.y + 1, -1, -1));
+
+ /* label & buttons */
+ ADD_LABEL (8); /* Overwrite this file? */
+ yes_id = ADD_BUTTON (9); /* Yes */
+ no_id = ADD_BUTTON (10); /* No */
+ if (do_append)
+ ADD_BUTTON (11); /* Append */
+ if (do_reget)
+ ADD_BUTTON (12); /* Reget */
+ group_add_widget (g, hline_new (WY (10) - wd->rect.y + 1, -1, -1));
+
+ /* label & buttons */
+ ADD_LABEL (13); /* Overwrite all files? */
+ group_add_widget (g, dlg_widgets[14].widget);
+ for (i = 15; i <= 19; i++)
+ ADD_BUTTON (i);
+ group_add_widget (g, hline_new (WY (19) - wd->rect.y + 1, -1, -1));
+
+ ADD_BUTTON (20); /* Abort */
+
+ group_select_widget_by_id (g, safe_overwrite ? no_id : yes_id);
+
+ result = dlg_run (ui->replace_dlg);
+
+ if (result != B_CANCEL)
+ ui->dont_overwrite_with_zero = CHECK (dlg_widgets[14].widget)->state;
+
+ widget_destroy (wd);
+
+ return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
+
+#undef ADD_BUTTON
+#undef NEW_BUTTON
+#undef ADD_LABEL
+#undef NEW_LABEL
+#undef WCOLS
+#undef WX
+#undef W
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_wildcarded (const char *p)
+{
+ gboolean escaped = FALSE;
+
+ for (; *p != '\0'; p++)
+ {
+ if (*p == '\\')
+ {
+ if (p[1] >= '1' && p[1] <= '9' && !escaped)
+ return TRUE;
+ escaped = !escaped;
+ }
+ else
+ {
+ if ((*p == '*' || *p == '?') && !escaped)
+ return TRUE;
+ escaped = FALSE;
+ }
+ }
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+place_progress_buttons (WDialog * h, gboolean suspended)
+{
+ const size_t i = suspended ? 2 : 1;
+ Widget *w = WIDGET (h);
+ int buttons_width;
+
+ buttons_width = 2 + progress_buttons[0].len + progress_buttons[3].len;
+ buttons_width += progress_buttons[i].len;
+ button_set_text (BUTTON (progress_buttons[i].w), progress_buttons[i].text);
+
+ progress_buttons[0].w->rect.x = w->rect.x + (w->rect.cols - buttons_width) / 2;
+ progress_buttons[i].w->rect.x = progress_buttons[0].w->rect.x + progress_buttons[0].len + 1;
+ progress_buttons[3].w->rect.x = progress_buttons[i].w->rect.x + progress_buttons[i].len + 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+progress_button_callback (WButton * button, int action)
+{
+ (void) button;
+ (void) action;
+
+ /* don't close dialog in any case */
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+FileProgressStatus
+check_progress_buttons (file_op_context_t * ctx)
+{
+ int c;
+ Gpm_Event event;
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return FILE_CONT;
+
+ ui = ctx->ui;
+
+ get_event:
+ event.x = -1; /* Don't show the GPM cursor */
+ c = tty_get_event (&event, FALSE, ctx->suspended);
+ if (c == EV_NONE)
+ return FILE_CONT;
+
+ /* Reinitialize to avoid old values after events other than selecting a button */
+ ui->op_dlg->ret_value = FILE_CONT;
+
+ dlg_process_event (ui->op_dlg, c, &event);
+ switch (ui->op_dlg->ret_value)
+ {
+ case FILE_SKIP:
+ if (ctx->suspended)
+ {
+ /* redraw dialog in case of Skip after Suspend */
+ place_progress_buttons (ui->op_dlg, FALSE);
+ widget_draw (WIDGET (ui->op_dlg));
+ }
+ ctx->suspended = FALSE;
+ return FILE_SKIP;
+ case B_CANCEL:
+ case FILE_ABORT:
+ ctx->suspended = FALSE;
+ return FILE_ABORT;
+ case FILE_SUSPEND:
+ ctx->suspended = !ctx->suspended;
+ place_progress_buttons (ui->op_dlg, ctx->suspended);
+ widget_draw (WIDGET (ui->op_dlg));
+ MC_FALLTHROUGH;
+ default:
+ if (ctx->suspended)
+ goto get_event;
+ return FILE_CONT;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ File progress display routines */
+
+void
+file_op_context_create_ui (file_op_context_t * ctx, gboolean with_eta,
+ filegui_dialog_type_t dialog_type)
+{
+ file_op_context_ui_t *ui;
+ Widget *w;
+ WGroup *g;
+ int buttons_width;
+ int dlg_width = 58, dlg_height = 17;
+ int y = 2, x = 3;
+ WRect r;
+
+ if (ctx == NULL || ctx->ui != NULL)
+ return;
+
+#ifdef ENABLE_NLS
+ if (progress_buttons[0].len == -1)
+ {
+ size_t i;
+
+ for (i = 0; i < G_N_ELEMENTS (progress_buttons); i++)
+ progress_buttons[i].text = _(progress_buttons[i].text);
+ }
+#endif
+
+ ctx->dialog_type = dialog_type;
+ ctx->recursive_result = RECURSIVE_YES;
+ ctx->ui = g_new0 (file_op_context_ui_t, 1);
+
+ ui = ctx->ui;
+ ui->replace_result = REPLACE_YES;
+
+ ui->op_dlg = dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, dialog_colors,
+ file_ui_op_dlg_callback, NULL, NULL, op_names[ctx->operation]);
+ w = WIDGET (ui->op_dlg);
+ g = GROUP (ui->op_dlg);
+
+ if (dialog_type != FILEGUI_DIALOG_DELETE_ITEM)
+ {
+ ui->showing_eta = with_eta && ctx->progress_totals_computed;
+ ui->showing_bps = with_eta;
+
+ ui->src_file_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->src_file_label);
+
+ ui->src_file = label_new (y++, x, NULL);
+ group_add_widget (g, ui->src_file);
+
+ ui->tgt_file_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->tgt_file_label);
+
+ ui->tgt_file = label_new (y++, x, NULL);
+ group_add_widget (g, ui->tgt_file);
+
+ ui->progress_file_gauge = gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
+ if (!classic_progressbar && (current_panel == right_panel))
+ ui->progress_file_gauge->from_left_to_right = FALSE;
+ group_add_widget_autopos (g, ui->progress_file_gauge, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
+
+ ui->progress_file_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->progress_file_label);
+
+ if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
+ {
+ ui->total_bytes_label = hline_new (y++, -1, -1);
+ group_add_widget (g, ui->total_bytes_label);
+
+ if (ctx->progress_totals_computed)
+ {
+ ui->progress_total_gauge =
+ gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
+ if (!classic_progressbar && (current_panel == right_panel))
+ ui->progress_total_gauge->from_left_to_right = FALSE;
+ group_add_widget_autopos (g, ui->progress_total_gauge,
+ WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
+ }
+
+ ui->total_files_processed_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->total_files_processed_label);
+
+ ui->time_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->time_label);
+ }
+ }
+ else
+ {
+ ui->src_file = label_new (y++, x, NULL);
+ group_add_widget (g, ui->src_file);
+
+ ui->total_files_processed_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->total_files_processed_label);
+ }
+
+ group_add_widget (g, hline_new (y++, -1, -1));
+
+ progress_buttons[0].w = WIDGET (button_new (y, 0, progress_buttons[0].action,
+ progress_buttons[0].flags, progress_buttons[0].text,
+ progress_button_callback));
+ if (progress_buttons[0].len == -1)
+ progress_buttons[0].len = button_get_len (BUTTON (progress_buttons[0].w));
+
+ progress_buttons[1].w = WIDGET (button_new (y, 0, progress_buttons[1].action,
+ progress_buttons[1].flags, progress_buttons[1].text,
+ progress_button_callback));
+ if (progress_buttons[1].len == -1)
+ progress_buttons[1].len = button_get_len (BUTTON (progress_buttons[1].w));
+
+ if (progress_buttons[2].len == -1)
+ {
+ /* create and destroy button to get it length */
+ progress_buttons[2].w = WIDGET (button_new (y, 0, progress_buttons[2].action,
+ progress_buttons[2].flags,
+ progress_buttons[2].text,
+ progress_button_callback));
+ progress_buttons[2].len = button_get_len (BUTTON (progress_buttons[2].w));
+ widget_destroy (progress_buttons[2].w);
+ }
+ progress_buttons[2].w = progress_buttons[1].w;
+
+ progress_buttons[3].w = WIDGET (button_new (y, 0, progress_buttons[3].action,
+ progress_buttons[3].flags, progress_buttons[3].text,
+ NULL));
+ if (progress_buttons[3].len == -1)
+ progress_buttons[3].len = button_get_len (BUTTON (progress_buttons[3].w));
+
+ group_add_widget (g, progress_buttons[0].w);
+ group_add_widget (g, progress_buttons[1].w);
+ group_add_widget (g, progress_buttons[3].w);
+
+ buttons_width = 2 +
+ progress_buttons[0].len + MAX (progress_buttons[1].len, progress_buttons[2].len) +
+ progress_buttons[3].len;
+
+ /* adjust dialog sizes */
+ r = w->rect;
+ r.lines = y + 3;
+ r.cols = MAX (COLS * 2 / 3, buttons_width + 6);
+ widget_set_size_rect (w, &r);
+
+ place_progress_buttons (ui->op_dlg, FALSE);
+
+ widget_select (progress_buttons[0].w);
+
+ /* We will manage the dialog without any help, that's why
+ we have to call dlg_init */
+ dlg_init (ui->op_dlg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_op_context_destroy_ui (file_op_context_t * ctx)
+{
+ if (ctx != NULL && ctx->ui != NULL)
+ {
+ file_op_context_ui_t *ui = (file_op_context_ui_t *) ctx->ui;
+
+ dlg_run_done (ui->op_dlg);
+ widget_destroy (WIDGET (ui->op_dlg));
+ MC_PTR_FREE (ctx->ui);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ show progressbar for file
+ */
+
+void
+file_progress_show (file_op_context_t * ctx, off_t done, off_t total,
+ const char *stalled_msg, gboolean force_update)
+{
+ file_op_context_ui_t *ui;
+
+ if (!verbose || ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (total == 0)
+ {
+ gauge_show (ui->progress_file_gauge, FALSE);
+ return;
+ }
+
+ gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
+ gauge_show (ui->progress_file_gauge, TRUE);
+
+ if (!force_update)
+ return;
+
+ if (!ui->showing_eta || ctx->eta_secs <= 0.5)
+ label_set_text (ui->progress_file_label, stalled_msg);
+ else
+ {
+ char buffer2[BUF_TINY];
+
+ file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
+ if (ctx->bps == 0)
+ label_set_textv (ui->progress_file_label, "%s %s", buffer2, stalled_msg);
+ else
+ {
+ char buffer3[BUF_TINY];
+
+ file_bps_prepare_for_show (buffer3, ctx->bps);
+ label_set_textv (ui->progress_file_label, "%s (%s) %s", buffer2, buffer3, stalled_msg);
+ }
+
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_progress_show_count (file_op_context_t * ctx, size_t done, size_t total)
+{
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (ui->total_files_processed_label == NULL)
+ return;
+
+ if (ctx->progress_totals_computed)
+ label_set_textv (ui->total_files_processed_label, _("Files processed: %zu / %zu"), done,
+ total);
+ else
+ label_set_textv (ui->total_files_processed_label, _("Files processed: %zu"), done);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_progress_show_total (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ uintmax_t copied_bytes, gboolean show_summary)
+{
+ char buffer2[BUF_TINY];
+ char buffer3[BUF_TINY];
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (ui->progress_total_gauge != NULL)
+ {
+ if (ctx->progress_bytes == 0)
+ gauge_show (ui->progress_total_gauge, FALSE);
+ else
+ {
+ gauge_set_value (ui->progress_total_gauge, 1024,
+ (int) (1024 * copied_bytes / ctx->progress_bytes));
+ gauge_show (ui->progress_total_gauge, TRUE);
+ }
+ }
+
+ if (!show_summary && tctx->bps == 0)
+ return;
+
+ if (ui->time_label != NULL)
+ {
+ gint64 tv_current;
+ char buffer4[BUF_TINY];
+
+ tv_current = g_get_monotonic_time ();
+ file_frmt_time (buffer2, (tv_current - tctx->transfer_start) / G_USEC_PER_SEC);
+
+ if (ctx->progress_totals_computed)
+ {
+ file_eta_prepare_for_show (buffer3, tctx->eta_secs, TRUE);
+ if (tctx->bps == 0)
+ label_set_textv (ui->time_label, _("Time: %s %s"), buffer2, buffer3);
+ else
+ {
+ file_bps_prepare_for_show (buffer4, (long) tctx->bps);
+ label_set_textv (ui->time_label, _("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
+ }
+ }
+ else
+ {
+ if (tctx->bps == 0)
+ label_set_textv (ui->time_label, _("Time: %s"), buffer2);
+ else
+ {
+ file_bps_prepare_for_show (buffer4, (long) tctx->bps);
+ label_set_textv (ui->time_label, _("Time: %s (%s)"), buffer2, buffer4);
+ }
+ }
+ }
+
+ if (ui->total_bytes_label != NULL)
+ {
+ size_trunc_len (buffer2, 5, tctx->copied_bytes, 0, panels_options.kilobyte_si);
+
+ if (!ctx->progress_totals_computed)
+ hline_set_textv (ui->total_bytes_label, _(" Total: %s "), buffer2);
+ else
+ {
+ size_trunc_len (buffer3, 5, ctx->progress_bytes, 0, panels_options.kilobyte_si);
+ hline_set_textv (ui->total_bytes_label, _(" Total: %s / %s "), buffer2, buffer3);
+ }
+ }
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_progress_show_source (file_op_context_t * ctx, const vfs_path_t * vpath)
+{
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (vpath != NULL)
+ {
+ char *s;
+
+ s = vfs_path_tokens_get (vpath, -1, 1);
+ label_set_text (ui->src_file_label, _("Source"));
+ label_set_text (ui->src_file, truncFileString (ui->op_dlg, s));
+ g_free (s);
+ }
+ else
+ {
+ label_set_text (ui->src_file_label, NULL);
+ label_set_text (ui->src_file, NULL);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_progress_show_target (file_op_context_t * ctx, const vfs_path_t * vpath)
+{
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (vpath != NULL)
+ {
+ label_set_text (ui->tgt_file_label, _("Target"));
+ label_set_text (ui->tgt_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
+ }
+ else
+ {
+ label_set_text (ui->tgt_file_label, NULL);
+ label_set_text (ui->tgt_file, NULL);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+file_progress_show_deleting (file_op_context_t * ctx, const char *s, size_t * count)
+{
+ static gint64 timestamp = 0;
+ /* update with 25 FPS rate */
+ static const gint64 delay = G_USEC_PER_SEC / 25;
+
+ gboolean ret;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return FALSE;
+
+ ret = mc_time_elapsed (&timestamp, delay);
+
+ if (ret)
+ {
+ file_op_context_ui_t *ui;
+
+ ui = ctx->ui;
+
+ if (ui->src_file_label != NULL)
+ label_set_text (ui->src_file_label, _("Deleting"));
+
+ label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, s));
+ }
+
+ if (count != NULL)
+ (*count)++;
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+FileProgressStatus
+file_progress_real_query_replace (file_op_context_t * ctx, enum OperationMode mode,
+ const char *src, struct stat * src_stat,
+ const char *dst, struct stat * dst_stat)
+{
+ file_op_context_ui_t *ui;
+ FileProgressStatus replace_with_zero;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return FILE_CONT;
+
+ ui = ctx->ui;
+
+ if (ui->replace_result == REPLACE_YES || ui->replace_result == REPLACE_NO
+ || ui->replace_result == REPLACE_APPEND)
+ {
+ ui->src_filename = src;
+ ui->src_stat = src_stat;
+ ui->tgt_filename = dst;
+ ui->dst_stat = dst_stat;
+ ui->replace_result = overwrite_query_dialog (ctx, mode);
+ }
+
+ replace_with_zero = (src_stat->st_size == 0
+ && ui->dont_overwrite_with_zero) ? FILE_SKIP : FILE_CONT;
+
+ switch (ui->replace_result)
+ {
+ case REPLACE_OLDER:
+ do_refresh ();
+ if (src_stat->st_mtime > dst_stat->st_mtime)
+ return replace_with_zero;
+ else
+ return FILE_SKIP;
+
+ case REPLACE_SIZE:
+ do_refresh ();
+ if (src_stat->st_size == dst_stat->st_size)
+ return FILE_SKIP;
+ else
+ return replace_with_zero;
+
+ case REPLACE_SMALLER:
+ do_refresh ();
+ if (src_stat->st_size > dst_stat->st_size)
+ return FILE_CONT;
+ else
+ return FILE_SKIP;
+
+ case REPLACE_ALL:
+ do_refresh ();
+ return replace_with_zero;
+
+ case REPLACE_REGET:
+ /* Careful: we fall through and set do_append */
+ ctx->do_reget = dst_stat->st_size;
+ MC_FALLTHROUGH;
+
+ case REPLACE_APPEND:
+ ctx->do_append = TRUE;
+ MC_FALLTHROUGH;
+
+ case REPLACE_YES:
+ do_refresh ();
+ return FILE_CONT;
+
+ case REPLACE_NO:
+ case REPLACE_NONE:
+ do_refresh ();
+ return FILE_SKIP;
+
+ case REPLACE_ABORT:
+ default:
+ return FILE_ABORT;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+file_mask_dialog (file_op_context_t * ctx, gboolean only_one, const char *format, const void *text,
+ const char *def_text, gboolean * do_bg)
+{
+ size_t fmd_xlen;
+ vfs_path_t *vpath;
+ gboolean source_easy_patterns = easy_patterns;
+ char fmd_buf[BUF_MEDIUM];
+ char *dest_dir = NULL;
+ char *tmp;
+ char *def_text_secure;
+
+ if (ctx == NULL)
+ return NULL;
+
+ /* unselect checkbox if target filesystem doesn't support attributes */
+ ctx->op_preserve = copymove_persistent_attr && filegui__check_attrs_on_fs (def_text);
+ ctx->stable_symlinks = FALSE;
+ *do_bg = FALSE;
+
+ /* filter out a possible password from def_text */
+ vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE);
+ tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
+ vfs_path_free (vpath, TRUE);
+
+ if (source_easy_patterns)
+ def_text_secure = strutils_glob_escape (tmp);
+ else
+ def_text_secure = strutils_regex_escape (tmp);
+ g_free (tmp);
+
+ if (only_one)
+ {
+ int format_len, text_len;
+ int max_len;
+
+ format_len = str_term_width1 (format);
+ text_len = str_term_width1 (text);
+ max_len = COLS - 2 - 6;
+
+ if (format_len + text_len <= max_len)
+ {
+ fmd_xlen = format_len + text_len + 6;
+ fmd_xlen = MAX (fmd_xlen, 68);
+ }
+ else
+ {
+ text = str_trunc ((const char *) text, max_len - format_len);
+ fmd_xlen = max_len + 6;
+ }
+
+ g_snprintf (fmd_buf, sizeof (fmd_buf), format, (const char *) text);
+ }
+ else
+ {
+ fmd_xlen = COLS * 2 / 3;
+ fmd_xlen = MAX (fmd_xlen, 68);
+ g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
+ }
+
+ {
+ char *source_mask = NULL;
+ char *orig_mask;
+ int val;
+ struct stat buf;
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (fmd_buf, input_label_above, easy_patterns ? "*" : "^(.*)$",
+ "input-def", &source_mask, NULL, FALSE, FALSE,
+ INPUT_COMPLETE_FILENAMES),
+ QUICK_START_COLUMNS,
+ QUICK_SEPARATOR (FALSE),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns, NULL),
+ QUICK_STOP_COLUMNS,
+ QUICK_LABELED_INPUT (N_("to:"), input_label_above, def_text_secure, "input2", &dest_dir,
+ NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_START_COLUMNS,
+ QUICK_CHECKBOX (N_("Follow &links"), &ctx->follow_links, NULL),
+ QUICK_CHECKBOX (N_("Preserve &attributes"), &ctx->op_preserve, NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL),
+ QUICK_CHECKBOX (N_("&Stable symlinks"), &ctx->stable_symlinks, NULL),
+ QUICK_STOP_COLUMNS,
+ QUICK_START_BUTTONS (TRUE, TRUE),
+ QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
+#ifdef ENABLE_BACKGROUND
+ QUICK_BUTTON (N_("&Background"), B_USER, NULL, NULL),
+#endif /* ENABLE_BACKGROUND */
+ QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, fmd_xlen };
+
+ quick_dialog_t qdlg = {
+ r, op_names[ctx->operation], "[Mask Copy/Rename]",
+ quick_widgets, NULL, NULL
+ };
+
+ while (TRUE)
+ {
+ val = quick_dialog_skip (&qdlg, 4);
+
+ if (val == B_CANCEL)
+ {
+ g_free (def_text_secure);
+ return NULL;
+ }
+
+ ctx->stat_func = ctx->follow_links ? mc_stat : mc_lstat;
+
+ if (ctx->op_preserve)
+ {
+ ctx->preserve = TRUE;
+ ctx->umask_kill = 0777777;
+ ctx->preserve_uidgid = (geteuid () == 0);
+ }
+ else
+ {
+ mode_t i2;
+
+ ctx->preserve = ctx->preserve_uidgid = FALSE;
+ i2 = umask (0);
+ umask (i2);
+ ctx->umask_kill = i2 ^ 0777777;
+ }
+
+ if (*dest_dir == '\0')
+ {
+ g_free (def_text_secure);
+ g_free (source_mask);
+ g_free (dest_dir);
+ return NULL;
+ }
+
+ ctx->search_handle = mc_search_new (source_mask, NULL);
+ if (ctx->search_handle != NULL)
+ break;
+
+ message (D_ERROR, MSG_ERROR, _("Invalid source pattern '%s'"), source_mask);
+ MC_PTR_FREE (dest_dir);
+ MC_PTR_FREE (source_mask);
+ }
+
+ g_free (def_text_secure);
+ g_free (source_mask);
+
+ ctx->search_handle->is_case_sensitive = TRUE;
+ if (source_easy_patterns)
+ ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
+ else
+ ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
+
+ tmp = dest_dir;
+ dest_dir = tilde_expand (tmp);
+ g_free (tmp);
+ vpath = vfs_path_from_str (dest_dir);
+
+ ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
+ if (ctx->dest_mask == NULL)
+ ctx->dest_mask = dest_dir;
+ else
+ ctx->dest_mask++;
+
+ orig_mask = ctx->dest_mask;
+
+ if (*ctx->dest_mask == '\0'
+ || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
+ && (!only_one
+ || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
+ || (ctx->dive_into_subdirs
+ && ((!only_one && !is_wildcarded (ctx->dest_mask))
+ || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
+ ctx->dest_mask = g_strdup ("\\0");
+ else
+ {
+ ctx->dest_mask = g_strdup (ctx->dest_mask);
+ *orig_mask = '\0';
+ }
+
+ if (*dest_dir == '\0')
+ {
+ g_free (dest_dir);
+ dest_dir = g_strdup ("./");
+ }
+
+ vfs_path_free (vpath, TRUE);
+
+ if (val == B_USER)
+ *do_bg = TRUE;
+ }
+
+ return dest_dir;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/filegui.h b/src/filemanager/filegui.h
new file mode 100644
index 0000000..6387faa
--- /dev/null
+++ b/src/filemanager/filegui.h
@@ -0,0 +1,40 @@
+/** \file filegui.h
+ * \brief Header: file management GUI for the text mode edition
+ */
+
+#ifndef MC__FILEGUI_H
+#define MC__FILEGUI_H
+
+#include "lib/global.h"
+#include "fileopctx.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void file_op_context_create_ui (file_op_context_t * ctx, gboolean with_eta,
+ filegui_dialog_type_t dialog_type);
+void file_op_context_destroy_ui (file_op_context_t * ctx);
+
+char *file_mask_dialog (file_op_context_t * ctx, gboolean only_one, const char *format,
+ const void *text, const char *def_text, gboolean * do_bg);
+
+FileProgressStatus check_progress_buttons (file_op_context_t * ctx);
+
+void file_progress_show (file_op_context_t * ctx, off_t done, off_t total,
+ const char *stalled_msg, gboolean force_update);
+void file_progress_show_count (file_op_context_t * ctx, size_t done, size_t total);
+void file_progress_show_total (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ uintmax_t copied_bytes, gboolean show_summary);
+void file_progress_show_source (file_op_context_t * ctx, const vfs_path_t * vpath);
+void file_progress_show_target (file_op_context_t * ctx, const vfs_path_t * vpath);
+gboolean file_progress_show_deleting (file_op_context_t * ctx, const char *path, size_t * count);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__FILEGUI_H */
diff --git a/src/filemanager/filemanager.c b/src/filemanager/filemanager.c
new file mode 100644
index 0000000..b995024
--- /dev/null
+++ b/src/filemanager/filemanager.c
@@ -0,0 +1,1852 @@
+/*
+ Main dialog (file panels) of the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1996, 1997
+ Janne Kukonlehto, 1994, 1995
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ 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 filemanager.c
+ * \brief Source: main dialog (file panels) of the Midnight Commander
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <pwd.h> /* for username in xterm title */
+
+#include "lib/global.h"
+#include "lib/fileloc.h" /* MC_HINT */
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* KEY_M_* masks */
+#include "lib/skin.h"
+#include "lib/util.h"
+
+#include "lib/vfs/vfs.h"
+
+#include "src/args.h"
+#ifdef ENABLE_SUBSHELL
+#include "src/subshell/subshell.h"
+#endif
+#include "src/execute.h" /* toggle_subshell */
+#include "src/setup.h" /* variables */
+#include "src/learn.h" /* learn_keys() */
+#include "src/keymap.h"
+#include "lib/fileloc.h" /* MC_FILEPOS_FILE */
+#include "lib/keybind.h"
+#include "lib/event.h"
+
+#include "tree.h"
+#include "boxes.h" /* sort_box(), tree_box() */
+#include "layout.h"
+#include "cmd.h" /* commands */
+#include "hotlist.h"
+#include "panelize.h"
+#include "command.h" /* cmdline */
+#include "dir.h" /* dir_list_clean() */
+
+#ifdef USE_INTERNAL_EDIT
+#include "src/editor/edit.h"
+#endif
+
+#ifdef USE_DIFF_VIEW
+#include "src/diffviewer/ydiff.h"
+#endif
+
+#include "src/consaver/cons.saver.h" /* show_console_contents */
+#include "src/file_history.h" /* show_file_history() */
+
+#include "filemanager.h"
+
+/*** global variables ****************************************************************************/
+
+/* When the modes are active, left_panel, right_panel and tree_panel */
+/* point to a proper data structure. You should check with the functions */
+/* get_current_type and get_other_type the types of the panels before using */
+/* this pointer variables */
+
+/* The structures for the panels */
+WPanel *left_panel = NULL;
+WPanel *right_panel = NULL;
+/* Pointer to the selected and unselected panel */
+WPanel *current_panel = NULL;
+
+/* The Menubar */
+WMenuBar *the_menubar = NULL;
+/* The widget where we draw the prompt */
+WLabel *the_prompt;
+/* The hint bar */
+WLabel *the_hint;
+/* The button bar */
+WButtonBar *the_bar;
+
+/* The prompt */
+const char *mc_prompt = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifdef HAVE_CHARSET
+/*
+ * Don't restrict the output on the screen manager level,
+ * the translation tables take care of it.
+ */
+#endif /* !HAVE_CHARSET */
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static menu_t *left_menu, *right_menu;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** Stop MC main dialog and the current dialog if it exists.
+ * Needed to provide fast exit from MC viewer or editor on shell exit */
+static void
+stop_dialogs (void)
+{
+ dlg_close (filemanager);
+
+ if (top_dlg != NULL)
+ dlg_close (DIALOG (top_dlg->data));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+treebox_cmd (void)
+{
+ char *sel_dir;
+
+ sel_dir = tree_box (panel_current_entry (current_panel)->fname->str);
+ if (sel_dir != NULL)
+ {
+ vfs_path_t *sel_vdir;
+
+ sel_vdir = vfs_path_from_str (sel_dir);
+ panel_cd (current_panel, sel_vdir, cd_exact);
+ vfs_path_free (sel_vdir, TRUE);
+ g_free (sel_dir);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef LISTMODE_EDITOR
+static void
+listmode_cmd (void)
+{
+ char *newmode;
+
+ if (get_current_type () != view_listing)
+ return;
+
+ newmode = listmode_edit (current_panel->user_format);
+ if (!newmode)
+ return;
+
+ g_free (current_panel->user_format);
+ current_panel->list_format = list_user;
+ current_panel->user_format = newmode;
+ set_panel_formats (current_panel);
+
+ do_refresh ();
+}
+#endif /* LISTMODE_EDITOR */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_panel_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("File listin&g"), CK_PanelListing));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Quick view"), CK_PanelQuickView));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Info"), CK_PanelInfo));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Tree"), CK_PanelTree));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("&Listing format..."), CK_SetupListingFormat));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Sort order..."), CK_Sort));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Filter..."), CK_Filter));
+#ifdef HAVE_CHARSET
+ entries = g_list_prepend (entries, menu_entry_new (_("&Encoding..."), CK_SelectCodepage));
+#endif
+ entries = g_list_prepend (entries, menu_separator_new ());
+#ifdef ENABLE_VFS_FTP
+ entries = g_list_prepend (entries, menu_entry_new (_("FT&P link..."), CK_ConnectFtp));
+#endif
+#ifdef ENABLE_VFS_FISH
+ entries = g_list_prepend (entries, menu_entry_new (_("S&hell link..."), CK_ConnectFish));
+#endif
+#ifdef ENABLE_VFS_SFTP
+ entries = g_list_prepend (entries, menu_entry_new (_("SFTP li&nk..."), CK_ConnectSftp));
+#endif
+ entries = g_list_prepend (entries, menu_entry_new (_("Paneli&ze"), CK_Panelize));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Rescan"), CK_Reread));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_file_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&View"), CK_View));
+ entries = g_list_prepend (entries, menu_entry_new (_("Vie&w file..."), CK_ViewFile));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Filtered view"), CK_ViewFiltered));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Edit"), CK_Edit));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Copy"), CK_Copy));
+ entries = g_list_prepend (entries, menu_entry_new (_("C&hmod"), CK_ChangeMode));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Link"), CK_Link));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Symlink"), CK_LinkSymbolic));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("Relative symlin&k"), CK_LinkSymbolicRelative));
+ entries = g_list_prepend (entries, menu_entry_new (_("Edit s&ymlink"), CK_LinkSymbolicEdit));
+ entries = g_list_prepend (entries, menu_entry_new (_("Ch&own"), CK_ChangeOwn));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Advanced chown"), CK_ChangeOwnAdvanced));
+#ifdef ENABLE_EXT2FS_ATTR
+ entries = g_list_prepend (entries, menu_entry_new (_("Cha&ttr"), CK_ChangeAttributes));
+#endif
+ entries = g_list_prepend (entries, menu_entry_new (_("&Rename/Move"), CK_Move));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Mkdir"), CK_MakeDir));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Delete"), CK_Delete));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Quick cd"), CK_CdQuick));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("Select &group"), CK_Select));
+ entries = g_list_prepend (entries, menu_entry_new (_("U&nselect group"), CK_Unselect));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Invert selection"), CK_SelectInvert));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("E&xit"), CK_Quit));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_command_menu (void)
+{
+ /* I know, I'm lazy, but the tree widget when it's not running
+ * as a panel still has some problems, I have not yet finished
+ * the WTree widget port, sorry.
+ */
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&User menu"), CK_UserMenu));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Directory tree"), CK_Tree));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Find file"), CK_Find));
+ entries = g_list_prepend (entries, menu_entry_new (_("S&wap panels"), CK_Swap));
+ entries = g_list_prepend (entries, menu_entry_new (_("Switch &panels on/off"), CK_Shell));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Compare directories"), CK_CompareDirs));
+#ifdef USE_DIFF_VIEW
+ entries = g_list_prepend (entries, menu_entry_new (_("C&ompare files"), CK_CompareFiles));
+#endif
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("E&xternal panelize"), CK_ExternalPanelize));
+ entries = g_list_prepend (entries, menu_entry_new (_("Show directory s&izes"), CK_DirSize));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("Command &history"), CK_History));
+ entries =
+ g_list_prepend (entries,
+ menu_entry_new (_("Viewed/edited files hi&story"), CK_EditorViewerHistory));
+ entries = g_list_prepend (entries, menu_entry_new (_("Di&rectory hotlist"), CK_HotList));
+#ifdef ENABLE_VFS
+ entries = g_list_prepend (entries, menu_entry_new (_("&Active VFS list"), CK_VfsList));
+#endif
+#ifdef ENABLE_BACKGROUND
+ entries = g_list_prepend (entries, menu_entry_new (_("&Background jobs"), CK_Jobs));
+#endif
+ entries = g_list_prepend (entries, menu_entry_new (_("Screen lis&t"), CK_ScreenList));
+ entries = g_list_prepend (entries, menu_separator_new ());
+#ifdef ENABLE_VFS_UNDELFS
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("&Undelete files (ext2fs only)"), CK_Undelete));
+#endif
+#ifdef LISTMODE_EDITOR
+ entries = g_list_prepend (entries, menu_entry_new (_("&Listing format edit"), CK_ListMode));
+#endif
+#if defined (ENABLE_VFS_UNDELFS) || defined (LISTMODE_EDITOR)
+ entries = g_list_prepend (entries, menu_separator_new ());
+#endif
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("Edit &extension file"), CK_EditExtensionsFile));
+ entries = g_list_prepend (entries, menu_entry_new (_("Edit &menu file"), CK_EditUserMenu));
+ entries =
+ g_list_prepend (entries,
+ menu_entry_new (_("Edit hi&ghlighting group file"),
+ CK_EditFileHighlightFile));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_options_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&Configuration..."), CK_Options));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Layout..."), CK_OptionsLayout));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Panel options..."), CK_OptionsPanel));
+ entries = g_list_prepend (entries, menu_entry_new (_("C&onfirmation..."), CK_OptionsConfirm));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Appearance..."), CK_OptionsAppearance));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("&Display bits..."), CK_OptionsDisplayBits));
+ entries = g_list_prepend (entries, menu_entry_new (_("Learn &keys..."), CK_LearnKeys));
+#ifdef ENABLE_VFS
+ entries = g_list_prepend (entries, menu_entry_new (_("&Virtual FS..."), CK_OptionsVfs));
+#endif
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Save setup"), CK_SaveSetup));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+init_menu (void)
+{
+ left_menu = menu_new ("", create_panel_menu (), "[Left and Right Menus]");
+ menubar_add_menu (the_menubar, left_menu);
+ menubar_add_menu (the_menubar, menu_new (_("&File"), create_file_menu (), "[File Menu]"));
+ menubar_add_menu (the_menubar,
+ menu_new (_("&Command"), create_command_menu (), "[Command Menu]"));
+ menubar_add_menu (the_menubar,
+ menu_new (_("&Options"), create_options_menu (), "[Options Menu]"));
+ right_menu = menu_new ("", create_panel_menu (), "[Left and Right Menus]");
+ menubar_add_menu (the_menubar, right_menu);
+ update_menu ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menu_last_selected_cmd (void)
+{
+ menubar_activate (the_menubar, drop_menus, -1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menu_cmd (void)
+{
+ int selected;
+
+ if ((get_current_index () == 0) == current_panel->active)
+ selected = 0;
+ else
+ selected = g_list_length (the_menubar->menu) - 1;
+
+ menubar_activate (the_menubar, drop_menus, selected);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+sort_cmd (void)
+{
+ WPanel *p;
+ const panel_field_t *sort_order;
+
+ if (!SELECTED_IS_PANEL)
+ return;
+
+ p = MENU_PANEL;
+ sort_order = sort_box (&p->sort_info, p->sort_field);
+ panel_set_sort_order (p, sort_order);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+midnight_get_shortcut (long command)
+{
+ const char *ext_map;
+ const char *shortcut = NULL;
+
+ shortcut = keybind_lookup_keymap_shortcut (filemanager_map, command);
+ if (shortcut != NULL)
+ return g_strdup (shortcut);
+
+ shortcut = keybind_lookup_keymap_shortcut (panel_map, command);
+ if (shortcut != NULL)
+ return g_strdup (shortcut);
+
+ ext_map = keybind_lookup_keymap_shortcut (filemanager_map, CK_ExtendedKeyMap);
+ if (ext_map != NULL)
+ shortcut = keybind_lookup_keymap_shortcut (filemanager_x_map, command);
+ if (shortcut != NULL)
+ return g_strdup_printf ("%s %s", ext_map, shortcut);
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+midnight_get_title (const WDialog * h, size_t len)
+{
+ char *path;
+ char *login;
+ char *p;
+
+ (void) h;
+
+ title_path_prepare (&path, &login);
+
+ p = g_strdup_printf ("%s [%s]:%s", _("Panels:"), login, path);
+ g_free (path);
+ g_free (login);
+ path = g_strdup (str_trunc (p, len - 4));
+ g_free (p);
+
+ return path;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+toggle_panels_split (void)
+{
+ panels_layout.horizontal_split = !panels_layout.horizontal_split;
+ layout_change ();
+ do_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS
+/* event helper */
+static gboolean
+check_panel_timestamp (const WPanel * panel, panel_view_mode_t mode, struct vfs_class *vclass,
+ vfsid id)
+{
+ if (mode == view_listing)
+ {
+ const struct vfs_class *me;
+
+ me = vfs_path_get_last_path_vfs (panel->cwd_vpath);
+ if (me != vclass)
+ return FALSE;
+
+ if (vfs_getid (panel->cwd_vpath) != id)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+static gboolean
+check_current_panel_timestamp (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ ev_vfs_stamp_create_t *event_data = (ev_vfs_stamp_create_t *) data;
+
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+
+ event_data->ret =
+ check_panel_timestamp (current_panel, get_current_type (), event_data->vclass,
+ event_data->id);
+ return !event_data->ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+static gboolean
+check_other_panel_timestamp (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ ev_vfs_stamp_create_t *event_data = (ev_vfs_stamp_create_t *) data;
+
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+
+ event_data->ret =
+ check_panel_timestamp (other_panel, get_other_type (), event_data->vclass, event_data->id);
+ return !event_data->ret;
+}
+#endif /* ENABLE_VFS */
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+static gboolean
+print_vfs_message (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ ev_vfs_print_message_t *event_data = (ev_vfs_print_message_t *) data;
+
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+
+ if (mc_global.midnight_shutdown)
+ goto ret;
+
+ if (!mc_global.message_visible || the_hint == NULL || WIDGET (the_hint)->owner == NULL)
+ {
+ int col, row;
+
+ if (!nice_rotating_dash || (ok_to_refresh <= 0))
+ goto ret;
+
+ /* Preserve current cursor position */
+ tty_getyx (&row, &col);
+
+ tty_gotoyx (0, 0);
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_string (str_fit_to_term (event_data->msg, COLS - 1, J_LEFT));
+
+ /* Restore cursor position */
+ tty_gotoyx (row, col);
+ mc_refresh ();
+ goto ret;
+ }
+
+ if (mc_global.message_visible)
+ set_hintbar (event_data->msg);
+
+ ret:
+ MC_PTR_FREE (event_data->msg);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+create_panels (void)
+{
+ int current_index, other_index;
+ panel_view_mode_t current_mode, other_mode;
+ char *current_dir, *other_dir;
+ vfs_path_t *original_dir;
+
+ /*
+ * Following cases from command line are possible:
+ * 'mc' (no arguments): mc_run_param0 == NULL, mc_run_param1 == NULL
+ * active panel uses current directory
+ * passive panel uses "other_dir" from panels.ini
+ *
+ * 'mc dir1 dir2' (two arguments): mc_run_param0 != NULL, mc_run_param1 != NULL
+ * active panel uses mc_run_param0
+ * passive panel uses mc_run_param1
+ *
+ * 'mc dir1' (single argument): mc_run_param0 != NULL, mc_run_param1 == NULL
+ * active panel uses mc_run_param0
+ * passive panel uses "other_dir" from panels.ini
+ */
+
+ /* Set up panel directories */
+ if (boot_current_is_left)
+ {
+ /* left panel is active */
+ current_index = 0;
+ other_index = 1;
+ current_mode = startup_left_mode;
+ other_mode = startup_right_mode;
+
+ if (mc_run_param0 == NULL && mc_run_param1 == NULL)
+ {
+ /* no arguments */
+ current_dir = NULL; /* assume current dir */
+ other_dir = saved_other_dir; /* from ini */
+ }
+ else if (mc_run_param0 != NULL && mc_run_param1 != NULL)
+ {
+ /* two arguments */
+ current_dir = (char *) mc_run_param0;
+ other_dir = mc_run_param1;
+ }
+ else /* mc_run_param0 != NULL && mc_run_param1 == NULL */
+ {
+ /* one argument */
+ current_dir = (char *) mc_run_param0;
+ other_dir = saved_other_dir; /* from ini */
+ }
+ }
+ else
+ {
+ /* right panel is active */
+ current_index = 1;
+ other_index = 0;
+ current_mode = startup_right_mode;
+ other_mode = startup_left_mode;
+
+ if (mc_run_param0 == NULL && mc_run_param1 == NULL)
+ {
+ /* no arguments */
+ current_dir = NULL; /* assume current dir */
+ other_dir = saved_other_dir; /* from ini */
+ }
+ else if (mc_run_param0 != NULL && mc_run_param1 != NULL)
+ {
+ /* two arguments */
+ current_dir = (char *) mc_run_param0;
+ other_dir = mc_run_param1;
+ }
+ else /* mc_run_param0 != NULL && mc_run_param1 == NULL */
+ {
+ /* one argument */
+ current_dir = (char *) mc_run_param0;
+ other_dir = saved_other_dir; /* from ini */
+ }
+ }
+
+ /* 1. Get current dir */
+ original_dir = vfs_path_clone (vfs_get_raw_current_dir ());
+
+ /* 2. Create passive panel */
+ if (other_dir != NULL)
+ {
+ vfs_path_t *vpath;
+
+ if (g_path_is_absolute (other_dir))
+ vpath = vfs_path_from_str (other_dir);
+ else
+ vpath = vfs_path_append_new (original_dir, other_dir, (char *) NULL);
+ mc_chdir (vpath);
+ vfs_path_free (vpath, TRUE);
+ }
+ create_panel (other_index, other_mode);
+
+ /* 3. Create active panel */
+ if (current_dir == NULL)
+ mc_chdir (original_dir);
+ else
+ {
+ vfs_path_t *vpath;
+
+ if (g_path_is_absolute (current_dir))
+ vpath = vfs_path_from_str (current_dir);
+ else
+ vpath = vfs_path_append_new (original_dir, current_dir, (char *) NULL);
+ mc_chdir (vpath);
+ vfs_path_free (vpath, TRUE);
+ }
+ create_panel (current_index, current_mode);
+
+ if (startup_left_mode == view_listing)
+ current_panel = left_panel;
+ else if (right_panel != NULL)
+ current_panel = right_panel;
+ else
+ current_panel = left_panel;
+
+ vfs_path_free (original_dir, TRUE);
+
+#ifdef ENABLE_VFS
+ mc_event_add (MCEVENT_GROUP_CORE, "vfs_timestamp", check_other_panel_timestamp, NULL, NULL);
+ mc_event_add (MCEVENT_GROUP_CORE, "vfs_timestamp", check_current_panel_timestamp, NULL, NULL);
+#endif /* ENABLE_VFS */
+
+ mc_event_add (MCEVENT_GROUP_CORE, "vfs_print_message", print_vfs_message, NULL, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+midnight_put_panel_path (WPanel * panel)
+{
+ vfs_path_t *cwd_vpath;
+ const char *cwd_vpath_str;
+
+ if (!command_prompt)
+ return;
+
+#ifdef HAVE_CHARSET
+ cwd_vpath = remove_encoding_from_path (panel->cwd_vpath);
+#else
+ cwd_vpath = vfs_path_clone (panel->cwd_vpath);
+#endif
+
+ cwd_vpath_str = vfs_path_as_str (cwd_vpath);
+
+ command_insert (cmdline, cwd_vpath_str, FALSE);
+
+ if (!IS_PATH_SEP (cwd_vpath_str[strlen (cwd_vpath_str) - 1]))
+ command_insert (cmdline, PATH_SEP_STR, FALSE);
+
+ vfs_path_free (cwd_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+put_link (WPanel * panel)
+{
+ const file_entry_t *fe;
+
+ if (!command_prompt)
+ return;
+
+ fe = panel_current_entry (panel);
+
+ if (S_ISLNK (fe->st.st_mode))
+ {
+ char buffer[MC_MAXPATHLEN];
+ vfs_path_t *vpath;
+ int i;
+
+ vpath = vfs_path_append_new (panel->cwd_vpath, fe->fname->str, (char *) NULL);
+ i = mc_readlink (vpath, buffer, sizeof (buffer) - 1);
+ vfs_path_free (vpath, TRUE);
+
+ if (i > 0)
+ {
+ buffer[i] = '\0';
+ command_insert (cmdline, buffer, TRUE);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+put_current_link (void)
+{
+ put_link (current_panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+put_other_link (void)
+{
+ if (get_other_type () == view_listing)
+ put_link (other_panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Insert the selected file name into the input line */
+static void
+put_current_selected (void)
+{
+ const char *tmp;
+
+ if (!command_prompt)
+ return;
+
+ if (get_current_type () == view_tree)
+ {
+ WTree *tree;
+ const vfs_path_t *selected_name;
+
+ tree = (WTree *) get_panel_widget (get_current_index ());
+ selected_name = tree_selected_name (tree);
+ tmp = vfs_path_as_str (selected_name);
+ }
+ else
+ tmp = panel_current_entry (current_panel)->fname->str;
+
+ command_insert (cmdline, tmp, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+put_tagged (WPanel * panel)
+{
+ if (!command_prompt)
+ return;
+ input_disable_update (cmdline);
+ if (panel->marked)
+ {
+ int i;
+
+ for (i = 0; i < panel->dir.len; i++)
+ if (panel->dir.list[i].f.marked != 0)
+ command_insert (cmdline, panel->dir.list[i].fname->str, TRUE);
+ }
+ else
+ command_insert (cmdline, panel_current_entry (panel)->fname->str, TRUE);
+
+ input_enable_update (cmdline);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+put_current_tagged (void)
+{
+ put_tagged (current_panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+put_other_tagged (void)
+{
+ if (get_other_type () == view_listing)
+ put_tagged (other_panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+setup_mc (void)
+{
+#ifdef HAVE_SLANG
+#ifdef HAVE_CHARSET
+ tty_display_8bit (TRUE);
+#else
+ tty_display_8bit (mc_global.full_eight_bits);
+#endif /* HAVE_CHARSET */
+
+#else /* HAVE_SLANG */
+
+#ifdef HAVE_CHARSET
+ tty_display_8bit (TRUE);
+#else
+ tty_display_8bit (mc_global.eight_bit_clean);
+#endif /* HAVE_CHARSET */
+#endif /* HAVE_SLANG */
+
+ if ((tty_baudrate () < 9600) || mc_global.tty.slow_terminal)
+ verbose = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+setup_dummy_mc (void)
+{
+ vfs_path_t *vpath;
+ char *d;
+ int ret;
+
+ d = vfs_get_cwd ();
+ setup_mc ();
+ vpath = vfs_path_from_str (d);
+ ret = mc_chdir (vpath);
+ (void) ret;
+ vfs_path_free (vpath, TRUE);
+ g_free (d);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+done_mc (void)
+{
+ /* Setup shutdown
+ *
+ * We sync the profiles since the hotlist may have changed, while
+ * we only change the setup data if we have the auto save feature set
+ */
+
+ save_setup (auto_save_setup, panels_options.auto_save_setup);
+
+ vfs_stamp_path (vfs_get_raw_current_dir ());
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+create_file_manager (void)
+{
+ Widget *w = WIDGET (filemanager);
+ WGroup *g = GROUP (filemanager);
+
+ w->keymap = filemanager_map;
+ w->ext_keymap = filemanager_x_map;
+
+ filemanager->get_shortcut = midnight_get_shortcut;
+ filemanager->get_title = midnight_get_title;
+ /* allow rebind tab */
+ widget_want_tab (w, TRUE);
+
+ the_menubar = menubar_new (NULL);
+ group_add_widget (g, the_menubar);
+ init_menu ();
+
+ create_panels ();
+ group_add_widget (g, get_panel_widget (0));
+ group_add_widget (g, get_panel_widget (1));
+
+ the_hint = label_new (0, 0, NULL);
+ the_hint->transparent = TRUE;
+ the_hint->auto_adjust_cols = 0;
+ WIDGET (the_hint)->rect.cols = COLS;
+ group_add_widget (g, the_hint);
+
+ cmdline = command_new (0, 0, 0);
+ group_add_widget (g, cmdline);
+
+ the_prompt = label_new (0, 0, mc_prompt);
+ the_prompt->transparent = TRUE;
+ group_add_widget (g, the_prompt);
+
+ the_bar = buttonbar_new ();
+ group_add_widget (g, the_bar);
+ midnight_set_buttonbar (the_bar);
+
+#ifdef ENABLE_SUBSHELL
+ /* Must be done after creation of cmdline and prompt widgets to avoid potential
+ NULL dereference in load_prompt() -> ... -> setup_cmdline() -> label_set_text(). */
+ if (mc_global.tty.use_subshell)
+ add_select_channel (mc_global.tty.subshell_pty, load_prompt, NULL);
+#endif /* !ENABLE_SUBSHELL */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** result must be free'd (I think this should go in util.c) */
+static vfs_path_t *
+prepend_cwd_on_local (const char *filename)
+{
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (filename);
+ if (!vfs_file_is_local (vpath) || g_path_is_absolute (filename))
+ return vpath;
+
+ vfs_path_free (vpath, TRUE);
+
+ return vfs_path_append_new (vfs_get_raw_current_dir (), filename, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Invoke the internal view/edit routine with:
+ * the default processing and forcing the internal viewer/editor
+ */
+static gboolean
+mc_maybe_editor_or_viewer (void)
+{
+ gboolean ret;
+
+ switch (mc_global.mc_run_mode)
+ {
+#ifdef USE_INTERNAL_EDIT
+ case MC_RUN_EDITOR:
+ ret = edit_files ((GList *) mc_run_param0);
+ break;
+#endif /* USE_INTERNAL_EDIT */
+ case MC_RUN_VIEWER:
+ {
+ vfs_path_t *vpath = NULL;
+
+ if (mc_run_param0 != NULL && *(char *) mc_run_param0 != '\0')
+ vpath = prepend_cwd_on_local ((char *) mc_run_param0);
+
+ ret = view_file (vpath, FALSE, TRUE);
+ vfs_path_free (vpath, TRUE);
+ break;
+ }
+#ifdef USE_DIFF_VIEW
+ case MC_RUN_DIFFVIEWER:
+ ret = dview_diff_cmd (mc_run_param0, mc_run_param1);
+ break;
+#endif /* USE_DIFF_VIEW */
+ default:
+ ret = FALSE;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+show_editor_viewer_history (void)
+{
+ char *s;
+ int act;
+
+ s = show_file_history (WIDGET (filemanager), &act);
+ if (s != NULL)
+ {
+ vfs_path_t *s_vpath;
+
+ switch (act)
+ {
+ case CK_Edit:
+ s_vpath = vfs_path_from_str (s);
+ edit_file_at_line (s_vpath, use_internal_edit, 0);
+ break;
+
+ case CK_View:
+ s_vpath = vfs_path_from_str (s);
+ view_file (s_vpath, use_internal_view, FALSE);
+ break;
+
+ default:
+ {
+ char *d;
+
+ d = g_path_get_dirname (s);
+ s_vpath = vfs_path_from_str (d);
+ panel_cd (current_panel, s_vpath, cd_exact);
+ panel_set_current_by_name (current_panel, s);
+ g_free (d);
+ }
+ }
+
+ g_free (s);
+ vfs_path_free (s_vpath, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+quit_cmd_internal (int quiet)
+{
+ int q = quit;
+ size_t n;
+
+ n = dialog_switch_num () - 1;
+ if (n != 0)
+ {
+ char msg[BUF_MEDIUM];
+
+ g_snprintf (msg, sizeof (msg),
+ ngettext ("You have %zu opened screen. Quit anyway?",
+ "You have %zu opened screens. Quit anyway?", n), n);
+
+ if (query_dialog (_("The Midnight Commander"), msg, D_NORMAL, 2, _("&Yes"), _("&No")) != 0)
+ return FALSE;
+ q = 1;
+ }
+ else if (quiet || !confirm_exit)
+ q = 1;
+ else if (query_dialog (_("The Midnight Commander"),
+ _("Do you really want to quit the Midnight Commander?"),
+ D_NORMAL, 2, _("&Yes"), _("&No")) == 0)
+ q = 1;
+
+ if (q != 0)
+ {
+#ifdef ENABLE_SUBSHELL
+ if (!mc_global.tty.use_subshell)
+ stop_dialogs ();
+ else if ((q = exit_subshell ()? 1 : 0) != 0)
+#endif
+ stop_dialogs ();
+ }
+
+ if (q != 0)
+ quit |= 1;
+ return (quit != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+quit_cmd (void)
+{
+ return quit_cmd_internal (0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Repaint the contents of the panels without frames. To schedule panel
+ * for repainting, set panel->dirty to TRUE. There are many reasons why
+ * the panels need to be repainted, and this is a costly operation, so
+ * it's done once per event.
+ */
+
+static void
+update_dirty_panels (void)
+{
+ if (get_current_type () == view_listing && current_panel->dirty)
+ widget_draw (WIDGET (current_panel));
+
+ if (get_other_type () == view_listing && other_panel->dirty)
+ widget_draw (WIDGET (other_panel));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+toggle_show_hidden (void)
+{
+ panels_options.show_dot_files = !panels_options.show_dot_files;
+ update_panels (UP_RELOAD, UP_KEEPSEL);
+ /* redraw panels forced */
+ update_dirty_panels ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+midnight_execute_cmd (Widget * sender, long command)
+{
+ cb_ret_t res = MSG_HANDLED;
+
+ (void) sender;
+
+ /* stop quick search before executing any command */
+ send_message (current_panel, NULL, MSG_ACTION, CK_SearchStop, NULL);
+
+ switch (command)
+ {
+ case CK_ChangePanel:
+ (void) change_panel ();
+ break;
+ case CK_HotListAdd:
+ add2hotlist_cmd (current_panel);
+ break;
+ case CK_SetupListingFormat:
+ setup_listing_format_cmd ();
+ break;
+ case CK_ChangeMode:
+ chmod_cmd (current_panel);
+ break;
+ case CK_ChangeOwn:
+ chown_cmd (current_panel);
+ break;
+ case CK_ChangeOwnAdvanced:
+ advanced_chown_cmd (current_panel);
+ break;
+#ifdef ENABLE_EXT2FS_ATTR
+ case CK_ChangeAttributes:
+ chattr_cmd (current_panel);
+ break;
+#endif
+ case CK_CompareDirs:
+ compare_dirs_cmd ();
+ break;
+ case CK_Options:
+ configure_box ();
+ break;
+#ifdef ENABLE_VFS
+ case CK_OptionsVfs:
+ configure_vfs_box ();
+ break;
+#endif
+ case CK_OptionsConfirm:
+ confirm_box ();
+ break;
+ case CK_Copy:
+ copy_cmd (current_panel);
+ break;
+ case CK_PutCurrentPath:
+ midnight_put_panel_path (current_panel);
+ break;
+ case CK_PutCurrentSelected:
+ put_current_selected ();
+ break;
+ case CK_PutCurrentFullSelected:
+ midnight_put_panel_path (current_panel);
+ put_current_selected ();
+ break;
+ case CK_PutCurrentLink:
+ put_current_link ();
+ break;
+ case CK_PutCurrentTagged:
+ put_current_tagged ();
+ break;
+ case CK_PutOtherPath:
+ if (get_other_type () == view_listing)
+ midnight_put_panel_path (other_panel);
+ break;
+ case CK_PutOtherLink:
+ put_other_link ();
+ break;
+ case CK_PutOtherTagged:
+ put_other_tagged ();
+ break;
+ case CK_Delete:
+ delete_cmd (current_panel);
+ break;
+ case CK_ScreenList:
+ dialog_switch_list ();
+ break;
+#ifdef USE_DIFF_VIEW
+ case CK_CompareFiles:
+ diff_view_cmd ();
+ break;
+#endif
+ case CK_OptionsDisplayBits:
+ display_bits_box ();
+ break;
+ case CK_Edit:
+ edit_cmd (current_panel);
+ break;
+#ifdef USE_INTERNAL_EDIT
+ case CK_EditForceInternal:
+ edit_cmd_force_internal (current_panel);
+ break;
+#endif
+ case CK_EditExtensionsFile:
+ ext_cmd ();
+ break;
+ case CK_EditFileHighlightFile:
+ edit_fhl_cmd ();
+ break;
+ case CK_EditUserMenu:
+ edit_mc_menu_cmd ();
+ break;
+ case CK_LinkSymbolicEdit:
+ edit_symlink_cmd ();
+ break;
+ case CK_ExternalPanelize:
+ external_panelize_cmd ();
+ break;
+ case CK_ViewFiltered:
+ view_filtered_cmd (current_panel);
+ break;
+ case CK_Find:
+ find_cmd (current_panel);
+ break;
+#ifdef ENABLE_VFS_FISH
+ case CK_ConnectFish:
+ fishlink_cmd ();
+ break;
+#endif
+#ifdef ENABLE_VFS_FTP
+ case CK_ConnectFtp:
+ ftplink_cmd ();
+ break;
+#endif
+#ifdef ENABLE_VFS_SFTP
+ case CK_ConnectSftp:
+ sftplink_cmd ();
+ break;
+#endif
+ case CK_Panelize:
+ panel_panelize_cd ();
+ break;
+ case CK_Help:
+ help_cmd ();
+ break;
+ case CK_History:
+ /* show the history of command line widget */
+ send_message (cmdline, NULL, MSG_ACTION, CK_History, NULL);
+ break;
+ case CK_PanelInfo:
+ if (sender == WIDGET (the_menubar))
+ info_cmd (); /* menu */
+ else
+ info_cmd_no_menu (); /* shortcut or buttonbar */
+ break;
+#ifdef ENABLE_BACKGROUND
+ case CK_Jobs:
+ jobs_box ();
+ break;
+#endif
+ case CK_OptionsLayout:
+ layout_box ();
+ break;
+ case CK_OptionsAppearance:
+ appearance_box ();
+ break;
+ case CK_LearnKeys:
+ learn_keys ();
+ break;
+ case CK_Link:
+ link_cmd (LINK_HARDLINK);
+ break;
+ case CK_PanelListing:
+ listing_cmd ();
+ break;
+#ifdef LISTMODE_EDITOR
+ case CK_ListMode:
+ listmode_cmd ();
+ break;
+#endif
+ case CK_Menu:
+ menu_cmd ();
+ break;
+ case CK_MenuLastSelected:
+ menu_last_selected_cmd ();
+ break;
+ case CK_MakeDir:
+ mkdir_cmd (current_panel);
+ break;
+ case CK_OptionsPanel:
+ panel_options_box ();
+ break;
+#ifdef HAVE_CHARSET
+ case CK_SelectCodepage:
+ encoding_cmd ();
+ break;
+#endif
+ case CK_CdQuick:
+ quick_cd_cmd (current_panel);
+ break;
+ case CK_HotList:
+ hotlist_cmd (current_panel);
+ break;
+ case CK_PanelQuickView:
+ if (sender == WIDGET (the_menubar))
+ quick_view_cmd (); /* menu */
+ else
+ quick_cmd_no_menu (); /* shortcut or buttonabr */
+ break;
+ case CK_QuitQuiet:
+ quiet_quit_cmd ();
+ break;
+ case CK_Quit:
+ quit_cmd ();
+ break;
+ case CK_LinkSymbolicRelative:
+ link_cmd (LINK_SYMLINK_RELATIVE);
+ break;
+ case CK_Move:
+ rename_cmd (current_panel);
+ break;
+ case CK_Reread:
+ reread_cmd ();
+ break;
+#ifdef ENABLE_VFS
+ case CK_VfsList:
+ vfs_list (current_panel);
+ break;
+#endif
+ case CK_SaveSetup:
+ save_setup_cmd ();
+ break;
+ case CK_Select:
+ case CK_Unselect:
+ case CK_SelectInvert:
+ case CK_Filter:
+ res = send_message (current_panel, filemanager, MSG_ACTION, command, NULL);
+ break;
+ case CK_Shell:
+ toggle_subshell ();
+ break;
+ case CK_DirSize:
+ smart_dirsize_cmd (current_panel);
+ break;
+ case CK_Sort:
+ sort_cmd ();
+ break;
+ case CK_ExtendedKeyMap:
+ WIDGET (filemanager)->ext_mode = TRUE;
+ break;
+ case CK_Suspend:
+ mc_event_raise (MCEVENT_GROUP_CORE, "suspend", NULL);
+ break;
+ case CK_Swap:
+ swap_cmd ();
+ break;
+ case CK_LinkSymbolic:
+ link_cmd (LINK_SYMLINK_ABSOLUTE);
+ break;
+ case CK_ShowHidden:
+ toggle_show_hidden ();
+ break;
+ case CK_SplitVertHoriz:
+ toggle_panels_split ();
+ break;
+ case CK_SplitEqual:
+ panels_split_equal ();
+ break;
+ case CK_SplitMore:
+ panels_split_more ();
+ break;
+ case CK_SplitLess:
+ panels_split_less ();
+ break;
+ case CK_PanelTree:
+ panel_tree_cmd ();
+ break;
+ case CK_Tree:
+ treebox_cmd ();
+ break;
+#ifdef ENABLE_VFS_UNDELFS
+ case CK_Undelete:
+ undelete_cmd ();
+ break;
+#endif
+ case CK_UserMenu:
+ user_file_menu_cmd ();
+ break;
+ case CK_View:
+ view_cmd (current_panel);
+ break;
+ case CK_ViewFile:
+ view_file_cmd (current_panel);
+ break;
+ case CK_EditorViewerHistory:
+ show_editor_viewer_history ();
+ break;
+ case CK_Cancel:
+ /* don't close panels due to SIGINT */
+ break;
+ default:
+ res = MSG_NOT_HANDLED;
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Whether the command-line should not respond to key events.
+ *
+ * This is TRUE if a QuickView or TreeView have the focus, as they're going
+ * to consume some keys and there's no sense in passing to the command-line
+ * just the leftovers.
+ */
+static gboolean
+is_cmdline_mute (void)
+{
+ /* When one of panels is other than view_listing,
+ current_panel points to view_listing panel all time independently of
+ it's activity. Thus, we can't use get_current_type() here.
+ current_panel should point to actually current active panel
+ independently of it's type. */
+ return (!current_panel->active
+ && (get_other_type () == view_quick || get_other_type () == view_tree));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Handles the Enter key on the command-line.
+ *
+ * Returns TRUE if non-whitespace was indeed processed.
+ */
+static gboolean
+handle_cmdline_enter (void)
+{
+ const char *s;
+
+ for (s = input_get_ctext (cmdline); *s != '\0' && whitespace (*s); s++)
+ ;
+
+ if (*s != '\0')
+ {
+ send_message (cmdline, NULL, MSG_KEY, '\n', NULL);
+ return TRUE;
+ }
+
+ input_insert (cmdline, "", FALSE);
+ cmdline->point = 0;
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+midnight_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ long command;
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ panel_init ();
+ setup_panels ();
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ load_hint (TRUE);
+ group_default_callback (w, NULL, MSG_DRAW, 0, NULL);
+ /* We handle the special case of the output lines */
+ if (mc_global.tty.console_flag != '\0' && output_lines != 0)
+ {
+ unsigned char end_line;
+
+ end_line = LINES - (mc_global.keybar_visible ? 1 : 0) - 1;
+ show_console_contents (output_start_y, end_line - output_lines, end_line);
+ }
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ widget_adjust_position (w->pos_flags, &w->rect);
+ setup_panels ();
+ menubar_arrange (the_menubar);
+ return MSG_HANDLED;
+
+ case MSG_IDLE:
+ /* We only need the first idle event to show user menu after start */
+ widget_idle (w, FALSE);
+
+ if (boot_current_is_left)
+ widget_select (get_panel_widget (0));
+ else
+ widget_select (get_panel_widget (1));
+
+ if (auto_menu)
+ midnight_execute_cmd (NULL, CK_UserMenu);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ if (w->ext_mode)
+ {
+ command = widget_lookup_key (w, parm);
+ if (command != CK_IgnoreKey)
+ return midnight_execute_cmd (NULL, command);
+ }
+
+ /* FIXME: should handle all menu shortcuts before this point */
+ if (widget_get_state (WIDGET (the_menubar), WST_FOCUSED))
+ return MSG_NOT_HANDLED;
+
+ if (parm == '\n' && !is_cmdline_mute ())
+ {
+ if (handle_cmdline_enter ())
+ return MSG_HANDLED;
+ /* Else: the panel will handle it. */
+ }
+
+ if ((!mc_global.tty.alternate_plus_minus
+ || !(mc_global.tty.console_flag != '\0' || mc_global.tty.xterm_flag)) && !quote
+ && !current_panel->quick_search.active)
+ {
+ if (!only_leading_plus_minus)
+ {
+ /* Special treatment, since the input line will eat them */
+ if (parm == '+')
+ return send_message (current_panel, filemanager, MSG_ACTION, CK_Select, NULL);
+
+ if (parm == '\\' || parm == '-')
+ return send_message (current_panel, filemanager, MSG_ACTION, CK_Unselect, NULL);
+
+ if (parm == '*')
+ return send_message (current_panel, filemanager, MSG_ACTION, CK_SelectInvert,
+ NULL);
+ }
+ else if (!command_prompt || input_is_empty (cmdline))
+ {
+ /* Special treatment '+', '-', '\', '*' only when this is
+ * first char on input line
+ */
+ if (parm == '+')
+ return send_message (current_panel, filemanager, MSG_ACTION, CK_Select, NULL);
+
+ if (parm == '\\' || parm == '-')
+ return send_message (current_panel, filemanager, MSG_ACTION, CK_Unselect, NULL);
+
+ if (parm == '*')
+ return send_message (current_panel, filemanager, MSG_ACTION, CK_SelectInvert,
+ NULL);
+ }
+ }
+ return MSG_NOT_HANDLED;
+
+ case MSG_HOTKEY_HANDLED:
+ if ((get_current_type () == view_listing) && current_panel->quick_search.active)
+ {
+ current_panel->dirty = TRUE; /* FIXME: unneeded? */
+ send_message (current_panel, NULL, MSG_ACTION, CK_SearchStop, NULL);
+ }
+ return MSG_HANDLED;
+
+ case MSG_UNHANDLED_KEY:
+ {
+ cb_ret_t v = MSG_NOT_HANDLED;
+
+ command = widget_lookup_key (w, parm);
+ if (command != CK_IgnoreKey)
+ v = midnight_execute_cmd (NULL, command);
+
+ if (v == MSG_NOT_HANDLED && command_prompt && !is_cmdline_mute ())
+ v = send_message (cmdline, NULL, MSG_KEY, parm, NULL);
+
+ return v;
+ }
+
+ case MSG_POST_KEY:
+ if (!widget_get_state (WIDGET (the_menubar), WST_FOCUSED))
+ update_dirty_panels ();
+ return MSG_HANDLED;
+
+ case MSG_ACTION:
+ /* Handle shortcuts, menu, and buttonbar. */
+ return midnight_execute_cmd (sender, parm);
+
+ case MSG_DESTROY:
+ panel_deinit ();
+ return MSG_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+update_menu (void)
+{
+ menu_set_name (left_menu, panels_layout.horizontal_split ? _("&Above") : _("&Left"));
+ menu_set_name (right_menu, panels_layout.horizontal_split ? _("&Below") : _("&Right"));
+ menubar_arrange (the_menubar);
+ widget_set_visibility (WIDGET (the_menubar), menubar_visible);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+midnight_set_buttonbar (WButtonBar * b)
+{
+ Widget *w = WIDGET (filemanager);
+
+ buttonbar_set_label (b, 1, Q_ ("ButtonBar|Help"), w->keymap, NULL);
+ buttonbar_set_label (b, 2, Q_ ("ButtonBar|Menu"), w->keymap, NULL);
+ buttonbar_set_label (b, 3, Q_ ("ButtonBar|View"), w->keymap, NULL);
+ buttonbar_set_label (b, 4, Q_ ("ButtonBar|Edit"), w->keymap, NULL);
+ buttonbar_set_label (b, 5, Q_ ("ButtonBar|Copy"), w->keymap, NULL);
+ buttonbar_set_label (b, 6, Q_ ("ButtonBar|RenMov"), w->keymap, NULL);
+ buttonbar_set_label (b, 7, Q_ ("ButtonBar|Mkdir"), w->keymap, NULL);
+ buttonbar_set_label (b, 8, Q_ ("ButtonBar|Delete"), w->keymap, NULL);
+ buttonbar_set_label (b, 9, Q_ ("ButtonBar|PullDn"), w->keymap, NULL);
+ buttonbar_set_label (b, 10, Q_ ("ButtonBar|Quit"), w->keymap, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Return a random hint. If force is TRUE, ignore the timeout.
+ */
+
+char *
+get_random_hint (gboolean force)
+{
+ static const gint64 update_period = 60 * G_USEC_PER_SEC;
+ static gint64 tv = 0;
+
+ char *data, *result, *eop;
+ size_t len, start;
+ GIConv conv;
+
+ /* Do not change hints more often than one minute */
+ if (!force && !mc_time_elapsed (&tv, update_period))
+ return g_strdup ("");
+
+ data = load_mc_home_file (mc_global.share_data_dir, MC_HINT, NULL, &len);
+ if (data == NULL)
+ return NULL;
+
+ /* get a random entry */
+ srand ((unsigned int) (tv / G_USEC_PER_SEC));
+ start = ((size_t) rand ()) % (len - 1);
+
+ /* Search the start of paragraph */
+ for (; start != 0; start--)
+ if (data[start] == '\n' && data[start + 1] == '\n')
+ {
+ start += 2;
+ break;
+ }
+
+ /* Search the end of paragraph */
+ for (eop = data + start; *eop != '\0'; eop++)
+ {
+ if (*eop == '\n' && *(eop + 1) == '\n')
+ {
+ *eop = '\0';
+ break;
+ }
+ if (*eop == '\n')
+ *eop = ' ';
+ }
+
+ /* hint files are stored in utf-8 */
+ /* try convert hint file from utf-8 to terminal encoding */
+ conv = str_crt_conv_from ("UTF-8");
+ if (conv == INVALID_CONV)
+ result = g_strndup (data + start, len - start);
+ else
+ {
+ GString *buffer;
+ gboolean nok;
+
+ buffer = g_string_sized_new (len - start);
+ nok = (str_convert (conv, data + start, buffer) == ESTR_FAILURE);
+ result = g_string_free (buffer, nok);
+ str_close_conv (conv);
+ }
+
+ g_free (data);
+ return result;
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load new hint and display it.
+ * IF force is not 0, ignore the timeout.
+ */
+
+void
+load_hint (gboolean force)
+{
+ char *hint;
+
+ if (WIDGET (the_hint)->owner == NULL)
+ return;
+
+ if (!mc_global.message_visible)
+ {
+ label_set_text (the_hint, NULL);
+ return;
+ }
+
+ hint = get_random_hint (force);
+
+ if (hint != NULL)
+ {
+ if (*hint != '\0')
+ set_hintbar (hint);
+ g_free (hint);
+ }
+ else
+ {
+ char text[BUF_SMALL];
+
+ g_snprintf (text, sizeof (text), _("GNU Midnight Commander %s\n"), mc_global.mc_version);
+ set_hintbar (text);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Change current panel in the file manager.
+ *
+ * @return current_panel
+ */
+
+WPanel *
+change_panel (void)
+{
+ input_complete_free (cmdline);
+ group_select_next_widget (GROUP (filemanager));
+ return current_panel;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Save current stat of directories to avoid reloading the panels
+ * when no modifications have taken place
+ */
+void
+save_cwds_stat (void)
+{
+ if (panels_options.fast_reload)
+ {
+ mc_stat (current_panel->cwd_vpath, &(current_panel->dir_stat));
+ if (get_other_type () == view_listing)
+ mc_stat (other_panel->cwd_vpath, &(other_panel->dir_stat));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+quiet_quit_cmd (void)
+{
+ print_last_revert = TRUE;
+ return quit_cmd_internal (1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Run the main dialog that occupies the whole screen */
+gboolean
+do_nc (void)
+{
+ gboolean ret;
+
+#ifdef USE_INTERNAL_EDIT
+ edit_stack_init ();
+#endif
+
+ filemanager = dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, dialog_colors,
+ midnight_callback, NULL, "[main]", NULL);
+
+ /* Check if we were invoked as an editor or file viewer */
+ if (mc_global.mc_run_mode != MC_RUN_FULL)
+ {
+ setup_dummy_mc ();
+ ret = mc_maybe_editor_or_viewer ();
+ }
+ else
+ {
+ /* We only need the first idle event to show user menu after start */
+ widget_idle (WIDGET (filemanager), TRUE);
+
+ setup_mc ();
+ mc_filehighlight = mc_fhl_new (TRUE);
+
+ create_file_manager ();
+ (void) dlg_run (filemanager);
+
+ mc_fhl_free (&mc_filehighlight);
+
+ ret = TRUE;
+
+ /* widget_destroy destroys even current_panel->cwd_vpath, so we have to save a copy :) */
+ if (mc_args__last_wd_file != NULL && vfs_current_is_local ())
+ last_wd_string = g_strdup (vfs_path_as_str (current_panel->cwd_vpath));
+
+ /* don't handle VFS timestamps for dirs opened in panels */
+ mc_event_destroy (MCEVENT_GROUP_CORE, "vfs_timestamp");
+ }
+
+ /* Program end */
+ mc_global.midnight_shutdown = TRUE;
+ dialog_switch_shutdown ();
+ done_mc ();
+ widget_destroy (WIDGET (filemanager));
+ current_panel = NULL;
+
+#ifdef USE_INTERNAL_EDIT
+ edit_stack_free ();
+#endif
+
+ if ((quit & SUBSHELL_EXIT) == 0)
+ tty_clear_screen ();
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/filemanager.h b/src/filemanager/filemanager.h
new file mode 100644
index 0000000..ca607ce
--- /dev/null
+++ b/src/filemanager/filemanager.h
@@ -0,0 +1,53 @@
+/** \file filemanager.h
+ * \brief Header: main dialog (file panels) for Midnight Commander
+ */
+
+#ifndef MC__FILEMANAGER_H
+#define MC__FILEMANAGER_H
+
+#include "lib/widget.h"
+
+#include "panel.h"
+#include "layout.h"
+
+/* TODO: merge content of layout.h here */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define MENU_PANEL (mc_global.widget.is_right ? right_panel : left_panel)
+#define MENU_PANEL_IDX (mc_global.widget.is_right ? 1 : 0)
+#define SELECTED_IS_PANEL (get_panel_type (MENU_PANEL_IDX) == view_listing)
+
+#define other_panel get_other_panel()
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern WMenuBar *the_menubar;
+extern WLabel *the_prompt;
+extern WLabel *the_hint;
+extern WButtonBar *the_bar;
+
+extern WPanel *left_panel;
+extern WPanel *right_panel;
+extern WPanel *current_panel;
+
+extern const char *mc_prompt;
+
+/*** declarations of public functions ************************************************************/
+
+void update_menu (void);
+void midnight_set_buttonbar (WButtonBar * b);
+char *get_random_hint (gboolean force);
+void load_hint (gboolean force);
+WPanel *change_panel (void);
+void save_cwds_stat (void);
+gboolean quiet_quit_cmd (void);
+gboolean do_nc (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__FILEMANAGER_H */
diff --git a/src/filemanager/filenot.c b/src/filemanager/filenot.c
new file mode 100644
index 0000000..2bfc76a
--- /dev/null
+++ b/src/filemanager/filenot.c
@@ -0,0 +1,150 @@
+/*
+ Wrapper for routines to notify the
+ tree about the changes made to the directory
+ structure.
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Author:
+ Janne Kukonlehto
+ Miguel de Icaza
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ 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 filenot.c
+ * \brief Source: wrapper for routines to notify the
+ * tree about the changes made to the directory
+ * structure.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/fs.h"
+#include "lib/util.h"
+#include "lib/vfs/vfs.h"
+
+#include "filenot.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+get_absolute_name (const vfs_path_t * vpath)
+{
+ if (vpath == NULL)
+ return NULL;
+
+ if (IS_PATH_SEP (*vfs_path_get_by_index (vpath, 0)->path))
+ return vfs_path_clone (vpath);
+
+ return vfs_path_append_vpath_new (vfs_get_raw_current_dir (), vpath, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+my_mkdir_rec (const vfs_path_t * vpath, mode_t mode)
+{
+ vfs_path_t *q;
+ int result;
+
+ if (mc_mkdir (vpath, mode) == 0)
+ return 0;
+ if (errno != ENOENT)
+ return (-1);
+
+ /* FIXME: should check instead if vpath is at the root of that filesystem */
+ if (!vfs_file_is_local (vpath))
+ return (-1);
+
+ if (strcmp (vfs_path_as_str (vpath), PATH_SEP_STR) == 0)
+ {
+ errno = ENOTDIR;
+ return (-1);
+ }
+
+ q = vfs_path_append_new (vpath, "..", (char *) NULL);
+ result = my_mkdir_rec (q, mode);
+ vfs_path_free (q, TRUE);
+
+ if (result == 0)
+ result = mc_mkdir (vpath, mode);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+my_mkdir (const vfs_path_t * vpath, mode_t mode)
+{
+ int result;
+
+ result = my_mkdir_rec (vpath, mode);
+ if (result == 0)
+ {
+ vfs_path_t *my_s;
+
+ my_s = get_absolute_name (vpath);
+ vfs_path_free (my_s, TRUE);
+ }
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+my_rmdir (const char *path)
+{
+ int result;
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str_flags (path, VPF_NO_CANON);
+ /* FIXME: Should receive a Wtree! */
+ result = mc_rmdir (vpath);
+ if (result == 0)
+ {
+ vfs_path_t *my_s;
+
+ my_s = get_absolute_name (vpath);
+ vfs_path_free (my_s, TRUE);
+ }
+ vfs_path_free (vpath, TRUE);
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/filenot.h b/src/filemanager/filenot.h
new file mode 100644
index 0000000..33991e8
--- /dev/null
+++ b/src/filemanager/filenot.h
@@ -0,0 +1,26 @@
+/** \file file.h
+ * \brief Header: File and directory operation routines
+ */
+
+#ifndef MC__FILENOT_H
+#define MC__FILENOT_H
+
+#include "lib/global.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* Misc Unix functions */
+int my_mkdir (const vfs_path_t * vpath, mode_t mode);
+int my_rmdir (const char *path);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__FILE_H */
diff --git a/src/filemanager/fileopctx.c b/src/filemanager/fileopctx.c
new file mode 100644
index 0000000..a118749
--- /dev/null
+++ b/src/filemanager/fileopctx.c
@@ -0,0 +1,128 @@
+/*
+ File operation contexts for the Midnight Commander
+
+ Copyright (C) 1999-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Federico Mena <federico@nuclecu.unam.mx>
+ Miguel de Icaza <miguel@nuclecu.unam.mx>
+
+ 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 fileopctx.c
+ * \brief Source: file operation contexts
+ * \date 1998-2007
+ * \author Federico Mena <federico@nuclecu.unam.mx>
+ * \author Miguel de Icaza <miguel@nuclecu.unam.mx>
+ */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "fileopctx.h"
+#include "filegui.h"
+#include "lib/search.h"
+#include "lib/vfs/vfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * \fn file_op_context_t * file_op_context_new (FileOperation op)
+ * \param op file operation struct
+ * \return The newly-created context, filled with the default file mask values.
+ *
+ * Creates a new file operation context with the default values. If you later want
+ * to have a user interface for this, call file_op_context_create_ui().
+ */
+
+file_op_context_t *
+file_op_context_new (FileOperation op)
+{
+ file_op_context_t *ctx;
+
+ ctx = g_new0 (file_op_context_t, 1);
+ ctx->operation = op;
+ ctx->eta_secs = 0.0;
+ ctx->progress_bytes = 0;
+ ctx->op_preserve = TRUE;
+ ctx->do_reget = 1;
+ ctx->stat_func = mc_lstat;
+ ctx->preserve = TRUE;
+ ctx->preserve_uidgid = (geteuid () == 0);
+ ctx->umask_kill = 0777777;
+ ctx->erase_at_end = TRUE;
+ ctx->skip_all = FALSE;
+
+ return ctx;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * \fn void file_op_context_destroy (file_op_context_t *ctx)
+ * \param ctx The file operation context to destroy.
+ *
+ * Destroys the specified file operation context and its associated UI data, if
+ * it exists.
+ */
+
+void
+file_op_context_destroy (file_op_context_t * ctx)
+{
+ if (ctx != NULL)
+ {
+ file_op_context_destroy_ui (ctx);
+ mc_search_free (ctx->search_handle);
+ g_free (ctx);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+file_op_total_context_t *
+file_op_total_context_new (void)
+{
+ file_op_total_context_t *tctx;
+ tctx = g_new0 (file_op_total_context_t, 1);
+ tctx->ask_overwrite = TRUE;
+ return tctx;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_op_total_context_destroy (file_op_total_context_t * tctx)
+{
+ g_free (tctx);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/fileopctx.h b/src/filemanager/fileopctx.h
new file mode 100644
index 0000000..ed0b5d6
--- /dev/null
+++ b/src/filemanager/fileopctx.h
@@ -0,0 +1,198 @@
+/** \file fileopctx.h
+ * \brief Header: file operation contexts
+ * \date 1998
+ * \author Federico Mena <federico@nuclecu.unam.mx>
+ * \author Miguel de Icaza <miguel@nuclecu.unam.mx>
+ */
+
+#ifndef MC__FILEOPCTX_H
+#define MC__FILEOPCTX_H
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+#include "lib/vfs/vfs.h"
+
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+typedef int (*mc_stat_fn) (const vfs_path_t * vpath, struct stat * buf);
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ FILEGUI_DIALOG_ONE_ITEM,
+ FILEGUI_DIALOG_MULTI_ITEM,
+ FILEGUI_DIALOG_DELETE_ITEM
+} filegui_dialog_type_t;
+
+typedef enum
+{
+ OP_COPY = 0,
+ OP_MOVE = 1,
+ OP_DELETE = 2
+} FileOperation;
+
+typedef enum
+{
+ RECURSIVE_YES = 0,
+ RECURSIVE_NO = 1,
+ RECURSIVE_ALWAYS = 2,
+ RECURSIVE_NEVER = 3,
+ RECURSIVE_ABORT = 4
+} FileCopyMode;
+
+/* ATTENTION: avoid overlapping with B_* values (lib/widget/dialog.h) */
+typedef enum
+{
+ FILE_CONT = 10,
+ FILE_RETRY,
+ FILE_SKIP,
+ FILE_ABORT,
+ FILE_SKIPALL,
+ FILE_SUSPEND
+} FileProgressStatus;
+
+/* First argument passed to real functions */
+enum OperationMode
+{
+ Foreground,
+ Background
+};
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct mc_search_struct;
+
+/* This structure describes a context for file operations. It is used to update
+ * the progress windows and pass around options.
+ */
+typedef struct
+{
+ /* Operation type (copy, move, delete) */
+ FileOperation operation;
+
+ /* The estimated time of arrival in seconds */
+ double eta_secs;
+
+ /* Transferred bytes per second */
+ long bps;
+
+ /* Transferred seconds */
+ long bps_time;
+
+ /* Whether the panel total has been computed */
+ gboolean progress_totals_computed;
+ filegui_dialog_type_t dialog_type;
+
+ /* Counters for progress indicators */
+ size_t progress_count;
+ uintmax_t progress_bytes;
+
+ /* The value of the "preserve Attributes" checkbox in the copy file dialog.
+ * We can't use the value of "ctx->preserve" because it can change in order
+ * to preserve file attributes when moving files across filesystem boundaries
+ * (we want to keep the value of the checkbox between copy operations).
+ */
+ gboolean op_preserve;
+
+ /* Result from the recursive query */
+ FileCopyMode recursive_result;
+
+ /* Whether to do a reget */
+ off_t do_reget;
+
+ /* Controls appending to files */
+ gboolean do_append;
+
+ /* Whether to stat or lstat */
+ gboolean follow_links;
+
+ /* Pointer to the stat function we will use */
+ mc_stat_fn stat_func;
+
+ /* Whether to recompute symlinks */
+ gboolean stable_symlinks;
+
+ /* Preserve the original files' owner, group, permissions, and
+ * timestamps (owner, group only as root).
+ */
+ gboolean preserve;
+
+ /* If running as root, preserve the original uid/gid (we don't want to
+ * try chown for non root) preserve_uidgid = preserve && uid == 0
+ */
+ gboolean preserve_uidgid;
+
+ /* The bits to preserve in created files' modes on file copy */
+ mode_t umask_kill;
+
+ /* The mask of files to actually operate on */
+ char *dest_mask;
+
+ /* search handler */
+ struct mc_search_struct *search_handle;
+
+ /* Whether to dive into subdirectories for recursive operations */
+ gboolean dive_into_subdirs;
+
+ /* When moving directories cross filesystem boundaries delete the
+ * successfully copied files when all files below the directory and its
+ * subdirectories were processed.
+ *
+ * If erase_at_end is FALSE files will be deleted immediately after their
+ * successful copy (Note: this behavior is not tested and at the moment
+ * it can't be changed at runtime).
+ */
+ gboolean erase_at_end;
+
+ /* PID of the child for background operations */
+ pid_t pid;
+
+ /* toggle if all errors should be ignored */
+ gboolean skip_all;
+
+ /* Whether the file operation is in pause */
+ gboolean suspended;
+
+ /* User interface data goes here */
+ void *ui;
+} file_op_context_t;
+
+typedef struct
+{
+ size_t progress_count;
+ size_t prev_progress_count; /* Used in OP_MOVE between copy and remove directories */
+ uintmax_t progress_bytes;
+ uintmax_t copied_bytes;
+ size_t bps;
+ size_t bps_count;
+ gint64 transfer_start;
+ double eta_secs;
+
+ gboolean ask_overwrite;
+} file_op_total_context_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern const char *op_names[3];
+
+/*** declarations of public functions ************************************************************/
+
+file_op_context_t *file_op_context_new (FileOperation op);
+void file_op_context_destroy (file_op_context_t * ctx);
+
+file_op_total_context_t *file_op_total_context_new (void);
+void file_op_total_context_destroy (file_op_total_context_t * tctx);
+
+/* The following functions are implemented separately by each port */
+FileProgressStatus file_progress_real_query_replace (file_op_context_t * ctx,
+ enum OperationMode mode, const char *src,
+ struct stat *src_stat, const char *dst,
+ struct stat *dst_stat);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__FILEOPCTX_H */
diff --git a/src/filemanager/find.c b/src/filemanager/find.c
new file mode 100644
index 0000000..c0d2cf9
--- /dev/null
+++ b/src/filemanager/find.c
@@ -0,0 +1,1968 @@
+/*
+ Find file command for the Midnight Commander
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1995
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2022
+
+ 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 find.c
+ * \brief Source: Find file command
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h"
+#include "lib/skin.h"
+#include "lib/search.h"
+#include "lib/mcconfig.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/widget.h"
+#include "lib/util.h" /* canonicalize_pathname() */
+
+#include "src/setup.h" /* verbose */
+#include "src/history.h" /* MC_HISTORY_SHARED_SEARCH */
+
+#include "dir.h"
+#include "cmd.h" /* find_cmd(), view_file_at_line() */
+#include "boxes.h"
+#include "panelize.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define MAX_REFRESH_INTERVAL (G_USEC_PER_SEC / 20) /* 50 ms */
+#define MIN_REFRESH_FILE_SIZE (256 * 1024) /* 256 KB */
+
+/*** file scope type declarations ****************************************************************/
+
+/* A couple of extra messages we need */
+enum
+{
+ B_STOP = B_USER + 1,
+ B_AGAIN,
+ B_PANELIZE,
+ B_TREE,
+ B_VIEW
+};
+
+typedef enum
+{
+ FIND_CONT = 0,
+ FIND_SUSPEND,
+ FIND_ABORT
+} FindProgressStatus;
+
+/* find file options */
+typedef struct
+{
+ /* file name options */
+ gboolean file_case_sens;
+ gboolean file_pattern;
+ gboolean find_recurs;
+ gboolean follow_symlinks;
+ gboolean skip_hidden;
+ gboolean file_all_charsets;
+
+ /* file content options */
+ gboolean content_case_sens;
+ gboolean content_regexp;
+ gboolean content_first_hit;
+ gboolean content_whole_words;
+ gboolean content_all_charsets;
+
+ /* whether use ignore dirs or not */
+ gboolean ignore_dirs_enable;
+ /* list of directories to be ignored, separated by ':' */
+ char *ignore_dirs;
+} find_file_options_t;
+
+typedef struct
+{
+ char *dir;
+ gsize start;
+ gsize end;
+} find_match_location_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/* button callbacks */
+static int start_stop (WButton * button, int action);
+static int find_do_view_file (WButton * button, int action);
+static int find_do_edit_file (WButton * button, int action);
+
+/*** file scope variables ************************************************************************/
+
+/* Parsed ignore dirs */
+static char **find_ignore_dirs = NULL;
+
+/* static variables to remember find parameters */
+static WInput *in_start; /* Start path */
+static WInput *in_name; /* Filename */
+static WInput *in_with; /* Text */
+static WInput *in_ignore;
+static WLabel *content_label; /* 'Content:' label */
+static WCheck *file_case_sens_cbox; /* "case sensitive" checkbox */
+static WCheck *file_pattern_cbox; /* File name is glob or regexp */
+static WCheck *recursively_cbox;
+static WCheck *follow_sym_cbox;
+static WCheck *skip_hidden_cbox;
+static WCheck *content_case_sens_cbox; /* "case sensitive" checkbox */
+static WCheck *content_regexp_cbox; /* "find regular expression" checkbox */
+static WCheck *content_first_hit_cbox; /* "First hit" checkbox" */
+static WCheck *content_whole_words_cbox; /* "whole words" checkbox */
+#ifdef HAVE_CHARSET
+static WCheck *file_all_charsets_cbox;
+static WCheck *content_all_charsets_cbox;
+#endif
+static WCheck *ignore_dirs_cbox;
+
+static gboolean running = FALSE; /* nice flag */
+static char *find_pattern = NULL; /* Pattern to search */
+static char *content_pattern = NULL; /* pattern to search inside files; if
+ content_regexp_flag is true, it contains the
+ regex pattern, else the search string. */
+static gboolean content_is_empty = TRUE; /* remember content field state; initially is empty */
+static unsigned long matches; /* Number of matches */
+static gboolean is_start = FALSE; /* Status of the start/stop toggle button */
+static char *old_dir = NULL;
+
+static gint64 last_refresh;
+
+/* Where did we stop */
+static gboolean resuming;
+static int last_line;
+static int last_pos;
+static off_t last_off;
+static int last_i;
+
+static size_t ignore_count = 0;
+
+static WDialog *find_dlg; /* The dialog */
+static WLabel *status_label; /* Finished, Searching etc. */
+static WLabel *found_num_label; /* Number of found items */
+
+/* This keeps track of the directory stack */
+static GQueue dir_queue = G_QUEUE_INIT;
+
+/* *INDENT-OFF* */
+static struct
+{
+ int ret_cmd;
+ button_flags_t flags;
+ const char *text;
+ int len; /* length including space and brackets */
+ int x;
+ Widget *button;
+ bcback_fn callback;
+} fbuts[] =
+{
+ { B_ENTER, DEFPUSH_BUTTON, N_("&Chdir"), 0, 0, NULL, NULL },
+ { B_AGAIN, NORMAL_BUTTON, N_("&Again"), 0, 0, NULL, NULL },
+ { B_STOP, NORMAL_BUTTON, N_("S&uspend"), 0, 0, NULL, start_stop },
+ { B_STOP, NORMAL_BUTTON, N_("Con&tinue"), 0, 0, NULL, NULL },
+ { B_CANCEL, NORMAL_BUTTON, N_("&Quit"), 0, 0, NULL, NULL },
+
+ { B_PANELIZE, NORMAL_BUTTON, N_("Pane&lize"), 0, 0, NULL, NULL },
+ { B_VIEW, NORMAL_BUTTON, N_("&View - F3"), 0, 0, NULL, find_do_view_file },
+ { B_VIEW, NORMAL_BUTTON, N_("&Edit - F4"), 0, 0, NULL, find_do_edit_file }
+};
+/* *INDENT-ON* */
+
+static const size_t fbuts_num = G_N_ELEMENTS (fbuts);
+static const size_t quit_button = 4; /* index of "Quit" button */
+
+static WListbox *find_list; /* Listbox with the file list */
+
+static find_file_options_t options = {
+ TRUE, TRUE, TRUE, FALSE, FALSE, FALSE,
+ TRUE, FALSE, FALSE, FALSE, FALSE,
+ FALSE, NULL
+};
+
+static char *in_start_dir = INPUT_LAST_TEXT;
+
+static mc_search_t *search_file_handle = NULL;
+static mc_search_t *search_content_handle = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* don't use max macro to avoid double str_term_width1() call in widget length calculation */
+#undef max
+
+static int
+max (int a, int b)
+{
+ return (a > b ? a : b);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+parse_ignore_dirs (const char *ignore_dirs)
+{
+ size_t r = 0, w = 0; /* read and write iterators */
+
+ if (!options.ignore_dirs_enable || ignore_dirs == NULL || ignore_dirs[0] == '\0')
+ return;
+
+ find_ignore_dirs = g_strsplit (ignore_dirs, ":", -1);
+
+ /* Values like '/foo::/bar: produce holes in list.
+ * Find and remove them */
+ for (; find_ignore_dirs[r] != NULL; r++)
+ {
+ if (find_ignore_dirs[r][0] == '\0')
+ {
+ /* empty entry -- skip it */
+ MC_PTR_FREE (find_ignore_dirs[r]);
+ continue;
+ }
+
+ if (r != w)
+ {
+ /* copy entry to the previous free array cell */
+ find_ignore_dirs[w] = find_ignore_dirs[r];
+ find_ignore_dirs[r] = NULL;
+ }
+
+ canonicalize_pathname (find_ignore_dirs[w]);
+ if (find_ignore_dirs[w][0] != '\0')
+ w++;
+ else
+ MC_PTR_FREE (find_ignore_dirs[w]);
+ }
+
+ if (find_ignore_dirs[0] == NULL)
+ {
+ g_strfreev (find_ignore_dirs);
+ find_ignore_dirs = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_load_options (void)
+{
+ static gboolean loaded = FALSE;
+
+ if (loaded)
+ return;
+
+ loaded = TRUE;
+
+ options.file_case_sens =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "file_case_sens", TRUE);
+ options.file_pattern =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "file_shell_pattern", TRUE);
+ options.find_recurs =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "file_find_recurs", TRUE);
+ options.follow_symlinks =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "follow_symlinks", FALSE);
+ options.skip_hidden =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "file_skip_hidden", FALSE);
+ options.file_all_charsets =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "file_all_charsets", FALSE);
+ options.content_case_sens =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "content_case_sens", TRUE);
+ options.content_regexp =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "content_regexp", FALSE);
+ options.content_first_hit =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "content_first_hit", FALSE);
+ options.content_whole_words =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "content_whole_words", FALSE);
+ options.content_all_charsets =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "content_all_charsets", FALSE);
+ options.ignore_dirs_enable =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "ignore_dirs_enable", TRUE);
+ options.ignore_dirs =
+ mc_config_get_string (mc_global.main_config, "FindFile", "ignore_dirs", "");
+
+ if (options.ignore_dirs[0] == '\0')
+ MC_PTR_FREE (options.ignore_dirs);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_save_options (void)
+{
+ mc_config_set_bool (mc_global.main_config, "FindFile", "file_case_sens",
+ options.file_case_sens);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "file_shell_pattern",
+ options.file_pattern);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "file_find_recurs", options.find_recurs);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "follow_symlinks",
+ options.follow_symlinks);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "file_skip_hidden", options.skip_hidden);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "file_all_charsets",
+ options.file_all_charsets);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "content_case_sens",
+ options.content_case_sens);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "content_regexp",
+ options.content_regexp);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "content_first_hit",
+ options.content_first_hit);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "content_whole_words",
+ options.content_whole_words);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "content_all_charsets",
+ options.content_all_charsets);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "ignore_dirs_enable",
+ options.ignore_dirs_enable);
+ mc_config_set_string (mc_global.main_config, "FindFile", "ignore_dirs", options.ignore_dirs);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline char *
+add_to_list (const char *text, void *data)
+{
+ return listbox_add_item (find_list, LISTBOX_APPEND_AT_END, 0, text, data, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+stop_idle (void *data)
+{
+ widget_idle (WIDGET (data), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+status_update (const char *text)
+{
+ label_set_text (status_label, text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+found_num_update (void)
+{
+ label_set_textv (found_num_label, _("Found: %lu"), matches);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+get_list_info (char **file, char **dir, gsize * start, gsize * end)
+{
+ find_match_location_t *location;
+
+ listbox_get_current (find_list, file, (void **) &location);
+ if (location != NULL)
+ {
+ if (dir != NULL)
+ *dir = location->dir;
+ if (start != NULL)
+ *start = location->start;
+ if (end != NULL)
+ *end = location->end;
+ }
+ else
+ {
+ if (dir != NULL)
+ *dir = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** check regular expression */
+
+static gboolean
+find_check_regexp (const char *r)
+{
+ mc_search_t *search;
+ gboolean regexp_ok = FALSE;
+
+ search = mc_search_new (r, NULL);
+
+ if (search != NULL)
+ {
+ search->search_type = MC_SEARCH_T_REGEX;
+ regexp_ok = mc_search_prepare (search);
+ mc_search_free (search);
+ }
+
+ return regexp_ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_toggle_enable_ignore_dirs (void)
+{
+ widget_disable (WIDGET (in_ignore), !ignore_dirs_cbox->state);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_toggle_enable_params (void)
+{
+ gboolean disable = input_is_empty (in_name);
+
+ widget_disable (WIDGET (file_pattern_cbox), disable);
+ widget_disable (WIDGET (file_case_sens_cbox), disable);
+#ifdef HAVE_CHARSET
+ widget_disable (WIDGET (file_all_charsets_cbox), disable);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_toggle_enable_content (void)
+{
+ widget_disable (WIDGET (content_regexp_cbox), content_is_empty);
+ widget_disable (WIDGET (content_case_sens_cbox), content_is_empty);
+#ifdef HAVE_CHARSET
+ widget_disable (WIDGET (content_all_charsets_cbox), content_is_empty);
+#endif
+ widget_disable (WIDGET (content_whole_words_cbox), content_is_empty);
+ widget_disable (WIDGET (content_first_hit_cbox), content_is_empty);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for the parameter dialog.
+ * Validate regex, prevent closing the dialog if it's invalid.
+ */
+
+static cb_ret_t
+find_parm_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ /* FIXME: HACK: use first draw of dialog to resolve widget state dependencies.
+ * Use this time moment to check input field content. We can't do that in MSG_INIT
+ * because history is not loaded yet.
+ * Probably, we want new MSG_ACTIVATE message as complement to MSG_VALIDATE one. Or
+ * we could name it MSG_POST_INIT.
+ *
+ * In one or two other places we use MSG_IDLE instead of MSG_DRAW for a similar
+ * purpose. We should remember to fix those places too when we introduce the new
+ * message.
+ */
+ static gboolean first_draw = TRUE;
+
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ group_default_callback (w, NULL, MSG_INIT, 0, NULL);
+ first_draw = TRUE;
+ return MSG_HANDLED;
+
+ case MSG_NOTIFY:
+ if (sender == WIDGET (ignore_dirs_cbox))
+ {
+ find_toggle_enable_ignore_dirs ();
+ return MSG_HANDLED;
+ }
+
+ return MSG_NOT_HANDLED;
+
+ case MSG_VALIDATE:
+ if (h->ret_value != B_ENTER)
+ return MSG_HANDLED;
+
+ /* check filename regexp */
+ if (!file_pattern_cbox->state && !input_is_empty (in_name)
+ && !find_check_regexp (input_get_ctext (in_name)))
+ {
+ /* Don't stop the dialog */
+ widget_set_state (w, WST_ACTIVE, TRUE);
+ message (D_ERROR, MSG_ERROR, _("Malformed regular expression"));
+ widget_select (WIDGET (in_name));
+ return MSG_HANDLED;
+ }
+
+ /* check content regexp */
+ if (content_regexp_cbox->state && !content_is_empty
+ && !find_check_regexp (input_get_ctext (in_with)))
+ {
+ /* Don't stop the dialog */
+ widget_set_state (w, WST_ACTIVE, TRUE);
+ message (D_ERROR, MSG_ERROR, _("Malformed regular expression"));
+ widget_select (WIDGET (in_with));
+ return MSG_HANDLED;
+ }
+
+ return MSG_HANDLED;
+
+ case MSG_POST_KEY:
+ if (GROUP (h)->current->data == in_name)
+ find_toggle_enable_params ();
+ else if (GROUP (h)->current->data == in_with)
+ {
+ content_is_empty = input_is_empty (in_with);
+ find_toggle_enable_content ();
+ }
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ if (first_draw)
+ {
+ find_toggle_enable_ignore_dirs ();
+ find_toggle_enable_params ();
+ find_toggle_enable_content ();
+ }
+
+ first_draw = FALSE;
+ MC_FALLTHROUGH; /* to call MSG_DRAW default handler */
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * find_parameters: gets information from the user
+ *
+ * If the return value is TRUE, then the following holds:
+ *
+ * start_dir, ignore_dirs, pattern and content contain the information provided by the user.
+ * They are newly allocated strings and must be freed when unneeded.
+ *
+ * start_dir_len is -1 when user entered an absolute path, otherwise it is a length
+ * of start_dir (which is absolute). It is used to get a relative pats of find results.
+ */
+
+static gboolean
+find_parameters (WPanel * panel, char **start_dir, ssize_t * start_dir_len,
+ char **ignore_dirs, char **pattern, char **content)
+{
+ WGroup *g;
+
+ /* Size of the find parameters window */
+#ifdef HAVE_CHARSET
+ const int lines = 19;
+#else
+ const int lines = 18;
+#endif
+ int cols = 68;
+
+ gboolean return_value;
+
+ /* file name */
+ const char *file_name_label = N_("File name:");
+ const char *file_recurs_label = N_("&Find recursively");
+ const char *file_follow_symlinks = N_("Follow s&ymlinks");
+ const char *file_pattern_label = N_("&Using shell patterns");
+#ifdef HAVE_CHARSET
+ const char *file_all_charsets_label = N_("&All charsets");
+#endif
+ const char *file_case_label = N_("Cas&e sensitive");
+ const char *file_skip_hidden_label = N_("S&kip hidden");
+
+ /* file content */
+ const char *content_content_label = N_("Content:");
+ const char *content_use_label = N_("Sea&rch for content");
+ const char *content_regexp_label = N_("Re&gular expression");
+ const char *content_case_label = N_("Case sens&itive");
+#ifdef HAVE_CHARSET
+ const char *content_all_charsets_label = N_("A&ll charsets");
+#endif
+ const char *content_whole_words_label = N_("&Whole words");
+ const char *content_first_hit_label = N_("Fir&st hit");
+
+ const char *buts[] = { N_("&Tree"), N_("&OK"), N_("&Cancel") };
+
+ /* button lengths */
+ int b0, b1, b2, b12;
+ int y1, y2, x1, x2;
+ /* column width */
+ int cw;
+
+#ifdef ENABLE_NLS
+ {
+ size_t i;
+
+ file_name_label = _(file_name_label);
+ file_recurs_label = _(file_recurs_label);
+ file_follow_symlinks = _(file_follow_symlinks);
+ file_pattern_label = _(file_pattern_label);
+#ifdef HAVE_CHARSET
+ file_all_charsets_label = _(file_all_charsets_label);
+#endif
+ file_case_label = _(file_case_label);
+ file_skip_hidden_label = _(file_skip_hidden_label);
+
+ /* file content */
+ content_content_label = _(content_content_label);
+ content_use_label = _(content_use_label);
+ content_regexp_label = _(content_regexp_label);
+ content_case_label = _(content_case_label);
+#ifdef HAVE_CHARSET
+ content_all_charsets_label = _(content_all_charsets_label);
+#endif
+ content_whole_words_label = _(content_whole_words_label);
+ content_first_hit_label = _(content_first_hit_label);
+
+ for (i = 0; i < G_N_ELEMENTS (buts); i++)
+ buts[i] = _(buts[i]);
+ }
+#endif /* ENABLE_NLS */
+
+ /* calculate dialog width */
+
+ /* widget widths */
+ cw = str_term_width1 (file_name_label);
+ cw = max (cw, str_term_width1 (file_recurs_label) + 4);
+ cw = max (cw, str_term_width1 (file_follow_symlinks) + 4);
+ cw = max (cw, str_term_width1 (file_pattern_label) + 4);
+#ifdef HAVE_CHARSET
+ cw = max (cw, str_term_width1 (file_all_charsets_label) + 4);
+#endif
+ cw = max (cw, str_term_width1 (file_case_label) + 4);
+ cw = max (cw, str_term_width1 (file_skip_hidden_label) + 4);
+
+ cw = max (cw, str_term_width1 (content_content_label) + 4);
+ cw = max (cw, str_term_width1 (content_use_label) + 4);
+ cw = max (cw, str_term_width1 (content_regexp_label) + 4);
+ cw = max (cw, str_term_width1 (content_case_label) + 4);
+#ifdef HAVE_CHARSET
+ cw = max (cw, str_term_width1 (content_all_charsets_label) + 4);
+#endif
+ cw = max (cw, str_term_width1 (content_whole_words_label) + 4);
+ cw = max (cw, str_term_width1 (content_first_hit_label) + 4);
+
+ /* button width */
+ b0 = str_term_width1 (buts[0]) + 3;
+ b1 = str_term_width1 (buts[1]) + 5; /* default button */
+ b2 = str_term_width1 (buts[2]) + 3;
+ b12 = b1 + b2 + 1;
+
+ cols = max (cols, max (b12, cw * 2 + 1) + 6);
+
+ find_load_options ();
+
+ if (in_start_dir == NULL)
+ in_start_dir = g_strdup (".");
+
+ find_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, find_parm_callback,
+ NULL, "[Find File]", _("Find File"));
+ g = GROUP (find_dlg);
+
+ x1 = 3;
+ x2 = cols / 2 + 1;
+ cw = (cols - 7) / 2;
+ y1 = 2;
+
+ group_add_widget (g, label_new (y1++, x1, _("Start at:")));
+ in_start =
+ input_new (y1, x1, input_colors, cols - b0 - 7, in_start_dir, "start",
+ INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES);
+ group_add_widget (g, in_start);
+
+ group_add_widget (g, button_new (y1++, cols - b0 - 3, B_TREE, NORMAL_BUTTON, buts[0], NULL));
+
+ ignore_dirs_cbox =
+ check_new (y1++, x1, options.ignore_dirs_enable, _("Ena&ble ignore directories:"));
+ group_add_widget (g, ignore_dirs_cbox);
+
+ in_ignore =
+ input_new (y1++, x1, input_colors, cols - 6,
+ options.ignore_dirs != NULL ? options.ignore_dirs : "", "ignoredirs",
+ INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES);
+ group_add_widget (g, in_ignore);
+
+ group_add_widget (g, hline_new (y1++, -1, -1));
+
+ y2 = y1;
+
+ /* Start 1st column */
+ group_add_widget (g, label_new (y1++, x1, file_name_label));
+ in_name =
+ input_new (y1++, x1, input_colors, cw, INPUT_LAST_TEXT, "name",
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
+ group_add_widget (g, in_name);
+
+ /* Start 2nd column */
+ content_label = label_new (y2++, x2, content_content_label);
+ group_add_widget (g, content_label);
+ in_with =
+ input_new (y2++, x2, input_colors, cw, content_is_empty ? "" : INPUT_LAST_TEXT,
+ MC_HISTORY_SHARED_SEARCH, INPUT_COMPLETE_NONE);
+ in_with->label = content_label;
+ group_add_widget (g, in_with);
+
+ /* Continue 1st column */
+ recursively_cbox = check_new (y1++, x1, options.find_recurs, file_recurs_label);
+ group_add_widget (g, recursively_cbox);
+
+ follow_sym_cbox = check_new (y1++, x1, options.follow_symlinks, file_follow_symlinks);
+ group_add_widget (g, follow_sym_cbox);
+
+ file_pattern_cbox = check_new (y1++, x1, options.file_pattern, file_pattern_label);
+ group_add_widget (g, file_pattern_cbox);
+
+ file_case_sens_cbox = check_new (y1++, x1, options.file_case_sens, file_case_label);
+ group_add_widget (g, file_case_sens_cbox);
+
+#ifdef HAVE_CHARSET
+ file_all_charsets_cbox =
+ check_new (y1++, x1, options.file_all_charsets, file_all_charsets_label);
+ group_add_widget (g, file_all_charsets_cbox);
+#endif
+
+ skip_hidden_cbox = check_new (y1++, x1, options.skip_hidden, file_skip_hidden_label);
+ group_add_widget (g, skip_hidden_cbox);
+
+ /* Continue 2nd column */
+ content_whole_words_cbox =
+ check_new (y2++, x2, options.content_whole_words, content_whole_words_label);
+ group_add_widget (g, content_whole_words_cbox);
+
+ content_regexp_cbox = check_new (y2++, x2, options.content_regexp, content_regexp_label);
+ group_add_widget (g, content_regexp_cbox);
+
+ content_case_sens_cbox = check_new (y2++, x2, options.content_case_sens, content_case_label);
+ group_add_widget (g, content_case_sens_cbox);
+
+#ifdef HAVE_CHARSET
+ content_all_charsets_cbox =
+ check_new (y2++, x2, options.content_all_charsets, content_all_charsets_label);
+ group_add_widget (g, content_all_charsets_cbox);
+#endif
+
+ content_first_hit_cbox =
+ check_new (y2++, x2, options.content_first_hit, content_first_hit_label);
+ group_add_widget (g, content_first_hit_cbox);
+
+ /* buttons */
+ y1 = max (y1, y2);
+ x1 = (cols - b12) / 2;
+ group_add_widget (g, hline_new (y1++, -1, -1));
+ group_add_widget (g, button_new (y1, x1, B_ENTER, DEFPUSH_BUTTON, buts[1], NULL));
+ group_add_widget (g, button_new (y1, x1 + b1 + 1, B_CANCEL, NORMAL_BUTTON, buts[2], NULL));
+
+ find_par_start:
+ widget_select (WIDGET (in_name));
+
+ switch (dlg_run (find_dlg))
+ {
+ case B_CANCEL:
+ return_value = FALSE;
+ break;
+
+ case B_TREE:
+ {
+ const char *start_cstr;
+ const char *temp_dir;
+
+ start_cstr = input_get_ctext (in_start);
+
+ if (input_is_empty (in_start) || DIR_IS_DOT (start_cstr))
+ temp_dir = vfs_path_as_str (panel->cwd_vpath);
+ else
+ temp_dir = start_cstr;
+
+ if (in_start_dir != INPUT_LAST_TEXT)
+ g_free (in_start_dir);
+ in_start_dir = tree_box (temp_dir);
+ if (in_start_dir == NULL)
+ in_start_dir = g_strdup (temp_dir);
+
+ input_assign_text (in_start, in_start_dir);
+
+ /* Warning: Dreadful goto */
+ goto find_par_start;
+ }
+
+ default:
+ {
+ char *s;
+
+#ifdef HAVE_CHARSET
+ options.file_all_charsets = file_all_charsets_cbox->state;
+ options.content_all_charsets = content_all_charsets_cbox->state;
+#endif
+ options.content_case_sens = content_case_sens_cbox->state;
+ options.content_regexp = content_regexp_cbox->state;
+ options.content_first_hit = content_first_hit_cbox->state;
+ options.content_whole_words = content_whole_words_cbox->state;
+ options.find_recurs = recursively_cbox->state;
+ options.follow_symlinks = follow_sym_cbox->state;
+ options.file_pattern = file_pattern_cbox->state;
+ options.file_case_sens = file_case_sens_cbox->state;
+ options.skip_hidden = skip_hidden_cbox->state;
+ options.ignore_dirs_enable = ignore_dirs_cbox->state;
+ g_free (options.ignore_dirs);
+ options.ignore_dirs = input_get_text (in_ignore);
+
+ *content = !input_is_empty (in_with) ? input_get_text (in_with) : NULL;
+ if (input_is_empty (in_name))
+ *pattern = g_strdup (options.file_pattern ? "*" : ".*");
+ else
+ *pattern = input_get_text (in_name);
+ *start_dir = (char *) (!input_is_empty (in_start) ? input_get_ctext (in_start) : ".");
+ if (in_start_dir != INPUT_LAST_TEXT)
+ g_free (in_start_dir);
+ in_start_dir = g_strdup (*start_dir);
+
+ s = tilde_expand (*start_dir);
+ canonicalize_pathname (s);
+
+ if (DIR_IS_DOT (s))
+ {
+ *start_dir = g_strdup (vfs_path_as_str (panel->cwd_vpath));
+ /* FIXME: is panel->cwd_vpath canonicalized? */
+ /* relative paths will be used in panelization */
+ *start_dir_len = (ssize_t) strlen (*start_dir);
+ g_free (s);
+ }
+ else if (g_path_is_absolute (s))
+ {
+ *start_dir = s;
+ *start_dir_len = -1;
+ }
+ else
+ {
+ /* relative paths will be used in panelization */
+ *start_dir =
+ mc_build_filename (vfs_path_as_str (panel->cwd_vpath), s, (char *) NULL);
+ *start_dir_len = (ssize_t) vfs_path_len (panel->cwd_vpath);
+ g_free (s);
+ }
+
+ if (!options.ignore_dirs_enable || input_is_empty (in_ignore)
+ || DIR_IS_DOT (input_get_ctext (in_ignore)))
+ *ignore_dirs = NULL;
+ else
+ *ignore_dirs = input_get_text (in_ignore);
+
+ find_save_options ();
+
+ return_value = TRUE;
+ }
+ }
+
+ widget_destroy (WIDGET (find_dlg));
+
+ return return_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+push_directory (vfs_path_t * dir)
+{
+ g_queue_push_head (&dir_queue, (void *) dir);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline vfs_path_t *
+pop_directory (void)
+{
+ return (vfs_path_t *) g_queue_pop_head (&dir_queue);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+queue_dir_free (gpointer data)
+{
+ vfs_path_free ((vfs_path_t *) data, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Remove all the items from the stack */
+
+static void
+clear_stack (void)
+{
+ g_queue_clear_full (&dir_queue, queue_dir_free);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+insert_file (const char *dir, const char *file, gsize start, gsize end)
+{
+ char *tmp_name = NULL;
+ static char *dirname = NULL;
+ find_match_location_t *location;
+
+ while (IS_PATH_SEP (dir[0]) && IS_PATH_SEP (dir[1]))
+ dir++;
+
+ if (old_dir != NULL)
+ {
+ if (strcmp (old_dir, dir) != 0)
+ {
+ g_free (old_dir);
+ old_dir = g_strdup (dir);
+ dirname = add_to_list (dir, NULL);
+ }
+ }
+ else
+ {
+ old_dir = g_strdup (dir);
+ dirname = add_to_list (dir, NULL);
+ }
+
+ tmp_name = g_strdup_printf (" %s", file);
+ location = g_malloc (sizeof (*location));
+ location->dir = dirname;
+ location->start = start;
+ location->end = end;
+ add_to_list (tmp_name, location);
+ g_free (tmp_name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_add_match (const char *dir, const char *file, gsize start, gsize end)
+{
+ insert_file (dir, file, start, end);
+
+ /* Don't scroll */
+ if (matches == 0)
+ listbox_select_first (find_list);
+ widget_draw (WIDGET (find_list));
+
+ matches++;
+ found_num_update ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FindProgressStatus
+check_find_events (WDialog * h)
+{
+ Gpm_Event event;
+ int c;
+
+ event.x = -1;
+ c = tty_get_event (&event, GROUP (h)->mouse_status == MOU_REPEAT, FALSE);
+ if (c != EV_NONE)
+ {
+ dlg_process_event (h, c, &event);
+ if (h->ret_value == B_ENTER
+ || h->ret_value == B_CANCEL || h->ret_value == B_AGAIN || h->ret_value == B_PANELIZE)
+ {
+ /* dialog terminated */
+ return FIND_ABORT;
+ }
+ if (!widget_get_state (WIDGET (h), WST_IDLE))
+ {
+ /* searching suspended */
+ return FIND_SUSPEND;
+ }
+ }
+
+ return FIND_CONT;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * search_content:
+ *
+ * Search the content_pattern string in the DIRECTORY/FILE.
+ * It will add the found entries to the find listbox.
+ *
+ * returns FALSE if do_search should look for another file
+ * TRUE if do_search should exit and proceed to the event handler
+ */
+
+static gboolean
+search_content (WDialog * h, const char *directory, const char *filename)
+{
+ struct stat s;
+ char buffer[BUF_4K] = ""; /* raw input buffer */
+ int file_fd;
+ gboolean ret_val = FALSE;
+ vfs_path_t *vpath;
+ gint64 tv;
+ gboolean status_updated = FALSE;
+
+ vpath = vfs_path_build_filename (directory, filename, (char *) NULL);
+
+ if (mc_stat (vpath, &s) != 0 || !S_ISREG (s.st_mode))
+ {
+ vfs_path_free (vpath, TRUE);
+ return FALSE;
+ }
+
+ file_fd = mc_open (vpath, O_RDONLY);
+ vfs_path_free (vpath, TRUE);
+
+ if (file_fd == -1)
+ return FALSE;
+
+ /* get time elapsed from last refresh */
+ tv = g_get_monotonic_time ();
+
+ if (s.st_size >= MIN_REFRESH_FILE_SIZE || (tv - last_refresh) > MAX_REFRESH_INTERVAL)
+ {
+ g_snprintf (buffer, sizeof (buffer), _("Grepping in %s"), filename);
+ status_update (str_trunc (buffer, WIDGET (h)->rect.cols - 8));
+ mc_refresh ();
+ last_refresh = tv;
+ status_updated = TRUE;
+ }
+
+ tty_enable_interrupt_key ();
+ tty_got_interrupt ();
+
+ {
+ int line = 1;
+ int pos = 0;
+ int n_read = 0;
+ off_t off = 0; /* file_fd's offset corresponding to strbuf[0] */
+ gboolean found = FALSE;
+ gsize found_len;
+ gsize found_start;
+ char result[BUF_MEDIUM];
+ char *strbuf = NULL; /* buffer for fetched string */
+ int strbuf_size = 0;
+ int i = -1; /* compensate for a newline we'll add when we first enter the loop */
+
+ if (resuming)
+ {
+ /* We've been previously suspended, start from the previous position */
+ resuming = FALSE;
+ line = last_line;
+ pos = last_pos;
+ off = last_off;
+ i = last_i;
+ }
+
+ while (!ret_val)
+ {
+ char ch = '\0';
+
+ off += i + 1; /* the previous line, plus a newline character */
+ i = 0;
+
+ /* read to buffer and get line from there */
+ while (TRUE)
+ {
+ if (pos >= n_read)
+ {
+ pos = 0;
+ n_read = mc_read (file_fd, buffer, sizeof (buffer));
+ if (n_read <= 0)
+ break;
+ }
+
+ ch = buffer[pos++];
+ if (ch == '\0')
+ {
+ /* skip possible leading zero(s) */
+ if (i == 0)
+ {
+ off++;
+ continue;
+ }
+ break;
+ }
+
+ if (i >= strbuf_size - 1)
+ {
+ strbuf_size += 128;
+ strbuf = g_realloc (strbuf, strbuf_size);
+ }
+
+ /* Strip newline */
+ if (ch == '\n')
+ break;
+
+ strbuf[i++] = ch;
+ }
+
+ if (i == 0)
+ {
+ if (ch == '\0')
+ break;
+
+ /* if (ch == '\n'): do not search in empty strings */
+ goto skip_search;
+ }
+
+ strbuf[i] = '\0';
+
+ if (!found /* Search in binary line once */
+ && mc_search_run (search_content_handle, (const void *) strbuf, 0, i, &found_len))
+ {
+ if (!status_updated)
+ {
+ /* if we add results for a file, we have to ensure that
+ name of this file is shown in status bar */
+ g_snprintf (result, sizeof (result), _("Grepping in %s"), filename);
+ status_update (str_trunc (result, WIDGET (h)->rect.cols - 8));
+ mc_refresh ();
+ last_refresh = tv;
+ status_updated = TRUE;
+ }
+
+ g_snprintf (result, sizeof (result), "%d:%s", line, filename);
+ found_start = off + search_content_handle->normal_offset + 1; /* off by one: ticket 3280 */
+ find_add_match (directory, result, found_start, found_start + found_len);
+ found = TRUE;
+ }
+
+ if (found && options.content_first_hit)
+ break;
+
+ if (ch == '\n')
+ {
+ skip_search:
+ found = FALSE;
+ line++;
+ }
+
+ if ((line & 0xff) == 0)
+ {
+ FindProgressStatus res;
+
+ res = check_find_events (h);
+ switch (res)
+ {
+ case FIND_ABORT:
+ stop_idle (h);
+ ret_val = TRUE;
+ break;
+ case FIND_SUSPEND:
+ resuming = TRUE;
+ last_line = line;
+ last_pos = pos;
+ last_off = off;
+ last_i = i;
+ ret_val = TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ g_free (strbuf);
+ }
+
+ tty_disable_interrupt_key ();
+ mc_close (file_fd);
+ return ret_val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ If dir is absolute, this means we're within dir and searching file here.
+ If dir is relative, this means we're going to add dir to the directory stack.
+**/
+static gboolean
+find_ignore_dir_search (const char *dir)
+{
+ if (find_ignore_dirs != NULL)
+ {
+ const size_t dlen = strlen (dir);
+ const unsigned char dabs = g_path_is_absolute (dir) ? 1 : 0;
+
+ char **ignore_dir;
+
+ for (ignore_dir = find_ignore_dirs; *ignore_dir != NULL; ignore_dir++)
+ {
+ const size_t ilen = strlen (*ignore_dir);
+ const unsigned char iabs = g_path_is_absolute (*ignore_dir) ? 2 : 0;
+
+ /* ignore dir is too long -- skip it */
+ if (dlen < ilen)
+ continue;
+
+ /* handle absolute and relative paths */
+ switch (iabs | dabs)
+ {
+ case 0: /* both paths are relative */
+ case 3: /* both paths are absolute */
+ /* if ignore dir is not a path of dir -- skip it */
+ if (strncmp (dir, *ignore_dir, ilen) == 0)
+ {
+ /* be sure that ignore dir is not a part of dir like:
+ ignore dir is "h", dir is "home" */
+ if (dir[ilen] == '\0' || IS_PATH_SEP (dir[ilen]))
+ return TRUE;
+ }
+ break;
+ case 1: /* dir is absolute, ignore_dir is relative */
+ {
+ char *d;
+
+ d = strstr (dir, *ignore_dir);
+ if (d != NULL && IS_PATH_SEP (d[-1])
+ && (d[ilen] == '\0' || IS_PATH_SEP (d[ilen])))
+ return TRUE;
+ }
+ break;
+ case 2: /* dir is relative, ignore_dir is absolute */
+ /* FIXME: skip this case */
+ break;
+ default: /* this cannot occurs */
+ return FALSE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_rotate_dash (const WDialog * h, gboolean show)
+{
+ static size_t pos = 0;
+ static const char rotating_dash[4] = "|/-\\";
+ const Widget *w = CONST_WIDGET (h);
+ const int *colors;
+
+ colors = widget_get_colors (w);
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ widget_gotoyx (h, w->rect.lines - 7, w->rect.cols - 4);
+ tty_print_char (show ? rotating_dash[pos] : ' ');
+ pos = (pos + 1) % sizeof (rotating_dash);
+ mc_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+do_search (WDialog * h)
+{
+ static struct vfs_dirent *dp = NULL;
+ static DIR *dirp = NULL;
+ static char *directory = NULL;
+ static gboolean pop_start_dir = TRUE;
+ struct stat tmp_stat;
+ gsize bytes_found;
+ unsigned short count;
+
+ if (h == NULL)
+ { /* someone forces me to close dirp */
+ if (dirp != NULL)
+ {
+ mc_closedir (dirp);
+ dirp = NULL;
+ }
+ MC_PTR_FREE (directory);
+ dp = NULL;
+ pop_start_dir = TRUE;
+ return 1;
+ }
+
+ for (count = 0; count < 32; count++)
+ {
+ while (dp == NULL)
+ {
+ if (dirp != NULL)
+ {
+ mc_closedir (dirp);
+ dirp = NULL;
+ }
+
+ while (dirp == NULL)
+ {
+ vfs_path_t *tmp_vpath = NULL;
+
+ tty_setcolor (REVERSE_COLOR);
+
+ while (TRUE)
+ {
+ tmp_vpath = pop_directory ();
+ if (tmp_vpath == NULL)
+ {
+ running = FALSE;
+ if (ignore_count == 0)
+ status_update (_("Finished"));
+ else
+ {
+ char msg[BUF_SMALL];
+
+ g_snprintf (msg, sizeof (msg),
+ ngettext ("Finished (ignored %zu directory)",
+ "Finished (ignored %zu directories)",
+ ignore_count), ignore_count);
+ status_update (msg);
+ }
+ if (verbose)
+ find_rotate_dash (h, FALSE);
+ stop_idle (h);
+ return 0;
+ }
+
+ /* The start directory is the first one in the stack (see do_find() below).
+ Do not apply ignore_dir to it. */
+ if (pop_start_dir)
+ {
+ pop_start_dir = FALSE;
+ break;
+ }
+
+ pop_start_dir = FALSE;
+
+ /* handle absolute ignore dirs here */
+ if (!find_ignore_dir_search (vfs_path_as_str (tmp_vpath)))
+ break;
+
+ vfs_path_free (tmp_vpath, TRUE);
+ ignore_count++;
+ }
+
+ g_free (directory);
+
+ if (verbose)
+ {
+ char buffer[BUF_MEDIUM];
+
+ directory = (char *) vfs_path_as_str (tmp_vpath);
+ g_snprintf (buffer, sizeof (buffer), _("Searching %s"), directory);
+ status_update (str_trunc (directory, WIDGET (h)->rect.cols - 8));
+ }
+
+ dirp = mc_opendir (tmp_vpath);
+ directory = vfs_path_free (tmp_vpath, FALSE);
+ } /* while (!dirp) */
+
+ /* skip invalid filenames */
+ while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
+ ;
+ } /* while (!dp) */
+
+ if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
+ {
+ /* skip invalid filenames */
+ while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
+ ;
+
+ return 1;
+ }
+
+ if (!(options.skip_hidden && (dp->d_name[0] == '.')))
+ {
+ gboolean search_ok;
+
+ if (options.find_recurs && (directory != NULL))
+ { /* Can directory be NULL ? */
+ /* handle relative ignore dirs here */
+ if (options.ignore_dirs_enable && find_ignore_dir_search (dp->d_name))
+ ignore_count++;
+ else
+ {
+ vfs_path_t *tmp_vpath;
+ int stat_res;
+
+ tmp_vpath = vfs_path_build_filename (directory, dp->d_name, (char *) NULL);
+
+ if (options.follow_symlinks)
+ stat_res = mc_stat (tmp_vpath, &tmp_stat);
+ else
+ stat_res = mc_lstat (tmp_vpath, &tmp_stat);
+
+ if (stat_res == 0 && S_ISDIR (tmp_stat.st_mode))
+ push_directory (tmp_vpath);
+ else
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ }
+
+ search_ok = mc_search_run (search_file_handle, dp->d_name,
+ 0, strlen (dp->d_name), &bytes_found);
+
+ if (search_ok)
+ {
+ if (content_pattern == NULL)
+ find_add_match (directory, dp->d_name, 0, 0);
+ else if (search_content (h, directory, dp->d_name))
+ return 1;
+ }
+ }
+
+ /* skip invalid filenames */
+ while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
+ ;
+ } /* for */
+
+ if (verbose)
+ find_rotate_dash (h, TRUE);
+
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+init_find_vars (void)
+{
+ MC_PTR_FREE (old_dir);
+ matches = 0;
+ ignore_count = 0;
+
+ /* Remove all the items from the stack */
+ clear_stack ();
+
+ g_strfreev (find_ignore_dirs);
+ find_ignore_dirs = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_do_view_edit (gboolean unparsed_view, gboolean edit, char *dir, char *file, off_t search_start,
+ off_t search_end)
+{
+ const char *filename = NULL;
+ int line;
+ vfs_path_t *fullname_vpath;
+
+ if (content_pattern != NULL)
+ {
+ filename = strchr (file + 4, ':') + 1;
+ line = atoi (file + 4);
+ }
+ else
+ {
+ filename = file + 4;
+ line = 0;
+ }
+
+ fullname_vpath = vfs_path_build_filename (dir, filename, (char *) NULL);
+ if (edit)
+ edit_file_at_line (fullname_vpath, use_internal_edit, line);
+ else
+ view_file_at_line (fullname_vpath, unparsed_view, use_internal_view, line, search_start,
+ search_end);
+ vfs_path_free (fullname_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+view_edit_currently_selected_file (gboolean unparsed_view, gboolean edit)
+{
+ char *text = NULL;
+ find_match_location_t *location;
+
+ listbox_get_current (find_list, &text, (void **) &location);
+
+ if ((text == NULL) || (location == NULL) || (location->dir == NULL))
+ return MSG_NOT_HANDLED;
+
+ find_do_view_edit (unparsed_view, edit, location->dir, text, location->start, location->end);
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_calc_button_locations (const WDialog * h, gboolean all_buttons)
+{
+ const int cols = CONST_WIDGET (h)->rect.cols;
+
+ int l1, l2;
+
+ l1 = fbuts[0].len + fbuts[1].len + fbuts[is_start ? 3 : 2].len + fbuts[4].len + 3;
+ l2 = fbuts[5].len + fbuts[6].len + fbuts[7].len + 2;
+
+ fbuts[0].x = (cols - l1) / 2;
+ fbuts[1].x = fbuts[0].x + fbuts[0].len + 1;
+ fbuts[2].x = fbuts[1].x + fbuts[1].len + 1;
+ fbuts[3].x = fbuts[2].x;
+ fbuts[4].x = fbuts[2].x + fbuts[is_start ? 3 : 2].len + 1;
+
+ if (all_buttons)
+ {
+ fbuts[5].x = (cols - l2) / 2;
+ fbuts[6].x = fbuts[5].x + fbuts[5].len + 1;
+ fbuts[7].x = fbuts[6].x + fbuts[6].len + 1;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_adjust_header (WDialog * h)
+{
+ char title[BUF_MEDIUM];
+ int title_len;
+
+ if (content_pattern != NULL)
+ g_snprintf (title, sizeof (title), _("Find File: \"%s\". Content: \"%s\""), find_pattern,
+ content_pattern);
+ else
+ g_snprintf (title, sizeof (title), _("Find File: \"%s\""), find_pattern);
+
+ title_len = str_term_width1 (title);
+ if (title_len > WIDGET (h)->rect.cols - 6)
+ {
+ /* title is too wide, truncate it */
+ title_len = WIDGET (h)->rect.cols - 6;
+ title_len = str_column_to_pos (title, title_len);
+ title_len -= 3; /* reserve space for three dots */
+ title_len = str_offset_to_pos (title, title_len);
+ /* mark that title is truncated */
+ memmove (title + title_len, "...", 4);
+ }
+
+ frame_set_title (FRAME (h->bg), title);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_relocate_buttons (const WDialog * h, gboolean all_buttons)
+{
+ size_t i;
+
+ find_calc_button_locations (h, all_buttons);
+
+ for (i = 0; i < fbuts_num; i++)
+ fbuts[i].button->rect.x = CONST_WIDGET (h)->rect.x + fbuts[i].x;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+find_resize (WDialog * h)
+{
+ Widget *w = WIDGET (h);
+ WRect r = w->rect;
+
+ r.lines = LINES - 4;
+ r.cols = COLS - 16;
+ dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
+ find_adjust_header (h);
+ find_relocate_buttons (h, TRUE);
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+find_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ group_default_callback (w, NULL, MSG_INIT, 0, NULL);
+ find_adjust_header (h);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ if (parm == KEY_F (3) || parm == KEY_F (13))
+ {
+ gboolean unparsed_view = (parm == KEY_F (13));
+
+ return view_edit_currently_selected_file (unparsed_view, FALSE);
+ }
+ if (parm == KEY_F (4))
+ return view_edit_currently_selected_file (FALSE, TRUE);
+ return MSG_NOT_HANDLED;
+
+ case MSG_RESIZE:
+ return find_resize (h);
+
+ case MSG_IDLE:
+ do_search (h);
+ return MSG_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Handles the Stop/Start button in the find window */
+
+static int
+start_stop (WButton * button, int action)
+{
+ Widget *w = WIDGET (button);
+
+ (void) action;
+
+ running = is_start;
+ widget_idle (WIDGET (find_dlg), running);
+ is_start = !is_start;
+
+ status_update (is_start ? _("Stopped") : _("Searching"));
+ button_set_text (button, fbuts[is_start ? 3 : 2].text);
+
+ find_relocate_buttons (DIALOG (w->owner), FALSE);
+ widget_draw (WIDGET (w->owner));
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Handle view command, when invoked as a button */
+
+static int
+find_do_view_file (WButton * button, int action)
+{
+ (void) button;
+ (void) action;
+
+ view_edit_currently_selected_file (FALSE, FALSE);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Handle edit command, when invoked as a button */
+
+static int
+find_do_edit_file (WButton * button, int action)
+{
+ (void) button;
+ (void) action;
+
+ view_edit_currently_selected_file (FALSE, TRUE);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+setup_gui (void)
+{
+ WGroup *g;
+ size_t i;
+ int lines, cols;
+ int y;
+
+ static gboolean i18n_flag = FALSE;
+
+ if (!i18n_flag)
+ {
+ for (i = 0; i < fbuts_num; i++)
+ {
+#ifdef ENABLE_NLS
+ fbuts[i].text = _(fbuts[i].text);
+#endif /* ENABLE_NLS */
+ fbuts[i].len = str_term_width1 (fbuts[i].text) + 3;
+ if (fbuts[i].flags == DEFPUSH_BUTTON)
+ fbuts[i].len += 2;
+ }
+
+ i18n_flag = TRUE;
+ }
+
+ lines = LINES - 4;
+ cols = COLS - 16;
+
+ find_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, find_callback, NULL,
+ "[Find File]", NULL);
+ g = GROUP (find_dlg);
+
+ find_calc_button_locations (find_dlg, TRUE);
+
+ y = 2;
+ find_list = listbox_new (y, 2, lines - 10, cols - 4, FALSE, NULL);
+ group_add_widget_autopos (g, find_list, WPOS_KEEP_ALL, NULL);
+ y += WIDGET (find_list)->rect.lines;
+
+ group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
+
+ found_num_label = label_new (y++, 4, NULL);
+ group_add_widget_autopos (g, found_num_label, WPOS_KEEP_BOTTOM, NULL);
+
+ status_label = label_new (y++, 4, _("Searching"));
+ group_add_widget_autopos (g, status_label, WPOS_KEEP_BOTTOM, NULL);
+
+ group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
+
+ for (i = 0; i < fbuts_num; i++)
+ {
+ if (i == 3)
+ fbuts[3].button = fbuts[2].button;
+ else
+ {
+ fbuts[i].button =
+ WIDGET (button_new
+ (y, fbuts[i].x, fbuts[i].ret_cmd, fbuts[i].flags, fbuts[i].text,
+ fbuts[i].callback));
+ group_add_widget_autopos (g, fbuts[i].button, WPOS_KEEP_BOTTOM, NULL);
+ }
+
+ if (i == quit_button)
+ y++;
+ }
+
+ widget_select (WIDGET (find_list));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+run_process (void)
+{
+ int ret;
+
+ search_content_handle = mc_search_new (content_pattern, NULL);
+ if (search_content_handle)
+ {
+ search_content_handle->search_type =
+ options.content_regexp ? MC_SEARCH_T_REGEX : MC_SEARCH_T_NORMAL;
+ search_content_handle->is_case_sensitive = options.content_case_sens;
+ search_content_handle->whole_words = options.content_whole_words;
+#ifdef HAVE_CHARSET
+ search_content_handle->is_all_charsets = options.content_all_charsets;
+#endif
+ }
+ search_file_handle = mc_search_new (find_pattern, NULL);
+ search_file_handle->search_type = options.file_pattern ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
+ search_file_handle->is_case_sensitive = options.file_case_sens;
+#ifdef HAVE_CHARSET
+ search_file_handle->is_all_charsets = options.file_all_charsets;
+#endif
+ search_file_handle->is_entire_line = options.file_pattern;
+
+ resuming = FALSE;
+
+ widget_idle (WIDGET (find_dlg), TRUE);
+ ret = dlg_run (find_dlg);
+
+ mc_search_free (search_file_handle);
+ search_file_handle = NULL;
+ mc_search_free (search_content_handle);
+ search_content_handle = NULL;
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+kill_gui (void)
+{
+ Widget *w = WIDGET (find_dlg);
+
+ widget_idle (w, FALSE);
+ widget_destroy (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+do_find (WPanel * panel, const char *start_dir, ssize_t start_dir_len, const char *ignore_dirs,
+ char **dirname, char **filename)
+{
+ int return_value = 0;
+ char *dir_tmp = NULL, *file_tmp = NULL;
+
+ setup_gui ();
+
+ init_find_vars ();
+ parse_ignore_dirs (ignore_dirs);
+ push_directory (vfs_path_from_str (start_dir));
+
+ return_value = run_process ();
+
+ /* Clear variables */
+ init_find_vars ();
+
+ get_list_info (&file_tmp, &dir_tmp, NULL, NULL);
+
+ if (dir_tmp)
+ *dirname = g_strdup (dir_tmp);
+ if (file_tmp)
+ *filename = g_strdup (file_tmp);
+
+ if (return_value == B_PANELIZE && *filename)
+ {
+ struct stat st;
+ GList *entry;
+ dir_list *list = &panel->dir;
+ char *name = NULL;
+ gboolean ok = TRUE;
+
+ panel_clean_dir (panel);
+ dir_list_init (list);
+
+ for (entry = listbox_get_first_link (find_list); entry != NULL && ok;
+ entry = g_list_next (entry))
+ {
+ const char *lc_filename = NULL;
+ WLEntry *le = LENTRY (entry->data);
+ find_match_location_t *location = le->data;
+ char *p;
+ gboolean link_to_dir, stale_link;
+
+ if ((le->text == NULL) || (location == NULL) || (location->dir == NULL))
+ continue;
+
+ if (!content_is_empty)
+ lc_filename = strchr (le->text + 4, ':') + 1;
+ else
+ lc_filename = le->text + 4;
+
+ name = mc_build_filename (location->dir, lc_filename, (char *) NULL);
+ /* skip initial start dir */
+ if (start_dir_len < 0)
+ p = name;
+ else
+ {
+ p = name + (size_t) start_dir_len;
+ if (IS_PATH_SEP (*p))
+ p++;
+ }
+
+ if (!handle_path (p, &st, &link_to_dir, &stale_link))
+ {
+ g_free (name);
+ continue;
+ }
+
+ /* don't add files more than once to the panel */
+ if (!content_is_empty && list->len != 0
+ && strcmp (list->list[list->len - 1].fname->str, p) == 0)
+ {
+ g_free (name);
+ continue;
+ }
+
+ ok = dir_list_append (list, p, &st, link_to_dir, stale_link);
+
+ g_free (name);
+
+ if ((list->len & 15) == 0)
+ rotate_dash (TRUE);
+ }
+
+ panel->is_panelized = TRUE;
+ panel_panelize_absolutize_if_needed (panel);
+ panel_panelize_save (panel);
+ }
+
+ kill_gui ();
+ do_search (NULL); /* force do_search to release resources */
+ MC_PTR_FREE (old_dir);
+ rotate_dash (FALSE);
+
+ return return_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+find_cmd (WPanel * panel)
+{
+ char *start_dir = NULL, *ignore_dirs = NULL;
+ ssize_t start_dir_len;
+
+ find_pattern = NULL;
+ content_pattern = NULL;
+
+ while (find_parameters (panel, &start_dir, &start_dir_len,
+ &ignore_dirs, &find_pattern, &content_pattern))
+ {
+ char *filename = NULL, *dirname = NULL;
+ int v = B_CANCEL;
+
+ content_is_empty = content_pattern == NULL;
+
+ if (find_pattern[0] != '\0')
+ {
+ last_refresh = 0;
+
+ is_start = FALSE;
+
+ if (!content_is_empty && !str_is_valid_string (content_pattern))
+ MC_PTR_FREE (content_pattern);
+
+ v = do_find (panel, start_dir, start_dir_len, ignore_dirs, &dirname, &filename);
+ }
+
+ g_free (start_dir);
+ g_free (ignore_dirs);
+ MC_PTR_FREE (find_pattern);
+
+ if (v == B_ENTER)
+ {
+ if (dirname != NULL)
+ {
+ vfs_path_t *dirname_vpath;
+
+ dirname_vpath = vfs_path_from_str (dirname);
+ panel_cd (panel, dirname_vpath, cd_exact);
+ vfs_path_free (dirname_vpath, TRUE);
+ /* *INDENT-OFF* */
+ if (filename != NULL)
+ panel_set_current_by_name (panel,
+ filename + (content_pattern != NULL
+ ? strchr (filename + 4, ':') - filename + 1
+ : 4));
+ /* *INDENT-ON* */
+ }
+ else if (filename != NULL)
+ {
+ vfs_path_t *filename_vpath;
+
+ filename_vpath = vfs_path_from_str (filename);
+ panel_cd (panel, filename_vpath, cd_exact);
+ vfs_path_free (filename_vpath, TRUE);
+ }
+ }
+
+ MC_PTR_FREE (content_pattern);
+ g_free (dirname);
+ g_free (filename);
+
+ if (v == B_ENTER || v == B_CANCEL)
+ break;
+
+ if (v == B_PANELIZE)
+ {
+ panel_re_sort (panel);
+ panel_set_current_by_name (panel, NULL);
+ break;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/hotlist.c b/src/filemanager/hotlist.c
new file mode 100644
index 0000000..fa04a3b
--- /dev/null
+++ b/src/filemanager/hotlist.c
@@ -0,0 +1,1733 @@
+/*
+ Directory hotlist -- for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Radek Doulik, 1994
+ Janne Kukonlehto, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2012-2022
+
+ Janne did the original Hotlist code, Andrej made the groupable
+ hotlist; the move hotlist and revamped the file format and made
+ it stronger.
+
+ 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 hotlist.c
+ * \brief Source: directory hotlist
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* COLS */
+#include "lib/tty/key.h" /* KEY_M_CTRL */
+#include "lib/skin.h" /* colors */
+#include "lib/mcconfig.h" /* Load/save directories hotlist */
+#include "lib/fileloc.h"
+#include "lib/strutil.h"
+#include "lib/vfs/vfs.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+#include "src/setup.h" /* For profile_bname */
+#include "src/history.h"
+
+#include "command.h" /* cmdline */
+
+#include "hotlist.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define UX 3
+#define UY 2
+
+#define B_ADD_CURRENT B_USER
+#define B_REMOVE (B_USER + 1)
+#define B_NEW_GROUP (B_USER + 2)
+#define B_NEW_ENTRY (B_USER + 3)
+#define B_ENTER_GROUP (B_USER + 4)
+#define B_UP_GROUP (B_USER + 5)
+#define B_INSERT (B_USER + 6)
+#define B_APPEND (B_USER + 7)
+#define B_MOVE (B_USER + 8)
+#ifdef ENABLE_VFS
+#define B_FREE_ALL_VFS (B_USER + 9)
+#define B_REFRESH_VFS (B_USER + 10)
+#endif
+
+#define TKN_GROUP 0
+#define TKN_ENTRY 1
+#define TKN_STRING 2
+#define TKN_URL 3
+#define TKN_ENDGROUP 4
+#define TKN_COMMENT 5
+#define TKN_EOL 125
+#define TKN_EOF 126
+#define TKN_UNKNOWN 127
+
+#define SKIP_TO_EOL \
+{ \
+ int _tkn; \
+ while ((_tkn = hot_next_token ()) != TKN_EOF && _tkn != TKN_EOL) ; \
+}
+
+#define CHECK_TOKEN(_TKN_) \
+tkn = hot_next_token (); \
+if (tkn != _TKN_) \
+{ \
+ hotlist_state.readonly = TRUE; \
+ hotlist_state.file_error = TRUE; \
+ while (tkn != TKN_EOL && tkn != TKN_EOF) \
+ tkn = hot_next_token (); \
+ break; \
+}
+
+/*** file scope type declarations ****************************************************************/
+
+enum HotListType
+{
+ HL_TYPE_GROUP,
+ HL_TYPE_ENTRY,
+ HL_TYPE_COMMENT,
+ HL_TYPE_DOTDOT
+};
+
+static struct
+{
+ /*
+ * these reflect run time state
+ */
+
+ gboolean loaded; /* hotlist is loaded */
+ gboolean readonly; /* hotlist readonly */
+ gboolean file_error; /* parse error while reading file */
+ gboolean running; /* we are running dlg (and have to
+ update listbox */
+ gboolean moving; /* we are in moving hotlist currently */
+ gboolean modified; /* hotlist was modified */
+ hotlist_t type; /* LIST_HOTLIST || LIST_VFSLIST */
+} hotlist_state;
+
+/* Directory hotlist */
+struct hotlist
+{
+ enum HotListType type;
+ char *directory;
+ char *label;
+ struct hotlist *head;
+ struct hotlist *up;
+ struct hotlist *next;
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static WPanel *our_panel;
+
+static gboolean hotlist_has_dot_dot = TRUE;
+
+static WDialog *hotlist_dlg, *movelist_dlg;
+static WGroupbox *hotlist_group, *movelist_group;
+static WListbox *l_hotlist, *l_movelist;
+static WLabel *pname;
+
+static struct
+{
+ int ret_cmd, flags, y, x, len;
+ const char *text;
+ int type;
+ widget_pos_flags_t pos_flags;
+} hotlist_but[] =
+{
+ /* *INDENT-OFF* */
+ { B_ENTER, DEFPUSH_BUTTON, 0, 0, 0, N_("Change &to"),
+ LIST_HOTLIST | LIST_VFSLIST | LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+#ifdef ENABLE_VFS
+ { B_FREE_ALL_VFS, NORMAL_BUTTON, 0, 20, 0, N_("&Free VFSs now"),
+ LIST_VFSLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_REFRESH_VFS, NORMAL_BUTTON, 0, 43, 0, N_("&Refresh"),
+ LIST_VFSLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+#endif
+ { B_ADD_CURRENT, NORMAL_BUTTON, 0, 20, 0, N_("&Add current"),
+ LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_UP_GROUP, NORMAL_BUTTON, 0, 42, 0, N_("&Up"),
+ LIST_HOTLIST | LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_CANCEL, NORMAL_BUTTON, 0, 53, 0, N_("&Cancel"),
+ LIST_HOTLIST | LIST_VFSLIST | LIST_MOVELIST, WPOS_KEEP_RIGHT | WPOS_KEEP_BOTTOM },
+ { B_NEW_GROUP, NORMAL_BUTTON, 1, 0, 0, N_("New &group"),
+ LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_NEW_ENTRY, NORMAL_BUTTON, 1, 15, 0, N_("New &entry"),
+ LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_INSERT, NORMAL_BUTTON, 1, 0, 0, N_("&Insert"),
+ LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_APPEND, NORMAL_BUTTON, 1, 15, 0, N_("A&ppend"),
+ LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_REMOVE, NORMAL_BUTTON, 1, 30, 0, N_("&Remove"),
+ LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_MOVE, NORMAL_BUTTON, 1, 42, 0, N_("&Move"),
+ LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM }
+ /* *INDENT-ON* */
+};
+
+static const size_t hotlist_but_num = G_N_ELEMENTS (hotlist_but);
+
+static struct hotlist *hotlist = NULL;
+
+static struct hotlist *current_group;
+
+static GString *tkn_buf = NULL;
+
+static char *hotlist_file_name;
+static FILE *hotlist_file;
+static time_t hotlist_file_mtime;
+
+static int list_level = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void init_movelist (struct hotlist *item);
+static void add_new_group_cmd (void);
+static void add_new_entry_cmd (WPanel * panel);
+static void remove_from_hotlist (struct hotlist *entry);
+static void load_hotlist (void);
+static void add_dotdot_to_list (void);
+
+/* --------------------------------------------------------------------------------------------- */
+/** If current->data is 0, then we are dealing with a VFS pathname */
+
+static void
+update_path_name (void)
+{
+ const char *text = "";
+ char *p;
+ WListbox *list = hotlist_state.moving ? l_movelist : l_hotlist;
+ Widget *w = WIDGET (list);
+
+ if (!listbox_is_empty (list))
+ {
+ char *ctext = NULL;
+ void *cdata = NULL;
+
+ listbox_get_current (list, &ctext, &cdata);
+ if (cdata == NULL)
+ text = ctext;
+ else
+ {
+ struct hotlist *hlp = (struct hotlist *) cdata;
+
+ if (hlp->type == HL_TYPE_ENTRY || hlp->type == HL_TYPE_DOTDOT)
+ text = hlp->directory;
+ else if (hlp->type == HL_TYPE_GROUP)
+ text = _("Subgroup - press ENTER to see list");
+ }
+ }
+
+ p = g_strconcat (" ", current_group->label, " ", (char *) NULL);
+ if (hotlist_state.moving)
+ groupbox_set_title (movelist_group, str_trunc (p, w->rect.cols - 2));
+ else
+ {
+ groupbox_set_title (hotlist_group, str_trunc (p, w->rect.cols - 2));
+ label_set_text (pname, str_trunc (text, w->rect.cols));
+ }
+ g_free (p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fill_listbox (WListbox * list)
+{
+ struct hotlist *current;
+ GString *buff;
+
+ buff = g_string_new ("");
+
+ for (current = current_group->head; current != NULL; current = current->next)
+ switch (current->type)
+ {
+ case HL_TYPE_GROUP:
+ {
+ /* buff clean up */
+ g_string_truncate (buff, 0);
+ g_string_append (buff, "->");
+ g_string_append (buff, current->label);
+ listbox_add_item (list, LISTBOX_APPEND_AT_END, 0, buff->str, current, FALSE);
+ }
+ break;
+ case HL_TYPE_DOTDOT:
+ case HL_TYPE_ENTRY:
+ listbox_add_item (list, LISTBOX_APPEND_AT_END, 0, current->label, current, FALSE);
+ break;
+ default:
+ break;
+ }
+
+ g_string_free (buff, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+unlink_entry (struct hotlist *entry)
+{
+ struct hotlist *current = current_group->head;
+
+ if (current == entry)
+ current_group->head = entry->next;
+ else
+ {
+ while (current != NULL && current->next != entry)
+ current = current->next;
+ if (current != NULL)
+ current->next = entry->next;
+ }
+ entry->next = entry->up = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS
+static void
+add_name_to_list (const char *path)
+{
+ listbox_add_item (l_hotlist, LISTBOX_APPEND_AT_END, 0, path, NULL, FALSE);
+}
+#endif /* !ENABLE_VFS */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+hotlist_run_cmd (int action)
+{
+ switch (action)
+ {
+ case B_MOVE:
+ {
+ struct hotlist *saved = current_group;
+ struct hotlist *item = NULL;
+ struct hotlist *moveto_item = NULL;
+ struct hotlist *moveto_group = NULL;
+ int ret;
+
+ if (listbox_is_empty (l_hotlist))
+ return 0; /* empty group - nothing to do */
+
+ listbox_get_current (l_hotlist, NULL, (void **) &item);
+ init_movelist (item);
+ hotlist_state.moving = TRUE;
+ ret = dlg_run (movelist_dlg);
+ hotlist_state.moving = FALSE;
+ listbox_get_current (l_movelist, NULL, (void **) &moveto_item);
+ moveto_group = current_group;
+ widget_destroy (WIDGET (movelist_dlg));
+ current_group = saved;
+ if (ret == B_CANCEL)
+ return 0;
+ if (moveto_item == item)
+ return 0; /* If we insert/append a before/after a
+ it hardly changes anything ;) */
+ unlink_entry (item);
+ listbox_remove_current (l_hotlist);
+ item->up = moveto_group;
+ if (moveto_group->head == NULL)
+ moveto_group->head = item;
+ else if (moveto_item == NULL)
+ { /* we have group with just comments */
+ struct hotlist *p = moveto_group->head;
+
+ /* skip comments */
+ while (p->next != NULL)
+ p = p->next;
+ p->next = item;
+ }
+ else if (ret == B_ENTER || ret == B_APPEND)
+ {
+ if (moveto_item->next == NULL)
+ moveto_item->next = item;
+ else
+ {
+ item->next = moveto_item->next;
+ moveto_item->next = item;
+ }
+ }
+ else if (moveto_group->head == moveto_item)
+ {
+ moveto_group->head = item;
+ item->next = moveto_item;
+ }
+ else
+ {
+ struct hotlist *p = moveto_group->head;
+
+ while (p->next != moveto_item)
+ p = p->next;
+ item->next = p->next;
+ p->next = item;
+ }
+ listbox_remove_list (l_hotlist);
+ fill_listbox (l_hotlist);
+ repaint_screen ();
+ hotlist_state.modified = TRUE;
+ return 0;
+ }
+ case B_REMOVE:
+ {
+ struct hotlist *entry = NULL;
+
+ listbox_get_current (l_hotlist, NULL, (void **) &entry);
+ remove_from_hotlist (entry);
+ }
+ return 0;
+
+ case B_NEW_GROUP:
+ add_new_group_cmd ();
+ return 0;
+
+ case B_ADD_CURRENT:
+ add2hotlist_cmd (our_panel);
+ return 0;
+
+ case B_NEW_ENTRY:
+ add_new_entry_cmd (our_panel);
+ return 0;
+
+ case B_ENTER:
+ case B_ENTER_GROUP:
+ {
+ WListbox *list;
+ void *data;
+ struct hotlist *hlp;
+
+ list = hotlist_state.moving ? l_movelist : l_hotlist;
+ listbox_get_current (list, NULL, &data);
+
+ if (data == NULL)
+ return 1;
+
+ hlp = (struct hotlist *) data;
+
+ if (hlp->type == HL_TYPE_ENTRY)
+ return (action == B_ENTER ? 1 : 0);
+ if (hlp->type != HL_TYPE_DOTDOT)
+ {
+ listbox_remove_list (list);
+ current_group = hlp;
+ fill_listbox (list);
+ return 0;
+ }
+ }
+ MC_FALLTHROUGH; /* if list empty - just go up */
+
+ case B_UP_GROUP:
+ {
+ WListbox *list = hotlist_state.moving ? l_movelist : l_hotlist;
+
+ listbox_remove_list (list);
+ current_group = current_group->up;
+ fill_listbox (list);
+ return 0;
+ }
+
+#ifdef ENABLE_VFS
+ case B_FREE_ALL_VFS:
+ vfs_expire (TRUE);
+ MC_FALLTHROUGH;
+
+ case B_REFRESH_VFS:
+ listbox_remove_list (l_hotlist);
+ listbox_add_item (l_hotlist, LISTBOX_APPEND_AT_END, 0, mc_config_get_home_dir (), NULL,
+ FALSE);
+ vfs_fill_names (add_name_to_list);
+ return 0;
+#endif /* ENABLE_VFS */
+
+ default:
+ return 1;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+hotlist_button_callback (WButton * button, int action)
+{
+ int ret;
+
+ (void) button;
+ ret = hotlist_run_cmd (action);
+ update_path_name ();
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline cb_ret_t
+hotlist_handle_key (WDialog * h, int key)
+{
+ switch (key)
+ {
+ case KEY_M_CTRL | '\n':
+ goto l1;
+
+ case '\n':
+ case KEY_ENTER:
+ if (hotlist_button_callback (NULL, B_ENTER) != 0)
+ {
+ h->ret_value = B_ENTER;
+ dlg_close (h);
+ }
+ return MSG_HANDLED;
+
+ case KEY_RIGHT:
+ /* enter to the group */
+ if (hotlist_state.type == LIST_VFSLIST)
+ return MSG_NOT_HANDLED;
+ return hotlist_button_callback (NULL, B_ENTER_GROUP) == 0 ? MSG_HANDLED : MSG_NOT_HANDLED;
+
+ case KEY_LEFT:
+ /* leave the group */
+ if (hotlist_state.type == LIST_VFSLIST)
+ return MSG_NOT_HANDLED;
+ return hotlist_button_callback (NULL, B_UP_GROUP) == 0 ? MSG_HANDLED : MSG_NOT_HANDLED;
+
+ case KEY_DC:
+ if (hotlist_state.moving)
+ return MSG_NOT_HANDLED;
+ hotlist_button_callback (NULL, B_REMOVE);
+ return MSG_HANDLED;
+
+ l1:
+ case ALT ('\n'):
+ case ALT ('\r'):
+ if (!hotlist_state.moving)
+ {
+ void *ldata = NULL;
+
+ listbox_get_current (l_hotlist, NULL, &ldata);
+
+ if (ldata != NULL)
+ {
+ struct hotlist *hlp = (struct hotlist *) ldata;
+
+ if (hlp->type == HL_TYPE_ENTRY)
+ {
+ char *tmp;
+
+ tmp = g_strconcat ("cd ", hlp->directory, (char *) NULL);
+ input_insert (cmdline, tmp, FALSE);
+ g_free (tmp);
+ h->ret_value = B_CANCEL;
+ dlg_close (h);
+ }
+ }
+ }
+ return MSG_HANDLED; /* ignore key */
+
+ default:
+ return MSG_NOT_HANDLED;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+hotlist_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ case MSG_NOTIFY: /* MSG_NOTIFY is fired by the listbox to tell us the item has changed. */
+ update_path_name ();
+ return MSG_HANDLED;
+
+ case MSG_UNHANDLED_KEY:
+ return hotlist_handle_key (h, parm);
+
+ case MSG_POST_KEY:
+ /*
+ * The code here has two purposes:
+ *
+ * (1) Always stay on the hotlist.
+ *
+ * Activating a button using its hotkey (and even pressing ENTER, as
+ * there's a "default button") moves the focus to the button. But we
+ * want to stay on the hotlist, to be able to use the usual keys (up,
+ * down, etc.). So we do `widget_select (lst)`.
+ *
+ * (2) Refresh the hotlist.
+ *
+ * We may have run a command that changed the contents of the list.
+ * We therefore need to refresh it. So we do `widget_draw (lst)`.
+ */
+ {
+ Widget *lst;
+
+ lst = WIDGET (h == hotlist_dlg ? l_hotlist : l_movelist);
+
+ /* widget_select() already redraws the widget, but since it's a
+ * no-op if the widget is already selected ("focused"), we have
+ * to call widget_draw() separately. */
+ if (!widget_get_state (lst, WST_FOCUSED))
+ widget_select (lst);
+ else
+ widget_draw (lst);
+ }
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ {
+ WRect r = w->rect;
+
+ r.lines = LINES - (h == hotlist_dlg ? 2 : 6);
+ r.cols = COLS - 6;
+
+ return dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
+ }
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static lcback_ret_t
+hotlist_listbox_callback (WListbox * list)
+{
+ WDialog *dlg = DIALOG (WIDGET (list)->owner);
+
+ if (!listbox_is_empty (list))
+ {
+ void *data = NULL;
+
+ listbox_get_current (list, NULL, &data);
+
+ if (data != NULL)
+ {
+ struct hotlist *hlp = (struct hotlist *) data;
+
+ if (hlp->type == HL_TYPE_ENTRY)
+ {
+ dlg->ret_value = B_ENTER;
+ dlg_close (dlg);
+ return LISTBOX_DONE;
+ }
+ else
+ {
+ hotlist_button_callback (NULL, B_ENTER);
+ send_message (dlg, NULL, MSG_POST_KEY, '\n', NULL);
+ return LISTBOX_CONT;
+ }
+ }
+ else
+ {
+ dlg->ret_value = B_ENTER;
+ dlg_close (dlg);
+ return LISTBOX_DONE;
+ }
+ }
+
+ hotlist_button_callback (NULL, B_UP_GROUP);
+ send_message (dlg, NULL, MSG_POST_KEY, 'u', NULL);
+ return LISTBOX_CONT;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Expands all button names (once) and recalculates button positions.
+ * returns number of columns in the dialog box, which is 10 chars longer
+ * then buttonbar.
+ *
+ * If common width of the window (i.e. in xterm) is less than returned
+ * width - sorry :) (anyway this did not handled in previous version too)
+ */
+
+static int
+init_i18n_stuff (int list_type, int cols)
+{
+ size_t i;
+
+ static gboolean i18n_flag = FALSE;
+
+ if (!i18n_flag)
+ {
+ for (i = 0; i < hotlist_but_num; i++)
+ {
+#ifdef ENABLE_NLS
+ hotlist_but[i].text = _(hotlist_but[i].text);
+#endif /* ENABLE_NLS */
+ hotlist_but[i].len = str_term_width1 (hotlist_but[i].text) + 3;
+ if (hotlist_but[i].flags == DEFPUSH_BUTTON)
+ hotlist_but[i].len += 2;
+ }
+
+ i18n_flag = TRUE;
+ }
+
+ /* Dynamic resizing of buttonbars */
+ {
+ int len[2], count[2]; /* at most two lines of buttons */
+ int cur_x[2];
+
+ len[0] = len[1] = 0;
+ count[0] = count[1] = 0;
+ cur_x[0] = cur_x[1] = 0;
+
+ /* Count len of buttonbars, assuming 1 extra space between buttons */
+ for (i = 0; i < hotlist_but_num; i++)
+ if ((hotlist_but[i].type & list_type) != 0)
+ {
+ int row;
+
+ row = hotlist_but[i].y;
+ ++count[row];
+ len[row] += hotlist_but[i].len + 1;
+ }
+
+ (len[0])--;
+ (len[1])--;
+
+ cols = MAX (cols, MAX (len[0], len[1]));
+
+ /* arrange buttons */
+ for (i = 0; i < hotlist_but_num; i++)
+ if ((hotlist_but[i].type & list_type) != 0)
+ {
+ int row;
+
+ row = hotlist_but[i].y;
+
+ if (hotlist_but[i].x != 0)
+ {
+ /* not first int the row */
+ if (hotlist_but[i].ret_cmd == B_CANCEL)
+ hotlist_but[i].x = cols - hotlist_but[i].len - 6;
+ else
+ hotlist_but[i].x = cur_x[row];
+ }
+
+ cur_x[row] += hotlist_but[i].len + 1;
+ }
+ }
+
+ return cols;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+init_hotlist (hotlist_t list_type)
+{
+ size_t i;
+ const char *title, *help_node;
+ int lines, cols;
+ int y;
+ int dh = 0;
+ WGroup *g;
+ WGroupbox *path_box;
+ Widget *hotlist_widget;
+
+ do_refresh ();
+
+ lines = LINES - 2;
+ cols = init_i18n_stuff (list_type, COLS - 6);
+
+#ifdef ENABLE_VFS
+ if (list_type == LIST_VFSLIST)
+ {
+ title = _("Active VFS directories");
+ help_node = "[vfshot]"; /* FIXME - no such node */
+ dh = 1;
+ }
+ else
+#endif /* !ENABLE_VFS */
+ {
+ title = _("Directory hotlist");
+ help_node = "[Hotlist]";
+ }
+
+ hotlist_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, hotlist_callback,
+ NULL, help_node, title);
+ g = GROUP (hotlist_dlg);
+
+ y = UY;
+ hotlist_group = groupbox_new (y, UX, lines - 10 + dh, cols - 2 * UX, _("Top level group"));
+ hotlist_widget = WIDGET (hotlist_group);
+ group_add_widget_autopos (g, hotlist_widget, WPOS_KEEP_ALL, NULL);
+
+ l_hotlist =
+ listbox_new (y + 1, UX + 1, hotlist_widget->rect.lines - 2, hotlist_widget->rect.cols - 2,
+ FALSE, hotlist_listbox_callback);
+
+ /* Fill the hotlist with the active VFS or the hotlist */
+#ifdef ENABLE_VFS
+ if (list_type == LIST_VFSLIST)
+ {
+ listbox_add_item (l_hotlist, LISTBOX_APPEND_AT_END, 0, mc_config_get_home_dir (), NULL,
+ FALSE);
+ vfs_fill_names (add_name_to_list);
+ }
+ else
+#endif /* !ENABLE_VFS */
+ fill_listbox (l_hotlist);
+
+ /* insert before groupbox to view scrollbar */
+ group_add_widget_autopos (g, l_hotlist, WPOS_KEEP_ALL, NULL);
+
+ y += hotlist_widget->rect.lines;
+
+ path_box = groupbox_new (y, UX, 3, hotlist_widget->rect.cols, _("Directory path"));
+ group_add_widget_autopos (g, path_box, WPOS_KEEP_BOTTOM | WPOS_KEEP_HORZ, NULL);
+
+ pname = label_new (y + 1, UX + 2, NULL);
+ group_add_widget_autopos (g, pname, WPOS_KEEP_BOTTOM | WPOS_KEEP_LEFT, NULL);
+ y += WIDGET (path_box)->rect.lines;
+
+ group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
+
+ for (i = 0; i < hotlist_but_num; i++)
+ if ((hotlist_but[i].type & list_type) != 0)
+ group_add_widget_autopos (g,
+ button_new (y + hotlist_but[i].y, UX + hotlist_but[i].x,
+ hotlist_but[i].ret_cmd, hotlist_but[i].flags,
+ hotlist_but[i].text, hotlist_button_callback),
+ hotlist_but[i].pos_flags, NULL);
+
+ widget_select (WIDGET (l_hotlist));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+init_movelist (struct hotlist *item)
+{
+ size_t i;
+ char *hdr;
+ int lines, cols;
+ int y;
+ WGroup *g;
+ Widget *movelist_widget;
+
+ do_refresh ();
+
+ lines = LINES - 6;
+ cols = init_i18n_stuff (LIST_MOVELIST, COLS - 6);
+
+ hdr = g_strdup_printf (_("Moving %s"), item->label);
+
+ movelist_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, hotlist_callback,
+ NULL, "[Hotlist]", hdr);
+ g = GROUP (movelist_dlg);
+
+ g_free (hdr);
+
+ y = UY;
+ movelist_group = groupbox_new (y, UX, lines - 7, cols - 2 * UX, _("Directory label"));
+ movelist_widget = WIDGET (movelist_group);
+ group_add_widget_autopos (g, movelist_widget, WPOS_KEEP_ALL, NULL);
+
+ l_movelist =
+ listbox_new (y + 1, UX + 1, movelist_widget->rect.lines - 2, movelist_widget->rect.cols - 2,
+ FALSE, hotlist_listbox_callback);
+ fill_listbox (l_movelist);
+ /* insert before groupbox to view scrollbar */
+ group_add_widget_autopos (g, l_movelist, WPOS_KEEP_ALL, NULL);
+
+ y += movelist_widget->rect.lines;
+
+ group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
+
+ for (i = 0; i < hotlist_but_num; i++)
+ if ((hotlist_but[i].type & LIST_MOVELIST) != 0)
+ group_add_widget_autopos (g,
+ button_new (y + hotlist_but[i].y, UX + hotlist_but[i].x,
+ hotlist_but[i].ret_cmd, hotlist_but[i].flags,
+ hotlist_but[i].text, hotlist_button_callback),
+ hotlist_but[i].pos_flags, NULL);
+
+ widget_select (WIDGET (l_movelist));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Destroy the list dialog.
+ * Don't confuse with done_hotlist() for the list in memory.
+ */
+
+static void
+hotlist_done (void)
+{
+ widget_destroy (WIDGET (hotlist_dlg));
+ l_hotlist = NULL;
+#if 0
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+#endif
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline char *
+find_group_section (struct hotlist *grp)
+{
+ return g_strconcat (grp->directory, ".Group", (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct hotlist *
+add2hotlist (char *label, char *directory, enum HotListType type, listbox_append_t pos)
+{
+ struct hotlist *new;
+ struct hotlist *current = NULL;
+
+ /*
+ * Hotlist is neither loaded nor loading.
+ * Must be called by "Ctrl-x a" before using hotlist.
+ */
+ if (current_group == NULL)
+ load_hotlist ();
+
+ listbox_get_current (l_hotlist, NULL, (void **) &current);
+
+ /* Make sure '..' stays at the top of the list. */
+ if ((current != NULL) && (current->type == HL_TYPE_DOTDOT))
+ pos = LISTBOX_APPEND_AFTER;
+
+ new = g_new0 (struct hotlist, 1);
+
+ new->type = type;
+ new->label = label;
+ new->directory = directory;
+ new->up = current_group;
+
+ if (type == HL_TYPE_GROUP)
+ {
+ current_group = new;
+ add_dotdot_to_list ();
+ current_group = new->up;
+ }
+
+ if (current_group->head == NULL)
+ {
+ /* first element in group */
+ current_group->head = new;
+ }
+ else if (pos == LISTBOX_APPEND_AFTER)
+ {
+ new->next = current->next;
+ current->next = new;
+ }
+ else if (pos == LISTBOX_APPEND_BEFORE && current == current_group->head)
+ {
+ /* should be inserted before first item */
+ new->next = current;
+ current_group->head = new;
+ }
+ else if (pos == LISTBOX_APPEND_BEFORE)
+ {
+ struct hotlist *p = current_group->head;
+
+ while (p->next != current)
+ p = p->next;
+
+ new->next = current;
+ p->next = new;
+ }
+ else
+ { /* append at the end */
+ struct hotlist *p = current_group->head;
+
+ while (p->next != NULL)
+ p = p->next;
+
+ p->next = new;
+ }
+
+ if (hotlist_state.running && type != HL_TYPE_COMMENT && type != HL_TYPE_DOTDOT)
+ {
+ if (type == HL_TYPE_GROUP)
+ {
+ char *lbl;
+
+ lbl = g_strconcat ("->", new->label, (char *) NULL);
+ listbox_add_item (l_hotlist, pos, 0, lbl, new, FALSE);
+ g_free (lbl);
+ }
+ else
+ listbox_add_item (l_hotlist, pos, 0, new->label, new, FALSE);
+ listbox_set_current (l_hotlist, l_hotlist->current);
+ }
+
+ return new;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+add_new_entry_input (const char *header, const char *text1, const char *text2,
+ const char *help, char **r1, char **r2)
+{
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (text1, input_label_above, *r1, "input-lbl", r1, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_LABELED_INPUT (text2, input_label_above, *r2, "input-lbl", r2, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD),
+ QUICK_START_BUTTONS (TRUE, TRUE),
+ QUICK_BUTTON (N_("&Append"), B_APPEND, NULL, NULL),
+ QUICK_BUTTON (N_("&Insert"), B_INSERT, NULL, NULL),
+ QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 64 };
+
+ quick_dialog_t qdlg = {
+ r, header, help,
+ quick_widgets, NULL, NULL
+ };
+
+ int ret;
+
+ ret = quick_dialog (&qdlg);
+
+ return (ret != B_CANCEL) ? ret : 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+add_new_entry_cmd (WPanel * panel)
+{
+ char *title, *url, *to_free;
+ int ret;
+
+ /* Take current directory as default value for input fields */
+ to_free = title = url = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD);
+
+ ret = add_new_entry_input (_("New hotlist entry"), _("Directory label:"),
+ _("Directory path:"), "[Hotlist]", &title, &url);
+ g_free (to_free);
+
+ if (ret == 0)
+ return;
+ if (title == NULL || *title == '\0' || url == NULL || *url == '\0')
+ {
+ g_free (title);
+ g_free (url);
+ return;
+ }
+
+ if (ret == B_ENTER || ret == B_APPEND)
+ add2hotlist (title, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AFTER);
+ else
+ add2hotlist (title, url, HL_TYPE_ENTRY, LISTBOX_APPEND_BEFORE);
+
+ hotlist_state.modified = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+add_new_group_input (const char *header, const char *label, char **result)
+{
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (label, input_label_above, "", "input", result, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_START_BUTTONS (TRUE, TRUE),
+ QUICK_BUTTON (N_("&Append"), B_APPEND, NULL, NULL),
+ QUICK_BUTTON (N_("&Insert"), B_INSERT, NULL, NULL),
+ QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 64 };
+
+ quick_dialog_t qdlg = {
+ r, header, "[Hotlist]",
+ quick_widgets, NULL, NULL
+ };
+
+ int ret;
+
+ ret = quick_dialog (&qdlg);
+
+ return (ret != B_CANCEL) ? ret : 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+add_new_group_cmd (void)
+{
+ char *label;
+ int ret;
+
+ ret = add_new_group_input (_("New hotlist group"), _("Name of new group:"), &label);
+ if (ret == 0 || label == NULL || *label == '\0')
+ return;
+
+ if (ret == B_ENTER || ret == B_APPEND)
+ add2hotlist (label, 0, HL_TYPE_GROUP, LISTBOX_APPEND_AFTER);
+ else
+ add2hotlist (label, 0, HL_TYPE_GROUP, LISTBOX_APPEND_BEFORE);
+
+ hotlist_state.modified = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+remove_group (struct hotlist *grp)
+{
+ struct hotlist *current = grp->head;
+
+ while (current != NULL)
+ {
+ struct hotlist *next = current->next;
+
+ if (current->type == HL_TYPE_GROUP)
+ remove_group (current);
+
+ g_free (current->label);
+ g_free (current->directory);
+ g_free (current);
+
+ current = next;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+remove_from_hotlist (struct hotlist *entry)
+{
+ if (entry == NULL)
+ return;
+
+ if (entry->type == HL_TYPE_DOTDOT)
+ return;
+
+ if (confirm_directory_hotlist_delete)
+ {
+ char text[BUF_MEDIUM];
+ int result;
+
+ if (safe_delete)
+ query_set_sel (1);
+
+ g_snprintf (text, sizeof (text), _("Are you sure you want to remove entry \"%s\"?"),
+ str_trunc (entry->label, 30));
+ result = query_dialog (Q_ ("DialogTitle|Delete"), text, D_ERROR | D_CENTER, 2,
+ _("&Yes"), _("&No"));
+ if (result != 0)
+ return;
+ }
+
+ if (entry->type == HL_TYPE_GROUP)
+ {
+ struct hotlist *head = entry->head;
+
+ if (head != NULL && (head->type != HL_TYPE_DOTDOT || head->next != NULL))
+ {
+ char text[BUF_MEDIUM];
+ int result;
+
+ g_snprintf (text, sizeof (text), _("Group \"%s\" is not empty.\nRemove it?"),
+ str_trunc (entry->label, 30));
+ result = query_dialog (Q_ ("DialogTitle|Delete"), text, D_ERROR | D_CENTER, 2,
+ _("&Yes"), _("&No"));
+ if (result != 0)
+ return;
+ }
+
+ remove_group (entry);
+ }
+
+ unlink_entry (entry);
+
+ g_free (entry->label);
+ g_free (entry->directory);
+ g_free (entry);
+ /* now remove list entry from screen */
+ listbox_remove_current (l_hotlist);
+ hotlist_state.modified = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+load_group (struct hotlist *grp)
+{
+ gchar **profile_keys, **keys;
+ char *group_section;
+ struct hotlist *current = 0;
+
+ group_section = find_group_section (grp);
+
+ keys = mc_config_get_keys (mc_global.main_config, group_section, NULL);
+
+ current_group = grp;
+
+ for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
+ add2hotlist (mc_config_get_string (mc_global.main_config, group_section, *profile_keys, ""),
+ g_strdup (*profile_keys), HL_TYPE_GROUP, LISTBOX_APPEND_AT_END);
+
+ g_strfreev (keys);
+
+ keys = mc_config_get_keys (mc_global.main_config, grp->directory, NULL);
+
+ for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
+ add2hotlist (mc_config_get_string (mc_global.main_config, group_section, *profile_keys, ""),
+ g_strdup (*profile_keys), HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
+
+ g_free (group_section);
+ g_strfreev (keys);
+
+ for (current = grp->head; current; current = current->next)
+ load_group (current);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+hot_skip_blanks (void)
+{
+ int c;
+
+ while ((c = getc (hotlist_file)) != EOF && c != '\n' && g_ascii_isspace (c))
+ ;
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+hot_next_token (void)
+{
+ int c, ret = 0;
+ size_t l;
+
+ if (tkn_buf == NULL)
+ tkn_buf = g_string_new ("");
+ g_string_set_size (tkn_buf, 0);
+
+ again:
+ c = hot_skip_blanks ();
+ switch (c)
+ {
+ case EOF:
+ ret = TKN_EOF;
+ break;
+ case '\n':
+ ret = TKN_EOL;
+ break;
+ case '#':
+ while ((c = getc (hotlist_file)) != EOF && c != '\n')
+ g_string_append_c (tkn_buf, c);
+ ret = TKN_COMMENT;
+ break;
+ case '"':
+ while ((c = getc (hotlist_file)) != EOF && c != '"')
+ {
+ if (c == '\\')
+ {
+ c = getc (hotlist_file);
+ if (c == EOF)
+ {
+ g_string_free (tkn_buf, TRUE);
+ return TKN_EOF;
+ }
+ }
+ g_string_append_c (tkn_buf, c == '\n' ? ' ' : c);
+ }
+ ret = (c == EOF) ? TKN_EOF : TKN_STRING;
+ break;
+ case '\\':
+ c = getc (hotlist_file);
+ if (c == EOF)
+ {
+ g_string_free (tkn_buf, TRUE);
+ return TKN_EOF;
+ }
+ if (c == '\n')
+ goto again;
+
+ MC_FALLTHROUGH; /* it is taken as normal character */
+
+ default:
+ do
+ {
+ g_string_append_c (tkn_buf, g_ascii_toupper (c));
+ }
+ while ((c = fgetc (hotlist_file)) != EOF && (g_ascii_isalnum (c) || !isascii (c)));
+ if (c != EOF)
+ ungetc (c, hotlist_file);
+ l = tkn_buf->len;
+ if (strncmp (tkn_buf->str, "GROUP", l) == 0)
+ ret = TKN_GROUP;
+ else if (strncmp (tkn_buf->str, "ENTRY", l) == 0)
+ ret = TKN_ENTRY;
+ else if (strncmp (tkn_buf->str, "ENDGROUP", l) == 0)
+ ret = TKN_ENDGROUP;
+ else if (strncmp (tkn_buf->str, "URL", l) == 0)
+ ret = TKN_URL;
+ else
+ ret = TKN_UNKNOWN;
+ break;
+ }
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+hot_load_group (struct hotlist *grp)
+{
+ int tkn;
+ struct hotlist *new_grp;
+ char *label, *url;
+
+ current_group = grp;
+
+ while ((tkn = hot_next_token ()) != TKN_ENDGROUP)
+ switch (tkn)
+ {
+ case TKN_GROUP:
+ CHECK_TOKEN (TKN_STRING);
+ new_grp =
+ add2hotlist (g_strndup (tkn_buf->str, tkn_buf->len), 0, HL_TYPE_GROUP,
+ LISTBOX_APPEND_AT_END);
+ SKIP_TO_EOL;
+ hot_load_group (new_grp);
+ current_group = grp;
+ break;
+ case TKN_ENTRY:
+ {
+ CHECK_TOKEN (TKN_STRING);
+ label = g_strndup (tkn_buf->str, tkn_buf->len);
+ CHECK_TOKEN (TKN_URL);
+ CHECK_TOKEN (TKN_STRING);
+ url = tilde_expand (tkn_buf->str);
+ add2hotlist (label, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
+ SKIP_TO_EOL;
+ }
+ break;
+ case TKN_COMMENT:
+ label = g_strndup (tkn_buf->str, tkn_buf->len);
+ add2hotlist (label, 0, HL_TYPE_COMMENT, LISTBOX_APPEND_AT_END);
+ break;
+ case TKN_EOF:
+ hotlist_state.readonly = TRUE;
+ hotlist_state.file_error = TRUE;
+ return;
+ case TKN_EOL:
+ /* skip empty lines */
+ break;
+ default:
+ hotlist_state.readonly = TRUE;
+ hotlist_state.file_error = TRUE;
+ SKIP_TO_EOL;
+ break;
+ }
+ SKIP_TO_EOL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+hot_load_file (struct hotlist *grp)
+{
+ int tkn;
+ struct hotlist *new_grp;
+ char *label, *url;
+
+ current_group = grp;
+
+ while ((tkn = hot_next_token ()) != TKN_EOF)
+ switch (tkn)
+ {
+ case TKN_GROUP:
+ CHECK_TOKEN (TKN_STRING);
+ new_grp =
+ add2hotlist (g_strndup (tkn_buf->str, tkn_buf->len), 0, HL_TYPE_GROUP,
+ LISTBOX_APPEND_AT_END);
+ SKIP_TO_EOL;
+ hot_load_group (new_grp);
+ current_group = grp;
+ break;
+ case TKN_ENTRY:
+ {
+ CHECK_TOKEN (TKN_STRING);
+ label = g_strndup (tkn_buf->str, tkn_buf->len);
+ CHECK_TOKEN (TKN_URL);
+ CHECK_TOKEN (TKN_STRING);
+ url = tilde_expand (tkn_buf->str);
+ add2hotlist (label, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
+ SKIP_TO_EOL;
+ }
+ break;
+ case TKN_COMMENT:
+ label = g_strndup (tkn_buf->str, tkn_buf->len);
+ add2hotlist (label, 0, HL_TYPE_COMMENT, LISTBOX_APPEND_AT_END);
+ break;
+ case TKN_EOL:
+ /* skip empty lines */
+ break;
+ default:
+ hotlist_state.readonly = TRUE;
+ hotlist_state.file_error = TRUE;
+ SKIP_TO_EOL;
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+clean_up_hotlist_groups (const char *section)
+{
+ char *grp_section;
+
+ grp_section = g_strconcat (section, ".Group", (char *) NULL);
+ if (mc_config_has_group (mc_global.main_config, section))
+ mc_config_del_group (mc_global.main_config, section);
+
+ if (mc_config_has_group (mc_global.main_config, grp_section))
+ {
+ char **profile_keys, **keys;
+
+ keys = mc_config_get_keys (mc_global.main_config, grp_section, NULL);
+
+ for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
+ clean_up_hotlist_groups (*profile_keys);
+
+ g_strfreev (keys);
+ mc_config_del_group (mc_global.main_config, grp_section);
+ }
+ g_free (grp_section);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+load_hotlist (void)
+{
+ gboolean remove_old_list = FALSE;
+ struct stat stat_buf;
+
+ if (hotlist_state.loaded)
+ {
+ stat (hotlist_file_name, &stat_buf);
+ if (hotlist_file_mtime < stat_buf.st_mtime)
+ done_hotlist ();
+ else
+ return;
+ }
+
+ if (hotlist_file_name == NULL)
+ hotlist_file_name = mc_config_get_full_path (MC_HOTLIST_FILE);
+
+ hotlist = g_new0 (struct hotlist, 1);
+ hotlist->type = HL_TYPE_GROUP;
+ hotlist->label = g_strdup (_("Top level group"));
+ hotlist->up = hotlist;
+ /*
+ * compatibility :-(
+ */
+ hotlist->directory = g_strdup ("Hotlist");
+
+ hotlist_file = fopen (hotlist_file_name, "r");
+ if (hotlist_file == NULL)
+ {
+ int result;
+
+ load_group (hotlist);
+ hotlist_state.loaded = TRUE;
+ /*
+ * just to be sure we got copy
+ */
+ hotlist_state.modified = TRUE;
+ result = save_hotlist ();
+ hotlist_state.modified = FALSE;
+ if (result != 0)
+ remove_old_list = TRUE;
+ else
+ message (D_ERROR, _("Hotlist Load"),
+ _
+ ("MC was unable to write %s file,\nyour old hotlist entries were not deleted"),
+ MC_USERCONF_DIR PATH_SEP_STR MC_HOTLIST_FILE);
+ }
+ else
+ {
+ hot_load_file (hotlist);
+ fclose (hotlist_file);
+ hotlist_state.loaded = TRUE;
+ }
+
+ if (remove_old_list)
+ {
+ GError *mcerror = NULL;
+
+ clean_up_hotlist_groups ("Hotlist");
+ if (!mc_config_save_file (mc_global.main_config, &mcerror))
+ setup_save_config_show_error (mc_global.main_config->ini_path, &mcerror);
+
+ mc_error_message (&mcerror, NULL);
+ }
+
+ stat (hotlist_file_name, &stat_buf);
+ hotlist_file_mtime = stat_buf.st_mtime;
+ current_group = hotlist;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+hot_save_group (struct hotlist *grp)
+{
+ struct hotlist *current;
+ int i;
+ char *s;
+
+#define INDENT(n) \
+do { \
+ for (i = 0; i < n; i++) \
+ putc (' ', hotlist_file); \
+} while (0)
+
+ for (current = grp->head; current != NULL; current = current->next)
+ switch (current->type)
+ {
+ case HL_TYPE_GROUP:
+ INDENT (list_level);
+ fputs ("GROUP \"", hotlist_file);
+ for (s = current->label; *s != '\0'; s++)
+ {
+ if (*s == '"' || *s == '\\')
+ putc ('\\', hotlist_file);
+ putc (*s, hotlist_file);
+ }
+ fputs ("\"\n", hotlist_file);
+ list_level += 2;
+ hot_save_group (current);
+ list_level -= 2;
+ INDENT (list_level);
+ fputs ("ENDGROUP\n", hotlist_file);
+ break;
+ case HL_TYPE_ENTRY:
+ INDENT (list_level);
+ fputs ("ENTRY \"", hotlist_file);
+ for (s = current->label; *s != '\0'; s++)
+ {
+ if (*s == '"' || *s == '\\')
+ putc ('\\', hotlist_file);
+ putc (*s, hotlist_file);
+ }
+ fputs ("\" URL \"", hotlist_file);
+ for (s = current->directory; *s != '\0'; s++)
+ {
+ if (*s == '"' || *s == '\\')
+ putc ('\\', hotlist_file);
+ putc (*s, hotlist_file);
+ }
+ fputs ("\"\n", hotlist_file);
+ break;
+ case HL_TYPE_COMMENT:
+ fprintf (hotlist_file, "#%s\n", current->label);
+ break;
+ case HL_TYPE_DOTDOT:
+ /* do nothing */
+ break;
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+add_dotdot_to_list (void)
+{
+ if (current_group != hotlist && hotlist_has_dot_dot)
+ add2hotlist (g_strdup (".."), g_strdup (".."), HL_TYPE_DOTDOT, LISTBOX_APPEND_AT_END);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+add2hotlist_cmd (WPanel * panel)
+{
+ char *lc_prompt;
+ const char *cp = N_("Label for \"%s\":");
+ int l;
+ char *label_string, *label;
+
+#ifdef ENABLE_NLS
+ cp = _(cp);
+#endif
+
+ /* extra variable to use it in the button callback */
+ our_panel = panel;
+
+ l = str_term_width1 (cp);
+ label_string = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD);
+ lc_prompt = g_strdup_printf (cp, str_trunc (label_string, COLS - 2 * UX - (l + 8)));
+ label =
+ input_dialog (_("Add to hotlist"), lc_prompt, MC_HISTORY_HOTLIST_ADD, label_string,
+ INPUT_COMPLETE_NONE);
+ g_free (lc_prompt);
+
+ if (label == NULL || *label == '\0')
+ {
+ g_free (label_string);
+ g_free (label);
+ }
+ else
+ {
+ add2hotlist (label, label_string, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
+ hotlist_state.modified = TRUE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+hotlist_show (hotlist_t list_type, WPanel * panel)
+{
+ char *target = NULL;
+ int res;
+
+ /* extra variable to use it in the button callback */
+ our_panel = panel;
+
+ hotlist_state.type = list_type;
+ load_hotlist ();
+
+ init_hotlist (list_type);
+
+ /* display file info */
+ tty_setcolor (SELECTED_COLOR);
+
+ hotlist_state.running = TRUE;
+ res = dlg_run (hotlist_dlg);
+ hotlist_state.running = FALSE;
+ save_hotlist ();
+
+ if (res == B_ENTER)
+ {
+ char *text = NULL;
+ struct hotlist *hlp = NULL;
+
+ listbox_get_current (l_hotlist, &text, (void **) &hlp);
+ target = g_strdup (hlp != NULL ? hlp->directory : text);
+ }
+
+ hotlist_done ();
+ return target;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+save_hotlist (void)
+{
+ gboolean saved = FALSE;
+ struct stat stat_buf;
+
+ if (!hotlist_state.readonly && hotlist_state.modified && hotlist_file_name != NULL)
+ {
+ mc_util_make_backup_if_possible (hotlist_file_name, ".bak");
+
+ hotlist_file = fopen (hotlist_file_name, "w");
+ if (hotlist_file == NULL)
+ mc_util_restore_from_backup_if_possible (hotlist_file_name, ".bak");
+ else
+ {
+ hot_save_group (hotlist);
+ fclose (hotlist_file);
+ stat (hotlist_file_name, &stat_buf);
+ hotlist_file_mtime = stat_buf.st_mtime;
+ hotlist_state.modified = FALSE;
+ saved = TRUE;
+ }
+ }
+
+ return saved;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Unload list from memory.
+ * Don't confuse with hotlist_done() for GUI.
+ */
+
+void
+done_hotlist (void)
+{
+ if (hotlist != NULL)
+ {
+ remove_group (hotlist);
+ g_free (hotlist->label);
+ g_free (hotlist->directory);
+ MC_PTR_FREE (hotlist);
+ }
+
+ hotlist_state.loaded = FALSE;
+
+ MC_PTR_FREE (hotlist_file_name);
+ l_hotlist = NULL;
+ current_group = NULL;
+
+ if (tkn_buf != NULL)
+ {
+ g_string_free (tkn_buf, TRUE);
+ tkn_buf = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/hotlist.h b/src/filemanager/hotlist.h
new file mode 100644
index 0000000..94bc305
--- /dev/null
+++ b/src/filemanager/hotlist.h
@@ -0,0 +1,33 @@
+/** \file hotlist.h
+ * \brief Header: directory hotlist
+ */
+
+#ifndef MC__HOTLIST_H
+#define MC__HOTLIST_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#include "panel.h"
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ LIST_VFSLIST = 0x01,
+ LIST_HOTLIST = 0x02,
+ LIST_MOVELIST = 0x04
+} hotlist_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void add2hotlist_cmd (WPanel * panel);
+char *hotlist_show (hotlist_t list_type, WPanel * panel);
+gboolean save_hotlist (void);
+void done_hotlist (void);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__HOTLIST_H */
diff --git a/src/filemanager/info.c b/src/filemanager/info.c
new file mode 100644
index 0000000..790f820
--- /dev/null
+++ b/src/filemanager/info.c
@@ -0,0 +1,377 @@
+/*
+ Panel managing.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2023
+
+ 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 info.c
+ * \brief Source: panel managing
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <inttypes.h> /* PRIuMAX */
+
+#include "lib/global.h"
+#include "lib/unixcompat.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* is_idle() */
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/timefmt.h" /* file_date() */
+#include "lib/util.h"
+#include "lib/widget.h"
+
+#include "src/setup.h" /* panels_options */
+
+#include "filemanager.h" /* the_menubar */
+#include "layout.h"
+#include "mountlist.h"
+#ifdef ENABLE_EXT2FS_ATTR
+#include "cmd.h" /* chattr_get_as_str() */
+#endif
+
+#include "info.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+struct WInfo
+{
+ Widget widget;
+ gboolean ready;
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct my_statfs myfs_stats;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+info_box (WInfo * info)
+{
+ Widget *w = WIDGET (info);
+
+ const char *title = _("Information");
+ const int len = str_term_width1 (title);
+
+ tty_set_normal_attrs ();
+ tty_setcolor (NORMAL_COLOR);
+ widget_erase (w);
+ tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE);
+
+ widget_gotoyx (w, 0, (w->rect.cols - len - 2) / 2);
+ tty_printf (" %s ", title);
+
+ widget_gotoyx (w, 2, 0);
+ tty_print_alt_char (ACS_LTEE, FALSE);
+ widget_gotoyx (w, 2, w->rect.cols - 1);
+ tty_print_alt_char (ACS_RTEE, FALSE);
+ tty_draw_hline (w->rect.y + 2, w->rect.x + 1, ACS_HLINE, w->rect.cols - 2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+info_show_info (WInfo * info)
+{
+ const WRect *w = &CONST_WIDGET (info)->rect;
+ const file_entry_t *fe;
+ static int i18n_adjust = 0;
+ static const char *file_label;
+ GString *buff;
+ struct stat st;
+ char rp_cwd[PATH_MAX];
+ const char *p_rp_cwd;
+
+ if (!is_idle ())
+ return;
+
+ info_box (info);
+
+ tty_setcolor (MARKED_COLOR);
+ widget_gotoyx (w, 1, 3);
+ tty_printf (_("Midnight Commander %s"), mc_global.mc_version);
+
+ if (!info->ready)
+ return;
+
+ if (get_current_type () != view_listing)
+ return;
+
+ /* don't rely on vpath CWD when cd_symlinks enabled */
+ p_rp_cwd = mc_realpath (vfs_path_as_str (current_panel->cwd_vpath), rp_cwd);
+ if (p_rp_cwd == NULL)
+ p_rp_cwd = vfs_path_as_str (current_panel->cwd_vpath);
+
+ my_statfs (&myfs_stats, p_rp_cwd);
+
+ fe = panel_current_entry (current_panel);
+
+ st = fe->st;
+
+ /* Print only lines which fit */
+
+ if (i18n_adjust == 0)
+ {
+ /* This printf pattern string is used as a reference for size */
+ file_label = _("File: %s");
+ i18n_adjust = str_term_width1 (file_label) + 2;
+ }
+
+ tty_setcolor (NORMAL_COLOR);
+
+ buff = g_string_new ("");
+
+ switch (w->lines - 2)
+ {
+ /* Note: all cases are fall-throughs */
+
+ default:
+ MC_FALLTHROUGH;
+ case 17:
+ widget_gotoyx (w, 17, 3);
+ if ((myfs_stats.nfree == 0 && myfs_stats.nodes == 0) ||
+ (myfs_stats.nfree == (uintmax_t) (-1) && myfs_stats.nodes == (uintmax_t) (-1)))
+ tty_print_string (_("No node information"));
+ else if (myfs_stats.nfree == (uintmax_t) (-1))
+ tty_printf ("%s - / %" PRIuMAX, _("Free nodes:"), myfs_stats.nodes);
+ else if (myfs_stats.nodes == (uintmax_t) (-1))
+ tty_printf ("%s %" PRIuMAX " / -", _("Free nodes:"), myfs_stats.nfree);
+ else
+ tty_printf ("%s %" PRIuMAX " / %" PRIuMAX " (%d%%)",
+ _("Free nodes:"),
+ myfs_stats.nfree, myfs_stats.nodes,
+ myfs_stats.nodes == 0 ? 0 :
+ (int) (100 * (long double) myfs_stats.nfree / myfs_stats.nodes));
+ MC_FALLTHROUGH;
+ case 16:
+ widget_gotoyx (w, 16, 3);
+ if (myfs_stats.avail == 0 && myfs_stats.total == 0)
+ tty_print_string (_("No space information"));
+ else
+ {
+ char buffer1[6], buffer2[6];
+
+ size_trunc_len (buffer1, 5, myfs_stats.avail, 1, panels_options.kilobyte_si);
+ size_trunc_len (buffer2, 5, myfs_stats.total, 1, panels_options.kilobyte_si);
+ tty_printf (_("Free space: %s / %s (%d%%)"), buffer1, buffer2,
+ myfs_stats.total == 0 ? 0 :
+ (int) (100 * (long double) myfs_stats.avail / myfs_stats.total));
+ }
+ MC_FALLTHROUGH;
+ case 15:
+ widget_gotoyx (w, 15, 3);
+ tty_printf (_("Type: %s"),
+ myfs_stats.typename ? myfs_stats.typename : _("non-local vfs"));
+ if (myfs_stats.type != 0xffff && myfs_stats.type != -1)
+ tty_printf (" (%Xh)", (unsigned int) myfs_stats.type);
+ MC_FALLTHROUGH;
+ case 14:
+ widget_gotoyx (w, 14, 3);
+ str_printf (buff, _("Device: %s"),
+ str_trunc (myfs_stats.device, w->cols - i18n_adjust));
+ tty_print_string (buff->str);
+ g_string_set_size (buff, 0);
+ MC_FALLTHROUGH;
+ case 13:
+ widget_gotoyx (w, 13, 3);
+ str_printf (buff, _("Filesystem: %s"),
+ str_trunc (myfs_stats.mpoint, w->cols - i18n_adjust));
+ tty_print_string (buff->str);
+ g_string_set_size (buff, 0);
+ MC_FALLTHROUGH;
+ case 12:
+ widget_gotoyx (w, 12, 3);
+ str_printf (buff, _("Accessed: %s"), file_date (st.st_atime));
+ tty_print_string (buff->str);
+ g_string_set_size (buff, 0);
+ MC_FALLTHROUGH;
+ case 11:
+ widget_gotoyx (w, 11, 3);
+ str_printf (buff, _("Modified: %s"), file_date (st.st_mtime));
+ tty_print_string (buff->str);
+ g_string_set_size (buff, 0);
+ MC_FALLTHROUGH;
+ case 10:
+ widget_gotoyx (w, 10, 3);
+ /* The field st_ctime is changed by writing or by setting inode
+ information (i.e., owner, group, link count, mode, etc.). */
+ /* TRANSLATORS: Time of last status change as in stat(2) man. */
+ str_printf (buff, _("Changed: %s"), file_date (st.st_ctime));
+ tty_print_string (buff->str);
+ g_string_set_size (buff, 0);
+ MC_FALLTHROUGH;
+ case 9:
+ widget_gotoyx (w, 9, 3);
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ if (S_ISCHR (st.st_mode) || S_ISBLK (st.st_mode))
+ tty_printf (_("Dev. type: major %lu, minor %lu"),
+ (unsigned long) major (st.st_rdev), (unsigned long) minor (st.st_rdev));
+ else
+#endif
+ {
+ char buffer[10];
+ size_trunc_len (buffer, 9, st.st_size, 0, panels_options.kilobyte_si);
+ tty_printf (_("Size: %s"), buffer);
+#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
+ tty_printf (ngettext (" (%lu block)", " (%lu blocks)",
+ (unsigned long) st.st_blocks), (unsigned long) st.st_blocks);
+#endif
+ }
+ MC_FALLTHROUGH;
+ case 8:
+ widget_gotoyx (w, 8, 3);
+ tty_printf (_("Owner: %s/%s"), get_owner (st.st_uid), get_group (st.st_gid));
+ MC_FALLTHROUGH;
+ case 7:
+ widget_gotoyx (w, 7, 3);
+ tty_printf (_("Links: %d"), (int) st.st_nlink);
+ MC_FALLTHROUGH;
+ case 6:
+ widget_gotoyx (w, 6, 3);
+
+ {
+ vfs_path_t *vpath;
+#ifdef ENABLE_EXT2FS_ATTR
+ unsigned long attr;
+#endif
+
+ vpath = vfs_path_from_str (fe->fname->str);
+
+#ifdef ENABLE_EXT2FS_ATTR
+ if (mc_fgetflags (vpath, &attr) == 0)
+ tty_printf (_("Attributes: %s"), chattr_get_as_str (attr));
+ else
+#endif
+ tty_print_string (_("Attributes: unavailable"));
+
+ vfs_path_free (vpath, TRUE);
+ }
+ MC_FALLTHROUGH;
+ case 5:
+ widget_gotoyx (w, 5, 3);
+ tty_printf (_("Mode: %s (%04o)"),
+ string_perm (st.st_mode), (unsigned) st.st_mode & 07777);
+ MC_FALLTHROUGH;
+ case 4:
+ widget_gotoyx (w, 4, 3);
+ tty_printf (_("Location: %Xh:%Xh"), (unsigned int) st.st_dev, (unsigned int) st.st_ino);
+ MC_FALLTHROUGH;
+ case 3:
+ {
+ const char *fname;
+
+ widget_gotoyx (w, 3, 2);
+ fname = fe->fname->str;
+ str_printf (buff, file_label, str_trunc (fname, w->cols - i18n_adjust));
+ tty_print_string (buff->str);
+ }
+ MC_FALLTHROUGH;
+ case 2:
+ MC_FALLTHROUGH;
+ case 1:
+ MC_FALLTHROUGH;
+ case 0:
+ ;
+ } /* switch */
+ g_string_free (buff, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+info_hook (void *data)
+{
+ WInfo *info = (WInfo *) data;
+ Widget *other_widget;
+
+ other_widget = get_panel_widget (get_current_index ());
+ if (!other_widget)
+ return;
+ if (widget_overlapped (WIDGET (info), other_widget))
+ return;
+
+ info->ready = TRUE;
+ info_show_info (info);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+info_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WInfo *info = (WInfo *) w;
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ init_my_statfs ();
+ add_hook (&select_file_hook, info_hook, info);
+ info->ready = FALSE;
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ info_hook (info);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ delete_hook (&select_file_hook, info_hook);
+ free_my_statfs ();
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WInfo *
+info_new (int y, int x, int lines, int cols)
+{
+ WRect r = { y, x, lines, cols };
+ WInfo *info;
+ Widget *w;
+
+ info = g_new (struct WInfo, 1);
+ w = WIDGET (info);
+ widget_init (w, &r, info_callback, NULL);
+
+ return info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/info.h b/src/filemanager/info.h
new file mode 100644
index 0000000..cba2592
--- /dev/null
+++ b/src/filemanager/info.h
@@ -0,0 +1,24 @@
+/** \file info.h
+ * \brief Header: panel managing
+ */
+
+#ifndef MC__INFO_H
+#define MC__INFO_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct WInfo;
+typedef struct WInfo WInfo;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WInfo *info_new (int y, int x, int lines, int cols);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__INFO_H */
diff --git a/src/filemanager/ioblksize.h b/src/filemanager/ioblksize.h
new file mode 100644
index 0000000..91aa633
--- /dev/null
+++ b/src/filemanager/ioblksize.h
@@ -0,0 +1,86 @@
+/* I/O block size definitions for coreutils
+ Copyright (C) 1989-2016 Free Software Foundation, Inc.
+
+ This program 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.
+
+ This program 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 this file _after_ system headers if possible. */
+
+/* sys/stat.h will already have been included by system.h. */
+#include "lib/stat-size.h"
+
+/* *INDENT-OFF* */
+
+/* As of May 2014, 128KiB is determined to be the minimum
+ blksize to best minimize system call overhead.
+ This can be tested with this script:
+
+ for i in $(seq 0 10); do
+ bs=$((1024*2**$i))
+ printf "%7s=" $bs
+ timeout --foreground -sINT 2 \
+ dd bs=$bs if=/dev/zero of=/dev/null 2>&1 \
+ | sed -n 's/.* \([0-9.]* [GM]B\/s\)/\1/p'
+ done
+
+ With the results shown for these systems:
+ system #1: 1.7GHz pentium-m with 400MHz DDR2 RAM, arch=i686
+ system #2: 2.1GHz i3-2310M with 1333MHz DDR3 RAM, arch=x86_64
+ system #3: 3.2GHz i7-970 with 1333MHz DDR3, arch=x86_64
+ system #4: 2.20GHz Xeon E5-2660 with 1333MHz DDR3, arch=x86_64
+ system #5: 2.30GHz i7-3615QM with 1600MHz DDR3, arch=x86_64
+ system #6: 1.30GHz i5-4250U with 1-channel 1600MHz DDR3, arch=x86_64
+ system #7: 3.55GHz IBM,8231-E2B with 1066MHz DDR3, POWER7 revision 2.1
+
+ per-system transfer rate (GB/s)
+ blksize #1 #2 #3 #4 #5 #6 #7
+ ------------------------------------------------------------------------
+ 1024 .73 1.7 2.6 .64 1.0 2.5 1.3
+ 2048 1.3 3.0 4.4 1.2 2.0 4.4 2.5
+ 4096 2.4 5.1 6.5 2.3 3.7 7.4 4.8
+ 8192 3.5 7.3 8.5 4.0 6.0 10.4 9.2
+ 16384 3.9 9.4 10.1 6.3 8.3 13.3 16.8
+ 32768 5.2 9.9 11.1 8.1 10.7 13.2 28.0
+ 65536 5.3 11.2 12.0 10.6 12.8 16.1 41.4
+ 131072 5.5 11.8 12.3 12.1 14.0 16.7 54.8
+ 262144 5.7 11.6 12.5 12.3 14.7 16.4 40.0
+ 524288 5.7 11.4 12.5 12.1 14.7 15.5 34.5
+ 1048576 5.8 11.4 12.6 12.2 14.9 15.7 36.5
+
+
+ Note that this is to minimize system call overhead.
+ Other values may be appropriate to minimize file system
+ or disk overhead. For example on my current GNU/Linux system
+ the readahead setting is 128KiB which was read using:
+
+ file="."
+ device=$(df --output=source --local "$file" | tail -n1)
+ echo $(( $(blockdev --getra $device) * 512 ))
+
+ However there isn't a portable way to get the above.
+ In the future we could use the above method if available
+ and default to io_blksize() if not.
+ */
+
+
+enum { IO_BUFSIZE = 128 * 1024 };
+
+/* *INDENT-ON* */
+
+static inline size_t
+io_blksize (struct stat sb)
+{
+ size_t blksize = ST_BLKSIZE (sb);
+
+ return MAX (IO_BUFSIZE, blksize);
+}
diff --git a/src/filemanager/layout.c b/src/filemanager/layout.c
new file mode 100644
index 0000000..c9d581f
--- /dev/null
+++ b/src/filemanager/layout.c
@@ -0,0 +1,1580 @@
+/*
+ Panel layout module for the Midnight Commander
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Janne Kukonlehto, 1995
+ Miguel de Icaza, 1995
+ Andrew Borodin <aborodin@vmail.ru>, 2011-2022
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Avi Kelman <patcherton.fixesthings@gmail.com>, 2013
+
+ 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 layout.c
+ * \brief Source: panel layout module
+ */
+
+#include <config.h>
+
+#include <pwd.h> /* for username in xterm title */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/tty/key.h"
+#include "lib/tty/mouse.h"
+#include "lib/mcconfig.h"
+#include "lib/vfs/vfs.h" /* vfs_get_cwd () */
+#include "lib/strutil.h"
+#include "lib/widget.h"
+#include "lib/event.h"
+#include "lib/util.h" /* mc_time_elapsed() */
+
+#include "src/consaver/cons.saver.h"
+#include "src/viewer/mcviewer.h" /* The view widget */
+#include "src/setup.h"
+#ifdef ENABLE_SUBSHELL
+#include "src/subshell/subshell.h"
+#endif
+
+#include "command.h"
+#include "filemanager.h"
+#include "tree.h"
+/* Needed for the extern declarations of integer parameters */
+#include "dir.h"
+#include "layout.h"
+#include "info.h" /* The Info widget */
+
+/*** global variables ****************************************************************************/
+
+panels_layout_t panels_layout = {
+ /* Set if the panels are split horizontally */
+ .horizontal_split = FALSE,
+
+ /* vertical split */
+ .vertical_equal = TRUE,
+ .left_panel_size = 0,
+
+ /* horizontal split */
+ .horizontal_equal = TRUE,
+ .top_panel_size = 0
+};
+
+/* Controls the display of the rotating dash on the verbose mode */
+gboolean nice_rotating_dash = TRUE;
+
+/* The number of output lines shown (if available) */
+int output_lines = 0;
+
+/* Set if the command prompt is to be displayed */
+gboolean command_prompt = TRUE;
+
+/* Set if the main menu is visible */
+gboolean menubar_visible = TRUE;
+
+/* Set to show current working dir in xterm window title */
+gboolean xterm_title = TRUE;
+
+/* Set to show free space on device assigned to current directory */
+gboolean free_space = TRUE;
+
+/* The starting line for the output of the subprogram */
+int output_start_y = 0;
+
+int ok_to_refresh = 1;
+
+/*** file scope macro definitions ****************************************************************/
+
+/* The maximum number of views managed by the create_panel routine */
+/* Must be at least two (for current and other). Please note that until */
+/* Janne gets around this, we will only manage two of them :-) */
+#define MAX_VIEWS 2
+
+/* Width 12 for a wee Quick (Hex) View */
+#define MINWIDTH 12
+#define MINHEIGHT 5
+
+#define B_2LEFT B_USER
+#define B_2RIGHT (B_USER + 1)
+#define B_PLUS (B_USER + 2)
+#define B_MINUS (B_USER + 3)
+
+#define LAYOUT_OPTIONS_COUNT G_N_ELEMENTS (check_options)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ gboolean menubar_visible;
+ gboolean command_prompt;
+ gboolean keybar_visible;
+ gboolean message_visible;
+ gboolean xterm_title;
+ gboolean free_space;
+ int output_lines;
+} layout_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct
+{
+ panel_view_mode_t type;
+ Widget *widget;
+ char *last_saved_dir; /* last view_list working directory */
+} panels[MAX_VIEWS] =
+{
+ /* *INDENT-OFF* */
+ /* init MAX_VIEWS items */
+ { view_listing, NULL, NULL},
+ { view_listing, NULL, NULL}
+ /* *INDENT-ON* */
+};
+
+static layout_t old_layout;
+static panels_layout_t old_panels_layout;
+
+static gboolean equal_split;
+static int _output_lines;
+
+static int height;
+
+static WRadio *radio_widget;
+
+static struct
+{
+ const char *text;
+ gboolean *variable;
+ WCheck *widget;
+} check_options[] =
+{
+ /* *INDENT-OFF* */
+ { N_("&Equal split"), &equal_split, NULL },
+ { N_("&Menubar visible"), &menubar_visible, NULL },
+ { N_("Command &prompt"), &command_prompt, NULL },
+ { N_("&Keybar visible"), &mc_global.keybar_visible, NULL },
+ { N_("H&intbar visible"), &mc_global.message_visible, NULL },
+ { N_("&XTerm window title"), &xterm_title, NULL },
+ { N_("&Show free space"), &free_space, NULL }
+ /* *INDENT-ON* */
+};
+
+static const char *output_lines_label = NULL;
+static int output_lines_label_len;
+
+static WButton *bleft_widget, *bright_widget;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* don't use max() macro to avoid double call of str_term_width1() in widget width calculation */
+#undef max
+
+static int
+max (int a, int b)
+{
+ return a > b ? a : b;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+check_split (panels_layout_t * layout)
+{
+ if (layout->horizontal_split)
+ {
+ if (layout->horizontal_equal)
+ layout->top_panel_size = height / 2;
+ else if (layout->top_panel_size < MINHEIGHT)
+ layout->top_panel_size = MINHEIGHT;
+ else if (layout->top_panel_size > height - MINHEIGHT)
+ layout->top_panel_size = height - MINHEIGHT;
+ }
+ else
+ {
+ int md_cols = CONST_WIDGET (filemanager)->rect.cols;
+
+ if (layout->vertical_equal)
+ layout->left_panel_size = md_cols / 2;
+ else if (layout->left_panel_size < MINWIDTH)
+ layout->left_panel_size = MINWIDTH;
+ else if (layout->left_panel_size > md_cols - MINWIDTH)
+ layout->left_panel_size = md_cols - MINWIDTH;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+update_split (const WDialog * h)
+{
+ /* Check split has to be done before testing if it changed, since
+ it can change due to calling check_split() as well */
+ check_split (&panels_layout);
+
+ if (panels_layout.horizontal_split)
+ check_options[0].widget->state = panels_layout.horizontal_equal;
+ else
+ check_options[0].widget->state = panels_layout.vertical_equal;
+ widget_draw (WIDGET (check_options[0].widget));
+
+ tty_setcolor (check_options[0].widget->state ? DISABLED_COLOR : COLOR_NORMAL);
+
+ widget_gotoyx (h, 6, 5);
+ if (panels_layout.horizontal_split)
+ tty_printf ("%03d", panels_layout.top_panel_size);
+ else
+ tty_printf ("%03d", panels_layout.left_panel_size);
+
+ widget_gotoyx (h, 6, 17);
+ if (panels_layout.horizontal_split)
+ tty_printf ("%03d", height - panels_layout.top_panel_size);
+ else
+ tty_printf ("%03d", CONST_WIDGET (filemanager)->rect.cols - panels_layout.left_panel_size);
+
+ widget_gotoyx (h, 6, 12);
+ tty_print_char ('=');
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+b_left_right_cback (WButton * button, int action)
+{
+ (void) action;
+
+ if (button == bright_widget)
+ {
+ if (panels_layout.horizontal_split)
+ panels_layout.top_panel_size++;
+ else
+ panels_layout.left_panel_size++;
+ }
+ else
+ {
+ if (panels_layout.horizontal_split)
+ panels_layout.top_panel_size--;
+ else
+ panels_layout.left_panel_size--;
+ }
+
+ update_split (DIALOG (WIDGET (button)->owner));
+ layout_change ();
+ do_refresh ();
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+bplus_cback (WButton * button, int action)
+{
+ (void) button;
+ (void) action;
+
+ if (_output_lines < 99)
+ _output_lines++;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+bminus_cback (WButton * button, int action)
+{
+ (void) button;
+ (void) action;
+
+ if (_output_lines > 0)
+ _output_lines--;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+layout_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_DRAW:
+ frame_callback (w, NULL, MSG_DRAW, 0, NULL);
+
+ old_layout.output_lines = -1;
+
+ update_split (DIALOG (w->owner));
+
+ if (old_layout.output_lines != _output_lines)
+ {
+ old_layout.output_lines = _output_lines;
+ tty_setcolor (mc_global.tty.console_flag != '\0' ? COLOR_NORMAL : DISABLED_COLOR);
+ widget_gotoyx (w, 9, 5);
+ tty_print_string (output_lines_label);
+ widget_gotoyx (w, 9, 5 + 3 + output_lines_label_len);
+ tty_printf ("%02d", _output_lines);
+ }
+ return MSG_HANDLED;
+
+ default:
+ return frame_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+layout_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_POST_KEY:
+ {
+ const Widget *mw = CONST_WIDGET (filemanager);
+ gboolean _menubar_visible, _command_prompt, _keybar_visible, _message_visible;
+
+ _menubar_visible = check_options[1].widget->state;
+ _command_prompt = check_options[2].widget->state;
+ _keybar_visible = check_options[3].widget->state;
+ _message_visible = check_options[4].widget->state;
+
+ if (mc_global.tty.console_flag == '\0')
+ height =
+ mw->rect.lines - (_keybar_visible ? 1 : 0) - (_command_prompt ? 1 : 0) -
+ (_menubar_visible ? 1 : 0) - _output_lines - (_message_visible ? 1 : 0);
+ else
+ {
+ int minimum;
+
+ if (_output_lines < 0)
+ _output_lines = 0;
+ height =
+ mw->rect.lines - (_keybar_visible ? 1 : 0) - (_command_prompt ? 1 : 0) -
+ (_menubar_visible ? 1 : 0) - _output_lines - (_message_visible ? 1 : 0);
+ minimum = MINHEIGHT * (1 + (panels_layout.horizontal_split ? 1 : 0));
+ if (height < minimum)
+ {
+ _output_lines -= minimum - height;
+ height = minimum;
+ }
+ }
+
+ if (old_layout.output_lines != _output_lines)
+ {
+ old_layout.output_lines = _output_lines;
+ tty_setcolor (mc_global.tty.console_flag != '\0' ? COLOR_NORMAL : DISABLED_COLOR);
+ widget_gotoyx (h, 9, 5 + 3 + output_lines_label_len);
+ tty_printf ("%02d", _output_lines);
+ }
+ }
+ return MSG_HANDLED;
+
+ case MSG_NOTIFY:
+ if (sender == WIDGET (radio_widget))
+ {
+ if ((panels_layout.horizontal_split ? 1 : 0) == radio_widget->sel)
+ update_split (h);
+ else
+ {
+ int eq;
+
+ panels_layout.horizontal_split = radio_widget->sel != 0;
+
+ if (panels_layout.horizontal_split)
+ {
+ eq = panels_layout.horizontal_equal;
+ if (eq)
+ panels_layout.top_panel_size = height / 2;
+ }
+ else
+ {
+ eq = panels_layout.vertical_equal;
+ if (eq)
+ panels_layout.left_panel_size = CONST_WIDGET (filemanager)->rect.cols / 2;
+ }
+
+ widget_disable (WIDGET (bleft_widget), eq);
+ widget_disable (WIDGET (bright_widget), eq);
+
+ update_split (h);
+ layout_change ();
+ do_refresh ();
+ }
+
+ return MSG_HANDLED;
+ }
+
+ if (sender == WIDGET (check_options[0].widget))
+ {
+ gboolean eq;
+
+ if (panels_layout.horizontal_split)
+ {
+ panels_layout.horizontal_equal = check_options[0].widget->state;
+ eq = panels_layout.horizontal_equal;
+ }
+ else
+ {
+ panels_layout.vertical_equal = check_options[0].widget->state;
+ eq = panels_layout.vertical_equal;
+ }
+
+ widget_disable (WIDGET (bleft_widget), eq);
+ widget_disable (WIDGET (bright_widget), eq);
+
+ update_split (h);
+ layout_change ();
+ do_refresh ();
+
+ return MSG_HANDLED;
+ }
+
+ {
+ gboolean ok = TRUE;
+
+ if (sender == WIDGET (check_options[1].widget))
+ menubar_visible = check_options[1].widget->state;
+ else if (sender == WIDGET (check_options[2].widget))
+ command_prompt = check_options[2].widget->state;
+ else if (sender == WIDGET (check_options[3].widget))
+ mc_global.keybar_visible = check_options[3].widget->state;
+ else if (sender == WIDGET (check_options[4].widget))
+ mc_global.message_visible = check_options[4].widget->state;
+ else if (sender == WIDGET (check_options[5].widget))
+ xterm_title = check_options[5].widget->state;
+ else if (sender == WIDGET (check_options[6].widget))
+ free_space = check_options[6].widget->state;
+ else
+ ok = FALSE;
+
+ if (ok)
+ {
+ update_split (h);
+ layout_change ();
+ do_refresh ();
+ return MSG_HANDLED;
+ }
+ }
+
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WDialog *
+layout_dlg_create (void)
+{
+ WDialog *layout_dlg;
+ WGroup *g;
+ int l1 = 0, width;
+ int b1, b2, b;
+ size_t i;
+
+ const char *title1 = N_("Panel split");
+ const char *title2 = N_("Console output");
+ const char *title3 = N_("Other options");
+
+ const char *s_split_direction[2] = {
+ N_("&Vertical"),
+ N_("&Horizontal")
+ };
+
+ const char *ok_button = N_("&OK");
+ const char *cancel_button = N_("&Cancel");
+
+ output_lines_label = _("Output lines:");
+
+#ifdef ENABLE_NLS
+ {
+ static gboolean i18n = FALSE;
+
+ title1 = _(title1);
+ title2 = _(title2);
+ title3 = _(title3);
+
+ i = G_N_ELEMENTS (s_split_direction);
+ while (i-- != 0)
+ s_split_direction[i] = _(s_split_direction[i]);
+
+ if (!i18n)
+ {
+ for (i = 0; i < (size_t) LAYOUT_OPTIONS_COUNT; i++)
+ check_options[i].text = _(check_options[i].text);
+ i18n = TRUE;
+ }
+
+ ok_button = _(ok_button);
+ cancel_button = _(cancel_button);
+ }
+#endif
+
+ /* radiobuttons */
+ i = G_N_ELEMENTS (s_split_direction);
+ while (i-- != 0)
+ l1 = max (l1, str_term_width1 (s_split_direction[i]) + 7);
+ /* checkboxes */
+ for (i = 0; i < (size_t) LAYOUT_OPTIONS_COUNT; i++)
+ l1 = max (l1, str_term_width1 (check_options[i].text) + 7);
+ /* groupboxes */
+ l1 = max (l1, str_term_width1 (title1) + 4);
+ l1 = max (l1, str_term_width1 (title2) + 4);
+ l1 = max (l1, str_term_width1 (title3) + 4);
+ /* label + "+"/"-" buttons */
+ output_lines_label_len = str_term_width1 (output_lines_label);
+ l1 = max (l1, output_lines_label_len + 12);
+ /* buttons */
+ b1 = str_term_width1 (ok_button) + 5; /* default button */
+ b2 = str_term_width1 (cancel_button) + 3;
+ b = b1 + b2 + 1;
+ /* dialog width */
+ width = max (l1 * 2 + 7, b);
+
+ layout_dlg =
+ dlg_create (TRUE, 0, 0, 15, width, WPOS_CENTER, FALSE, dialog_colors, layout_callback, NULL,
+ "[Layout]", _("Layout"));
+ g = GROUP (layout_dlg);
+
+ /* draw background */
+ layout_dlg->bg->callback = layout_bg_callback;
+
+#define XTRACT(i) (*check_options[i].variable != 0), check_options[i].text
+
+ /* "Panel split" groupbox */
+ group_add_widget (g, groupbox_new (2, 3, 6, l1, title1));
+
+ radio_widget = radio_new (3, 5, 2, s_split_direction);
+ radio_widget->sel = panels_layout.horizontal_split ? 1 : 0;
+ group_add_widget (g, radio_widget);
+
+ check_options[0].widget = check_new (5, 5, XTRACT (0));
+ group_add_widget (g, check_options[0].widget);
+
+ equal_split = panels_layout.horizontal_split ?
+ panels_layout.horizontal_equal : panels_layout.vertical_equal;
+
+ bleft_widget = button_new (6, 8, B_2LEFT, NARROW_BUTTON, "&<", b_left_right_cback);
+ widget_disable (WIDGET (bleft_widget), equal_split);
+ group_add_widget (g, bleft_widget);
+
+ bright_widget = button_new (6, 14, B_2RIGHT, NARROW_BUTTON, "&>", b_left_right_cback);
+ widget_disable (WIDGET (bright_widget), equal_split);
+ group_add_widget (g, bright_widget);
+
+ /* "Console output" groupbox */
+ {
+ widget_state_t disabled;
+ Widget *w;
+
+ disabled = mc_global.tty.console_flag != '\0' ? 0 : WST_DISABLED;
+
+ w = WIDGET (groupbox_new (8, 3, 3, l1, title2));
+ w->state |= disabled;
+ group_add_widget (g, w);
+
+ w = WIDGET (button_new (9, output_lines_label_len + 5, B_PLUS,
+ NARROW_BUTTON, "&+", bplus_cback));
+ w->state |= disabled;
+ group_add_widget (g, w);
+
+ w = WIDGET (button_new (9, output_lines_label_len + 5 + 5, B_MINUS,
+ NARROW_BUTTON, "&-", bminus_cback));
+ w->state |= disabled;
+ group_add_widget (g, w);
+ }
+
+ /* "Other options" groupbox */
+ group_add_widget (g, groupbox_new (2, 4 + l1, 9, l1, title3));
+
+ for (i = 1; i < (size_t) LAYOUT_OPTIONS_COUNT; i++)
+ {
+ check_options[i].widget = check_new (i + 2, 6 + l1, XTRACT (i));
+ group_add_widget (g, check_options[i].widget);
+ }
+
+#undef XTRACT
+
+ group_add_widget (g, hline_new (11, -1, -1));
+ /* buttons */
+ group_add_widget (g, button_new (12, (width - b) / 2, B_ENTER, DEFPUSH_BUTTON, ok_button, 0));
+ group_add_widget (g,
+ button_new (12, (width - b) / 2 + b1 + 1, B_CANCEL, NORMAL_BUTTON,
+ cancel_button, 0));
+
+ widget_select (WIDGET (radio_widget));
+
+ return layout_dlg;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_do_cols (int idx)
+{
+ if (get_panel_type (idx) == view_listing)
+ set_panel_formats (PANEL (panels[idx].widget));
+ else
+ panel_update_cols (panels[idx].widget, frame_half);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Save current list_view widget directory into panel */
+
+static Widget *
+restore_into_right_dir_panel (int idx, gboolean last_was_panel, int y, int x, int lines, int cols)
+{
+ WPanel *new_widget;
+ const char *p_name;
+
+ p_name = get_nth_panel_name (idx);
+
+ if (last_was_panel)
+ {
+ vfs_path_t *saved_dir_vpath;
+
+ saved_dir_vpath = vfs_path_from_str (panels[idx].last_saved_dir);
+ new_widget = panel_sized_with_dir_new (p_name, y, x, lines, cols, saved_dir_vpath);
+ vfs_path_free (saved_dir_vpath, TRUE);
+ }
+ else
+ new_widget = panel_sized_new (p_name, y, x, lines, cols);
+
+ return WIDGET (new_widget);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+layout_save (void)
+{
+ old_layout.menubar_visible = menubar_visible;
+ old_layout.command_prompt = command_prompt;
+ old_layout.keybar_visible = mc_global.keybar_visible;
+ old_layout.message_visible = mc_global.message_visible;
+ old_layout.xterm_title = xterm_title;
+ old_layout.free_space = free_space;
+ old_layout.output_lines = -1;
+
+ _output_lines = output_lines;
+
+ old_panels_layout = panels_layout;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+layout_restore (void)
+{
+ menubar_visible = old_layout.menubar_visible;
+ command_prompt = old_layout.command_prompt;
+ mc_global.keybar_visible = old_layout.keybar_visible;
+ mc_global.message_visible = old_layout.message_visible;
+ xterm_title = old_layout.xterm_title;
+ free_space = old_layout.free_space;
+ output_lines = old_layout.output_lines;
+
+ panels_layout = old_panels_layout;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+layout_change (void)
+{
+ setup_panels ();
+ /* update the main menu, because perhaps there was a change in the way
+ how the panel are split (horizontal/vertical),
+ and a change of menu visibility. */
+ update_menu ();
+ load_hint (TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+layout_box (void)
+{
+ WDialog *layout_dlg;
+
+ layout_save ();
+
+ layout_dlg = layout_dlg_create ();
+
+ if (dlg_run (layout_dlg) == B_ENTER)
+ {
+ size_t i;
+
+ for (i = 0; i < (size_t) LAYOUT_OPTIONS_COUNT; i++)
+ if (check_options[i].widget != NULL)
+ *check_options[i].variable = check_options[i].widget->state;
+
+ output_lines = _output_lines;
+ }
+ else
+ layout_restore ();
+
+ widget_destroy (WIDGET (layout_dlg));
+ layout_change ();
+ do_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_update_cols (Widget * widget, panel_display_t frame_size)
+{
+ const Widget *mw = CONST_WIDGET (filemanager);
+ int cols, x;
+
+ /* don't touch panel if it is not in dialog yet */
+ /* if panel is not in dialog it is not in widgets list
+ and cannot be compared with get_panel_widget() result */
+ if (widget->owner == NULL)
+ return;
+
+ if (panels_layout.horizontal_split)
+ {
+ widget->rect.cols = mw->rect.cols;
+ return;
+ }
+
+ if (frame_size == frame_full)
+ {
+ cols = mw->rect.cols;
+ x = mw->rect.x;
+ }
+ else if (widget == get_panel_widget (0))
+ {
+ cols = panels_layout.left_panel_size;
+ x = mw->rect.x;
+ }
+ else
+ {
+ cols = mw->rect.cols - panels_layout.left_panel_size;
+ x = mw->rect.x + panels_layout.left_panel_size;
+ }
+
+ widget->rect.cols = cols;
+ widget->rect.x = x;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+setup_panels (void)
+{
+ /* File manager screen layout:
+ *
+ * +---------------------------------------------------------------+
+ * | Menu bar |
+ * +-------------------------------+-------------------------------+
+ * | | |
+ * | | |
+ * | | |
+ * | | |
+ * | Left panel | Right panel |
+ * | | |
+ * | | |
+ * | | |
+ * | | |
+ * +-------------------------------+-------------------------------+
+ * | Hint (message) bar |
+ * +---------------------------------------------------------------+
+ * | |
+ * | Console content |
+ * | |
+ * +--------+------------------------------------------------------+
+ * | Prompt | Command line |
+ * | Key (button) bar |
+ * +--------+------------------------------------------------------+
+ */
+
+ Widget *mw = WIDGET (filemanager);
+ const WRect *r = &CONST_WIDGET (mw)->rect;
+ int start_y;
+ gboolean active;
+ WRect rb;
+
+ active = widget_get_state (mw, WST_ACTIVE);
+
+ /* lock the group to avoid many redraws */
+ if (active)
+ widget_set_state (mw, WST_SUSPENDED, TRUE);
+
+ /* initial height of panels */
+ height =
+ r->lines - (menubar_visible ? 1 : 0) - (mc_global.message_visible ? 1 : 0) -
+ (command_prompt ? 1 : 0) - (mc_global.keybar_visible ? 1 : 0);
+
+ if (mc_global.tty.console_flag != '\0')
+ {
+ int minimum;
+
+ if (output_lines < 0)
+ output_lines = 0;
+ else
+ height -= output_lines;
+ minimum = MINHEIGHT * (1 + (panels_layout.horizontal_split ? 1 : 0));
+ if (height < minimum)
+ {
+ output_lines -= minimum - height;
+ height = minimum;
+ }
+ }
+
+ rb = *r;
+ rb.lines = 1;
+ widget_set_size_rect (WIDGET (the_menubar), &rb);
+ widget_set_visibility (WIDGET (the_menubar), menubar_visible);
+
+ check_split (&panels_layout);
+ start_y = r->y + (menubar_visible ? 1 : 0);
+
+ /* update columns first... */
+ panel_do_cols (0);
+ panel_do_cols (1);
+
+ /* ...then rows and origin */
+ if (panels_layout.horizontal_split)
+ {
+ widget_set_size (panels[0].widget, start_y, r->x, panels_layout.top_panel_size,
+ panels[0].widget->rect.cols);
+ widget_set_size (panels[1].widget, start_y + panels_layout.top_panel_size, r->x,
+ height - panels_layout.top_panel_size, panels[1].widget->rect.cols);
+ }
+ else
+ {
+ widget_set_size (panels[0].widget, start_y, r->x, height, panels[0].widget->rect.cols);
+ widget_set_size (panels[1].widget, start_y, panels[1].widget->rect.x, height,
+ panels[1].widget->rect.cols);
+ }
+
+ widget_set_size (WIDGET (the_hint), height + start_y, r->x, 1, r->cols);
+ widget_set_visibility (WIDGET (the_hint), mc_global.message_visible);
+
+ /* Output window */
+ if (mc_global.tty.console_flag != '\0' && output_lines != 0)
+ {
+ unsigned char end_line;
+
+ end_line = r->lines - (mc_global.keybar_visible ? 1 : 0) - 1;
+ output_start_y = end_line - (command_prompt ? 1 : 0) - output_lines + 1;
+ show_console_contents (output_start_y, end_line - output_lines, end_line);
+ }
+
+ if (command_prompt)
+ {
+#ifdef ENABLE_SUBSHELL
+ if (!mc_global.tty.use_subshell || !do_load_prompt ())
+#endif
+ setup_cmdline ();
+ }
+ else
+ {
+ /* make invisible */
+ widget_hide (WIDGET (cmdline));
+ widget_hide (WIDGET (the_prompt));
+ }
+
+ rb = *r;
+ rb.y = r->lines - 1;
+ rb.lines = 1;
+ widget_set_size_rect (WIDGET (the_bar), &rb);
+ widget_set_visibility (WIDGET (the_bar), mc_global.keybar_visible);
+
+ update_xterm_title_path ();
+
+ /* unlock */
+ if (active)
+ {
+ widget_set_state (mw, WST_ACTIVE, TRUE);
+ widget_draw (mw);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panels_split_equal (void)
+{
+ if (panels_layout.horizontal_split)
+ panels_layout.horizontal_equal = TRUE;
+ else
+ panels_layout.vertical_equal = TRUE;
+
+ layout_change ();
+ do_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panels_split_more (void)
+{
+ if (panels_layout.horizontal_split)
+ {
+ panels_layout.horizontal_equal = FALSE;
+ panels_layout.top_panel_size++;
+ }
+ else
+ {
+ panels_layout.vertical_equal = FALSE;
+ panels_layout.left_panel_size++;
+ }
+
+ layout_change ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panels_split_less (void)
+{
+ if (panels_layout.horizontal_split)
+ {
+ panels_layout.horizontal_equal = FALSE;
+ panels_layout.top_panel_size--;
+ }
+ else
+ {
+ panels_layout.vertical_equal = FALSE;
+ panels_layout.left_panel_size--;
+ }
+
+ layout_change ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+setup_cmdline (void)
+{
+ const Widget *mw = CONST_WIDGET (filemanager);
+ const WRect *r = &mw->rect;
+ int prompt_width;
+ int y;
+ char *tmp_prompt = (char *) mc_prompt;
+
+ if (!command_prompt)
+ return;
+
+#ifdef ENABLE_SUBSHELL
+ if (mc_global.tty.use_subshell)
+ {
+ /* Workaround: avoid crash on FreeBSD (see ticket #4213 for details) */
+ if (subshell_prompt != NULL)
+ tmp_prompt = g_string_free (subshell_prompt, FALSE);
+ else
+ tmp_prompt = g_strdup (mc_prompt);
+ (void) strip_ctrl_codes (tmp_prompt);
+ }
+#endif
+
+ prompt_width = str_term_width1 (tmp_prompt);
+
+ /* Check for prompts too big */
+ if (r->cols > 8 && prompt_width > r->cols - 8)
+ {
+ int prompt_len;
+
+ prompt_width = r->cols - 8;
+ prompt_len = str_offset_to_pos (tmp_prompt, prompt_width);
+ tmp_prompt[prompt_len] = '\0';
+ }
+
+#ifdef ENABLE_SUBSHELL
+ if (mc_global.tty.use_subshell)
+ {
+ subshell_prompt = g_string_new (tmp_prompt);
+ g_free (tmp_prompt);
+ mc_prompt = subshell_prompt->str;
+ }
+#endif
+
+ y = r->lines - 1 - (mc_global.keybar_visible ? 1 : 0);
+
+ widget_set_size (WIDGET (the_prompt), y, r->x, 1, prompt_width);
+ label_set_text (the_prompt, mc_prompt);
+ widget_set_size (WIDGET (cmdline), y, r->x + prompt_width, 1, r->cols - prompt_width);
+
+ widget_show (WIDGET (the_prompt));
+ widget_show (WIDGET (cmdline));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+use_dash (gboolean flag)
+{
+ if (flag)
+ ok_to_refresh++;
+ else
+ ok_to_refresh--;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+set_hintbar (const char *str)
+{
+ label_set_text (the_hint, str);
+ if (ok_to_refresh > 0)
+ mc_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+rotate_dash (gboolean show)
+{
+ static gint64 timestamp = 0;
+ /* update with 10 FPS rate */
+ static const gint64 delay = G_USEC_PER_SEC / 10;
+
+ const Widget *w = CONST_WIDGET (filemanager);
+
+ if (!nice_rotating_dash || (ok_to_refresh <= 0))
+ return;
+
+ if (show && !mc_time_elapsed (&timestamp, delay))
+ return;
+
+ widget_gotoyx (w, menubar_visible ? 1 : 0, w->rect.cols - 1);
+ tty_setcolor (NORMAL_COLOR);
+
+ if (!show)
+ tty_print_alt_char (ACS_URCORNER, FALSE);
+ else
+ {
+ static const char rotating_dash[4] = "|/-\\";
+ static size_t pos = 0;
+
+ tty_print_char (rotating_dash[pos]);
+ pos = (pos + 1) % sizeof (rotating_dash);
+ }
+
+ mc_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+get_nth_panel_name (int num)
+{
+ if (num == 0)
+ return "New Left Panel";
+
+ if (num == 1)
+ return "New Right Panel";
+
+ {
+ static char buffer[BUF_SMALL];
+
+ g_snprintf (buffer, sizeof (buffer), "%ith Panel", num);
+ return buffer;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* I wonder if I should start to use the folding mode than Dugan uses */
+/* */
+/* This is the centralized managing of the panel display types */
+/* This routine takes care of destroying and creating new widgets */
+/* Please note that it could manage MAX_VIEWS, not just left and right */
+/* Currently nothing in the code takes advantage of this and has hard- */
+/* coded values for two panels only */
+
+/* Set the num-th panel to the view type: type */
+/* This routine also keeps at least one WPanel object in the screen */
+/* since a lot of routines depend on the current_panel variable */
+
+void
+create_panel (int num, panel_view_mode_t type)
+{
+ WRect r = { 0, 0, 0, 0 };
+ unsigned int the_other = 0; /* Index to the other panel */
+ const char *file_name = NULL; /* For Quick view */
+ Widget *new_widget = NULL, *old_widget = NULL;
+ panel_view_mode_t old_type = view_listing;
+ WPanel *the_other_panel = NULL;
+
+ if (num >= MAX_VIEWS)
+ {
+ fprintf (stderr, "Cannot allocate more that %d views\n", MAX_VIEWS);
+ abort ();
+ }
+ /* Check that we will have a WPanel * at least */
+ if (type != view_listing)
+ {
+ the_other = num == 0 ? 1 : 0;
+
+ if (panels[the_other].type != view_listing)
+ return;
+ }
+
+ /* Get rid of it */
+ if (panels[num].widget != NULL)
+ {
+ Widget *w = panels[num].widget;
+ WPanel *panel = PANEL (w);
+
+ r = w->rect;
+ old_widget = w;
+ old_type = panels[num].type;
+
+ if (old_type == view_listing && panel->frame_size == frame_full && type != view_listing)
+ {
+ int md_cols = CONST_WIDGET (filemanager)->rect.cols;
+
+ if (panels_layout.horizontal_split)
+ {
+ r.cols = md_cols;
+ r.x = 0;
+ }
+ else
+ {
+ r.cols = md_cols - panels_layout.left_panel_size;
+ if (num == 1)
+ r.x = panels_layout.left_panel_size;
+ }
+ }
+ }
+
+ /* Restoring saved path from panels.ini for nonlist panel */
+ /* when it's first creation (for example view_info) */
+ if (old_widget == NULL && type != view_listing)
+ panels[num].last_saved_dir = vfs_get_cwd ();
+
+ switch (type)
+ {
+ case view_nothing:
+ case view_listing:
+ {
+ gboolean last_was_panel;
+
+ last_was_panel = old_widget != NULL && get_panel_type (num) != view_listing;
+ new_widget =
+ restore_into_right_dir_panel (num, last_was_panel, r.y, r.x, r.lines, r.cols);
+ break;
+ }
+
+ case view_info:
+ new_widget = WIDGET (info_new (r.y, r.x, r.lines, r.cols));
+ break;
+
+ case view_tree:
+ new_widget = WIDGET (tree_new (r.y, r.x, r.lines, r.cols, TRUE));
+ break;
+
+ case view_quick:
+ new_widget = WIDGET (mcview_new (r.y, r.x, r.lines, r.cols, TRUE));
+ the_other_panel = PANEL (panels[the_other].widget);
+ if (the_other_panel != NULL)
+ file_name = panel_current_entry (the_other_panel)->fname->str;
+ else
+ file_name = "";
+
+ mcview_load ((WView *) new_widget, 0, file_name, 0, 0, 0);
+ break;
+
+ default:
+ break;
+ }
+
+ if (type != view_listing)
+ /* Must save dir, for restoring after change type to */
+ /* view_listing */
+ save_panel_dir (num);
+
+ panels[num].type = type;
+ panels[num].widget = new_widget;
+
+ /* We use replace to keep the circular list of the dialog in the */
+ /* same state. Maybe we could just kill it and then replace it */
+ if (old_widget != NULL)
+ {
+ if (old_type == view_listing)
+ {
+ /* save and write directory history of panel
+ * ... and other histories of filemanager */
+ dlg_save_history (filemanager);
+ }
+
+ widget_replace (old_widget, new_widget);
+ }
+
+ if (type == view_listing)
+ {
+ WPanel *panel = PANEL (new_widget);
+
+ /* if existing panel changed type to view_listing, then load history */
+ if (old_widget != NULL)
+ {
+ ev_history_load_save_t event_data = { NULL, new_widget };
+
+ mc_event_raise (filemanager->event_group, MCEVENT_HISTORY_LOAD, &event_data);
+ }
+
+ if (num == 0)
+ left_panel = panel;
+ else
+ right_panel = panel;
+
+ /* forced update format after set new sizes */
+ set_panel_formats (panel);
+ }
+
+ if (type == view_tree)
+ the_tree = (WTree *) new_widget;
+
+ /* Prevent current_panel's value from becoming invalid.
+ * It's just a quick hack to prevent segfaults. Comment out and
+ * try following:
+ * - select left panel
+ * - invoke menu left/tree
+ * - as long as you stay in the left panel almost everything that uses
+ * current_panel causes segfault, e.g. C-Enter, C-x c, ...
+ */
+ if ((type != view_listing) && (current_panel == PANEL (old_widget)))
+ current_panel = num == 0 ? right_panel : left_panel;
+
+ g_free (old_widget);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This routine is deeply sticked to the two panels idea.
+ What should it do in more panels. ANSWER - don't use it
+ in any multiple panels environment. */
+
+void
+swap_panels (void)
+{
+ WPanel *panel1, *panel2;
+ Widget *tmp_widget;
+
+ panel1 = PANEL (panels[0].widget);
+ panel2 = PANEL (panels[1].widget);
+
+ if (panels[0].type == view_listing && panels[1].type == view_listing &&
+ !mc_config_get_bool (mc_global.main_config, CONFIG_PANELS_SECTION, "simple_swap", FALSE))
+ {
+ WPanel panel;
+
+#define panelswap(x) panel.x = panel1->x; panel1->x = panel2->x; panel2->x = panel.x;
+ /* Change content and related stuff */
+ panelswap (dir);
+ panelswap (active);
+ panelswap (cwd_vpath);
+ panelswap (lwd_vpath);
+ panelswap (marked);
+ panelswap (dirs_marked);
+ panelswap (total);
+ panelswap (top);
+ panelswap (current);
+ panelswap (is_panelized);
+ panelswap (panelized_descr);
+ panelswap (dir_stat);
+#undef panelswap
+
+ panel1->quick_search.active = FALSE;
+ panel2->quick_search.active = FALSE;
+
+ if (current_panel == panel1)
+ current_panel = panel2;
+ else
+ current_panel = panel1;
+
+ /* if sort options are different -> resort panels */
+ if (memcmp (&panel1->sort_info, &panel2->sort_info, sizeof (dir_sort_options_t)) != 0)
+ {
+ panel_re_sort (other_panel);
+ panel_re_sort (current_panel);
+ }
+
+ if (widget_is_active (panels[0].widget))
+ widget_select (panels[1].widget);
+ else if (widget_is_active (panels[1].widget))
+ widget_select (panels[0].widget);
+ }
+ else
+ {
+ WPanel *tmp_panel;
+ WRect r;
+ int tmp_type;
+
+ tmp_panel = right_panel;
+ right_panel = left_panel;
+ left_panel = tmp_panel;
+
+ if (panels[0].type == view_listing)
+ {
+ if (strcmp (panel1->name, get_nth_panel_name (0)) == 0)
+ {
+ g_free (panel1->name);
+ panel1->name = g_strdup (get_nth_panel_name (1));
+ }
+ }
+ if (panels[1].type == view_listing)
+ {
+ if (strcmp (panel2->name, get_nth_panel_name (1)) == 0)
+ {
+ g_free (panel2->name);
+ panel2->name = g_strdup (get_nth_panel_name (0));
+ }
+ }
+
+ r = panels[0].widget->rect;
+ panels[0].widget->rect = panels[1].widget->rect;
+ panels[1].widget->rect = r;
+
+ tmp_widget = panels[0].widget;
+ panels[0].widget = panels[1].widget;
+ panels[1].widget = tmp_widget;
+ tmp_type = panels[0].type;
+ panels[0].type = panels[1].type;
+ panels[1].type = tmp_type;
+
+ /* force update formats because of possible changed sizes */
+ if (panels[0].type == view_listing)
+ set_panel_formats (PANEL (panels[0].widget));
+ if (panels[1].type == view_listing)
+ set_panel_formats (PANEL (panels[1].widget));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+panel_view_mode_t
+get_panel_type (int idx)
+{
+ return panels[idx].type;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+Widget *
+get_panel_widget (int idx)
+{
+ return panels[idx].widget;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+get_current_index (void)
+{
+ return (panels[0].widget == WIDGET (current_panel) ? 0 : 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+get_other_index (void)
+{
+ return (get_current_index () == 0 ? 1 : 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WPanel *
+get_other_panel (void)
+{
+ return PANEL (get_panel_widget (get_other_index ()));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Returns the view type for the current panel/view */
+
+panel_view_mode_t
+get_current_type (void)
+{
+ return (panels[0].widget == WIDGET (current_panel) ? panels[0].type : panels[1].type);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Returns the view type of the unselected panel */
+
+panel_view_mode_t
+get_other_type (void)
+{
+ return (panels[0].widget == WIDGET (current_panel) ? panels[1].type : panels[0].type);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Save current list_view widget directory into panel */
+
+void
+save_panel_dir (int idx)
+{
+ panel_view_mode_t type;
+
+ type = get_panel_type (idx);
+ if (type == view_listing)
+ {
+ WPanel *p;
+
+ p = PANEL (get_panel_widget (idx));
+ if (p != NULL)
+ {
+ g_free (panels[idx].last_saved_dir); /* last path no needed */
+ /* Because path can be nonlocal */
+ panels[idx].last_saved_dir = g_strdup (vfs_path_as_str (p->cwd_vpath));
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Return working dir, if it's view_listing - cwd,
+ but for other types - last_saved_dir */
+
+char *
+get_panel_dir_for (const WPanel * widget)
+{
+ int i;
+
+ for (i = 0; i < MAX_VIEWS; i++)
+ if (PANEL (get_panel_widget (i)) == widget)
+ break;
+
+ if (i >= MAX_VIEWS)
+ return g_strdup (".");
+
+ if (get_panel_type (i) == view_listing)
+ {
+ vfs_path_t *cwd_vpath;
+
+ cwd_vpath = PANEL (get_panel_widget (i))->cwd_vpath;
+ return g_strdup (vfs_path_as_str (cwd_vpath));
+ }
+
+ return g_strdup (panels[i].last_saved_dir);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_SUBSHELL
+gboolean
+do_load_prompt (void)
+{
+ gboolean ret = FALSE;
+
+ if (!read_subshell_prompt ())
+ return ret;
+
+ /* Don't actually change the prompt if it's invisible */
+ if (top_dlg != NULL && DIALOG (top_dlg->data) == filemanager && command_prompt)
+ {
+ setup_cmdline ();
+
+ /* since the prompt has changed, and we are called from one of the
+ * tty_get_event channels, the prompt updating does not take place
+ * automatically: force a cursor update and a screen refresh
+ */
+ widget_update_cursor (WIDGET (filemanager));
+ mc_refresh ();
+ ret = TRUE;
+ }
+ update_subshell_prompt = TRUE;
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+load_prompt (int fd, void *unused)
+{
+ (void) fd;
+ (void) unused;
+
+ if (should_read_new_subshell_prompt)
+ do_load_prompt ();
+ else
+ flush_subshell (0, QUIETLY);
+
+ return 0;
+}
+#endif /* ENABLE_SUBSHELL */
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+title_path_prepare (char **path, char **login)
+{
+ char host[BUF_TINY];
+ struct passwd *pw = NULL;
+ int res = 0;
+
+ *path =
+ vfs_path_to_str_flags (current_panel->cwd_vpath, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
+
+ res = gethostname (host, sizeof (host));
+ if (res != 0)
+ host[0] = '\0';
+ else
+ host[sizeof (host) - 1] = '\0';
+
+ pw = getpwuid (getuid ());
+ if (pw != NULL)
+ *login = g_strdup_printf ("%s@%s", pw->pw_name, host);
+ else
+ *login = g_strdup (host);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Show current directory in the xterm title */
+void
+update_xterm_title_path (void)
+{
+ if (mc_global.tty.xterm_flag && xterm_title)
+ {
+ char *p;
+ char *path;
+ char *login;
+
+ title_path_prepare (&path, &login);
+
+ p = g_strdup_printf ("mc [%s]:%s", login, path);
+ g_free (login);
+ g_free (path);
+
+ fprintf (stdout, "\33]0;%s\7", str_term_form (p));
+ g_free (p);
+
+ if (!mc_global.tty.alternate_plus_minus)
+ numeric_keypad_mode ();
+ (void) fflush (stdout);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/layout.h b/src/filemanager/layout.h
new file mode 100644
index 0000000..2566cfa
--- /dev/null
+++ b/src/filemanager/layout.h
@@ -0,0 +1,98 @@
+/** \file layout.h
+ * \brief Header: panel layout module
+ */
+
+#ifndef MC__LAYOUT_H
+#define MC__LAYOUT_H
+
+#include "lib/global.h"
+#include "lib/widget.h"
+
+#include "panel.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+typedef enum
+{
+ view_listing = 0, /* Directory listing */
+ view_info = 1, /* Information panel */
+ view_tree = 2, /* Tree view */
+ view_quick = 3, /* Quick view */
+ view_nothing = 4, /* Undefined */
+} panel_view_mode_t;
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ gboolean horizontal_split;
+
+ /* vertical split */
+ gboolean vertical_equal;
+ int left_panel_size;
+
+ /* horizontal split */
+ gboolean horizontal_equal;
+ int top_panel_size;
+} panels_layout_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern int output_lines;
+extern gboolean command_prompt;
+extern gboolean menubar_visible;
+extern int output_start_y;
+extern gboolean xterm_title;
+extern gboolean free_space;
+extern gboolean nice_rotating_dash;
+
+extern int ok_to_refresh;
+
+extern panels_layout_t panels_layout;
+
+/*** declarations of public functions ************************************************************/
+void layout_change (void);
+void layout_box (void);
+void panel_update_cols (Widget * widget, panel_display_t frame_size);
+void setup_panels (void);
+void panels_split_equal (void);
+void panels_split_more (void);
+void panels_split_less (void);
+void destroy_panels (void);
+void setup_cmdline (void);
+void create_panel (int num, panel_view_mode_t type);
+void swap_panels (void);
+panel_view_mode_t get_panel_type (int idx);
+panel_view_mode_t get_current_type (void);
+panel_view_mode_t get_other_type (void);
+int get_current_index (void);
+int get_other_index (void);
+const char *get_nth_panel_name (int num);
+
+Widget *get_panel_widget (int idx);
+
+WPanel *get_other_panel (void);
+
+void save_panel_dir (int idx);
+char *get_panel_dir_for (const WPanel * widget);
+
+void set_hintbar (const char *str);
+
+/* Rotating dash routines */
+void use_dash (gboolean flag); /* Disable/Enable rotate_dash routines */
+void rotate_dash (gboolean show);
+
+#ifdef ENABLE_SUBSHELL
+gboolean do_load_prompt (void);
+int load_prompt (int fd, void *unused);
+#endif
+
+void update_xterm_title_path (void);
+
+void title_path_prepare (char **path, char **login);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__LAYOUT_H */
diff --git a/src/filemanager/mountlist.c b/src/filemanager/mountlist.c
new file mode 100644
index 0000000..d7fd734
--- /dev/null
+++ b/src/filemanager/mountlist.c
@@ -0,0 +1,1575 @@
+/*
+ Return a list of mounted file systems
+
+ Copyright (C) 1991-2023
+ Free Software Foundation, Inc.
+
+ 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 mountlist.c
+ * \brief Source: list of mounted filesystems
+ */
+
+#include <config.h>
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h> /* SIZE_MAX */
+#include <sys/types.h>
+
+#include <errno.h>
+
+/* This header needs to be included before sys/mount.h on *BSD */
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+
+#if defined STAT_STATVFS || defined STAT_STATVFS64 /* POSIX 1003.1-2001 (and later) with XSI */
+#include <sys/statvfs.h>
+#else
+/* Don't include backward-compatibility files unless they're needed.
+ Eventually we'd like to remove all this cruft. */
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#ifdef MOUNTED_GETFSSTAT /* OSF_1, also (obsolete) Apple Darwin 1.3 */
+#ifdef HAVE_SYS_UCRED_H
+#include <grp.h> /* needed on OSF V4.0 for definition of NGROUPS,
+ NGROUPS is used as an array dimension in ucred.h */
+#include <sys/ucred.h> /* needed by powerpc-apple-darwin1.3.7 */
+#endif
+#ifdef HAVE_SYS_MOUNT_H
+#include <sys/mount.h>
+#endif
+#ifdef HAVE_SYS_FS_TYPES_H
+#include <sys/fs_types.h> /* needed by powerpc-apple-darwin1.3.7 */
+#endif
+#ifdef HAVE_STRUCT_FSSTAT_F_FSTYPENAME
+#define FS_TYPE(Ent) ((Ent).f_fstypename)
+#else
+#define FS_TYPE(Ent) mnt_names[(Ent).f_type]
+#endif
+#endif /* MOUNTED_GETFSSTAT */
+#endif /* STAT_STATVFS || STAT_STATVFS64 */
+
+#ifdef HAVE_SYS_VFS_H
+#include <sys/vfs.h>
+#endif
+#ifdef HAVE_SYS_FS_S5PARAM_H /* Fujitsu UXP/V */
+#include <sys/fs/s5param.h>
+#endif
+#ifdef HAVE_SYS_STATFS_H
+#include <sys/statfs.h>
+#endif
+
+#ifdef MOUNTED_GETMNTENT1 /* glibc, HP-UX, IRIX, Cygwin, Android,
+ also (obsolete) 4.3BSD, SunOS */
+#include <mntent.h>
+#include <sys/types.h>
+#if defined __ANDROID__ /* Android */
+ /* Bionic versions from between 2014-01-09 and 2015-01-08 define MOUNTED to
+ an incorrect value; older Bionic versions don't define it at all. */
+#undef MOUNTED
+#define MOUNTED "/proc/mounts"
+#elif !defined MOUNTED
+#ifdef _PATH_MOUNTED /* GNU libc */
+#define MOUNTED _PATH_MOUNTED
+#endif
+#ifdef MNT_MNTTAB /* HP-UX. */
+#define MOUNTED MNT_MNTTAB
+#endif
+#endif
+#endif
+
+#ifdef MOUNTED_GETMNTINFO /* Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD */
+#include <sys/mount.h>
+#endif
+
+#ifdef MOUNTED_GETMNTINFO2 /* NetBSD, Minix */
+#include <sys/statvfs.h>
+#endif
+
+#ifdef MOUNTED_FS_STAT_DEV /* Haiku, also (obsolete) BeOS */
+#include <fs_info.h>
+#include <dirent.h>
+#endif
+
+#ifdef MOUNTED_FREAD_FSTYP /* (obsolete) SVR3 */
+#include <mnttab.h>
+#include <sys/fstyp.h>
+#include <sys/statfs.h>
+#endif
+
+#ifdef MOUNTED_GETEXTMNTENT /* Solaris >= 8 */
+#include <sys/mnttab.h>
+#endif
+
+#ifdef MOUNTED_GETMNTENT2 /* Solaris < 8, also (obsolete) SVR4 */
+#include <sys/mnttab.h>
+#endif
+
+#ifdef MOUNTED_VMOUNT /* AIX */
+#include <fshelp.h>
+#include <sys/vfs.h>
+#endif
+
+#ifdef MOUNTED_INTERIX_STATVFS /* Interix */
+#include <sys/statvfs.h>
+#include <dirent.h>
+#endif
+
+#ifdef HAVE_SYS_MNTENT_H
+/* This is to get MNTOPT_IGNORE on e.g. SVR4. */
+#include <sys/mntent.h>
+#endif
+
+#ifdef MOUNTED_GETMNTENT1
+#if !HAVE_SETMNTENT /* Android <= 4.4 */
+#define setmntent(fp,mode) fopen (fp, mode)
+#endif
+#if !HAVE_ENDMNTENT /* Android <= 4.4 */
+#define endmntent(fp) fclose (fp)
+#endif
+#endif
+
+#ifndef HAVE_HASMNTOPT
+#define hasmntopt(mnt, opt) ((char *) 0)
+#endif
+
+#undef MNT_IGNORE
+#ifdef MNTOPT_IGNORE
+#if defined __sun && defined __SVR4
+/* Solaris defines hasmntopt(struct mnttab *, char *)
+ while it is otherwise hasmntopt(struct mnttab *, const char *). */
+#define MNT_IGNORE(M) hasmntopt (M, (char *) MNTOPT_IGNORE)
+#else
+#define MNT_IGNORE(M) hasmntopt (M, MNTOPT_IGNORE)
+#endif
+#else
+#define MNT_IGNORE(M) 0
+#endif
+
+#ifdef HAVE_INFOMOUNT_QNX
+#include <sys/disk.h>
+#include <sys/fsys.h>
+#endif
+
+#ifdef HAVE_SYS_STATVFS_H /* SVR4. */
+#include <sys/statvfs.h>
+#endif
+
+#include "lib/global.h"
+#include "lib/strutil.h" /* str_verscmp() */
+#include "lib/unixcompat.h" /* makedev */
+#include "mountlist.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#if defined (__QNX__) && !defined(__QNXNTO__) && !defined (HAVE_INFOMOUNT_LIST)
+#define HAVE_INFOMOUNT_QNX
+#endif
+
+#if defined(HAVE_INFOMOUNT_LIST) || defined(HAVE_INFOMOUNT_QNX)
+#define HAVE_INFOMOUNT
+#endif
+
+/* The results of opendir() in this file are not used with dirfd and fchdir,
+ therefore save some unnecessary work in fchdir.c. */
+#undef opendir
+#undef closedir
+
+#define ME_DUMMY_0(Fs_name, Fs_type) \
+ (strcmp (Fs_type, "autofs") == 0 \
+ || strcmp (Fs_type, "proc") == 0 \
+ || strcmp (Fs_type, "subfs") == 0 \
+ /* for Linux 2.6/3.x */ \
+ || strcmp (Fs_type, "debugfs") == 0 \
+ || strcmp (Fs_type, "devpts") == 0 \
+ || strcmp (Fs_type, "fusectl") == 0 \
+ || strcmp (Fs_type, "fuse.portal") == 0 \
+ || strcmp (Fs_type, "mqueue") == 0 \
+ || strcmp (Fs_type, "rpc_pipefs") == 0 \
+ || strcmp (Fs_type, "sysfs") == 0 \
+ /* FreeBSD, Linux 2.4 */ \
+ || strcmp (Fs_type, "devfs") == 0 \
+ /* for NetBSD 3.0 */ \
+ || strcmp (Fs_type, "kernfs") == 0 \
+ /* for Irix 6.5 */ \
+ || strcmp (Fs_type, "ignore") == 0)
+
+/* Historically, we have marked as "dummy" any file system of type "none",
+ but now that programs like du need to know about bind-mounted directories,
+ we grant an exception to any with "bind" in its list of mount options.
+ I.e., those are *not* dummy entries. */
+#ifdef MOUNTED_GETMNTENT1
+#define ME_DUMMY(Fs_name, Fs_type, Bind) \
+ (ME_DUMMY_0 (Fs_name, Fs_type) \
+ || (strcmp (Fs_type, "none") == 0 && !Bind))
+#else
+#define ME_DUMMY(Fs_name, Fs_type) \
+ (ME_DUMMY_0 (Fs_name, Fs_type) || strcmp (Fs_type, "none") == 0)
+#endif
+
+#ifdef __CYGWIN__
+#include <windows.h>
+#define ME_REMOTE me_remote
+/* All cygwin mount points include ':' or start with '//'; so it
+ requires a native Windows call to determine remote disks. */
+static int
+me_remote (char const *fs_name, char const *fs_type)
+{
+ (void) fs_type;
+
+ if (fs_name[0] && fs_name[1] == ':')
+ {
+ char drive[4];
+ sprintf (drive, "%c:\\", fs_name[0]);
+ switch (GetDriveType (drive))
+ {
+ case DRIVE_REMOVABLE:
+ case DRIVE_FIXED:
+ case DRIVE_CDROM:
+ case DRIVE_RAMDISK:
+ return 0;
+ }
+ }
+ return 1;
+}
+#endif
+#ifndef ME_REMOTE
+/* A file system is 'remote' if its Fs_name contains a ':'
+ or if (it is of type (smbfs or smb3 or cifs) and its Fs_name starts with '//')
+ or if it is of any other of the listed types
+ or Fs_name is equal to "-hosts" (used by autofs to mount remote fs).
+ "VM" file systems like prl_fs or vboxsf are not considered remote here. */
+#define ME_REMOTE(Fs_name, Fs_type) \
+ (strchr (Fs_name, ':') != NULL \
+ || ((Fs_name)[0] == '/' \
+ && (Fs_name)[1] == '/' \
+ && (strcmp (Fs_type, "smbfs") == 0 \
+ || strcmp (Fs_type, "smb3") == 0 \
+ || strcmp (Fs_type, "cifs") == 0)) \
+ || strcmp (Fs_type, "acfs") == 0 \
+ || strcmp (Fs_type, "afs") == 0 \
+ || strcmp (Fs_type, "coda") == 0 \
+ || strcmp (Fs_type, "auristorfs") == 0 \
+ || strcmp (Fs_type, "fhgfs") == 0 \
+ || strcmp (Fs_type, "gpfs") == 0 \
+ || strcmp (Fs_type, "ibrix") == 0 \
+ || strcmp (Fs_type, "ocfs2") == 0 \
+ || strcmp (Fs_type, "vxfs") == 0 \
+ || strcmp ("-hosts", Fs_name) == 0)
+#endif
+
+/* Many space usage primitives use all 1 bits to denote a value that is
+ not applicable or unknown. Propagate this information by returning
+ a uintmax_t value that is all 1 bits if X is all 1 bits, even if X
+ is unsigned and narrower than uintmax_t. */
+#define PROPAGATE_ALL_ONES(x) \
+ ((sizeof (x) < sizeof (uintmax_t) \
+ && (~ (x) == (sizeof (x) < sizeof (int) \
+ ? - (1 << (sizeof (x) * CHAR_BIT)) \
+ : 0))) \
+ ? UINTMAX_MAX : (uintmax_t) (x))
+
+/* Extract the top bit of X as an uintmax_t value. */
+#define EXTRACT_TOP_BIT(x) ((x) & ((uintmax_t) 1 << (sizeof (x) * CHAR_BIT - 1)))
+
+/* If a value is negative, many space usage primitives store it into an
+ integer variable by assignment, even if the variable's type is unsigned.
+ So, if a space usage variable X's top bit is set, convert X to the
+ uintmax_t value V such that (- (uintmax_t) V) is the negative of
+ the original value. If X's top bit is clear, just yield X.
+ Use PROPAGATE_TOP_BIT if the original value might be negative;
+ otherwise, use PROPAGATE_ALL_ONES. */
+#define PROPAGATE_TOP_BIT(x) ((x) | ~ (EXTRACT_TOP_BIT (x) - 1))
+
+#ifdef STAT_STATVFS
+#if ! (defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
+/* The FRSIZE fallback is not required in this case. */
+#undef STAT_STATFS2_FRSIZE
+#else
+#include <sys/utsname.h>
+#include <sys/statfs.h>
+#define STAT_STATFS2_BSIZE 1
+#endif
+#endif
+
+/*** file scope type declarations ****************************************************************/
+
+/* A mount table entry. */
+struct mount_entry
+{
+ char *me_devname; /* Device node name, including "/dev/". */
+ char *me_mountdir; /* Mount point directory name. */
+ char *me_mntroot; /* Directory on filesystem of device used
+ as root for the (bind) mount. */
+ char *me_type; /* "nfs", "4.2", etc. */
+ dev_t me_dev; /* Device number of me_mountdir. */
+ unsigned int me_dummy:1; /* Nonzero for dummy file systems. */
+ unsigned int me_remote:1; /* Nonzero for remote filesystems. */
+ unsigned int me_type_malloced:1; /* Nonzero if me_type was malloced. */
+};
+
+struct fs_usage
+{
+ uintmax_t fsu_blocksize; /* Size of a block. */
+ uintmax_t fsu_blocks; /* Total blocks. */
+ uintmax_t fsu_bfree; /* Free blocks available to superuser. */
+ uintmax_t fsu_bavail; /* Free blocks available to non-superuser. */
+ int fsu_bavail_top_bit_set; /* 1 if fsu_bavail represents a value < 0. */
+ uintmax_t fsu_files; /* Total file nodes. */
+ uintmax_t fsu_ffree; /* Free file nodes. */
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+#ifdef HAVE_INFOMOUNT_LIST
+static GSList *mc_mount_list = NULL;
+#endif /* HAVE_INFOMOUNT_LIST */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef STAT_STATVFS
+/* Return true if statvfs works. This is false for statvfs on systems
+ with GNU libc on Linux kernels before 2.6.36, which stats all
+ preceding entries in /proc/mounts; that makes df hang if even one
+ of the corresponding file systems is hard-mounted but not available. */
+static int
+statvfs_works (void)
+{
+#if ! (defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
+ return 1;
+#else
+ static int statvfs_works_cache = -1;
+ struct utsname name;
+
+ if (statvfs_works_cache < 0)
+ statvfs_works_cache = (uname (&name) == 0 && 0 <= str_verscmp (name.release, "2.6.36"));
+ return statvfs_works_cache;
+#endif
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_INFOMOUNT_LIST
+static void
+free_mount_entry (struct mount_entry *me)
+{
+ if (me == NULL)
+ return;
+ g_free (me->me_devname);
+ g_free (me->me_mountdir);
+ g_free (me->me_mntroot);
+ if (me->me_type_malloced)
+ g_free (me->me_type);
+ g_free (me);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef MOUNTED_GETMNTINFO /* Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD */
+
+#ifndef HAVE_STRUCT_STATFS_F_FSTYPENAME
+static char *
+fstype_to_string (short int t)
+{
+ switch (t)
+ {
+#ifdef MOUNT_PC
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_PC:
+ return "pc";
+#endif
+#ifdef MOUNT_MFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_MFS:
+ return "mfs";
+#endif
+#ifdef MOUNT_LO
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_LO:
+ return "lo";
+#endif
+#ifdef MOUNT_TFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_TFS:
+ return "tfs";
+#endif
+#ifdef MOUNT_TMP
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_TMP:
+ return "tmp";
+#endif
+#ifdef MOUNT_UFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_UFS:
+ return "ufs";
+#endif
+#ifdef MOUNT_NFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_NFS:
+ return "nfs";
+#endif
+#ifdef MOUNT_MSDOS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_MSDOS:
+ return "msdos";
+#endif
+#ifdef MOUNT_LFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_LFS:
+ return "lfs";
+#endif
+#ifdef MOUNT_LOFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_LOFS:
+ return "lofs";
+#endif
+#ifdef MOUNT_FDESC
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_FDESC:
+ return "fdesc";
+#endif
+#ifdef MOUNT_PORTAL
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_PORTAL:
+ return "portal";
+#endif
+#ifdef MOUNT_NULL
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_NULL:
+ return "null";
+#endif
+#ifdef MOUNT_UMAP
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_UMAP:
+ return "umap";
+#endif
+#ifdef MOUNT_KERNFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_KERNFS:
+ return "kernfs";
+#endif
+#ifdef MOUNT_PROCFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_PROCFS:
+ return "procfs";
+#endif
+#ifdef MOUNT_AFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_AFS:
+ return "afs";
+#endif
+#ifdef MOUNT_CD9660
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_CD9660:
+ return "cd9660";
+#endif
+#ifdef MOUNT_UNION
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_UNION:
+ return "union";
+#endif
+#ifdef MOUNT_DEVFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_DEVFS:
+ return "devfs";
+#endif
+#ifdef MOUNT_EXT2FS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_EXT2FS:
+ return "ext2fs";
+#endif
+ default:
+ return "?";
+ }
+}
+#endif /* ! HAVE_STRUCT_STATFS_F_FSTYPENAME */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+fsp_to_string (const struct statfs *fsp)
+{
+#ifdef HAVE_STRUCT_STATFS_F_FSTYPENAME
+ return (char *) (fsp->f_fstypename);
+#else
+ return fstype_to_string (fsp->f_type);
+#endif
+}
+#endif /* MOUNTED_GETMNTINFO */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef MOUNTED_VMOUNT /* AIX */
+static char *
+fstype_to_string (int t)
+{
+ struct vfs_ent *e;
+
+ e = getvfsbytype (t);
+ if (!e || !e->vfsent_name)
+ return "none";
+ else
+ return e->vfsent_name;
+}
+#endif /* MOUNTED_VMOUNT */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if defined MOUNTED_GETMNTENT1 || defined MOUNTED_GETMNTENT2
+
+/* Return the device number from MOUNT_OPTIONS, if possible.
+ Otherwise return (dev_t) -1. */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static dev_t
+dev_from_mount_options (char const *mount_options)
+{
+ /* GNU/Linux allows file system implementations to define their own
+ meaning for "dev=" mount options, so don't trust the meaning
+ here. */
+#ifndef __linux__
+ static char const dev_pattern[] = ",dev=";
+ char const *devopt = strstr (mount_options, dev_pattern);
+
+ if (devopt)
+ {
+ char const *optval = devopt + sizeof (dev_pattern) - 1;
+ char *optvalend;
+ unsigned long int dev;
+ errno = 0;
+ dev = strtoul (optval, &optvalend, 16);
+ if (optval != optvalend
+ && (*optvalend == '\0' || *optvalend == ',')
+ && !(dev == ULONG_MAX && errno == ERANGE) && dev == (dev_t) dev)
+ return dev;
+ }
+#endif
+
+ (void) mount_options;
+ return -1;
+}
+
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if defined MOUNTED_GETMNTENT1 && (defined __linux__ || defined __ANDROID__) /* GNU/Linux, Android */
+
+/* Unescape the paths in mount tables.
+ STR is updated in place. */
+static void
+unescape_tab (char *str)
+{
+ size_t i, j = 0;
+ size_t len;
+
+ len = strlen (str) + 1;
+
+ for (i = 0; i < len; i++)
+ {
+ if (str[i] == '\\' && (i + 4 < len)
+ && str[i + 1] >= '0' && str[i + 1] <= '3'
+ && str[i + 2] >= '0' && str[i + 2] <= '7' && str[i + 3] >= '0' && str[i + 3] <= '7')
+ {
+ str[j++] = (str[i + 1] - '0') * 64 + (str[i + 2] - '0') * 8 + (str[i + 3] - '0');
+ i += 3;
+ }
+ else
+ str[j++] = str[i];
+ }
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Return a list of the currently mounted file systems, or NULL on error.
+ Add each entry to the tail of the list so that they stay in order. */
+
+static GSList *
+read_file_system_list (void)
+{
+ GSList *mount_list = NULL;
+ struct mount_entry *me;
+
+#ifdef MOUNTED_GETMNTENT1 /* glibc, HP-UX, IRIX, Cygwin, Android,
+ also (obsolete) 4.3BSD, SunOS */
+ {
+ FILE *fp;
+
+#if defined __linux__ || defined __ANDROID__
+ /* Try parsing mountinfo first, as that make device IDs available.
+ Note we could use libmount routines to simplify this parsing a little
+ (and that code is in previous versions of this function), however
+ libmount depends on libselinux which pulls in many dependencies. */
+ char const *mountinfo = "/proc/self/mountinfo";
+
+ fp = fopen (mountinfo, "r");
+ if (fp != NULL)
+ {
+ char *line = NULL;
+ size_t buf_size = 0;
+
+ while (getline (&line, &buf_size, fp) != -1)
+ {
+ unsigned int devmaj, devmin;
+ int target_s, target_e, type_s, type_e;
+ int source_s, source_e, mntroot_s, mntroot_e;
+ char test;
+ char *dash;
+ int rc;
+
+ rc = sscanf (line, "%*u " /* id - discarded */
+ "%*u " /* parent - discarded */
+ "%u:%u " /* dev major:minor */
+ "%n%*s%n " /* mountroot */
+ "%n%*s%n" /* target, start and end */
+ "%c", /* more data... */
+ &devmaj, &devmin, &mntroot_s, &mntroot_e, &target_s, &target_e, &test);
+
+ if (rc != 3 && rc != 7) /* 7 if %n included in count. */
+ continue;
+
+ /* skip optional fields, terminated by " - " */
+ dash = strstr (line + target_e, " - ");
+ if (dash == NULL)
+ continue;
+
+ rc = sscanf (dash, " - " /* */
+ "%n%*s%n " /* FS type, start and end */
+ "%n%*s%n " /* source, start and end */
+ "%c", /* more data... */
+ &type_s, &type_e, &source_s, &source_e, &test);
+ if (rc != 1 && rc != 5) /* 5 if %n included in count. */
+ continue;
+
+ /* manipulate the sub-strings in place. */
+ line[mntroot_e] = '\0';
+ line[target_e] = '\0';
+ dash[type_e] = '\0';
+ dash[source_e] = '\0';
+ unescape_tab (dash + source_s);
+ unescape_tab (line + target_s);
+ unescape_tab (line + mntroot_s);
+
+ me = g_malloc (sizeof *me);
+
+ me->me_devname = g_strdup (dash + source_s);
+ me->me_mountdir = g_strdup (line + target_s);
+ me->me_mntroot = g_strdup (line + mntroot_s);
+ me->me_type = g_strdup (dash + type_s);
+ me->me_type_malloced = 1;
+ me->me_dev = makedev (devmaj, devmin);
+ /* we pass "false" for the "Bind" option as that's only
+ significant when the Fs_type is "none" which will not be
+ the case when parsing "/proc/self/mountinfo", and only
+ applies for static /etc/mtab files. */
+ me->me_dummy = ME_DUMMY (me->me_devname, me->me_type, FALSE);
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+
+ free (line);
+
+ if (ferror (fp) != 0)
+ {
+ int saved_errno = errno;
+
+ fclose (fp);
+ errno = saved_errno;
+ goto free_then_fail;
+ }
+
+ if (fclose (fp) == EOF)
+ goto free_then_fail;
+ }
+ else /* fallback to /proc/self/mounts (/etc/mtab). */
+#endif /* __linux __ || __ANDROID__ */
+ {
+ struct mntent *mnt;
+ const char *table = MOUNTED;
+
+ fp = setmntent (table, "r");
+ if (fp == NULL)
+ return NULL;
+
+ while ((mnt = getmntent (fp)) != NULL)
+ {
+ gboolean bind;
+
+ bind = hasmntopt (mnt, "bind") != NULL;
+
+ me = g_malloc (sizeof (*me));
+ me->me_devname = g_strdup (mnt->mnt_fsname);
+ me->me_mountdir = g_strdup (mnt->mnt_dir);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (mnt->mnt_type);
+ me->me_type_malloced = 1;
+ me->me_dummy = ME_DUMMY (me->me_devname, me->me_type, bind);
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+ me->me_dev = dev_from_mount_options (mnt->mnt_opts);
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+
+ if (endmntent (fp) == 0)
+ goto free_then_fail;
+ }
+ }
+#endif /* MOUNTED_GETMNTENT1. */
+
+#ifdef MOUNTED_GETMNTINFO /* Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD */
+ {
+ struct statfs *fsp;
+ int entries;
+
+ entries = getmntinfo (&fsp, MNT_NOWAIT);
+ if (entries < 0)
+ return NULL;
+ for (; entries-- > 0; fsp++)
+ {
+ char *fs_type = fsp_to_string (fsp);
+
+ me = g_malloc (sizeof (*me));
+ me->me_devname = g_strdup (fsp->f_mntfromname);
+ me->me_mountdir = g_strdup (fsp->f_mntonname);
+ me->me_mntroot = NULL;
+ me->me_type = fs_type;
+ me->me_type_malloced = 0;
+ me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+ me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+ }
+#endif /* MOUNTED_GETMNTINFO */
+
+#ifdef MOUNTED_GETMNTINFO2 /* NetBSD, Minix */
+ {
+ struct statvfs *fsp;
+ int entries;
+
+ entries = getmntinfo (&fsp, MNT_NOWAIT);
+ if (entries < 0)
+ return NULL;
+ for (; entries-- > 0; fsp++)
+ {
+ me = g_malloc (sizeof (*me));
+ me->me_devname = g_strdup (fsp->f_mntfromname);
+ me->me_mountdir = g_strdup (fsp->f_mntonname);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (fsp->f_fstypename);
+ me->me_type_malloced = 1;
+ me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+ me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+ }
+#endif /* MOUNTED_GETMNTINFO2 */
+
+#if defined MOUNTED_FS_STAT_DEV /* Haiku, also (obsolete) BeOS */
+ {
+ /* The next_dev() and fs_stat_dev() system calls give the list of
+ all file systems, including the information returned by statvfs()
+ (fs type, total blocks, free blocks etc.), but without the mount
+ point. But on BeOS all file systems except / are mounted in the
+ rootfs, directly under /.
+ The directory name of the mount point is often, but not always,
+ identical to the volume name of the device.
+ We therefore get the list of subdirectories of /, and the list
+ of all file systems, and match the two lists. */
+
+ DIR *dirp;
+ struct rootdir_entry
+ {
+ char *name;
+ dev_t dev;
+ ino_t ino;
+ struct rootdir_entry *next;
+ };
+ struct rootdir_entry *rootdir_list;
+ struct rootdir_entry **rootdir_tail;
+ int32 pos;
+ dev_t dev;
+ fs_info fi;
+
+ /* All volumes are mounted in the rootfs, directly under /. */
+ rootdir_list = NULL;
+ rootdir_tail = &rootdir_list;
+ dirp = opendir (PATH_SEP_STR);
+ if (dirp)
+ {
+ struct dirent *d;
+
+ while ((d = readdir (dirp)) != NULL)
+ {
+ char *name;
+ struct stat statbuf;
+
+ if (DIR_IS_DOT (d->d_name))
+ continue;
+
+ if (DIR_IS_DOTDOT (d->d_name))
+ name = g_strdup (PATH_SEP_STR);
+ else
+ name = g_strconcat (PATH_SEP_STR, d->d_name, (char *) NULL);
+
+ if (lstat (name, &statbuf) >= 0 && S_ISDIR (statbuf.st_mode))
+ {
+ struct rootdir_entry *re = g_malloc (sizeof (*re));
+ re->name = name;
+ re->dev = statbuf.st_dev;
+ re->ino = statbuf.st_ino;
+
+ /* Add to the linked list. */
+ *rootdir_tail = re;
+ rootdir_tail = &re->next;
+ }
+ else
+ g_free (name);
+ }
+ closedir (dirp);
+ }
+ *rootdir_tail = NULL;
+
+ for (pos = 0; (dev = next_dev (&pos)) >= 0;)
+ if (fs_stat_dev (dev, &fi) >= 0)
+ {
+ /* Note: fi.dev == dev. */
+ struct rootdir_entry *re;
+
+ for (re = rootdir_list; re; re = re->next)
+ if (re->dev == fi.dev && re->ino == fi.root)
+ break;
+
+ me = g_malloc (sizeof (*me));
+ me->me_devname =
+ g_strdup (fi.device_name[0] != '\0' ? fi.device_name : fi.fsh_name);
+ me->me_mountdir = g_strdup (re != NULL ? re->name : fi.fsh_name);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (fi.fsh_name);
+ me->me_type_malloced = 1;
+ me->me_dev = fi.dev;
+ me->me_dummy = 0;
+ me->me_remote = (fi.flags & B_FS_IS_SHARED) != 0;
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+
+ while (rootdir_list != NULL)
+ {
+ struct rootdir_entry *re = rootdir_list;
+
+ rootdir_list = re->next;
+ g_free (re->name);
+ g_free (re);
+ }
+ }
+#endif /* MOUNTED_FS_STAT_DEV */
+
+#ifdef MOUNTED_GETFSSTAT /* OSF/1, also (obsolete) Apple Darwin 1.3 */
+ {
+ int numsys, counter;
+ size_t bufsize;
+ struct statfs *stats;
+
+ numsys = getfsstat (NULL, 0L, MNT_NOWAIT);
+ if (numsys < 0)
+ return NULL;
+ if (SIZE_MAX / sizeof (*stats) <= numsys)
+ {
+ fprintf (stderr, "%s\n", _("Memory exhausted!"));
+ exit (EXIT_FAILURE);
+ }
+
+ bufsize = (1 + numsys) * sizeof (*stats);
+ stats = g_malloc (bufsize);
+ numsys = getfsstat (stats, bufsize, MNT_NOWAIT);
+
+ if (numsys < 0)
+ {
+ g_free (stats);
+ return NULL;
+ }
+
+ for (counter = 0; counter < numsys; counter++)
+ {
+ me = g_malloc (sizeof (*me));
+ me->me_devname = g_strdup (stats[counter].f_mntfromname);
+ me->me_mountdir = g_strdup (stats[counter].f_mntonname);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (FS_TYPE (stats[counter]));
+ me->me_type_malloced = 1;
+ me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+ me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+
+ g_free (stats);
+ }
+#endif /* MOUNTED_GETFSSTAT */
+
+#if defined MOUNTED_FREAD_FSTYP /* (obsolete) SVR3 */
+ {
+ struct mnttab mnt;
+ char *table = "/etc/mnttab";
+ FILE *fp;
+
+ fp = fopen (table, "r");
+ if (fp == NULL)
+ return NULL;
+
+ while (fread (&mnt, sizeof (mnt), 1, fp) > 0)
+ {
+ me = g_malloc (sizeof (*me));
+ me->me_devname = g_strdup (mnt.mt_dev);
+ me->me_mountdir = g_strdup (mnt.mt_filsys);
+ me->me_mntroot = NULL;
+ me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */
+ me->me_type = "";
+ me->me_type_malloced = 0;
+ {
+ struct statfs fsd;
+ char typebuf[FSTYPSZ];
+
+ if (statfs (me->me_mountdir, &fsd, sizeof (fsd), 0) != -1
+ && sysfs (GETFSTYP, fsd.f_fstyp, typebuf) != -1)
+ {
+ me->me_type = g_strdup (typebuf);
+ me->me_type_malloced = 1;
+ }
+ }
+ me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+
+ if (ferror (fp))
+ {
+ /* The last fread() call must have failed. */
+ int saved_errno = errno;
+
+ fclose (fp);
+ errno = saved_errno;
+ goto free_then_fail;
+ }
+
+ if (fclose (fp) == EOF)
+ goto free_then_fail;
+ }
+#endif /* MOUNTED_FREAD_FSTYP */
+
+#ifdef MOUNTED_GETEXTMNTENT /* Solaris >= 8 */
+ {
+ struct extmnttab mnt;
+ const char *table = MNTTAB;
+ FILE *fp;
+ int ret;
+
+ /* No locking is needed, because the contents of /etc/mnttab is generated by the kernel. */
+
+ errno = 0;
+ fp = fopen (table, "r");
+ if (fp == NULL)
+ ret = errno;
+ else
+ {
+ while ((ret = getextmntent (fp, &mnt, 1)) == 0)
+ {
+ me = g_malloc (sizeof *me);
+ me->me_devname = g_strdup (mnt.mnt_special);
+ me->me_mountdir = g_strdup (mnt.mnt_mountp);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (mnt.mnt_fstype);
+ me->me_type_malloced = 1;
+ me->me_dummy = MNT_IGNORE (&mnt) != 0;
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+ me->me_dev = makedev (mnt.mnt_major, mnt.mnt_minor);
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+
+ ret = fclose (fp) == EOF ? errno : 0 < ret ? 0 : -1;
+ /* Here ret = -1 means success, ret >= 0 means failure. */
+ }
+
+ if (ret >= 0)
+ {
+ errno = ret;
+ goto free_then_fail;
+ }
+ }
+#endif /* MOUNTED_GETEXTMNTENT */
+
+#ifdef MOUNTED_GETMNTENT2 /* Solaris < 8, also (obsolete) SVR4 */
+ {
+ struct mnttab mnt;
+ const char *table = MNTTAB;
+ FILE *fp;
+ int ret;
+ int lockfd = -1;
+
+#if defined F_RDLCK && defined F_SETLKW
+ /* MNTTAB_LOCK is a macro name of our own invention; it's not present in
+ e.g. Solaris 2.6. If the SVR4 folks ever define a macro
+ for this file name, we should use their macro name instead.
+ (Why not just lock MNTTAB directly? We don't know.) */
+#ifndef MNTTAB_LOCK
+#define MNTTAB_LOCK "/etc/.mnttab.lock"
+#endif
+ lockfd = open (MNTTAB_LOCK, O_RDONLY);
+ if (lockfd >= 0)
+ {
+ struct flock flock;
+
+ flock.l_type = F_RDLCK;
+ flock.l_whence = SEEK_SET;
+ flock.l_start = 0;
+ flock.l_len = 0;
+ while (fcntl (lockfd, F_SETLKW, &flock) == -1)
+ if (errno != EINTR)
+ {
+ int saved_errno = errno;
+ close (lockfd);
+ errno = saved_errno;
+ return NULL;
+ }
+ }
+ else if (errno != ENOENT)
+ return NULL;
+#endif
+
+ errno = 0;
+ fp = fopen (table, "r");
+ if (fp == NULL)
+ ret = errno;
+ else
+ {
+ while ((ret = getmntent (fp, &mnt)) == 0)
+ {
+ me = g_malloc (sizeof (*me));
+ me->me_devname = g_strdup (mnt.mnt_special);
+ me->me_mountdir = g_strdup (mnt.mnt_mountp);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (mnt.mnt_fstype);
+ me->me_type_malloced = 1;
+ me->me_dummy = MNT_IGNORE (&mnt) != 0;
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+ me->me_dev = dev_from_mount_options (mnt.mnt_mntopts);
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+
+ ret = fclose (fp) == EOF ? errno : 0 < ret ? 0 : -1;
+ /* Here ret = -1 means success, ret >= 0 means failure. */
+ }
+
+ if (lockfd >= 0 && close (lockfd) != 0)
+ ret = errno;
+
+ if (ret >= 0)
+ {
+ errno = ret;
+ goto free_then_fail;
+ }
+ }
+#endif /* MOUNTED_GETMNTENT2. */
+
+#ifdef MOUNTED_VMOUNT /* AIX */
+ {
+ int bufsize;
+ void *entries;
+ char *thisent;
+ struct vmount *vmp;
+ int n_entries;
+ int i;
+
+ /* Ask how many bytes to allocate for the mounted file system info. */
+ entries = &bufsize;
+ if (mntctl (MCTL_QUERY, sizeof (bufsize), entries) != 0)
+ return NULL;
+ entries = g_malloc (bufsize);
+
+ /* Get the list of mounted file systems. */
+ n_entries = mntctl (MCTL_QUERY, bufsize, entries);
+ if (n_entries < 0)
+ {
+ int saved_errno = errno;
+
+ g_free (entries);
+ errno = saved_errno;
+ return NULL;
+ }
+
+ for (i = 0, thisent = entries; i < n_entries; i++, thisent += vmp->vmt_length)
+ {
+ char *options, *ignore;
+
+ vmp = (struct vmount *) thisent;
+ me = g_malloc (sizeof (*me));
+ if (vmp->vmt_flags & MNT_REMOTE)
+ {
+ char *host, *dir;
+
+ me->me_remote = 1;
+ /* Prepend the remote dirname. */
+ host = thisent + vmp->vmt_data[VMT_HOSTNAME].vmt_off;
+ dir = thisent + vmp->vmt_data[VMT_OBJECT].vmt_off;
+ me->me_devname = g_strconcat (host, ":", dir, (char *) NULL);
+ }
+ else
+ {
+ me->me_remote = 0;
+ me->me_devname = g_strdup (thisent + vmp->vmt_data[VMT_OBJECT].vmt_off);
+ }
+ me->me_mountdir = g_strdup (thisent + vmp->vmt_data[VMT_STUB].vmt_off);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (fstype_to_string (vmp->vmt_gfstype));
+ me->me_type_malloced = 1;
+ options = thisent + vmp->vmt_data[VMT_ARGS].vmt_off;
+ ignore = strstr (options, "ignore");
+ me->me_dummy = (ignore
+ && (ignore == options || ignore[-1] == ',')
+ && (ignore[sizeof ("ignore") - 1] == ','
+ || ignore[sizeof ("ignore") - 1] == '\0'));
+ me->me_dev = (dev_t) (-1); /* vmt_fsid might be the info we want. */
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+ g_free (entries);
+ }
+#endif /* MOUNTED_VMOUNT. */
+
+#ifdef MOUNTED_INTERIX_STATVFS /* Interix */
+ {
+ DIR *dirp = opendir ("/dev/fs");
+ char node[9 + NAME_MAX];
+
+ if (!dirp)
+ goto free_then_fail;
+
+ while (1)
+ {
+ struct statvfs dev;
+ struct dirent entry;
+ struct dirent *result;
+
+ if (readdir_r (dirp, &entry, &result) || result == NULL)
+ break;
+
+ strcpy (node, "/dev/fs/");
+ strcat (node, entry.d_name);
+
+ if (statvfs (node, &dev) == 0)
+ {
+ me = g_malloc (sizeof *me);
+ me->me_devname = g_strdup (dev.f_mntfromname);
+ me->me_mountdir = g_strdup (dev.f_mntonname);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (dev.f_fstypename);
+ me->me_type_malloced = 1;
+ me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+ me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+ }
+ closedir (dirp);
+ }
+#endif /* MOUNTED_INTERIX_STATVFS */
+
+ return g_slist_reverse (mount_list);
+
+ free_then_fail:
+ {
+ int saved_errno = errno;
+
+ g_slist_free_full (mount_list, (GDestroyNotify) free_mount_entry);
+
+ errno = saved_errno;
+ return NULL;
+ }
+}
+#endif /* HAVE_INFOMOUNT_LIST */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_INFOMOUNT_QNX
+/**
+ ** QNX has no [gs]etmnt*(), [gs]etfs*(), or /etc/mnttab, but can do
+ ** this via the following code.
+ ** Note that, as this is based on CWD, it only fills one mount_entry
+ ** structure. See my_statfs() below for the "other side" of this hack.
+ */
+
+static GSList *
+read_file_system_list (void)
+{
+ struct _disk_entry de;
+ struct statfs fs;
+ int i, fd;
+ char *tp, dev[_POSIX_NAME_MAX], dir[_POSIX_PATH_MAX];
+ struct mount_entry *me = NULL;
+ static GSList *list = NULL;
+
+ if (list != NULL)
+ {
+ me = (struct mount_entry *) list->data;
+
+ g_free (me->me_devname);
+ g_free (me->me_mountdir);
+ g_free (me->me_mntroot);
+ g_free (me->me_type);
+ }
+ else
+ {
+ me = (struct mount_entry *) g_malloc (sizeof (struct mount_entry));
+ list = g_slist_prepend (list, me);
+ }
+
+ if (!getcwd (dir, _POSIX_PATH_MAX))
+ return (NULL);
+
+ fd = open (dir, O_RDONLY);
+ if (fd == -1)
+ return (NULL);
+
+ i = disk_get_entry (fd, &de);
+
+ close (fd);
+
+ if (i == -1)
+ return (NULL);
+
+ switch (de.disk_type)
+ {
+ case _UNMOUNTED:
+ tp = "unmounted";
+ break;
+ case _FLOPPY:
+ tp = "Floppy";
+ break;
+ case _HARD:
+ tp = "Hard";
+ break;
+ case _RAMDISK:
+ tp = "Ram";
+ break;
+ case _REMOVABLE:
+ tp = "Removable";
+ break;
+ case _TAPE:
+ tp = "Tape";
+ break;
+ case _CDROM:
+ tp = "CDROM";
+ break;
+ default:
+ tp = "unknown";
+ }
+
+ if (fsys_get_mount_dev (dir, &dev) == -1)
+ return (NULL);
+
+ if (fsys_get_mount_pt (dev, &dir) == -1)
+ return (NULL);
+
+ me->me_devname = g_strdup (dev);
+ me->me_mountdir = g_strdup (dir);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (tp);
+ me->me_dev = de.disk_type;
+
+#ifdef DEBUG
+ fprintf (stderr,
+ "disk_get_entry():\n\tdisk_type=%d (%s)\n\tdriver_name='%-*.*s'\n\tdisk_drv=%d\n",
+ de.disk_type, tp, _DRIVER_NAME_LEN, _DRIVER_NAME_LEN, de.driver_name, de.disk_drv);
+ fprintf (stderr, "fsys_get_mount_dev():\n\tdevice='%s'\n", dev);
+ fprintf (stderr, "fsys_get_mount_pt():\n\tmount point='%s'\n", dir);
+#endif /* DEBUG */
+
+ return (list);
+}
+#endif /* HAVE_INFOMOUNT_QNX */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_INFOMOUNT
+/* Fill in the fields of FSP with information about space usage for
+ the file system on which FILE resides.
+ DISK is the device on which FILE is mounted, for space-getting
+ methods that need to know it.
+ Return 0 if successful, -1 if not. When returning -1, ensure that
+ ERRNO is either a system error value, or zero if DISK is NULL
+ on a system that requires a non-NULL value. */
+static int
+get_fs_usage (char const *file, char const *disk, struct fs_usage *fsp)
+{
+#ifdef STAT_STATVFS /* POSIX, except pre-2.6.36 glibc/Linux */
+
+ if (statvfs_works ())
+ {
+ struct statvfs vfsd;
+
+ if (statvfs (file, &vfsd) < 0)
+ return -1;
+
+ /* f_frsize isn't guaranteed to be supported. */
+ fsp->fsu_blocksize = (vfsd.f_frsize
+ ? PROPAGATE_ALL_ONES (vfsd.f_frsize)
+ : PROPAGATE_ALL_ONES (vfsd.f_bsize));
+
+ fsp->fsu_blocks = PROPAGATE_ALL_ONES (vfsd.f_blocks);
+ fsp->fsu_bfree = PROPAGATE_ALL_ONES (vfsd.f_bfree);
+ fsp->fsu_bavail = PROPAGATE_TOP_BIT (vfsd.f_bavail);
+ fsp->fsu_bavail_top_bit_set = EXTRACT_TOP_BIT (vfsd.f_bavail) != 0;
+ fsp->fsu_files = PROPAGATE_ALL_ONES (vfsd.f_files);
+ fsp->fsu_ffree = PROPAGATE_ALL_ONES (vfsd.f_ffree);
+ }
+ else
+#endif
+
+ {
+#if defined STAT_STATVFS64 /* AIX */
+
+ struct statvfs64 fsd;
+
+ if (statvfs64 (file, &fsd) < 0)
+ return -1;
+
+ /* f_frsize isn't guaranteed to be supported. */
+ /* *INDENT-OFF* */
+ fsp->fsu_blocksize = fsd.f_frsize
+ ? PROPAGATE_ALL_ONES (fsd.f_frsize)
+ : PROPAGATE_ALL_ONES (fsd.f_bsize);
+ /* *INDENT-ON* */
+
+#elif defined STAT_STATFS3_OSF1 /* OSF/1 */
+
+ struct statfs fsd;
+
+ if (statfs (file, &fsd, sizeof (struct statfs)) != 0)
+ return -1;
+
+ fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_fsize);
+
+#elif defined STAT_STATFS2_FRSIZE /* 2.6 < glibc/Linux < 2.6.36 */
+
+ struct statfs fsd;
+
+ if (statfs (file, &fsd) < 0)
+ return -1;
+
+ fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_frsize);
+
+#elif defined STAT_STATFS2_BSIZE /* glibc/Linux < 2.6, 4.3BSD, SunOS 4, \
+ Mac OS X < 10.4, FreeBSD < 5.0, \
+ NetBSD < 3.0, OpenBSD < 4.4 */
+
+ struct statfs fsd;
+
+ if (statfs (file, &fsd) < 0)
+ return -1;
+
+ fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_bsize);
+
+#ifdef STATFS_TRUNCATES_BLOCK_COUNTS
+
+ /* In SunOS 4.1.2, 4.1.3, and 4.1.3_U1, the block counts in the
+ struct statfs are truncated to 2GB. These conditions detect that
+ truncation, presumably without botching the 4.1.1 case, in which
+ the values are not truncated. The correct counts are stored in
+ undocumented spare fields. */
+ if (fsd.f_blocks == 0x7fffffff / fsd.f_bsize && fsd.f_spare[0] > 0)
+ {
+ fsd.f_blocks = fsd.f_spare[0];
+ fsd.f_bfree = fsd.f_spare[1];
+ fsd.f_bavail = fsd.f_spare[2];
+ }
+#endif /* STATFS_TRUNCATES_BLOCK_COUNTS */
+
+#elif defined STAT_STATFS2_FSIZE /* 4.4BSD and older NetBSD */
+
+ struct statfs fsd;
+
+ if (statfs (file, &fsd) < 0)
+ return -1;
+
+ fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_fsize);
+
+#elif defined STAT_STATFS4 /* SVR3, old Irix */
+
+ struct statfs fsd;
+
+ if (statfs (file, &fsd, sizeof (fsd), 0) < 0)
+ return -1;
+
+ /* Empirically, the block counts on most SVR3 and SVR3-derived
+ systems seem to always be in terms of 512-byte blocks,
+ no matter what value f_bsize has. */
+ fsp->fsu_blocksize = 512;
+#endif
+
+#if (defined STAT_STATVFS64 || defined STAT_STATFS3_OSF1 \
+ || defined STAT_STATFS2_FRSIZE || defined STAT_STATFS2_BSIZE \
+ || defined STAT_STATFS2_FSIZE || defined STAT_STATFS4)
+
+ fsp->fsu_blocks = PROPAGATE_ALL_ONES (fsd.f_blocks);
+ fsp->fsu_bfree = PROPAGATE_ALL_ONES (fsd.f_bfree);
+ fsp->fsu_bavail = PROPAGATE_TOP_BIT (fsd.f_bavail);
+ fsp->fsu_bavail_top_bit_set = EXTRACT_TOP_BIT (fsd.f_bavail) != 0;
+ fsp->fsu_files = PROPAGATE_ALL_ONES (fsd.f_files);
+ fsp->fsu_ffree = PROPAGATE_ALL_ONES (fsd.f_ffree);
+
+#endif
+ }
+
+ (void) disk; /* avoid argument-unused warning */
+
+ return 0;
+}
+#endif /* HAVE_INFOMOUNT */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+free_my_statfs (void)
+{
+#ifdef HAVE_INFOMOUNT_LIST
+ g_clear_slist (&mc_mount_list, (GDestroyNotify) free_mount_entry);
+#endif /* HAVE_INFOMOUNT_LIST */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+init_my_statfs (void)
+{
+#ifdef HAVE_INFOMOUNT_LIST
+ free_my_statfs ();
+ mc_mount_list = read_file_system_list ();
+#endif /* HAVE_INFOMOUNT_LIST */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+my_statfs (struct my_statfs *myfs_stats, const char *path)
+{
+#ifdef HAVE_INFOMOUNT_LIST
+ size_t len = 0;
+ struct mount_entry *entry = NULL;
+ GSList *temp;
+ struct fs_usage fs_use;
+
+ for (temp = mc_mount_list; temp != NULL; temp = g_slist_next (temp))
+ {
+ struct mount_entry *me;
+ size_t i;
+
+ me = (struct mount_entry *) temp->data;
+ i = strlen (me->me_mountdir);
+ if (i > len && (strncmp (path, me->me_mountdir, i) == 0) &&
+ (entry == NULL || IS_PATH_SEP (path[i]) || path[i] == '\0'))
+ {
+ len = i;
+ entry = me;
+ }
+ }
+
+ if (entry != NULL)
+ {
+ memset (&fs_use, 0, sizeof (fs_use));
+ get_fs_usage (entry->me_mountdir, NULL, &fs_use);
+
+ myfs_stats->type = entry->me_dev;
+ myfs_stats->typename = entry->me_type;
+ myfs_stats->mpoint = entry->me_mountdir;
+ myfs_stats->mroot = entry->me_mntroot;
+ myfs_stats->device = entry->me_devname;
+ myfs_stats->avail =
+ ((uintmax_t) (getuid ()? fs_use.fsu_bavail : fs_use.fsu_bfree) *
+ fs_use.fsu_blocksize) >> 10;
+ myfs_stats->total = ((uintmax_t) fs_use.fsu_blocks * fs_use.fsu_blocksize) >> 10;
+ myfs_stats->nfree = (uintmax_t) fs_use.fsu_ffree;
+ myfs_stats->nodes = (uintmax_t) fs_use.fsu_files;
+ }
+ else
+#endif /* HAVE_INFOMOUNT_LIST */
+
+#ifdef HAVE_INFOMOUNT_QNX
+ /*
+ ** This is the "other side" of the hack to read_file_system_list() above.
+ ** It's not the most efficient approach, but consumes less memory. It
+ ** also accommodates QNX's ability to mount filesystems on the fly.
+ */
+ struct mount_entry *entry;
+ struct fs_usage fs_use;
+
+ entry = read_file_system_list ();
+ if (entry != NULL)
+ {
+ get_fs_usage (entry->me_mountdir, NULL, &fs_use);
+
+ myfs_stats->type = entry->me_dev;
+ myfs_stats->typename = entry->me_type;
+ myfs_stats->mpoint = entry->me_mountdir;
+ myfs_stats->mroot = entry->me_mntroot;
+ myfs_stats->device = entry->me_devname;
+
+ myfs_stats->avail = ((uintmax_t) fs_use.fsu_bfree * fs_use.fsu_blocksize) >> 10;
+ myfs_stats->total = ((uintmax_t) fs_use.fsu_blocks * fs_use.fsu_blocksize) >> 10;
+ myfs_stats->nfree = (uintmax_t) fs_use.fsu_ffree;
+ myfs_stats->nodes = (uintmax_t) fs_use.fsu_files;
+ }
+ else
+#endif /* HAVE_INFOMOUNT_QNX */
+ {
+ myfs_stats->type = 0;
+ myfs_stats->mpoint = "unknown";
+ myfs_stats->device = "unknown";
+ myfs_stats->avail = 0;
+ myfs_stats->total = 0;
+ myfs_stats->nfree = 0;
+ myfs_stats->nodes = 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/mountlist.h b/src/filemanager/mountlist.h
new file mode 100644
index 0000000..f506488
--- /dev/null
+++ b/src/filemanager/mountlist.h
@@ -0,0 +1,44 @@
+/*
+ Declarations for list of mounted filesystems
+ */
+
+/** \file mountlist.h
+ * \brief Header: list of mounted filesystems
+ */
+
+#ifndef MC__MOUNTLIST_H
+#define MC__MOUNTLIST_H
+
+#include <stdint.h> /* uintmax_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* Filesystem status */
+struct my_statfs
+{
+ int type;
+ char *typename;
+ const char *mpoint;
+ const char *mroot;
+ const char *device;
+ uintmax_t avail; /* in kB */
+ uintmax_t total; /* in kB */
+ uintmax_t nfree;
+ uintmax_t nodes;
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void init_my_statfs (void);
+void my_statfs (struct my_statfs *myfs_stats, const char *path);
+void free_my_statfs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__MOUNTLIST_H */
diff --git a/src/filemanager/panel.c b/src/filemanager/panel.c
new file mode 100644
index 0000000..ec1dbc3
--- /dev/null
+++ b/src/filemanager/panel.c
@@ -0,0 +1,5428 @@
+/*
+ Panel managing.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1995
+ Timur Bakeyev, 1997, 1999
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2023
+
+ 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 panel.c
+ * \brief Source: panel managin module
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* XCTRL and ALT macros */
+#include "lib/skin.h"
+#include "lib/strescape.h"
+#include "lib/mcconfig.h"
+#include "lib/vfs/vfs.h"
+#include "lib/unixcompat.h"
+#include "lib/search.h"
+#include "lib/timefmt.h" /* file_date() */
+#include "lib/util.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h" /* get_codepage_id () */
+#endif
+#include "lib/event.h"
+
+#include "src/setup.h" /* For loading/saving panel options */
+#include "src/execute.h"
+#ifdef HAVE_CHARSET
+#include "src/selcodepage.h" /* select_charset (), SELECT_CHARSET_NO_TRANSLATE */
+#endif
+#include "src/keymap.h" /* global_keymap_t */
+#include "src/history.h"
+#ifdef ENABLE_SUBSHELL
+#include "src/subshell/subshell.h" /* do_subshell_chdir() */
+#endif
+
+#include "src/usermenu.h"
+
+#include "dir.h"
+#include "boxes.h"
+#include "tree.h"
+#include "ext.h" /* regexp_command */
+#include "layout.h" /* Most layout variables are here */
+#include "cmd.h"
+#include "command.h" /* cmdline */
+#include "filemanager.h"
+#include "mountlist.h" /* my_statfs */
+#include "cd.h" /* cd_error_message() */
+
+#include "panel.h"
+
+/*** global variables ****************************************************************************/
+
+/* The hook list for the select file function */
+hook_t *select_file_hook = NULL;
+
+mc_fhl_t *mc_filehighlight = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+typedef enum
+{
+ FATTR_NORMAL = 0,
+ FATTR_CURRENT,
+ FATTR_MARKED,
+ FATTR_MARKED_CURRENT,
+ FATTR_STATUS
+} file_attr_t;
+
+/* select/unselect dialog results */
+#define SELECT_RESET ((mc_search_t *)(-1))
+#define SELECT_ERROR ((mc_search_t *)(-2))
+
+/* mouse position relative to file list */
+#define MOUSE_UPPER_FILE_LIST (-1)
+#define MOUSE_BELOW_FILE_LIST (-2)
+#define MOUSE_AFTER_LAST_FILE (-3)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef enum
+{
+ MARK_DONT_MOVE = 0,
+ MARK_DOWN = 1,
+ MARK_FORCE_DOWN = 2,
+ MARK_FORCE_UP = 3
+} mark_act_t;
+
+/*
+ * This describes a format item. The parse_display_format routine parses
+ * the user specified format and creates a linked list of format_item_t structures.
+ */
+typedef struct format_item_t
+{
+ int requested_field_len;
+ int field_len;
+ align_crt_t just_mode;
+ gboolean expand;
+ const char *(*string_fn) (file_entry_t *, int len);
+ char *title;
+ const char *id;
+} format_item_t;
+
+/* File name scroll states */
+typedef enum
+{
+ FILENAME_NOSCROLL = 1,
+ FILENAME_SCROLL_LEFT = 2,
+ FILENAME_SCROLL_RIGHT = 4
+} filename_scroll_flag_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static const char *string_file_name (file_entry_t * fe, int len);
+static const char *string_file_size (file_entry_t * fe, int len);
+static const char *string_file_size_brief (file_entry_t * fe, int len);
+static const char *string_file_type (file_entry_t * fe, int len);
+static const char *string_file_mtime (file_entry_t * fe, int len);
+static const char *string_file_atime (file_entry_t * fe, int len);
+static const char *string_file_ctime (file_entry_t * fe, int len);
+static const char *string_file_permission (file_entry_t * fe, int len);
+static const char *string_file_perm_octal (file_entry_t * fe, int len);
+static const char *string_file_nlinks (file_entry_t * fe, int len);
+static const char *string_inode (file_entry_t * fe, int len);
+static const char *string_file_nuid (file_entry_t * fe, int len);
+static const char *string_file_ngid (file_entry_t * fe, int len);
+static const char *string_file_owner (file_entry_t * fe, int len);
+static const char *string_file_group (file_entry_t * fe, int len);
+static const char *string_marked (file_entry_t * fe, int len);
+static const char *string_space (file_entry_t * fe, int len);
+static const char *string_dot (file_entry_t * fe, int len);
+
+/*** file scope variables ************************************************************************/
+
+/* *INDENT-OFF* */
+static panel_field_t panel_fields[] = {
+ {
+ "unsorted", 12, TRUE, J_LEFT_FIT,
+ /* TRANSLATORS: one single character to represent 'unsorted' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|u"),
+ N_("&Unsorted"), TRUE, FALSE,
+ string_file_name,
+ (GCompareFunc) unsorted
+ }
+ ,
+ {
+ "name", 12, TRUE, J_LEFT_FIT,
+ /* TRANSLATORS: one single character to represent 'name' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|n"),
+ N_("&Name"), TRUE, TRUE,
+ string_file_name,
+ (GCompareFunc) sort_name
+ }
+ ,
+ {
+ "version", 12, TRUE, J_LEFT_FIT,
+ /* TRANSLATORS: one single character to represent 'version' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|v"),
+ N_("&Version"), TRUE, FALSE,
+ string_file_name,
+ (GCompareFunc) sort_vers
+ }
+ ,
+ {
+ "extension", 12, TRUE, J_LEFT_FIT,
+ /* TRANSLATORS: one single character to represent 'extension' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|e"),
+ N_("E&xtension"), TRUE, FALSE,
+ string_file_name, /* TODO: string_file_ext */
+ (GCompareFunc) sort_ext
+ }
+ ,
+ {
+ "size", 7, FALSE, J_RIGHT,
+ /* TRANSLATORS: one single character to represent 'size' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|s"),
+ N_("&Size"), TRUE, TRUE,
+ string_file_size,
+ (GCompareFunc) sort_size
+ }
+ ,
+ {
+ "bsize", 7, FALSE, J_RIGHT,
+ "",
+ N_("Block Size"), FALSE, FALSE,
+ string_file_size_brief,
+ (GCompareFunc) sort_size
+ }
+ ,
+ {
+ "type", 1, FALSE, J_LEFT,
+ "",
+ "", FALSE, TRUE,
+ string_file_type,
+ NULL
+ }
+ ,
+ {
+ "mtime", 12, FALSE, J_RIGHT,
+ /* TRANSLATORS: one single character to represent 'Modify time' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|m"),
+ N_("&Modify time"), TRUE, TRUE,
+ string_file_mtime,
+ (GCompareFunc) sort_time
+ }
+ ,
+ {
+ "atime", 12, FALSE, J_RIGHT,
+ /* TRANSLATORS: one single character to represent 'Access time' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|a"),
+ N_("&Access time"), TRUE, TRUE,
+ string_file_atime,
+ (GCompareFunc) sort_atime
+ }
+ ,
+ {
+ "ctime", 12, FALSE, J_RIGHT,
+ /* TRANSLATORS: one single character to represent 'Change time' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|h"),
+ N_("C&hange time"), TRUE, TRUE,
+ string_file_ctime,
+ (GCompareFunc) sort_ctime
+ }
+ ,
+ {
+ "perm", 10, FALSE, J_LEFT,
+ "",
+ N_("Permission"), FALSE, TRUE,
+ string_file_permission,
+ NULL
+ }
+ ,
+ {
+ "mode", 6, FALSE, J_RIGHT,
+ "",
+ N_("Perm"), FALSE, TRUE,
+ string_file_perm_octal,
+ NULL
+ }
+ ,
+ {
+ "nlink", 2, FALSE, J_RIGHT,
+ "",
+ N_("Nl"), FALSE, TRUE,
+ string_file_nlinks, NULL
+ }
+ ,
+ {
+ "inode", 5, FALSE, J_RIGHT,
+ /* TRANSLATORS: one single character to represent 'inode' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|i"),
+ N_("&Inode"), TRUE, TRUE,
+ string_inode,
+ (GCompareFunc) sort_inode
+ }
+ ,
+ {
+ "nuid", 5, FALSE, J_RIGHT,
+ "",
+ N_("UID"), FALSE, FALSE,
+ string_file_nuid,
+ NULL
+ }
+ ,
+ {
+ "ngid", 5, FALSE, J_RIGHT,
+ "",
+ N_("GID"), FALSE, FALSE,
+ string_file_ngid,
+ NULL
+ }
+ ,
+ {
+ "owner", 8, FALSE, J_LEFT_FIT,
+ "",
+ N_("Owner"), FALSE, TRUE,
+ string_file_owner,
+ NULL
+ }
+ ,
+ {
+ "group", 8, FALSE, J_LEFT_FIT,
+ "",
+ N_("Group"), FALSE, TRUE,
+ string_file_group,
+ NULL
+ }
+ ,
+ {
+ "mark", 1, FALSE, J_RIGHT,
+ "",
+ " ", FALSE, TRUE,
+ string_marked,
+ NULL
+ }
+ ,
+ {
+ "|", 1, FALSE, J_RIGHT,
+ "",
+ " ", FALSE, TRUE,
+ NULL,
+ NULL
+ }
+ ,
+ {
+ "space", 1, FALSE, J_RIGHT,
+ "",
+ " ", FALSE, TRUE,
+ string_space,
+ NULL
+ }
+ ,
+ {
+ "dot", 1, FALSE, J_RIGHT,
+ "",
+ " ", FALSE, FALSE,
+ string_dot,
+ NULL
+ }
+ ,
+ {
+ NULL, 0, FALSE, J_RIGHT, NULL, NULL, FALSE, FALSE, NULL, NULL
+ }
+};
+/* *INDENT-ON* */
+
+static char *panel_sort_up_char = NULL;
+static char *panel_sort_down_char = NULL;
+
+static char *panel_hiddenfiles_show_char = NULL;
+static char *panel_hiddenfiles_hide_char = NULL;
+static char *panel_history_prev_item_char = NULL;
+static char *panel_history_next_item_char = NULL;
+static char *panel_history_show_list_char = NULL;
+static char *panel_filename_scroll_left_char = NULL;
+static char *panel_filename_scroll_right_char = NULL;
+
+/* Panel that selection started */
+static WPanel *mouse_mark_panel = NULL;
+
+static gboolean mouse_marking = FALSE;
+static int state_mark = 0;
+
+static GString *string_file_name_buffer;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static panelized_descr_t *
+panelized_descr_new (void)
+{
+ panelized_descr_t *p;
+
+ p = g_new0 (panelized_descr_t, 1);
+ p->list.len = -1;
+
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panelized_descr_free (panelized_descr_t * p)
+{
+ if (p != NULL)
+ {
+ dir_list_free_list (&p->list);
+ vfs_path_free (p->root_vpath, TRUE);
+ g_free (p);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+set_colors (const WPanel * panel)
+{
+ (void) panel;
+
+ tty_set_normal_attrs ();
+ tty_setcolor (NORMAL_COLOR);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Delete format_item_t object */
+
+static void
+format_item_free (format_item_t * format)
+{
+ g_free (format->title);
+ g_free (format);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Extract the number of available lines in a panel */
+
+static int
+panel_lines (const WPanel * p)
+{
+ /* 3 lines are: top frame, column header, button frame */
+ return (CONST_WIDGET (p)->rect.lines - 3 - (panels_options.show_mini_info ? 2 : 0));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This code relies on the default justification!!! */
+
+static void
+add_permission_string (const char *dest, int width, file_entry_t * fe, file_attr_t attr, int color,
+ gboolean is_octal)
+{
+ int i, r, l;
+
+ l = get_user_permissions (&fe->st);
+
+ if (is_octal)
+ {
+ /* Place of the access bit in octal mode */
+ l = width + l - 3;
+ r = l + 1;
+ }
+ else
+ {
+ /* The same to the triplet in string mode */
+ l = l * 3 + 1;
+ r = l + 3;
+ }
+
+ for (i = 0; i < width; i++)
+ {
+ if (i >= l && i < r)
+ {
+ if (attr == FATTR_CURRENT || attr == FATTR_MARKED_CURRENT)
+ tty_setcolor (MARKED_SELECTED_COLOR);
+ else
+ tty_setcolor (MARKED_COLOR);
+ }
+ else if (color >= 0)
+ tty_setcolor (color);
+ else
+ tty_lowlevel_setcolor (-color);
+
+ tty_print_char (dest[i]);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** String representations of various file attributes name */
+
+static const char *
+string_file_name (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ mc_g_string_copy (string_file_name_buffer, fe->fname);
+
+ return string_file_name_buffer->str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static unsigned int
+ilog10 (dev_t n)
+{
+ unsigned int digits = 0;
+
+ do
+ {
+ digits++;
+ n /= 10;
+ }
+ while (n != 0);
+
+ return digits;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+format_device_number (char *buf, size_t bufsize, dev_t dev)
+{
+ dev_t major_dev, minor_dev;
+ unsigned int major_digits, minor_digits;
+
+ major_dev = major (dev);
+ major_digits = ilog10 (major_dev);
+
+ minor_dev = minor (dev);
+ minor_digits = ilog10 (minor_dev);
+
+ g_assert (bufsize >= 1);
+
+ if (major_digits + 1 + minor_digits + 1 <= bufsize)
+ g_snprintf (buf, bufsize, "%lu,%lu", (unsigned long) major_dev, (unsigned long) minor_dev);
+ else
+ g_strlcpy (buf, _("[dev]"), bufsize);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** size */
+
+static const char *
+string_file_size (file_entry_t * fe, int len)
+{
+ static char buffer[BUF_TINY];
+
+ /* Don't ever show size of ".." since we don't calculate it */
+ if (DIR_IS_DOTDOT (fe->fname->str))
+ return _("UP--DIR");
+
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ if (S_ISBLK (fe->st.st_mode) || S_ISCHR (fe->st.st_mode))
+ format_device_number (buffer, len + 1, fe->st.st_rdev);
+ else
+#endif
+ size_trunc_len (buffer, (unsigned int) len, fe->st.st_size, 0, panels_options.kilobyte_si);
+
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** bsize */
+
+static const char *
+string_file_size_brief (file_entry_t * fe, int len)
+{
+ if (S_ISLNK (fe->st.st_mode) && !link_isdir (fe))
+ return _("SYMLINK");
+
+ if ((S_ISDIR (fe->st.st_mode) || link_isdir (fe)) && !DIR_IS_DOTDOT (fe->fname->str))
+ return _("SUB-DIR");
+
+ return string_file_size (fe, len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This functions return a string representation of a file entry type */
+
+static const char *
+string_file_type (file_entry_t * fe, int len)
+{
+ static char buffer[2];
+
+ (void) len;
+
+ if (S_ISDIR (fe->st.st_mode))
+ buffer[0] = PATH_SEP;
+ else if (S_ISLNK (fe->st.st_mode))
+ {
+ if (link_isdir (fe))
+ buffer[0] = '~';
+ else if (fe->f.stale_link != 0)
+ buffer[0] = '!';
+ else
+ buffer[0] = '@';
+ }
+ else if (S_ISCHR (fe->st.st_mode))
+ buffer[0] = '-';
+ else if (S_ISSOCK (fe->st.st_mode))
+ buffer[0] = '=';
+ else if (S_ISDOOR (fe->st.st_mode))
+ buffer[0] = '>';
+ else if (S_ISBLK (fe->st.st_mode))
+ buffer[0] = '+';
+ else if (S_ISFIFO (fe->st.st_mode))
+ buffer[0] = '|';
+ else if (S_ISNAM (fe->st.st_mode))
+ buffer[0] = '#';
+ else if (!S_ISREG (fe->st.st_mode))
+ buffer[0] = '?'; /* non-regular of unknown kind */
+ else if (is_exe (fe->st.st_mode))
+ buffer[0] = '*';
+ else
+ buffer[0] = ' ';
+ buffer[1] = '\0';
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** mtime */
+
+static const char *
+string_file_mtime (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ return file_date (fe->st.st_mtime);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** atime */
+
+static const char *
+string_file_atime (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ return file_date (fe->st.st_atime);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** ctime */
+
+static const char *
+string_file_ctime (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ return file_date (fe->st.st_ctime);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** perm */
+
+static const char *
+string_file_permission (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ return string_perm (fe->st.st_mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** mode */
+
+static const char *
+string_file_perm_octal (file_entry_t * fe, int len)
+{
+ static char buffer[10];
+
+ (void) len;
+
+ g_snprintf (buffer, sizeof (buffer), "0%06lo", (unsigned long) fe->st.st_mode);
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** nlink */
+
+static const char *
+string_file_nlinks (file_entry_t * fe, int len)
+{
+ static char buffer[BUF_TINY];
+
+ (void) len;
+
+ g_snprintf (buffer, sizeof (buffer), "%16d", (int) fe->st.st_nlink);
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** inode */
+
+static const char *
+string_inode (file_entry_t * fe, int len)
+{
+ static char buffer[10];
+
+ (void) len;
+
+ g_snprintf (buffer, sizeof (buffer), "%lu", (unsigned long) fe->st.st_ino);
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** nuid */
+
+static const char *
+string_file_nuid (file_entry_t * fe, int len)
+{
+ static char buffer[10];
+
+ (void) len;
+
+ g_snprintf (buffer, sizeof (buffer), "%lu", (unsigned long) fe->st.st_uid);
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** ngid */
+
+static const char *
+string_file_ngid (file_entry_t * fe, int len)
+{
+ static char buffer[10];
+
+ (void) len;
+
+ g_snprintf (buffer, sizeof (buffer), "%lu", (unsigned long) fe->st.st_gid);
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** owner */
+
+static const char *
+string_file_owner (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ return get_owner (fe->st.st_uid);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** group */
+
+static const char *
+string_file_group (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ return get_group (fe->st.st_gid);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** mark */
+
+static const char *
+string_marked (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ return fe->f.marked != 0 ? "*" : " ";
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** space */
+
+static const char *
+string_space (file_entry_t * fe, int len)
+{
+ (void) fe;
+ (void) len;
+
+ return " ";
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** dot */
+
+static const char *
+string_dot (file_entry_t * fe, int len)
+{
+ (void) fe;
+ (void) len;
+
+ return ".";
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+file_compute_color (file_attr_t attr, file_entry_t * fe)
+{
+ switch (attr)
+ {
+ case FATTR_CURRENT:
+ return (SELECTED_COLOR);
+ case FATTR_MARKED:
+ return (MARKED_COLOR);
+ case FATTR_MARKED_CURRENT:
+ return (MARKED_SELECTED_COLOR);
+ case FATTR_STATUS:
+ return (NORMAL_COLOR);
+ case FATTR_NORMAL:
+ default:
+ if (!panels_options.filetype_mode)
+ return (NORMAL_COLOR);
+ }
+
+ return mc_fhl_get_color (mc_filehighlight, fe);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Returns the number of items in the given panel */
+
+static int
+panel_items (const WPanel * p)
+{
+ return panel_lines (p) * p->list_cols;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Formats the file number file_index of panel in the buffer dest */
+
+static filename_scroll_flag_t
+format_file (WPanel * panel, int file_index, int width, file_attr_t attr, gboolean isstatus,
+ int *field_length)
+{
+ int color = NORMAL_COLOR;
+ int length = 0;
+ GSList *format, *home;
+ file_entry_t *fe = NULL;
+ filename_scroll_flag_t res = FILENAME_NOSCROLL;
+
+ *field_length = 0;
+
+ if (file_index < panel->dir.len)
+ {
+ fe = &panel->dir.list[file_index];
+ color = file_compute_color (attr, fe);
+ }
+
+ home = isstatus ? panel->status_format : panel->format;
+
+ for (format = home; format != NULL && length != width; format = g_slist_next (format))
+ {
+ format_item_t *fi = (format_item_t *) format->data;
+
+ if (fi->string_fn != NULL)
+ {
+ const char *txt = " ";
+ int len, perm = 0;
+ const char *prepared_text;
+ int name_offset = 0;
+
+ if (fe != NULL)
+ txt = fi->string_fn (fe, fi->field_len);
+
+ len = fi->field_len;
+ if (len + length > width)
+ len = width - length;
+ if (len <= 0)
+ break;
+
+ if (!isstatus && panel->content_shift > -1 && strcmp (fi->id, "name") == 0)
+ {
+ int str_len;
+ int i;
+
+ *field_length = len + 1;
+
+ str_len = str_length (txt);
+ i = MAX (0, str_len - len);
+ panel->max_shift = MAX (panel->max_shift, i);
+ i = MIN (panel->content_shift, i);
+
+ if (i > -1)
+ {
+ name_offset = str_offset_to_pos (txt, i);
+ if (str_len > len)
+ {
+ res = FILENAME_SCROLL_LEFT;
+ if (str_length (txt + name_offset) > len)
+ res |= FILENAME_SCROLL_RIGHT;
+ }
+ }
+ }
+
+ if (panels_options.permission_mode)
+ {
+ if (strcmp (fi->id, "perm") == 0)
+ perm = 1;
+ else if (strcmp (fi->id, "mode") == 0)
+ perm = 2;
+ }
+
+ if (color >= 0)
+ tty_setcolor (color);
+ else
+ tty_lowlevel_setcolor (-color);
+
+ if (!isstatus && panel->content_shift > -1)
+ prepared_text = str_fit_to_term (txt + name_offset, len, HIDE_FIT (fi->just_mode));
+ else
+ prepared_text = str_fit_to_term (txt, len, fi->just_mode);
+
+ if (perm != 0 && fe != NULL)
+ add_permission_string (prepared_text, fi->field_len, fe, attr, color, perm != 1);
+ else
+ tty_print_string (prepared_text);
+
+ length += len;
+ }
+ else
+ {
+ if (attr == FATTR_CURRENT || attr == FATTR_MARKED_CURRENT)
+ tty_setcolor (SELECTED_COLOR);
+ else
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_one_vline (TRUE);
+ length++;
+ }
+ }
+
+ if (length < width)
+ {
+ int y, x;
+
+ tty_getyx (&y, &x);
+ tty_draw_hline (y, x, ' ', width - length);
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+repaint_file (WPanel * panel, int file_index, file_attr_t attr)
+{
+ Widget *w = WIDGET (panel);
+
+ int nth_column = 0;
+ int width;
+ int offset = 0;
+ filename_scroll_flag_t ret_frm;
+ int ypos = 0;
+ gboolean panel_is_split;
+ int fln = 0;
+
+ panel_is_split = panel->list_cols > 1;
+ width = w->rect.cols - 2;
+
+ if (panel_is_split)
+ {
+ nth_column = (file_index - panel->top) / panel_lines (panel);
+ width /= panel->list_cols;
+
+ offset = width * nth_column;
+
+ if (nth_column + 1 >= panel->list_cols)
+ width = w->rect.cols - offset - 2;
+ }
+
+ /* Nothing to paint */
+ if (width <= 0)
+ return;
+
+ ypos = file_index - panel->top;
+
+ if (panel_is_split)
+ ypos %= panel_lines (panel);
+
+ ypos += 2; /* top frame and header */
+ widget_gotoyx (w, ypos, offset + 1);
+
+ ret_frm = format_file (panel, file_index, width, attr, FALSE, &fln);
+
+ if (panel_is_split && nth_column + 1 < panel->list_cols)
+ {
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_one_vline (TRUE);
+ }
+
+ if (ret_frm != FILENAME_NOSCROLL)
+ {
+ if (!panel_is_split && fln > 0)
+ {
+ if (panel->list_format != list_long)
+ width = fln;
+ else
+ {
+ offset = width - fln + 1;
+ width = fln - 1;
+ }
+ }
+
+ widget_gotoyx (w, ypos, offset);
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_string (panel_filename_scroll_left_char);
+
+ if ((ret_frm & FILENAME_SCROLL_RIGHT) != 0)
+ {
+ offset += width;
+ if (nth_column + 1 >= panel->list_cols)
+ offset++;
+
+ widget_gotoyx (w, ypos, offset);
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_string (panel_filename_scroll_right_char);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+repaint_status (WPanel * panel)
+{
+ int width;
+
+ width = WIDGET (panel)->rect.cols - 2;
+ if (width > 0)
+ {
+ int fln = 0;
+
+ (void) format_file (panel, panel->current, width, FATTR_STATUS, TRUE, &fln);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+display_mini_info (WPanel * panel)
+{
+ Widget *w = WIDGET (panel);
+ const file_entry_t *fe;
+
+ if (!panels_options.show_mini_info || panel->current < 0)
+ return;
+
+ widget_gotoyx (w, panel_lines (panel) + 3, 1);
+
+ if (panel->quick_search.active)
+ {
+ tty_setcolor (INPUT_COLOR);
+ tty_print_char ('/');
+ tty_print_string (str_fit_to_term
+ (panel->quick_search.buffer->str, w->rect.cols - 3, J_LEFT));
+ return;
+ }
+
+ /* Status resolves links and show them */
+ set_colors (panel);
+
+ fe = panel_current_entry (panel);
+
+ if (S_ISLNK (fe->st.st_mode))
+ {
+ char link_target[MC_MAXPATHLEN];
+ vfs_path_t *lc_link_vpath;
+ int len;
+
+ lc_link_vpath = vfs_path_append_new (panel->cwd_vpath, fe->fname->str, (char *) NULL);
+ len = mc_readlink (lc_link_vpath, link_target, MC_MAXPATHLEN - 1);
+ vfs_path_free (lc_link_vpath, TRUE);
+ if (len > 0)
+ {
+ link_target[len] = 0;
+ tty_print_string ("-> ");
+ tty_print_string (str_fit_to_term (link_target, w->rect.cols - 5, J_LEFT_FIT));
+ }
+ else
+ tty_print_string (str_fit_to_term (_("<readlink failed>"), w->rect.cols - 2, J_LEFT));
+ }
+ else if (DIR_IS_DOTDOT (fe->fname->str))
+ {
+ /* FIXME:
+ * while loading directory (dir_list_load() and dir_list_reload()),
+ * the actual stat info about ".." directory isn't got;
+ * so just don't display incorrect info about ".." directory */
+ tty_print_string (str_fit_to_term (_("UP--DIR"), w->rect.cols - 2, J_LEFT));
+ }
+ else
+ /* Default behavior */
+ repaint_status (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+paint_dir (WPanel * panel)
+{
+ int i;
+ int items; /* Number of items */
+
+ items = panel_items (panel);
+ /* reset max len of filename because we have the new max length for the new file list */
+ panel->max_shift = -1;
+
+ for (i = 0; i < items; i++)
+ {
+ file_attr_t attr = FATTR_NORMAL; /* Color value of the line */
+ int n;
+ gboolean marked;
+
+ n = i + panel->top;
+ marked = (panel->dir.list[n].f.marked != 0);
+
+ if (n < panel->dir.len)
+ {
+ if (panel->current == n && panel->active)
+ attr = marked ? FATTR_MARKED_CURRENT : FATTR_CURRENT;
+ else if (marked)
+ attr = FATTR_MARKED;
+ }
+
+ repaint_file (panel, n, attr);
+ }
+
+ tty_set_normal_attrs ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+display_total_marked_size (const WPanel * panel, int y, int x, gboolean size_only)
+{
+ const Widget *w = CONST_WIDGET (panel);
+
+ char buffer[BUF_SMALL], b_bytes[BUF_SMALL];
+ const char *buf;
+ int cols;
+
+ if (panel->marked <= 0)
+ return;
+
+ buf = size_only ? b_bytes : buffer;
+ cols = w->rect.cols - 2;
+
+ g_strlcpy (b_bytes, size_trunc_sep (panel->total, panels_options.kilobyte_si),
+ sizeof (b_bytes));
+
+ if (!size_only)
+ g_snprintf (buffer, sizeof (buffer),
+ ngettext ("%s in %d file", "%s in %d files", panel->marked),
+ b_bytes, panel->marked);
+
+ /* don't forget spaces around buffer content */
+ buf = str_trunc (buf, cols - 4);
+
+ if (x < 0)
+ /* center in panel */
+ x = (w->rect.cols - str_term_width1 (buf)) / 2 - 1;
+
+ /*
+ * y == panel_lines (panel) + 2 for mini_info_separator
+ * y == w->lines - 1 for panel bottom frame
+ */
+ widget_gotoyx (w, y, x);
+ tty_setcolor (MARKED_COLOR);
+ tty_printf (" %s ", buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mini_info_separator (const WPanel * panel)
+{
+ if (panels_options.show_mini_info)
+ {
+ const Widget *w = CONST_WIDGET (panel);
+ int y;
+
+ y = panel_lines (panel) + 2;
+
+ tty_setcolor (NORMAL_COLOR);
+ tty_draw_hline (w->rect.y + y, w->rect.x + 1, ACS_HLINE, w->rect.cols - 2);
+ /* Status displays total marked size.
+ * Centered in panel, full format. */
+ display_total_marked_size (panel, y, -1, FALSE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+show_free_space (const WPanel * panel)
+{
+ /* Used to figure out how many free space we have */
+ static struct my_statfs myfs_stats;
+ /* Old current working directory for displaying free space */
+ static char *old_cwd = NULL;
+
+ /* Don't try to stat non-local fs */
+ if (!vfs_file_is_local (panel->cwd_vpath) || !free_space)
+ return;
+
+ if (old_cwd == NULL || strcmp (old_cwd, vfs_path_as_str (panel->cwd_vpath)) != 0)
+ {
+ char rpath[PATH_MAX];
+
+ init_my_statfs ();
+ g_free (old_cwd);
+ old_cwd = g_strdup (vfs_path_as_str (panel->cwd_vpath));
+
+ if (mc_realpath (old_cwd, rpath) == NULL)
+ return;
+
+ my_statfs (&myfs_stats, rpath);
+ }
+
+ if (myfs_stats.avail != 0 || myfs_stats.total != 0)
+ {
+ const Widget *w = CONST_WIDGET (panel);
+ char buffer1[6], buffer2[6], tmp[BUF_SMALL];
+
+ size_trunc_len (buffer1, sizeof (buffer1) - 1, myfs_stats.avail, 1,
+ panels_options.kilobyte_si);
+ size_trunc_len (buffer2, sizeof (buffer2) - 1, myfs_stats.total, 1,
+ panels_options.kilobyte_si);
+ g_snprintf (tmp, sizeof (tmp), " %s / %s (%d%%) ", buffer1, buffer2,
+ myfs_stats.total == 0 ? 0 :
+ (int) (100 * (long double) myfs_stats.avail / myfs_stats.total));
+ widget_gotoyx (w, w->rect.lines - 1, w->rect.cols - 2 - (int) strlen (tmp));
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_string (tmp);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Prepare path string for showing in panel's header.
+ * Passwords will removed, also home dir will replaced by ~
+ *
+ * @param panel WPanel object
+ *
+ * @return newly allocated string.
+ */
+
+static char *
+panel_correct_path_to_show (const WPanel * panel)
+{
+ vfs_path_t *last_vpath;
+ const vfs_path_element_t *path_element;
+ char *return_path;
+ int elements_count;
+
+ elements_count = vfs_path_elements_count (panel->cwd_vpath);
+
+ /* get last path element */
+ path_element = vfs_path_element_clone (vfs_path_get_by_index (panel->cwd_vpath, -1));
+
+ if (elements_count > 1 && (strcmp (path_element->class->name, "cpiofs") == 0 ||
+ strcmp (path_element->class->name, "extfs") == 0 ||
+ strcmp (path_element->class->name, "tarfs") == 0))
+ {
+ const char *archive_name;
+ const vfs_path_element_t *prev_path_element;
+
+ /* get previous path element for catching archive name */
+ prev_path_element = vfs_path_get_by_index (panel->cwd_vpath, -2);
+ archive_name = strrchr (prev_path_element->path, PATH_SEP);
+ if (archive_name != NULL)
+ last_vpath = vfs_path_from_str_flags (archive_name + 1, VPF_NO_CANON);
+ else
+ {
+ last_vpath = vfs_path_from_str_flags (prev_path_element->path, VPF_NO_CANON);
+ last_vpath->relative = TRUE;
+ }
+ }
+ else
+ last_vpath = vfs_path_new (TRUE);
+
+ vfs_path_add_element (last_vpath, path_element);
+ return_path =
+ vfs_path_to_str_flags (last_vpath, 0,
+ VPF_STRIP_HOME | VPF_STRIP_PASSWORD | VPF_HIDE_CHARSET);
+ vfs_path_free (last_vpath, TRUE);
+
+ return return_path;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get Current path element encoding
+ *
+ * @param panel WPanel object
+ *
+ * @return newly allocated string or NULL if path charset is same as system charset
+ */
+
+#ifdef HAVE_CHARSET
+static char *
+panel_get_encoding_info_str (const WPanel * panel)
+{
+ char *ret_str = NULL;
+ const vfs_path_element_t *path_element;
+
+ path_element = vfs_path_get_by_index (panel->cwd_vpath, -1);
+ if (path_element->encoding != NULL)
+ ret_str = g_strdup_printf ("[%s]", path_element->encoding);
+
+ return ret_str;
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+show_dir (const WPanel * panel)
+{
+ const Widget *w = CONST_WIDGET (panel);
+ gchar *tmp;
+
+ set_colors (panel);
+ tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE);
+
+ if (panels_options.show_mini_info)
+ {
+ int y;
+
+ y = panel_lines (panel) + 2;
+
+ widget_gotoyx (w, y, 0);
+ tty_print_alt_char (ACS_LTEE, FALSE);
+ widget_gotoyx (w, y, w->rect.cols - 1);
+ tty_print_alt_char (ACS_RTEE, FALSE);
+ }
+
+ widget_gotoyx (w, 0, 1);
+ tty_print_string (panel_history_prev_item_char);
+
+ tmp = panels_options.show_dot_files ? panel_hiddenfiles_show_char : panel_hiddenfiles_hide_char;
+ tmp = g_strdup_printf ("%s[%s]%s", tmp, panel_history_show_list_char,
+ panel_history_next_item_char);
+
+ widget_gotoyx (w, 0, w->rect.cols - 6);
+ tty_print_string (tmp);
+
+ g_free (tmp);
+
+ widget_gotoyx (w, 0, 3);
+
+ if (panel->is_panelized)
+ tty_printf (" %s ", _("Panelize"));
+#ifdef HAVE_CHARSET
+ else
+ {
+ tmp = panel_get_encoding_info_str (panel);
+ if (tmp != NULL)
+ {
+ tty_printf ("%s", tmp);
+ widget_gotoyx (w, 0, 3 + strlen (tmp));
+ g_free (tmp);
+ }
+ }
+#endif
+
+ if (panel->active)
+ tty_setcolor (REVERSE_COLOR);
+
+ tmp = panel_correct_path_to_show (panel);
+ tty_printf (" %s ", str_term_trim (tmp, MIN (MAX (w->rect.cols - 12, 0), w->rect.cols)));
+ g_free (tmp);
+
+ if (!panels_options.show_mini_info)
+ {
+ if (panel->marked == 0)
+ {
+ const file_entry_t *fe;
+
+ fe = panel_current_entry (panel);
+
+ /* Show size of curret file in the bottom of panel */
+ if (S_ISREG (fe->st.st_mode))
+ {
+ char buffer[BUF_SMALL];
+
+ g_snprintf (buffer, sizeof (buffer), " %s ",
+ size_trunc_sep (fe->st.st_size, panels_options.kilobyte_si));
+ tty_setcolor (NORMAL_COLOR);
+ widget_gotoyx (w, w->rect.lines - 1, 4);
+ tty_print_string (buffer);
+ }
+ }
+ else
+ {
+ /* Show total size of marked files
+ * In the bottom of panel, display size only. */
+ display_total_marked_size (panel, w->rect.lines - 1, 2, TRUE);
+ }
+ }
+
+ show_free_space (panel);
+
+ if (panel->active)
+ tty_set_normal_attrs ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+adjust_top_file (WPanel * panel)
+{
+ int items;
+
+ /* Update panel->current to avoid out of range in panel->dir.list[panel->current]
+ * when panel is redrawing when directory is reloading, for example in path:
+ * dir_list_reload() -> mc_refresh() -> dialog_change_screen_size() ->
+ * midnight_callback (MSG_RESIZE) -> setup_panels() -> panel_callback(MSG_DRAW) ->
+ * display_mini_info()
+ */
+ panel->current = CLAMP (panel->current, 0, panel->dir.len - 1);
+
+ items = panel_items (panel);
+
+ if (panel->dir.len <= items)
+ {
+ /* If all files fit, show them all. */
+ panel->top = 0;
+ }
+ else
+ {
+ int i;
+
+ /* top_file has to be in the range [current-items+1, current] so that
+ the current file is visible.
+ top_file should be in the range [0, count-items] so that there's
+ no empty space wasted.
+ Within these ranges, adjust it by as little as possible. */
+
+ if (panel->top < 0)
+ panel->top = 0;
+
+ i = panel->current - items + 1;
+ if (panel->top < i)
+ panel->top = i;
+
+ i = panel->dir.len - items;
+ if (panel->top > i)
+ panel->top = i;
+
+ if (panel->top > panel->current)
+ panel->top = panel->current;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** add "#enc:encoding" to end of path */
+/* if path ends width a previous #enc:, only encoding is changed no additional
+ * #enc: is appended
+ * return new string
+ */
+
+static char *
+panel_save_name (WPanel * panel)
+{
+ /* If the program is shutting down */
+ if ((mc_global.midnight_shutdown && auto_save_setup) || saving_setup)
+ return g_strdup (panel->name);
+
+ return g_strconcat ("Temporal:", panel->name, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+directory_history_add (WPanel * panel, const vfs_path_t * vpath)
+{
+ char *tmp;
+
+ tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
+ panel->dir_history.list = list_append_unique (panel->dir_history.list, tmp);
+ panel->dir_history.current = panel->dir_history.list;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* "history_load" event handler */
+static gboolean
+panel_load_history (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ WPanel *p = PANEL (init_data);
+ ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
+
+ (void) event_group_name;
+ (void) event_name;
+
+ if (ev->receiver == NULL || ev->receiver == WIDGET (p))
+ {
+ if (ev->cfg != NULL)
+ p->dir_history.list = mc_config_history_load (ev->cfg, p->dir_history.name);
+ else
+ p->dir_history.list = mc_config_history_get (p->dir_history.name);
+
+ directory_history_add (p, p->cwd_vpath);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* "history_save" event handler */
+static gboolean
+panel_save_history (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ WPanel *p = PANEL (init_data);
+
+ (void) event_group_name;
+ (void) event_name;
+
+ if (p->dir_history.list != NULL)
+ {
+ ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
+
+ mc_config_history_save (ev->cfg, p->dir_history.name, p->dir_history.list);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_destroy (WPanel * p)
+{
+ size_t i;
+
+ if (panels_options.auto_save_setup)
+ {
+ char *name;
+
+ name = panel_save_name (p);
+ panel_save_setup (p, name);
+ g_free (name);
+ }
+
+ panel_clean_dir (p);
+
+ /* clean history */
+ if (p->dir_history.list != NULL)
+ {
+ /* directory history is already saved before this moment */
+ p->dir_history.list = g_list_first (p->dir_history.list);
+ g_list_free_full (p->dir_history.list, g_free);
+ }
+ g_free (p->dir_history.name);
+
+ file_filter_clear (&p->filter);
+
+ g_slist_free_full (p->format, (GDestroyNotify) format_item_free);
+ g_slist_free_full (p->status_format, (GDestroyNotify) format_item_free);
+
+ g_free (p->user_format);
+ for (i = 0; i < LIST_FORMATS; i++)
+ g_free (p->user_status_format[i]);
+
+ g_free (p->name);
+
+ panelized_descr_free (p->panelized_descr);
+
+ g_string_free (p->quick_search.buffer, TRUE);
+ g_string_free (p->quick_search.prev_buffer, TRUE);
+
+ vfs_path_free (p->lwd_vpath, TRUE);
+ vfs_path_free (p->cwd_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_paint_sort_info (const WPanel * panel)
+{
+ if (*panel->sort_field->hotkey != '\0')
+ {
+ const char *sort_sign =
+ panel->sort_info.reverse ? panel_sort_up_char : panel_sort_down_char;
+ char *str;
+
+ str = g_strdup_printf ("%s%s", sort_sign, Q_ (panel->sort_field->hotkey));
+ widget_gotoyx (panel, 1, 1);
+ tty_print_string (str);
+ g_free (str);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+panel_get_title_without_hotkey (const char *title)
+{
+ static char translated_title[BUF_TINY];
+
+ if (title == NULL || title[0] == '\0')
+ translated_title[0] = '\0';
+ else
+ {
+ char *hkey;
+
+ g_snprintf (translated_title, sizeof (translated_title), "%s", _(title));
+
+ hkey = strchr (translated_title, '&');
+ if (hkey != NULL && hkey[1] != '\0')
+ memmove (hkey, hkey + 1, strlen (hkey));
+ }
+
+ return translated_title;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_print_header (const WPanel * panel)
+{
+ const Widget *w = CONST_WIDGET (panel);
+
+ int y, x;
+ int i;
+ GString *format_txt;
+
+ widget_gotoyx (w, 1, 1);
+ tty_getyx (&y, &x);
+ tty_setcolor (NORMAL_COLOR);
+ tty_draw_hline (y, x, ' ', w->rect.cols - 2);
+
+ format_txt = g_string_new ("");
+
+ for (i = 0; i < panel->list_cols; i++)
+ {
+ GSList *format;
+
+ for (format = panel->format; format != NULL; format = g_slist_next (format))
+ {
+ format_item_t *fi = (format_item_t *) format->data;
+
+ if (fi->string_fn != NULL)
+ {
+ g_string_set_size (format_txt, 0);
+
+ if (panel->list_format == list_long && strcmp (fi->id, panel->sort_field->id) == 0)
+ g_string_append (format_txt,
+ panel->sort_info.reverse
+ ? panel_sort_up_char : panel_sort_down_char);
+
+ g_string_append (format_txt, fi->title);
+
+ if (panel->filter.handler != NULL && strcmp (fi->id, "name") == 0)
+ {
+ g_string_append (format_txt, " [");
+ g_string_append (format_txt, panel->filter.value);
+ g_string_append (format_txt, "]");
+ }
+
+ tty_setcolor (HEADER_COLOR);
+ tty_print_string (str_fit_to_term (format_txt->str, fi->field_len, J_CENTER_LEFT));
+ }
+ else
+ {
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_one_vline (TRUE);
+ }
+ }
+
+ if (i < panel->list_cols - 1)
+ {
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_one_vline (TRUE);
+ }
+ }
+
+ g_string_free (format_txt, TRUE);
+
+ if (panel->list_format != list_long)
+ panel_paint_sort_info (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+parse_panel_size (WPanel * panel, const char *format, gboolean isstatus)
+{
+ panel_display_t frame = frame_half;
+
+ format = skip_separators (format);
+
+ if (strncmp (format, "full", 4) == 0)
+ {
+ frame = frame_full;
+ format += 4;
+ }
+ else if (strncmp (format, "half", 4) == 0)
+ {
+ frame = frame_half;
+ format += 4;
+ }
+
+ if (!isstatus)
+ {
+ panel->frame_size = frame;
+ panel->list_cols = 1;
+ }
+
+ /* Now, the optional column specifier */
+ format = skip_separators (format);
+
+ if (g_ascii_isdigit (*format))
+ {
+ if (!isstatus)
+ {
+ panel->list_cols = g_ascii_digit_value (*format);
+ if (panel->list_cols < 1)
+ panel->list_cols = 1;
+ }
+
+ format++;
+ }
+
+ if (!isstatus)
+ panel_update_cols (WIDGET (panel), panel->frame_size);
+
+ return skip_separators (format);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* *INDENT-OFF* */
+/* Format is:
+
+ all := panel_format? format
+ panel_format := [full|half] [1-9]
+ format := one_format_item_t
+ | format , one_format_item_t
+
+ one_format_item_t := just format.id [opt_size]
+ just := [<=>]
+ opt_size := : size [opt_expand]
+ size := [0-9]+
+ opt_expand := +
+
+ */
+/* *INDENT-ON* */
+
+static GSList *
+parse_display_format (WPanel * panel, const char *format, char **error, gboolean isstatus,
+ int *res_total_cols)
+{
+ GSList *home = NULL; /* The formats we return */
+ int total_cols = 0; /* Used columns by the format */
+ size_t i;
+
+ static size_t i18n_timelength = 0; /* flag: check ?Time length at startup */
+
+ *error = NULL;
+
+ if (i18n_timelength == 0)
+ {
+ i18n_timelength = i18n_checktimelength (); /* Mustn't be 0 */
+
+ for (i = 0; panel_fields[i].id != NULL; i++)
+ if (strcmp ("time", panel_fields[i].id + 1) == 0)
+ panel_fields[i].min_size = i18n_timelength;
+ }
+
+ /*
+ * This makes sure that the panel and mini status full/half mode
+ * setting is equal
+ */
+ format = parse_panel_size (panel, format, isstatus);
+
+ while (*format != '\0')
+ { /* format can be an empty string */
+ format_item_t *darr;
+ align_crt_t justify; /* Which mode. */
+ gboolean set_justify = TRUE; /* flag: set justification mode? */
+ gboolean found = FALSE;
+ size_t klen = 0;
+
+ darr = g_new0 (format_item_t, 1);
+ home = g_slist_append (home, darr);
+
+ format = skip_separators (format);
+
+ switch (*format)
+ {
+ case '<':
+ justify = J_LEFT;
+ format = skip_separators (format + 1);
+ break;
+ case '=':
+ justify = J_CENTER;
+ format = skip_separators (format + 1);
+ break;
+ case '>':
+ justify = J_RIGHT;
+ format = skip_separators (format + 1);
+ break;
+ default:
+ justify = J_LEFT;
+ set_justify = FALSE;
+ break;
+ }
+
+ for (i = 0; !found && panel_fields[i].id != NULL; i++)
+ {
+ klen = strlen (panel_fields[i].id);
+ found = strncmp (format, panel_fields[i].id, klen) == 0;
+ }
+
+ if (found)
+ {
+ i--;
+ format += klen;
+
+ darr->requested_field_len = panel_fields[i].min_size;
+ darr->string_fn = panel_fields[i].string_fn;
+ darr->title = g_strdup (panel_get_title_without_hotkey (panel_fields[i].title_hotkey));
+ darr->id = panel_fields[i].id;
+ darr->expand = panel_fields[i].expands;
+ darr->just_mode = panel_fields[i].default_just;
+
+ if (set_justify)
+ {
+ if (IS_FIT (darr->just_mode))
+ darr->just_mode = MAKE_FIT (justify);
+ else
+ darr->just_mode = justify;
+ }
+
+ format = skip_separators (format);
+
+ /* If we have a size specifier */
+ if (*format == ':')
+ {
+ int req_length;
+
+ /* If the size was specified, we don't want
+ * auto-expansion by default
+ */
+ darr->expand = FALSE;
+ format++;
+ req_length = atoi (format);
+ darr->requested_field_len = req_length;
+
+ format = skip_numbers (format);
+
+ /* Now, if they insist on expansion */
+ if (*format == '+')
+ {
+ darr->expand = TRUE;
+ format++;
+ }
+ }
+ }
+ else
+ {
+ size_t pos;
+ char *tmp_format;
+
+ pos = strlen (format);
+ if (pos > 8)
+ pos = 8;
+
+ tmp_format = g_strndup (format, pos);
+ g_slist_free_full (home, (GDestroyNotify) format_item_free);
+ *error =
+ g_strconcat (_("Unknown tag on display format:"), " ", tmp_format, (char *) NULL);
+ g_free (tmp_format);
+
+ return NULL;
+ }
+
+ total_cols += darr->requested_field_len;
+ }
+
+ *res_total_cols = total_cols;
+ return home;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GSList *
+use_display_format (WPanel * panel, const char *format, char **error, gboolean isstatus)
+{
+#define MAX_EXPAND 4
+ int expand_top = 0; /* Max used element in expand */
+ int usable_columns; /* Usable columns in the panel */
+ int total_cols = 0;
+ GSList *darr, *home;
+
+ if (format == NULL)
+ format = DEFAULT_USER_FORMAT;
+
+ home = parse_display_format (panel, format, error, isstatus, &total_cols);
+
+ if (*error != NULL)
+ return NULL;
+
+ panel->dirty = TRUE;
+
+ usable_columns = WIDGET (panel)->rect.cols - 2;
+ /* Status needn't to be split */
+ if (!isstatus)
+ {
+ usable_columns /= panel->list_cols;
+ if (panel->list_cols > 1)
+ usable_columns--;
+ }
+
+ /* Look for the expandable fields and set field_len based on the requested field len */
+ for (darr = home; darr != NULL && expand_top < MAX_EXPAND; darr = g_slist_next (darr))
+ {
+ format_item_t *fi = (format_item_t *) darr->data;
+
+ fi->field_len = fi->requested_field_len;
+ if (fi->expand)
+ expand_top++;
+ }
+
+ /* If we used more columns than the available columns, adjust that */
+ if (total_cols > usable_columns)
+ {
+ int dif;
+ int pdif = 0;
+
+ dif = total_cols - usable_columns;
+
+ while (dif != 0 && pdif != dif)
+ {
+ pdif = dif;
+
+ for (darr = home; darr != NULL; darr = g_slist_next (darr))
+ {
+ format_item_t *fi = (format_item_t *) darr->data;
+
+ if (dif != 0 && fi->field_len != 1)
+ {
+ fi->field_len--;
+ dif--;
+ }
+ }
+ }
+
+ total_cols = usable_columns; /* give up, the rest should be truncated */
+ }
+
+ /* Expand the available space */
+ if (usable_columns > total_cols && expand_top != 0)
+ {
+ int i;
+ int spaces;
+
+ spaces = (usable_columns - total_cols) / expand_top;
+
+ for (i = 0, darr = home; darr != NULL && i < expand_top; darr = g_slist_next (darr))
+ {
+ format_item_t *fi = (format_item_t *) darr->data;
+
+ if (fi->expand)
+ {
+ fi->field_len += spaces;
+ if (i == 0)
+ fi->field_len += (usable_columns - total_cols) % expand_top;
+ i++;
+ }
+ }
+ }
+
+ return home;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Given the panel->view_type returns the format string to be parsed */
+
+static const char *
+panel_format (WPanel * panel)
+{
+ switch (panel->list_format)
+ {
+ case list_long:
+ return "full perm space nlink space owner space group space size space mtime space name";
+
+ case list_brief:
+ {
+ static char format[BUF_TINY];
+ int brief_cols = panel->brief_cols;
+
+ if (brief_cols < 1)
+ brief_cols = 2;
+
+ if (brief_cols > 9)
+ brief_cols = 9;
+
+ g_snprintf (format, sizeof (format), "half %d type name", brief_cols);
+ return format;
+ }
+
+ case list_user:
+ return panel->user_format;
+
+ default:
+ case list_full:
+ return "half type name | size | mtime";
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+mini_status_format (WPanel * panel)
+{
+ if (panel->user_mini_status)
+ return panel->user_status_format[panel->list_format];
+
+ switch (panel->list_format)
+ {
+ case list_long:
+ return "full perm space nlink space owner space group space size space mtime space name";
+
+ case list_brief:
+ return "half type name space bsize space perm space";
+
+ case list_full:
+ return "half type name";
+
+ default:
+ case list_user:
+ return panel->user_format;
+ }
+}
+
+/* */
+/* Panel operation commands */
+/* */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+cd_up_dir (WPanel * panel)
+{
+ vfs_path_t *up_dir;
+
+ up_dir = vfs_path_from_str ("..");
+ panel_cd (panel, up_dir, cd_exact);
+ vfs_path_free (up_dir, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Used to emulate Lynx's entering leaving a directory with the arrow keys */
+
+static cb_ret_t
+maybe_cd (WPanel * panel, gboolean move_up_dir)
+{
+ if (panels_options.navigate_with_arrows && input_is_empty (cmdline))
+ {
+ const file_entry_t *fe;
+
+ if (move_up_dir)
+ {
+ cd_up_dir (panel);
+ return MSG_HANDLED;
+ }
+
+ fe = panel_current_entry (panel);
+
+ if (S_ISDIR (fe->st.st_mode) || link_isdir (fe))
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (fe->fname->str);
+ panel_cd (panel, vpath, cd_exact);
+ vfs_path_free (vpath, TRUE);
+ return MSG_HANDLED;
+ }
+ }
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* if command line is empty then do 'cd ..' */
+static cb_ret_t
+force_maybe_cd (WPanel * panel)
+{
+ if (input_is_empty (cmdline))
+ {
+ cd_up_dir (panel);
+ return MSG_HANDLED;
+ }
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+unselect_item (WPanel * panel)
+{
+ repaint_file (panel, panel->current,
+ panel_current_entry (panel)->f.marked != 0 ? FATTR_MARKED : FATTR_NORMAL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Select/unselect all the files like a current file by extension */
+
+static void
+panel_select_ext_cmd (WPanel * panel)
+{
+ const file_entry_t *fe;
+ GString *filename;
+ gboolean do_select;
+ char *reg_exp, *cur_file_ext;
+ mc_search_t *search;
+ int i;
+
+ fe = panel_current_entry (panel);
+
+ filename = fe->fname;
+ if (filename == NULL)
+ return;
+
+ do_select = (fe->f.marked == 0);
+
+ cur_file_ext = strutils_regex_escape (extension (filename->str));
+ if (cur_file_ext[0] != '\0')
+ reg_exp = g_strconcat ("^.*\\.", cur_file_ext, "$", (char *) NULL);
+ else
+ reg_exp = g_strdup ("^[^\\.]+$");
+
+ g_free (cur_file_ext);
+
+ search = mc_search_new (reg_exp, NULL);
+ search->search_type = MC_SEARCH_T_REGEX;
+ search->is_case_sensitive = FALSE;
+
+ for (i = 0; i < panel->dir.len; i++)
+ {
+ fe = &panel->dir.list[i];
+
+ if (DIR_IS_DOTDOT (fe->fname->str) || S_ISDIR (fe->st.st_mode))
+ continue;
+
+ if (!mc_search_run (search, fe->fname->str, 0, fe->fname->len, NULL))
+ continue;
+
+ do_file_mark (panel, i, do_select ? 1 : 0);
+ }
+
+ mc_search_free (search);
+ g_free (reg_exp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+panel_current_at_half (const WPanel * panel)
+{
+ int lines, top;
+
+ lines = panel_lines (panel);
+
+ /* define top file of column */
+ top = panel->top;
+ if (panel->list_cols > 1)
+ top += lines * ((panel->current - top) / lines);
+
+ return (panel->current - top - lines / 2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+move_down (WPanel * panel)
+{
+ int items;
+
+ if (panel->current + 1 == panel->dir.len)
+ return;
+
+ unselect_item (panel);
+ panel->current++;
+
+ items = panel_items (panel);
+
+ if (panels_options.scroll_pages && panel->current - panel->top == items)
+ {
+ /* Scroll window half screen */
+ panel->top += items / 2;
+ if (panel->top > panel->dir.len - items)
+ panel->top = panel->dir.len - items;
+ paint_dir (panel);
+ }
+ else if (panels_options.scroll_center && panel_current_at_half (panel) > 0)
+ {
+ /* Scroll window when cursor is halfway down */
+ panel->top++;
+ if (panel->top > panel->dir.len - items)
+ panel->top = panel->dir.len - items;
+ }
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+move_up (WPanel * panel)
+{
+ if (panel->current == 0)
+ return;
+
+ unselect_item (panel);
+ panel->current--;
+
+ if (panels_options.scroll_pages && panel->current < panel->top)
+ {
+ /* Scroll window half screen */
+ panel->top -= panel_items (panel) / 2;
+ if (panel->top < 0)
+ panel->top = 0;
+ paint_dir (panel);
+ }
+ else if (panels_options.scroll_center && panel_current_at_half (panel) < 0)
+ {
+ /* Scroll window when cursor is halfway up */
+ panel->top--;
+ if (panel->top < 0)
+ panel->top = 0;
+ }
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Changes the current by lines (may be negative) */
+
+static void
+panel_move_current (WPanel * panel, int lines)
+{
+ int new_pos;
+ gboolean adjust = FALSE;
+
+ new_pos = panel->current + lines;
+ if (new_pos >= panel->dir.len)
+ new_pos = panel->dir.len - 1;
+
+ if (new_pos < 0)
+ new_pos = 0;
+
+ unselect_item (panel);
+ panel->current = new_pos;
+
+ if (panel->current - panel->top >= panel_items (panel))
+ {
+ panel->top += lines;
+ adjust = TRUE;
+ }
+
+ if (panel->current - panel->top < 0)
+ {
+ panel->top += lines;
+ adjust = TRUE;
+ }
+
+ if (adjust)
+ {
+ if (panel->top > panel->current)
+ panel->top = panel->current;
+ if (panel->top < 0)
+ panel->top = 0;
+ paint_dir (panel);
+ }
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+move_left (WPanel * panel)
+{
+ if (panel->list_cols > 1)
+ {
+ panel_move_current (panel, -panel_lines (panel));
+ return MSG_HANDLED;
+ }
+
+ return maybe_cd (panel, TRUE); /* cd .. */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+move_right (WPanel * panel)
+{
+ if (panel->list_cols > 1)
+ {
+ panel_move_current (panel, panel_lines (panel));
+ return MSG_HANDLED;
+ }
+
+ return maybe_cd (panel, FALSE); /* cd (current) */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+prev_page (WPanel * panel)
+{
+ int items;
+
+ if (panel->current == 0 && panel->top == 0)
+ return;
+
+ unselect_item (panel);
+ items = panel_items (panel);
+ if (panel->top < items)
+ items = panel->top;
+ if (items == 0)
+ panel->current = 0;
+ else
+ panel->current -= items;
+ panel->top -= items;
+
+ select_item (panel);
+ paint_dir (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+goto_parent_dir (WPanel * panel)
+{
+ if (!panel->is_panelized)
+ cd_up_dir (panel);
+ else
+ {
+ GString *fname;
+ const char *bname;
+ vfs_path_t *dname_vpath;
+
+ fname = panel_current_entry (panel)->fname;
+
+ if (g_path_is_absolute (fname->str))
+ fname = mc_g_string_dup (fname);
+ else
+ {
+ char *fname2;
+
+ fname2 =
+ mc_build_filename (vfs_path_as_str (panel->panelized_descr->root_vpath), fname->str,
+ (char *) NULL);
+
+ fname = g_string_new (fname2);
+ g_free (fname2);
+ }
+
+ bname = x_basename (fname->str);
+
+ if (bname == fname->str)
+ dname_vpath = vfs_path_from_str (".");
+ else
+ {
+ g_string_truncate (fname, bname - fname->str);
+ dname_vpath = vfs_path_from_str (fname->str);
+ }
+
+ panel_cd (panel, dname_vpath, cd_exact);
+ panel_set_current_by_name (panel, bname);
+
+ vfs_path_free (dname_vpath, TRUE);
+ g_string_free (fname, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+next_page (WPanel * panel)
+{
+ int items;
+
+ if (panel->current == panel->dir.len - 1)
+ return;
+
+ unselect_item (panel);
+ items = panel_items (panel);
+ if (panel->top > panel->dir.len - 2 * items)
+ items = panel->dir.len - items - panel->top;
+ if (panel->top + items < 0)
+ items = -panel->top;
+ if (items == 0)
+ panel->current = panel->dir.len - 1;
+ else
+ panel->current += items;
+ panel->top += items;
+
+ select_item (panel);
+ paint_dir (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+goto_child_dir (WPanel * panel)
+{
+ const file_entry_t *fe;
+
+ fe = panel_current_entry (panel);
+
+ if (S_ISDIR (fe->st.st_mode) || link_isdir (fe))
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (fe->fname->str);
+ panel_cd (panel, vpath, cd_exact);
+ vfs_path_free (vpath, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+goto_top_file (WPanel * panel)
+{
+ unselect_item (panel);
+ panel->current = panel->top;
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+goto_middle_file (WPanel * panel)
+{
+ unselect_item (panel);
+ panel->current = panel->top + panel_items (panel) / 2;
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+goto_bottom_file (WPanel * panel)
+{
+ unselect_item (panel);
+ panel->current = panel->top + panel_items (panel) - 1;
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+move_home (WPanel * panel)
+{
+ if (panel->current == 0)
+ return;
+
+ unselect_item (panel);
+
+ if (panels_options.torben_fj_mode)
+ {
+ int middle_pos;
+
+ middle_pos = panel->top + panel_items (panel) / 2;
+
+ if (panel->current > middle_pos)
+ {
+ goto_middle_file (panel);
+ return;
+ }
+ if (panel->current != panel->top)
+ {
+ goto_top_file (panel);
+ return;
+ }
+ }
+
+ panel->top = 0;
+ panel->current = 0;
+
+ paint_dir (panel);
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+move_end (WPanel * panel)
+{
+ if (panel->current == panel->dir.len - 1)
+ return;
+
+ unselect_item (panel);
+
+ if (panels_options.torben_fj_mode)
+ {
+ int items, middle_pos;
+
+ items = panel_items (panel);
+ middle_pos = panel->top + items / 2;
+
+ if (panel->current < middle_pos)
+ {
+ goto_middle_file (panel);
+ return;
+ }
+ if (panel->current != panel->top + items - 1)
+ {
+ goto_bottom_file (panel);
+ return;
+ }
+ }
+
+ panel->current = panel->dir.len - 1;
+ paint_dir (panel);
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+do_mark_file (WPanel * panel, mark_act_t do_move)
+{
+ do_file_mark (panel, panel->current, panel_current_entry (panel)->f.marked ? 0 : 1);
+
+ if ((panels_options.mark_moves_down && do_move == MARK_DOWN) || do_move == MARK_FORCE_DOWN)
+ move_down (panel);
+ else if (do_move == MARK_FORCE_UP)
+ move_up (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+mark_file (WPanel * panel)
+{
+ do_mark_file (panel, MARK_DOWN);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+mark_file_up (WPanel * panel)
+{
+ do_mark_file (panel, MARK_FORCE_UP);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+mark_file_down (WPanel * panel)
+{
+ do_mark_file (panel, MARK_FORCE_DOWN);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mark_file_right (WPanel * panel)
+{
+ int lines;
+
+ if (state_mark < 0)
+ state_mark = panel_current_entry (panel)->f.marked ? 0 : 1;
+
+ lines = panel_lines (panel);
+ lines = MIN (lines, panel->dir.len - panel->current - 1);
+ for (; lines != 0; lines--)
+ {
+ do_file_mark (panel, panel->current, state_mark);
+ move_down (panel);
+ }
+ do_file_mark (panel, panel->current, state_mark);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mark_file_left (WPanel * panel)
+{
+ int lines;
+
+ if (state_mark < 0)
+ state_mark = panel_current_entry (panel)->f.marked ? 0 : 1;
+
+ lines = panel_lines (panel);
+ lines = MIN (lines, panel->current + 1);
+ for (; lines != 0; lines--)
+ {
+ do_file_mark (panel, panel->current, state_mark);
+ move_up (panel);
+ }
+ do_file_mark (panel, panel->current, state_mark);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mc_search_t *
+panel_select_unselect_files_dialog (select_flags_t * flags, const char *title,
+ const char *history_name, const char *help_section, char **str)
+{
+ gboolean files_only = (*flags & SELECT_FILES_ONLY) != 0;
+ gboolean case_sens = (*flags & SELECT_MATCH_CASE) != 0;
+ gboolean shell_patterns = (*flags & SELECT_SHELL_PATTERNS) != 0;
+
+ char *reg_exp;
+ mc_search_t *search;
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_INPUT (INPUT_LAST_TEXT, history_name, &reg_exp, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
+ QUICK_START_COLUMNS,
+ QUICK_CHECKBOX (N_("&Files only"), &files_only, NULL),
+ QUICK_CHECKBOX (N_("&Using shell patterns"), &shell_patterns, NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("&Case sensitive"), &case_sens, NULL),
+ QUICK_STOP_COLUMNS,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 50 };
+
+ quick_dialog_t qdlg = {
+ r, title, help_section,
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) == B_CANCEL)
+ return NULL;
+
+ if (*reg_exp == '\0')
+ {
+ g_free (reg_exp);
+ if (str != NULL)
+ *str = NULL;
+ return SELECT_RESET;
+ }
+
+ search = mc_search_new (reg_exp, NULL);
+ search->search_type = shell_patterns ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
+ search->is_entire_line = TRUE;
+ search->is_case_sensitive = case_sens;
+
+ if (str != NULL)
+ *str = reg_exp;
+ else
+ g_free (reg_exp);
+
+ if (!mc_search_prepare (search))
+ {
+ message (D_ERROR, MSG_ERROR, _("Malformed regular expression"));
+ mc_search_free (search);
+ return SELECT_ERROR;
+ }
+
+ /* result flags */
+ *flags = 0;
+ if (case_sens)
+ *flags |= SELECT_MATCH_CASE;
+ if (files_only)
+ *flags |= SELECT_FILES_ONLY;
+ if (shell_patterns)
+ *flags |= SELECT_SHELL_PATTERNS;
+
+ return search;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_select_unselect_files (WPanel * panel, const char *title, const char *history_name,
+ const char *help_section, gboolean do_select)
+{
+ mc_search_t *search;
+ gboolean files_only;
+ int i;
+
+ search = panel_select_unselect_files_dialog (&panels_options.select_flags, title, history_name,
+ help_section, NULL);
+ if (search == NULL || search == SELECT_RESET || search == SELECT_ERROR)
+ return;
+
+ files_only = (panels_options.select_flags & SELECT_FILES_ONLY) != 0;
+
+ for (i = 0; i < panel->dir.len; i++)
+ {
+ if (DIR_IS_DOTDOT (panel->dir.list[i].fname->str))
+ continue;
+ if (S_ISDIR (panel->dir.list[i].st.st_mode) && files_only)
+ continue;
+
+ if (mc_search_run
+ (search, panel->dir.list[i].fname->str, 0, panel->dir.list[i].fname->len, NULL))
+ do_file_mark (panel, i, do_select ? 1 : 0);
+ }
+
+ mc_search_free (search);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_select_files (WPanel * panel)
+{
+ panel_select_unselect_files (panel, _("Select"), MC_HISTORY_FM_PANEL_SELECT,
+ "[Select/Unselect Files]", TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_unselect_files (WPanel * panel)
+{
+ panel_select_unselect_files (panel, _("Unselect"), MC_HISTORY_FM_PANEL_UNSELECT,
+ "[Select/Unselect Files]", FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_select_invert_files (WPanel * panel)
+{
+ int i;
+
+ for (i = 0; i < panel->dir.len; i++)
+ {
+ file_entry_t *file = &panel->dir.list[i];
+
+ if (!panels_options.reverse_files_only || !S_ISDIR (file->st.st_mode))
+ do_file_mark (panel, i, file->f.marked ? 0 : 1);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_do_set_filter (WPanel * panel)
+{
+ /* *INDENT-OFF* */
+ file_filter_t ff = { .value = NULL, .handler = NULL, .flags = panel->filter.flags };
+ /* *INDENT-ON* */
+
+ ff.handler =
+ panel_select_unselect_files_dialog (&ff.flags, _("Filter"), MC_HISTORY_FM_PANEL_FILTER,
+ "[Filter...]", &ff.value);
+
+ if (ff.handler == NULL || ff.handler == SELECT_ERROR)
+ return;
+
+ if (ff.handler == SELECT_RESET)
+ ff.handler = NULL;
+
+ panel_set_filter (panel, &ff);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Incremental search of a file name in the panel.
+ * @param panel instance of WPanel structure
+ * @param c_code key code
+ */
+
+static void
+do_search (WPanel * panel, int c_code)
+{
+ int curr;
+ int i;
+ gboolean wrapped = FALSE;
+ char *act;
+ mc_search_t *search;
+ char *reg_exp, *esc_str;
+ gboolean is_found = FALSE;
+
+ if (c_code == KEY_BACKSPACE)
+ {
+ if (panel->quick_search.buffer->len != 0)
+ {
+ act = panel->quick_search.buffer->str + panel->quick_search.buffer->len;
+ str_prev_noncomb_char (&act, panel->quick_search.buffer->str);
+ g_string_set_size (panel->quick_search.buffer, act - panel->quick_search.buffer->str);
+ }
+ panel->quick_search.chpoint = 0;
+ }
+ else
+ {
+ if (c_code != 0 && (gsize) panel->quick_search.chpoint < sizeof (panel->quick_search.ch))
+ {
+ panel->quick_search.ch[panel->quick_search.chpoint] = c_code;
+ panel->quick_search.chpoint++;
+ }
+
+ if (panel->quick_search.chpoint > 0)
+ {
+ switch (str_is_valid_char (panel->quick_search.ch, panel->quick_search.chpoint))
+ {
+ case -2:
+ return;
+ case -1:
+ panel->quick_search.chpoint = 0;
+ return;
+ default:
+ g_string_append_len (panel->quick_search.buffer, panel->quick_search.ch,
+ panel->quick_search.chpoint);
+ panel->quick_search.chpoint = 0;
+ }
+ }
+ }
+
+ reg_exp = g_strdup_printf ("%s*", panel->quick_search.buffer->str);
+ esc_str = strutils_escape (reg_exp, -1, ",|\\{}[]", TRUE);
+ search = mc_search_new (esc_str, NULL);
+ search->search_type = MC_SEARCH_T_GLOB;
+ search->is_entire_line = TRUE;
+
+ switch (panels_options.qsearch_mode)
+ {
+ case QSEARCH_CASE_SENSITIVE:
+ search->is_case_sensitive = TRUE;
+ break;
+ case QSEARCH_CASE_INSENSITIVE:
+ search->is_case_sensitive = FALSE;
+ break;
+ default:
+ search->is_case_sensitive = panel->sort_info.case_sensitive;
+ break;
+ }
+
+ curr = panel->current;
+
+ for (i = panel->current; !wrapped || i != panel->current; i++)
+ {
+ if (i >= panel->dir.len)
+ {
+ i = 0;
+ if (wrapped)
+ break;
+ wrapped = TRUE;
+ }
+ if (mc_search_run
+ (search, panel->dir.list[i].fname->str, 0, panel->dir.list[i].fname->len, NULL))
+ {
+ curr = i;
+ is_found = TRUE;
+ break;
+ }
+ }
+ if (is_found)
+ {
+ unselect_item (panel);
+ panel->current = curr;
+ select_item (panel);
+ widget_draw (WIDGET (panel));
+ }
+ else if (c_code != KEY_BACKSPACE)
+ {
+ act = panel->quick_search.buffer->str + panel->quick_search.buffer->len;
+ str_prev_noncomb_char (&act, panel->quick_search.buffer->str);
+ g_string_set_size (panel->quick_search.buffer, act - panel->quick_search.buffer->str);
+ }
+ mc_search_free (search);
+ g_free (reg_exp);
+ g_free (esc_str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Start new search.
+ * @param panel instance of WPanel structure
+ */
+
+static void
+start_search (WPanel * panel)
+{
+ if (panel->quick_search.active)
+ {
+ if (panel->current == panel->dir.len - 1)
+ panel->current = 0;
+ else
+ move_down (panel);
+
+ /* in case if there was no search string we need to recall
+ previous string, with which we ended previous searching */
+ if (panel->quick_search.buffer->len == 0)
+ mc_g_string_copy (panel->quick_search.buffer, panel->quick_search.prev_buffer);
+
+ do_search (panel, 0);
+ }
+ else
+ {
+ panel->quick_search.active = TRUE;
+ g_string_set_size (panel->quick_search.buffer, 0);
+ panel->quick_search.ch[0] = '\0';
+ panel->quick_search.chpoint = 0;
+ display_mini_info (panel);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+stop_search (WPanel * panel)
+{
+ if (!panel->quick_search.active)
+ return;
+
+ panel->quick_search.active = FALSE;
+
+ /* if user overrdied search string, we need to store it
+ to the quick_search.prev_buffer */
+ if (panel->quick_search.buffer->len != 0)
+ mc_g_string_copy (panel->quick_search.prev_buffer, panel->quick_search.buffer);
+
+ display_mini_info (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Return TRUE if the Enter key has been processed, FALSE otherwise */
+
+static gboolean
+do_enter_on_file_entry (WPanel * panel, file_entry_t * fe)
+{
+ const char *fname = fe->fname->str;
+ vfs_path_t *full_name_vpath;
+ gboolean ok;
+
+ /*
+ * Directory or link to directory - change directory.
+ * Try the same for the entries on which mc_lstat() has failed.
+ */
+ if (S_ISDIR (fe->st.st_mode) || link_isdir (fe) || (fe->st.st_mode == 0))
+ {
+ vfs_path_t *fname_vpath;
+
+ fname_vpath = vfs_path_from_str (fname);
+ if (!panel_cd (panel, fname_vpath, cd_exact))
+ cd_error_message (fname);
+ vfs_path_free (fname_vpath, TRUE);
+ return TRUE;
+ }
+
+ full_name_vpath = vfs_path_append_new (panel->cwd_vpath, fname, (char *) NULL);
+
+ /* Try associated command */
+ ok = regex_command (full_name_vpath, "Open") != 0;
+ vfs_path_free (full_name_vpath, TRUE);
+ if (ok)
+ return TRUE;
+
+ /* Check if the file is executable */
+ full_name_vpath = vfs_path_append_new (panel->cwd_vpath, fname, (char *) NULL);
+ ok = (is_exe (fe->st.st_mode) && if_link_is_exe (full_name_vpath, fe));
+ vfs_path_free (full_name_vpath, TRUE);
+ if (!ok)
+ return FALSE;
+
+ if (confirm_execute
+ && query_dialog (_("The Midnight Commander"), _("Do you really want to execute?"), D_NORMAL,
+ 2, _("&Yes"), _("&No")) != 0)
+ return TRUE;
+
+ if (!vfs_current_is_local ())
+ {
+ int ret;
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_append_new (vfs_get_raw_current_dir (), fname, (char *) NULL);
+ ret = mc_setctl (tmp_vpath, VFS_SETCTL_RUN, NULL);
+ vfs_path_free (tmp_vpath, TRUE);
+ /* We took action only if the dialog was shown or the execution was successful */
+ return confirm_execute || (ret == 0);
+ }
+
+ {
+ char *tmp, *cmd;
+
+ tmp = name_quote (fname, FALSE);
+ cmd = g_strconcat (".", PATH_SEP_STR, tmp, (char *) NULL);
+ g_free (tmp);
+ shell_execute (cmd, 0);
+ g_free (cmd);
+ }
+
+#ifdef HAVE_CHARSET
+ mc_global.source_codepage = default_source_codepage;
+#endif
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+do_enter (WPanel * panel)
+{
+ return do_enter_on_file_entry (panel, panel_current_entry (panel));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_cycle_listing_format (WPanel * panel)
+{
+ panel->list_format = (panel->list_format + 1) % LIST_FORMATS;
+
+ if (set_panel_formats (panel) == 0)
+ do_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chdir_other_panel (WPanel * panel)
+{
+ const file_entry_t *entry;
+ vfs_path_t *new_dir_vpath;
+ char *curr_entry = NULL;
+ WPanel *p;
+
+ entry = panel_current_entry (panel);
+
+ if (get_other_type () != view_listing)
+ create_panel (get_other_index (), view_listing);
+
+ if (S_ISDIR (entry->st.st_mode) || link_isdir (entry))
+ new_dir_vpath = vfs_path_append_new (panel->cwd_vpath, entry->fname->str, (char *) NULL);
+ else
+ {
+ new_dir_vpath = vfs_path_append_new (panel->cwd_vpath, "..", (char *) NULL);
+ curr_entry = strrchr (vfs_path_get_last_path_str (panel->cwd_vpath), PATH_SEP);
+ }
+
+ p = change_panel ();
+ panel_cd (p, new_dir_vpath, cd_exact);
+ vfs_path_free (new_dir_vpath, TRUE);
+
+ if (curr_entry != NULL)
+ panel_set_current_by_name (p, curr_entry);
+ (void) change_panel ();
+
+ move_down (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Make the current directory of the current panel also the current
+ * directory of the other panel. Put the other panel to the listing
+ * mode if needed. If the current panel is panelized, the other panel
+ * doesn't become panelized.
+ */
+
+static void
+panel_sync_other (const WPanel * panel)
+{
+ if (get_other_type () != view_listing)
+ create_panel (get_other_index (), view_listing);
+
+ panel_do_cd (other_panel, panel->cwd_vpath, cd_exact);
+
+ /* try to set current filename on the other panel */
+ if (!panel->is_panelized)
+ panel_set_current_by_name (other_panel, panel_current_entry (panel)->fname->str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chdir_to_readlink (WPanel * panel)
+{
+ const file_entry_t *fe;
+ vfs_path_t *new_dir_vpath;
+ char buffer[MC_MAXPATHLEN];
+ int i;
+ struct stat st;
+ vfs_path_t *panel_fname_vpath;
+ gboolean ok;
+ WPanel *cpanel;
+
+ if (get_other_type () != view_listing)
+ return;
+
+ fe = panel_current_entry (panel);
+
+ if (!S_ISLNK (fe->st.st_mode))
+ return;
+
+ i = readlink (fe->fname->str, buffer, MC_MAXPATHLEN - 1);
+ if (i < 0)
+ return;
+
+ panel_fname_vpath = vfs_path_from_str (fe->fname->str);
+ ok = (mc_stat (panel_fname_vpath, &st) >= 0);
+ vfs_path_free (panel_fname_vpath, TRUE);
+ if (!ok)
+ return;
+
+ buffer[i] = '\0';
+ if (!S_ISDIR (st.st_mode))
+ {
+ char *p;
+
+ p = strrchr (buffer, PATH_SEP);
+ if (p != NULL && p[1] == '\0')
+ {
+ *p = '\0';
+ p = strrchr (buffer, PATH_SEP);
+ }
+ if (p == NULL)
+ return;
+
+ p[1] = '\0';
+ }
+ if (IS_PATH_SEP (*buffer))
+ new_dir_vpath = vfs_path_from_str (buffer);
+ else
+ new_dir_vpath = vfs_path_append_new (panel->cwd_vpath, buffer, (char *) NULL);
+
+ cpanel = change_panel ();
+ panel_cd (cpanel, new_dir_vpath, cd_exact);
+ vfs_path_free (new_dir_vpath, TRUE);
+ (void) change_panel ();
+
+ move_down (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ function return 0 if not found and REAL_INDEX+1 if found
+ */
+
+static gsize
+panel_get_format_field_index_by_name (const WPanel * panel, const char *name)
+{
+ GSList *format;
+ gsize lc_index;
+
+ for (lc_index = 1, format = panel->format;
+ format != NULL && strcmp (((format_item_t *) format->data)->title, name) != 0;
+ format = g_slist_next (format), lc_index++)
+ ;
+
+ if (format == NULL)
+ lc_index = 0;
+
+ return lc_index;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const panel_field_t *
+panel_get_sortable_field_by_format (const WPanel * panel, gsize lc_index)
+{
+ const panel_field_t *pfield;
+ const format_item_t *format;
+
+ format = (const format_item_t *) g_slist_nth_data (panel->format, lc_index);
+ if (format == NULL)
+ return NULL;
+
+ pfield = panel_get_field_by_title (format->title);
+ if (pfield == NULL)
+ return NULL;
+ if (pfield->sort_routine == NULL)
+ return NULL;
+ return pfield;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_toggle_sort_order_prev (WPanel * panel)
+{
+ gsize lc_index, i;
+ const char *title;
+ const panel_field_t *pfield = NULL;
+
+ title = panel_get_title_without_hotkey (panel->sort_field->title_hotkey);
+ lc_index = panel_get_format_field_index_by_name (panel, title);
+
+ if (lc_index > 1)
+ {
+ /* search for prev sortable column in panel format */
+ for (i = lc_index - 1;
+ i != 0 && (pfield = panel_get_sortable_field_by_format (panel, i - 1)) == NULL; i--)
+ ;
+ }
+
+ if (pfield == NULL)
+ {
+ /* Sortable field not found. Try to search in each array */
+ for (i = g_slist_length (panel->format);
+ i != 0 && (pfield = panel_get_sortable_field_by_format (panel, i - 1)) == NULL; i--)
+ ;
+ }
+
+ if (pfield != NULL)
+ {
+ panel->sort_field = pfield;
+ panel_set_sort_order (panel, pfield);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_toggle_sort_order_next (WPanel * panel)
+{
+ gsize lc_index, i;
+ const panel_field_t *pfield = NULL;
+ gsize format_field_count;
+ const char *title;
+
+ format_field_count = g_slist_length (panel->format);
+ title = panel_get_title_without_hotkey (panel->sort_field->title_hotkey);
+ lc_index = panel_get_format_field_index_by_name (panel, title);
+
+ if (lc_index != 0 && lc_index != format_field_count)
+ {
+ /* search for prev sortable column in panel format */
+ for (i = lc_index;
+ i != format_field_count
+ && (pfield = panel_get_sortable_field_by_format (panel, i)) == NULL; i++)
+ ;
+ }
+
+ if (pfield == NULL)
+ {
+ /* Sortable field not found. Try to search in each array */
+ for (i = 0;
+ i != format_field_count
+ && (pfield = panel_get_sortable_field_by_format (panel, i)) == NULL; i++)
+ ;
+ }
+
+ if (pfield != NULL)
+ {
+ panel->sort_field = pfield;
+ panel_set_sort_order (panel, pfield);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_select_sort_order (WPanel * panel)
+{
+ const panel_field_t *sort_order;
+
+ sort_order = sort_box (&panel->sort_info, panel->sort_field);
+ if (sort_order != NULL)
+ {
+ panel->sort_field = sort_order;
+ panel_set_sort_order (panel, sort_order);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * panel_content_scroll_left:
+ * @param panel the pointer to the panel on which we operate
+ *
+ * scroll long filename to the left (decrement scroll pointer)
+ *
+ */
+
+static void
+panel_content_scroll_left (WPanel * panel)
+{
+ if (panel->content_shift > -1)
+ {
+ if (panel->content_shift > panel->max_shift)
+ panel->content_shift = panel->max_shift;
+
+ panel->content_shift--;
+ show_dir (panel);
+ paint_dir (panel);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * panel_content_scroll_right:
+ * @param panel the pointer to the panel on which we operate
+ *
+ * scroll long filename to the right (increment scroll pointer)
+ *
+ */
+
+static void
+panel_content_scroll_right (WPanel * panel)
+{
+ if (panel->content_shift < 0 || panel->content_shift < panel->max_shift)
+ {
+ panel->content_shift++;
+ show_dir (panel);
+ paint_dir (panel);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_set_sort_type_by_id (WPanel * panel, const char *name)
+{
+ if (strcmp (panel->sort_field->id, name) == 0)
+ panel->sort_info.reverse = !panel->sort_info.reverse;
+ else
+ {
+ const panel_field_t *sort_order;
+
+ sort_order = panel_get_field_by_id (name);
+ if (sort_order == NULL)
+ return;
+
+ panel->sort_field = sort_order;
+ }
+
+ panel_set_sort_order (panel, panel->sort_field);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * If we moved to the parent directory move the 'current' pointer to
+ * the old directory name; If we leave VFS dir, remove FS specificator.
+ *
+ * You do _NOT_ want to add any vfs aware code here. <pavel@ucw.cz>
+ */
+
+static const char *
+get_parent_dir_name (const vfs_path_t * cwd_vpath, const vfs_path_t * lwd_vpath)
+{
+ size_t llen, clen;
+ const char *p, *lwd;
+
+ llen = vfs_path_len (lwd_vpath);
+ clen = vfs_path_len (cwd_vpath);
+
+ if (llen <= clen)
+ return NULL;
+
+ lwd = vfs_path_as_str (lwd_vpath);
+
+ p = g_strrstr (lwd, VFS_PATH_URL_DELIMITER);
+
+ if (p == NULL)
+ {
+ const char *cwd;
+
+ cwd = vfs_path_as_str (cwd_vpath);
+
+ p = strrchr (lwd, PATH_SEP);
+
+ if (p != NULL && strncmp (cwd, lwd, (size_t) (p - lwd)) == 0
+ && (clen == (size_t) (p - lwd) || (p == lwd && IS_PATH_SEP (cwd[0]) && cwd[1] == '\0')))
+ return (p + 1);
+
+ return NULL;
+ }
+
+ /* skip VFS prefix */
+ while (--p > lwd && !IS_PATH_SEP (*p))
+ ;
+ /* get last component */
+ while (--p > lwd && !IS_PATH_SEP (*p))
+ ;
+
+ /* return last component */
+ return (p != lwd || IS_PATH_SEP (*p)) ? p + 1 : p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Wrapper for do_subshell_chdir, check for availability of subshell */
+
+static void
+subshell_chdir (const vfs_path_t * vpath)
+{
+#ifdef ENABLE_SUBSHELL
+ if (mc_global.tty.use_subshell && vfs_current_is_local ())
+ do_subshell_chdir (vpath, FALSE);
+#else /* ENABLE_SUBSHELL */
+ (void) vpath;
+#endif /* ENABLE_SUBSHELL */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Changes the current directory of the panel.
+ * Don't record change in the directory history.
+ */
+
+static gboolean
+panel_do_cd_int (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum cd_type)
+{
+ vfs_path_t *olddir_vpath;
+
+ /* Convert *new_path to a suitable pathname, handle ~user */
+ if (cd_type == cd_parse_command)
+ {
+ const vfs_path_element_t *element;
+
+ element = vfs_path_get_by_index (new_dir_vpath, 0);
+ if (strcmp (element->path, "-") == 0)
+ new_dir_vpath = panel->lwd_vpath;
+ }
+
+ if (mc_chdir (new_dir_vpath) == -1)
+ return FALSE;
+
+ /* Success: save previous directory, shutdown status of previous dir */
+ olddir_vpath = vfs_path_clone (panel->cwd_vpath);
+ panel_set_lwd (panel, panel->cwd_vpath);
+ input_complete_free (cmdline);
+
+ vfs_path_free (panel->cwd_vpath, TRUE);
+ vfs_setup_cwd ();
+ panel->cwd_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
+
+ vfs_release_path (olddir_vpath);
+
+ subshell_chdir (panel->cwd_vpath);
+
+ /* Reload current panel */
+ panel_clean_dir (panel);
+
+ if (!dir_list_load (&panel->dir, panel->cwd_vpath, panel->sort_field->sort_routine,
+ &panel->sort_info, &panel->filter))
+ message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
+
+ panel_set_current_by_name (panel, get_parent_dir_name (panel->cwd_vpath, olddir_vpath));
+
+ load_hint (FALSE);
+ panel->dirty = TRUE;
+ update_xterm_title_path ();
+
+ vfs_path_free (olddir_vpath, TRUE);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+directory_history_next (WPanel * panel)
+{
+ gboolean ok;
+
+ do
+ {
+ GList *next;
+
+ ok = TRUE;
+ next = g_list_next (panel->dir_history.current);
+ if (next != NULL)
+ {
+ vfs_path_t *data_vpath;
+
+ data_vpath = vfs_path_from_str ((char *) next->data);
+ ok = panel_do_cd_int (panel, data_vpath, cd_exact);
+ vfs_path_free (data_vpath, TRUE);
+ panel->dir_history.current = next;
+ }
+ /* skip directories that present in history but absent in file system */
+ }
+ while (!ok);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+directory_history_prev (WPanel * panel)
+{
+ gboolean ok;
+
+ do
+ {
+ GList *prev;
+
+ ok = TRUE;
+ prev = g_list_previous (panel->dir_history.current);
+ if (prev != NULL)
+ {
+ vfs_path_t *data_vpath;
+
+ data_vpath = vfs_path_from_str ((char *) prev->data);
+ ok = panel_do_cd_int (panel, data_vpath, cd_exact);
+ vfs_path_free (data_vpath, TRUE);
+ panel->dir_history.current = prev;
+ }
+ /* skip directories that present in history but absent in file system */
+ }
+ while (!ok);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+directory_history_list (WPanel * panel)
+{
+ history_descriptor_t hd;
+ gboolean ok = FALSE;
+ size_t pos;
+
+ pos = g_list_position (panel->dir_history.current, panel->dir_history.list);
+
+ history_descriptor_init (&hd, WIDGET (panel)->rect.y, WIDGET (panel)->rect.x,
+ panel->dir_history.list, (int) pos);
+ history_show (&hd);
+
+ panel->dir_history.list = hd.list;
+ if (hd.text != NULL)
+ {
+ vfs_path_t *s_vpath;
+
+ s_vpath = vfs_path_from_str (hd.text);
+ ok = panel_do_cd_int (panel, s_vpath, cd_exact);
+ if (ok)
+ directory_history_add (panel, panel->cwd_vpath);
+ else
+ cd_error_message (hd.text);
+ vfs_path_free (s_vpath, TRUE);
+ g_free (hd.text);
+ }
+
+ if (!ok)
+ {
+ /* Since history is fully modified in history_show(), panel->dir_history actually
+ * points to the invalid place. Try restore current position here. */
+
+ size_t i;
+
+ panel->dir_history.current = panel->dir_history.list;
+
+ for (i = 0; i <= pos; i++)
+ {
+ GList *prev;
+
+ prev = g_list_previous (panel->dir_history.current);
+ if (prev == NULL)
+ break;
+
+ panel->dir_history.current = prev;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+panel_execute_cmd (WPanel * panel, long command)
+{
+ int res = MSG_HANDLED;
+
+ if (command != CK_Search)
+ stop_search (panel);
+
+ switch (command)
+ {
+ case CK_Up:
+ case CK_Down:
+ case CK_Left:
+ case CK_Right:
+ case CK_Bottom:
+ case CK_Top:
+ case CK_PageDown:
+ case CK_PageUp:
+ /* reset state of marks flag */
+ state_mark = -1;
+ break;
+ default:
+ break;
+ }
+
+ switch (command)
+ {
+ case CK_CycleListingFormat:
+ panel_cycle_listing_format (panel);
+ break;
+ case CK_PanelOtherCd:
+ chdir_other_panel (panel);
+ break;
+ case CK_PanelOtherCdLink:
+ chdir_to_readlink (panel);
+ break;
+ case CK_CopySingle:
+ copy_cmd_local (panel);
+ break;
+ case CK_DeleteSingle:
+ delete_cmd_local (panel);
+ break;
+ case CK_Enter:
+ do_enter (panel);
+ break;
+ case CK_ViewRaw:
+ view_raw_cmd (panel);
+ break;
+ case CK_EditNew:
+ edit_cmd_new ();
+ break;
+ case CK_MoveSingle:
+ rename_cmd_local (panel);
+ break;
+ case CK_SelectInvert:
+ panel_select_invert_files (panel);
+ break;
+ case CK_Select:
+ panel_select_files (panel);
+ break;
+ case CK_SelectExt:
+ panel_select_ext_cmd (panel);
+ break;
+ case CK_Unselect:
+ panel_unselect_files (panel);
+ break;
+ case CK_Filter:
+ panel_do_set_filter (panel);
+ break;
+ case CK_PageDown:
+ next_page (panel);
+ break;
+ case CK_PageUp:
+ prev_page (panel);
+ break;
+ case CK_CdChild:
+ goto_child_dir (panel);
+ break;
+ case CK_CdParent:
+ goto_parent_dir (panel);
+ break;
+ case CK_History:
+ directory_history_list (panel);
+ break;
+ case CK_HistoryNext:
+ directory_history_next (panel);
+ break;
+ case CK_HistoryPrev:
+ directory_history_prev (panel);
+ break;
+ case CK_BottomOnScreen:
+ goto_bottom_file (panel);
+ break;
+ case CK_MiddleOnScreen:
+ goto_middle_file (panel);
+ break;
+ case CK_TopOnScreen:
+ goto_top_file (panel);
+ break;
+ case CK_Mark:
+ mark_file (panel);
+ break;
+ case CK_MarkUp:
+ mark_file_up (panel);
+ break;
+ case CK_MarkDown:
+ mark_file_down (panel);
+ break;
+ case CK_MarkLeft:
+ mark_file_left (panel);
+ break;
+ case CK_MarkRight:
+ mark_file_right (panel);
+ break;
+ case CK_CdParentSmart:
+ res = force_maybe_cd (panel);
+ break;
+ case CK_Up:
+ move_up (panel);
+ break;
+ case CK_Down:
+ move_down (panel);
+ break;
+ case CK_Left:
+ res = move_left (panel);
+ break;
+ case CK_Right:
+ res = move_right (panel);
+ break;
+ case CK_Bottom:
+ move_end (panel);
+ break;
+ case CK_Top:
+ move_home (panel);
+ break;
+#ifdef HAVE_CHARSET
+ case CK_SelectCodepage:
+ panel_change_encoding (panel);
+ break;
+#endif
+ case CK_ScrollLeft:
+ panel_content_scroll_left (panel);
+ break;
+ case CK_ScrollRight:
+ panel_content_scroll_right (panel);
+ break;
+ case CK_Search:
+ start_search (panel);
+ break;
+ case CK_SearchStop:
+ break;
+ case CK_PanelOtherSync:
+ panel_sync_other (panel);
+ break;
+ case CK_Sort:
+ panel_select_sort_order (panel);
+ break;
+ case CK_SortPrev:
+ panel_toggle_sort_order_prev (panel);
+ break;
+ case CK_SortNext:
+ panel_toggle_sort_order_next (panel);
+ break;
+ case CK_SortReverse:
+ panel->sort_info.reverse = !panel->sort_info.reverse;
+ panel_set_sort_order (panel, panel->sort_field);
+ break;
+ case CK_SortByName:
+ panel_set_sort_type_by_id (panel, "name");
+ break;
+ case CK_SortByExt:
+ panel_set_sort_type_by_id (panel, "extension");
+ break;
+ case CK_SortBySize:
+ panel_set_sort_type_by_id (panel, "size");
+ break;
+ case CK_SortByMTime:
+ panel_set_sort_type_by_id (panel, "mtime");
+ break;
+ default:
+ res = MSG_NOT_HANDLED;
+ break;
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+panel_key (WPanel * panel, int key)
+{
+ long command;
+
+ if (is_abort_char (key))
+ {
+ stop_search (panel);
+ return MSG_HANDLED;
+ }
+
+ if (panel->quick_search.active && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
+ {
+ do_search (panel, key);
+ return MSG_HANDLED;
+ }
+
+ command = widget_lookup_key (WIDGET (panel), key);
+ if (command != CK_IgnoreKey)
+ return panel_execute_cmd (panel, command);
+
+ if (panels_options.torben_fj_mode && key == ALT ('h'))
+ {
+ goto_middle_file (panel);
+ return MSG_HANDLED;
+ }
+
+ if (!command_prompt && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
+ {
+ start_search (panel);
+ do_search (panel, key);
+ return MSG_HANDLED;
+ }
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+panel_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WPanel *panel = PANEL (w);
+ WDialog *h = DIALOG (w->owner);
+ WButtonBar *bb;
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ /* subscribe to "history_load" event */
+ mc_event_add (h->event_group, MCEVENT_HISTORY_LOAD, panel_load_history, w, NULL);
+ /* subscribe to "history_save" event */
+ mc_event_add (h->event_group, MCEVENT_HISTORY_SAVE, panel_save_history, w, NULL);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ /* Repaint everything, including frame and separator */
+ widget_erase (w);
+ show_dir (panel);
+ panel_print_header (panel);
+ adjust_top_file (panel);
+ paint_dir (panel);
+ mini_info_separator (panel);
+ display_mini_info (panel);
+ panel->dirty = FALSE;
+ return MSG_HANDLED;
+
+ case MSG_FOCUS:
+ state_mark = -1;
+ current_panel = panel;
+ panel->active = TRUE;
+
+ if (mc_chdir (panel->cwd_vpath) != 0)
+ {
+ char *cwd;
+
+ cwd = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD);
+ cd_error_message (cwd);
+ g_free (cwd);
+ }
+ else
+ subshell_chdir (panel->cwd_vpath);
+
+ update_xterm_title_path ();
+ select_item (panel);
+
+ bb = buttonbar_find (h);
+ midnight_set_buttonbar (bb);
+ widget_draw (WIDGET (bb));
+ return MSG_HANDLED;
+
+ case MSG_UNFOCUS:
+ /* Janne: look at this for the multiple panel options */
+ stop_search (panel);
+ panel->active = FALSE;
+ unselect_item (panel);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ return panel_key (panel, parm);
+
+ case MSG_ACTION:
+ return panel_execute_cmd (panel, parm);
+
+ case MSG_DESTROY:
+ vfs_stamp_path (panel->cwd_vpath);
+ /* unsubscribe from "history_load" event */
+ mc_event_del (h->event_group, MCEVENT_HISTORY_LOAD, panel_load_history, w);
+ /* unsubscribe from "history_save" event */
+ mc_event_del (h->event_group, MCEVENT_HISTORY_SAVE, panel_save_history, w);
+ panel_destroy (panel);
+ free_my_statfs ();
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* */
+/* Panel mouse events support routines */
+/* */
+
+static void
+mouse_toggle_mark (WPanel * panel)
+{
+ do_mark_file (panel, MARK_DONT_MOVE);
+ mouse_marking = (panel_current_entry (panel)->f.marked != 0);
+ mouse_mark_panel = current_panel;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mouse_set_mark (WPanel * panel)
+{
+ if (mouse_mark_panel == panel)
+ {
+ const file_entry_t *fe;
+
+ fe = panel_current_entry (panel);
+
+ if (mouse_marking && fe->f.marked == 0)
+ do_mark_file (panel, MARK_DONT_MOVE);
+ else if (!mouse_marking && fe->f.marked != 0)
+ do_mark_file (panel, MARK_DONT_MOVE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mark_if_marking (WPanel * panel, const mouse_event_t * event, int previous_current)
+{
+ if ((event->buttons & GPM_B_RIGHT) == 0)
+ return;
+
+ if (event->msg == MSG_MOUSE_DOWN)
+ mouse_toggle_mark (panel);
+ else
+ {
+ int pcurr, curr1, curr2;
+
+ pcurr = panel->current;
+ curr1 = MIN (previous_current, panel->current);
+ curr2 = MAX (previous_current, panel->current);
+
+ for (; curr1 <= curr2; curr1++)
+ {
+ panel->current = curr1;
+ mouse_set_mark (panel);
+ }
+
+ panel->current = pcurr;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Determine which column was clicked, and sort the panel on
+ * that column, or reverse sort on that column if already
+ * sorted on that column.
+ */
+
+static void
+mouse_sort_col (WPanel * panel, int x)
+{
+ int i = 0;
+ GSList *format;
+ const char *lc_sort_name = NULL;
+ panel_field_t *col_sort_format = NULL;
+
+ for (format = panel->format; format != NULL; format = g_slist_next (format))
+ {
+ format_item_t *fi = (format_item_t *) format->data;
+
+ i += fi->field_len;
+ if (x < i + 1)
+ {
+ /* found column */
+ lc_sort_name = fi->title;
+ break;
+ }
+ }
+
+ if (lc_sort_name == NULL)
+ return;
+
+ for (i = 0; panel_fields[i].id != NULL; i++)
+ {
+ const char *title;
+
+ title = panel_get_title_without_hotkey (panel_fields[i].title_hotkey);
+ if (panel_fields[i].sort_routine != NULL && strcmp (title, lc_sort_name) == 0)
+ {
+ col_sort_format = &panel_fields[i];
+ break;
+ }
+ }
+
+ if (col_sort_format != NULL)
+ {
+ if (panel->sort_field == col_sort_format)
+ /* reverse the sort if clicked column is already the sorted column */
+ panel->sort_info.reverse = !panel->sort_info.reverse;
+ else
+ /* new sort is forced to be ascending */
+ panel->sort_info.reverse = FALSE;
+
+ panel_set_sort_order (panel, col_sort_format);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+panel_mouse_is_on_item (const WPanel * panel, int y, int x)
+{
+ int lines, col_width, col;
+
+ if (y < 0)
+ return MOUSE_UPPER_FILE_LIST;
+
+ lines = panel_lines (panel);
+ if (y >= lines)
+ return MOUSE_BELOW_FILE_LIST;
+
+ col_width = (CONST_WIDGET (panel)->rect.cols - 2) / panel->list_cols;
+ /* column where mouse is */
+ col = x / col_width;
+
+ y += panel->top + lines * col;
+
+ /* are we below or in the next column of last file? */
+ if (y > panel->dir.len)
+ return MOUSE_AFTER_LAST_FILE;
+
+ /* we are on item of the file file; return an index to select a file */
+ return y;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WPanel *panel = PANEL (w);
+ gboolean is_active;
+
+ is_active = widget_is_active (w);
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ if (event->y == 0)
+ {
+ /* top frame */
+ if (event->x == 1)
+ /* "<" button */
+ directory_history_prev (panel);
+ else if (event->x == w->rect.cols - 2)
+ /* ">" button */
+ directory_history_next (panel);
+ else if (event->x >= w->rect.cols - 5 && event->x <= w->rect.cols - 3)
+ /* "^" button */
+ directory_history_list (panel);
+ else if (event->x == w->rect.cols - 6)
+ /* "." button show/hide hidden files */
+ send_message (filemanager, NULL, MSG_ACTION, CK_ShowHidden, NULL);
+ else
+ {
+ /* no other events on 1st line, return MOU_UNHANDLED */
+ event->result.abort = TRUE;
+ /* avoid extra panel redraw */
+ panel->dirty = FALSE;
+ }
+ break;
+ }
+
+ if (event->y == 1)
+ {
+ /* sort on clicked column */
+ mouse_sort_col (panel, event->x + 1);
+ break;
+ }
+
+ if (!is_active)
+ (void) change_panel ();
+ MC_FALLTHROUGH;
+
+ case MSG_MOUSE_DRAG:
+ {
+ int my_index;
+ int previous_current;
+
+ my_index = panel_mouse_is_on_item (panel, event->y - 2, event->x);
+ previous_current = panel->current;
+
+ switch (my_index)
+ {
+ case MOUSE_UPPER_FILE_LIST:
+ move_up (panel);
+ mark_if_marking (panel, event, previous_current);
+ break;
+
+ case MOUSE_BELOW_FILE_LIST:
+ move_down (panel);
+ mark_if_marking (panel, event, previous_current);
+ break;
+
+ case MOUSE_AFTER_LAST_FILE:
+ break; /* do nothing */
+
+ default:
+ if (my_index != panel->current)
+ {
+ unselect_item (panel);
+ panel->current = my_index;
+ select_item (panel);
+ }
+
+ mark_if_marking (panel, event, previous_current);
+ break;
+ }
+ }
+ break;
+
+ case MSG_MOUSE_UP:
+ break;
+
+ case MSG_MOUSE_CLICK:
+ if ((event->count & GPM_DOUBLE) != 0 && (event->buttons & GPM_B_LEFT) != 0 &&
+ panel_mouse_is_on_item (panel, event->y - 2, event->x) >= 0)
+ do_enter (panel);
+ break;
+
+ case MSG_MOUSE_MOVE:
+ break;
+
+ case MSG_MOUSE_SCROLL_UP:
+ if (is_active)
+ {
+ if (panels_options.mouse_move_pages && panel->top > 0)
+ prev_page (panel);
+ else /* We are in first page */
+ move_up (panel);
+ }
+ break;
+
+ case MSG_MOUSE_SCROLL_DOWN:
+ if (is_active)
+ {
+ if (panels_options.mouse_move_pages
+ && panel->top + panel_items (panel) < panel->dir.len)
+ next_page (panel);
+ else /* We are in last page */
+ move_down (panel);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (panel->dirty)
+ widget_draw (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+reload_panelized (WPanel * panel)
+{
+ int i, j;
+ dir_list *list = &panel->dir;
+
+ /* refresh current VFS directory required for vfs_path_from_str() */
+ (void) mc_chdir (panel->cwd_vpath);
+
+ for (i = 0, j = 0; i < list->len; i++)
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (list->list[i].fname->str);
+ if (mc_lstat (vpath, &list->list[i].st) != 0)
+ g_string_free (list->list[i].fname, TRUE);
+ else
+ {
+ if (j != i)
+ list->list[j] = list->list[i];
+ j++;
+ }
+ vfs_path_free (vpath, TRUE);
+ }
+ if (j == 0)
+ dir_list_init (list);
+ else
+ list->len = j;
+
+ recalculate_panel_summary (panel);
+
+ if (panel != current_panel)
+ (void) mc_chdir (current_panel->cwd_vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+update_one_panel_widget (WPanel * panel, panel_update_flags_t flags, const char *current_file)
+{
+ gboolean free_pointer;
+ char *my_current_file = NULL;
+
+ if ((flags & UP_RELOAD) != 0)
+ {
+ panel->is_panelized = FALSE;
+ mc_setctl (panel->cwd_vpath, VFS_SETCTL_FLUSH, NULL);
+ memset (&(panel->dir_stat), 0, sizeof (panel->dir_stat));
+ }
+
+ /* If current_file == -1 (an invalid pointer) then preserve current */
+ free_pointer = current_file == UP_KEEPSEL;
+
+ if (free_pointer)
+ {
+ const GString *fname;
+
+ fname = panel_current_entry (panel)->fname;
+ my_current_file = g_strndup (fname->str, fname->len);
+ current_file = my_current_file;
+ }
+
+ if (panel->is_panelized)
+ reload_panelized (panel);
+ else
+ panel_reload (panel);
+
+ panel_set_current_by_name (panel, current_file);
+ panel->dirty = TRUE;
+
+ if (free_pointer)
+ g_free (my_current_file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+update_one_panel (int which, panel_update_flags_t flags, const char *current_file)
+{
+ if (get_panel_type (which) == view_listing)
+ {
+ WPanel *panel;
+
+ panel = PANEL (get_panel_widget (which));
+ if (panel->is_panelized)
+ flags &= ~UP_RELOAD;
+ update_one_panel_widget (panel, flags, current_file);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_set_current (WPanel * panel, int i)
+{
+ if (i != panel->current)
+ {
+ panel->dirty = TRUE;
+ panel->current = i;
+ panel->top = panel->current - (WIDGET (panel)->rect.lines - 2) / 2;
+ if (panel->top < 0)
+ panel->top = 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+static gboolean
+event_update_panels (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+ (void) data;
+
+ update_panels (UP_RELOAD, UP_KEEPSEL);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+static gboolean
+panel_save_current_file_to_clip_file (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+ (void) data;
+
+ if (current_panel->marked == 0)
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_to_file",
+ (gpointer) panel_current_entry (current_panel)->fname->str);
+ else
+ {
+ int i;
+ gboolean first = TRUE;
+ char *flist = NULL;
+
+ for (i = 0; i < current_panel->dir.len; i++)
+ {
+ const file_entry_t *fe = &current_panel->dir.list[i];
+
+ if (fe->f.marked != 0)
+ { /* Skip the unmarked ones */
+ if (first)
+ {
+ flist = g_strndup (fe->fname->str, fe->fname->len);
+ first = FALSE;
+ }
+ else
+ {
+ /* Add empty lines after the file */
+ char *tmp;
+
+ tmp = g_strconcat (flist, "\n", fe->fname->str, (char *) NULL);
+ g_free (flist);
+ flist = tmp;
+ }
+ }
+ }
+
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_to_file", (gpointer) flist);
+ g_free (flist);
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+panel_recursive_cd_to_parent (const vfs_path_t * vpath)
+{
+ vfs_path_t *cwd_vpath;
+
+ cwd_vpath = vfs_path_clone (vpath);
+
+ while (mc_chdir (cwd_vpath) < 0)
+ {
+ const char *panel_cwd_path;
+ vfs_path_t *tmp_vpath;
+
+ /* check if path contains only '/' */
+ panel_cwd_path = vfs_path_as_str (cwd_vpath);
+ if (panel_cwd_path != NULL && IS_PATH_SEP (panel_cwd_path[0]) && panel_cwd_path[1] == '\0')
+ {
+ vfs_path_free (cwd_vpath, TRUE);
+ return NULL;
+ }
+
+ tmp_vpath = vfs_path_vtokens_get (cwd_vpath, 0, -1);
+ vfs_path_free (cwd_vpath, TRUE);
+ cwd_vpath =
+ vfs_path_build_filename (PATH_SEP_STR, vfs_path_as_str (tmp_vpath), (char *) NULL);
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+
+ return cwd_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_dir_list_callback (dir_list_cb_state_t state, void *data)
+{
+ static int count = 0;
+
+ (void) data;
+
+ switch (state)
+ {
+ case DIR_OPEN:
+ count = 0;
+ break;
+
+ case DIR_READ:
+ count++;
+ if ((count & 15) == 0)
+ rotate_dash (TRUE);
+ break;
+
+ case DIR_CLOSE:
+ rotate_dash (FALSE);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_set_current_by_name (WPanel * panel, const char *name)
+{
+ int i;
+ char *subdir;
+
+ if (name == NULL)
+ {
+ panel_set_current (panel, 0);
+ return;
+ }
+
+ /* We only want the last component of the directory,
+ * and from this only the name without suffix.
+ * Cut prefix if the panel is not panelized */
+ if (panel->is_panelized)
+ subdir = vfs_strip_suffix_from_filename (name);
+ else
+ subdir = vfs_strip_suffix_from_filename (x_basename (name));
+
+ /* Search that subdir or filename without prefix (if not panelized panel),
+ make it current if found */
+ for (i = 0; i < panel->dir.len; i++)
+ if (strcmp (subdir, panel->dir.list[i].fname->str) == 0)
+ {
+ panel_set_current (panel, i);
+ g_free (subdir);
+ return;
+ }
+
+ /* Make current near the filee that is missing */
+ if (panel->current >= panel->dir.len)
+ panel_set_current (panel, panel->dir.len - 1);
+ g_free (subdir);
+
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_clean_dir (WPanel * panel)
+{
+ panel->top = 0;
+ panel->current = 0;
+ panel->marked = 0;
+ panel->dirs_marked = 0;
+ panel->total = 0;
+ panel->quick_search.active = FALSE;
+ panel->is_panelized = FALSE;
+ panel->dirty = TRUE;
+ panel->content_shift = -1;
+ panel->max_shift = -1;
+
+ dir_list_free_list (&panel->dir);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Set Up panel's current dir object
+ *
+ * @param panel panel object
+ * @param path_str string contain path
+ */
+
+void
+panel_set_cwd (WPanel * panel, const vfs_path_t * vpath)
+{
+ if (vpath != panel->cwd_vpath) /* check if new vpath is not the panel->cwd_vpath object */
+ {
+ vfs_path_free (panel->cwd_vpath, TRUE);
+ panel->cwd_vpath = vfs_path_clone (vpath);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Set Up panel's last working dir object
+ *
+ * @param panel panel object
+ * @param path_str string contain path
+ */
+
+void
+panel_set_lwd (WPanel * panel, const vfs_path_t * vpath)
+{
+ if (vpath != panel->lwd_vpath) /* check if new vpath is not the panel->lwd_vpath object */
+ {
+ vfs_path_free (panel->lwd_vpath, TRUE);
+ panel->lwd_vpath = vfs_path_clone (vpath);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Creatie an empty panel with specified size.
+ *
+ * @param panel_name name of panel for setup receiving
+ *
+ * @return new instance of WPanel
+ */
+
+WPanel *
+panel_sized_empty_new (const char *panel_name, int y, int x, int lines, int cols)
+{
+ WRect r = { y, x, lines, cols };
+ WPanel *panel;
+ Widget *w;
+ char *section;
+ int i, err;
+
+ panel = g_new0 (WPanel, 1);
+ w = WIDGET (panel);
+ widget_init (w, &r, panel_callback, panel_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
+ w->keymap = panel_map;
+
+ panel->dir.size = DIR_LIST_MIN_SIZE;
+ panel->dir.list = g_new (file_entry_t, panel->dir.size);
+ panel->dir.len = 0;
+ panel->dir.callback = panel_dir_list_callback;
+
+ panel->list_cols = 1;
+ panel->brief_cols = 2;
+ panel->dirty = TRUE;
+ panel->content_shift = -1;
+ panel->max_shift = -1;
+
+ panel->list_format = list_full;
+ panel->user_format = g_strdup (DEFAULT_USER_FORMAT);
+
+ panel->filter.flags = FILE_FILTER_DEFAULT_FLAGS;
+
+ for (i = 0; i < LIST_FORMATS; i++)
+ panel->user_status_format[i] = g_strdup (DEFAULT_USER_FORMAT);
+
+#ifdef HAVE_CHARSET
+ panel->codepage = SELECT_CHARSET_NO_TRANSLATE;
+#endif
+
+ panel->frame_size = frame_half;
+
+ panel->quick_search.buffer = g_string_sized_new (MC_MAXFILENAMELEN);
+ panel->quick_search.prev_buffer = g_string_sized_new (MC_MAXFILENAMELEN);
+
+ panel->name = g_strdup (panel_name);
+ panel->dir_history.name = g_strconcat ("Dir Hist ", panel->name, (char *) NULL);
+ /* directories history will be get later */
+
+ section = g_strconcat ("Temporal:", panel->name, (char *) NULL);
+ if (!mc_config_has_group (mc_global.main_config, section))
+ {
+ g_free (section);
+ section = g_strdup (panel->name);
+ }
+ panel_load_setup (panel, section);
+ g_free (section);
+
+ if (panel->filter.value != NULL)
+ {
+ gboolean case_sens = (panel->filter.flags & SELECT_MATCH_CASE) != 0;
+ gboolean shell_patterns = (panel->filter.flags & SELECT_SHELL_PATTERNS) != 0;
+
+ panel->filter.handler = mc_search_new (panel->filter.value, NULL);
+ panel->filter.handler->search_type = shell_patterns ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
+ panel->filter.handler->is_entire_line = TRUE;
+ panel->filter.handler->is_case_sensitive = case_sens;
+
+ /* FIXME: silent check -- do not display an error message */
+ if (!mc_search_prepare (panel->filter.handler))
+ file_filter_clear (&panel->filter);
+ }
+
+ /* Load format strings */
+ err = set_panel_formats (panel);
+ if (err != 0)
+ set_panel_formats (panel);
+
+ return panel;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Panel creation for specified size and directory.
+ *
+ * @param panel_name name of panel for setup retrieving
+ * @param y y coordinate of top-left corner
+ * @param x x coordinate of top-left corner
+ * @param lines vertical size
+ * @param cols horizontal size
+ * @param vpath working panel directory. If NULL then current directory is used
+ *
+ * @return new instance of WPanel
+ */
+
+WPanel *
+panel_sized_with_dir_new (const char *panel_name, int y, int x, int lines, int cols,
+ const vfs_path_t * vpath)
+{
+ WPanel *panel;
+ char *curdir = NULL;
+#ifdef HAVE_CHARSET
+ const vfs_path_element_t *path_element;
+#endif
+
+ panel = panel_sized_empty_new (panel_name, y, x, lines, cols);
+
+ if (vpath != NULL)
+ {
+ curdir = vfs_get_cwd ();
+ panel_set_cwd (panel, vpath);
+ }
+ else
+ {
+ vfs_setup_cwd ();
+ panel->cwd_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
+ }
+
+ panel_set_lwd (panel, vfs_get_raw_current_dir ());
+
+#ifdef HAVE_CHARSET
+ path_element = vfs_path_get_by_index (panel->cwd_vpath, -1);
+ if (path_element->encoding != NULL)
+ panel->codepage = get_codepage_index (path_element->encoding);
+#endif
+
+ if (mc_chdir (panel->cwd_vpath) != 0)
+ {
+#ifdef HAVE_CHARSET
+ panel->codepage = SELECT_CHARSET_NO_TRANSLATE;
+#endif
+ vfs_setup_cwd ();
+ vfs_path_free (panel->cwd_vpath, TRUE);
+ panel->cwd_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
+ }
+
+ /* Load the default format */
+ if (!dir_list_load (&panel->dir, panel->cwd_vpath, panel->sort_field->sort_routine,
+ &panel->sort_info, &panel->filter))
+ message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
+
+ /* Restore old right path */
+ if (curdir != NULL)
+ {
+ vfs_path_t *tmp_vpath;
+ int err;
+
+ tmp_vpath = vfs_path_from_str (curdir);
+ mc_chdir (tmp_vpath);
+ vfs_path_free (tmp_vpath, TRUE);
+ (void) err;
+ }
+ g_free (curdir);
+
+ return panel;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_reload (WPanel * panel)
+{
+ struct stat current_stat;
+ vfs_path_t *cwd_vpath;
+
+ if (panels_options.fast_reload && stat (vfs_path_as_str (panel->cwd_vpath), &current_stat) == 0
+ && current_stat.st_ctime == panel->dir_stat.st_ctime
+ && current_stat.st_mtime == panel->dir_stat.st_mtime)
+ return;
+
+ cwd_vpath = panel_recursive_cd_to_parent (panel->cwd_vpath);
+ vfs_path_free (panel->cwd_vpath, TRUE);
+
+ if (cwd_vpath == NULL)
+ {
+ panel->cwd_vpath = vfs_path_from_str (PATH_SEP_STR);
+ panel_clean_dir (panel);
+ dir_list_init (&panel->dir);
+ return;
+ }
+
+ panel->cwd_vpath = cwd_vpath;
+ memset (&(panel->dir_stat), 0, sizeof (panel->dir_stat));
+ show_dir (panel);
+
+ if (!dir_list_reload (&panel->dir, panel->cwd_vpath, panel->sort_field->sort_routine,
+ &panel->sort_info, &panel->filter))
+ message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
+
+ panel->dirty = TRUE;
+ if (panel->current >= panel->dir.len)
+ panel_set_current (panel, panel->dir.len - 1);
+
+ recalculate_panel_summary (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Switches the panel to the mode specified in the format */
+/* Setting up both format and status string. Return: 0 - on success; */
+/* 1 - format error; 2 - status error; 3 - errors in both formats. */
+
+int
+set_panel_formats (WPanel * p)
+{
+ GSList *form;
+ char *err = NULL;
+ int retcode = 0;
+
+ form = use_display_format (p, panel_format (p), &err, FALSE);
+
+ if (err != NULL)
+ {
+ g_free (err);
+ retcode = 1;
+ }
+ else
+ {
+ g_slist_free_full (p->format, (GDestroyNotify) format_item_free);
+ p->format = form;
+ }
+
+ if (panels_options.show_mini_info)
+ {
+ form = use_display_format (p, mini_status_format (p), &err, TRUE);
+
+ if (err != NULL)
+ {
+ g_free (err);
+ retcode += 2;
+ }
+ else
+ {
+ g_slist_free_full (p->status_format, (GDestroyNotify) format_item_free);
+ p->status_format = form;
+ }
+ }
+
+ panel_update_cols (WIDGET (p), p->frame_size);
+
+ if (retcode)
+ message (D_ERROR, _("Warning"),
+ _("User supplied format looks invalid, reverting to default."));
+ if (retcode & 0x01)
+ {
+ g_free (p->user_format);
+ p->user_format = g_strdup (DEFAULT_USER_FORMAT);
+ }
+ if (retcode & 0x02)
+ {
+ g_free (p->user_status_format[p->list_format]);
+ p->user_status_format[p->list_format] = g_strdup (DEFAULT_USER_FORMAT);
+ }
+
+ return retcode;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_set_filter (WPanel * panel, const file_filter_t * filter)
+{
+ MC_PTR_FREE (panel->filter.value);
+ mc_search_free (panel->filter.handler);
+ panel->filter.handler = NULL;
+
+ /* NULL to clear filter */
+ if (filter != NULL)
+ panel->filter = *filter;
+
+ reread_cmd ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Select current item and readjust the panel */
+void
+select_item (WPanel * panel)
+{
+ adjust_top_file (panel);
+
+ panel->dirty = TRUE;
+
+ execute_hooks (select_file_hook);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Clears all files in the panel, used only when one file was marked */
+void
+unmark_files (WPanel * panel)
+{
+ if (panel->marked != 0)
+ {
+ int i;
+
+ for (i = 0; i < panel->dir.len; i++)
+ file_mark (panel, i, 0);
+
+ panel->dirs_marked = 0;
+ panel->marked = 0;
+ panel->total = 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Recalculate the panels summary information, used e.g. when marked
+ files might have been removed by an external command */
+
+void
+recalculate_panel_summary (WPanel * panel)
+{
+ int i;
+
+ panel->marked = 0;
+ panel->dirs_marked = 0;
+ panel->total = 0;
+
+ for (i = 0; i < panel->dir.len; i++)
+ if (panel->dir.list[i].f.marked != 0)
+ {
+ /* do_file_mark will return immediately if newmark == oldmark.
+ So we have to first unmark it to get panel's summary information
+ updated. (Norbert) */
+ panel->dir.list[i].f.marked = 0;
+ do_file_mark (panel, i, 1);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This routine marks a file or a directory */
+
+void
+do_file_mark (WPanel * panel, int idx, int mark)
+{
+ if (panel->dir.list[idx].f.marked == mark)
+ return;
+
+ /* Only '..' can't be marked, '.' isn't visible */
+ if (DIR_IS_DOTDOT (panel->dir.list[idx].fname->str))
+ return;
+
+ file_mark (panel, idx, mark);
+ if (panel->dir.list[idx].f.marked != 0)
+ {
+ panel->marked++;
+
+ if (S_ISDIR (panel->dir.list[idx].st.st_mode))
+ {
+ if (panel->dir.list[idx].f.dir_size_computed != 0)
+ panel->total += (uintmax_t) panel->dir.list[idx].st.st_size;
+ panel->dirs_marked++;
+ }
+ else
+ panel->total += (uintmax_t) panel->dir.list[idx].st.st_size;
+
+ set_colors (panel);
+ }
+ else
+ {
+ if (S_ISDIR (panel->dir.list[idx].st.st_mode))
+ {
+ if (panel->dir.list[idx].f.dir_size_computed != 0)
+ panel->total -= (uintmax_t) panel->dir.list[idx].st.st_size;
+ panel->dirs_marked--;
+ }
+ else
+ panel->total -= (uintmax_t) panel->dir.list[idx].st.st_size;
+
+ panel->marked--;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Changes the current directory of the panel.
+ * Record change in the directory history.
+ */
+gboolean
+panel_do_cd (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum cd_type)
+{
+ gboolean r;
+
+ r = panel_do_cd_int (panel, new_dir_vpath, cd_type);
+ if (r)
+ directory_history_add (panel, panel->cwd_vpath);
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_mark (WPanel * panel, int lc_index, int val)
+{
+ if (panel->dir.list[lc_index].f.marked != val)
+ {
+ panel->dir.list[lc_index].f.marked = val;
+ panel->dirty = TRUE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_re_sort (WPanel * panel)
+{
+ char *filename;
+ const file_entry_t *fe;
+ int i;
+
+ if (panel == NULL)
+ return;
+
+ fe = panel_current_entry (panel);
+ filename = g_strndup (fe->fname->str, fe->fname->len);
+ unselect_item (panel);
+ dir_list_sort (&panel->dir, panel->sort_field->sort_routine, &panel->sort_info);
+ panel->current = -1;
+
+ for (i = panel->dir.len; i != 0; i--)
+ if (strcmp (panel->dir.list[i - 1].fname->str, filename) == 0)
+ {
+ panel->current = i - 1;
+ break;
+ }
+
+ g_free (filename);
+ panel->top = panel->current - panel_items (panel) / 2;
+ select_item (panel);
+ panel->dirty = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_set_sort_order (WPanel * panel, const panel_field_t * sort_order)
+{
+ if (sort_order == NULL)
+ return;
+
+ panel->sort_field = sort_order;
+
+ /* The directory is already sorted, we have to load the unsorted stuff */
+ if (sort_order->sort_routine == (GCompareFunc) unsorted)
+ {
+ char *current_file;
+ const GString *fname;
+
+ fname = panel_current_entry (panel)->fname;
+ current_file = g_strndup (fname->str, fname->len);
+ panel_reload (panel);
+ panel_set_current_by_name (panel, current_file);
+ g_free (current_file);
+ }
+ panel_re_sort (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+
+/**
+ * Change panel encoding.
+ * @param panel WPanel object
+ */
+
+void
+panel_change_encoding (WPanel * panel)
+{
+ const char *encoding = NULL;
+ char *errmsg;
+ int r;
+
+ r = select_charset (-1, -1, panel->codepage, FALSE);
+
+ if (r == SELECT_CHARSET_CANCEL)
+ return; /* Cancel */
+
+ panel->codepage = r;
+
+ if (panel->codepage == SELECT_CHARSET_NO_TRANSLATE)
+ {
+ /* No translation */
+ vfs_path_t *cd_path_vpath;
+
+ g_free (init_translation_table (mc_global.display_codepage, mc_global.display_codepage));
+ cd_path_vpath = remove_encoding_from_path (panel->cwd_vpath);
+ panel_do_cd (panel, cd_path_vpath, cd_parse_command);
+ show_dir (panel);
+ vfs_path_free (cd_path_vpath, TRUE);
+ return;
+ }
+
+ errmsg = init_translation_table (panel->codepage, mc_global.display_codepage);
+ if (errmsg != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, "%s", errmsg);
+ g_free (errmsg);
+ return;
+ }
+
+ encoding = get_codepage_id (panel->codepage);
+ if (encoding != NULL)
+ {
+ vfs_path_change_encoding (panel->cwd_vpath, encoding);
+
+ if (!panel_do_cd (panel, panel->cwd_vpath, cd_parse_command))
+ cd_error_message (vfs_path_as_str (panel->cwd_vpath));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Remove encode info from last path element.
+ *
+ */
+vfs_path_t *
+remove_encoding_from_path (const vfs_path_t * vpath)
+{
+ vfs_path_t *ret_vpath;
+ GString *tmp_conv;
+ int indx;
+
+ ret_vpath = vfs_path_new (FALSE);
+
+ tmp_conv = g_string_new ("");
+
+ for (indx = 0; indx < vfs_path_elements_count (vpath); indx++)
+ {
+ GIConv converter;
+ vfs_path_element_t *path_element;
+
+ path_element = vfs_path_element_clone (vfs_path_get_by_index (vpath, indx));
+
+ if (path_element->encoding == NULL)
+ {
+ vfs_path_add_element (ret_vpath, path_element);
+ continue;
+ }
+
+ converter = str_crt_conv_to (path_element->encoding);
+ if (converter == INVALID_CONV)
+ {
+ vfs_path_add_element (ret_vpath, path_element);
+ continue;
+ }
+
+ MC_PTR_FREE (path_element->encoding);
+
+ str_vfs_convert_from (converter, path_element->path, tmp_conv);
+
+ g_free (path_element->path);
+ path_element->path = g_strndup (tmp_conv->str, tmp_conv->len);
+
+ g_string_set_size (tmp_conv, 0);
+
+ str_close_conv (converter);
+ str_close_conv (path_element->dir.converter);
+ path_element->dir.converter = INVALID_CONV;
+ vfs_path_add_element (ret_vpath, path_element);
+ }
+ g_string_free (tmp_conv, TRUE);
+ return ret_vpath;
+}
+#endif /* HAVE_CHARSET */
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * This routine reloads the directory in both panels. It tries to
+ * select current_file in current_panel and other_file in other_panel.
+ * If current_file == -1 then it automatically sets current_file and
+ * other_file to the current files in the panels.
+ *
+ * If flags has the UP_ONLY_CURRENT bit toggled on, then it
+ * will not reload the other panel.
+ *
+ * @param flags for reload panel
+ * @param current_file name of the current file
+ */
+
+void
+update_panels (panel_update_flags_t flags, const char *current_file)
+{
+ WPanel *panel;
+
+ /* first, update other panel... */
+ if ((flags & UP_ONLY_CURRENT) == 0)
+ update_one_panel (get_other_index (), flags, UP_KEEPSEL);
+ /* ...then current one */
+ update_one_panel (get_current_index (), flags, current_file);
+
+ if (get_current_type () == view_listing)
+ panel = PANEL (get_panel_widget (get_current_index ()));
+ else
+ panel = PANEL (get_panel_widget (get_other_index ()));
+
+ if (!panel->is_panelized)
+ (void) mc_chdir (panel->cwd_vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gsize
+panel_get_num_of_sortable_fields (void)
+{
+ gsize ret = 0, lc_index;
+
+ for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
+ if (panel_fields[lc_index].is_user_choice)
+ ret++;
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char **
+panel_get_sortable_fields (gsize * array_size)
+{
+ char **ret;
+ gsize lc_index, i;
+
+ lc_index = panel_get_num_of_sortable_fields ();
+
+ ret = g_try_new0 (char *, lc_index + 1);
+ if (ret == NULL)
+ return NULL;
+
+ if (array_size != NULL)
+ *array_size = lc_index;
+
+ lc_index = 0;
+
+ for (i = 0; panel_fields[i].id != NULL; i++)
+ if (panel_fields[i].is_user_choice)
+ ret[lc_index++] = g_strdup (_(panel_fields[i].title_hotkey));
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const panel_field_t *
+panel_get_field_by_id (const char *name)
+{
+ gsize lc_index;
+
+ for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
+ if (panel_fields[lc_index].id != NULL && strcmp (name, panel_fields[lc_index].id) == 0)
+ return &panel_fields[lc_index];
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const panel_field_t *
+panel_get_field_by_title_hotkey (const char *name)
+{
+ gsize lc_index;
+
+ for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
+ if (panel_fields[lc_index].title_hotkey != NULL &&
+ strcmp (name, _(panel_fields[lc_index].title_hotkey)) == 0)
+ return &panel_fields[lc_index];
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const panel_field_t *
+panel_get_field_by_title (const char *name)
+{
+ gsize lc_index;
+
+ for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
+ {
+ const char *title;
+
+ title = panel_get_title_without_hotkey (panel_fields[lc_index].title_hotkey);
+ if (strcmp (title, name) == 0)
+ return &panel_fields[lc_index];
+ }
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gsize
+panel_get_num_of_user_possible_fields (void)
+{
+ gsize ret = 0, lc_index;
+
+ for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
+ if (panel_fields[lc_index].use_in_user_format)
+ ret++;
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char **
+panel_get_user_possible_fields (gsize * array_size)
+{
+ char **ret;
+ gsize lc_index, i;
+
+ lc_index = panel_get_num_of_user_possible_fields ();
+
+ ret = g_try_new0 (char *, lc_index + 1);
+ if (ret == NULL)
+ return NULL;
+
+ if (array_size != NULL)
+ *array_size = lc_index;
+
+ lc_index = 0;
+
+ for (i = 0; panel_fields[i].id != NULL; i++)
+ if (panel_fields[i].use_in_user_format)
+ ret[lc_index++] = g_strdup (_(panel_fields[i].title_hotkey));
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_panelize_cd (void)
+{
+ WPanel *panel;
+ int i;
+ dir_list *list;
+ panelized_descr_t *pdescr;
+ dir_list *plist;
+ gboolean panelized_same;
+
+ if (!SELECTED_IS_PANEL)
+ create_panel (MENU_PANEL_IDX, view_listing);
+
+ panel = PANEL (get_panel_widget (MENU_PANEL_IDX));
+
+ dir_list_clean (&panel->dir);
+
+ if (panel->panelized_descr == NULL)
+ panel->panelized_descr = panelized_descr_new ();
+
+ pdescr = panel->panelized_descr;
+ plist = &pdescr->list;
+
+ if (pdescr->root_vpath == NULL)
+ panel_panelize_change_root (panel, panel->cwd_vpath);
+
+ if (plist->len < 1)
+ dir_list_init (plist);
+ else if (plist->len > panel->dir.size)
+ dir_list_grow (&panel->dir, plist->len - panel->dir.size);
+
+ list = &panel->dir;
+ list->len = plist->len;
+
+ panelized_same = vfs_path_equal (pdescr->root_vpath, panel->cwd_vpath);
+
+ for (i = 0; i < plist->len; i++)
+ {
+ if (panelized_same || DIR_IS_DOTDOT (plist->list[i].fname->str))
+ list->list[i].fname = mc_g_string_dup (plist->list[i].fname);
+ else
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath =
+ vfs_path_append_new (pdescr->root_vpath, plist->list[i].fname->str, (char *) NULL);
+ list->list[i].fname = g_string_new (vfs_path_as_str (tmp_vpath));
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ list->list[i].f.link_to_dir = plist->list[i].f.link_to_dir;
+ list->list[i].f.stale_link = plist->list[i].f.stale_link;
+ list->list[i].f.dir_size_computed = plist->list[i].f.dir_size_computed;
+ list->list[i].f.marked = plist->list[i].f.marked;
+ list->list[i].st = plist->list[i].st;
+ list->list[i].name_sort_key = plist->list[i].name_sort_key;
+ list->list[i].extension_sort_key = plist->list[i].extension_sort_key;
+ }
+
+ panel->is_panelized = TRUE;
+ panel_panelize_absolutize_if_needed (panel);
+
+ panel_set_current_by_name (panel, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Change root directory of panelized content.
+ * @param panel file panel
+ * @param new_root new path
+ */
+void
+panel_panelize_change_root (WPanel * panel, const vfs_path_t * new_root)
+{
+ if (panel->panelized_descr == NULL)
+ panel->panelized_descr = panelized_descr_new ();
+ else
+ vfs_path_free (panel->panelized_descr->root_vpath, TRUE);
+
+ panel->panelized_descr->root_vpath = vfs_path_clone (new_root);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Conditionally switches a panel's directory to "/" (root).
+ *
+ * If a panelized panel's listing contain absolute paths, this function
+ * sets the panel's directory to "/". Otherwise it does nothing.
+ *
+ * Rationale:
+ *
+ * This makes tokenized strings like "%d/%p" work. This also makes other
+ * places work where such naive concatenation is done in code (e.g., when
+ * pressing ctrl+shift+enter, for CK_PutCurrentFullSelected).
+ *
+ * When to call:
+ *
+ * You should always call this function after you populate the listing
+ * of a panelized panel.
+ */
+void
+panel_panelize_absolutize_if_needed (WPanel * panel)
+{
+ const dir_list *const list = &panel->dir;
+
+ /* Note: We don't support mixing of absolute and relative paths, which is
+ * why it's ok for us to check only the 1st entry. */
+ if (list->len > 1 && g_path_is_absolute (list->list[1].fname->str))
+ {
+ vfs_path_t *root;
+
+ root = vfs_path_from_str (PATH_SEP_STR);
+ panel_set_cwd (panel, root);
+ if (panel == current_panel)
+ mc_chdir (root);
+ vfs_path_free (root, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_panelize_save (WPanel * panel)
+{
+ int i;
+ dir_list *list = &panel->dir;
+ dir_list *plist;
+
+ panel_panelize_change_root (panel, panel->cwd_vpath);
+
+ plist = &panel->panelized_descr->list;
+
+ if (plist->len > 0)
+ dir_list_clean (plist);
+ if (panel->dir.len == 0)
+ return;
+
+ if (panel->dir.len > plist->size)
+ dir_list_grow (plist, panel->dir.len - plist->size);
+ plist->len = panel->dir.len;
+
+ for (i = 0; i < panel->dir.len; i++)
+ {
+ plist->list[i].fname = mc_g_string_dup (list->list[i].fname);
+ plist->list[i].f.link_to_dir = list->list[i].f.link_to_dir;
+ plist->list[i].f.stale_link = list->list[i].f.stale_link;
+ plist->list[i].f.dir_size_computed = list->list[i].f.dir_size_computed;
+ plist->list[i].f.marked = list->list[i].f.marked;
+ plist->list[i].st = list->list[i].st;
+ plist->list[i].name_sort_key = list->list[i].name_sort_key;
+ plist->list[i].extension_sort_key = list->list[i].extension_sort_key;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_init (void)
+{
+ panel_sort_up_char = mc_skin_get ("widget-panel", "sort-up-char", "'");
+ panel_sort_down_char = mc_skin_get ("widget-panel", "sort-down-char", ".");
+ panel_hiddenfiles_show_char = mc_skin_get ("widget-panel", "hiddenfiles-show-char", ".");
+ panel_hiddenfiles_hide_char = mc_skin_get ("widget-panel", "hiddenfiles-hide-char", ".");
+ panel_history_prev_item_char = mc_skin_get ("widget-panel", "history-prev-item-char", "<");
+ panel_history_next_item_char = mc_skin_get ("widget-panel", "history-next-item-char", ">");
+ panel_history_show_list_char = mc_skin_get ("widget-panel", "history-show-list-char", "^");
+ panel_filename_scroll_left_char =
+ mc_skin_get ("widget-panel", "filename-scroll-left-char", "{");
+ panel_filename_scroll_right_char =
+ mc_skin_get ("widget-panel", "filename-scroll-right-char", "}");
+
+ string_file_name_buffer = g_string_sized_new (MC_MAXFILENAMELEN);
+
+ mc_event_add (MCEVENT_GROUP_FILEMANAGER, "update_panels", event_update_panels, NULL, NULL);
+ mc_event_add (MCEVENT_GROUP_FILEMANAGER, "panel_save_current_file_to_clip_file",
+ panel_save_current_file_to_clip_file, NULL, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_deinit (void)
+{
+ g_free (panel_sort_up_char);
+ g_free (panel_sort_down_char);
+ g_free (panel_hiddenfiles_show_char);
+ g_free (panel_hiddenfiles_hide_char);
+ g_free (panel_history_prev_item_char);
+ g_free (panel_history_next_item_char);
+ g_free (panel_history_show_list_char);
+ g_free (panel_filename_scroll_left_char);
+ g_free (panel_filename_scroll_right_char);
+ g_string_free (string_file_name_buffer, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+panel_cd (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum exact)
+{
+ gboolean res;
+ const vfs_path_t *_new_dir_vpath = new_dir_vpath;
+
+ if (panel->is_panelized)
+ {
+ size_t new_vpath_len;
+
+ new_vpath_len = vfs_path_len (new_dir_vpath);
+ if (vfs_path_equal_len (new_dir_vpath, panel->panelized_descr->root_vpath, new_vpath_len))
+ _new_dir_vpath = panel->panelized_descr->root_vpath;
+ }
+
+ res = panel_do_cd (panel, _new_dir_vpath, exact);
+
+#ifdef HAVE_CHARSET
+ if (res)
+ {
+ const vfs_path_element_t *path_element;
+
+ path_element = vfs_path_get_by_index (panel->cwd_vpath, -1);
+ if (path_element->encoding != NULL)
+ panel->codepage = get_codepage_index (path_element->encoding);
+ else
+ panel->codepage = SELECT_CHARSET_NO_TRANSLATE;
+ }
+#endif /* HAVE_CHARSET */
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/panel.h b/src/filemanager/panel.h
new file mode 100644
index 0000000..5bfc36c
--- /dev/null
+++ b/src/filemanager/panel.h
@@ -0,0 +1,285 @@
+/** \file panel.h
+ * \brief Header: defines WPanel structure
+ */
+
+#ifndef MC__PANEL_H
+#define MC__PANEL_H
+
+#include <inttypes.h> /* uintmax_t */
+#include <limits.h> /* MB_LEN_MAX */
+
+#include "lib/global.h" /* gboolean */
+#include "lib/fs.h" /* MC_MAXPATHLEN */
+#include "lib/strutil.h"
+#include "lib/widget.h" /* Widget */
+#include "lib/filehighlight.h"
+#include "lib/file-entry.h"
+
+#include "dir.h" /* dir_list */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define PANEL(x) ((WPanel *)(x))
+#define DEFAULT_USER_FORMAT "half type name | size | perm"
+
+#define LIST_FORMATS 4
+
+#define UP_KEEPSEL ((char *) -1)
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ list_full, /* Name, size, perm/date */
+ list_brief, /* Name */
+ list_long, /* Like ls -l */
+ list_user /* User defined */
+} list_format_t;
+
+typedef enum
+{
+ frame_full, /* full screen frame */
+ frame_half /* half screen frame */
+} panel_display_t;
+
+typedef enum
+{
+ UP_OPTIMIZE = 0,
+ UP_RELOAD = 1,
+ UP_ONLY_CURRENT = 2
+} panel_update_flags_t;
+
+/* run mode and params */
+enum cd_enum
+{
+ cd_parse_command,
+ cd_exact
+};
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct panel_field_struct
+{
+ const char *id;
+ int min_size;
+ gboolean expands;
+ align_crt_t default_just;
+ const char *hotkey;
+ const char *title_hotkey;
+ gboolean is_user_choice;
+ gboolean use_in_user_format;
+ const char *(*string_fn) (file_entry_t *, int);
+ GCompareFunc sort_routine; /* used by mouse_sort_col() */
+} panel_field_t;
+
+typedef struct
+{
+ dir_list list;
+ vfs_path_t *root_vpath;
+} panelized_descr_t;
+
+typedef struct
+{
+ Widget widget;
+
+ char *name; /* The panel name */
+
+ panel_display_t frame_size; /* half or full frame */
+
+ gboolean active; /* If panel is currently selected */
+ gboolean dirty; /* Should we redisplay the panel? */
+
+ gboolean is_panelized; /* Panelization: special mode, can't reload the file list */
+ panelized_descr_t *panelized_descr; /* Panelization descriptor */
+
+#ifdef HAVE_CHARSET
+ int codepage; /* Panel codepage */
+#endif
+
+ dir_list dir; /* Directory contents */
+ struct stat dir_stat; /* Stat of current dir: used by execute () */
+
+ vfs_path_t *cwd_vpath; /* Current Working Directory */
+ vfs_path_t *lwd_vpath; /* Last Working Directory */
+
+ list_format_t list_format; /* Listing type */
+ GSList *format; /* Display format */
+ char *user_format; /* User format */
+ int list_cols; /* Number of file list columns */
+ int brief_cols; /* Number of columns in case of list_brief format */
+ /* sort */
+ dir_sort_options_t sort_info;
+ const panel_field_t *sort_field;
+
+ int marked; /* Count of marked files */
+ int dirs_marked; /* Count of marked directories */
+ uintmax_t total; /* Bytes in marked files */
+
+ int top; /* The file shown on the top of the panel */
+ int current; /* Index to the currently selected file */
+
+ GSList *status_format; /* Mini status format */
+ gboolean user_mini_status; /* Is user_status_format used */
+ char *user_status_format[LIST_FORMATS]; /* User format for status line */
+
+ file_filter_t filter; /* File name filter */
+
+ struct
+ {
+ char *name; /* Directory history name for history file */
+ GList *list; /* Directory history */
+ GList *current; /* Pointer to the current history item */
+ } dir_history;
+
+ struct
+ {
+ gboolean active;
+ GString *buffer;
+ GString *prev_buffer;
+ char ch[MB_LEN_MAX]; /* Buffer for multi-byte character */
+ int chpoint; /* Point after last characters in @ch */
+ } quick_search;
+
+ int content_shift; /* Number of characters of filename need to skip from left side. */
+ int max_shift; /* Max shift for visible part of current panel */
+} WPanel;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern hook_t *select_file_hook;
+
+extern mc_fhl_t *mc_filehighlight;
+
+/*** declarations of public functions ************************************************************/
+
+WPanel *panel_sized_empty_new (const char *panel_name, int y, int x, int lines, int cols);
+WPanel *panel_sized_with_dir_new (const char *panel_name, int y, int x, int lines, int cols,
+ const vfs_path_t * vpath);
+
+void panel_clean_dir (WPanel * panel);
+
+void panel_reload (WPanel * panel);
+void panel_set_sort_order (WPanel * panel, const panel_field_t * sort_order);
+void panel_re_sort (WPanel * panel);
+
+#ifdef HAVE_CHARSET
+void panel_change_encoding (WPanel * panel);
+vfs_path_t *remove_encoding_from_path (const vfs_path_t * vpath);
+#endif
+
+void update_panels (panel_update_flags_t flags, const char *current_file);
+int set_panel_formats (WPanel * p);
+
+void panel_set_filter (WPanel * panel, const file_filter_t * filter);
+
+void panel_set_current_by_name (WPanel * panel, const char *name);
+
+void unmark_files (WPanel * panel);
+void select_item (WPanel * panel);
+
+void recalculate_panel_summary (WPanel * panel);
+void file_mark (WPanel * panel, int idx, int val);
+void do_file_mark (WPanel * panel, int idx, int val);
+
+gboolean panel_do_cd (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum cd_type);
+gboolean panel_cd (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum cd_type);
+
+gsize panel_get_num_of_sortable_fields (void);
+char **panel_get_sortable_fields (gsize * array_size);
+const panel_field_t *panel_get_field_by_id (const char *name);
+const panel_field_t *panel_get_field_by_title (const char *name);
+const panel_field_t *panel_get_field_by_title_hotkey (const char *name);
+gsize panel_get_num_of_user_possible_fields (void);
+char **panel_get_user_possible_fields (gsize * array_size);
+void panel_set_cwd (WPanel * panel, const vfs_path_t * vpath);
+void panel_set_lwd (WPanel * panel, const vfs_path_t * vpath);
+
+void panel_panelize_cd (void);
+void panel_panelize_change_root (WPanel * panel, const vfs_path_t * new_root);
+void panel_panelize_absolutize_if_needed (WPanel * panel);
+void panel_panelize_save (WPanel * panel);
+
+void panel_init (void);
+void panel_deinit (void);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Empty panel creation.
+ *
+ * @param panel_name name of panel for setup retrieving
+ *
+ * @return new instance of WPanel
+ */
+
+static inline WPanel *
+panel_empty_new (const char *panel_name)
+{
+ /* Unknown sizes of the panel at startup */
+ return panel_sized_empty_new (panel_name, 0, 0, 1, 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Panel creation for specified directory.
+ *
+ * @param panel_name name of panel for setup retrieving
+ * @param vpath working panel directory. If NULL then current directory is used
+ *
+ * @return new instance of WPanel
+ */
+
+static inline WPanel *
+panel_with_dir_new (const char *panel_name, const vfs_path_t * vpath)
+{
+ /* Unknown sizes of the panel at startup */
+ return panel_sized_with_dir_new (panel_name, 0, 0, 1, 1, vpath);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Panel creation.
+ *
+ * @param panel_name name of panel for setup retrieving
+ *
+ * @return new instance of WPanel
+ */
+
+static inline WPanel *
+panel_new (const char *panel_name)
+{
+ return panel_with_dir_new (panel_name, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Panel creation with specified size.
+ *
+ * @param panel_name name of panel for setup retrieving
+ * @param y y coordinate of top-left corner
+ * @param x x coordinate of top-left corner
+ * @param lines vertical size
+ * @param cols horizontal size
+ *
+ * @return new instance of WPanel
+ */
+
+static inline WPanel *
+panel_sized_new (const char *panel_name, int y, int x, int lines, int cols)
+{
+ return panel_sized_with_dir_new (panel_name, y, x, lines, cols, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline file_entry_t *
+panel_current_entry (const WPanel * panel)
+{
+ return &(panel->dir.list[panel->current]);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC__PANEL_H */
diff --git a/src/filemanager/panelize.c b/src/filemanager/panelize.c
new file mode 100644
index 0000000..e90076c
--- /dev/null
+++ b/src/filemanager/panelize.c
@@ -0,0 +1,573 @@
+/*
+ External panelize
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Janne Kukonlehto, 1995
+ Jakub Jelinek, 1995
+ Andrew Borodin <aborodin@vmail.ru> 2011-2023
+
+ 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 panelize.c
+ * \brief Source: External panelization module
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+
+#include "lib/skin.h"
+#include "lib/tty/tty.h"
+#include "lib/vfs/vfs.h"
+#include "lib/mcconfig.h" /* Load/save directories panelize */
+#include "lib/strutil.h"
+#include "lib/widget.h"
+#include "lib/util.h" /* mc_pipe_t */
+
+#include "src/history.h"
+
+#include "filemanager.h" /* current_panel */
+#include "layout.h" /* rotate_dash() */
+#include "panel.h" /* WPanel, dir.h */
+
+#include "panelize.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define UX 3
+#define UY 2
+
+#define B_ADD B_USER
+#define B_REMOVE (B_USER + 1)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ char *command;
+ char *label;
+} panelize_entry_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static WListbox *l_panelize;
+static WDialog *panelize_dlg;
+static int last_listitem;
+static WInput *pname;
+static GSList *panelize = NULL;
+
+static const char *panelize_section = "Panelize";
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panelize_entry_free (gpointer data)
+{
+ panelize_entry_t *entry = (panelize_entry_t *) data;
+
+ g_free (entry->command);
+ g_free (entry->label);
+ g_free (entry);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+panelize_entry_cmp_by_label (gconstpointer a, gconstpointer b)
+{
+ const panelize_entry_t *entry = (const panelize_entry_t *) a;
+ const char *label = (const char *) b;
+
+ return strcmp (entry->label, label);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panelize_entry_add_to_listbox (gpointer data, gpointer user_data)
+{
+ panelize_entry_t *entry = (panelize_entry_t *) data;
+
+ (void) user_data;
+
+ listbox_add_item (l_panelize, LISTBOX_APPEND_AT_END, 0, entry->label, entry, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+update_command (void)
+{
+ if (l_panelize->current != last_listitem)
+ {
+ panelize_entry_t *data = NULL;
+
+ last_listitem = l_panelize->current;
+ listbox_get_current (l_panelize, NULL, (void **) &data);
+ input_assign_text (pname, data->command);
+ pname->point = 0;
+ input_update (pname, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+panelize_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_INIT:
+ group_default_callback (w, NULL, MSG_INIT, 0, NULL);
+ MC_FALLTHROUGH;
+
+ case MSG_NOTIFY: /* MSG_NOTIFY is fired by the listbox to tell us the item has changed. */
+ update_command ();
+ return MSG_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+external_panelize_init (void)
+{
+ struct
+ {
+ int ret_cmd;
+ button_flags_t flags;
+ const char *text;
+ } panelize_but[] =
+ {
+ /* *INDENT-OFF* */
+ { B_ENTER, DEFPUSH_BUTTON, N_("Pane&lize") },
+ { B_REMOVE, NORMAL_BUTTON, N_("&Remove") },
+ { B_ADD, NORMAL_BUTTON, N_("&Add new") },
+ { B_CANCEL, NORMAL_BUTTON, N_("&Cancel") }
+ /* *INDENT-ON* */
+ };
+
+ WGroup *g;
+
+ size_t i;
+ int blen;
+ int panelize_cols;
+ int x, y;
+
+ last_listitem = 0;
+
+ do_refresh ();
+
+ i = G_N_ELEMENTS (panelize_but);
+ blen = i - 1; /* gaps between buttons */
+ while (i-- != 0)
+ {
+#ifdef ENABLE_NLS
+ panelize_but[i].text = _(panelize_but[i].text);
+#endif
+ blen += str_term_width1 (panelize_but[i].text) + 3 + 1;
+ if (panelize_but[i].flags == DEFPUSH_BUTTON)
+ blen += 2;
+ }
+
+ panelize_cols = COLS - 6;
+ panelize_cols = MAX (panelize_cols, blen + 4);
+
+ panelize_dlg =
+ dlg_create (TRUE, 0, 0, 20, panelize_cols, WPOS_CENTER, FALSE, dialog_colors,
+ panelize_callback, NULL, "[External panelize]", _("External panelize"));
+ g = GROUP (panelize_dlg);
+
+ /* add listbox to the dialogs */
+ y = UY;
+ group_add_widget (g, groupbox_new (y++, UX, 12, panelize_cols - UX * 2, ""));
+
+ l_panelize = listbox_new (y, UX + 1, 10, panelize_cols - UX * 2 - 2, FALSE, NULL);
+ g_slist_foreach (panelize, panelize_entry_add_to_listbox, NULL);
+ listbox_set_current (l_panelize, listbox_search_text (l_panelize, _("Other command")));
+ group_add_widget (g, l_panelize);
+
+ y += WIDGET (l_panelize)->rect.lines + 1;
+ group_add_widget (g, label_new (y++, UX, _("Command")));
+ pname =
+ input_new (y++, UX, input_colors, panelize_cols - UX * 2, "", "in",
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_COMMANDS |
+ INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES | INPUT_COMPLETE_CD |
+ INPUT_COMPLETE_SHELL_ESC);
+ group_add_widget (g, pname);
+
+ group_add_widget (g, hline_new (y++, -1, -1));
+
+ x = (panelize_cols - blen) / 2;
+ for (i = 0; i < G_N_ELEMENTS (panelize_but); i++)
+ {
+ WButton *b;
+
+ b = button_new (y, x,
+ panelize_but[i].ret_cmd, panelize_but[i].flags, panelize_but[i].text, NULL);
+ group_add_widget (g, b);
+
+ x += button_get_len (b) + 1;
+ }
+
+ widget_select (WIDGET (l_panelize));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+external_panelize_done (void)
+{
+ widget_destroy (WIDGET (panelize_dlg));
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+add2panelize (char *label, char *command)
+{
+ panelize_entry_t *entry;
+
+ entry = g_try_new (panelize_entry_t, 1);
+ if (entry != NULL)
+ {
+ entry->label = label;
+ entry->command = command;
+
+ panelize = g_slist_insert_sorted (panelize, entry, panelize_entry_cmp_by_label);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+add2panelize_cmd (void)
+{
+ if (!input_is_empty (pname))
+ {
+ char *label;
+
+ label = input_dialog (_("Add to external panelize"),
+ _("Enter command label:"), MC_HISTORY_FM_PANELIZE_ADD, "",
+ INPUT_COMPLETE_NONE);
+ if (label == NULL || *label == '\0')
+ g_free (label);
+ else
+ add2panelize (label, input_get_text (pname));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+remove_from_panelize (panelize_entry_t * entry)
+{
+ if (strcmp (entry->label, _("Other command")) != 0)
+ {
+ panelize = g_slist_remove (panelize, entry);
+ panelize_entry_free (entry);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+do_external_panelize (const char *command)
+{
+ dir_list *list = &current_panel->dir;
+ mc_pipe_t *external;
+ GError *error = NULL;
+ GString *remain_file_name = NULL;
+
+ external = mc_popen (command, TRUE, TRUE, &error);
+ if (external == NULL)
+ {
+ message (D_ERROR, _("External panelize"), "%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ /* Clear the counters and the directory list */
+ panel_clean_dir (current_panel);
+
+ panel_panelize_change_root (current_panel, current_panel->cwd_vpath);
+
+ dir_list_init (list);
+
+ while (TRUE)
+ {
+ GString *line;
+ gboolean ok;
+
+ /* init buffers before call of mc_pread() */
+ external->out.len = MC_PIPE_BUFSIZE;
+ external->err.len = MC_PIPE_BUFSIZE;
+ external->err.null_term = TRUE;
+
+ mc_pread (external, &error);
+
+ if (error != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("External panelize:\n%s"), error->message);
+ g_error_free (error);
+ break;
+ }
+
+ if (external->err.len > 0)
+ message (D_ERROR, MSG_ERROR, _("External panelize:\n%s"), external->err.buf);
+
+ if (external->out.len == MC_PIPE_STREAM_EOF)
+ break;
+
+ if (external->out.len == 0)
+ continue;
+
+ if (external->out.len == MC_PIPE_ERROR_READ)
+ {
+ message (D_ERROR, MSG_ERROR,
+ _("External panelize:\nfailed to read data from child stdout:\n%s"),
+ unix_error_string (external->out.error));
+ break;
+ }
+
+ ok = TRUE;
+
+ while (ok && (line = mc_pstream_get_string (&external->out)) != NULL)
+ {
+ char *name;
+ gboolean link_to_dir, stale_link;
+ struct stat st;
+
+ /* handle a \n-separated file list */
+
+ if (line->str[line->len - 1] == '\n')
+ {
+ /* entire file name or last chunk */
+
+ g_string_truncate (line, line->len - 1);
+
+ /* join filename chunks */
+ if (remain_file_name != NULL)
+ {
+ g_string_append_len (remain_file_name, line->str, line->len);
+ g_string_free (line, TRUE);
+ line = remain_file_name;
+ remain_file_name = NULL;
+ }
+ }
+ else
+ {
+ /* first or middle chunk of file name */
+
+ if (remain_file_name == NULL)
+ remain_file_name = line;
+ else
+ {
+ g_string_append_len (remain_file_name, line->str, line->len);
+ g_string_free (line, TRUE);
+ }
+
+ continue;
+ }
+
+ name = line->str;
+
+ if (name[0] == '.' && IS_PATH_SEP (name[1]))
+ name += 2;
+
+ if (handle_path (name, &st, &link_to_dir, &stale_link))
+ {
+ ok = dir_list_append (list, name, &st, link_to_dir, stale_link);
+
+ if (ok)
+ {
+ file_mark (current_panel, list->len - 1, 0);
+
+ if ((list->len & 31) == 0)
+ rotate_dash (TRUE);
+ }
+ }
+
+ g_string_free (line, TRUE);
+ }
+ }
+
+ if (remain_file_name != NULL)
+ g_string_free (remain_file_name, TRUE);
+
+ mc_pclose (external, NULL);
+
+ current_panel->is_panelized = TRUE;
+ panel_panelize_absolutize_if_needed (current_panel);
+
+ panel_set_current_by_name (current_panel, NULL);
+ panel_re_sort (current_panel);
+ rotate_dash (FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+external_panelize_cmd (void)
+{
+ if (!vfs_current_is_local ())
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot run external panelize in a non-local directory"));
+ return;
+ }
+
+ external_panelize_init ();
+
+ /* display file info */
+ tty_setcolor (SELECTED_COLOR);
+
+ switch (dlg_run (panelize_dlg))
+ {
+ case B_CANCEL:
+ break;
+
+ case B_ADD:
+ add2panelize_cmd ();
+ break;
+
+ case B_REMOVE:
+ {
+ panelize_entry_t *entry;
+
+ listbox_get_current (l_panelize, NULL, (void **) &entry);
+ remove_from_panelize (entry);
+ break;
+ }
+
+ case B_ENTER:
+ if (!input_is_empty (pname))
+ {
+ char *cmd;
+
+ cmd = input_get_text (pname);
+ widget_destroy (WIDGET (panelize_dlg));
+ do_external_panelize (cmd);
+ g_free (cmd);
+ repaint_screen ();
+ return;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ external_panelize_done ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+external_panelize_load (void)
+{
+ char **keys;
+
+ keys = mc_config_get_keys (mc_global.main_config, panelize_section, NULL);
+
+ add2panelize (g_strdup (_("Other command")), g_strdup (""));
+
+ if (*keys == NULL)
+ {
+ add2panelize (g_strdup (_("Modified git files")), g_strdup ("git ls-files --modified"));
+ add2panelize (g_strdup (_("Find rejects after patching")),
+ g_strdup ("find . -name \\*.rej -print"));
+ add2panelize (g_strdup (_("Find *.orig after patching")),
+ g_strdup ("find . -name \\*.orig -print"));
+ add2panelize (g_strdup (_("Find SUID and SGID programs")),
+ g_strdup
+ ("find . \\( \\( -perm -04000 -a -perm /011 \\) -o \\( -perm -02000 -a -perm /01 \\) \\) -print"));
+ }
+ else
+ {
+ GIConv conv;
+ char **profile_keys;
+
+ conv = str_crt_conv_from ("UTF-8");
+
+ for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
+ {
+ GString *buffer;
+
+ if (mc_global.utf8_display || conv == INVALID_CONV)
+ buffer = g_string_new (*profile_keys);
+ else
+ {
+ buffer = g_string_new ("");
+ if (str_convert (conv, *profile_keys, buffer) == ESTR_FAILURE)
+ g_string_assign (buffer, *profile_keys);
+ }
+
+ add2panelize (g_string_free (buffer, FALSE),
+ mc_config_get_string (mc_global.main_config, panelize_section,
+ *profile_keys, ""));
+ }
+
+ str_close_conv (conv);
+ }
+
+ g_strfreev (keys);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+external_panelize_save (void)
+{
+ GSList *l;
+
+ mc_config_del_group (mc_global.main_config, panelize_section);
+
+ for (l = panelize; l != NULL; l = g_slist_next (l))
+ {
+ panelize_entry_t *current = (panelize_entry_t *) l->data;
+
+ if (strcmp (current->label, _("Other command")) != 0)
+ mc_config_set_string (mc_global.main_config,
+ panelize_section, current->label, current->command);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+external_panelize_free (void)
+{
+ g_clear_slist (&panelize, panelize_entry_free);
+ panelize = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/panelize.h b/src/filemanager/panelize.h
new file mode 100644
index 0000000..eacf976
--- /dev/null
+++ b/src/filemanager/panelize.h
@@ -0,0 +1,25 @@
+/** \file panelize.h
+ * \brief Header: External panelization module
+ */
+
+#ifndef MC__PANELIZE_H
+#define MC__PANELIZE_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void external_panelize_cmd (void);
+void external_panelize_load (void);
+void external_panelize_save (void);
+void external_panelize_free (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__PANELIZE_H */
diff --git a/src/filemanager/tree.c b/src/filemanager/tree.c
new file mode 100644
index 0000000..fd50407
--- /dev/null
+++ b/src/filemanager/tree.c
@@ -0,0 +1,1345 @@
+/*
+ Directory tree browser for the Midnight Commander
+ This module has been converted to be a widget.
+
+ The program load and saves the tree each time the tree widget is
+ created and destroyed. This is required for the future vfs layer,
+ it will be possible to have tree views over virtual file systems.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Janne Kukonlehto, 1994, 1996
+ Norbert Warmuth, 1997
+ Miguel de Icaza, 1996, 1999
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2022
+
+ 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 tree.c
+ * \brief Source: directory tree browser
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h"
+#include "lib/skin.h"
+#include "lib/vfs/vfs.h"
+#include "lib/fileloc.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+#include "lib/event.h" /* mc_event_raise() */
+
+#include "src/setup.h" /* confirm_delete, panels_options */
+#include "src/keymap.h"
+#include "src/history.h"
+
+#include "dir.h"
+#include "filemanager.h" /* the_menubar */
+#include "file.h" /* copy_dir_dir(), move_dir_dir(), erase_dir() */
+#include "layout.h" /* command_prompt */
+#include "treestore.h"
+#include "cmd.h"
+#include "filegui.h"
+#include "cd.h" /* cd_error_message() */
+
+#include "tree.h"
+
+/*** global variables ****************************************************************************/
+
+/* The pointer to the tree */
+WTree *the_tree = NULL;
+
+/* If this is true, then when browsing the tree the other window will
+ * automatically reload it's directory with the contents of the currently
+ * selected directory.
+ */
+gboolean xtree_mode = FALSE;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define tlines(t) (t->is_panel ? WIDGET (t)->rect.lines - 2 - \
+ (panels_options.show_mini_info ? 2 : 0) : WIDGET (t)->rect.lines)
+
+/*** file scope type declarations ****************************************************************/
+
+struct WTree
+{
+ Widget widget;
+ struct TreeStore *store;
+ tree_entry *selected_ptr; /* The selected directory */
+ GString *search_buffer; /* Current search string */
+ tree_entry **tree_shown; /* Entries currently on screen */
+ gboolean is_panel; /* panel or plain widget flag */
+ gboolean searching; /* Are we on searching mode? */
+ int topdiff; /* The difference between the topmost
+ shown and the selected */
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static void tree_rescan (void *data);
+
+/*** file scope variables ************************************************************************/
+
+/* Specifies the display mode: 1d or 2d */
+static gboolean tree_navigation_flag = FALSE;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static tree_entry *
+back_ptr (tree_entry * ptr, int *count)
+{
+ int i;
+
+ for (i = 0; ptr != NULL && ptr->prev != NULL && i < *count; ptr = ptr->prev, i++)
+ ;
+
+ *count = i;
+ return ptr;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static tree_entry *
+forw_ptr (tree_entry * ptr, int *count)
+{
+ int i;
+
+ for (i = 0; ptr != NULL && ptr->next != NULL && i < *count; ptr = ptr->next, i++)
+ ;
+
+ *count = i;
+ return ptr;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+remove_callback (tree_entry * entry, void *data)
+{
+ WTree *tree = data;
+
+ if (tree->selected_ptr == entry)
+ {
+ if (tree->selected_ptr->next != NULL)
+ tree->selected_ptr = tree->selected_ptr->next;
+ else
+ tree->selected_ptr = tree->selected_ptr->prev;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Save the ${XDG_CACHE_HOME}/mc/Tree file */
+
+static void
+save_tree (WTree * tree)
+{
+ int error;
+
+ (void) tree;
+
+ error = tree_store_save ();
+ if (error != 0)
+ {
+ char *tree_name;
+
+ tree_name = mc_config_get_full_path (MC_TREESTORE_FILE);
+ fprintf (stderr, _("Cannot open the %s file for writing:\n%s\n"), tree_name,
+ unix_error_string (error));
+ g_free (tree_name);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_remove_entry (WTree * tree, const vfs_path_t * name_vpath)
+{
+ (void) tree;
+ tree_store_remove_entry (name_vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_destroy (WTree * tree)
+{
+ tree_store_remove_entry_remove_hook (remove_callback);
+ save_tree (tree);
+
+ MC_PTR_FREE (tree->tree_shown);
+ g_string_free (tree->search_buffer, TRUE);
+ tree->selected_ptr = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Loads the .mc.tree file */
+
+static void
+load_tree (WTree * tree)
+{
+ vfs_path_t *vpath;
+
+ tree_store_load ();
+
+ tree->selected_ptr = tree->store->tree_first;
+ vpath = vfs_path_from_str (mc_config_get_home_dir ());
+ tree_chdir (tree, vpath);
+ vfs_path_free (vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_show_mini_info (WTree * tree, int tree_lines, int tree_cols)
+{
+ Widget *w = WIDGET (tree);
+ int line;
+
+ /* Show mini info */
+ if (tree->is_panel)
+ {
+ if (!panels_options.show_mini_info)
+ return;
+ line = tree_lines + 2;
+ }
+ else
+ line = tree_lines + 1;
+
+ if (tree->searching)
+ {
+ /* Show search string */
+ tty_setcolor (INPUT_COLOR);
+ tty_draw_hline (w->rect.y + line, w->rect.x + 1, ' ', tree_cols);
+ widget_gotoyx (w, line, 1);
+ tty_print_char (PATH_SEP);
+ tty_print_string (str_fit_to_term (tree->search_buffer->str, tree_cols - 2, J_LEFT_FIT));
+ tty_print_char (' ');
+ }
+ else
+ {
+ /* Show full name of selected directory */
+
+ const int *colors;
+
+ colors = widget_get_colors (w);
+ tty_setcolor (tree->is_panel ? NORMAL_COLOR : colors[DLG_COLOR_NORMAL]);
+ tty_draw_hline (w->rect.y + line, w->rect.x + 1, ' ', tree_cols);
+ widget_gotoyx (w, line, 1);
+ tty_print_string (str_fit_to_term
+ (vfs_path_as_str (tree->selected_ptr->name), tree_cols, J_LEFT_FIT));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+show_tree (WTree * tree)
+{
+ Widget *w = WIDGET (tree);
+ tree_entry *current;
+ int i, j;
+ int topsublevel = 0;
+ int x = 0, y = 0;
+ int tree_lines, tree_cols;
+
+ /* Initialize */
+ tree_lines = tlines (tree);
+ tree_cols = w->rect.cols;
+
+ widget_gotoyx (w, y, x);
+ if (tree->is_panel)
+ {
+ tree_cols -= 2;
+ x = y = 1;
+ }
+
+ g_free (tree->tree_shown);
+ tree->tree_shown = g_new0 (tree_entry *, tree_lines);
+
+ if (tree->store->tree_first != NULL)
+ topsublevel = tree->store->tree_first->sublevel;
+
+ if (tree->selected_ptr == NULL)
+ {
+ tree->selected_ptr = tree->store->tree_first;
+ tree->topdiff = 0;
+ }
+ current = tree->selected_ptr;
+
+ /* Calculate the directory which is to be shown on the topmost line */
+ if (!tree_navigation_flag)
+ current = back_ptr (current, &tree->topdiff);
+ else
+ {
+ i = 0;
+
+ while (current->prev != NULL && i < tree->topdiff)
+ {
+ current = current->prev;
+
+ if (current->sublevel < tree->selected_ptr->sublevel)
+ {
+ if (vfs_path_equal (current->name, tree->selected_ptr->name))
+ i++;
+ }
+ else if (current->sublevel == tree->selected_ptr->sublevel)
+ {
+ const char *cname;
+
+ cname = vfs_path_as_str (current->name);
+ for (j = strlen (cname) - 1; !IS_PATH_SEP (cname[j]); j--)
+ ;
+ if (vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
+ i++;
+ }
+ else if (current->sublevel == tree->selected_ptr->sublevel + 1)
+ {
+ j = vfs_path_len (tree->selected_ptr->name);
+ if (j > 1 && vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
+ i++;
+ }
+ }
+ tree->topdiff = i;
+ }
+
+ /* Loop for every line */
+ for (i = 0; i < tree_lines; i++)
+ {
+ const int *colors;
+
+ colors = widget_get_colors (w);
+ tty_setcolor (tree->is_panel ? NORMAL_COLOR : colors[DLG_COLOR_NORMAL]);
+
+ /* Move to the beginning of the line */
+ tty_draw_hline (w->rect.y + y + i, w->rect.x + x, ' ', tree_cols);
+
+ if (current == NULL)
+ continue;
+
+ if (tree->is_panel)
+ {
+ gboolean selected;
+
+ selected = widget_get_state (w, WST_FOCUSED) && current == tree->selected_ptr;
+ tty_setcolor (selected ? SELECTED_COLOR : NORMAL_COLOR);
+ }
+ else
+ {
+ int idx = current == tree->selected_ptr ? DLG_COLOR_FOCUS : DLG_COLOR_NORMAL;
+
+ tty_setcolor (colors[idx]);
+ }
+
+ tree->tree_shown[i] = current;
+ if (current->sublevel == topsublevel)
+ /* Show full name */
+ tty_print_string (str_fit_to_term
+ (vfs_path_as_str (current->name),
+ tree_cols + (tree->is_panel ? 0 : 1), J_LEFT_FIT));
+ else
+ {
+ /* Sub level directory */
+ tty_set_alt_charset (TRUE);
+
+ /* Output branch parts */
+ for (j = 0; j < current->sublevel - topsublevel - 1; j++)
+ {
+ if (tree_cols - 8 - 3 * j < 9)
+ break;
+ tty_print_char (' ');
+ if ((current->submask & (1 << (j + topsublevel + 1))) != 0)
+ tty_print_char (ACS_VLINE);
+ else
+ tty_print_char (' ');
+ tty_print_char (' ');
+ }
+ tty_print_char (' ');
+ j++;
+ if (current->next == NULL || (current->next->submask & (1 << current->sublevel)) == 0)
+ tty_print_char (ACS_LLCORNER);
+ else
+ tty_print_char (ACS_LTEE);
+ tty_print_char (ACS_HLINE);
+ tty_set_alt_charset (FALSE);
+
+ /* Show sub-name */
+ tty_print_char (' ');
+ tty_print_string (str_fit_to_term
+ (current->subname, tree_cols - x - 3 * j, J_LEFT_FIT));
+ }
+
+ /* Calculate the next value for current */
+ current = current->next;
+ if (tree_navigation_flag)
+ for (; current != NULL; current = current->next)
+ {
+ if (current->sublevel < tree->selected_ptr->sublevel)
+ {
+ if (vfs_path_equal_len (current->name, tree->selected_ptr->name,
+ vfs_path_len (current->name)))
+ break;
+ }
+ else if (current->sublevel == tree->selected_ptr->sublevel)
+ {
+ const char *cname;
+
+ cname = vfs_path_as_str (current->name);
+ for (j = strlen (cname) - 1; !IS_PATH_SEP (cname[j]); j--)
+ ;
+ if (vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
+ break;
+ }
+ else if (current->sublevel == tree->selected_ptr->sublevel + 1
+ && vfs_path_len (tree->selected_ptr->name) > 1)
+ {
+ if (vfs_path_equal_len (current->name, tree->selected_ptr->name,
+ vfs_path_len (tree->selected_ptr->name)))
+ break;
+ }
+ }
+ }
+
+ tree_show_mini_info (tree, tree_lines, tree_cols);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_check_focus (WTree * tree)
+{
+ if (tree->topdiff < 3)
+ tree->topdiff = 3;
+ else if (tree->topdiff >= tlines (tree) - 3)
+ tree->topdiff = tlines (tree) - 3 - 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move_backward (WTree * tree, int i)
+{
+ if (!tree_navigation_flag)
+ tree->selected_ptr = back_ptr (tree->selected_ptr, &i);
+ else
+ {
+ tree_entry *current;
+ int j = 0;
+
+ current = tree->selected_ptr;
+ while (j < i && current->prev != NULL
+ && current->prev->sublevel >= tree->selected_ptr->sublevel)
+ {
+ current = current->prev;
+ if (current->sublevel == tree->selected_ptr->sublevel)
+ {
+ tree->selected_ptr = current;
+ j++;
+ }
+ }
+ i = j;
+ }
+
+ tree->topdiff -= i;
+ tree_check_focus (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move_forward (WTree * tree, int i)
+{
+ if (!tree_navigation_flag)
+ tree->selected_ptr = forw_ptr (tree->selected_ptr, &i);
+ else
+ {
+ tree_entry *current;
+ int j = 0;
+
+ current = tree->selected_ptr;
+ while (j < i && current->next != NULL
+ && current->next->sublevel >= tree->selected_ptr->sublevel)
+ {
+ current = current->next;
+ if (current->sublevel == tree->selected_ptr->sublevel)
+ {
+ tree->selected_ptr = current;
+ j++;
+ }
+ }
+ i = j;
+ }
+
+ tree->topdiff += i;
+ tree_check_focus (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move_to_child (WTree * tree)
+{
+ tree_entry *current;
+
+ /* Do we have a starting point? */
+ if (tree->selected_ptr == NULL)
+ return;
+
+ /* Take the next entry */
+ current = tree->selected_ptr->next;
+ /* Is it the child of the selected entry */
+ if (current != NULL && current->sublevel > tree->selected_ptr->sublevel)
+ {
+ /* Yes -> select this entry */
+ tree->selected_ptr = current;
+ tree->topdiff++;
+ tree_check_focus (tree);
+ }
+ else
+ {
+ /* No -> rescan and try again */
+ tree_rescan (tree);
+ current = tree->selected_ptr->next;
+ if (current != NULL && current->sublevel > tree->selected_ptr->sublevel)
+ {
+ tree->selected_ptr = current;
+ tree->topdiff++;
+ tree_check_focus (tree);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tree_move_to_parent (WTree * tree)
+{
+ tree_entry *current;
+ tree_entry *old;
+
+ if (tree->selected_ptr == NULL)
+ return FALSE;
+
+ old = tree->selected_ptr;
+
+ for (current = tree->selected_ptr->prev;
+ current != NULL && current->sublevel >= tree->selected_ptr->sublevel;
+ current = current->prev)
+ tree->topdiff--;
+
+ if (current == NULL)
+ current = tree->store->tree_first;
+ tree->selected_ptr = current;
+ tree_check_focus (tree);
+ return tree->selected_ptr != old;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move_to_top (WTree * tree)
+{
+ tree->selected_ptr = tree->store->tree_first;
+ tree->topdiff = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move_to_bottom (WTree * tree)
+{
+ tree->selected_ptr = tree->store->tree_last;
+ tree->topdiff = tlines (tree) - 3 - 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_chdir_sel (WTree * tree)
+{
+ if (tree->is_panel)
+ {
+ WPanel *p;
+
+ p = change_panel ();
+
+ if (panel_cd (p, tree->selected_ptr->name, cd_exact))
+ select_item (p);
+ else
+ cd_error_message (vfs_path_as_str (tree->selected_ptr->name));
+
+ widget_draw (WIDGET (p));
+ (void) change_panel ();
+ show_tree (tree);
+ }
+ else
+ {
+ WDialog *h = DIALOG (WIDGET (tree)->owner);
+
+ h->ret_value = B_ENTER;
+ dlg_close (h);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+maybe_chdir (WTree * tree)
+{
+ if (xtree_mode && tree->is_panel && is_idle ())
+ tree_chdir_sel (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Search tree for text */
+
+static gboolean
+search_tree (WTree * tree, const GString * text)
+{
+ tree_entry *current = tree->selected_ptr;
+ gboolean wrapped = FALSE;
+ gboolean found = FALSE;
+
+ while (!found && (!wrapped || current != tree->selected_ptr))
+ if (strncmp (current->subname, text->str, text->len) == 0)
+ {
+ tree->selected_ptr = current;
+ found = TRUE;
+ }
+ else
+ {
+ current = current->next;
+ if (current == NULL)
+ {
+ current = tree->store->tree_first;
+ wrapped = TRUE;
+ }
+
+ tree->topdiff++;
+ }
+
+ tree_check_focus (tree);
+ return found;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_do_search (WTree * tree, int key)
+{
+ /* TODO: support multi-byte characters, see do_search() in panel.c */
+
+ if (tree->search_buffer->len != 0 && key == KEY_BACKSPACE)
+ g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1);
+ else if (key != 0)
+ g_string_append_c (tree->search_buffer, (gchar) key);
+
+ if (!search_tree (tree, tree->search_buffer))
+ g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1);
+
+ show_tree (tree);
+ maybe_chdir (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_rescan (void *data)
+{
+ WTree *tree = data;
+ vfs_path_t *old_vpath;
+
+ old_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
+ if (old_vpath == NULL)
+ return;
+
+ if (tree->selected_ptr != NULL && mc_chdir (tree->selected_ptr->name) == 0)
+ {
+ int ret;
+
+ tree_store_rescan (tree->selected_ptr->name);
+ ret = mc_chdir (old_vpath);
+ (void) ret;
+ }
+ vfs_path_free (old_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_forget (void *data)
+{
+ WTree *tree = data;
+
+ if (tree->selected_ptr != NULL)
+ tree_remove_entry (tree, tree->selected_ptr->name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_copy (WTree * tree, const char *default_dest)
+{
+ char msg[BUF_MEDIUM];
+ char *dest;
+
+ if (tree->selected_ptr == NULL)
+ return;
+
+ g_snprintf (msg, sizeof (msg), _("Copy \"%s\" directory to:"),
+ str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50));
+ dest = input_expand_dialog (Q_ ("DialogTitle|Copy"),
+ msg, MC_HISTORY_FM_TREE_COPY, default_dest,
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
+
+ if (dest != NULL && *dest != '\0')
+ {
+ file_op_context_t *ctx;
+ file_op_total_context_t *tctx;
+
+ ctx = file_op_context_new (OP_COPY);
+ tctx = file_op_total_context_new ();
+ file_op_context_create_ui (ctx, FALSE, FILEGUI_DIALOG_MULTI_ITEM);
+ tctx->ask_overwrite = FALSE;
+ copy_dir_dir (tctx, ctx, vfs_path_as_str (tree->selected_ptr->name), dest, TRUE, FALSE,
+ FALSE, NULL);
+ file_op_total_context_destroy (tctx);
+ file_op_context_destroy (ctx);
+ }
+
+ g_free (dest);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move (WTree * tree, const char *default_dest)
+{
+ char msg[BUF_MEDIUM];
+ char *dest;
+
+ if (tree->selected_ptr == NULL)
+ return;
+
+ g_snprintf (msg, sizeof (msg), _("Move \"%s\" directory to:"),
+ str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50));
+ dest =
+ input_expand_dialog (Q_ ("DialogTitle|Move"), msg, MC_HISTORY_FM_TREE_MOVE, default_dest,
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
+
+ if (dest != NULL && *dest != '\0')
+ {
+ vfs_path_t *dest_vpath;
+ struct stat buf;
+
+ dest_vpath = vfs_path_from_str (dest);
+
+ if (mc_stat (dest_vpath, &buf))
+ message (D_ERROR, MSG_ERROR, _("Cannot stat the destination\n%s"),
+ unix_error_string (errno));
+ else if (!S_ISDIR (buf.st_mode))
+ file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), dest);
+ else
+ {
+ file_op_context_t *ctx;
+ file_op_total_context_t *tctx;
+
+ ctx = file_op_context_new (OP_MOVE);
+ tctx = file_op_total_context_new ();
+ file_op_context_create_ui (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM);
+ move_dir_dir (tctx, ctx, vfs_path_as_str (tree->selected_ptr->name), dest);
+ file_op_total_context_destroy (tctx);
+ file_op_context_destroy (ctx);
+ }
+
+ vfs_path_free (dest_vpath, TRUE);
+ }
+
+ g_free (dest);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static void
+tree_mkdir (WTree * tree)
+{
+ char old_dir[MC_MAXPATHLEN];
+
+ if (tree->selected_ptr == NULL || chdir (tree->selected_ptr->name) != 0)
+ return;
+ /* FIXME
+ mkdir_cmd (tree);
+ */
+ tree_rescan (tree);
+ chdir (old_dir);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_rmdir (void *data)
+{
+ WTree *tree = data;
+ file_op_context_t *ctx;
+ file_op_total_context_t *tctx;
+
+ if (tree->selected_ptr == NULL)
+ return;
+
+ if (confirm_delete)
+ {
+ char *buf;
+ int result;
+
+ buf = g_strdup_printf (_("Delete %s?"), vfs_path_as_str (tree->selected_ptr->name));
+
+ result = query_dialog (Q_ ("DialogTitle|Delete"), buf, D_ERROR, 2, _("&Yes"), _("&No"));
+ g_free (buf);
+ if (result != 0)
+ return;
+ }
+
+ ctx = file_op_context_new (OP_DELETE);
+ tctx = file_op_total_context_new ();
+
+ file_op_context_create_ui (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM);
+ if (erase_dir (tctx, ctx, tree->selected_ptr->name) == FILE_CONT)
+ tree_forget (tree);
+ file_op_total_context_destroy (tctx);
+ file_op_context_destroy (ctx);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+tree_move_up (WTree * tree)
+{
+ tree_move_backward (tree, 1);
+ show_tree (tree);
+ maybe_chdir (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+tree_move_down (WTree * tree)
+{
+ tree_move_forward (tree, 1);
+ show_tree (tree);
+ maybe_chdir (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+tree_move_home (WTree * tree)
+{
+ tree_move_to_top (tree);
+ show_tree (tree);
+ maybe_chdir (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+tree_move_end (WTree * tree)
+{
+ tree_move_to_bottom (tree);
+ show_tree (tree);
+ maybe_chdir (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move_pgup (WTree * tree)
+{
+ tree_move_backward (tree, tlines (tree) - 1);
+ show_tree (tree);
+ maybe_chdir (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move_pgdn (WTree * tree)
+{
+ tree_move_forward (tree, tlines (tree) - 1);
+ show_tree (tree);
+ maybe_chdir (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tree_move_left (WTree * tree)
+{
+ gboolean v = FALSE;
+
+ if (tree_navigation_flag)
+ {
+ v = tree_move_to_parent (tree);
+ show_tree (tree);
+ maybe_chdir (tree);
+ }
+
+ return v;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tree_move_right (WTree * tree)
+{
+ gboolean v = FALSE;
+
+ if (tree_navigation_flag)
+ {
+ tree_move_to_child (tree);
+ show_tree (tree);
+ maybe_chdir (tree);
+ v = TRUE;
+ }
+
+ return v;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_start_search (WTree * tree)
+{
+ if (tree->searching)
+ {
+ if (tree->selected_ptr == tree->store->tree_last)
+ tree_move_to_top (tree);
+ else
+ {
+ gboolean i;
+
+ /* set navigation mode temporarily to 'Static' because in
+ * dynamic navigation mode tree_move_forward will not move
+ * to a lower sublevel if necessary (sequent searches must
+ * start with the directory followed the last found directory)
+ */
+ i = tree_navigation_flag;
+ tree_navigation_flag = FALSE;
+ tree_move_forward (tree, 1);
+ tree_navigation_flag = i;
+ }
+ tree_do_search (tree, 0);
+ }
+ else
+ {
+ tree->searching = TRUE;
+ g_string_set_size (tree->search_buffer, 0);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_toggle_navig (WTree * tree)
+{
+ Widget *w = WIDGET (tree);
+ WButtonBar *b;
+
+ tree_navigation_flag = !tree_navigation_flag;
+
+ b = buttonbar_find (DIALOG (w->owner));
+ buttonbar_set_label (b, 4,
+ tree_navigation_flag ? Q_ ("ButtonBar|Static") : Q_ ("ButtonBar|Dynamc"),
+ w->keymap, w);
+ widget_draw (WIDGET (b));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+tree_execute_cmd (WTree * tree, long command)
+{
+ cb_ret_t res = MSG_HANDLED;
+
+ if (command != CK_Search)
+ tree->searching = FALSE;
+
+ switch (command)
+ {
+ case CK_Help:
+ {
+ ev_help_t event_data = { NULL, "[Directory Tree]" };
+ mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
+ }
+ break;
+ case CK_Forget:
+ tree_forget (tree);
+ break;
+ case CK_ToggleNavigation:
+ tree_toggle_navig (tree);
+ break;
+ case CK_Copy:
+ tree_copy (tree, "");
+ break;
+ case CK_Move:
+ tree_move (tree, "");
+ break;
+ case CK_Up:
+ tree_move_up (tree);
+ break;
+ case CK_Down:
+ tree_move_down (tree);
+ break;
+ case CK_Top:
+ tree_move_home (tree);
+ break;
+ case CK_Bottom:
+ tree_move_end (tree);
+ break;
+ case CK_PageUp:
+ tree_move_pgup (tree);
+ break;
+ case CK_PageDown:
+ tree_move_pgdn (tree);
+ break;
+ case CK_Enter:
+ tree_chdir_sel (tree);
+ break;
+ case CK_Reread:
+ tree_rescan (tree);
+ break;
+ case CK_Search:
+ tree_start_search (tree);
+ break;
+ case CK_Delete:
+ tree_rmdir (tree);
+ break;
+ case CK_Quit:
+ if (!tree->is_panel)
+ dlg_close (DIALOG (WIDGET (tree)->owner));
+ return res;
+ default:
+ res = MSG_NOT_HANDLED;
+ }
+
+ show_tree (tree);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+tree_key (WTree * tree, int key)
+{
+ long command;
+
+ if (is_abort_char (key))
+ {
+ if (tree->is_panel)
+ {
+ tree->searching = FALSE;
+ show_tree (tree);
+ return MSG_HANDLED; /* eat abort char */
+ }
+ /* modal tree dialog: let upper layer see the
+ abort character and close the dialog */
+ return MSG_NOT_HANDLED;
+ }
+
+ if (tree->searching && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
+ {
+ tree_do_search (tree, key);
+ show_tree (tree);
+ return MSG_HANDLED;
+ }
+
+ command = widget_lookup_key (WIDGET (tree), key);
+ switch (command)
+ {
+ case CK_IgnoreKey:
+ break;
+ case CK_Left:
+ return tree_move_left (tree) ? MSG_HANDLED : MSG_NOT_HANDLED;
+ case CK_Right:
+ return tree_move_right (tree) ? MSG_HANDLED : MSG_NOT_HANDLED;
+ default:
+ tree_execute_cmd (tree, command);
+ return MSG_HANDLED;
+ }
+
+ /* Do not eat characters not meant for the tree below ' ' (e.g. C-l). */
+ if (!command_prompt && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
+ {
+ tree_start_search (tree);
+ tree_do_search (tree, key);
+ return MSG_HANDLED;
+ }
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_frame (WDialog * h, WTree * tree)
+{
+ Widget *w = WIDGET (tree);
+
+ (void) h;
+
+ tty_setcolor (NORMAL_COLOR);
+ widget_erase (w);
+ if (tree->is_panel)
+ {
+ const char *title = _("Directory tree");
+ const int len = str_term_width1 (title);
+
+ tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE);
+
+ widget_gotoyx (w, 0, (w->rect.cols - len - 2) / 2);
+ tty_printf (" %s ", title);
+
+ if (panels_options.show_mini_info)
+ {
+ int y;
+
+ y = w->rect.lines - 3;
+ widget_gotoyx (w, y, 0);
+ tty_print_alt_char (ACS_LTEE, FALSE);
+ widget_gotoyx (w, y, w->rect.cols - 1);
+ tty_print_alt_char (ACS_RTEE, FALSE);
+ tty_draw_hline (w->rect.y + y, w->rect.x + 1, ACS_HLINE, w->rect.cols - 2);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+tree_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WTree *tree = (WTree *) w;
+ WDialog *h = DIALOG (w->owner);
+ WButtonBar *b;
+
+ switch (msg)
+ {
+ case MSG_DRAW:
+ tree_frame (h, tree);
+ show_tree (tree);
+ if (widget_get_state (w, WST_FOCUSED))
+ {
+ b = buttonbar_find (h);
+ widget_draw (WIDGET (b));
+ }
+ return MSG_HANDLED;
+
+ case MSG_FOCUS:
+ b = buttonbar_find (h);
+ buttonbar_set_label (b, 1, Q_ ("ButtonBar|Help"), w->keymap, w);
+ buttonbar_set_label (b, 2, Q_ ("ButtonBar|Rescan"), w->keymap, w);
+ buttonbar_set_label (b, 3, Q_ ("ButtonBar|Forget"), w->keymap, w);
+ buttonbar_set_label (b, 4, tree_navigation_flag ? Q_ ("ButtonBar|Static")
+ : Q_ ("ButtonBar|Dynamc"), w->keymap, w);
+ buttonbar_set_label (b, 5, Q_ ("ButtonBar|Copy"), w->keymap, w);
+ buttonbar_set_label (b, 6, Q_ ("ButtonBar|RenMov"), w->keymap, w);
+#if 0
+ /* FIXME: mkdir is currently defunct */
+ buttonbar_set_label (b, 7, Q_ ("ButtonBar|Mkdir"), w->keymap, w);
+#else
+ buttonbar_clear_label (b, 7, w);
+#endif
+ buttonbar_set_label (b, 8, Q_ ("ButtonBar|Rmdir"), w->keymap, w);
+
+ return MSG_HANDLED;
+
+ case MSG_UNFOCUS:
+ tree->searching = FALSE;
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ return tree_key (tree, parm);
+
+ case MSG_ACTION:
+ /* command from buttonbar */
+ return tree_execute_cmd (tree, parm);
+
+ case MSG_DESTROY:
+ tree_destroy (tree);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Mouse callback
+ */
+static void
+tree_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WTree *tree = (WTree *) w;
+ int y;
+
+ y = event->y;
+ if (tree->is_panel)
+ y--;
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ /* rest of the upper frame - call menu */
+ if (tree->is_panel && event->y == WIDGET (w->owner)->rect.y)
+ {
+ /* return MOU_UNHANDLED */
+ event->result.abort = TRUE;
+ }
+ else if (!widget_get_state (w, WST_FOCUSED))
+ (void) change_panel ();
+ break;
+
+ case MSG_MOUSE_CLICK:
+ {
+ int lines;
+
+ lines = tlines (tree);
+
+ if (y < 0)
+ {
+ tree_move_backward (tree, lines - 1);
+ show_tree (tree);
+ }
+ else if (y >= lines)
+ {
+ tree_move_forward (tree, lines - 1);
+ show_tree (tree);
+ }
+ else if ((event->count & GPM_DOUBLE) != 0)
+ {
+ if (tree->tree_shown[y] != NULL)
+ {
+ tree->selected_ptr = tree->tree_shown[y];
+ tree->topdiff = y;
+ }
+
+ tree_chdir_sel (tree);
+ }
+ }
+ break;
+
+ case MSG_MOUSE_SCROLL_UP:
+ case MSG_MOUSE_SCROLL_DOWN:
+ /* TODO: Ticket #2218 */
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WTree *
+tree_new (int y, int x, int lines, int cols, gboolean is_panel)
+{
+ WRect r = { y, x, lines, cols };
+ WTree *tree;
+ Widget *w;
+
+ tree = g_new (WTree, 1);
+
+ w = WIDGET (tree);
+ widget_init (w, &r, tree_callback, tree_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
+ w->keymap = tree_map;
+
+ tree->is_panel = is_panel;
+ tree->selected_ptr = NULL;
+
+ tree->store = tree_store_get ();
+ tree_store_add_entry_remove_hook (remove_callback, tree);
+ tree->tree_shown = NULL;
+ tree->search_buffer = g_string_sized_new (MC_MAXPATHLEN);
+ tree->topdiff = w->rect.lines / 2;
+ tree->searching = FALSE;
+
+ load_tree (tree);
+ return tree;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tree_chdir (WTree * tree, const vfs_path_t * dir)
+{
+ tree_entry *current;
+
+ current = tree_store_whereis (dir);
+ if (current != NULL)
+ {
+ tree->selected_ptr = current;
+ tree_check_focus (tree);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Return name of the currently selected entry */
+
+const vfs_path_t *
+tree_selected_name (const WTree * tree)
+{
+ return tree->selected_ptr->name;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+sync_tree (const vfs_path_t * vpath)
+{
+ tree_chdir (the_tree, vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WTree *
+find_tree (const WDialog * h)
+{
+ return (WTree *) widget_find_by_type (CONST_WIDGET (h), tree_callback);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/tree.h b/src/filemanager/tree.h
new file mode 100644
index 0000000..f1dbba6
--- /dev/null
+++ b/src/filemanager/tree.h
@@ -0,0 +1,35 @@
+/** \file tree.h
+ * \brief Header: directory tree browser
+ */
+
+#ifndef MC__TREE_H
+#define MC__TREE_H
+
+#include "lib/global.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct WTree WTree;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern WTree *the_tree;
+extern gboolean xtree_mode;
+
+/*** declarations of public functions ************************************************************/
+
+WTree *tree_new (int y, int x, int lines, int cols, gboolean is_panel);
+
+void tree_chdir (WTree * tree, const vfs_path_t * dir);
+const vfs_path_t *tree_selected_name (const WTree * tree);
+
+void sync_tree (const vfs_path_t * vpath);
+
+WTree *find_tree (const WDialog * h);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__TREE_H */
diff --git a/src/filemanager/treestore.c b/src/filemanager/treestore.c
new file mode 100644
index 0000000..2d23c93
--- /dev/null
+++ b/src/filemanager/treestore.c
@@ -0,0 +1,941 @@
+/*
+ Tree Store
+ Contains a storage of the file system tree representation
+
+ This module has been converted to be a widget.
+
+ The program load and saves the tree each time the tree widget is
+ created and destroyed. This is required for the future vfs layer,
+ it will be possible to have tree views over virtual file systems.
+
+ Copyright (C) 1999-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Janne Kukonlehto, 1994, 1996
+ Norbert Warmuth, 1997
+ Miguel de Icaza, 1996, 1999
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013
+
+ 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 treestore.c
+ * \brief Source: tree store
+ *
+ * Contains a storage of the file system tree representation.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "lib/mcconfig.h"
+#include "lib/vfs/vfs.h"
+#include "lib/fileloc.h"
+#include "lib/strescape.h"
+#include "lib/hook.h"
+#include "lib/util.h"
+
+#include "src/setup.h" /* setup_init() */
+
+#include "treestore.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static tree_entry *tree_store_add_entry (const vfs_path_t * name);
+
+/*** file scope variables ************************************************************************/
+
+static struct TreeStore ts;
+
+static hook_t *remove_entry_hooks;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+tree_store_dirty (gboolean dirty)
+{
+ ts.dirty = dirty;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ *
+ * @return the number of common bytes in the strings.
+ */
+
+static size_t
+str_common (const vfs_path_t * s1_vpath, const vfs_path_t * s2_vpath)
+{
+ size_t result = 0;
+ const char *s1, *s2;
+
+ s1 = vfs_path_as_str (s1_vpath);
+ s2 = vfs_path_as_str (s2_vpath);
+
+ while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
+ result++;
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** The directory names are arranged in a single linked list in the same
+ * order as they are displayed. When the tree is displayed the expected
+ * order is like this:
+ * /
+ * /bin
+ * /etc
+ * /etc/X11
+ * /etc/rc.d
+ * /etc.old/X11
+ * /etc.old/rc.d
+ * /usr
+ *
+ * i.e. the required collating sequence when comparing two directory names is
+ * '\0' < PATH_SEP < all-other-characters-in-encoding-order
+ *
+ * Since strcmp doesn't fulfil this requirement we use pathcmp when
+ * inserting directory names into the list. The meaning of the return value
+ * of pathcmp and strcmp are the same (an integer less than, equal to, or
+ * greater than zero if p1 is found to be less than, to match, or be greater
+ * than p2.
+ */
+
+static int
+pathcmp (const vfs_path_t * p1_vpath, const vfs_path_t * p2_vpath)
+{
+ int ret_val;
+ const char *p1, *p2;
+
+ p1 = vfs_path_as_str (p1_vpath);
+ p2 = vfs_path_as_str (p2_vpath);
+
+ for (; *p1 == *p2; p1++, p2++)
+ if (*p1 == '\0')
+ return 0;
+
+ if (*p1 == '\0')
+ ret_val = -1;
+ else if (*p2 == '\0')
+ ret_val = 1;
+ else if (IS_PATH_SEP (*p1))
+ ret_val = -1;
+ else if (IS_PATH_SEP (*p2))
+ ret_val = 1;
+ else
+ ret_val = (*p1 - *p2);
+
+ return ret_val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+decode (char *buffer)
+{
+ char *res, *p, *q;
+
+ res = g_strdup (buffer);
+
+ for (p = q = res; *p != '\0'; p++, q++)
+ {
+ if (*p == '\n')
+ {
+ *q = '\0';
+ return res;
+ }
+
+ if (*p != '\\')
+ {
+ *q = *p;
+ continue;
+ }
+
+ p++;
+
+ switch (*p)
+ {
+ case 'n':
+ *q = '\n';
+ break;
+ case '\\':
+ *q = '\\';
+ break;
+ default:
+ break;
+ }
+ }
+
+ *q = *p;
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Loads the tree store from the specified filename */
+
+static int
+tree_store_load_from (const char *name)
+{
+ FILE *file;
+ char buffer[MC_MAXPATHLEN + 20];
+
+ g_return_val_if_fail (name != NULL, 0);
+
+ if (ts.loaded)
+ return 1;
+
+ file = fopen (name, "r");
+
+ if (file != NULL
+ && (fgets (buffer, sizeof (buffer), file) == NULL
+ || strncmp (buffer, TREE_SIGNATURE, strlen (TREE_SIGNATURE)) != 0))
+ {
+ fclose (file);
+ file = NULL;
+ }
+
+ if (file != NULL)
+ {
+ char oldname[MC_MAXPATHLEN] = "\0";
+
+ ts.loaded = TRUE;
+
+ /* File open -> read contents */
+ while (fgets (buffer, MC_MAXPATHLEN, file))
+ {
+ tree_entry *e;
+ gboolean scanned;
+ char *lc_name;
+
+ /* Skip invalid records */
+ if (buffer[0] != '0' && buffer[0] != '1')
+ continue;
+
+ if (buffer[1] != ':')
+ continue;
+
+ scanned = buffer[0] == '1';
+
+ lc_name = decode (buffer + 2);
+ if (!IS_PATH_SEP (lc_name[0]))
+ {
+ /* Clear-text decompression */
+ char *s;
+
+ s = strtok (lc_name, " ");
+ if (s != NULL)
+ {
+ char *different;
+ int common;
+
+ common = atoi (s);
+ different = strtok (NULL, "");
+ if (different != NULL)
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (oldname);
+ g_strlcpy (oldname + common, different, sizeof (oldname) - (size_t) common);
+ if (vfs_file_is_local (vpath))
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_from_str (oldname);
+ e = tree_store_add_entry (tmp_vpath);
+ vfs_path_free (tmp_vpath, TRUE);
+ e->scanned = scanned;
+ }
+ vfs_path_free (vpath, TRUE);
+ }
+ }
+ }
+ else
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (lc_name);
+ if (vfs_file_is_local (vpath))
+ {
+ e = tree_store_add_entry (vpath);
+ e->scanned = scanned;
+ }
+ vfs_path_free (vpath, TRUE);
+ g_strlcpy (oldname, lc_name, sizeof (oldname));
+ }
+ g_free (lc_name);
+ }
+
+ fclose (file);
+ }
+
+ /* Nothing loaded, we add some standard directories */
+ if (!ts.tree_first)
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_from_str (PATH_SEP_STR);
+ tree_store_add_entry (tmp_vpath);
+ tree_store_rescan (tmp_vpath);
+ vfs_path_free (tmp_vpath, TRUE);
+ ts.loaded = TRUE;
+ }
+
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+encode (const vfs_path_t * vpath, size_t offset)
+{
+ return strutils_escape (vfs_path_as_str (vpath) + offset, -1, "\n\\", FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Saves the tree to the specified filename */
+
+static int
+tree_store_save_to (char *name)
+{
+ tree_entry *current;
+ FILE *file;
+
+ file = fopen (name, "w");
+ if (file == NULL)
+ return errno;
+
+ fprintf (file, "%s\n", TREE_SIGNATURE);
+
+ for (current = ts.tree_first; current != NULL; current = current->next)
+ if (vfs_file_is_local (current->name))
+ {
+ int i, common;
+
+ /* Clear-text compression */
+ if (current->prev != NULL
+ && (common = str_common (current->prev->name, current->name)) > 2)
+ {
+ char *encoded;
+
+ encoded = encode (current->name, common);
+ i = fprintf (file, "%d:%d %s\n", current->scanned ? 1 : 0, common, encoded);
+ g_free (encoded);
+ }
+ else
+ {
+ char *encoded;
+
+ encoded = encode (current->name, 0);
+ i = fprintf (file, "%d:%s\n", current->scanned ? 1 : 0, encoded);
+ g_free (encoded);
+ }
+
+ if (i == EOF)
+ {
+ fprintf (stderr, _("Cannot write to the %s file:\n%s\n"),
+ name, unix_error_string (errno));
+ break;
+ }
+ }
+
+ tree_store_dirty (FALSE);
+ fclose (file);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static tree_entry *
+tree_store_add_entry (const vfs_path_t * name)
+{
+ int flag = -1;
+ tree_entry *current;
+ tree_entry *old = NULL;
+ tree_entry *new;
+ int submask = 0;
+
+ if (ts.tree_last != NULL && ts.tree_last->next != NULL)
+ abort ();
+
+ /* Search for the correct place */
+ for (current = ts.tree_first;
+ current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next)
+ old = current;
+
+ if (flag == 0)
+ return current; /* Already in the list */
+
+ /* Not in the list -> add it */
+ new = g_new0 (tree_entry, 1);
+ if (current == NULL)
+ {
+ /* Append to the end of the list */
+ if (ts.tree_first == NULL)
+ {
+ /* Empty list */
+ ts.tree_first = new;
+ new->prev = NULL;
+ }
+ else
+ {
+ if (old != NULL)
+ old->next = new;
+ new->prev = old;
+ }
+ new->next = NULL;
+ ts.tree_last = new;
+ }
+ else
+ {
+ /* Insert in to the middle of the list */
+ new->prev = old;
+ if (old != NULL)
+ {
+ /* Yes, in the middle */
+ new->next = old->next;
+ old->next = new;
+ }
+ else
+ {
+ /* Nope, in the beginning of the list */
+ new->next = ts.tree_first;
+ ts.tree_first = new;
+ }
+ new->next->prev = new;
+ }
+
+ /* Calculate attributes */
+ new->name = vfs_path_clone (name);
+ new->sublevel = vfs_path_tokens_count (new->name);
+
+ {
+ const char *new_name;
+
+ new_name = vfs_path_get_last_path_str (new->name);
+ new->subname = strrchr (new_name, PATH_SEP);
+ if (new->subname == NULL)
+ new->subname = new_name;
+ else
+ new->subname++;
+ }
+
+ if (new->next != NULL)
+ submask = new->next->submask;
+
+ submask |= 1 << new->sublevel;
+ submask &= (2 << new->sublevel) - 1;
+ new->submask = submask;
+ new->mark = FALSE;
+
+ /* Correct the submasks of the previous entries */
+ for (current = new->prev;
+ current != NULL && current->sublevel > new->sublevel; current = current->prev)
+ current->submask |= 1 << new->sublevel;
+
+ tree_store_dirty (TRUE);
+ return new;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_store_notify_remove (tree_entry * entry)
+{
+ hook_t *p;
+
+ for (p = remove_entry_hooks; p != NULL; p = p->next)
+ {
+ tree_store_remove_fn r = (tree_store_remove_fn) p->hook_fn;
+
+ r (entry, p->hook_data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static tree_entry *
+remove_entry (tree_entry * entry)
+{
+ tree_entry *current = entry->prev;
+ long submask = 0;
+ tree_entry *ret = NULL;
+
+ tree_store_notify_remove (entry);
+
+ /* Correct the submasks of the previous entries */
+ if (entry->next != NULL)
+ submask = entry->next->submask;
+
+ for (; current != NULL && current->sublevel > entry->sublevel; current = current->prev)
+ {
+ submask |= 1 << current->sublevel;
+ submask &= (2 << current->sublevel) - 1;
+ current->submask = submask;
+ }
+
+ /* Unlink the entry from the list */
+ if (entry->prev != NULL)
+ entry->prev->next = entry->next;
+ else
+ ts.tree_first = entry->next;
+
+ if (entry->next != NULL)
+ entry->next->prev = entry->prev;
+ else
+ ts.tree_last = entry->prev;
+
+ /* Free the memory used by the entry */
+ vfs_path_free (entry->name, TRUE);
+ g_free (entry);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+process_special_dirs (GList ** special_dirs, const char *file)
+{
+ gchar **start_buff;
+ mc_config_t *cfg;
+
+ cfg = mc_config_init (file, TRUE);
+ if (cfg == NULL)
+ return;
+
+ start_buff = mc_config_get_string_list (cfg, "Special dirs", "list", NULL);
+ if (start_buff != NULL)
+ {
+ gchar **buffers;
+
+ for (buffers = start_buff; *buffers != NULL; buffers++)
+ {
+ *special_dirs = g_list_prepend (*special_dirs, *buffers);
+ *buffers = NULL;
+ }
+
+ g_strfreev (start_buff);
+ }
+ mc_config_deinit (cfg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+should_skip_directory (const vfs_path_t * vpath)
+{
+ static GList *special_dirs = NULL;
+ GList *l;
+ static gboolean loaded = FALSE;
+ gboolean ret = FALSE;
+
+ if (!loaded)
+ {
+ const char *profile_name;
+
+ profile_name = setup_init ();
+ process_special_dirs (&special_dirs, profile_name);
+ process_special_dirs (&special_dirs, mc_global.profile_name);
+
+ loaded = TRUE;
+ }
+
+ for (l = special_dirs; l != NULL; l = g_list_next (l))
+ if (strncmp (vfs_path_as_str (vpath), l->data, strlen (l->data)) == 0)
+ {
+ ret = TRUE;
+ break;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+queue_vpath_free (gpointer data)
+{
+ vfs_path_free ((vfs_path_t *) data, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Searches for specified directory */
+tree_entry *
+tree_store_whereis (const vfs_path_t * name)
+{
+ tree_entry *current;
+ int flag = -1;
+
+ for (current = ts.tree_first;
+ current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next)
+ ;
+
+ return flag == 0 ? current : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct TreeStore *
+tree_store_get (void)
+{
+ return &ts;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * \fn int tree_store_load(void)
+ * \brief Loads the tree from the default location
+ * \return 1 if success (true), 0 otherwise (false)
+ */
+
+int
+tree_store_load (void)
+{
+ char *name;
+ int retval;
+
+ name = mc_config_get_full_path (MC_TREESTORE_FILE);
+ retval = tree_store_load_from (name);
+ g_free (name);
+
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * \fn int tree_store_save(void)
+ * \brief Saves the tree to the default file in an atomic fashion
+ * \return 0 if success, errno on error
+ */
+
+int
+tree_store_save (void)
+{
+ char *name;
+ int retval;
+
+ name = mc_config_get_full_path (MC_TREESTORE_FILE);
+ mc_util_make_backup_if_possible (name, ".tmp");
+
+ retval = tree_store_save_to (name);
+ if (retval != 0)
+ mc_util_restore_from_backup_if_possible (name, ".tmp");
+ else
+ mc_util_unlink_backup_if_possible (name, ".tmp");
+
+ g_free (name);
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
+{
+ add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
+{
+ delete_hook (&remove_entry_hooks, (void (*)(void *)) callback);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tree_store_remove_entry (const vfs_path_t * name_vpath)
+{
+ tree_entry *current, *base;
+ size_t len;
+
+ g_return_if_fail (name_vpath != NULL);
+
+ /* Miguel Ugly hack */
+ {
+ gboolean is_root;
+ const char *name_vpath_str;
+
+ name_vpath_str = vfs_path_as_str (name_vpath);
+ is_root = (IS_PATH_SEP (name_vpath_str[0]) && name_vpath_str[1] == '\0');
+ if (is_root)
+ return;
+ }
+ /* Miguel Ugly hack end */
+
+ base = tree_store_whereis (name_vpath);
+ if (base == NULL)
+ return; /* Doesn't exist */
+
+ len = vfs_path_len (base->name);
+ current = base->next;
+ while (current != NULL && vfs_path_equal_len (current->name, base->name, len))
+ {
+ gboolean ok;
+ tree_entry *old;
+ const char *cname;
+
+ cname = vfs_path_as_str (current->name);
+ ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]));
+ if (!ok)
+ break;
+
+ old = current;
+ current = current->next;
+ remove_entry (old);
+ }
+ remove_entry (base);
+ tree_store_dirty (TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This subdirectory exists -> clear deletion mark */
+
+void
+tree_store_mark_checked (const char *subname)
+{
+ vfs_path_t *name;
+ tree_entry *current, *base;
+ int flag = 1;
+ const char *cname;
+
+ if (!ts.loaded)
+ return;
+
+ if (ts.check_name == NULL)
+ return;
+
+ /* Calculate the full name of the subdirectory */
+ if (DIR_IS_DOT (subname) || DIR_IS_DOTDOT (subname))
+ return;
+
+ cname = vfs_path_as_str (ts.check_name);
+ if (IS_PATH_SEP (cname[0]) && cname[1] == '\0')
+ name = vfs_path_build_filename (PATH_SEP_STR, subname, (char *) NULL);
+ else
+ name = vfs_path_append_new (ts.check_name, subname, (char *) NULL);
+
+ /* Search for the subdirectory */
+ for (current = ts.check_start;
+ current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next)
+ ;
+
+ if (flag != 0)
+ {
+ /* Doesn't exist -> add it */
+ current = tree_store_add_entry (name);
+ ts.add_queue_vpath = g_list_prepend (ts.add_queue_vpath, name);
+ }
+ else
+ vfs_path_free (name, TRUE);
+
+ /* Clear the deletion mark from the subdirectory and its children */
+ base = current;
+ if (base != NULL)
+ {
+ size_t len;
+
+ len = vfs_path_len (base->name);
+ base->mark = FALSE;
+ for (current = base->next;
+ current != NULL && vfs_path_equal_len (current->name, base->name, len);
+ current = current->next)
+ {
+ gboolean ok;
+
+ cname = vfs_path_as_str (current->name);
+ ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
+ if (!ok)
+ break;
+
+ current->mark = FALSE;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Mark the subdirectories of the current directory for delete */
+
+tree_entry *
+tree_store_start_check (const vfs_path_t * vpath)
+{
+ tree_entry *current, *retval;
+ size_t len;
+
+ if (!ts.loaded)
+ return NULL;
+
+ g_return_val_if_fail (ts.check_name == NULL, NULL);
+ ts.check_start = NULL;
+
+ /* Search for the start of subdirectories */
+ current = tree_store_whereis (vpath);
+ if (current == NULL)
+ {
+ struct stat s;
+
+ if (mc_stat (vpath, &s) == -1 || !S_ISDIR (s.st_mode))
+ return NULL;
+
+ current = tree_store_add_entry (vpath);
+ ts.check_name = vfs_path_clone (vpath);
+
+ return current;
+ }
+
+ ts.check_name = vfs_path_clone (vpath);
+
+ retval = current;
+
+ /* Mark old subdirectories for delete */
+ ts.check_start = current->next;
+ len = vfs_path_len (ts.check_name);
+
+ for (current = ts.check_start;
+ current != NULL && vfs_path_equal_len (current->name, ts.check_name, len);
+ current = current->next)
+ {
+ gboolean ok;
+ const char *cname;
+
+ cname = vfs_path_as_str (current->name);
+ ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
+ if (!ok)
+ break;
+
+ current->mark = TRUE;
+ }
+
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Delete subdirectories which still have the deletion mark */
+
+void
+tree_store_end_check (void)
+{
+ tree_entry *current;
+ size_t len;
+ GList *the_queue;
+
+ if (!ts.loaded)
+ return;
+
+ g_return_if_fail (ts.check_name != NULL);
+
+ /* Check delete marks and delete if found */
+ len = vfs_path_len (ts.check_name);
+
+ current = ts.check_start;
+ while (current != NULL && vfs_path_equal_len (current->name, ts.check_name, len))
+ {
+ gboolean ok;
+ tree_entry *old;
+ const char *cname;
+
+ cname = vfs_path_as_str (current->name);
+ ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
+ if (!ok)
+ break;
+
+ old = current;
+ current = current->next;
+ if (old->mark)
+ remove_entry (old);
+ }
+
+ /* get the stuff in the scan order */
+ the_queue = g_list_reverse (ts.add_queue_vpath);
+ ts.add_queue_vpath = NULL;
+ vfs_path_free (ts.check_name, TRUE);
+ ts.check_name = NULL;
+
+ g_list_free_full (the_queue, queue_vpath_free);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+tree_entry *
+tree_store_rescan (const vfs_path_t * vpath)
+{
+ DIR *dirp;
+ struct stat buf;
+ tree_entry *entry;
+
+ if (should_skip_directory (vpath))
+ {
+ entry = tree_store_add_entry (vpath);
+ entry->scanned = TRUE;
+ return entry;
+ }
+
+ entry = tree_store_start_check (vpath);
+ if (entry == NULL)
+ return NULL;
+
+ dirp = mc_opendir (vpath);
+ if (dirp != NULL)
+ {
+ struct vfs_dirent *dp;
+
+ for (dp = mc_readdir (dirp); dp != NULL; dp = mc_readdir (dirp))
+ if (!DIR_IS_DOT (dp->d_name) && !DIR_IS_DOTDOT (dp->d_name))
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_append_new (vpath, dp->d_name, (char *) NULL);
+ if (mc_lstat (tmp_vpath, &buf) != -1 && S_ISDIR (buf.st_mode))
+ tree_store_mark_checked (dp->d_name);
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+
+ mc_closedir (dirp);
+ }
+ tree_store_end_check ();
+ entry->scanned = TRUE;
+
+ return entry;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/treestore.h b/src/filemanager/treestore.h
new file mode 100644
index 0000000..34e15a9
--- /dev/null
+++ b/src/filemanager/treestore.h
@@ -0,0 +1,63 @@
+/** \file treestore.h
+ * \brief Header: tree store
+ *
+ * Contains a storage of the file system tree representation.
+ */
+
+#ifndef MC__TREE_STORE_H
+#define MC__TREE_STORE_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*
+ * Register/unregister notification functions for "entry_remove"
+ */
+struct tree_entry;
+typedef void (*tree_store_remove_fn) (struct tree_entry * tree, void *data);
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct tree_entry
+{
+ vfs_path_t *name; /* The full path of directory */
+ int sublevel; /* Number of parent directories (slashes) */
+ long submask; /* Bitmask of existing sublevels after this entry */
+ const char *subname; /* The last part of name (the actual name) */
+ gboolean mark; /* Flag: Is this entry marked (e. g. for delete)? */
+ gboolean scanned; /* Flag: childs scanned or not */
+ struct tree_entry *next; /* Next item in the list */
+ struct tree_entry *prev; /* Previous item in the list */
+} tree_entry;
+
+struct TreeStore
+{
+ tree_entry *tree_first; /* First entry in the list */
+ tree_entry *tree_last; /* Last entry in the list */
+ tree_entry *check_start; /* Start of checked subdirectories */
+ vfs_path_t *check_name;
+ GList *add_queue_vpath; /* List of vfs_path_t objects of added directories */
+ gboolean loaded;
+ gboolean dirty;
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+struct TreeStore *tree_store_get (void);
+int tree_store_load (void);
+int tree_store_save (void);
+void tree_store_remove_entry (const vfs_path_t * name_vpath);
+tree_entry *tree_store_start_check (const vfs_path_t * vpath);
+void tree_store_mark_checked (const char *subname);
+void tree_store_end_check (void);
+tree_entry *tree_store_whereis (const vfs_path_t * name);
+tree_entry *tree_store_rescan (const vfs_path_t * vpath);
+
+void tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data);
+void tree_store_remove_entry_remove_hook (tree_store_remove_fn callback);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__TREE_STORE_H */
diff --git a/src/help.c b/src/help.c
new file mode 100644
index 0000000..a14744a
--- /dev/null
+++ b/src/help.c
@@ -0,0 +1,1181 @@
+/*
+ Hypertext file browser.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ 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 help.c
+ * \brief Source: hypertext file browser
+ *
+ * Implements the hypertext file viewer.
+ * The hypertext file is a file that may have one or more nodes. Each
+ * node ends with a ^D character and starts with a bracket, then the
+ * name of the node and then a closing bracket. Right after the closing
+ * bracket a newline is placed. This newline is not to be displayed by
+ * the help viewer and must be skipped - its sole purpose is to facilitate
+ * the work of the people managing the help file template (xnc.hlp) .
+ *
+ * Links in the hypertext file are specified like this: the text that
+ * will be highlighted should have a leading ^A, then it comes the
+ * text, then a ^B indicating that highlighting is done, then the name
+ * of the node you want to link to and then a ^C.
+ *
+ * The file must contain a ^D at the beginning and at the end of the
+ * file or the program will not be able to detect the end of file.
+ *
+ * Laziness/widgeting attack: This file does use the dialog manager
+ * and uses mainly the dialog to achieve the help work. there is only
+ * one specialized widget and it's only used to forward the mouse messages
+ * to the appropriate routine.
+ */
+
+
+#include <config.h>
+
+#include <errno.h>
+#include <limits.h> /* MB_LEN_MAX */
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/fileloc.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+#include "lib/event-types.h"
+
+#include "keymap.h"
+#include "help.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define MAXLINKNAME 80
+#define HISTORY_SIZE 20
+#define HELP_WINDOW_WIDTH MIN(80, COLS - 16)
+
+#define STRING_LINK_START "\01"
+#define STRING_LINK_POINTER "\02"
+#define STRING_LINK_END "\03"
+#define STRING_NODE_END "\04"
+
+/*** file scope type declarations ****************************************************************/
+
+/* Link areas for the mouse */
+typedef struct Link_Area
+{
+ int x1, y1, x2, y2;
+ const char *link_name;
+} Link_Area;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static char *fdata = NULL; /* Pointer to the loaded data file */
+static int help_lines; /* Lines in help viewer */
+static int history_ptr; /* For the history queue */
+static const char *main_node; /* The main node */
+static const char *last_shown = NULL; /* Last byte shown in a screen */
+static gboolean end_of_node = FALSE; /* Flag: the last character of the node shown? */
+static const char *currentpoint;
+static const char *selected_item;
+
+/* The widget variables */
+static WDialog *whelp;
+
+static struct
+{
+ const char *page; /* Pointer to the selected page */
+ const char *link; /* Pointer to the selected link */
+} history[HISTORY_SIZE];
+
+static GSList *link_area = NULL;
+static gboolean inside_link_area = FALSE;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** returns the position where text was found in the start buffer
+ * or 0 if not found
+ */
+static const char *
+search_string (const char *start, const char *text)
+{
+ const char *result = NULL;
+ char *local_text = g_strdup (text);
+ char *d = local_text;
+ const char *e = start;
+
+ /* fmt sometimes replaces a space with a newline in the help file */
+ /* Replace the newlines in the link name with spaces to correct the situation */
+ while (*d != '\0')
+ {
+ if (*d == '\n')
+ *d = ' ';
+ str_next_char (&d);
+ }
+
+ /* Do search */
+ for (d = local_text; *e; e++)
+ {
+ if (*d == *e)
+ d++;
+ else
+ d = local_text;
+ if (*d == '\0')
+ {
+ result = e + 1;
+ break;
+ }
+ }
+
+ g_free (local_text);
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Searches text in the buffer pointed by start. Search ends
+ * if the CHAR_NODE_END is found in the text.
+ * @return NULL on failure
+ */
+
+static const char *
+search_string_node (const char *start, const char *text)
+{
+ const char *d = text;
+ const char *e = start;
+
+ if (start != NULL)
+ for (; *e && *e != CHAR_NODE_END; e++)
+ {
+ if (*d == *e)
+ d++;
+ else
+ d = text;
+ if (*d == '\0')
+ return e + 1;
+ }
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Searches the_char in the buffer pointer by start and searches
+ * it can search forward (direction = 1) or backward (direction = -1)
+ */
+
+static const char *
+search_char_node (const char *start, char the_char, int direction)
+{
+ const char *e;
+
+ for (e = start; (*e != '\0') && (*e != CHAR_NODE_END); e += direction)
+ if (*e == the_char)
+ return e;
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Returns the new current pointer when moved lines lines */
+
+static const char *
+move_forward2 (const char *c, int lines)
+{
+ const char *p;
+ int line;
+
+ currentpoint = c;
+ for (line = 0, p = currentpoint; (*p != '\0') && (*p != CHAR_NODE_END); str_cnext_char (&p))
+ {
+ if (line == lines)
+ return currentpoint = p;
+
+ if (*p == '\n')
+ line++;
+ }
+ return currentpoint = c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+move_backward2 (const char *c, int lines)
+{
+ const char *p;
+ int line;
+
+ currentpoint = c;
+ for (line = 0, p = currentpoint; (*p != '\0') && ((int) (p - fdata) >= 0); str_cprev_char (&p))
+ {
+ if (*p == CHAR_NODE_END)
+ {
+ /* We reached the beginning of the node */
+ /* Skip the node headers */
+ while (*p != ']')
+ str_cnext_char (&p);
+ return currentpoint = p + 2; /* Skip the newline following the start of the node */
+ }
+
+ if (*(p - 1) == '\n')
+ line++;
+ if (line == lines)
+ return currentpoint = p;
+ }
+ return currentpoint = c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+move_forward (int i)
+{
+ if (!end_of_node)
+ currentpoint = move_forward2 (currentpoint, i);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+move_backward (int i)
+{
+ currentpoint = move_backward2 (currentpoint, ++i);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+move_to_top (void)
+{
+ while (((int) (currentpoint > fdata) > 0) && (*currentpoint != CHAR_NODE_END))
+ currentpoint--;
+
+ while (*currentpoint != ']')
+ currentpoint++;
+ currentpoint = currentpoint + 2; /* Skip the newline following the start of the node */
+ selected_item = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+move_to_bottom (void)
+{
+ while ((*currentpoint != '\0') && (*currentpoint != CHAR_NODE_END))
+ currentpoint++;
+ currentpoint--;
+ move_backward (1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+help_follow_link (const char *start, const char *lc_selected_item)
+{
+ const char *p;
+
+ if (lc_selected_item == NULL)
+ return start;
+
+ for (p = lc_selected_item; *p && *p != CHAR_NODE_END && *p != CHAR_LINK_POINTER; p++)
+ ;
+ if (*p == CHAR_LINK_POINTER)
+ {
+ int i;
+ char link_name[MAXLINKNAME];
+
+ link_name[0] = '[';
+ for (i = 1; *p != CHAR_LINK_END && *p && *p != CHAR_NODE_END && i < MAXLINKNAME - 3;)
+ link_name[i++] = *++p;
+ link_name[i - 1] = ']';
+ link_name[i] = '\0';
+ p = search_string (fdata, link_name);
+ if (p != NULL)
+ {
+ p += 1; /* Skip the newline following the start of the node */
+ return p;
+ }
+ }
+
+ /* Create a replacement page with the error message */
+ return _("Help file format error\n");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+select_next_link (const char *current_link)
+{
+ const char *p;
+
+ if (current_link == NULL)
+ return NULL;
+
+ p = search_string_node (current_link, STRING_LINK_END);
+ if (p == NULL)
+ return NULL;
+ p = search_string_node (p, STRING_LINK_START);
+ if (p == NULL)
+ return NULL;
+ return p - 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+select_prev_link (const char *current_link)
+{
+ return current_link == NULL ? NULL : search_char_node (current_link - 1, CHAR_LINK_START, -1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+start_link_area (int x, int y, const char *link_name)
+{
+ Link_Area *la;
+
+ if (inside_link_area)
+ message (D_NORMAL, _("Warning"), "%s", _("Internal bug: Double start of link area"));
+
+ /* Allocate memory for a new link area */
+ la = g_new (Link_Area, 1);
+ /* Save the beginning coordinates of the link area */
+ la->x1 = x;
+ la->y1 = y;
+ /* Save the name of the destination anchor */
+ la->link_name = link_name;
+ link_area = g_slist_prepend (link_area, la);
+
+ inside_link_area = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+end_link_area (int x, int y)
+{
+ if (inside_link_area)
+ {
+ Link_Area *la = (Link_Area *) link_area->data;
+ /* Save the end coordinates of the link area */
+ la->x2 = x;
+ la->y2 = y;
+ inside_link_area = FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+clear_link_areas (void)
+{
+ g_clear_slist (&link_area, g_free);
+ inside_link_area = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+help_print_word (WDialog * h, GString * word, int *col, int *line, gboolean add_space)
+{
+ if (*line >= help_lines)
+ g_string_set_size (word, 0);
+ else
+ {
+ int w;
+
+ w = str_term_width1 (word->str);
+ if (*col + w >= HELP_WINDOW_WIDTH)
+ {
+ *col = 0;
+ (*line)++;
+ }
+
+ if (*line >= help_lines)
+ g_string_set_size (word, 0);
+ else
+ {
+ widget_gotoyx (h, *line + 2, *col + 2);
+ tty_print_string (word->str);
+ g_string_set_size (word, 0);
+ *col += w;
+ }
+ }
+
+ if (add_space)
+ {
+ if (*col < HELP_WINDOW_WIDTH - 1)
+ {
+ tty_print_char (' ');
+ (*col)++;
+ }
+ else
+ {
+ *col = 0;
+ (*line)++;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+help_show (WDialog * h, const char *paint_start)
+{
+ const char *p, *n;
+ int col, line, c;
+ gboolean painting = TRUE;
+ gboolean acs; /* Flag: Alternate character set active? */
+ gboolean repeat_paint;
+ int active_col, active_line; /* Active link position */
+ char buff[MB_LEN_MAX + 1];
+ GString *word;
+
+ word = g_string_sized_new (32);
+
+ tty_setcolor (HELP_NORMAL_COLOR);
+ do
+ {
+ line = col = active_col = active_line = 0;
+ repeat_paint = FALSE;
+ acs = FALSE;
+
+ clear_link_areas ();
+ if ((int) (selected_item - paint_start) < 0)
+ selected_item = NULL;
+
+ p = paint_start;
+ n = paint_start;
+ while ((n[0] != '\0') && (n[0] != CHAR_NODE_END) && (line < help_lines))
+ {
+ p = n;
+ n = str_cget_next_char (p);
+ memcpy (buff, p, n - p);
+ buff[n - p] = '\0';
+
+ c = (unsigned char) buff[0];
+ switch (c)
+ {
+ case CHAR_LINK_START:
+ if (selected_item == NULL)
+ selected_item = p;
+ if (p != selected_item)
+ tty_setcolor (HELP_LINK_COLOR);
+ else
+ {
+ tty_setcolor (HELP_SLINK_COLOR);
+
+ /* Store the coordinates of the link */
+ active_col = col + 2;
+ active_line = line + 2;
+ }
+ start_link_area (col, line, p);
+ break;
+ case CHAR_LINK_POINTER:
+ painting = FALSE;
+ break;
+ case CHAR_LINK_END:
+ painting = TRUE;
+ help_print_word (h, word, &col, &line, FALSE);
+ end_link_area (col - 1, line);
+ tty_setcolor (HELP_NORMAL_COLOR);
+ break;
+ case CHAR_ALTERNATE:
+ acs = TRUE;
+ break;
+ case CHAR_NORMAL:
+ acs = FALSE;
+ break;
+ case CHAR_VERSION:
+ widget_gotoyx (h, line + 2, col + 2);
+ tty_print_string (mc_global.mc_version);
+ col += str_term_width1 (mc_global.mc_version);
+ break;
+ case CHAR_FONT_BOLD:
+ tty_setcolor (HELP_BOLD_COLOR);
+ break;
+ case CHAR_FONT_ITALIC:
+ tty_setcolor (HELP_ITALIC_COLOR);
+ break;
+ case CHAR_FONT_NORMAL:
+ help_print_word (h, word, &col, &line, FALSE);
+ tty_setcolor (HELP_NORMAL_COLOR);
+ break;
+ case '\n':
+ if (painting)
+ help_print_word (h, word, &col, &line, FALSE);
+ line++;
+ col = 0;
+ break;
+ case ' ':
+ case '\t':
+ /* word delimiter */
+ if (painting)
+ {
+ help_print_word (h, word, &col, &line, c == ' ');
+ if (c == '\t')
+ {
+ col = (col / 8 + 1) * 8;
+ if (col >= HELP_WINDOW_WIDTH)
+ {
+ line++;
+ col = 8;
+ }
+ }
+ }
+ break;
+ default:
+ if (painting && (line < help_lines))
+ {
+ if (!acs)
+ /* accumulate symbols in a word */
+ g_string_append (word, buff);
+ else if (col < HELP_WINDOW_WIDTH)
+ {
+ widget_gotoyx (h, line + 2, col + 2);
+
+ if ((c == ' ') || (c == '.'))
+ tty_print_char (c);
+ else
+#ifndef HAVE_SLANG
+ tty_print_char (acs_map[c]);
+#else
+ SLsmg_draw_object (WIDGET (h)->rect.y + line + 2,
+ WIDGET (h)->rect.x + col + 2, c);
+#endif
+ col++;
+ }
+ }
+ }
+ }
+
+ /* print last word */
+ if (n[0] == CHAR_NODE_END)
+ help_print_word (h, word, &col, &line, FALSE);
+
+ last_shown = p;
+ end_of_node = line < help_lines;
+ tty_setcolor (HELP_NORMAL_COLOR);
+ if ((int) (selected_item - last_shown) >= 0)
+ {
+ if ((link_area == NULL) || (link_area->data == NULL))
+ selected_item = NULL;
+ else
+ {
+ selected_item = ((Link_Area *) link_area->data)->link_name;
+ repeat_paint = TRUE;
+ }
+ }
+ }
+ while (repeat_paint);
+
+ g_string_free (word, TRUE);
+
+ /* Position the cursor over a nice link */
+ if (active_col)
+ widget_gotoyx (h, active_line, active_col);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** show help */
+
+static void
+help_help (WDialog * h)
+{
+ const char *p;
+
+ history_ptr = (history_ptr + 1) % HISTORY_SIZE;
+ history[history_ptr].page = currentpoint;
+ history[history_ptr].link = selected_item;
+
+ p = search_string (fdata, "[How to use help]");
+ if (p != NULL)
+ {
+ currentpoint = p + 1; /* Skip the newline following the start of the node */
+ selected_item = NULL;
+ widget_draw (WIDGET (h));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+help_index (WDialog * h)
+{
+ const char *new_item;
+
+ new_item = search_string (fdata, "[Contents]");
+
+ if (new_item == NULL)
+ message (D_ERROR, MSG_ERROR, _("Cannot find node %s in help file"), "[Contents]");
+ else
+ {
+ history_ptr = (history_ptr + 1) % HISTORY_SIZE;
+ history[history_ptr].page = currentpoint;
+ history[history_ptr].link = selected_item;
+
+ currentpoint = new_item + 1; /* Skip the newline following the start of the node */
+ selected_item = NULL;
+ widget_draw (WIDGET (h));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+help_back (WDialog * h)
+{
+ currentpoint = history[history_ptr].page;
+ selected_item = history[history_ptr].link;
+ history_ptr--;
+ if (history_ptr < 0)
+ history_ptr = HISTORY_SIZE - 1;
+
+ widget_draw (WIDGET (h)); /* FIXME: unneeded? */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+help_next_link (gboolean move_down)
+{
+ const char *new_item;
+
+ new_item = select_next_link (selected_item);
+ if (new_item != NULL)
+ {
+ selected_item = new_item;
+ if ((int) (selected_item - last_shown) >= 0)
+ {
+ if (move_down)
+ move_forward (1);
+ else
+ selected_item = NULL;
+ }
+ }
+ else if (move_down)
+ move_forward (1);
+ else
+ selected_item = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+help_prev_link (gboolean move_up)
+{
+ const char *new_item;
+
+ new_item = select_prev_link (selected_item);
+ selected_item = new_item;
+ if ((selected_item == NULL) || (selected_item < currentpoint))
+ {
+ if (move_up)
+ move_backward (1);
+ else if ((link_area != NULL) && (link_area->data != NULL))
+ selected_item = ((Link_Area *) link_area->data)->link_name;
+ else
+ selected_item = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+help_next_node (void)
+{
+ const char *new_item;
+
+ new_item = currentpoint;
+ while ((*new_item != '\0') && (*new_item != CHAR_NODE_END))
+ new_item++;
+
+ if (*++new_item == '[')
+ while (*++new_item != '\0')
+ if ((*new_item == ']') && (*++new_item != '\0') && (*++new_item != '\0'))
+ {
+ currentpoint = new_item;
+ selected_item = NULL;
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+help_prev_node (void)
+{
+ const char *new_item;
+
+ new_item = currentpoint;
+ while (((int) (new_item - fdata) > 1) && (*new_item != CHAR_NODE_END))
+ new_item--;
+ new_item--;
+ while (((int) (new_item - fdata) > 0) && (*new_item != CHAR_NODE_END))
+ new_item--;
+ while (*new_item != ']')
+ new_item++;
+ currentpoint = new_item + 2;
+ selected_item = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+help_select_link (void)
+{
+ /* follow link */
+ if (selected_item == NULL)
+ {
+#ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
+ /* Is there any reason why the right key would take us
+ * backward if there are no links selected?, I agree
+ * with Torben than doing nothing in this case is better
+ */
+ /* If there are no links, go backward in history */
+ history_ptr--;
+ if (history_ptr < 0)
+ history_ptr = HISTORY_SIZE - 1;
+
+ currentpoint = history[history_ptr].page;
+ selected_item = history[history_ptr].link;
+#endif
+ }
+ else
+ {
+ history_ptr = (history_ptr + 1) % HISTORY_SIZE;
+ history[history_ptr].page = currentpoint;
+ history[history_ptr].link = selected_item;
+ currentpoint = help_follow_link (currentpoint, selected_item);
+ }
+
+ selected_item = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+help_execute_cmd (long command)
+{
+ cb_ret_t ret = MSG_HANDLED;
+
+ switch (command)
+ {
+ case CK_Help:
+ help_help (whelp);
+ break;
+ case CK_Index:
+ help_index (whelp);
+ break;
+ case CK_Back:
+ help_back (whelp);
+ break;
+ case CK_Up:
+ help_prev_link (TRUE);
+ break;
+ case CK_Down:
+ help_next_link (TRUE);
+ break;
+ case CK_PageDown:
+ move_forward (help_lines - 1);
+ break;
+ case CK_PageUp:
+ move_backward (help_lines - 1);
+ break;
+ case CK_HalfPageDown:
+ move_forward (help_lines / 2);
+ break;
+ case CK_HalfPageUp:
+ move_backward (help_lines / 2);
+ break;
+ case CK_Top:
+ move_to_top ();
+ break;
+ case CK_Bottom:
+ move_to_bottom ();
+ break;
+ case CK_Enter:
+ help_select_link ();
+ break;
+ case CK_LinkNext:
+ help_next_link (FALSE);
+ break;
+ case CK_LinkPrev:
+ help_prev_link (FALSE);
+ break;
+ case CK_NodeNext:
+ help_next_node ();
+ break;
+ case CK_NodePrev:
+ help_prev_node ();
+ break;
+ case CK_Quit:
+ dlg_close (whelp);
+ break;
+ default:
+ ret = MSG_NOT_HANDLED;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+help_handle_key (WDialog * h, int key)
+{
+ Widget *w = WIDGET (h);
+ long command;
+
+ command = widget_lookup_key (w, key);
+ if (command == CK_IgnoreKey)
+ return MSG_NOT_HANDLED;
+
+ return help_execute_cmd (command);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+help_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_DRAW:
+ frame_callback (w, NULL, MSG_DRAW, 0, NULL);
+ help_show (DIALOG (w->owner), currentpoint);
+ return MSG_HANDLED;
+
+ default:
+ return frame_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+help_resize (WDialog * h)
+{
+ Widget *w = WIDGET (h);
+ WButtonBar *bb;
+ WRect r = w->rect;
+
+ help_lines = MIN (LINES - 4, MAX (2 * LINES / 3, 18));
+ r.lines = help_lines + 4;
+ r.cols = HELP_WINDOW_WIDTH + 4;
+ dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
+ bb = buttonbar_find (h);
+ widget_set_size (WIDGET (bb), LINES - 1, 0, 1, COLS);
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+help_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_RESIZE:
+ return help_resize (h);
+
+ case MSG_KEY:
+ {
+ cb_ret_t ret;
+
+ ret = help_handle_key (h, parm);
+ if (ret == MSG_HANDLED)
+ widget_draw (w);
+
+ return ret;
+ }
+
+ case MSG_ACTION:
+ /* Handle shortcuts and buttonbar. */
+ return help_execute_cmd (parm);
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+interactive_display_finish (void)
+{
+ clear_link_areas ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** translate help file into terminal encoding */
+
+static void
+translate_file (char *filedata)
+{
+ GIConv conv;
+
+ conv = str_crt_conv_from ("UTF-8");
+ if (conv != INVALID_CONV)
+ {
+ GString *translated_data;
+ gboolean nok;
+
+ g_free (fdata);
+
+ /* initial allocation for largest whole help file */
+ translated_data = g_string_sized_new (32 * 1024);
+ nok = (str_convert (conv, filedata, translated_data) == ESTR_FAILURE);
+ fdata = g_string_free (translated_data, nok);
+
+ str_close_conv (conv);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+md_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_RESIZE:
+ widget_default_callback (w, NULL, MSG_RESIZE, 0, data);
+ w->rect.lines = help_lines;
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+help_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ int x, y;
+ GSList *current_area;
+
+ if (msg != MSG_MOUSE_CLICK)
+ return;
+
+ if ((event->buttons & GPM_B_RIGHT) != 0)
+ {
+ /* Right button click */
+ help_back (whelp);
+ return;
+ }
+
+ /* Left bytton click */
+
+ /* The event is relative to the dialog window, adjust it: */
+ x = event->x - 1;
+ y = event->y - 1;
+
+ /* Test whether the mouse click is inside one of the link areas */
+ for (current_area = link_area; current_area != NULL; current_area = g_slist_next (current_area))
+ {
+ Link_Area *la = (Link_Area *) current_area->data;
+
+ /* Test one line link area */
+ if (y == la->y1 && x >= la->x1 && y == la->y2 && x <= la->x2)
+ break;
+
+ /* Test two line link area */
+ if (la->y1 + 1 == la->y2)
+ {
+ /* The first line || The second line */
+ if ((y == la->y1 && x >= la->x1) || (y == la->y2 && x <= la->x2))
+ break;
+ }
+ /* Mouse will not work with link areas of more than two lines */
+ }
+
+ /* Test whether a link area was found */
+ if (current_area != NULL)
+ {
+ Link_Area *la = (Link_Area *) current_area->data;
+
+ /* The click was inside a link area -> follow the link */
+ history_ptr = (history_ptr + 1) % HISTORY_SIZE;
+ history[history_ptr].page = currentpoint;
+ history[history_ptr].link = la->link_name;
+ currentpoint = help_follow_link (currentpoint, la->link_name);
+ selected_item = NULL;
+ }
+ else if (y < 0)
+ move_backward (help_lines - 1);
+ else if (y >= help_lines)
+ move_forward (help_lines - 1);
+ else if (y < help_lines / 2)
+ move_backward (1);
+ else
+ move_forward (1);
+
+ /* Show the new node */
+ widget_draw (WIDGET (w->owner));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static Widget *
+mousedispatch_new (const WRect * r)
+{
+ Widget *w;
+
+ w = g_new0 (Widget, 1);
+ widget_init (w, r, md_callback, help_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR;
+
+ return w;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+gboolean
+help_interactive_display (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ const dlg_colors_t help_colors = {
+ HELP_NORMAL_COLOR, /* common text color */
+ 0, /* unused in help */
+ HELP_BOLD_COLOR, /* bold text color */
+ 0, /* unused in help */
+ HELP_TITLE_COLOR /* title color */
+ };
+
+ Widget *wh;
+ WGroup *g;
+ WButtonBar *help_bar;
+ Widget *md;
+ char *hlpfile = NULL;
+ char *filedata;
+ ev_help_t *event_data = (ev_help_t *) data;
+ WRect r = { 1, 1, 1, 1 };
+
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+
+ if (event_data->filename != NULL)
+ g_file_get_contents (event_data->filename, &filedata, NULL, NULL);
+ else
+ filedata = load_mc_home_file (mc_global.share_data_dir, MC_HELP, &hlpfile, NULL);
+
+ if (filedata == NULL)
+ message (D_ERROR, MSG_ERROR, _("Cannot open file %s\n%s"),
+ event_data->filename ? event_data->filename : hlpfile, unix_error_string (errno));
+
+ g_free (hlpfile);
+
+ if (filedata == NULL)
+ return TRUE;
+
+ translate_file (filedata);
+
+ g_free (filedata);
+
+ if (fdata == NULL)
+ return TRUE;
+
+ if ((event_data->node == NULL) || (*event_data->node == '\0'))
+ event_data->node = "[main]";
+
+ main_node = search_string (fdata, event_data->node);
+
+ if (main_node == NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot find node %s in help file"), event_data->node);
+
+ /* Fallback to [main], return if it also cannot be found */
+ main_node = search_string (fdata, "[main]");
+ if (main_node == NULL)
+ {
+ interactive_display_finish ();
+ return TRUE;
+ }
+ }
+
+ help_lines = MIN (LINES - 4, MAX (2 * LINES / 3, 18));
+
+ whelp =
+ dlg_create (TRUE, 0, 0, help_lines + 4, HELP_WINDOW_WIDTH + 4, WPOS_CENTER | WPOS_TRYUP,
+ FALSE, help_colors, help_callback, NULL, "[Help]", _("Help"));
+ wh = WIDGET (whelp);
+ g = GROUP (whelp);
+ wh->keymap = help_map;
+ widget_want_tab (wh, TRUE);
+ /* draw background */
+ whelp->bg->callback = help_bg_callback;
+
+ selected_item = search_string_node (main_node, STRING_LINK_START) - 1;
+ currentpoint = main_node + 1; /* Skip the newline following the start of the node */
+
+ for (history_ptr = HISTORY_SIZE; history_ptr;)
+ {
+ history_ptr--;
+ history[history_ptr].page = currentpoint;
+ history[history_ptr].link = selected_item;
+ }
+
+ help_bar = buttonbar_new ();
+ WIDGET (help_bar)->rect.y -= wh->rect.y;
+ WIDGET (help_bar)->rect.x -= wh->rect.x;
+
+ r.lines = help_lines;
+ r.cols = HELP_WINDOW_WIDTH - 2;
+ md = mousedispatch_new (&r);
+
+ group_add_widget (g, md);
+ group_add_widget (g, help_bar); /* FIXME */
+
+ buttonbar_set_label (help_bar, 1, Q_ ("ButtonBar|Help"), wh->keymap, NULL);
+ buttonbar_set_label (help_bar, 2, Q_ ("ButtonBar|Index"), wh->keymap, NULL);
+ buttonbar_set_label (help_bar, 3, Q_ ("ButtonBar|Prev"), wh->keymap, NULL);
+ buttonbar_set_label (help_bar, 4, "", wh->keymap, NULL);
+ buttonbar_set_label (help_bar, 5, "", wh->keymap, NULL);
+ buttonbar_set_label (help_bar, 6, "", wh->keymap, NULL);
+ buttonbar_set_label (help_bar, 7, "", wh->keymap, NULL);
+ buttonbar_set_label (help_bar, 8, "", wh->keymap, NULL);
+ buttonbar_set_label (help_bar, 9, "", wh->keymap, NULL);
+ buttonbar_set_label (help_bar, 10, Q_ ("ButtonBar|Quit"), wh->keymap, NULL);
+
+ dlg_run (whelp);
+ interactive_display_finish ();
+ widget_destroy (wh);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/help.h b/src/help.h
new file mode 100644
index 0000000..8d0854c
--- /dev/null
+++ b/src/help.h
@@ -0,0 +1,57 @@
+/** \file help.h
+ * \brief Header: hypertext file browser
+ *
+ * Implements the hypertext file viewer.
+ * The hypertext file is a file that may have one or more nodes. Each
+ * node ends with a ^D character and starts with a bracket, then the
+ * name of the node and then a closing bracket. Right after the closing
+ * bracket a newline is placed. This newline is not to be displayed by
+ * the help viewer and must be skipped - its sole purpose is to facilitate
+ * the work of the people managing the help file template (xnc.hlp) .
+ *
+ * Links in the hypertext file are specified like this: the text that
+ * will be highlighted should have a leading ^A, then it comes the
+ * text, then a ^B indicating that highlighting is done, then the name
+ * of the node you want to link to and then a ^C.
+ *
+ * The file must contain a ^D at the beginning and at the end of the
+ * file or the program will not be able to detect the end of file.
+ *
+ * Laziness/widgeting attack: This file does use the dialog manager
+ * and uses mainly the dialog to achieve the help work. there is only
+ * one specialized widget and it's only used to forward the mouse messages
+ * to the appropriate routine.
+ *
+ * This file is included by help.c and man2hlp.c
+ */
+
+#ifndef MC__HELP_H
+#define MC__HELP_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* Markers used in the help files */
+#define CHAR_LINK_START '\01' /* Ctrl-A */
+#define CHAR_LINK_POINTER '\02' /* Ctrl-B */
+#define CHAR_LINK_END '\03' /* Ctrl-C */
+#define CHAR_NODE_END '\04' /* Ctrl-D */
+#define CHAR_ALTERNATE '\05' /* Ctrl-E */
+#define CHAR_NORMAL '\06' /* Ctrl-F */
+#define CHAR_VERSION '\07' /* Ctrl-G */
+#define CHAR_FONT_BOLD '\010' /* Ctrl-H */
+#define CHAR_FONT_NORMAL '\013' /* Ctrl-K */
+#define CHAR_FONT_ITALIC '\024' /* Ctrl-T */
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+gboolean help_interactive_display (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__HELP_H */
diff --git a/src/history.h b/src/history.h
new file mode 100644
index 0000000..52109b0
--- /dev/null
+++ b/src/history.h
@@ -0,0 +1,61 @@
+/** \file src/history.h
+ * \brief Header: defines history section names
+ */
+
+#ifndef MC__HISTORY_H
+#define MC__HISTORY_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* history section names */
+
+#define MC_HISTORY_EDIT_SAVE_AS "mc.edit.save-as"
+#define MC_HISTORY_EDIT_LOAD "mc.edit.load"
+#define MC_HISTORY_EDIT_SAVE_BLOCK "mc.edit.save-block"
+#define MC_HISTORY_EDIT_INSERT_FILE "mc.edit.insert-file"
+#define MC_HISTORY_EDIT_GOTO_LINE "mc.edit.goto-line"
+#define MC_HISTORY_EDIT_SORT "mc.edit.sort"
+#define MC_HISTORY_EDIT_PASTE_EXTCMD "mc.edit.paste-extcmd"
+#define MC_HISTORY_EDIT_REPEAT "mc.edit.repeat-action"
+
+#define MC_HISTORY_FM_VIEW_FILE "mc.fm.view-file"
+#define MC_HISTORY_FM_MKDIR "mc.fm.mkdir"
+#define MC_HISTORY_FM_LINK "mc.fm.link"
+#define MC_HISTORY_FM_EDIT_LINK "mc.fm.edit-link"
+#define MC_HISTORY_FM_TREE_COPY "mc.fm.tree-copy"
+#define MC_HISTORY_FM_TREE_MOVE "mc.fm.tree-move"
+#define MC_HISTORY_FM_PANELIZE_ADD "mc.fm.panelize.add"
+#define MC_HISTORY_FM_FILTERED_VIEW "mc.fm.filtered-view"
+#define MC_HISTORY_FM_PANEL_SELECT ":select_cmd: Select "
+#define MC_HISTORY_FM_PANEL_UNSELECT ":select_cmd: Unselect "
+#define MC_HISTORY_FM_PANEL_FILTER "mc.fm.panel-filter"
+#define MC_HISTORY_FM_MENU_EXEC_PARAM "mc.fm.menu.exec.parameter"
+
+#define MC_HISTORY_ESC_TIMEOUT "mc.esc.timeout"
+
+#define MC_HISTORY_VIEW_GOTO "mc.view.goto"
+#define MC_HISTORY_VIEW_GOTO_LINE "mc.view.goto-line"
+#define MC_HISTORY_VIEW_GOTO_ADDR "mc.view.goto-addr"
+#define MC_HISTORY_VIEW_SEARCH_REGEX "mc.view.search.regex"
+
+#define MC_HISTORY_FTPFS_ACCOUNT "mc.vfs.ftp.account"
+
+#define MC_HISTORY_EXT_PARAMETER "mc.ext.parameter"
+
+#define MC_HISTORY_HOTLIST_ADD "mc.hotlist.add"
+
+#define MC_HISTORY_SHARED_SEARCH "mc.shared.search"
+
+#define MC_HISTORY_YDIFF_GOTO_LINE "mc.ydiff.goto-line"
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__HISTORY_H */
diff --git a/src/keymap.c b/src/keymap.c
new file mode 100644
index 0000000..3f6cce4
--- /dev/null
+++ b/src/keymap.c
@@ -0,0 +1,985 @@
+/*
+ Default values and initialization of keybinding engine
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Vitja Makarov, 2005
+ Ilia Maslakov <il.smind@gmail.com>, 2009, 2010
+ Andrew Borodin <aborodin@vmail.ru>, 2010-2021
+
+ 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 "lib/global.h"
+
+#include "lib/fileloc.h"
+#include "lib/keybind.h"
+#include "lib/mcconfig.h" /* mc_config_t */
+#include "lib/util.h"
+#include "lib/widget.h" /* dialog_map, input_map, listbox_map, menu_map, radio_map */
+
+#include "args.h" /* mc_args__keymap_file */
+
+#include "keymap.h"
+
+/*** global variables ****************************************************************************/
+
+GArray *filemanager_keymap = NULL;
+GArray *filemanager_x_keymap = NULL;
+GArray *panel_keymap = NULL;
+GArray *dialog_keymap = NULL;
+GArray *menu_keymap = NULL;
+GArray *input_keymap = NULL;
+GArray *listbox_keymap = NULL;
+GArray *radio_keymap = NULL;
+GArray *tree_keymap = NULL;
+GArray *help_keymap = NULL;
+#ifdef ENABLE_EXT2FS_ATTR
+GArray *chattr_keymap = NULL;
+#endif
+#ifdef USE_INTERNAL_EDIT
+GArray *editor_keymap = NULL;
+GArray *editor_x_keymap = NULL;
+#endif
+GArray *viewer_keymap = NULL;
+GArray *viewer_hex_keymap = NULL;
+#ifdef USE_DIFF_VIEW
+GArray *diff_keymap = NULL;
+#endif
+
+const global_keymap_t *filemanager_map = NULL;
+const global_keymap_t *filemanager_x_map = NULL;
+const global_keymap_t *panel_map = NULL;
+const global_keymap_t *tree_map = NULL;
+const global_keymap_t *help_map = NULL;
+#ifdef ENABLE_EXT2FS_ATTR
+const global_keymap_t *chattr_map = NULL;
+#endif
+#ifdef USE_INTERNAL_EDIT
+const global_keymap_t *editor_map = NULL;
+const global_keymap_t *editor_x_map = NULL;
+#endif
+const global_keymap_t *viewer_map = NULL;
+const global_keymap_t *viewer_hex_map = NULL;
+#ifdef USE_DIFF_VIEW
+const global_keymap_t *diff_map = NULL;
+#endif
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/* default keymaps in ini (key=value) format */
+typedef struct global_keymap_ini_t
+{
+ const char *key;
+ const char *value;
+} global_keymap_ini_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* midnight */
+static const global_keymap_ini_t default_filemanager_keymap[] = {
+ {"ChangePanel", "tab; ctrl-i"},
+ {"Help", "f1"},
+ {"UserMenu", "f2"},
+ {"View", "f3"},
+ {"Edit", "f4"},
+ {"Copy", "f5"},
+ {"Move", "f6"},
+ {"MakeDir", "f7"},
+ {"Delete", "f8"},
+ {"Menu", "f9"},
+ {"Quit", "f10"},
+ {"MenuLastSelected", "f19"},
+ {"QuitQuiet", "f20"},
+ {"History", "alt-h"},
+ {"EditorViewerHistory", "alt-shift-e"},
+ {"DirSize", "ctrl-space"},
+ /* Copy useful information to the command line */
+ {"PutCurrentPath", "alt-a"},
+ {"PutOtherPath", "alt-shift-a"},
+ {"PutCurrentSelected", "alt-enter; ctrl-enter"},
+ {"PutCurrentFullSelected", "ctrl-shift-enter"},
+ {"CdQuick", "alt-c"},
+ /* To access the directory hotlist */
+ {"HotList", "ctrl-backslash"},
+ /* Suspend */
+ {"Suspend", "ctrl-z"},
+ /* The filtered view command */
+ {"ViewFiltered", "alt-exclamation"},
+ /* Find file */
+ {"Find", "alt-question"},
+ /* Panel refresh */
+ {"Reread", "ctrl-r"},
+ /* Switch listing between long, user defined and full formats */
+ /* Swap panels */
+ {"Swap", "ctrl-u"},
+ /* Resize panels */
+ {"SplitEqual", "alt-equal"},
+ {"SplitMore", "alt-shift-right"},
+ {"SplitLess", "alt-shift-left"},
+ /* View output */
+ {"Shell", "ctrl-o"},
+ {"ShowHidden", "alt-dot"},
+ {"SplitVertHoriz", "alt-comma"},
+ {"ExtendedKeyMap", "ctrl-x"},
+ /* Select/unselect group */
+ {"Select", "kpplus"},
+ {"Unselect", "kpminus"},
+ {"SelectInvert", "kpasterisk"},
+ /* List of screens */
+ {"ScreenList", "alt-prime"},
+ {NULL, NULL}
+};
+
+static const global_keymap_ini_t default_filemanager_x_keymap[] = {
+ {"CompareDirs", "d"},
+#ifdef USE_DIFF_VIEW
+ {"CompareFiles", "ctrl-d"},
+#endif /* USE_DIFF_VIEW */
+#ifdef ENABLE_VFS
+ {"VfsList", "a"},
+#endif /* ENABLE_VFS */
+ {"PutCurrentPath", "p"},
+ {"PutOtherPath", "ctrl-p"},
+ {"PutCurrentTagged", "t"},
+ {"PutOtherTagged", "ctrl-t"},
+ {"ChangeMode", "c"},
+ {"ChangeOwn", "o"},
+#ifdef ENABLE_EXT2FS_ATTR
+ {"ChangeAttributes", "e"},
+#endif /* ENABLE_EXT2FS_ATTR */
+ {"PutCurrentLink", "r"},
+ {"PutOtherLink", "ctrl-r"},
+ {"Link", "l"},
+ {"LinkSymbolic", "s"},
+ {"LinkSymbolicRelative", "v"},
+ {"LinkSymbolicEdit", "ctrl-s"},
+ {"PanelInfo", "i"},
+ {"PanelQuickView", "q"},
+ {"HotListAdd", "h"},
+#ifdef ENABLE_BACKGROUND
+ {"Jobs", "j"},
+#endif /* ENABLE_BACKGROUND */
+ {"ExternalPanelize", "!"},
+ {NULL, NULL}
+};
+
+/* panel */
+static const global_keymap_ini_t default_panel_keymap[] = {
+ {"CycleListingFormat", "alt-t"},
+ {"PanelOtherCd", "alt-o"},
+ {"PanelOtherCdLink", "alt-l"},
+ {"CopySingle", "f15"},
+ {"DeleteSingle", "f18"},
+ {"Enter", "enter"},
+ {"EditNew", "f14"},
+ {"MoveSingle", "f16"},
+ {"SelectInvert", "alt-asterisk"},
+ {"Select", "alt-plus"},
+ {"Unselect", "alt-minus"},
+ {"ViewRaw", "f13"},
+ {"CdChild", "ctrl-pgdn"},
+ {"CdParent", "ctrl-pgup"},
+ {"History", "alt-shift-h"},
+ {"HistoryNext", "alt-u"},
+ {"HistoryPrev", "alt-y"},
+ {"BottomOnScreen", "alt-j"},
+ {"MiddleOnScreen", "alt-r"},
+ {"TopOnScreen", "alt-g"},
+ {"Mark", "insert; ctrl-t"},
+ {"MarkDown", "shift-down"},
+ {"MarkUp", "shift-up"},
+ {"Up", "up; ctrl-p"},
+ {"Down", "down; ctrl-n"},
+ {"Left", "left"},
+ {"Right", "right"},
+ {"Top", "alt-lt; home; a1"},
+ {"Bottom", "alt-gt; end; c1"},
+ {"PageDown", "pgdn; ctrl-v"},
+ {"PageUp", "pgup; alt-v"},
+#ifdef HAVE_CHARSET
+ {"SelectCodepage", "alt-e"},
+#endif
+ {"Search", "ctrl-s; alt-s"},
+ {"PanelOtherSync", "alt-i"},
+ {NULL, NULL}
+};
+
+/* dialog */
+static const global_keymap_ini_t default_dialog_keymap[] = {
+ {"Ok", "enter"},
+ {"Cancel", "f10; esc; ctrl-g"},
+ {"Up", "up; left"},
+ {"Down", "down; right"},
+#if 0
+ {"Left", "up; left"},
+ {"Right", "down; right"},
+#endif
+ {"Help", "f1"},
+ {"Suspend", "ctrl-z"},
+ {"Refresh", "ctrl-l"},
+ {"ScreenList", "alt-prime"},
+ {"ScreenNext", "alt-rbrace"},
+ {"ScreenPrev", "alt-lbrace"},
+ {NULL, NULL}
+};
+
+/* menubar */
+static const global_keymap_ini_t default_menu_keymap[] = {
+ {"Help", "f1"},
+ {"Left", "left; ctrl-b"},
+ {"Right", "right; ctrl-f"},
+ {"Up", "up; ctrl-p"},
+ {"Down", "down; ctrl-n"},
+ {"Home", "home; alt-lt; ctrl-a"},
+ {"End", "end; alt-gt; ctrl-e"},
+ {"Enter", "enter"},
+ {"Quit", "f10; ctrl-g; esc"},
+ {NULL, NULL}
+};
+
+/* input line */
+static const global_keymap_ini_t default_input_keymap[] = {
+ /* Motion */
+ {"Home", "ctrl-a; alt-lt; home; a1"},
+ {"End", "ctrl-e; alt-gt; end; c1"},
+ {"Left", "left; alt-left; ctrl-b"},
+ {"Right", "right; alt-right; ctrl-f"},
+ {"WordLeft", "ctrl-left; alt-b"},
+ {"WordRight", "ctrl-right; alt-f"},
+ /* Mark */
+ {"MarkLeft", "shift-left"},
+ {"MarkRight", "shift-right"},
+ {"MarkToWordBegin", "ctrl-shift-left"},
+ {"MarkToWordEnd", "ctrl-shift-right"},
+ {"MarkToHome", "shift-home"},
+ {"MarkToEnd", "shift-end"},
+ /* Editing */
+ {"Backspace", "backspace; ctrl-h"},
+ {"Delete", "delete; ctrl-d"},
+ {"DeleteToWordEnd", "alt-d"},
+ {"DeleteToWordBegin", "alt-backspace"},
+ /* Region manipulation */
+ {"Remove", "ctrl-w"},
+ {"Store", "alt-w"},
+ {"Yank", "ctrl-y"},
+ {"DeleteToEnd", "ctrl-k"},
+ /* History */
+ {"History", "alt-h"},
+ {"HistoryPrev", "alt-p; ctrl-down"},
+ {"HistoryNext", "alt-n; ctrl-up"},
+ /* Completion */
+ {"Complete", "alt-tab"},
+ {NULL, NULL}
+};
+
+/* listbox */
+static const global_keymap_ini_t default_listbox_keymap[] = {
+ {"Up", "up; ctrl-p"},
+ {"Down", "down; ctrl-n"},
+ {"Top", "home; alt-lt; a1"},
+ {"Bottom", "end; alt-gt; c1"},
+ {"PageUp", "pgup; alt-v"},
+ {"PageDown", "pgdn; ctrl-v"},
+ {"Delete", "delete; d"},
+ {"Clear", "shift-delete; shift-d"},
+ {"View", "f3"},
+ {"Edit", "f4"},
+ {"Enter", "enter"},
+ {NULL, NULL}
+};
+
+/* radio */
+static const global_keymap_ini_t default_radio_keymap[] = {
+ {"Up", "up; ctrl-p"},
+ {"Down", "down; ctrl-n"},
+ {"Top", "home; alt-lt; a1"},
+ {"Bottom", "end; alt-gt; c1"},
+ {"Select", "space"},
+ {NULL, NULL}
+};
+
+/* tree */
+static const global_keymap_ini_t default_tree_keymap[] = {
+ {"Help", "f1"},
+ {"Rescan", "f2; ctrl-r"},
+ {"Forget", "f3"},
+ {"ToggleNavigation", "f4"},
+ {"Copy", "f5"},
+ {"Move", "f6"},
+#if 0
+ {"MakeDir", "f7"},
+#endif
+ {"Delete", "f8; delete"},
+ {"Up", "up; ctrl-p"},
+ {"Down", "down; ctrl-n"},
+ {"Left", "left"},
+ {"Right", "right"},
+ {"Top", "home; alt-lt; a1"},
+ {"Bottom", "end; alt-gt; c1"},
+ {"PageUp", "pgup; alt-v"},
+ {"PageDown", "pgdn; ctrl-v"},
+ {"Enter", "enter"},
+ {"Search", "ctrl-s; alt-s"},
+ {NULL, NULL}
+};
+
+/* help */
+static const global_keymap_ini_t default_help_keymap[] = {
+ {"Help", "f1"},
+ {"Index", "f2; c"},
+ {"Back", "f3; left; l"},
+ {"Quit", "f10; esc"},
+ {"Up", "up; ctrl-p"},
+ {"Down", "down; ctrl-n"},
+ {"PageDown", "f; space; pgdn; ctrl-v"},
+ {"PageUp", "b; pgup; alt-v; backspace"},
+ {"HalfPageDown", "d"},
+ {"HalfPageUp", "u"},
+ {"Top", "home; ctrl-home; ctrl-pgup; a1; alt-lt; g"},
+ {"Bottom", "end; ctrl-end; ctrl-pgdn; c1; alt-gt; shift-g"},
+ {"Enter", "right; enter"},
+ {"LinkNext", "tab"},
+ {"LinkPrev", "alt-tab"},
+ {"NodeNext", "n"},
+ {"NodePrev", "p"},
+ {NULL, NULL}
+};
+
+#ifdef ENABLE_EXT2FS_ATTR
+/* chattr dialog */
+static const global_keymap_ini_t default_chattr_keymap[] = {
+ {"Up", "up; left; ctrl-p"},
+ {"Down", "down; right; ctrl-n"},
+ {"Top", "home; alt-lt; a1"},
+ {"Bottom", "end; alt-gt; c1"},
+ {"PageUp", "pgup; alt-v"},
+ {"PageDown", "pgdn; ctrl-v"},
+ {"Mark", "t; shift-t"},
+ {"MarkAndDown", "insert"},
+ {NULL, NULL}
+};
+#endif /* ENABLE_EXT2FS_ATTR */
+
+#ifdef USE_INTERNAL_EDIT
+static const global_keymap_ini_t default_editor_keymap[] = {
+ {"Enter", "enter"},
+ {"Return", "shift-enter; ctrl-enter; ctrl-shift-enter"}, /* useful for pasting multiline text */
+ {"Tab", "tab; shift-tab; ctrl-tab; ctrl-shift-tab"}, /* ditto */
+ {"BackSpace", "backspace; ctrl-h"},
+ {"Delete", "delete; ctrl-d"},
+ {"Left", "left"},
+ {"Right", "right"},
+ {"Up", "up"},
+ {"Down", "down"},
+ {"Home", "home"},
+ {"End", "end"},
+ {"PageUp", "pgup"},
+ {"PageDown", "pgdn"},
+ {"WordLeft", "ctrl-left; ctrl-z"},
+ {"WordRight", "ctrl-right; ctrl-x"},
+ {"InsertOverwrite", "insert"},
+ {"Help", "f1"},
+ {"Save", "f2"},
+ {"Mark", "f3"},
+ {"Replace", "f4"},
+ {"Copy", "f5"},
+ {"Move", "f6"},
+ {"Search", "f7"},
+ {"Remove", "f8; ctrl-delete"},
+ {"Menu", "f9"},
+ {"Quit", "f10; esc"},
+ {"UserMenu", "f11"},
+ {"SaveAs", "f12; ctrl-f2"},
+ {"MarkColumn", "f13"},
+ {"ReplaceContinue", "f14; ctrl-f4"},
+ {"InsertFile", "f15"},
+ {"SearchContinue", "f17; ctrl-f7"},
+ {"EditNew", "ctrl-n"},
+ {"DeleteToWordBegin", "alt-backspace"},
+ {"DeleteToWordEnd", "alt-d"},
+ {"DeleteLine", "ctrl-y"},
+ {"DeleteToEnd", "ctrl-k"},
+ {"Undo", "ctrl-u; ctrl-backspace"},
+ {"Redo", "alt-r"},
+#ifdef HAVE_CHARSET
+ {"SelectCodepage", "alt-e"},
+#endif
+ {"Goto", "alt-l; alt-shift-l"},
+ {"Refresh", "ctrl-l"},
+ {"Shell", "ctrl-o"},
+ {"Top", "ctrl-home; ctrl-pgup; alt-lt"},
+ {"Bottom", "ctrl-end; ctrl-pgdn; alt-gt"},
+ {"TopOnScreen", "ctrl-pgup"},
+ {"BottomOnScreen", "ctrl-pgdn"},
+ {"ScrollUp", "ctrl-up"},
+ {"ScrollDown", "ctrl-down"},
+ {"Store", "ctrl-insert"},
+ {"Paste", "shift-insert"},
+ {"Cut", "shift-delete"},
+ {"BlockSave", "ctrl-f"},
+ {"MarkLeft", "shift-left"},
+ {"MarkRight", "shift-right"},
+ {"MarkUp", "shift-up"},
+ {"MarkDown", "shift-down"},
+ {"MarkPageUp", "shift-pgup"},
+ {"MarkPageDown", "shift-pgdn"},
+ {"MarkToWordBegin", "ctrl-shift-left"},
+ {"MarkToWordEnd", "ctrl-shift-right"},
+ {"MarkToHome", "shift-home"},
+ {"MarkToEnd", "shift-end"},
+ {"MarkToFileBegin", "ctrl-shift-home"},
+ {"MarkToFileEnd", "ctrl-shift-end"},
+ {"MarkToPageBegin", "ctrl-shift-pgup"},
+ {"MarkToPageEnd", "ctrl-shift-pgdn"},
+ {"MarkScrollUp", "ctrl-shift-up"},
+ {"MarkScrollDown", "ctrl-shift-down"},
+ {"MarkColumnLeft", "alt-left"},
+ {"MarkColumnRight", "alt-right"},
+ {"MarkColumnUp", "alt-up"},
+ {"MarkColumnDown", "alt-down"},
+ {"MarkColumnPageUp", "alt-pgup"},
+ {"MarkColumnPageDown", "alt-pgdn"},
+ {"InsertLiteral", "ctrl-q"},
+ {"Complete", "alt-tab"},
+ {"MatchBracket", "alt-b"},
+ {"ParagraphFormat", "alt-p"},
+ {"Bookmark", "alt-k"},
+ {"BookmarkFlush", "alt-o"},
+ {"BookmarkNext", "alt-j"},
+ {"BookmarkPrev", "alt-i"},
+ {"MacroStartStopRecord", "ctrl-r"},
+ {"MacroExecute", "ctrl-a"},
+ {"ShowNumbers", "alt-n"},
+ {"ShowTabTws", "alt-underline"},
+ {"SyntaxOnOff", "ctrl-s"},
+ {"Find", "alt-enter"},
+ {"FilePrev", "alt-minus"},
+ {"FileNext", "alt-plus"},
+ {"Sort", "alt-t"},
+ {"Mail", "alt-m"},
+ {"ExternalCommand", "alt-u"},
+#ifdef HAVE_ASPELL
+ {"SpellCheckCurrentWord", "ctrl-p"},
+#endif
+ {"ExtendedKeyMap", "ctrl-x"},
+ {NULL, NULL}
+};
+
+/* emacs keyboard layout emulation */
+static const global_keymap_ini_t default_editor_x_keymap[] = {
+ {NULL, NULL}
+};
+#endif /* USE_INTERNAL_EDIT */
+
+/* viewer */
+static const global_keymap_ini_t default_viewer_keymap[] = {
+ {"Help", "f1"},
+ {"WrapMode", "f2"},
+ {"Quit", "f3; f10; q; esc"},
+ {"HexMode", "f4"},
+ {"Goto", "f5"},
+ {"Search", "f7"},
+ {"SearchContinue", "f17; n"},
+ {"MagicMode", "f8"},
+ {"NroffMode", "f9"},
+ {"Home", "ctrl-a"},
+ {"End", "ctrl-e"},
+ {"Left", "h; left"},
+ {"Right", "l; right"},
+ {"LeftQuick", "ctrl-left"},
+ {"RightQuick", "ctrl-right"},
+ {"Up", "k; y; insert; up; ctrl-p"},
+ {"Down", "j; e; delete; down; enter; ctrl-n"},
+ {"PageDown", "f; space; pgdn; ctrl-v"},
+ {"PageUp", "b; pgup; alt-v; backspace"},
+ {"HalfPageDown", "d"},
+ {"HalfPageUp", "u"},
+ {"Top", "home; ctrl-home; ctrl-pgup; a1; alt-lt; g"},
+ {"Bottom", "end; ctrl-end; ctrl-pgdn; c1; alt-gt; shift-g"},
+ {"BookmarkGoto", "m"},
+ {"Bookmark", "r"},
+ {"FileNext", "ctrl-f"},
+ {"FilePrev", "ctrl-b"},
+#ifdef HAVE_CHARSET
+ {"SelectCodepage", "alt-e"},
+#endif
+ {"Shell", "ctrl-o"},
+ {"Ruler", "alt-r"},
+ {"SearchForward", "slash"},
+ {"SearchBackward", "question"},
+ {"SearchForwardContinue", "ctrl-s"},
+ {"SearchBackwardContinue", "ctrl-r"},
+ {"SearchOppositeContinue", "shift-n"},
+ {"History", "alt-shift-e"},
+ {NULL, NULL}
+};
+
+/* hex viewer */
+static const global_keymap_ini_t default_viewer_hex_keymap[] = {
+ {"Help", "f1"},
+ {"HexEditMode", "f2"},
+ {"Quit", "f3; f10; q; esc"},
+ {"HexMode", "f4"},
+ {"Goto", "f5"},
+ {"Save", "f6"},
+ {"Search", "f7"},
+ {"SearchContinue", "f17; n"},
+ {"MagicMode", "f8"},
+ {"NroffMode", "f9"},
+ {"ToggleNavigation", "tab"},
+ {"Home", "ctrl-a; home"},
+ {"End", "ctrl-e; end"},
+ {"Left", "b; left"},
+ {"Right", "f; right"},
+ {"Up", "k; y; up"},
+ {"Down", "j; delete; down"},
+ {"PageDown", "pgdn; ctrl-v"},
+ {"PageUp", "pgup; alt-v"},
+ {"Top", "ctrl-home; ctrl-pgup; a1; alt-lt; g"},
+ {"Bottom", "ctrl-end; ctrl-pgdn; c1; alt-gt; shift-g"},
+#ifdef HAVE_CHARSET
+ {"SelectCodepage", "alt-e"},
+#endif
+ {"Shell", "ctrl-o"},
+ {"SearchForward", "slash"},
+ {"SearchBackward", "question"},
+ {"SearchForwardContinue", "ctrl-s"},
+ {"SearchBackwardContinue", "ctrl-r"},
+ {"SearchOppositeContinue", "shift-n"},
+ {"History", "alt-shift-e"},
+ {NULL, NULL}
+};
+
+#ifdef USE_DIFF_VIEW
+/* diff viewer */
+static const global_keymap_ini_t default_diff_keymap[] = {
+ {"ShowSymbols", "alt-s; s"},
+ {"ShowNumbers", "alt-n; l"},
+ {"SplitFull", "f"},
+ {"SplitEqual", "equal"},
+ {"SplitMore", "gt"},
+ {"SplitLess", "lt"},
+ {"Tab2", "2"},
+ {"Tab3", "3"},
+ {"Tab4", "4"},
+ {"Tab8", "8"},
+ {"Swap", "ctrl-u"},
+ {"Redo", "ctrl-r"},
+ {"HunkNext", "n; enter; space"},
+ {"HunkPrev", "p; backspace"},
+ {"Goto", "g; shift-g"},
+ {"Save", "f2"},
+ {"Edit", "f4"},
+ {"EditOther", "f14"},
+ {"Merge", "f5"},
+ {"MergeOther", "f15"},
+ {"Search", "f7"},
+ {"SearchContinue", "f17"},
+ {"Options", "f9"},
+ {"Top", "ctrl-home"},
+ {"Bottom", "ctrl-end"},
+ {"Down", "down"},
+ {"Up", "up"},
+ {"LeftQuick", "ctrl-left"},
+ {"RightQuick", "ctrl-right"},
+ {"Left", "left"},
+ {"Right", "right"},
+ {"PageDown", "pgdn"},
+ {"PageUp", "pgup"},
+ {"Home", "home"},
+ {"End", "end"},
+ {"Help", "f1"},
+ {"Quit", "f10; q; shift-q; esc"},
+#ifdef HAVE_CHARSET
+ {"SelectCodepage", "alt-e"},
+#endif
+ {"Shell", "ctrl-o"},
+ {NULL, NULL}
+};
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+create_default_keymap_section (mc_config_t * keymap, const char *section,
+ const global_keymap_ini_t * k)
+{
+ size_t i;
+
+ for (i = 0; k[i].key != NULL; i++)
+ mc_config_set_string_raw (keymap, section, k[i].key, k[i].value);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mc_config_t *
+create_default_keymap (void)
+{
+ mc_config_t *keymap;
+
+ keymap = mc_config_init (NULL, TRUE);
+
+ create_default_keymap_section (keymap, KEYMAP_SECTION_FILEMANAGER, default_filemanager_keymap);
+ create_default_keymap_section (keymap, KEYMAP_SECTION_FILEMANAGER_EXT,
+ default_filemanager_x_keymap);
+ create_default_keymap_section (keymap, KEYMAP_SECTION_PANEL, default_panel_keymap);
+ create_default_keymap_section (keymap, KEYMAP_SECTION_DIALOG, default_dialog_keymap);
+ create_default_keymap_section (keymap, KEYMAP_SECTION_MENU, default_menu_keymap);
+ create_default_keymap_section (keymap, KEYMAP_SECTION_INPUT, default_input_keymap);
+ create_default_keymap_section (keymap, KEYMAP_SECTION_LISTBOX, default_listbox_keymap);
+ create_default_keymap_section (keymap, KEYMAP_SECTION_RADIO, default_radio_keymap);
+ create_default_keymap_section (keymap, KEYMAP_SECTION_TREE, default_tree_keymap);
+ create_default_keymap_section (keymap, KEYMAP_SECTION_HELP, default_help_keymap);
+#ifdef ENABLE_EXT2FS_ATTR
+ create_default_keymap_section (keymap, KEYMAP_SECTION_HELP, default_chattr_keymap);
+#endif
+#ifdef USE_INTERNAL_EDIT
+ create_default_keymap_section (keymap, KEYMAP_SECTION_EDITOR, default_editor_keymap);
+ create_default_keymap_section (keymap, KEYMAP_SECTION_EDITOR_EXT, default_editor_x_keymap);
+#endif
+ create_default_keymap_section (keymap, KEYMAP_SECTION_VIEWER, default_viewer_keymap);
+ create_default_keymap_section (keymap, KEYMAP_SECTION_VIEWER_HEX, default_viewer_hex_keymap);
+#ifdef USE_DIFF_VIEW
+ create_default_keymap_section (keymap, KEYMAP_SECTION_DIFFVIEWER, default_diff_keymap);
+#endif
+
+ return keymap;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+load_keymap_from_section (const char *section_name, GArray * keymap, mc_config_t * cfg)
+{
+ gchar **profile_keys, **keys;
+
+ if (section_name == NULL)
+ return;
+
+ keys = mc_config_get_keys (cfg, section_name, NULL);
+
+ for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
+ {
+ gchar **values;
+
+ values = mc_config_get_string_list (cfg, section_name, *profile_keys, NULL);
+ if (values != NULL)
+ {
+ long action;
+
+ action = keybind_lookup_action (*profile_keys);
+ if (action > 0)
+ {
+ gchar **curr_values;
+
+ for (curr_values = values; *curr_values != NULL; curr_values++)
+ keybind_cmd_bind (keymap, *curr_values, action);
+ }
+
+ g_strfreev (values);
+ }
+ }
+
+ g_strfreev (keys);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get name of config file.
+ *
+ * @param subdir If not NULL, config is also searched in specified subdir.
+ * @param config_file_name If relative, file if searched in standard paths.
+ *
+ * @return newly allocated string with config name or NULL if file is not found.
+ */
+
+static char *
+load_setup_get_full_config_name (const char *subdir, const char *config_file_name)
+{
+ /*
+ TODO: IMHO, in future, this function shall be placed in mcconfig module.
+ */
+ char *lc_basename, *ret;
+ char *file_name;
+
+ if (config_file_name == NULL)
+ return NULL;
+
+ /* check for .keymap suffix */
+ if (g_str_has_suffix (config_file_name, ".keymap"))
+ file_name = g_strdup (config_file_name);
+ else
+ file_name = g_strconcat (config_file_name, ".keymap", (char *) NULL);
+
+ canonicalize_pathname (file_name);
+
+ if (g_path_is_absolute (file_name))
+ return file_name;
+
+ lc_basename = g_path_get_basename (file_name);
+ g_free (file_name);
+
+ if (lc_basename == NULL)
+ return NULL;
+
+ if (subdir != NULL)
+ ret = g_build_filename (mc_config_get_path (), subdir, lc_basename, (char *) NULL);
+ else
+ ret = g_build_filename (mc_config_get_path (), lc_basename, (char *) NULL);
+
+ if (exist_file (ret))
+ {
+ g_free (lc_basename);
+ canonicalize_pathname (ret);
+ return ret;
+ }
+ g_free (ret);
+
+ if (subdir != NULL)
+ ret = g_build_filename (mc_global.share_data_dir, subdir, lc_basename, (char *) NULL);
+ else
+ ret = g_build_filename (mc_global.share_data_dir, lc_basename, (char *) NULL);
+
+ g_free (lc_basename);
+
+ if (exist_file (ret))
+ {
+ canonicalize_pathname (ret);
+ return ret;
+ }
+
+ g_free (ret);
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ Create new mc_config object from specified ini-file or
+ append data to existing mc_config object from ini-file
+*/
+
+static void
+load_setup_init_config_from_file (mc_config_t ** config, const char *fname, gboolean read_only)
+{
+ /*
+ TODO: IMHO, in future, this function shall be placed in mcconfig module.
+ */
+ if (exist_file (fname))
+ {
+ if (*config != NULL)
+ mc_config_read_file (*config, fname, read_only, TRUE);
+ else
+ *config = mc_config_init (fname, read_only);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mc_config_t *
+load_setup_get_keymap_profile_config (gboolean load_from_file)
+{
+ /*
+ TODO: IMHO, in future, this function shall be placed in mcconfig module.
+ */
+ mc_config_t *keymap_config;
+ char *share_keymap, *sysconfig_keymap;
+ char *fname, *fname2;
+
+ /* 0) Create default keymap */
+ keymap_config = create_default_keymap ();
+ if (!load_from_file)
+ return keymap_config;
+
+ /* load and merge global keymaps */
+
+ /* 1) /usr/share/mc (mc_global.share_data_dir) */
+ share_keymap = g_build_filename (mc_global.share_data_dir, GLOBAL_KEYMAP_FILE, (char *) NULL);
+ load_setup_init_config_from_file (&keymap_config, share_keymap, TRUE);
+
+ /* 2) /etc/mc (mc_global.sysconfig_dir) */
+ sysconfig_keymap =
+ g_build_filename (mc_global.sysconfig_dir, GLOBAL_KEYMAP_FILE, (char *) NULL);
+ load_setup_init_config_from_file (&keymap_config, sysconfig_keymap, TRUE);
+
+ /* then load and merge one of user-defined keymap */
+
+ /* 3) --keymap=<keymap> */
+ fname = load_setup_get_full_config_name (NULL, mc_args__keymap_file);
+ if (fname != NULL && strcmp (fname, sysconfig_keymap) != 0 && strcmp (fname, share_keymap) != 0)
+ {
+ load_setup_init_config_from_file (&keymap_config, fname, TRUE);
+ goto done;
+ }
+ g_free (fname);
+
+ /* 4) getenv("MC_KEYMAP") */
+ fname = load_setup_get_full_config_name (NULL, g_getenv ("MC_KEYMAP"));
+ if (fname != NULL && strcmp (fname, sysconfig_keymap) != 0 && strcmp (fname, share_keymap) != 0)
+ {
+ load_setup_init_config_from_file (&keymap_config, fname, TRUE);
+ goto done;
+ }
+
+ MC_PTR_FREE (fname);
+
+ /* 5) main config; [Midnight Commander] -> keymap */
+ fname2 = mc_config_get_string (mc_global.main_config, CONFIG_APP_SECTION, "keymap", NULL);
+ if (fname2 != NULL && *fname2 != '\0')
+ fname = load_setup_get_full_config_name (NULL, fname2);
+ g_free (fname2);
+ if (fname != NULL && strcmp (fname, sysconfig_keymap) != 0 && strcmp (fname, share_keymap) != 0)
+ {
+ load_setup_init_config_from_file (&keymap_config, fname, TRUE);
+ goto done;
+ }
+ g_free (fname);
+
+ /* 6) ${XDG_CONFIG_HOME}/mc/mc.keymap */
+ fname = mc_config_get_full_path (GLOBAL_KEYMAP_FILE);
+ load_setup_init_config_from_file (&keymap_config, fname, TRUE);
+
+ done:
+ g_free (fname);
+ g_free (sysconfig_keymap);
+ g_free (share_keymap);
+
+ return keymap_config;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+keymap_load (gboolean load_from_file)
+{
+ /*
+ * Load keymap from GLOBAL_KEYMAP_FILE before ${XDG_CONFIG_HOME}/mc/mc.keymap, so that the user
+ * definitions override global settings.
+ */
+ mc_config_t *mc_global_keymap;
+
+ mc_global_keymap = load_setup_get_keymap_profile_config (load_from_file);
+
+ if (mc_global_keymap != NULL)
+ {
+#define LOAD_KEYMAP(s,km) \
+ km##_keymap = g_array_new (TRUE, FALSE, sizeof (global_keymap_t)); \
+ load_keymap_from_section (KEYMAP_SECTION_##s, km##_keymap, mc_global_keymap)
+
+ LOAD_KEYMAP (FILEMANAGER, filemanager);
+ LOAD_KEYMAP (FILEMANAGER_EXT, filemanager_x);
+ LOAD_KEYMAP (PANEL, panel);
+ LOAD_KEYMAP (DIALOG, dialog);
+ LOAD_KEYMAP (MENU, menu);
+ LOAD_KEYMAP (INPUT, input);
+ LOAD_KEYMAP (LISTBOX, listbox);
+ LOAD_KEYMAP (RADIO, radio);
+ LOAD_KEYMAP (TREE, tree);
+ LOAD_KEYMAP (HELP, help);
+#ifdef ENABLE_EXT2FS_ATTR
+ LOAD_KEYMAP (CHATTR, chattr);
+#endif
+#ifdef USE_INTERNAL_EDIT
+ LOAD_KEYMAP (EDITOR, editor);
+ LOAD_KEYMAP (EDITOR_EXT, editor_x);
+#endif
+ LOAD_KEYMAP (VIEWER, viewer);
+ LOAD_KEYMAP (VIEWER_HEX, viewer_hex);
+#ifdef USE_DIFF_VIEW
+ LOAD_KEYMAP (DIFFVIEWER, diff);
+#endif
+
+#undef LOAD_KEYMAP
+ mc_config_deinit (mc_global_keymap);
+ }
+
+#define SET_MAP(m) \
+ m##_map = (global_keymap_t *) m##_keymap->data
+
+ SET_MAP (filemanager);
+ SET_MAP (filemanager_x);
+ SET_MAP (panel);
+ SET_MAP (dialog);
+ SET_MAP (menu);
+ SET_MAP (input);
+ SET_MAP (listbox);
+ SET_MAP (radio);
+ SET_MAP (tree);
+ SET_MAP (help);
+#ifdef ENABLE_EXT2FS_ATTR
+ SET_MAP (chattr);
+#endif
+#ifdef USE_INTERNAL_EDIT
+ SET_MAP (editor);
+ SET_MAP (editor_x);
+#endif
+ SET_MAP (viewer);
+ SET_MAP (viewer_hex);
+#ifdef USE_DIFF_VIEW
+ SET_MAP (diff);
+#endif
+
+#undef SET_MAP
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+keymap_free (void)
+{
+#define FREE_KEYMAP(km) \
+ if (km##_keymap != NULL) \
+ g_array_free (km##_keymap, TRUE)
+
+ FREE_KEYMAP (filemanager);
+ FREE_KEYMAP (filemanager_x);
+ FREE_KEYMAP (panel);
+ FREE_KEYMAP (dialog);
+ FREE_KEYMAP (menu);
+ FREE_KEYMAP (input);
+ FREE_KEYMAP (listbox);
+ FREE_KEYMAP (radio);
+ FREE_KEYMAP (tree);
+ FREE_KEYMAP (help);
+#ifdef ENABLE_EXT2FS_ATTR
+ FREE_KEYMAP (chattr);
+#endif
+#ifdef USE_INTERNAL_EDIT
+ FREE_KEYMAP (editor);
+ FREE_KEYMAP (editor_x);
+#endif
+ FREE_KEYMAP (viewer);
+ FREE_KEYMAP (viewer_hex);
+#ifdef USE_DIFF_VIEW
+ FREE_KEYMAP (diff);
+#endif
+
+#undef FREE_KEYMAP
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/keymap.h b/src/keymap.h
new file mode 100644
index 0000000..5737fc5
--- /dev/null
+++ b/src/keymap.h
@@ -0,0 +1,64 @@
+#ifndef MC__KEYBIND_DEFAULTS_H
+#define MC__KEYBIND_DEFAULTS_H
+
+#include "lib/global.h"
+#include "lib/keybind.h" /* global_keymap_t */
+#include "lib/mcconfig.h" /* mc_config_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern GArray *filemanager_keymap;
+extern GArray *filemanager_x_keymap;
+extern GArray *panel_keymap;
+extern GArray *dialog_keymap;
+extern GArray *menu_keymap;
+extern GArray *input_keymap;
+extern GArray *listbox_keymap;
+extern GArray *radio_keymap;
+extern GArray *tree_keymap;
+extern GArray *help_keymap;
+#ifdef ENABLE_EXT2FS_ATTR
+extern GArray *chattr_keymap;
+#endif
+#ifdef USE_INTERNAL_EDIT
+extern GArray *editor_keymap;
+extern GArray *editor_x_keymap;
+#endif
+extern GArray *viewer_keymap;
+extern GArray *viewer_hex_keymap;
+#ifdef USE_DIFF_VIEW
+extern GArray *diff_keymap;
+#endif
+
+extern const global_keymap_t *filemanager_map;
+extern const global_keymap_t *filemanager_x_map;
+extern const global_keymap_t *panel_map;
+extern const global_keymap_t *tree_map;
+extern const global_keymap_t *help_map;
+#ifdef ENABLE_EXT2FS_ATTR
+extern const global_keymap_t *chattr_map;
+#endif
+#ifdef USE_INTERNAL_EDIT
+extern const global_keymap_t *editor_map;
+extern const global_keymap_t *editor_x_map;
+#endif
+extern const global_keymap_t *viewer_map;
+extern const global_keymap_t *viewer_hex_map;
+#ifdef USE_DIFF_VIEW
+extern const global_keymap_t *diff_map;
+#endif
+
+/*** declarations of public functions ************************************************************/
+
+void keymap_load (gboolean load_from_file);
+void keymap_free (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__KEYBIND_DEFAULTS_H */
diff --git a/src/learn.c b/src/learn.c
new file mode 100644
index 0000000..c704ce1
--- /dev/null
+++ b/src/learn.c
@@ -0,0 +1,424 @@
+/*
+ Learn keys
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Jakub Jelinek, 1995
+ Andrew Borodin <aborodin@vmail.ru>, 2012, 2013
+
+ 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 learn.c
+ * \brief Source: learn keys module
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h"
+#include "lib/mcconfig.h"
+#include "lib/strescape.h"
+#include "lib/strutil.h"
+#include "lib/util.h" /* convert_controls() */
+#include "lib/widget.h"
+
+#include "setup.h"
+#include "learn.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define UX 4
+#define UY 2
+
+#define ROWS 13
+#define COLSHIFT 23
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ Widget *button;
+ Widget *label;
+ gboolean ok;
+ char *sequence;
+} learnkey_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static WDialog *learn_dlg;
+static const char *learn_title = N_("Learn keys");
+
+static learnkey_t *learnkeys = NULL;
+static int learn_total;
+static int learnok;
+static gboolean learnchanged = FALSE;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+learn_button (WButton * button, int action)
+{
+ WDialog *d;
+ char *seq;
+
+ (void) button;
+
+ d = create_message (D_ERROR, _("Teach me a key"),
+ _("Please press the %s\n"
+ "and then wait until this message disappears.\n\n"
+ "Then, press it again to see if OK appears\n"
+ "next to its button.\n\n"
+ "If you want to escape, press a single Escape key\n"
+ "and wait as well."), _(key_name_conv_tab[action - B_USER].longname));
+ mc_refresh ();
+ if (learnkeys[action - B_USER].sequence != NULL)
+ MC_PTR_FREE (learnkeys[action - B_USER].sequence);
+
+ seq = learn_key ();
+ if (seq != NULL)
+ {
+ /* Esc hides the dialog and do not allow definitions of
+ * regular characters
+ */
+ gboolean seq_ok = FALSE;
+
+ if (strcmp (seq, "\\e") != 0 && strcmp (seq, "\\e\\e") != 0
+ && strcmp (seq, "^m") != 0 && strcmp (seq, "^i") != 0
+ && (seq[1] != '\0' || *seq < ' ' || *seq > '~'))
+ {
+ learnchanged = TRUE;
+ learnkeys[action - B_USER].sequence = seq;
+ seq = convert_controls (seq);
+ seq_ok = define_sequence (key_name_conv_tab[action - B_USER].code, seq, MCKEY_NOACTION);
+ }
+
+ if (!seq_ok)
+ message (D_NORMAL, _("Cannot accept this key"), _("You have entered \"%s\""), seq);
+
+ g_free (seq);
+ }
+
+ dlg_run_done (d);
+ widget_destroy (WIDGET (d));
+
+ widget_select (learnkeys[action - B_USER].button);
+
+ return 0; /* Do not kill learn_dlg */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+learn_move (gboolean right)
+{
+ int i, totalcols;
+
+ totalcols = (learn_total - 1) / ROWS + 1;
+ for (i = 0; i < learn_total; i++)
+ if (learnkeys[i].button == WIDGET (GROUP (learn_dlg)->current->data))
+ {
+ if (right)
+ {
+ if (i < learn_total - ROWS)
+ i += ROWS;
+ else
+ i %= ROWS;
+ }
+ else
+ {
+ if (i / ROWS != 0)
+ i -= ROWS;
+ else if (i + (totalcols - 1) * ROWS >= learn_total)
+ i += (totalcols - 2) * ROWS;
+ else
+ i += (totalcols - 1) * ROWS;
+ }
+ widget_select (learnkeys[i].button);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+learn_check_key (int c)
+{
+ int i;
+
+ for (i = 0; i < learn_total; i++)
+ {
+ if (key_name_conv_tab[i].code != c || learnkeys[i].ok)
+ continue;
+
+ widget_select (learnkeys[i].button);
+ /* TRANSLATORS: This label appears near learned keys. Keep it short. */
+ label_set_text (LABEL (learnkeys[i].label), _("OK"));
+ learnkeys[i].ok = TRUE;
+ learnok++;
+ if (learnok >= learn_total)
+ {
+ learn_dlg->ret_value = B_CANCEL;
+ if (learnchanged)
+ {
+ if (query_dialog (learn_title,
+ _
+ ("It seems that all your keys already\n"
+ "work fine. That's great."), D_ERROR, 2,
+ _("&Save"), _("&Discard")) == 0)
+ learn_dlg->ret_value = B_ENTER;
+ }
+ else
+ {
+ message (D_ERROR, learn_title, "%s",
+ _
+ ("Great! You have a complete terminal database!\n"
+ "All your keys work well."));
+ }
+ dlg_close (learn_dlg);
+ }
+ return TRUE;
+ }
+
+ switch (c)
+ {
+ case KEY_LEFT:
+ case 'h':
+ return learn_move (FALSE);
+ case KEY_RIGHT:
+ case 'l':
+ return learn_move (TRUE);
+ case 'j':
+ group_select_next_widget (GROUP (learn_dlg));
+ return TRUE;
+ case 'k':
+ group_select_prev_widget (GROUP (learn_dlg));
+ return TRUE;
+ default:
+ break;
+ }
+
+ /* Prevent from disappearing if a non-defined sequence is pressed
+ and contains a button hotkey. Only recognize hotkeys with ALT. */
+ return (c < 255 && g_ascii_isalnum (c));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+learn_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_KEY:
+ return learn_check_key (parm) ? MSG_HANDLED : MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+init_learn (void)
+{
+ WGroup *g;
+
+ const int dlg_width = 78;
+ const int dlg_height = 23;
+
+ /* buttons */
+ int bx0, bx1;
+ const char *b0 = N_("&Save");
+ const char *b1 = N_("&Cancel");
+ int bl0, bl1;
+
+ int x, y, i;
+ const key_code_name_t *key;
+
+#ifdef ENABLE_NLS
+ static gboolean i18n_flag = FALSE;
+ if (!i18n_flag)
+ {
+ learn_title = _(learn_title);
+ i18n_flag = TRUE;
+ }
+
+ b0 = _(b0);
+ b1 = _(b1);
+#endif /* ENABLE_NLS */
+
+ do_refresh ();
+
+ learn_dlg =
+ dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, dialog_colors,
+ learn_callback, NULL, "[Learn keys]", learn_title);
+ g = GROUP (learn_dlg);
+
+ /* find first unshown button */
+ for (key = key_name_conv_tab, learn_total = 0;
+ key->name != NULL && strcmp (key->name, "kpleft") != 0; key++, learn_total++)
+ ;
+
+ learnok = 0;
+ learnchanged = FALSE;
+
+ learnkeys = g_new (learnkey_t, learn_total);
+
+ x = UX;
+ y = UY;
+
+ /* add buttons and "OK" labels */
+ for (i = 0; i < learn_total; i++)
+ {
+ char buffer[BUF_TINY];
+ const char *label;
+ int padding;
+
+ learnkeys[i].ok = FALSE;
+ learnkeys[i].sequence = NULL;
+
+ label = _(key_name_conv_tab[i].longname);
+ padding = 16 - str_term_width1 (label);
+ padding = MAX (0, padding);
+ g_snprintf (buffer, sizeof (buffer), "%s%*s", label, padding, "");
+
+ learnkeys[i].button =
+ WIDGET (button_new (y, x, B_USER + i, NARROW_BUTTON, buffer, learn_button));
+ learnkeys[i].label = WIDGET (label_new (y, x + 19, NULL));
+ group_add_widget (g, learnkeys[i].button);
+ group_add_widget (g, learnkeys[i].label);
+
+ y++;
+ if (y == UY + ROWS)
+ {
+ x += COLSHIFT;
+ y = UY;
+ }
+ }
+
+ group_add_widget (g, hline_new (dlg_height - 8, -1, -1));
+ group_add_widget (g, label_new (dlg_height - 7, 5,
+ _
+ ("Press all the keys mentioned here. After you have done it, check\n"
+ "which keys are not marked with OK. Press space on the missing\n"
+ "key, or click with the mouse to define it. Move around with Tab.")));
+ group_add_widget (g, hline_new (dlg_height - 4, -1, -1));
+ /* buttons */
+ bl0 = str_term_width1 (b0) + 5; /* default button */
+ bl1 = str_term_width1 (b1) + 3; /* normal button */
+ bx0 = (dlg_width - (bl0 + bl1 + 1)) / 2;
+ bx1 = bx0 + bl0 + 1;
+ group_add_widget (g, button_new (dlg_height - 3, bx0, B_ENTER, DEFPUSH_BUTTON, b0, NULL));
+ group_add_widget (g, button_new (dlg_height - 3, bx1, B_CANCEL, NORMAL_BUTTON, b1, NULL));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+learn_done (void)
+{
+ widget_destroy (WIDGET (learn_dlg));
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+learn_save (void)
+{
+ int i;
+ char *section;
+ gboolean profile_changed = FALSE;
+
+ section = g_strconcat ("terminal:", getenv ("TERM"), (char *) NULL);
+
+ for (i = 0; i < learn_total; i++)
+ if (learnkeys[i].sequence != NULL)
+ {
+ char *esc_str;
+
+ esc_str = strutils_escape (learnkeys[i].sequence, -1, ";\\", TRUE);
+ mc_config_set_string_raw_value (mc_global.main_config, section,
+ key_name_conv_tab[i].name, esc_str);
+ g_free (esc_str);
+
+ profile_changed = TRUE;
+ }
+
+ /* On the one hand no good idea to save the complete setup but
+ * without 'Auto save setup' the new key-definitions will not be
+ * saved unless the user does an 'Options/Save Setup'.
+ * On the other hand a save-button that does not save anything to
+ * disk is much worse.
+ */
+ if (profile_changed)
+ mc_config_save_file (mc_global.main_config, NULL);
+
+ g_free (section);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+learn_keys (void)
+{
+ gboolean save_old_esc_mode = old_esc_mode;
+ gboolean save_alternate_plus_minus = mc_global.tty.alternate_plus_minus;
+ int result;
+
+ /* old_esc_mode cannot work in learn keys dialog */
+ old_esc_mode = 0;
+
+ /* don't translate KP_ADD, KP_SUBTRACT and
+ KP_MULTIPLY to '+', '-' and '*' in
+ correct_key_code */
+ mc_global.tty.alternate_plus_minus = TRUE;
+ application_keypad_mode ();
+
+ init_learn ();
+ result = dlg_run (learn_dlg);
+
+ old_esc_mode = save_old_esc_mode;
+ mc_global.tty.alternate_plus_minus = save_alternate_plus_minus;
+
+ if (!mc_global.tty.alternate_plus_minus)
+ numeric_keypad_mode ();
+
+ if (result == B_ENTER)
+ learn_save ();
+
+ learn_done ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/learn.h b/src/learn.h
new file mode 100644
index 0000000..d0eb056
--- /dev/null
+++ b/src/learn.h
@@ -0,0 +1,21 @@
+/** \file learn.h
+ * \brief Header: learn keys module
+ */
+
+#ifndef MC__LEARN_H
+#define MC__LEARN_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void learn_keys (void);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__LEARN_H */
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..c18e069
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,556 @@
+/*
+ Main program for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1996, 1997
+ Janne Kukonlehto, 1994, 1995
+ Norbert Warmuth, 1997
+
+ 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 main.c
+ * \brief Source: this is a main module
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <locale.h>
+#include <pwd.h> /* for username in xterm title */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <unistd.h> /* getsid() */
+
+#include "lib/global.h"
+
+#include "lib/event.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* For init_key() */
+#include "lib/tty/mouse.h" /* init_mouse() */
+#include "lib/skin.h"
+#include "lib/filehighlight.h"
+#include "lib/fileloc.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/vfs/vfs.h" /* vfs_init(), vfs_shut() */
+
+#include "filemanager/filemanager.h"
+#include "filemanager/treestore.h" /* tree_store_save */
+#include "filemanager/layout.h"
+#include "filemanager/ext.h" /* flush_extension_file() */
+#include "filemanager/command.h" /* cmdline */
+#include "filemanager/panel.h" /* panalized_panel */
+
+#include "vfs/plugins_init.h"
+
+#include "events_init.h"
+#include "args.h"
+#ifdef ENABLE_SUBSHELL
+#include "subshell/subshell.h"
+#endif
+#include "keymap.h"
+#include "setup.h" /* load_setup() */
+
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#include "selcodepage.h"
+#endif /* HAVE_CHARSET */
+
+#include "consaver/cons.saver.h" /* cons_saver_pid */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+check_codeset (void)
+{
+ const char *current_system_codepage = NULL;
+
+ current_system_codepage = str_detect_termencoding ();
+
+#ifdef HAVE_CHARSET
+ {
+ const char *_display_codepage;
+
+ _display_codepage = get_codepage_id (mc_global.display_codepage);
+
+ if (strcmp (_display_codepage, current_system_codepage) != 0)
+ {
+ mc_global.display_codepage = get_codepage_index (current_system_codepage);
+ if (mc_global.display_codepage == -1)
+ mc_global.display_codepage = 0;
+
+ mc_config_set_string (mc_global.main_config, CONFIG_MISC_SECTION, "display_codepage",
+ cp_display);
+ }
+ }
+#endif
+
+ mc_global.utf8_display = str_isutf8 (current_system_codepage);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** POSIX version. The only version we support. */
+
+static void
+OS_Setup (void)
+{
+ const char *datadir_env;
+
+ mc_shell_init ();
+
+ /* This is the directory, where MC was installed, on Unix this is DATADIR */
+ /* and can be overridden by the MC_DATADIR environment variable */
+ datadir_env = g_getenv ("MC_DATADIR");
+ if (datadir_env != NULL)
+ mc_global.sysconfig_dir = g_strdup (datadir_env);
+ else
+ mc_global.sysconfig_dir = g_strdup (SYSCONFDIR);
+
+ mc_global.share_data_dir = g_strdup (DATADIR);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+sigchld_handler_no_subshell (int sig)
+{
+#ifdef __linux__
+ int pid, status;
+
+ if (mc_global.tty.console_flag == '\0')
+ return;
+
+ /* COMMENT: if it were true that after the call to handle_console(..INIT)
+ the value of mc_global.tty.console_flag never changed, we could simply not install
+ this handler at all if (!mc_global.tty.console_flag && !mc_global.tty.use_subshell). */
+
+ /* That comment is no longer true. We need to wait() on a sigchld
+ handler (that's at least what the tarfs code expects currently). */
+
+ 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';
+ }
+ }
+ /* If we got here, some other child exited; ignore it */
+#endif /* __linux__ */
+
+ (void) sig;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+init_sigchld (void)
+{
+ struct sigaction sigchld_action;
+
+ memset (&sigchld_action, 0, sizeof (sigchld_action));
+ sigchld_action.sa_handler =
+#ifdef ENABLE_SUBSHELL
+ mc_global.tty.use_subshell ? sigchld_handler :
+#endif /* ENABLE_SUBSHELL */
+ sigchld_handler_no_subshell;
+
+ sigemptyset (&sigchld_action.sa_mask);
+
+#ifdef SA_RESTART
+ sigchld_action.sa_flags = SA_RESTART;
+#endif /* !SA_RESTART */
+
+ if (sigaction (SIGCHLD, &sigchld_action, NULL) == -1)
+ {
+#ifdef ENABLE_SUBSHELL
+ /*
+ * This may happen on QNX Neutrino 6, where SA_RESTART
+ * is defined but not implemented. Fallback to no subshell.
+ */
+ mc_global.tty.use_subshell = FALSE;
+#endif /* ENABLE_SUBSHELL */
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check MC_SID to prevent running one mc from another.
+ *
+ * @return TRUE if no parent mc in our session was found, FALSE otherwise.
+ */
+
+static gboolean
+check_sid (void)
+{
+ pid_t my_sid, old_sid;
+ const char *sid_str;
+
+ sid_str = getenv ("MC_SID");
+ if (sid_str == NULL)
+ return TRUE;
+
+ old_sid = (pid_t) strtol (sid_str, NULL, 0);
+ if (old_sid == 0)
+ return TRUE;
+
+ my_sid = getsid (0);
+ if (my_sid == -1)
+ return TRUE;
+
+ /* The parent mc is in a different session, it's OK */
+ return (old_sid != my_sid);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+main (int argc, char *argv[])
+{
+ GError *mcerror = NULL;
+ int exit_code = EXIT_FAILURE;
+
+ mc_global.run_from_parent_mc = !check_sid ();
+
+ /* We had LC_CTYPE before, LC_ALL includs LC_TYPE as well */
+#ifdef HAVE_SETLOCALE
+ (void) setlocale (LC_ALL, "");
+#endif
+ (void) bindtextdomain (PACKAGE, LOCALEDIR);
+ (void) textdomain (PACKAGE);
+
+ /* do this before args parsing */
+ str_init_strings (NULL);
+
+ mc_setup_run_mode (argv); /* are we mc? editor? viewer? etc... */
+
+ if (!mc_args_parse (&argc, &argv, "mc", &mcerror))
+ {
+ startup_exit_falure:
+ fprintf (stderr, _("Failed to run:\n%s\n"), mcerror->message);
+ g_error_free (mcerror);
+ startup_exit_ok:
+ mc_shell_deinit ();
+ str_uninit_strings ();
+ return exit_code;
+ }
+
+ /* do this before mc_args_show_info () to view paths in the --datadir-info output */
+ OS_Setup ();
+
+ if (!g_path_is_absolute (mc_config_get_home_dir ()))
+ {
+ mc_propagate_error (&mcerror, 0, "%s: %s", _("Home directory path is not absolute"),
+ mc_config_get_home_dir ());
+ mc_event_deinit (NULL);
+ goto startup_exit_falure;
+ }
+
+ if (!mc_args_show_info ())
+ {
+ exit_code = EXIT_SUCCESS;
+ goto startup_exit_ok;
+ }
+
+ if (!events_init (&mcerror))
+ goto startup_exit_falure;
+
+ mc_config_init_config_paths (&mcerror);
+ if (mcerror != NULL)
+ {
+ mc_event_deinit (NULL);
+ goto startup_exit_falure;
+ }
+
+ vfs_init ();
+ vfs_plugins_init ();
+
+ load_setup ();
+
+ /* Must be done after load_setup because depends on mc_global.vfs.cd_symlinks */
+ vfs_setup_work_dir ();
+
+ /* Set up temporary directory after VFS initialization */
+ mc_tmpdir ();
+
+ /* do this after vfs initialization and vfs working directory setup
+ due to mc_setctl() and mcedit_arg_vpath_new() calls in mc_setup_by_args() */
+ if (!mc_setup_by_args (argc, argv, &mcerror))
+ {
+ vfs_shut ();
+ done_setup ();
+ g_free (saved_other_dir);
+ mc_event_deinit (NULL);
+ goto startup_exit_falure;
+ }
+
+ /* Resolve the other_dir panel option.
+ * 1. Must be done after vfs_setup_work_dir().
+ * 2. Must be done after mc_setup_by_args() because of mc_run_mode.
+ */
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ {
+ char *buffer;
+ vfs_path_t *vpath;
+
+ buffer = mc_config_get_string (mc_global.panels_config, "Dirs", "other_dir", ".");
+ vpath = vfs_path_from_str (buffer);
+ if (vfs_file_is_local (vpath))
+ saved_other_dir = buffer;
+ else
+ g_free (buffer);
+ vfs_path_free (vpath, TRUE);
+ }
+
+ /* check terminal type
+ * $TERM must be set and not empty
+ * mc_global.tty.xterm_flag is used in init_key() and tty_init()
+ * Do this after mc_args_handle() where mc_args__force_xterm is set up.
+ */
+ mc_global.tty.xterm_flag = tty_check_term (mc_args__force_xterm);
+
+ /* NOTE: This has to be called before tty_init or whatever routine
+ calls any define_sequence */
+ init_key ();
+
+ /* Must be done before installing the SIGCHLD handler [[FIXME]] */
+ handle_console (CONSOLE_INIT);
+
+#ifdef ENABLE_SUBSHELL
+ /* Disallow subshell when invoked as standalone viewer or editor from running mc */
+ if (mc_global.mc_run_mode != MC_RUN_FULL && mc_global.run_from_parent_mc)
+ mc_global.tty.use_subshell = FALSE;
+
+ if (mc_global.tty.use_subshell)
+ subshell_get_console_attributes ();
+#endif /* ENABLE_SUBSHELL */
+
+ /* Install the SIGCHLD handler; must be done before init_subshell() */
+ init_sigchld ();
+
+ /* We need this, since ncurses endwin () doesn't restore the signals */
+ save_stop_handler ();
+
+ /* Must be done before init_subshell, to set up the terminal size: */
+ /* FIXME: Should be removed and LINES and COLS computed on subshell */
+ tty_init (!mc_args__nomouse, mc_global.tty.xterm_flag);
+
+ /* start check mc_global.display_codepage and mc_global.source_codepage */
+ check_codeset ();
+
+ /* Removing this from the X code let's us type C-c */
+ load_key_defs ();
+
+ keymap_load (!mc_args__nokeymap);
+
+#ifdef USE_INTERNAL_EDIT
+ macros_list = g_array_new (TRUE, FALSE, sizeof (macros_t));
+#endif /* USE_INTERNAL_EDIT */
+
+ tty_init_colors (mc_global.tty.disable_colors, mc_args__force_colors);
+
+ mc_skin_init (NULL, &mcerror);
+ dlg_set_default_colors ();
+ input_set_default_colors ();
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ command_set_default_colors ();
+
+ mc_error_message (&mcerror, NULL);
+
+#ifdef ENABLE_SUBSHELL
+ /* Done here to ensure that the subshell doesn't */
+ /* inherit the file descriptors opened below, etc */
+ if (mc_global.tty.use_subshell && mc_global.run_from_parent_mc)
+ {
+ int r;
+
+ r = query_dialog (_("Warning"),
+ _("GNU Midnight Commander\nis already running on this terminal.\n"
+ "Subshell support will be disabled."),
+ D_ERROR, 2, _("&OK"), _("&Quit"));
+ if (r == 0)
+ {
+ /* parent mc was found and the user wants to continue */
+ ;
+ }
+ else
+ {
+ /* parent mc was found and the user wants to quit mc */
+ mc_global.midnight_shutdown = TRUE;
+ }
+
+ mc_global.tty.use_subshell = FALSE;
+ }
+
+ if (mc_global.tty.use_subshell)
+ init_subshell ();
+#endif /* ENABLE_SUBSHELL */
+
+ if (!mc_global.midnight_shutdown)
+ {
+ /* Also done after init_subshell, to save any shell init file messages */
+ if (mc_global.tty.console_flag != '\0')
+ handle_console (CONSOLE_SAVE);
+
+ if (mc_global.tty.alternate_plus_minus)
+ application_keypad_mode ();
+
+ /* Done after subshell initialization to allow select and paste text by mouse
+ w/o Shift button in subshell in the native console */
+ init_mouse ();
+
+ /* Done after tty_enter_ca_mode (tty_init) because in VTE bracketed mode is
+ separate for the normal and alternate screens */
+ enable_bracketed_paste ();
+
+ /* subshell_prompt is NULL here */
+ mc_prompt = (geteuid () == 0) ? "# " : "$ ";
+ }
+
+ /* Program main loop */
+ if (mc_global.midnight_shutdown)
+ exit_code = EXIT_SUCCESS;
+ else
+ exit_code = do_nc ()? EXIT_SUCCESS : EXIT_FAILURE;
+
+ disable_bracketed_paste ();
+
+ disable_mouse ();
+
+ /* Save the tree store */
+ (void) tree_store_save ();
+
+ keymap_free ();
+
+ /* Virtual File System shutdown */
+ vfs_shut ();
+
+ flush_extension_file (); /* does only free memory */
+
+ mc_skin_deinit ();
+ tty_colors_done ();
+
+ tty_shutdown ();
+
+ done_setup ();
+
+ if (mc_global.tty.console_flag != '\0' && (quit & SUBSHELL_EXIT) == 0)
+ handle_console (CONSOLE_RESTORE);
+ if (mc_global.tty.alternate_plus_minus)
+ numeric_keypad_mode ();
+
+ (void) signal (SIGCHLD, SIG_DFL); /* Disable the SIGCHLD handler */
+
+ if (mc_global.tty.console_flag != '\0')
+ handle_console (CONSOLE_DONE);
+
+ if (mc_global.mc_run_mode == MC_RUN_FULL && mc_args__last_wd_file != NULL
+ && last_wd_string != NULL && !print_last_revert)
+ {
+ int last_wd_fd;
+
+ last_wd_fd = open (mc_args__last_wd_file, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL,
+ S_IRUSR | S_IWUSR);
+ if (last_wd_fd != -1)
+ {
+ ssize_t ret1;
+ int ret2;
+ ret1 = write (last_wd_fd, last_wd_string, strlen (last_wd_string));
+ ret2 = close (last_wd_fd);
+ (void) ret1;
+ (void) ret2;
+ }
+ }
+ g_free (last_wd_string);
+
+ mc_shell_deinit ();
+
+ done_key ();
+
+#ifdef USE_INTERNAL_EDIT
+ if (macros_list != NULL)
+ {
+ guint i;
+
+ for (i = 0; i < macros_list->len; i++)
+ {
+ macros_t *macros;
+
+ macros = &g_array_index (macros_list, struct macros_t, i);
+ if (macros != NULL && macros->macro != NULL)
+ (void) g_array_free (macros->macro, TRUE);
+ }
+ (void) g_array_free (macros_list, TRUE);
+ }
+#endif /* USE_INTERNAL_EDIT */
+
+ str_uninit_strings ();
+
+ if (mc_global.mc_run_mode != MC_RUN_EDITOR)
+ g_free (mc_run_param0);
+ else
+ g_list_free_full ((GList *) mc_run_param0, (GDestroyNotify) mcedit_arg_free);
+
+ g_free (mc_run_param1);
+ g_free (saved_other_dir);
+
+ mc_config_deinit_config_paths ();
+
+ (void) mc_event_deinit (&mcerror);
+ if (mcerror != NULL)
+ {
+ fprintf (stderr, _("\nFailed while close:\n%s\n"), mcerror->message);
+ g_error_free (mcerror);
+ exit_code = EXIT_FAILURE;
+ }
+
+ (void) putchar ('\n'); /* Hack to make shell's prompt start at left of screen */
+
+ return exit_code;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/man2hlp/Makefile.am b/src/man2hlp/Makefile.am
new file mode 100644
index 0000000..4ed83c6
--- /dev/null
+++ b/src/man2hlp/Makefile.am
@@ -0,0 +1 @@
+noinst_SCRIPTS = man2hlp
diff --git a/src/man2hlp/Makefile.in b/src/man2hlp/Makefile.in
new file mode 100644
index 0000000..e2273de
--- /dev/null
+++ b/src/man2hlp/Makefile.in
@@ -0,0 +1,584 @@
+# 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/man2hlp
+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 = man2hlp
+CONFIG_CLEAN_VPATH_FILES =
+SCRIPTS = $(noinst_SCRIPTS)
+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 =
+SOURCES =
+DIST_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)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/man2hlp.in
+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_SCRIPTS = man2hlp
+all: all-am
+
+.SUFFIXES:
+$(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/man2hlp/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/man2hlp/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):
+man2hlp: $(top_builddir)/config.status $(srcdir)/man2hlp.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+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 $(SCRIPTS)
+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 mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+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 Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags-am distclean distclean-generic \
+ distclean-libtool 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-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ 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/man2hlp/man2hlp.in b/src/man2hlp/man2hlp.in
new file mode 100644
index 0000000..8aa7131
--- /dev/null
+++ b/src/man2hlp/man2hlp.in
@@ -0,0 +1,987 @@
+#! @PERL_FOR_BUILD@
+#
+# Man page to help file converter
+# Copyright (C) 1994, 1995, 1998, 2000, 2001, 2002, 2003, 2004, 2005,
+# 2007, 2010, 2011
+# The Free Software Foundation, Inc.
+#
+# Originally written by:
+# Andrew V. Samoilov, 2002
+# Pavel Roskin, 2002
+# Andrew Borodin <aborodin@vmail.ru>, 2010
+#
+# Completely rewritten in Perl by:
+# Alexandr Prenko, 2010
+#
+# This program 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.
+#
+# This program 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 man2hlp
+# \brief Source: man page to help file converter
+
+use strict;
+use warnings;
+
+# Perl have no static variables, so this hash emulates them
+my %static = (
+ "string_len anchor_flag" => 0,
+ "string_len lc_link_flag" => 0,
+ "handle_link old" => undef
+);
+
+# Imported constants
+my $CHAR_LINK_START = chr(01); # Ctrl-A
+my $CHAR_LINK_POINTER = chr(02); # Ctrl-B
+my $CHAR_LINK_END = chr(03); # Ctrl-C
+my $CHAR_NODE_END = chr(04); # Ctrl-D
+my $CHAR_ALTERNATE = chr(05); # Ctrl-E
+my $CHAR_NORMAL = chr(06); # Ctrl-F
+my $CHAR_VERSION = chr(07); # Ctrl-G
+my $CHAR_FONT_BOLD = chr(010); # Ctrl-H
+my $CHAR_FONT_NORMAL = chr(013); # Ctrl-K
+my $CHAR_FONT_ITALIC = chr(024); # Ctrl-T
+# end of import
+
+my $col = 0; # Current output column
+my $out_row = 1; # Current output row
+my $in_row = 0; # Current input row
+my $no_split_flag = 0; # Flag: Don't split section on next ".SH"
+my $skip_flag = 0; # Flag: Skip this section.
+ # 0 = don't skip,
+ # 1 = skipping title,
+ # 2 = title skipped, skipping text
+my $link_flag = 0; # Flag: Next line is a link
+my $verbatim_flag = 0; # Flag: Copy input to output verbatim
+my $node = 0; # Flag: This line is an original ".SH"
+
+my $c_out; # Output filename
+my $f_out; # Output file
+
+my $c_in; # Current input filename
+
+my $indentation; # Indentation level, n spaces
+my $tp_flag; # Flag: .TP paragraph
+ # 1 = this line is .TP label,
+ # 2 = first line of label description.
+my $topics = undef;
+
+# Emulate C strtok()
+my $strtok;
+
+sub strtok($$) {
+ my ($str, $chars) = @_;
+
+ if (! defined $chars || $chars eq "")
+ {
+ my $result = $strtok;
+ $strtok = undef;
+ return $result;
+ }
+
+ $str = $strtok unless defined $str;
+ return undef unless defined $str;
+
+ my $result;
+ $str =~ s/^[$chars]+//;
+ ($result, $strtok) = split /[$chars]+/, $str, 2;
+ ($result, $strtok) = split /[$chars]+/, $strtok, 2 if defined $result && $result eq "";
+ $strtok = undef if ! defined $strtok || $strtok eq "";
+ return $result;
+}
+
+sub struct_node() {
+ return {
+ "node" => undef, # Section name
+ "lname" => undef, # Translated .SH, undef if not translated
+ "next" => undef,
+ "heading_level" => undef
+ }
+}
+
+my $nodes = struct_node();
+my $cnode; # Current node
+
+# Report error in input
+sub print_error($)
+{
+ my ($message) = @_;
+ warn sprintf "man2hlp: %s in file \"%s\" on line %d\n", $message, $c_in, $in_row;
+}
+
+# Do open, exit if it fails
+sub fopen_check ($$)
+{
+ my ($mode, $filename) = @_;
+ my $f;
+
+ unless (open $f, $mode, $filename)
+ {
+ warn sprintf("man2hlp: Cannot open file \"%s\" ($!)\n", $filename);
+ exit 3;
+ }
+ return $f;
+}
+
+# Do close, exit if it fails
+sub fclose_check($)
+{
+ my ($f) = @_;
+ unless (close $f)
+ {
+ warn "man2hlp: Cannot close file ($!)\n";
+ exit 3;
+ }
+}
+
+# Change output line
+sub newline()
+{
+ $out_row++;
+ $col = 0;
+ print $f_out "\n";
+}
+
+# Calculate the length of string
+sub string_len
+{
+ my ($buffer) = @_;
+ my $anchor_flag = \$static{"string_len anchor_flag"}; # Flag: Inside hypertext anchor name ho4u_v_Ariom
+ my $lc_link_flag = \$static{"string_len lc_link_flag"}; # Flag: Inside hypertext link target name
+ my $backslash_flag = 0; # Flag: Backslash quoting
+ my $len = 0; # Result: the length of the string
+
+
+ foreach my $c (split //, $buffer)
+ {
+ if ($c eq $CHAR_LINK_POINTER)
+ {
+ $$lc_link_flag = 1; # Link target name starts
+ }
+ elsif ($c eq $CHAR_LINK_END)
+ {
+ $$lc_link_flag = 0; # Link target name ends
+ }
+ elsif ($c eq $CHAR_NODE_END)
+ {
+ # Node anchor name starts
+ $$anchor_flag = 1;
+ # Ugly hack to prevent loss of one space
+ $len++;
+ }
+ # Don't add control characters to the length
+ next if ord($c) >= 0 && ord($c) < 32;
+ # Attempt to handle backslash quoting
+ if ($c eq '\\' && !$backslash_flag)
+ {
+ $backslash_flag = 1;
+ next;
+ }
+ $backslash_flag = 0;
+ # Increase length if not inside anchor name or link target name
+ $len++ if !$$anchor_flag && !$$lc_link_flag;
+ if ($$anchor_flag && $c eq ']')
+ {
+ # Node anchor name ends
+ $$anchor_flag = 0;
+ }
+ }
+ return $len;
+}
+
+# Output the string
+sub print_string($)
+{
+ my ($buffer) = @_;
+ my $len; # The length of current word
+ my $backslash_flag = 0;
+ my $font_change_flag = 0;
+ my $quotes_flag = 0;
+
+ # Skipping lines?
+ return if $skip_flag;
+ # Copying verbatim?
+ if ($verbatim_flag)
+ {
+ # Attempt to handle backslash quoting
+ foreach (split //, $buffer)
+ {
+ if ($_ eq '\\' && !$backslash_flag)
+ {
+ $backslash_flag = 1;
+ next;
+ }
+ $backslash_flag = 0;
+ print $f_out $_;
+ }
+ }
+ else
+ {
+ # Split into words
+ $buffer = strtok($buffer, " \t\n");
+ # Repeat for each word
+ while (defined $buffer)
+ {
+ # Skip empty strings
+ if ($buffer ne '')
+ {
+ $len = string_len($buffer);
+ # Words are separated by spaces
+ if ($col > 0)
+ {
+ print $f_out ' ';
+ $col++;
+ }
+ elsif ($indentation)
+ {
+ print $f_out ' ' while $col++ < $indentation;
+ }
+ # Attempt to handle backslash quoting
+ foreach (split //, $buffer)
+ {
+ # handle quotes: \(lq, \(rq, \(dq
+ if ($quotes_flag != 0)
+ {
+ if (($_ eq 'l' || $_ eq 'r' || $_ eq 'd') && $quotes_flag == 1)
+ {
+ # continue quotes handling
+ $quotes_flag = 2;
+ next;
+ }
+ elsif ($_ eq 'q' && $quotes_flag == 2)
+ {
+ # finish quotes handling
+ $quotes_flag = 0;
+ print $f_out '"';
+ next;
+ }
+ else
+ {
+ print $f_out '(' . $_;
+ print_error "Syntax error: unsupported \\(" . $_ . " command";
+ }
+ }
+ # handle \fR, \fB, \fI and \fP commands
+ if ($font_change_flag)
+ {
+ if ($_ eq 'B')
+ {
+ print $f_out $CHAR_FONT_BOLD;
+ }
+ elsif ($_ eq 'I')
+ {
+ print $f_out $CHAR_FONT_ITALIC;
+ }
+ elsif ($_ eq 'R' || $_ eq 'P')
+ {
+ print $f_out $CHAR_FONT_NORMAL;
+ }
+ else
+ {
+ print $f_out 'f' . $_;
+ print_error "Syntax error: unsupported \\f" . $_ . " command";
+ }
+
+ $font_change_flag = 0;
+ next;
+ }
+ if ($_ eq '(' && $backslash_flag)
+ {
+ $quotes_flag = 1;
+ $backslash_flag = 0;
+ next;
+ }
+ if ($_ eq 'f' && $backslash_flag)
+ {
+ $font_change_flag = 1;
+ $backslash_flag = 0;
+ next;
+ }
+ if ($_ eq '\\' && !$backslash_flag)
+ {
+ $backslash_flag = 1;
+ next;
+ }
+ $backslash_flag = 0;
+ $font_change_flag = 0;
+ $quotes_flag = 0;
+ print $f_out $_;
+ }
+ # Increase column
+ $col += $len;
+ }
+ # Get the next word
+ $buffer = strtok(undef, " \t\n");
+ } # while
+ }
+}
+
+# Like print_string but with printf-like syntax
+sub printf_string
+{
+ print_string sprintf shift, @_;
+}
+
+# Handle NODE and .SH commands. is_sh is 1 for .SH, 0 for NODE
+# FIXME: Consider to remove first parameter
+sub handle_node($$)
+{
+ my ($buffer, $is_sh) = @_;
+ my ($len, $heading_level);
+
+ # If we already skipped a section, don't skip another
+ $skip_flag = 0 if $skip_flag == 2;
+
+ # Get the command parameters
+ $buffer = strtok(undef, "");
+ if (! defined $buffer)
+ {
+ print_error "Syntax error: .SH: no title";
+ return;
+ }
+ else
+ {
+ # Remove quotes
+ $buffer =~ s/^"// and $buffer =~ s/"$//;
+ # Calculate heading level
+ $heading_level = 0;
+ $heading_level++ while substr($buffer, $heading_level, 1) eq ' ';
+ # Heading level must be even
+ if ($heading_level % 2)
+ {
+ print_error "Syntax error: .SH: odd heading level";
+ }
+ if ($no_split_flag)
+ {
+ # Don't start a new section
+ newline;
+ print_string $buffer;
+ newline;
+ newline;
+ $no_split_flag = 0;
+ }
+ elsif ($skip_flag)
+ {
+ # Skipping title and marking text for skipping
+ $skip_flag = 2;
+ }
+ else
+ {
+ $buffer = substr($buffer, $heading_level);
+ if (! $is_sh || ! $node)
+ {
+ # Start a new section, but omit empty section names
+ if ($buffer ne '')
+ {
+ printf $f_out "%s[%s]", $CHAR_NODE_END, $buffer;
+ newline;
+ }
+
+ # Add section to the linked list
+ if (! defined $cnode)
+ {
+ $cnode = $nodes;
+ }
+ else
+ {
+ $cnode->{'next'} = struct_node();
+ $cnode = $cnode->{'next'};
+ }
+ $cnode->{'node'} = $buffer;
+ $cnode->{'lname'} = undef;
+ $cnode->{'next'} = undef;
+ $cnode->{'heading_level'} = $heading_level;
+ }
+ if ($is_sh)
+ {
+ $cnode->{'lname'} = $buffer;
+ print_string $buffer;
+ newline;
+ newline;
+ }
+ } # Start new section
+ } # Has parameters
+ $node = ! $is_sh;
+}
+
+# Convert character from the macro name to the font marker
+sub char_to_font($)
+{
+ my ($c) = @_;
+ my %font = (
+ 'R' => $CHAR_FONT_NORMAL,
+ 'B' => $CHAR_FONT_BOLD,
+ 'I' => $CHAR_FONT_ITALIC
+ );
+ return exists $font{$c} ? $font{$c} : chr(0);
+}
+
+#
+# Handle alternate font commands (.BR, .IR, .RB, .RI, .BI, .IB)
+# Return 0 if the command wasn't recognized, 1 otherwise
+#
+sub handle_alt_font($)
+{
+ my ($buffer) = @_;
+ my $in_quotes = 0;
+ my $alt_state = 0;
+
+ return 0 if length($buffer) != 3;
+ return 0 if substr($buffer, 0, 1) ne '.';
+
+ my @font = (
+ char_to_font substr($buffer, 1, 1),
+ char_to_font substr($buffer, 2, 1)
+ );
+
+ # Exclude names with unknown characters, .BB, .II and .RR
+ if ($font[0] eq chr(0) || $font[1] eq chr(0) || $font[0] eq $font[1])
+ {
+ return 0;
+ }
+
+ my $p = strtok(undef, "");
+ return 1 unless defined $p;
+
+ $buffer = $font[0];
+
+ my @p = split //, $p;
+ while (@p)
+ {
+
+ if ($p[0] eq '"')
+ {
+ $in_quotes = !$in_quotes;
+ shift @p;
+ next;
+ }
+
+ if ($p[0] eq ' ' && !$in_quotes)
+ {
+ shift @p;
+ # Don't change font if we are at the end
+ if (@p)
+ {
+ $alt_state = $alt_state ? 0 : 1;
+ $buffer .= $font[$alt_state];
+ }
+
+ # Skip more spaces
+ shift @p while @p && $p[0] eq ' ';
+
+ next;
+ }
+
+ $buffer .= shift @p;
+ }
+
+ # Turn off attributes if necessary
+ if ($font[$alt_state] ne $CHAR_FONT_NORMAL)
+ {
+ $buffer .= $CHAR_FONT_NORMAL;
+ }
+
+ print_string $buffer;
+
+ return 1;
+}
+
+# Handle .IP and .TP commands. is_tp is 1 for .TP, 0 for .IP
+sub handle_tp_ip($)
+{
+ my ($is_tp) = @_;
+ newline if $col > 0;
+ newline;
+ if ($is_tp)
+ {
+ $tp_flag = 1;
+ $indentation = 0;
+ }
+ else
+ {
+ $indentation = 8;
+ }
+}
+
+# Handle all the roff dot commands. See man groff_man for details
+sub handle_command($)
+{
+ my ($buffer) = @_;
+ my $len;
+
+ # Get the command name
+ $buffer = strtok($buffer, " \t");
+
+ if ($buffer eq ".SH")
+ {
+ $indentation = 0;
+ handle_node $buffer, 1;
+ }
+ elsif ($buffer eq ".\\\"NODE")
+ {
+ handle_node $buffer, 0;
+ }
+ elsif ($buffer eq ".\\\"DONT_SPLIT\"")
+ {
+ $no_split_flag = 1;
+ }
+ elsif ($buffer eq ".\\\"SKIP_SECTION\"")
+ {
+ $skip_flag = 1;
+ }
+ elsif ($buffer eq ".\\\"LINK2\"")
+ {
+ # Next two input lines form a link
+ $link_flag = 2;
+ }
+ elsif ($buffer eq ".PP" || $buffer eq ".P" || $buffer eq ".LP")
+ {
+ $indentation = 0;
+ # End of paragraph
+ newline if $col > 0;
+ newline;
+ }
+ elsif ($buffer eq ".nf")
+ {
+ # Following input lines are to be handled verbatim
+ $verbatim_flag = 1;
+ newline if $col > 0;
+ }
+ elsif ($buffer eq ".I" || $buffer eq ".B" || $buffer eq ".SB")
+ {
+ # Bold text or italics text
+ my $backslash_flag = 0;
+
+ # .SB [text]
+ # Causes the text on the same line or the text on the
+ # next line to appear in boldface font, one point
+ # size smaller than the default font.
+ #
+
+ # FIXME: text is optional, so there is no error
+
+ my $p = strtok(undef, "");
+ if (! defined $p)
+ {
+ print_error "Syntax error: .I | .B | .SB : no text";
+ return;
+ }
+
+ $buffer = substr($buffer, 1, 1) eq 'I' ? $CHAR_FONT_ITALIC : $CHAR_FONT_BOLD;
+
+ # Attempt to handle backslash quoting
+ foreach (split //, $p)
+ {
+ if ($_ eq '\\' && !$backslash_flag)
+ {
+ $backslash_flag = 1;
+ next;
+ }
+ $backslash_flag = 0;
+ $buffer .= $_;
+ }
+ print_string $buffer . $CHAR_FONT_NORMAL;
+ }
+ elsif ($buffer eq ".TP")
+ {
+ handle_tp_ip 1;
+ }
+ elsif ($buffer eq ".IP")
+ {
+ handle_tp_ip 0;
+ }
+ elsif ($buffer eq ".\\\"TOPICS")
+ {
+ if ($out_row > 1)
+ {
+ print_error "Syntax error: .\\\"TOPICS must be first command";
+ return;
+ }
+ $buffer = strtok(undef, "");
+ if (! defined $buffer)
+ {
+ print_error "Syntax error: .\\\"TOPICS: no text";
+ return;
+ }
+ # Remove quotes
+ $buffer =~ s/^"// and $buffer =~ s/"$//;
+ $topics = $buffer;
+ }
+ elsif ($buffer eq ".br")
+ {
+ newline if $col;
+ }
+ elsif ($buffer =~ /^\.\\"/)
+ {
+ # Comment { Hello from K.O. ;-) }
+ }
+ elsif ($buffer eq ".TH")
+ {
+ # Title header
+ }
+ elsif ($buffer eq ".SM")
+ {
+ # Causes the text on the same line or the text on the
+ # next line to appear in a font that is one point
+ # size smaller than the default font.
+ $buffer = strtok(undef, "");
+ print_string $buffer if defined $buffer;
+ }
+ elsif (handle_alt_font($buffer) == 1)
+ {
+ return;
+ }
+ elsif ($buffer eq ".RE")
+ {
+ newline;
+ }
+ else
+ {
+ # Other commands are ignored
+ print_error sprintf "Warning: unsupported command %s", $buffer;
+ return;
+ }
+}
+
+sub struct_links()
+{
+ return {
+ 'linkname' => undef, # Section name
+ 'line' => undef, # Input line in ...
+ 'filename' => undef,
+ 'next' => undef
+ }
+}
+
+my $links = struct_links();
+my $current_link;
+
+
+sub handle_link($)
+{
+ my ($buffer) = @_;
+ my $old = \$static{"handle_link old"};
+ my $len;
+ my $amp;
+ my $amp_arg;
+
+ if ($link_flag == 1)
+ {
+ # Old format link, not supported
+ }
+ elsif ($link_flag == 2)
+ {
+ # First part of new format link
+ # Bold text or italics text
+ if (substr($buffer, 0, 2) eq '.I' || substr($buffer, 0, 2) eq '.B')
+ {
+ $buffer =~ s/^..[\s\t]*//;
+ }
+ $$old = $buffer;
+ $link_flag = 3;
+
+ }
+ elsif ($link_flag == 3)
+ {
+ # Second part of new format link
+ $buffer =~ s/^\.//;
+ $buffer =~ s/^\\//;
+ $buffer =~ s/^"//;
+ $buffer =~ s/"$//;
+
+ # "Layout\&)," -- "Layout" should be highlighted, but not "),"
+ ($$old, $amp_arg) = split /\\&/, $$old, 2;
+ $amp_arg = "" unless defined $amp_arg;
+ printf_string "%s%s%s%s%s%s\n", $CHAR_LINK_START, $$old,
+ $CHAR_LINK_POINTER, $buffer, $CHAR_LINK_END, $amp_arg;
+ $link_flag = 0;
+ # Add to the linked list
+ if (defined $current_link)
+ {
+ $current_link->{'next'} = struct_links();
+ $current_link = $current_link->{'next'};
+ $current_link->{'next'} = undef;
+ }
+ else
+ {
+ $current_link = $links;
+ }
+ $current_link->{'linkname'} = $buffer;
+ $current_link->{'filename'} = $c_in;
+ $current_link->{'line'} = $in_row;
+ }
+}
+
+sub main
+{
+ my $len; # Length of input line
+ my $c_man; # Manual filename
+ my $c_tmpl; # Template filename
+ my $f_man; # Manual file
+ my $f_tmpl; # Template file
+ my $buffer; # Full input line
+ my $lc_node = undef;
+ my $outfile_buffer; # Large buffer to keep the output file
+ my $cont_start; # Start of [Contents]
+ my $file_end; # Length of the output file
+
+ # Validity check for arguments
+ if (@ARGV != 3)
+ {
+ warn "Usage: man2hlp file.man template_file helpfile\n";
+ return 3;
+ }
+
+ $c_man = $ARGV[0];
+ $c_tmpl = $ARGV[1];
+ $c_out = $ARGV[2];
+
+ # First stage - process the manual, write to the output file
+
+ $f_man = fopen_check "<", $c_man;
+ $f_out = fopen_check ">", $c_out;
+ $c_in = $c_man;
+
+ # Repeat for each input line
+ while (<$f_man>)
+ {
+ # Remove terminating newline
+ chomp;
+ $buffer = $_;
+ my $input_line; # Input line without initial "\&"
+
+ if (substr($buffer, 0, 2) eq '\\&')
+ {
+ $input_line = substr($buffer, 2);
+ }
+ else
+ {
+ $input_line = $buffer;
+ }
+
+ $in_row++;
+ $len = length($input_line);
+
+ if ($verbatim_flag)
+ {
+ # Copy the line verbatim
+ if ($input_line eq ".fi")
+ {
+ $verbatim_flag = 0;
+ }
+ else
+ {
+ print_string $input_line;
+ newline;
+ }
+ }
+ elsif ($link_flag)
+ {
+ # The line is a link
+ handle_link $input_line;
+ }
+ elsif (substr($buffer, 0, 1) eq '.')
+ {
+ # The line is a roff command
+ handle_command $input_line;
+ }
+ else
+ {
+ #A normal line, just output it
+ print_string $input_line;
+ }
+ # .TP label processed as usual line
+ if ($tp_flag)
+ {
+ if ($tp_flag == 1)
+ {
+ $tp_flag = 2;
+ }
+ else
+ {
+ $tp_flag = 0;
+ $indentation = 8;
+ if ($col >= $indentation)
+ {
+ newline;
+ }
+ else
+ {
+ print $f_out " " while ++$col < $indentation;
+ }
+ }
+ }
+ }
+
+ newline;
+ fclose_check $f_man;
+ # First stage ends here, closing the manual
+
+ # Second stage - process the template file
+ $f_tmpl = fopen_check "<", $c_tmpl;
+ $c_in = $c_tmpl;
+
+ # Repeat for each input line
+ # Read a line
+ while (<$f_tmpl>)
+ {
+ $buffer = $_;
+ if (defined $lc_node)
+ {
+ if ($buffer ne "\n")
+ {
+ $cnode->{'lname'} = $buffer;
+ chomp $cnode->{'lname'};
+ }
+ $lc_node = undef;
+ }
+ else
+ {
+ my $char_node_end = index($buffer, $CHAR_NODE_END);
+ $lc_node = $char_node_end < 0 ? undef : substr($buffer, $char_node_end);
+
+ if (defined $lc_node && substr($lc_node, 1, 1) eq '[')
+ {
+ my $p = index($lc_node, ']');
+ if ($p >= 0) {
+ if (substr($lc_node, 1, 6) eq '[main]')
+ {
+ $lc_node = undef;
+ }
+ else
+ {
+ if (! defined $cnode)
+ {
+ $cnode = $nodes;
+ }
+ else
+ {
+ $cnode->{'next'} = struct_node();
+ $cnode = $cnode->{'next'};
+ }
+ $cnode->{'node'} = substr($lc_node, 2, $p-2);
+ $cnode->{'lname'} = undef;
+ $cnode->{'next'} = undef;
+ $cnode->{'heading_level'} = 0;
+ }
+ }
+ else
+ {
+ $lc_node = undef;
+ }
+ }
+ else
+ {
+ $lc_node = undef;
+ }
+ }
+ print $f_out $buffer;
+ }
+
+ $cont_start = tell $f_out;
+ if ($cont_start <= 0)
+ {
+ perror $c_out;
+ return 1;
+ }
+
+ if ($topics)
+ {
+ printf $f_out "\004[Contents]\n%s\n\n", $topics;
+ }
+ else
+ {
+ print $f_out "\004[Contents]\n";
+ }
+
+ for ($current_link = $links; defined $current_link && defined $current_link->{'linkname'};)
+ {
+ my $found = 0;
+ my $next = $current_link->{'next'};
+
+ if ($current_link->{'linkname'} eq "Contents")
+ {
+ $found = 1;
+ }
+ else
+ {
+ for ($cnode = $nodes; defined $cnode && defined $cnode->{'node'}; $cnode = $cnode->{'next'})
+ {
+ if ($cnode->{'node'} eq $current_link->{'linkname'})
+ {
+ $found = 1;
+ last;
+ }
+ }
+ }
+ if (! $found)
+ {
+ $buffer = sprintf "Stale link \"%s\"", $current_link->{'linkname'};
+ $c_in = $current_link->{'filename'};
+ $in_row = $current_link->{'line'};
+ print_error $buffer;
+ }
+
+ $current_link = $next;
+ }
+
+ for ($cnode = $nodes; defined $cnode && defined $cnode->{'node'};)
+ {
+ my $next = $cnode->{'next'};
+ $lc_node = $cnode->{'node'};
+
+ if (defined $lc_node && $lc_node ne '') {
+ printf $f_out " %*s\001%s\002%s\003", $cnode->{'heading_level'},
+ "", $cnode->{'lname'} ? $cnode->{'lname'} : $lc_node, $lc_node;
+ }
+ print $f_out "\n";
+ $cnode = $next;
+ }
+
+ $file_end = tell $f_out;
+
+ # Sanity check
+ if (($file_end <= 0) || ($file_end - $cont_start <= 0))
+ {
+ warn $c_out ."\n";
+ return 1;
+ }
+
+ fclose_check $f_out;
+ fclose_check $f_tmpl;
+ # Second stage ends here, closing all files, note the end of output
+
+ #
+ # Third stage - swap two parts of the output file.
+ # First, open the output file for reading and load it into the memory.
+ #
+ $outfile_buffer = '';
+ $f_out = fopen_check '<', $c_out;
+ $outfile_buffer .= $_ while <$f_out>;
+ fclose_check $f_out;
+ # Now the output file is in the memory
+
+ # Again open output file for writing
+ $f_out = fopen_check '>', $c_out;
+
+ # Write part after the "Contents" node
+ print $f_out substr($outfile_buffer, $cont_start, $file_end - $cont_start);
+
+ # Write part before the "Contents" node
+ print $f_out substr($outfile_buffer, 0, $cont_start-1);
+ print $f_out "\n";
+ fclose_check $f_out;
+
+ return 0;
+}
+
+exit main();
diff --git a/src/selcodepage.c b/src/selcodepage.c
new file mode 100644
index 0000000..c8a3cdc
--- /dev/null
+++ b/src/selcodepage.c
@@ -0,0 +1,178 @@
+/*
+ User interface for charset selection.
+
+ Copyright (C) 2001 Walery Studennikov <despair@sama.ru>
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Walery Studennikov <despair@sama.ru>, 2001
+
+ 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 selcodepage.c
+ * \brief Source: user %interface for charset %selection
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/widget.h"
+#include "lib/charsets.h"
+
+#include "setup.h"
+
+#include "selcodepage.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define ENTRY_LEN 30
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static unsigned char
+get_hotkey (int n)
+{
+ return (n <= 9) ? '0' + n : 'a' + n - 10;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Return value:
+ * -2 (SELECT_CHARSET_CANCEL) : Cancel
+ * -1 (SELECT_CHARSET_OTHER_8BIT) : "Other 8 bit" if seldisplay == TRUE
+ * -1 (SELECT_CHARSET_NO_TRANSLATE) : "No translation" if seldisplay == FALSE
+ * >= 0 : charset number
+ */
+int
+select_charset (int center_y, int center_x, int current_charset, gboolean seldisplay)
+{
+ Listbox *listbox;
+ size_t i;
+ int listbox_result;
+ char buffer[255];
+
+ /* Create listbox */
+ listbox =
+ listbox_window_centered_new (center_y, center_x, codepages->len + 1, ENTRY_LEN + 2,
+ _("Choose codepage"), "[Codepages Translation]");
+
+ if (!seldisplay)
+ LISTBOX_APPEND_TEXT (listbox, '-', _("- < No translation >"), NULL, FALSE);
+
+ /* insert all the items found */
+ for (i = 0; i < codepages->len; i++)
+ {
+ const char *name = ((codepage_desc *) g_ptr_array_index (codepages, i))->name;
+ g_snprintf (buffer, sizeof (buffer), "%c %s", get_hotkey (i), name);
+ LISTBOX_APPEND_TEXT (listbox, get_hotkey (i), buffer, NULL, FALSE);
+ }
+ if (seldisplay)
+ {
+ unsigned char hotkey = get_hotkey (codepages->len);
+ g_snprintf (buffer, sizeof (buffer), "%c %s", hotkey, _("Other 8 bit"));
+ LISTBOX_APPEND_TEXT (listbox, hotkey, buffer, NULL, FALSE);
+ }
+
+ /* Select the default entry */
+ i = (seldisplay)
+ ? ((current_charset < 0) ? codepages->len : (size_t) current_charset)
+ : ((size_t) current_charset + 1);
+
+ listbox_set_current (listbox->list, i);
+
+ listbox_result = listbox_run (listbox);
+
+ if (listbox_result < 0)
+ {
+ /* Cancel dialog */
+ return SELECT_CHARSET_CANCEL;
+ }
+ else
+ {
+ /* some charset has been selected */
+ if (seldisplay)
+ {
+ /* charset list is finished with "Other 8 bit" item */
+ return (listbox_result >= (int) codepages->len)
+ ? SELECT_CHARSET_OTHER_8BIT : listbox_result;
+ }
+ else
+ {
+ /* charset list is began with "- < No translation >" item */
+ return (listbox_result - 1);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Set codepage */
+gboolean
+do_set_codepage (int codepage)
+{
+ char *errmsg;
+ gboolean ret;
+
+ mc_global.source_codepage = codepage;
+ errmsg = init_translation_table (codepage == SELECT_CHARSET_NO_TRANSLATE ?
+ mc_global.display_codepage : mc_global.source_codepage,
+ mc_global.display_codepage);
+ ret = errmsg == NULL;
+
+ if (!ret)
+ {
+ message (D_ERROR, MSG_ERROR, "%s", errmsg);
+ g_free (errmsg);
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Show menu selecting codepage */
+gboolean
+do_select_codepage (void)
+{
+ int r;
+
+ r = select_charset (-1, -1, default_source_codepage, FALSE);
+ if (r == SELECT_CHARSET_CANCEL)
+ return FALSE;
+
+ default_source_codepage = r;
+ return do_set_codepage (default_source_codepage);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/selcodepage.h b/src/selcodepage.h
new file mode 100644
index 0000000..808073e
--- /dev/null
+++ b/src/selcodepage.h
@@ -0,0 +1,39 @@
+
+/** \file selcodepage.h
+ * \brief Header: user %interface for charset %selection
+ */
+
+#ifndef MC__SELCODEPAGE_H
+#define MC__SELCODEPAGE_H
+
+#include "lib/global.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* some results of select_charset() */
+#define SELECT_CHARSET_CANCEL -2
+/* select_charset() returns this value if dialog has been canceled */
+#define SELECT_CHARSET_OTHER_8BIT -1
+/* select_charset() returns this value if seldisplay == TRUE
+ * and the last item has been selected. Last item is "Other 8 bits" */
+#define SELECT_CHARSET_NO_TRANSLATE -1
+/* select_charset() returns this value if seldisplay == FALSE
+ * and the 1st item has been selected. 1st item is "No translation" */
+/* In other cases select_charset() returns non-negative value
+ * which is number of codepage in codepage list */
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+int select_charset (int center_y, int center_x, int current_charset, gboolean seldisplay);
+gboolean do_set_codepage (int);
+gboolean do_select_codepage (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__SELCODEPAGE_H */
diff --git a/src/setup.c b/src/setup.c
new file mode 100644
index 0000000..68e6f37
--- /dev/null
+++ b/src/setup.c
@@ -0,0 +1,1239 @@
+/*
+ Setup loading/saving.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ 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 setup.c
+ * \brief Source: setup loading/saving
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h"
+#include "lib/mcconfig.h" /* num_history_items_recorded */
+#include "lib/fileloc.h"
+#include "lib/timefmt.h"
+#include "lib/util.h"
+
+#ifdef ENABLE_VFS_FTP
+#include "src/vfs/ftpfs/ftpfs.h"
+#endif
+#ifdef ENABLE_VFS_FISH
+#include "src/vfs/fish/fish.h"
+#endif
+
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "filemanager/dir.h"
+#include "filemanager/filemanager.h"
+#include "filemanager/tree.h" /* xtree_mode */
+#include "filemanager/hotlist.h" /* load/save/done hotlist */
+#include "filemanager/panelize.h" /* load/save/done panelize */
+#include "filemanager/layout.h"
+#include "filemanager/cmd.h"
+
+#include "args.h"
+#include "execute.h" /* pause_after_run */
+#include "clipboard.h"
+
+#ifdef HAVE_CHARSET
+#include "selcodepage.h"
+#endif
+
+#ifdef USE_INTERNAL_EDIT
+#include "src/editor/edit.h"
+#endif
+
+#include "src/viewer/mcviewer.h" /* For the externs */
+
+#include "setup.h"
+
+/*** global variables ****************************************************************************/
+
+/* Only used at program boot */
+gboolean boot_current_is_left = TRUE;
+
+/* If on, default for "No" in delete operations */
+gboolean safe_delete = FALSE;
+/* If on, default for "No" in overwrite files */
+gboolean safe_overwrite = FALSE;
+
+/* Controls screen clearing before an exec */
+gboolean clear_before_exec = TRUE;
+
+/* Asks for confirmation before deleting a file */
+gboolean confirm_delete = TRUE;
+/* Asks for confirmation before deleting a hotlist entry */
+gboolean confirm_directory_hotlist_delete = FALSE;
+/* Asks for confirmation before overwriting a file */
+gboolean confirm_overwrite = TRUE;
+/* Asks for confirmation before executing a program by pressing enter */
+gboolean confirm_execute = FALSE;
+/* Asks for confirmation before leaving the program */
+gboolean confirm_exit = FALSE;
+
+/* If true, at startup the user-menu is invoked */
+gboolean auto_menu = FALSE;
+/* This flag indicates if the pull down menus by default drop down */
+gboolean drop_menus = FALSE;
+
+/* Asks for confirmation when using F3 to view a directory and there
+ are tagged files */
+gboolean confirm_view_dir = FALSE;
+
+/* Ask file name before start the editor */
+gboolean editor_ask_filename_before_edit = FALSE;
+
+panel_view_mode_t startup_left_mode;
+panel_view_mode_t startup_right_mode;
+
+gboolean copymove_persistent_attr = TRUE;
+
+/* Tab size */
+int option_tab_spacing = DEFAULT_TAB_SPACING;
+
+/* Ugly hack to allow panel_save_setup to work as a place holder for */
+/* default panel values */
+int saving_setup;
+
+panels_options_t panels_options = {
+ .show_mini_info = TRUE,
+ .kilobyte_si = FALSE,
+ .mix_all_files = FALSE,
+ .show_backups = TRUE,
+ .show_dot_files = TRUE,
+ .fast_reload = FALSE,
+ .fast_reload_msg_shown = FALSE,
+ .mark_moves_down = TRUE,
+ .reverse_files_only = TRUE,
+ .auto_save_setup = FALSE,
+ .navigate_with_arrows = FALSE,
+ .scroll_pages = TRUE,
+ .scroll_center = FALSE,
+ .mouse_move_pages = TRUE,
+ .filetype_mode = TRUE,
+ .permission_mode = FALSE,
+ .qsearch_mode = QSEARCH_PANEL_CASE,
+ .torben_fj_mode = FALSE,
+ .select_flags = SELECT_MATCH_CASE | SELECT_SHELL_PATTERNS
+};
+
+gboolean easy_patterns = TRUE;
+
+/* It true saves the setup when quitting */
+gboolean auto_save_setup = TRUE;
+
+/* If true, then the +, - and \ keys have their special meaning only if the
+ * command line is empty, otherwise they behave like regular letters
+ */
+gboolean only_leading_plus_minus = TRUE;
+
+/* Automatically fills name with current selected item name on mkdir */
+gboolean auto_fill_mkdir_name = TRUE;
+
+/* If set and you don't have subshell support, then C-o will give you a shell */
+gboolean output_starts_shell = FALSE;
+
+#ifdef USE_FILE_CMD
+/* If set, we execute the file command to check the file type */
+gboolean use_file_to_check_type = TRUE;
+#endif
+
+gboolean verbose = TRUE;
+
+/*
+ * Whether the Midnight Commander tries to provide more
+ * information about copy/move sizes and bytes transferred
+ * at the expense of some speed
+ */
+gboolean file_op_compute_totals = TRUE;
+
+/* If true use the internal viewer */
+gboolean use_internal_view = TRUE;
+/* If set, use the builtin editor */
+gboolean use_internal_edit = TRUE;
+
+#ifdef HAVE_CHARSET
+/* Numbers of (file I/O) and (input/display) codepages. -1 if not selected */
+int default_source_codepage = -1;
+char *autodetect_codeset = NULL;
+gboolean is_autodetect_codeset_enabled = FALSE;
+#endif /* !HAVE_CHARSET */
+
+#ifdef HAVE_ASPELL
+char *spell_language = NULL;
+#endif
+
+/* Value of "other_dir" key in ini file */
+char *saved_other_dir = NULL;
+
+/* If set, then print to the given file the last directory we were at */
+char *last_wd_string = NULL;
+
+/* Set when main loop should be terminated */
+int quit = 0;
+
+/* Set to TRUE to suppress printing the last directory */
+int print_last_revert = FALSE;
+
+#ifdef USE_INTERNAL_EDIT
+/* index to record_macro_buf[], -1 if not recording a macro */
+int macro_index = -1;
+
+/* macro stuff */
+struct macro_action_t record_macro_buf[MAX_MACRO_LENGTH];
+
+GArray *macros_list;
+#endif /* USE_INTERNAL_EDIT */
+
+/*** file scope macro definitions ****************************************************************/
+
+/* In order to use everywhere the same setup for the locale we use defines */
+#define FMTYEAR _("%b %e %Y")
+#define FMTTIME _("%b %e %H:%M")
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static char *profile_name = NULL; /* ${XDG_CONFIG_HOME}/mc/ini */
+static char *panels_profile_name = NULL; /* ${XDG_CACHE_HOME}/mc/panels.ini */
+
+/* *INDENT-OFF* */
+static const struct
+{
+ const char *key;
+ int list_format;
+} list_formats [] = {
+ { "full", list_full },
+ { "brief", list_brief },
+ { "long", list_long },
+ { "user", list_user },
+ { NULL, 0 }
+};
+
+static const struct
+{
+ const char *opt_name;
+ panel_view_mode_t opt_type;
+} panel_types [] = {
+ { "listing", view_listing },
+ { "quickview", view_quick },
+ { "info", view_info },
+ { "tree", view_tree },
+ { NULL, view_listing }
+};
+
+static const struct
+{
+ const char *opt_name;
+ int *opt_addr;
+} layout_int_options [] = {
+ { "output_lines", &output_lines },
+ { "left_panel_size", &panels_layout.left_panel_size },
+ { "top_panel_size", &panels_layout.top_panel_size },
+ { NULL, NULL }
+};
+
+static const struct
+{
+ const char *opt_name;
+ gboolean *opt_addr;
+} layout_bool_options [] = {
+ { "message_visible", &mc_global.message_visible },
+ { "keybar_visible", &mc_global.keybar_visible },
+ { "xterm_title", &xterm_title },
+ { "command_prompt", &command_prompt },
+ { "menubar_visible", &menubar_visible },
+ { "free_space", &free_space },
+ { "horizontal_split", &panels_layout.horizontal_split },
+ { "vertical_equal", &panels_layout.vertical_equal },
+ { "horizontal_equal", &panels_layout.horizontal_equal },
+ { NULL, NULL }
+};
+
+static const struct
+{
+ const char *opt_name;
+ gboolean *opt_addr;
+} bool_options [] = {
+ { "verbose", &verbose },
+ { "shell_patterns", &easy_patterns },
+ { "auto_save_setup", &auto_save_setup },
+ { "preallocate_space", &mc_global.vfs.preallocate_space },
+ { "auto_menu", &auto_menu },
+ { "use_internal_view", &use_internal_view },
+ { "use_internal_edit", &use_internal_edit },
+ { "clear_before_exec", &clear_before_exec },
+ { "confirm_delete", &confirm_delete },
+ { "confirm_overwrite", &confirm_overwrite },
+ { "confirm_execute", &confirm_execute },
+ { "confirm_history_cleanup", &mc_global.widget.confirm_history_cleanup },
+ { "confirm_exit", &confirm_exit },
+ { "confirm_directory_hotlist_delete", &confirm_directory_hotlist_delete },
+ { "confirm_view_dir", &confirm_view_dir },
+ { "safe_delete", &safe_delete },
+ { "safe_overwrite", &safe_overwrite },
+#ifndef HAVE_CHARSET
+ { "eight_bit_clean", &mc_global.eight_bit_clean },
+ { "full_eight_bits", &mc_global.full_eight_bits },
+#endif /* !HAVE_CHARSET */
+ { "use_8th_bit_as_meta", &use_8th_bit_as_meta },
+ { "mouse_move_pages_viewer", &mcview_mouse_move_pages },
+ { "mouse_close_dialog", &mouse_close_dialog},
+ { "fast_refresh", &fast_refresh },
+ { "drop_menus", &drop_menus },
+ { "wrap_mode", &mcview_global_flags.wrap },
+ { "old_esc_mode", &old_esc_mode },
+ { "cd_symlinks", &mc_global.vfs.cd_symlinks },
+ { "show_all_if_ambiguous", &mc_global.widget.show_all_if_ambiguous },
+#ifdef USE_FILE_CMD
+ { "use_file_to_guess_type", &use_file_to_check_type },
+#endif
+ { "alternate_plus_minus", &mc_global.tty.alternate_plus_minus },
+ { "only_leading_plus_minus", &only_leading_plus_minus },
+ { "show_output_starts_shell", &output_starts_shell },
+ { "xtree_mode", &xtree_mode },
+ { "file_op_compute_totals", &file_op_compute_totals },
+ { "classic_progressbar", &classic_progressbar },
+#ifdef ENABLE_VFS
+#ifdef ENABLE_VFS_FTP
+ { "use_netrc", &ftpfs_use_netrc },
+ { "ftpfs_always_use_proxy", &ftpfs_always_use_proxy },
+ { "ftpfs_use_passive_connections", &ftpfs_use_passive_connections },
+ { "ftpfs_use_passive_connections_over_proxy", &ftpfs_use_passive_connections_over_proxy },
+ { "ftpfs_use_unix_list_options", &ftpfs_use_unix_list_options },
+ { "ftpfs_first_cd_then_ls", &ftpfs_first_cd_then_ls },
+ { "ignore_ftp_chattr_errors", & ftpfs_ignore_chattr_errors} ,
+#endif /* ENABLE_VFS_FTP */
+#endif /* ENABLE_VFS */
+#ifdef USE_INTERNAL_EDIT
+ { "editor_fill_tabs_with_spaces", &edit_options.fill_tabs_with_spaces },
+ { "editor_return_does_auto_indent", &edit_options.return_does_auto_indent },
+ { "editor_backspace_through_tabs", &edit_options.backspace_through_tabs },
+ { "editor_fake_half_tabs", &edit_options.fake_half_tabs },
+ { "editor_option_save_position", &edit_options.save_position },
+ { "editor_option_auto_para_formatting", &edit_options.auto_para_formatting },
+ { "editor_option_typewriter_wrap", &edit_options.typewriter_wrap },
+ { "editor_edit_confirm_save", &edit_options.confirm_save },
+ { "editor_syntax_highlighting", &edit_options.syntax_highlighting },
+ { "editor_persistent_selections", &edit_options.persistent_selections },
+ { "editor_drop_selection_on_copy", &edit_options.drop_selection_on_copy },
+ { "editor_cursor_beyond_eol", &edit_options.cursor_beyond_eol },
+ { "editor_cursor_after_inserted_block", &edit_options.cursor_after_inserted_block },
+ { "editor_visible_tabs", &edit_options.visible_tabs },
+ { "editor_visible_spaces", &edit_options.visible_tws },
+ { "editor_line_state", &edit_options.line_state },
+ { "editor_simple_statusbar", &edit_options.simple_statusbar },
+ { "editor_check_new_line", &edit_options.check_nl_at_eof },
+ { "editor_show_right_margin", &edit_options.show_right_margin },
+ { "editor_group_undo", &edit_options.group_undo },
+ { "editor_state_full_filename", &edit_options.state_full_filename },
+#endif /* USE_INTERNAL_EDIT */
+ { "editor_ask_filename_before_edit", &editor_ask_filename_before_edit },
+ { "nice_rotating_dash", &nice_rotating_dash },
+ { "shadows", &mc_global.tty.shadows },
+ { "mcview_remember_file_position", &mcview_remember_file_position },
+ { "auto_fill_mkdir_name", &auto_fill_mkdir_name },
+ { "copymove_persistent_attr", &copymove_persistent_attr },
+ { NULL, NULL }
+};
+
+static const struct
+{
+ const char *opt_name;
+ int *opt_addr;
+} int_options [] = {
+ { "pause_after_run", &pause_after_run },
+ { "mouse_repeat_rate", &mou_auto_repeat },
+ { "double_click_speed", &double_click_speed },
+ { "old_esc_mode_timeout", &old_esc_mode_timeout },
+ { "max_dirt_limit", &mcview_max_dirt_limit },
+ { "num_history_items_recorded", &num_history_items_recorded },
+#ifdef ENABLE_VFS
+ { "vfs_timeout", &vfs_timeout },
+#ifdef ENABLE_VFS_FTP
+ { "ftpfs_directory_timeout", &ftpfs_directory_timeout },
+ { "ftpfs_retry_seconds", &ftpfs_retry_seconds },
+#endif /* ENABLE_VFS_FTP */
+#ifdef ENABLE_VFS_FISH
+ { "fish_directory_timeout", &fish_directory_timeout },
+#endif /* ENABLE_VFS_FISH */
+#endif /* ENABLE_VFS */
+ /* option_tab_spacing is used in internal viewer */
+ { "editor_tab_spacing", &option_tab_spacing },
+#ifdef USE_INTERNAL_EDIT
+ { "editor_word_wrap_line_length", &edit_options.word_wrap_line_length },
+ { "editor_option_save_mode", &edit_options.save_mode },
+#endif /* USE_INTERNAL_EDIT */
+ { NULL, NULL }
+};
+
+static const struct
+{
+ const char *opt_name;
+ char **opt_addr;
+ const char *opt_defval;
+} str_options[] = {
+#ifdef USE_INTERNAL_EDIT
+ { "editor_backup_extension", &edit_options.backup_ext, "~" },
+ { "editor_filesize_threshold", &edit_options.filesize_threshold, "64M" },
+ { "editor_stop_format_chars", &edit_options.stop_format_chars, "-+*\\,.;:&>" },
+#endif
+ { "mcview_eof", &mcview_show_eof, "" },
+ { NULL, NULL, NULL }
+};
+
+static const struct
+{
+ const char *opt_name;
+ gboolean *opt_addr;
+} panels_ini_options[] = {
+ { "show_mini_info", &panels_options.show_mini_info },
+ { "kilobyte_si", &panels_options.kilobyte_si },
+ { "mix_all_files", &panels_options.mix_all_files },
+ { "show_backups", &panels_options.show_backups },
+ { "show_dot_files", &panels_options.show_dot_files },
+ { "fast_reload", &panels_options.fast_reload },
+ { "fast_reload_msg_shown", &panels_options.fast_reload_msg_shown },
+ { "mark_moves_down", &panels_options.mark_moves_down },
+ { "reverse_files_only", &panels_options.reverse_files_only },
+ { "auto_save_setup_panels", &panels_options.auto_save_setup },
+ { "navigate_with_arrows", &panels_options.navigate_with_arrows },
+ { "panel_scroll_pages", &panels_options.scroll_pages },
+ { "panel_scroll_center", &panels_options.scroll_center },
+ { "mouse_move_pages", &panels_options.mouse_move_pages },
+ { "filetype_mode", &panels_options.filetype_mode },
+ { "permission_mode", &panels_options.permission_mode },
+ { "torben_fj_mode", &panels_options.torben_fj_mode },
+ { NULL, NULL }
+};
+/* *INDENT-ON* */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+setup__is_cfg_group_must_panel_config (const char *grp)
+{
+ return (!strcasecmp ("Dirs", grp) ||
+ !strcasecmp ("Temporal:New Right Panel", grp) ||
+ !strcasecmp ("Temporal:New Left Panel", grp) ||
+ !strcasecmp ("New Left Panel", grp) || !strcasecmp ("New Right Panel", grp))
+ ? grp : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+setup__move_panels_config_into_separate_file (const char *profile)
+{
+ mc_config_t *tmp_cfg;
+ char **groups, **curr_grp;
+
+ if (!exist_file (profile))
+ return;
+
+ tmp_cfg = mc_config_init (profile, FALSE);
+ if (tmp_cfg == NULL)
+ return;
+
+ groups = mc_config_get_groups (tmp_cfg, NULL);
+ if (*groups == NULL)
+ {
+ g_strfreev (groups);
+ mc_config_deinit (tmp_cfg);
+ return;
+ }
+
+ for (curr_grp = groups; *curr_grp != NULL; curr_grp++)
+ if (setup__is_cfg_group_must_panel_config (*curr_grp) == NULL)
+ mc_config_del_group (tmp_cfg, *curr_grp);
+
+ mc_config_save_to_file (tmp_cfg, panels_profile_name, NULL);
+ mc_config_deinit (tmp_cfg);
+
+ tmp_cfg = mc_config_init (profile, FALSE);
+ if (tmp_cfg == NULL)
+ {
+ g_strfreev (groups);
+ return;
+ }
+
+ for (curr_grp = groups; *curr_grp != NULL; curr_grp++)
+ {
+ const char *need_grp;
+
+ need_grp = setup__is_cfg_group_must_panel_config (*curr_grp);
+ if (need_grp != NULL)
+ mc_config_del_group (tmp_cfg, need_grp);
+ }
+ g_strfreev (groups);
+
+ mc_config_save_file (tmp_cfg, NULL);
+ mc_config_deinit (tmp_cfg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+load_config (void)
+{
+ size_t i;
+ const char *kt;
+
+ /* Load boolean options */
+ for (i = 0; bool_options[i].opt_name != NULL; i++)
+ *bool_options[i].opt_addr =
+ mc_config_get_bool (mc_global.main_config, CONFIG_APP_SECTION, bool_options[i].opt_name,
+ *bool_options[i].opt_addr);
+
+ /* Load integer options */
+ for (i = 0; int_options[i].opt_name != NULL; i++)
+ *int_options[i].opt_addr =
+ mc_config_get_int (mc_global.main_config, CONFIG_APP_SECTION, int_options[i].opt_name,
+ *int_options[i].opt_addr);
+
+ /* Load string options */
+ for (i = 0; str_options[i].opt_name != NULL; i++)
+ *str_options[i].opt_addr =
+ mc_config_get_string (mc_global.main_config, CONFIG_APP_SECTION,
+ str_options[i].opt_name, str_options[i].opt_defval);
+
+ /* Overwrite some options */
+#ifdef USE_INTERNAL_EDIT
+ if (edit_options.word_wrap_line_length <= 0)
+ edit_options.word_wrap_line_length = DEFAULT_WRAP_LINE_LENGTH;
+#else
+ /* Reset forced in case of build without internal editor */
+ use_internal_edit = FALSE;
+#endif /* USE_INTERNAL_EDIT */
+
+ if (option_tab_spacing <= 0)
+ option_tab_spacing = DEFAULT_TAB_SPACING;
+
+ kt = getenv ("KEYBOARD_KEY_TIMEOUT_US");
+ if (kt != NULL && kt[0] != '\0')
+ old_esc_mode_timeout = atoi (kt);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static panel_view_mode_t
+setup__load_panel_state (const char *section)
+{
+ char *buffer;
+ size_t i;
+ panel_view_mode_t mode = view_listing;
+
+ /* Load the display mode */
+ buffer = mc_config_get_string (mc_global.panels_config, section, "display", "listing");
+
+ for (i = 0; panel_types[i].opt_name != NULL; i++)
+ if (g_ascii_strcasecmp (panel_types[i].opt_name, buffer) == 0)
+ {
+ mode = panel_types[i].opt_type;
+ break;
+ }
+
+ g_free (buffer);
+
+ return mode;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+load_layout (void)
+{
+ size_t i;
+
+ /* actual options override legacy ones */
+ for (i = 0; layout_int_options[i].opt_name != NULL; i++)
+ *layout_int_options[i].opt_addr =
+ mc_config_get_int (mc_global.main_config, CONFIG_LAYOUT_SECTION,
+ layout_int_options[i].opt_name, *layout_int_options[i].opt_addr);
+
+ for (i = 0; layout_bool_options[i].opt_name != NULL; i++)
+ *layout_bool_options[i].opt_addr =
+ mc_config_get_bool (mc_global.main_config, CONFIG_LAYOUT_SECTION,
+ layout_bool_options[i].opt_name, *layout_bool_options[i].opt_addr);
+
+ startup_left_mode = setup__load_panel_state ("New Left Panel");
+ startup_right_mode = setup__load_panel_state ("New Right Panel");
+
+ /* At least one of the panels is a listing panel */
+ if (startup_left_mode != view_listing && startup_right_mode != view_listing)
+ startup_left_mode = view_listing;
+
+ boot_current_is_left =
+ mc_config_get_bool (mc_global.panels_config, "Dirs", "current_is_left", TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+load_keys_from_section (const char *terminal, mc_config_t * cfg)
+{
+ char *section_name;
+ gchar **profile_keys, **keys;
+ char *valcopy, *value;
+ long key_code;
+
+ if (terminal == NULL)
+ return;
+
+ section_name = g_strconcat ("terminal:", terminal, (char *) NULL);
+ keys = mc_config_get_keys (cfg, section_name, NULL);
+
+ for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
+ {
+ /* copy=other causes all keys from [terminal:other] to be loaded. */
+ if (g_ascii_strcasecmp (*profile_keys, "copy") == 0)
+ {
+ valcopy = mc_config_get_string (cfg, section_name, *profile_keys, "");
+ load_keys_from_section (valcopy, cfg);
+ g_free (valcopy);
+ continue;
+ }
+
+ key_code = tty_keyname_to_keycode (*profile_keys, NULL);
+ if (key_code != 0)
+ {
+ gchar **values;
+
+ values = mc_config_get_string_list (cfg, section_name, *profile_keys, NULL);
+ if (values != NULL)
+ {
+ gchar **curr_values;
+
+ for (curr_values = values; *curr_values != NULL; curr_values++)
+ {
+ valcopy = convert_controls (*curr_values);
+ define_sequence (key_code, valcopy, MCKEY_NOACTION);
+ g_free (valcopy);
+ }
+
+ g_strfreev (values);
+ }
+ else
+ {
+ value = mc_config_get_string (cfg, section_name, *profile_keys, "");
+ valcopy = convert_controls (value);
+ define_sequence (key_code, valcopy, MCKEY_NOACTION);
+ g_free (valcopy);
+ g_free (value);
+ }
+ }
+ }
+ g_strfreev (keys);
+ g_free (section_name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_save_type (const char *section, panel_view_mode_t type)
+{
+ size_t i;
+
+ for (i = 0; panel_types[i].opt_name != NULL; i++)
+ if (panel_types[i].opt_type == type)
+ {
+ mc_config_set_string (mc_global.panels_config, section, "display",
+ panel_types[i].opt_name);
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Load panels options from [Panels] section.
+ */
+static void
+panels_load_options (void)
+{
+ if (mc_config_has_group (mc_global.main_config, CONFIG_PANELS_SECTION))
+ {
+ size_t i;
+ int qmode;
+
+ for (i = 0; panels_ini_options[i].opt_name != NULL; i++)
+ *panels_ini_options[i].opt_addr =
+ mc_config_get_bool (mc_global.main_config, CONFIG_PANELS_SECTION,
+ panels_ini_options[i].opt_name,
+ *panels_ini_options[i].opt_addr);
+
+ qmode = mc_config_get_int (mc_global.main_config, CONFIG_PANELS_SECTION,
+ "quick_search_mode", (int) panels_options.qsearch_mode);
+ if (qmode < 0)
+ panels_options.qsearch_mode = QSEARCH_CASE_INSENSITIVE;
+ else if (qmode >= QSEARCH_NUM)
+ panels_options.qsearch_mode = QSEARCH_PANEL_CASE;
+ else
+ panels_options.qsearch_mode = (qsearch_mode_t) qmode;
+
+ panels_options.select_flags =
+ mc_config_get_int (mc_global.main_config, CONFIG_PANELS_SECTION, "select_flags",
+ (int) panels_options.select_flags);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Save panels options in [Panels] section.
+ */
+static void
+panels_save_options (void)
+{
+ size_t i;
+
+ for (i = 0; panels_ini_options[i].opt_name != NULL; i++)
+ mc_config_set_bool (mc_global.main_config, CONFIG_PANELS_SECTION,
+ panels_ini_options[i].opt_name, *panels_ini_options[i].opt_addr);
+
+ mc_config_set_int (mc_global.main_config, CONFIG_PANELS_SECTION,
+ "quick_search_mode", (int) panels_options.qsearch_mode);
+ mc_config_set_int (mc_global.main_config, CONFIG_PANELS_SECTION,
+ "select_flags", (int) panels_options.select_flags);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+save_config (void)
+{
+ size_t i;
+
+ /* Save boolean options */
+ for (i = 0; bool_options[i].opt_name != NULL; i++)
+ mc_config_set_bool (mc_global.main_config, CONFIG_APP_SECTION, bool_options[i].opt_name,
+ *bool_options[i].opt_addr);
+
+ /* Save integer options */
+ for (i = 0; int_options[i].opt_name != NULL; i++)
+ mc_config_set_int (mc_global.main_config, CONFIG_APP_SECTION, int_options[i].opt_name,
+ *int_options[i].opt_addr);
+
+ /* Save string options */
+ for (i = 0; str_options[i].opt_name != NULL; i++)
+ mc_config_set_string (mc_global.main_config, CONFIG_APP_SECTION, str_options[i].opt_name,
+ *str_options[i].opt_addr);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+save_layout (void)
+{
+ size_t i;
+
+ /* Save integer options */
+ for (i = 0; layout_int_options[i].opt_name != NULL; i++)
+ mc_config_set_int (mc_global.main_config, CONFIG_LAYOUT_SECTION,
+ layout_int_options[i].opt_name, *layout_int_options[i].opt_addr);
+
+ /* Save boolean options */
+ for (i = 0; layout_bool_options[i].opt_name != NULL; i++)
+ mc_config_set_bool (mc_global.main_config, CONFIG_LAYOUT_SECTION,
+ layout_bool_options[i].opt_name, *layout_bool_options[i].opt_addr);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* save panels.ini */
+static void
+save_panel_types (void)
+{
+ panel_view_mode_t type;
+
+ if (mc_global.mc_run_mode != MC_RUN_FULL)
+ return;
+
+ type = get_panel_type (0);
+ panel_save_type ("New Left Panel", type);
+ if (type == view_listing)
+ panel_save_setup (left_panel, left_panel->name);
+ type = get_panel_type (1);
+ panel_save_type ("New Right Panel", type);
+ if (type == view_listing)
+ panel_save_setup (right_panel, right_panel->name);
+
+ {
+ char *dirs;
+
+ dirs = get_panel_dir_for (other_panel);
+ mc_config_set_string (mc_global.panels_config, "Dirs", "other_dir", dirs);
+ g_free (dirs);
+ }
+
+ if (current_panel != NULL)
+ mc_config_set_bool (mc_global.panels_config, "Dirs", "current_is_left",
+ get_current_index () == 0);
+
+ if (mc_global.panels_config->ini_path == NULL)
+ mc_global.panels_config->ini_path = g_strdup (panels_profile_name);
+
+ mc_config_del_group (mc_global.panels_config, "Temporal:New Left Panel");
+ mc_config_del_group (mc_global.panels_config, "Temporal:New Right Panel");
+
+ mc_config_save_file (mc_global.panels_config, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+setup_init (void)
+{
+ if (profile_name == NULL)
+ {
+ char *profile;
+
+ profile = mc_config_get_full_path (MC_CONFIG_FILE);
+ if (!exist_file (profile))
+ {
+ char *inifile;
+
+ inifile = mc_build_filename (mc_global.sysconfig_dir, "mc.ini", (char *) NULL);
+ if (exist_file (inifile))
+ {
+ g_free (profile);
+ profile = inifile;
+ }
+ else
+ {
+ g_free (inifile);
+ inifile = mc_build_filename (mc_global.share_data_dir, "mc.ini", (char *) NULL);
+ if (!exist_file (inifile))
+ g_free (inifile);
+ else
+ {
+ g_free (profile);
+ profile = inifile;
+ }
+ }
+ }
+
+ profile_name = profile;
+ }
+
+ return profile_name;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+load_setup (void)
+{
+ const char *profile;
+
+#ifdef HAVE_CHARSET
+ const char *cbuffer;
+
+ load_codepages_list ();
+#endif /* HAVE_CHARSET */
+
+ profile = setup_init ();
+
+ /* mc.lib is common for all users, but has priority lower than
+ ${XDG_CONFIG_HOME}/mc/ini. FIXME: it's only used for keys and treestore now */
+ mc_global.profile_name =
+ g_build_filename (mc_global.sysconfig_dir, MC_GLOBAL_CONFIG_FILE, (char *) NULL);
+ if (!exist_file (mc_global.profile_name))
+ {
+ g_free (mc_global.profile_name);
+ mc_global.profile_name =
+ g_build_filename (mc_global.share_data_dir, MC_GLOBAL_CONFIG_FILE, (char *) NULL);
+ }
+
+ panels_profile_name = mc_config_get_full_path (MC_PANELS_FILE);
+
+ mc_global.main_config = mc_config_init (profile, FALSE);
+
+ if (!exist_file (panels_profile_name))
+ setup__move_panels_config_into_separate_file (profile);
+
+ mc_global.panels_config = mc_config_init (panels_profile_name, FALSE);
+
+ load_config ();
+ load_layout ();
+ panels_load_options ();
+ external_panelize_load ();
+
+ /* Load time formats */
+ user_recent_timeformat =
+ mc_config_get_string (mc_global.main_config, CONFIG_MISC_SECTION, "timeformat_recent",
+ FMTTIME);
+ user_old_timeformat =
+ mc_config_get_string (mc_global.main_config, CONFIG_MISC_SECTION, "timeformat_old",
+ FMTYEAR);
+
+#ifdef ENABLE_VFS_FTP
+ ftpfs_proxy_host =
+ mc_config_get_string (mc_global.main_config, CONFIG_MISC_SECTION, "ftp_proxy_host", "gate");
+ ftpfs_init_passwd ();
+#endif /* ENABLE_VFS_FTP */
+
+ /* The default color and the terminal dependent color */
+ mc_global.tty.setup_color_string =
+ mc_config_get_string (mc_global.main_config, "Colors", "base_color", "");
+ mc_global.tty.term_color_string =
+ mc_config_get_string (mc_global.main_config, "Colors", getenv ("TERM"), "");
+ mc_global.tty.color_terminal_string =
+ mc_config_get_string (mc_global.main_config, "Colors", "color_terminals", "");
+
+ /* Load the directory history */
+ /* directory_history_load (); */
+ /* Remove the temporal entries */
+
+#ifdef HAVE_CHARSET
+ if (codepages->len > 1)
+ {
+ char *buffer;
+
+ buffer =
+ mc_config_get_string (mc_global.main_config, CONFIG_MISC_SECTION, "display_codepage",
+ "");
+ if (buffer[0] != '\0')
+ {
+ mc_global.display_codepage = get_codepage_index (buffer);
+ cp_display = get_codepage_id (mc_global.display_codepage);
+ }
+ g_free (buffer);
+ buffer =
+ mc_config_get_string (mc_global.main_config, CONFIG_MISC_SECTION, "source_codepage",
+ "");
+ if (buffer[0] != '\0')
+ {
+ default_source_codepage = get_codepage_index (buffer);
+ mc_global.source_codepage = default_source_codepage; /* May be source_codepage doesn't need this */
+ cp_source = get_codepage_id (mc_global.source_codepage);
+ }
+ g_free (buffer);
+ }
+
+ autodetect_codeset =
+ mc_config_get_string (mc_global.main_config, CONFIG_MISC_SECTION, "autodetect_codeset", "");
+ if ((autodetect_codeset[0] != '\0') && (strcmp (autodetect_codeset, "off") != 0))
+ is_autodetect_codeset_enabled = TRUE;
+
+ g_free (init_translation_table (mc_global.source_codepage, mc_global.display_codepage));
+ cbuffer = get_codepage_id (mc_global.display_codepage);
+ if (cbuffer != NULL)
+ mc_global.utf8_display = str_isutf8 (cbuffer);
+#endif /* HAVE_CHARSET */
+
+#ifdef HAVE_ASPELL
+ spell_language =
+ mc_config_get_string (mc_global.main_config, CONFIG_MISC_SECTION, "spell_language", "en");
+#endif /* HAVE_ASPELL */
+
+ clipboard_store_path =
+ mc_config_get_string (mc_global.main_config, CONFIG_MISC_SECTION, "clipboard_store", "");
+ clipboard_paste_path =
+ mc_config_get_string (mc_global.main_config, CONFIG_MISC_SECTION, "clipboard_paste", "");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+save_setup (gboolean save_options, gboolean save_panel_options)
+{
+ gboolean ret = TRUE;
+
+ saving_setup = 1;
+
+ save_hotlist ();
+
+ if (save_panel_options)
+ save_panel_types ();
+
+ if (save_options)
+ {
+ char *tmp_profile;
+
+ save_config ();
+ save_layout ();
+ panels_save_options ();
+ external_panelize_save ();
+ /* directory_history_save (); */
+
+#ifdef ENABLE_VFS_FTP
+ mc_config_set_string (mc_global.main_config, CONFIG_MISC_SECTION, "ftpfs_password",
+ ftpfs_anonymous_passwd);
+ if (ftpfs_proxy_host)
+ mc_config_set_string (mc_global.main_config, CONFIG_MISC_SECTION, "ftp_proxy_host",
+ ftpfs_proxy_host);
+#endif /* ENABLE_VFS_FTP */
+
+#ifdef HAVE_CHARSET
+ mc_config_set_string (mc_global.main_config, CONFIG_MISC_SECTION, "display_codepage",
+ get_codepage_id (mc_global.display_codepage));
+ mc_config_set_string (mc_global.main_config, CONFIG_MISC_SECTION, "source_codepage",
+ get_codepage_id (default_source_codepage));
+ mc_config_set_string (mc_global.main_config, CONFIG_MISC_SECTION, "autodetect_codeset",
+ autodetect_codeset);
+#endif /* HAVE_CHARSET */
+
+#ifdef HAVE_ASPELL
+ mc_config_set_string (mc_global.main_config, CONFIG_MISC_SECTION, "spell_language",
+ spell_language);
+#endif /* HAVE_ASPELL */
+
+ mc_config_set_string (mc_global.main_config, CONFIG_MISC_SECTION, "clipboard_store",
+ clipboard_store_path);
+ mc_config_set_string (mc_global.main_config, CONFIG_MISC_SECTION, "clipboard_paste",
+ clipboard_paste_path);
+
+ tmp_profile = mc_config_get_full_path (MC_CONFIG_FILE);
+ ret = mc_config_save_to_file (mc_global.main_config, tmp_profile, NULL);
+ g_free (tmp_profile);
+ }
+
+ saving_setup = 0;
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+done_setup (void)
+{
+ size_t i;
+
+ g_free (clipboard_store_path);
+ g_free (clipboard_paste_path);
+ g_free (mc_global.profile_name);
+ g_free (mc_global.tty.color_terminal_string);
+ g_free (mc_global.tty.term_color_string);
+ g_free (mc_global.tty.setup_color_string);
+ g_free (profile_name);
+ g_free (panels_profile_name);
+ mc_config_deinit (mc_global.main_config);
+ mc_config_deinit (mc_global.panels_config);
+
+ g_free (user_recent_timeformat);
+ g_free (user_old_timeformat);
+
+ for (i = 0; str_options[i].opt_name != NULL; i++)
+ g_free (*str_options[i].opt_addr);
+
+ done_hotlist ();
+ external_panelize_free ();
+ /* directory_history_free (); */
+
+#ifdef HAVE_CHARSET
+ g_free (autodetect_codeset);
+ free_codepages_list ();
+#endif
+
+#ifdef HAVE_ASPELL
+ g_free (spell_language);
+#endif /* HAVE_ASPELL */
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+setup_save_config_show_error (const char *filename, GError ** mcerror)
+{
+ if (mcerror != NULL && *mcerror != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot save file %s:\n%s"), filename, (*mcerror)->message);
+ g_error_free (*mcerror);
+ *mcerror = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+load_key_defs (void)
+{
+ /*
+ * Load keys from mc.lib before ${XDG_CONFIG_HOME}/mc/ini, so that the user
+ * definitions override global settings.
+ */
+ mc_config_t *mc_global_config;
+
+ mc_global_config = mc_config_init (mc_global.profile_name, FALSE);
+ if (mc_global_config != NULL)
+ {
+ load_keys_from_section ("general", mc_global_config);
+ load_keys_from_section (getenv ("TERM"), mc_global_config);
+ mc_config_deinit (mc_global_config);
+ }
+
+ load_keys_from_section ("general", mc_global.main_config);
+ load_keys_from_section (getenv ("TERM"), mc_global.main_config);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS_FTP
+char *
+load_anon_passwd (void)
+{
+ char *buffer;
+
+ buffer =
+ mc_config_get_string (mc_global.main_config, CONFIG_MISC_SECTION, "ftpfs_password", "");
+
+ if ((buffer != NULL) && (buffer[0] != '\0'))
+ return buffer;
+
+ g_free (buffer);
+ return NULL;
+}
+#endif /* ENABLE_VFS_FTP */
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_load_setup (WPanel * panel, const char *section)
+{
+ size_t i;
+ char *buffer, buffer2[BUF_TINY];
+
+ panel->sort_info.reverse =
+ mc_config_get_bool (mc_global.panels_config, section, "reverse", FALSE);
+ panel->sort_info.case_sensitive =
+ mc_config_get_bool (mc_global.panels_config, section, "case_sensitive",
+ OS_SORT_CASE_SENSITIVE_DEFAULT);
+ panel->sort_info.exec_first =
+ mc_config_get_bool (mc_global.panels_config, section, "exec_first", FALSE);
+
+ /* Load sort order */
+ buffer = mc_config_get_string (mc_global.panels_config, section, "sort_order", "name");
+ panel->sort_field = panel_get_field_by_id (buffer);
+ if (panel->sort_field == NULL)
+ panel->sort_field = panel_get_field_by_id ("name");
+
+ g_free (buffer);
+
+ /* Load the listing format */
+ buffer = mc_config_get_string (mc_global.panels_config, section, "list_format", NULL);
+ if (buffer == NULL)
+ {
+ /* fallback to old option */
+ buffer = mc_config_get_string (mc_global.panels_config, section, "list_mode", "full");
+ }
+ panel->list_format = list_full;
+ for (i = 0; list_formats[i].key != NULL; i++)
+ if (g_ascii_strcasecmp (list_formats[i].key, buffer) == 0)
+ {
+ panel->list_format = list_formats[i].list_format;
+ break;
+ }
+ g_free (buffer);
+
+ panel->brief_cols = mc_config_get_int (mc_global.panels_config, section, "brief_cols", 2);
+
+ /* User formats */
+ g_free (panel->user_format);
+ panel->user_format =
+ mc_config_get_string (mc_global.panels_config, section, "user_format", DEFAULT_USER_FORMAT);
+
+ for (i = 0; i < LIST_FORMATS; i++)
+ {
+ g_free (panel->user_status_format[i]);
+ g_snprintf (buffer2, sizeof (buffer2), "user_status%lld", (long long) i);
+ panel->user_status_format[i] =
+ mc_config_get_string (mc_global.panels_config, section, buffer2, DEFAULT_USER_FORMAT);
+ }
+
+ panel->user_mini_status =
+ mc_config_get_bool (mc_global.panels_config, section, "user_mini_status", FALSE);
+
+ panel->filter.value =
+ mc_config_get_string (mc_global.panels_config, section, "filter_value", NULL);
+ panel->filter.flags =
+ mc_config_get_int (mc_global.panels_config, section, "filter_flags",
+ (int) FILE_FILTER_DEFAULT_FLAGS);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_save_setup (WPanel * panel, const char *section)
+{
+ char buffer[BUF_TINY];
+ size_t i;
+
+ mc_config_set_bool (mc_global.panels_config, section, "reverse", panel->sort_info.reverse);
+ mc_config_set_bool (mc_global.panels_config, section, "case_sensitive",
+ panel->sort_info.case_sensitive);
+ mc_config_set_bool (mc_global.panels_config, section, "exec_first",
+ panel->sort_info.exec_first);
+
+ mc_config_set_string (mc_global.panels_config, section, "sort_order", panel->sort_field->id);
+
+ for (i = 0; list_formats[i].key != NULL; i++)
+ if (list_formats[i].list_format == (int) panel->list_format)
+ {
+ mc_config_set_string (mc_global.panels_config, section, "list_format",
+ list_formats[i].key);
+ break;
+ }
+
+ mc_config_set_int (mc_global.panels_config, section, "brief_cols", panel->brief_cols);
+
+ mc_config_set_string (mc_global.panels_config, section, "user_format", panel->user_format);
+
+ for (i = 0; i < LIST_FORMATS; i++)
+ {
+ g_snprintf (buffer, sizeof (buffer), "user_status%lld", (long long) i);
+ mc_config_set_string (mc_global.panels_config, section, buffer,
+ panel->user_status_format[i]);
+ }
+
+ mc_config_set_bool (mc_global.panels_config, section, "user_mini_status",
+ panel->user_mini_status);
+
+ /* do not save the default filter */
+ if (panel->filter.handler != NULL)
+ mc_config_set_string (mc_global.panels_config, section, "filter_value",
+ panel->filter.value);
+ else
+ mc_config_del_key (mc_global.panels_config, section, "filter_value");
+ mc_config_set_int (mc_global.panels_config, section, "filter_flags", (int) panel->filter.flags);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/setup.h b/src/setup.h
new file mode 100644
index 0000000..b6e675d
--- /dev/null
+++ b/src/setup.h
@@ -0,0 +1,162 @@
+/** \file setup.h
+ * \brief Header: setup loading/saving
+ */
+
+#ifndef MC__SETUP_H
+#define MC__SETUP_H
+
+#include <config.h>
+
+#include "lib/global.h" /* GError */
+
+#include "filemanager/layout.h" /* panel_view_mode_t */
+#include "filemanager/panel.h" /* WPanel */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* TAB length for editor and viewer */
+#define DEFAULT_TAB_SPACING 8
+
+#define MAX_MACRO_LENGTH 1024
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ QSEARCH_CASE_INSENSITIVE = 0, /* quick search in case insensitive mode */
+ QSEARCH_CASE_SENSITIVE = 1, /* quick search in case sensitive mode */
+ QSEARCH_PANEL_CASE = 2, /* quick search get value from panel case_sensitive */
+ QSEARCH_NUM
+} qsearch_mode_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* panels ini options; [Panels] section */
+typedef struct
+{
+ gboolean show_mini_info; /* If true, show the mini-info on the panel */
+ gboolean kilobyte_si; /* If TRUE, SI units (1000 based) will be used for larger units
+ * (kilobyte, megabyte, ...). If FALSE, binary units (1024 based) will be used */
+ gboolean mix_all_files; /* If FALSE then directories are shown separately from files */
+ gboolean show_backups; /* If TRUE, show files ending in ~ */
+ gboolean show_dot_files; /* If TRUE, show files starting with a dot */
+ gboolean fast_reload; /* If TRUE then use stat() on the cwd to determine directory changes */
+ gboolean fast_reload_msg_shown; /* Have we shown the fast-reload warning in the past? */
+ gboolean mark_moves_down; /* If TRUE, marking a files moves the cursor down */
+ gboolean reverse_files_only; /* If TRUE, only selection of files is inverted */
+ gboolean auto_save_setup;
+ gboolean navigate_with_arrows; /* If TRUE: l&r arrows are used to chdir if the input line is empty */
+ gboolean scroll_pages; /* If TRUE, panel is scrolled by half the display when the cursor reaches
+ the end or the beginning of the panel */
+ gboolean scroll_center; /* If TRUE, scroll when the cursor hits the middle of the panel */
+ gboolean mouse_move_pages; /* Scroll page/item using mouse wheel */
+ gboolean filetype_mode; /* If TRUE then add per file type highlighting */
+ gboolean permission_mode; /* If TRUE, we use permission highlighting */
+ qsearch_mode_t qsearch_mode; /* Quick search mode */
+ gboolean torben_fj_mode; /* If TRUE, use some usability hacks by Torben */
+ select_flags_t select_flags; /* Select/unselect file flags */
+} panels_options_t;
+
+typedef struct macro_action_t
+{
+ long action;
+ int ch;
+} macro_action_t;
+
+typedef struct macros_t
+{
+ int hotkey;
+ GArray *macro;
+} macros_t;
+
+struct mc_fhl_struct;
+
+/*** global variables defined in .c file *********************************************************/
+
+/* global parameters */
+extern gboolean confirm_delete;
+extern gboolean confirm_directory_hotlist_delete;
+extern gboolean confirm_execute;
+extern gboolean confirm_exit;
+extern gboolean confirm_overwrite;
+extern gboolean confirm_view_dir;
+extern gboolean safe_delete;
+extern gboolean safe_overwrite;
+extern gboolean clear_before_exec;
+extern gboolean auto_menu;
+extern gboolean drop_menus;
+extern gboolean verbose;
+extern gboolean copymove_persistent_attr;
+extern gboolean classic_progressbar;
+extern gboolean easy_patterns;
+extern int option_tab_spacing;
+extern gboolean auto_save_setup;
+extern gboolean only_leading_plus_minus;
+extern int cd_symlinks;
+extern gboolean auto_fill_mkdir_name;
+extern gboolean output_starts_shell;
+#ifdef USE_FILE_CMD
+extern gboolean use_file_to_check_type;
+#endif
+extern gboolean file_op_compute_totals;
+extern gboolean editor_ask_filename_before_edit;
+
+extern panels_options_t panels_options;
+
+extern panel_view_mode_t startup_left_mode;
+extern panel_view_mode_t startup_right_mode;
+extern gboolean boot_current_is_left;
+extern gboolean use_internal_view;
+extern gboolean use_internal_edit;
+
+#ifdef HAVE_CHARSET
+extern int default_source_codepage;
+extern char *autodetect_codeset;
+extern gboolean is_autodetect_codeset_enabled;
+#endif /* !HAVE_CHARSET */
+
+#ifdef HAVE_ASPELL
+extern char *spell_language;
+#endif
+
+/* Value of "other_dir" key in ini file */
+extern char *saved_other_dir;
+
+/* If set, then print to the given file the last directory we were at */
+extern char *last_wd_string;
+
+extern int quit;
+/* Set to TRUE to suppress printing the last directory */
+extern gboolean print_last_revert;
+
+#ifdef USE_INTERNAL_EDIT
+/* index to record_macro_buf[], -1 if not recording a macro */
+extern int macro_index;
+
+/* macro stuff */
+extern struct macro_action_t record_macro_buf[MAX_MACRO_LENGTH];
+
+extern GArray *macros_list;
+#endif /* USE_INTERNAL_EDIT */
+
+extern int saving_setup;
+
+/*** declarations of public functions ************************************************************/
+
+const char *setup_init (void);
+void load_setup (void);
+gboolean save_setup (gboolean save_options, gboolean save_panel_options);
+void done_setup (void);
+void setup_save_config_show_error (const char *filename, GError ** mcerror);
+
+void load_key_defs (void);
+#ifdef ENABLE_VFS_FTP
+char *load_anon_passwd (void);
+#endif /* ENABLE_VFS_FTP */
+
+void panel_load_setup (WPanel * panel, const char *section);
+void panel_save_setup (WPanel * panel, const char *section);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__SETUP_H */
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 */
diff --git a/src/textconf.c b/src/textconf.c
new file mode 100644
index 0000000..e8accd9
--- /dev/null
+++ b/src/textconf.c
@@ -0,0 +1,264 @@
+/*
+ Print features specific for this build
+
+ Copyright (C) 2000-2023
+ Free Software Foundation, Inc.
+
+ 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 textconf.c
+ * \brief Source: prints features specific for this build
+ */
+
+#include <config.h>
+
+#include <limits.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#if defined (ENABLE_VFS) && defined(ENABLE_VFS_SFTP)
+#include <libssh2.h>
+#endif /* ENABLE_VFS_SFTP && ENABLE_VFS */
+
+#include "lib/global.h"
+#include "lib/fileloc.h"
+#include "lib/mcconfig.h"
+#include "lib/util.h" /* mc_get_profile_root() */
+#include "lib/tty/tty.h" /* S-Lang or ncurses version */
+
+#include "src/textconf.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+#ifdef ENABLE_VFS
+static const char *const vfs_supported[] = {
+#ifdef ENABLE_VFS_CPIO
+ "cpiofs",
+#endif
+#ifdef ENABLE_VFS_TAR
+ "tarfs",
+#endif
+#ifdef ENABLE_VFS_SFS
+ "sfs",
+#endif
+#ifdef ENABLE_VFS_EXTFS
+ "extfs",
+#endif
+#ifdef ENABLE_VFS_UNDELFS
+ "ext2undelfs",
+#endif
+#ifdef ENABLE_VFS_FTP
+ "ftpfs",
+#endif
+#ifdef ENABLE_VFS_SFTP
+ "sftpfs",
+#endif
+#ifdef ENABLE_VFS_FISH
+ "fish",
+#endif
+ NULL
+};
+#endif /* ENABLE_VFS */
+
+static const char *const features[] = {
+
+#ifdef USE_INTERNAL_EDIT
+#ifdef HAVE_ASPELL
+ N_("With builtin Editor and Aspell support"),
+#else
+ N_("With builtin Editor"),
+#endif /* HAVE_ASPELL */
+#endif /* USE_INTERNAL_EDIT */
+
+#ifdef ENABLE_SUBSHELL
+#ifdef SUBSHELL_OPTIONAL
+ N_("With optional subshell support"),
+#else
+ N_("With subshell support as default"),
+#endif
+#endif /* !ENABLE_SUBSHELL */
+
+#ifdef ENABLE_BACKGROUND
+ N_("With support for background operations"),
+#endif
+
+#ifdef HAVE_LIBGPM
+ N_("With mouse support on xterm and Linux console"),
+#else
+ N_("With mouse support on xterm"),
+#endif
+
+#ifdef HAVE_TEXTMODE_X11_SUPPORT
+ N_("With support for X11 events"),
+#endif
+
+#ifdef ENABLE_NLS
+ N_("With internationalization support"),
+#endif
+
+#ifdef HAVE_CHARSET
+ N_("With multiple codepages support"),
+#endif
+
+#ifdef ENABLE_EXT2FS_ATTR
+ N_("With ext2fs attributes support"),
+#endif
+
+ NULL
+};
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+show_version (void)
+{
+ size_t i;
+
+ printf (_("GNU Midnight Commander %s\n"), mc_global.mc_version);
+
+ printf (_("Built with GLib %d.%d.%d\n"),
+ GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, GLIB_MICRO_VERSION);
+
+#ifdef HAVE_SLANG
+ printf (_("Built with S-Lang %s with terminfo database\n"), SLANG_VERSION_STRING);
+#elif defined(USE_NCURSES)
+#ifdef NCURSES_VERSION
+ printf (_("Built with ncurses %s\n"), NCURSES_VERSION);
+#else
+ puts (_("Built with ncurses (unknown version)"));
+#endif /* !NCURSES_VERSION */
+#elif defined(USE_NCURSESW)
+#ifdef NCURSES_VERSION
+ printf (_("Built with ncursesw %s\n"), NCURSES_VERSION);
+#else
+ puts (_("Built with ncursesw (unknown version)"));
+#endif /* !NCURSES_VERSION */
+#else
+#error "Cannot compile mc without S-Lang or ncurses"
+#endif /* !HAVE_SLANG && !USE_NCURSES */
+
+#if defined (ENABLE_VFS) && defined(ENABLE_VFS_SFTP)
+ printf (_("Built with libssh2 %d.%d.%d\n"),
+ LIBSSH2_VERSION_MAJOR, LIBSSH2_VERSION_MINOR, LIBSSH2_VERSION_PATCH);
+#endif /* ENABLE_VFS_SFTP && ENABLE_VFS */
+
+ for (i = 0; features[i] != NULL; i++)
+ puts (_(features[i]));
+
+#ifdef ENABLE_VFS
+ puts (_("Virtual File Systems:"));
+ for (i = 0; vfs_supported[i] != NULL; i++)
+ printf ("%s %s", i == 0 ? "" : ",", _(vfs_supported[i]));
+ (void) puts ("");
+#endif /* ENABLE_VFS */
+
+ (void) puts (_("Data types:"));
+#define TYPE_INFO(T) \
+ (void)printf(" %s: %d;", #T, (int) (CHAR_BIT * sizeof(T)))
+ TYPE_INFO (char);
+ TYPE_INFO (int);
+ TYPE_INFO (long);
+ TYPE_INFO (void *);
+ TYPE_INFO (size_t);
+ TYPE_INFO (off_t);
+#undef TYPE_INFO
+ (void) puts ("");
+}
+
+/* --------------------------------------------------------------------------------------------- */
+#define PRINTF_GROUP(a) \
+ (void) printf ("[%s]\n", a)
+#define PRINTF_SECTION(a,b) \
+ (void) printf (" %-17s %s\n", a, b)
+#define PRINTF_SECTION2(a,b) \
+ (void) printf (" %-17s %s/\n", a, b)
+#define PRINTF(a, b, c) \
+ (void) printf ("\t%-15s %s/%s\n", a, b, c)
+#define PRINTF2(a, b, c) \
+ (void) printf ("\t%-15s %s%s\n", a, b, c)
+
+void
+show_datadirs_extended (void)
+{
+ (void) printf ("%s %s\n", _("Home directory:"), mc_config_get_home_dir ());
+ (void) printf ("%s %s\n", _("Profile root directory:"), mc_get_profile_root ());
+ (void) puts ("");
+
+ PRINTF_GROUP (_("System data"));
+
+ PRINTF_SECTION (_("Config directory:"), mc_global.sysconfig_dir);
+ PRINTF_SECTION (_("Data directory:"), mc_global.share_data_dir);
+
+ PRINTF_SECTION (_("File extension handlers:"), EXTHELPERSDIR);
+
+#if defined ENABLE_VFS_EXTFS || defined ENABLE_VFS_FISH
+ PRINTF_SECTION (_("VFS plugins and scripts:"), LIBEXECDIR);
+#ifdef ENABLE_VFS_EXTFS
+ PRINTF2 ("extfs.d:", LIBEXECDIR, MC_EXTFS_DIR PATH_SEP_STR);
+#endif
+#ifdef ENABLE_VFS_FISH
+ PRINTF2 ("fish:", LIBEXECDIR, FISH_PREFIX PATH_SEP_STR);
+#endif
+#endif /* ENABLE_VFS_EXTFS || defiined ENABLE_VFS_FISH */
+ (void) puts ("");
+
+ PRINTF_GROUP (_("User data"));
+
+ PRINTF_SECTION2 (_("Config directory:"), mc_config_get_path ());
+ PRINTF_SECTION2 (_("Data directory:"), mc_config_get_data_path ());
+ PRINTF ("skins:", mc_config_get_data_path (), MC_SKINS_DIR PATH_SEP_STR);
+#ifdef ENABLE_VFS_EXTFS
+ PRINTF ("extfs.d:", mc_config_get_data_path (), MC_EXTFS_DIR PATH_SEP_STR);
+#endif
+#ifdef ENABLE_VFS_FISH
+ PRINTF ("fish:", mc_config_get_data_path (), FISH_PREFIX PATH_SEP_STR);
+#endif
+#ifdef USE_INTERNAL_EDIT
+ PRINTF ("mcedit macros:", mc_config_get_data_path (), MC_MACRO_FILE);
+ PRINTF ("mcedit external macros:", mc_config_get_data_path (), EDIT_HOME_MACRO_FILE ".*");
+#endif
+ PRINTF_SECTION2 (_("Cache directory:"), mc_config_get_cache_path ());
+}
+
+#undef PRINTF
+#undef PRINTF_SECTION
+#undef PRINTF_GROUP
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_CONFIGURE_ARGS
+void
+show_configure_options (void)
+{
+ (void) puts (MC_CONFIGURE_ARGS);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/textconf.h b/src/textconf.h
new file mode 100644
index 0000000..9e039ae
--- /dev/null
+++ b/src/textconf.h
@@ -0,0 +1,23 @@
+/** \file textconf.h
+ * \brief Header: prints features specific for this build
+ */
+
+#ifndef MC__TEXTCONF_H
+#define MC__TEXTCONF_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+extern void show_version (void);
+extern void show_datadirs_extended (void);
+extern void show_configure_options (void);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__TEXTCONF_H */
diff --git a/src/usermenu.c b/src/usermenu.c
new file mode 100644
index 0000000..c328871
--- /dev/null
+++ b/src/usermenu.c
@@ -0,0 +1,1176 @@
+/*
+ User Menu implementation
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013
+
+ 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 usermenu.c
+ * \brief Source: user menu implementation
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/fileloc.h"
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/search.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+
+#ifdef USE_INTERNAL_EDIT
+#include "src/editor/edit.h" /* WEdit */
+#endif
+#include "src/viewer/mcviewer.h" /* for default_* externs */
+
+#include "src/args.h" /* mc_run_param0 */
+#include "src/execute.h"
+#include "src/setup.h"
+#include "src/history.h"
+
+#include "src/filemanager/dir.h"
+#include "src/filemanager/filemanager.h"
+#include "src/filemanager/layout.h"
+
+#include "usermenu.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define MAX_ENTRIES 16
+#define MAX_ENTRY_LEN 60
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static gboolean debug_flag = FALSE;
+static gboolean debug_error = FALSE;
+static char *menu = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** strip file's extension */
+static char *
+strip_ext (char *ss)
+{
+ char *s = ss;
+ char *e = NULL;
+
+ while (*s != '\0')
+ {
+ if (*s == '.')
+ e = s;
+ if (IS_PATH_SEP (*s) && e != NULL)
+ e = NULL; /* '.' in *directory* name */
+ s++;
+ }
+ if (e != NULL)
+ *e = '\0';
+ return ss;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check for the "shell_patterns" directive. If it's found and valid,
+ * interpret it and move the pointer past the directive. Return the
+ * current pointer.
+ */
+
+static char *
+check_patterns (char *p)
+{
+ static const char def_name[] = "shell_patterns=";
+ char *p0 = p;
+
+ if (strncmp (p, def_name, sizeof (def_name) - 1) != 0)
+ return p0;
+
+ p += sizeof (def_name) - 1;
+ if (*p == '1')
+ easy_patterns = TRUE;
+ else if (*p == '0')
+ easy_patterns = FALSE;
+ else
+ return p0;
+
+ /* Skip spaces */
+ p++;
+ while (whiteness (*p))
+ p++;
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Copies a whitespace separated argument from p to arg. Returns the
+ point after argument. */
+
+static char *
+extract_arg (char *p, char *arg, int size)
+{
+ while (*p != '\0' && whiteness (*p))
+ p++;
+
+ /* support quote space .mnu */
+ while (*p != '\0' && (*p != ' ' || *(p - 1) == '\\') && *p != '\t' && *p != '\n')
+ {
+ char *np;
+
+ np = str_get_next_char (p);
+ if (np - p >= size)
+ break;
+ memcpy (arg, p, np - p);
+ arg += np - p;
+ size -= np - p;
+ p = np;
+ }
+ *arg = '\0';
+ if (*p == '\0' || *p == '\n')
+ str_prev_char (&p);
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Tests whether the selected file in the panel is of any of the types
+ specified in argument. */
+
+static gboolean
+test_type (WPanel * panel, char *arg)
+{
+ int result = 0; /* False by default */
+ mode_t st_mode;
+
+ st_mode = panel_current_entry (panel)->st.st_mode;
+
+ for (; *arg != '\0'; arg++)
+ {
+ switch (*arg)
+ {
+ case 'n': /* Not a directory */
+ result |= !S_ISDIR (st_mode);
+ break;
+ case 'r': /* Regular file */
+ result |= S_ISREG (st_mode);
+ break;
+ case 'd': /* Directory */
+ result |= S_ISDIR (st_mode);
+ break;
+ case 'l': /* Link */
+ result |= S_ISLNK (st_mode);
+ break;
+ case 'c': /* Character special */
+ result |= S_ISCHR (st_mode);
+ break;
+ case 'b': /* Block special */
+ result |= S_ISBLK (st_mode);
+ break;
+ case 'f': /* Fifo (named pipe) */
+ result |= S_ISFIFO (st_mode);
+ break;
+ case 's': /* Socket */
+ result |= S_ISSOCK (st_mode);
+ break;
+ case 'x': /* Executable */
+ result |= (st_mode & 0111) != 0 ? 1 : 0;
+ break;
+ case 't':
+ result |= panel->marked != 0 ? 1 : 0;
+ break;
+ default:
+ debug_error = TRUE;
+ break;
+ }
+ }
+
+ return (result != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Calculates the truth value of the next condition starting from
+ p. Returns the point after condition. */
+
+static char *
+test_condition (const Widget * edit_widget, char *p, gboolean * condition)
+{
+ char arg[256];
+ const mc_search_type_t search_type = easy_patterns ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
+#ifdef USE_INTERNAL_EDIT
+ const WEdit *e = CONST_EDIT (edit_widget);
+#endif
+
+ /* Handle one condition */
+ for (; *p != '\n' && *p != '&' && *p != '|'; p++)
+ {
+ WPanel *panel = NULL;
+
+ /* support quote space .mnu */
+ if ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
+ continue;
+ if (*p >= 'a')
+ panel = current_panel;
+ else if (get_other_type () == view_listing)
+ panel = other_panel;
+
+ *p |= 0x20;
+
+ switch (*p++)
+ {
+ case '!':
+ p = test_condition (edit_widget, p, condition);
+ *condition = !*condition;
+ str_prev_char (&p);
+ break;
+ case 'f': /* file name pattern */
+ p = extract_arg (p, arg, sizeof (arg));
+#ifdef USE_INTERNAL_EDIT
+ if (e != NULL)
+ {
+ const char *edit_filename;
+
+ edit_filename = edit_get_file_name (e);
+ *condition = mc_search (arg, DEFAULT_CHARSET, edit_filename, search_type);
+ }
+ else
+#endif
+ *condition = panel != NULL &&
+ mc_search (arg, DEFAULT_CHARSET, panel_current_entry (panel)->fname->str,
+ search_type);
+ break;
+ case 'y': /* syntax pattern */
+#ifdef USE_INTERNAL_EDIT
+ if (e != NULL)
+ {
+ const char *syntax_type;
+
+ syntax_type = edit_get_syntax_type (e);
+ if (syntax_type != NULL)
+ {
+ p = extract_arg (p, arg, sizeof (arg));
+ *condition = mc_search (arg, DEFAULT_CHARSET, syntax_type, MC_SEARCH_T_NORMAL);
+ }
+ }
+#endif
+ break;
+ case 'd':
+ p = extract_arg (p, arg, sizeof (arg));
+ *condition = panel != NULL
+ && mc_search (arg, DEFAULT_CHARSET, vfs_path_as_str (panel->cwd_vpath),
+ search_type);
+ break;
+ case 't':
+ p = extract_arg (p, arg, sizeof (arg));
+ *condition = panel != NULL && test_type (panel, arg);
+ break;
+ case 'x': /* executable */
+ {
+ struct stat status;
+
+ p = extract_arg (p, arg, sizeof (arg));
+ *condition = stat (arg, &status) == 0 && is_exe (status.st_mode);
+ break;
+ }
+ default:
+ debug_error = TRUE;
+ break;
+ } /* switch */
+ } /* while */
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** General purpose condition debug output handler */
+
+static void
+debug_out (char *start, char *end, gboolean condition)
+{
+ static char *msg = NULL;
+
+ if (start == NULL && end == NULL)
+ {
+ /* Show output */
+ if (debug_flag && msg != NULL)
+ {
+ size_t len;
+
+ len = strlen (msg);
+ if (len != 0)
+ msg[len - 1] = '\0';
+ message (D_NORMAL, _("Debug"), "%s", msg);
+
+ }
+ debug_flag = FALSE;
+ MC_PTR_FREE (msg);
+ }
+ else
+ {
+ const char *type;
+ char *p;
+
+ /* Save debug info for later output */
+ if (!debug_flag)
+ return;
+ /* Save the result of the condition */
+ if (debug_error)
+ {
+ type = _("ERROR:");
+ debug_error = FALSE;
+ }
+ else if (condition)
+ type = _("True:");
+ else
+ type = _("False:");
+ /* This is for debugging, don't need to be super efficient. */
+ if (end == NULL)
+ p = g_strdup_printf ("%s %s %c \n", msg ? msg : "", type, *start);
+ else
+ p = g_strdup_printf ("%s %s %.*s \n", msg ? msg : "", type, (int) (end - start), start);
+ g_free (msg);
+ msg = p;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Calculates the truth value of one lineful of conditions. Returns
+ the point just before the end of line. */
+
+static char *
+test_line (const Widget * edit_widget, char *p, gboolean * result)
+{
+ char operator;
+
+ /* Repeat till end of line */
+ while (*p != '\0' && *p != '\n')
+ {
+ char *debug_start, *debug_end;
+ gboolean condition = TRUE;
+
+ /* support quote space .mnu */
+ while ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
+ p++;
+ if (*p == '\0' || *p == '\n')
+ break;
+ operator = *p++;
+ if (*p == '?')
+ {
+ debug_flag = TRUE;
+ p++;
+ }
+ /* support quote space .mnu */
+ while ((*p == ' ' && *(p - 1) != '\\') || *p == '\t')
+ p++;
+ if (*p == '\0' || *p == '\n')
+ break;
+
+ debug_start = p;
+ p = test_condition (edit_widget, p, &condition);
+ debug_end = p;
+ /* Add one debug statement */
+ debug_out (debug_start, debug_end, condition);
+
+ switch (operator)
+ {
+ case '+':
+ case '=':
+ /* Assignment */
+ *result = condition;
+ break;
+ case '&': /* Logical and */
+ *result = *result && condition;
+ break;
+ case '|': /* Logical or */
+ *result = *result || condition;
+ break;
+ default:
+ debug_error = TRUE;
+ break;
+ } /* switch */
+ /* Add one debug statement */
+ debug_out (&operator, NULL, *result);
+
+ } /* while (*p != '\n') */
+ /* Report debug message */
+ debug_out (NULL, NULL, TRUE);
+
+ if (*p == '\0' || *p == '\n')
+ str_prev_char (&p);
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** FIXME: recode this routine on version 3.0, it could be cleaner */
+
+static void
+execute_menu_command (const Widget * edit_widget, const char *commands, gboolean show_prompt)
+{
+ FILE *cmd_file;
+ int cmd_file_fd;
+ gboolean expand_prefix_found = FALSE;
+ char *parameter = NULL;
+ gboolean do_quote = FALSE;
+ char lc_prompt[80];
+ int col;
+ vfs_path_t *file_name_vpath;
+ gboolean run_view = FALSE;
+ char *cmd;
+
+ /* Skip menu entry title line */
+ commands = strchr (commands, '\n');
+ if (commands == NULL)
+ return;
+
+ cmd_file_fd = mc_mkstemps (&file_name_vpath, "mcusr", SCRIPT_SUFFIX);
+
+ if (cmd_file_fd == -1)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot create temporary command file\n%s"),
+ unix_error_string (errno));
+ return;
+ }
+
+ cmd_file = fdopen (cmd_file_fd, "w");
+ fputs ("#! /bin/sh\n", cmd_file);
+ commands++;
+
+ for (col = 0; *commands != '\0'; commands++)
+ {
+ if (col == 0)
+ {
+ if (!whitespace (*commands))
+ break;
+ while (whitespace (*commands))
+ commands++;
+ if (*commands == '\0')
+ break;
+ }
+ col++;
+ if (*commands == '\n')
+ col = 0;
+ if (parameter != NULL)
+ {
+ if (*commands == '}')
+ {
+ *parameter = '\0';
+ parameter =
+ input_dialog (_("Parameter"), lc_prompt, MC_HISTORY_FM_MENU_EXEC_PARAM, "",
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD |
+ INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_VARIABLES |
+ INPUT_COMPLETE_USERNAMES);
+ if (parameter == NULL || *parameter == '\0')
+ {
+ /* User canceled */
+ g_free (parameter);
+ fclose (cmd_file);
+ mc_unlink (file_name_vpath);
+ vfs_path_free (file_name_vpath, TRUE);
+ return;
+ }
+ if (do_quote)
+ {
+ char *tmp;
+
+ tmp = name_quote (parameter, FALSE);
+ fputs (tmp, cmd_file);
+ g_free (tmp);
+ }
+ else
+ fputs (parameter, cmd_file);
+
+ MC_PTR_FREE (parameter);
+ }
+ else if (parameter < lc_prompt + sizeof (lc_prompt) - 1)
+ *parameter++ = *commands;
+ }
+ else if (expand_prefix_found)
+ {
+ expand_prefix_found = FALSE;
+ if (g_ascii_isdigit ((gchar) * commands))
+ {
+ do_quote = (atoi (commands) != 0);
+ while (g_ascii_isdigit ((gchar) * commands))
+ commands++;
+ }
+ if (*commands == '{')
+ parameter = lc_prompt;
+ else
+ {
+ char *text;
+
+ text = expand_format (edit_widget, *commands, do_quote);
+ fputs (text, cmd_file);
+ g_free (text);
+ }
+ }
+ else if (*commands == '%')
+ {
+ int i;
+
+ i = check_format_view (commands + 1);
+ if (i != 0)
+ {
+ commands += i;
+ run_view = TRUE;
+ }
+ else
+ {
+ do_quote = TRUE; /* Default: Quote expanded macro */
+ expand_prefix_found = TRUE;
+ }
+ }
+ else
+ fputc (*commands, cmd_file);
+ }
+
+ fclose (cmd_file);
+ mc_chmod (file_name_vpath, S_IRWXU);
+
+ /* Execute the command indirectly to allow execution even on no-exec filesystems. */
+ cmd = g_strconcat ("/bin/sh ", vfs_path_as_str (file_name_vpath), (char *) NULL);
+
+ if (run_view)
+ {
+ mcview_viewer (cmd, NULL, 0, 0, 0);
+ dialog_switch_process_pending ();
+ }
+ else if (show_prompt)
+ shell_execute (cmd, EXECUTE_HIDE);
+ else
+ {
+ gboolean ok;
+
+ /* Prepare the terminal by setting its flag to the initial ones. This will cause \r
+ * to work as expected, instead of being ignored. */
+ tty_reset_shell_mode ();
+
+ ok = (system (cmd) != -1);
+
+ /* Restore terminal configuration. */
+ tty_raw_mode ();
+
+ /* Redraw the original screen's contents. */
+ tty_clear_screen ();
+ repaint_screen ();
+
+ if (!ok)
+ message (D_ERROR, MSG_ERROR, "%s", _("Error calling program"));
+ }
+
+ g_free (cmd);
+
+ mc_unlink (file_name_vpath);
+ vfs_path_free (file_name_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ ** Check owner of the menu file. Using menu file is allowed, if
+ ** owner of the menu is root or the actual user. In either case
+ ** file should not be group and word-writable.
+ **
+ ** Q. Should we apply this routine to system and home menu (and .ext files)?
+ */
+
+static gboolean
+menu_file_own (char *path)
+{
+ struct stat st;
+
+ if (stat (path, &st) == 0 && (st.st_uid == 0 || (st.st_uid == geteuid ()) != 0)
+ && ((st.st_mode & (S_IWGRP | S_IWOTH)) == 0))
+ return TRUE;
+
+ if (verbose)
+ message (D_NORMAL, _("Warning -- ignoring file"),
+ _("File %s is not owned by root or you or is world writable.\n"
+ "Using it may compromise your security"), path);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Formats defined:
+ %% The % character
+ %f The current file in the active panel (if non-local vfs, file will be copied locally
+ and %f will be full path to it) or the opened file in the internal editor.
+ %p Likewise.
+ %d The current working directory
+ %s "Selected files"; the tagged files if any, otherwise the current file
+ %t Tagged files
+ %u Tagged files (and they are untagged on return from expand_format)
+ %view Runs the commands and pipes standard output to the view command.
+ If %view is immediately followed by '{', recognize keywords
+ ascii, hex, nroff and unform
+
+ If the format letter is in uppercase, it refers to the other panel.
+
+ With a number followed the % character you can turn quoting on (default)
+ and off. For example:
+ %f quote expanded macro
+ %1f ditto
+ %0f don't quote expanded macro
+
+ expand_format returns a memory block that must be free()d.
+ */
+
+/* Returns how many characters we should advance if %view was found */
+int
+check_format_view (const char *p)
+{
+ const char *q = p;
+
+ if (strncmp (p, "view", 4) == 0)
+ {
+ q += 4;
+ if (*q == '{')
+ {
+ for (q++; *q != '\0' && *q != '}'; q++)
+ {
+ if (strncmp (q, DEFAULT_CHARSET, 5) == 0)
+ {
+ mcview_global_flags.hex = FALSE;
+ q += 4;
+ }
+ else if (strncmp (q, "hex", 3) == 0)
+ {
+ mcview_global_flags.hex = TRUE;
+ q += 2;
+ }
+ else if (strncmp (q, "nroff", 5) == 0)
+ {
+ mcview_global_flags.nroff = TRUE;
+ q += 4;
+ }
+ else if (strncmp (q, "unform", 6) == 0)
+ {
+ mcview_global_flags.nroff = FALSE;
+ q += 5;
+ }
+ }
+ if (*q == '}')
+ q++;
+ }
+ return q - p;
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+check_format_cd (const char *p)
+{
+ return (strncmp (p, "cd", 2)) != 0 ? 0 : 3;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Check if p has a "^var\{var-name\}" */
+/* Returns the number of skipped characters (zero on not found) */
+/* V will be set to the expanded variable name */
+
+int
+check_format_var (const char *p, char **v)
+{
+ *v = NULL;
+
+ if (strncmp (p, "var{", 4) == 0)
+ {
+ const char *q = p;
+ const char *dots = NULL;
+ const char *value;
+ char *var_name;
+
+ for (q += 4; *q != '\0' && *q != '}'; q++)
+ {
+ if (*q == ':')
+ dots = q + 1;
+ }
+ if (*q == '\0')
+ return 0;
+
+ if (dots == NULL || dots == q + 5)
+ {
+ message (D_ERROR,
+ _("Format error on file Extensions File"),
+ !dots ? _("The %%var macro has no default")
+ : _("The %%var macro has no variable"));
+ return 0;
+ }
+
+ /* Copy the variable name */
+ var_name = g_strndup (p + 4, dots - 2 - (p + 3));
+ value = getenv (var_name);
+ g_free (var_name);
+
+ if (value != NULL)
+ *v = g_strdup (value);
+ else
+ *v = g_strndup (dots, q - dots);
+
+ return q - p;
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+expand_format (const Widget * edit_widget, char c, gboolean do_quote)
+{
+ WPanel *panel = NULL;
+ char *(*quote_func) (const char *, gboolean);
+ const char *fname = NULL;
+ char *result;
+ char c_lc;
+
+#ifdef USE_INTERNAL_EDIT
+ const WEdit *e = CONST_EDIT (edit_widget);
+#else
+ (void) edit_widget;
+#endif
+
+ if (c == '%')
+ return g_strdup ("%");
+
+ switch (mc_global.mc_run_mode)
+ {
+ case MC_RUN_FULL:
+#ifdef USE_INTERNAL_EDIT
+ if (e != NULL)
+ fname = edit_get_file_name (e);
+ else
+#endif
+ {
+ if (g_ascii_islower ((gchar) c))
+ panel = current_panel;
+ else
+ {
+ if (get_other_type () != view_listing)
+ return g_strdup ("");
+ panel = other_panel;
+ }
+
+ fname = panel_current_entry (panel)->fname->str;
+ }
+ break;
+
+#ifdef USE_INTERNAL_EDIT
+ case MC_RUN_EDITOR:
+ fname = edit_get_file_name (e);
+ break;
+#endif
+
+ case MC_RUN_VIEWER:
+ /* mc_run_param0 is not NULL here because mcviewer isn't run without input file */
+ fname = (const char *) mc_run_param0;
+ break;
+
+ default:
+ /* other modes don't use formats */
+ return g_strdup ("");
+ }
+
+ if (do_quote)
+ quote_func = name_quote;
+ else
+ quote_func = fake_name_quote;
+
+ c_lc = g_ascii_tolower ((gchar) c);
+
+ switch (c_lc)
+ {
+ case 'f':
+ case 'p':
+ result = quote_func (fname, FALSE);
+ goto ret;
+ case 'x':
+ result = quote_func (extension (fname), FALSE);
+ goto ret;
+ case 'd':
+ {
+ const char *cwd;
+ char *qstr;
+
+ if (panel != NULL)
+ cwd = vfs_path_as_str (panel->cwd_vpath);
+ else
+ cwd = vfs_get_current_dir ();
+
+ qstr = quote_func (cwd, FALSE);
+
+ result = qstr;
+ goto ret;
+ }
+ case 'c':
+#ifdef USE_INTERNAL_EDIT
+ if (e != NULL)
+ {
+ result = g_strdup_printf ("%u", (unsigned int) edit_get_cursor_offset (e));
+ goto ret;
+ }
+#endif
+ break;
+ case 'i': /* indent equal number cursor position in line */
+#ifdef USE_INTERNAL_EDIT
+ if (e != NULL)
+ {
+ result = g_strnfill (edit_get_curs_col (e), ' ');
+ goto ret;
+ }
+#endif
+ break;
+ case 'y': /* syntax type */
+#ifdef USE_INTERNAL_EDIT
+ if (e != NULL)
+ {
+ const char *syntax_type;
+
+ syntax_type = edit_get_syntax_type (e);
+ if (syntax_type != NULL)
+ {
+ result = g_strdup (syntax_type);
+ goto ret;
+ }
+ }
+#endif
+ break;
+ case 'k': /* block file name */
+ case 'b': /* block file name / strip extension */
+#ifdef USE_INTERNAL_EDIT
+ if (e != NULL)
+ {
+ char *file;
+
+ file = mc_config_get_full_path (EDIT_HOME_BLOCK_FILE);
+ result = quote_func (file, FALSE);
+ g_free (file);
+ goto ret;
+ }
+#endif
+ if (c_lc == 'b')
+ {
+ result = strip_ext (quote_func (fname, FALSE));
+ goto ret;
+ }
+ break;
+ case 'n': /* strip extension in editor */
+#ifdef USE_INTERNAL_EDIT
+ if (e != NULL)
+ {
+ result = strip_ext (quote_func (fname, FALSE));
+ goto ret;
+ }
+#endif
+ break;
+ case 'm': /* menu file name */
+ if (menu != NULL)
+ {
+ result = quote_func (menu, FALSE);
+ goto ret;
+ }
+ break;
+ case 's':
+ if (panel == NULL || panel->marked == 0)
+ {
+ result = quote_func (fname, FALSE);
+ goto ret;
+ }
+
+ MC_FALLTHROUGH;
+
+ case 't':
+ case 'u':
+ {
+ GString *block;
+ int i;
+
+ if (panel == NULL)
+ {
+ result = g_strdup ("");
+ goto ret;
+ }
+
+ block = g_string_sized_new (16);
+
+ for (i = 0; i < panel->dir.len; i++)
+ if (panel->dir.list[i].f.marked != 0)
+ {
+ char *tmp;
+
+ tmp = quote_func (panel->dir.list[i].fname->str, FALSE);
+ g_string_append (block, tmp);
+ g_string_append_c (block, ' ');
+ g_free (tmp);
+
+ if (c_lc == 'u')
+ do_file_mark (panel, i, 0);
+ }
+ result = g_string_free (block, FALSE);
+ goto ret;
+ } /* sub case block */
+ default:
+ break;
+ } /* switch */
+
+ result = g_strdup ("% ");
+ result[1] = c;
+ ret:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * If edit_widget is NULL then we are called from the mc menu,
+ * otherwise we are called from the mcedit menu.
+ */
+
+gboolean
+user_menu_cmd (const Widget * edit_widget, const char *menu_file, int selected_entry)
+{
+ char *p;
+ char *data, **entries;
+ int max_cols, menu_lines, menu_limit;
+ int col, i;
+ gboolean accept_entry = TRUE;
+ int selected;
+ gboolean old_patterns;
+ gboolean res = FALSE;
+ gboolean interactive = TRUE;
+
+ if (!vfs_current_is_local ())
+ {
+ message (D_ERROR, MSG_ERROR, "%s", _("Cannot execute commands on non-local filesystems"));
+ return FALSE;
+ }
+ if (menu_file != NULL)
+ menu = g_strdup (menu_file);
+ else
+ menu = g_strdup (edit_widget != NULL ? EDIT_LOCAL_MENU : MC_LOCAL_MENU);
+ if (!exist_file (menu) || !menu_file_own (menu))
+ {
+ if (menu_file != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot open file %s\n%s"), menu,
+ unix_error_string (errno));
+ MC_PTR_FREE (menu);
+ return FALSE;
+ }
+
+ g_free (menu);
+ if (edit_widget != NULL)
+ menu = mc_config_get_full_path (EDIT_HOME_MENU);
+ else
+ menu = mc_config_get_full_path (MC_USERMENU_FILE);
+
+ if (!exist_file (menu))
+ {
+ g_free (menu);
+ menu =
+ mc_build_filename (mc_config_get_home_dir (),
+ edit_widget != NULL ? EDIT_GLOBAL_MENU : MC_GLOBAL_MENU,
+ (char *) NULL);
+ if (!exist_file (menu))
+ {
+ g_free (menu);
+ menu =
+ mc_build_filename (mc_global.sysconfig_dir,
+ edit_widget != NULL ? EDIT_GLOBAL_MENU : MC_GLOBAL_MENU,
+ (char *) NULL);
+ if (!exist_file (menu))
+ {
+ g_free (menu);
+ menu =
+ mc_build_filename (mc_global.share_data_dir,
+ edit_widget != NULL ? EDIT_GLOBAL_MENU : MC_GLOBAL_MENU,
+ (char *) NULL);
+ }
+ }
+ }
+ }
+
+ if (!g_file_get_contents (menu, &data, NULL, NULL))
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot open file %s\n%s"), menu, unix_error_string (errno));
+ MC_PTR_FREE (menu);
+ return FALSE;
+ }
+
+ max_cols = 0;
+ selected = 0;
+ menu_limit = 0;
+ entries = NULL;
+
+ /* Parse the menu file */
+ old_patterns = easy_patterns;
+ p = check_patterns (data);
+ for (menu_lines = col = 0; *p != '\0'; str_next_char (&p))
+ {
+ if (menu_lines >= menu_limit)
+ {
+ char **new_entries;
+
+ menu_limit += MAX_ENTRIES;
+ new_entries = g_try_realloc (entries, sizeof (new_entries[0]) * menu_limit);
+ if (new_entries == NULL)
+ break;
+
+ entries = new_entries;
+ new_entries += menu_limit;
+ while (--new_entries >= &entries[menu_lines])
+ *new_entries = NULL;
+ }
+
+ if (col == 0 && entries[menu_lines] == NULL)
+ switch (*p)
+ {
+ case '#':
+ /* do not show prompt if first line of external script is #silent */
+ if (selected_entry >= 0 && strncmp (p, "#silent", 7) == 0)
+ interactive = FALSE;
+ /* A commented menu entry */
+ accept_entry = TRUE;
+ break;
+
+ case '+':
+ if (*(p + 1) == '=')
+ {
+ /* Combined adding and default */
+ p = test_line (edit_widget, p + 1, &accept_entry);
+ if (selected == 0 && accept_entry)
+ selected = menu_lines;
+ }
+ else
+ {
+ /* A condition for adding the entry */
+ p = test_line (edit_widget, p, &accept_entry);
+ }
+ break;
+
+ case '=':
+ if (*(p + 1) == '+')
+ {
+ /* Combined adding and default */
+ p = test_line (edit_widget, p + 1, &accept_entry);
+ if (selected == 0 && accept_entry)
+ selected = menu_lines;
+ }
+ else
+ {
+ /* A condition for making the entry default */
+ i = 1;
+ p = test_line (edit_widget, p, &i);
+ if (selected == 0 && i != 0)
+ selected = menu_lines;
+ }
+ break;
+
+ default:
+ if (!whitespace (*p) && str_isprint (p))
+ {
+ /* A menu entry title line */
+ if (accept_entry)
+ entries[menu_lines] = p;
+ else
+ accept_entry = TRUE;
+ }
+ break;
+ }
+
+ if (*p == '\n')
+ {
+ if (entries[menu_lines] != NULL)
+ {
+ menu_lines++;
+ accept_entry = TRUE;
+ }
+ max_cols = MAX (max_cols, col);
+ col = 0;
+ }
+ else
+ {
+ if (*p == '\t')
+ *p = ' ';
+ col++;
+ }
+ }
+
+ if (menu_lines == 0)
+ {
+ message (D_ERROR, MSG_ERROR, _("No suitable entries found in %s"), menu);
+ res = FALSE;
+ }
+ else
+ {
+ if (selected_entry >= 0)
+ selected = selected_entry;
+ else
+ {
+ Listbox *listbox;
+
+ max_cols = MIN (MAX (max_cols, col), MAX_ENTRY_LEN);
+
+ /* Create listbox */
+ listbox = listbox_window_new (menu_lines, max_cols + 2, _("User menu"),
+ "[Edit Menu File]");
+ /* insert all the items found */
+ for (i = 0; i < menu_lines; i++)
+ {
+ p = entries[i];
+ LISTBOX_APPEND_TEXT (listbox, (unsigned char) p[0],
+ extract_line (p, p + MAX_ENTRY_LEN), p, FALSE);
+ }
+ /* Select the default entry */
+ listbox_set_current (listbox->list, selected);
+
+ selected = listbox_run (listbox);
+ }
+ if (selected >= 0)
+ {
+ execute_menu_command (edit_widget, entries[selected], interactive);
+ res = TRUE;
+ }
+
+ do_refresh ();
+ }
+
+ easy_patterns = old_patterns;
+ MC_PTR_FREE (menu);
+ g_free (entries);
+ g_free (data);
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/usermenu.h b/src/usermenu.h
new file mode 100644
index 0000000..7a997fe
--- /dev/null
+++ b/src/usermenu.h
@@ -0,0 +1,29 @@
+/** \file usermenu.h
+ * \brief Header: user menu implementation
+ */
+
+#ifndef MC__USERMENU_H
+#define MC__USERMENU_H
+
+#include "lib/global.h"
+#include "lib/widget.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+gboolean user_menu_cmd (const Widget * edit_widget, const char *menu_file, int selected_entry);
+char *expand_format (const Widget * edit_widget, char c, gboolean do_quote);
+int check_format_view (const char *p);
+int check_format_var (const char *p, char **v);
+int check_format_cd (const char *p);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__USERMENU_H */
diff --git a/src/util.c b/src/util.c
new file mode 100644
index 0000000..36159a9
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,75 @@
+/*
+ Various non-library utilities
+
+ Copyright (C) 2003-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Adam Byrtek, 2003
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ 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 "lib/global.h"
+#include "lib/util.h"
+
+#include "src/filemanager/file.h"
+#include "src/filemanager/filegui.h"
+
+#include "util.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+check_for_default (const vfs_path_t * default_file_vpath, const vfs_path_t * file_vpath)
+{
+ if (!exist_file (vfs_path_as_str (file_vpath)))
+ {
+ file_op_context_t *ctx;
+ file_op_total_context_t *tctx;
+
+ if (!exist_file (vfs_path_as_str (default_file_vpath)))
+ return FALSE;
+
+ ctx = file_op_context_new (OP_COPY);
+ tctx = file_op_total_context_new ();
+ file_op_context_create_ui (ctx, 0, FALSE);
+ copy_file_file (tctx, ctx, vfs_path_as_str (default_file_vpath),
+ vfs_path_as_str (file_vpath));
+ file_op_total_context_destroy (tctx);
+ file_op_context_destroy (ctx);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..912d24b
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,19 @@
+#ifndef MC_SRC_UTIL_H
+#define MC_SRC_UTIL_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* Check if the file exists. If not copy the default */
+gboolean check_for_default (const vfs_path_t * default_file_vpath, const vfs_path_t * file_vpath);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC_SRC_UTIL_H */
diff --git a/src/vfs/Makefile.am b/src/vfs/Makefile.am
new file mode 100644
index 0000000..1441953
--- /dev/null
+++ b/src/vfs/Makefile.am
@@ -0,0 +1,47 @@
+noinst_LTLIBRARIES = libmc-vfs.la
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+libmc_vfs_la_SOURCES = plugins_init.c plugins_init.h
+
+SUBDIRS = local
+libmc_vfs_la_LIBADD = local/libvfs-local.la
+
+if ENABLE_VFS_CPIO
+SUBDIRS += cpio
+libmc_vfs_la_LIBADD += cpio/libvfs-cpio.la
+endif
+
+if ENABLE_VFS_EXTFS
+SUBDIRS += extfs
+libmc_vfs_la_LIBADD += extfs/libvfs-extfs.la
+endif
+
+if ENABLE_VFS_FISH
+SUBDIRS += fish
+libmc_vfs_la_LIBADD += fish/libvfs-fish.la
+endif
+
+if ENABLE_VFS_FTP
+SUBDIRS += ftpfs
+libmc_vfs_la_LIBADD += ftpfs/libvfs-ftpfs.la
+endif
+
+if ENABLE_VFS_SFTP
+SUBDIRS += sftpfs
+libmc_vfs_la_LIBADD += sftpfs/libvfs-sftpfs.la
+endif
+
+if ENABLE_VFS_SFS
+SUBDIRS += sfs
+libmc_vfs_la_LIBADD += sfs/libvfs-sfs.la
+endif
+
+if ENABLE_VFS_TAR
+SUBDIRS += tar
+libmc_vfs_la_LIBADD += tar/libvfs-tar.la
+endif
+
+if ENABLE_VFS_UNDELFS
+SUBDIRS += undelfs
+libmc_vfs_la_LIBADD += undelfs/libvfs-undelfs.la
+endif
diff --git a/src/vfs/Makefile.in b/src/vfs/Makefile.in
new file mode 100644
index 0000000..a245efe
--- /dev/null
+++ b/src/vfs/Makefile.in
@@ -0,0 +1,875 @@
+# 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@
+@ENABLE_VFS_CPIO_TRUE@am__append_1 = cpio
+@ENABLE_VFS_CPIO_TRUE@am__append_2 = cpio/libvfs-cpio.la
+@ENABLE_VFS_EXTFS_TRUE@am__append_3 = extfs
+@ENABLE_VFS_EXTFS_TRUE@am__append_4 = extfs/libvfs-extfs.la
+@ENABLE_VFS_FISH_TRUE@am__append_5 = fish
+@ENABLE_VFS_FISH_TRUE@am__append_6 = fish/libvfs-fish.la
+@ENABLE_VFS_FTP_TRUE@am__append_7 = ftpfs
+@ENABLE_VFS_FTP_TRUE@am__append_8 = ftpfs/libvfs-ftpfs.la
+@ENABLE_VFS_SFTP_TRUE@am__append_9 = sftpfs
+@ENABLE_VFS_SFTP_TRUE@am__append_10 = sftpfs/libvfs-sftpfs.la
+@ENABLE_VFS_SFS_TRUE@am__append_11 = sfs
+@ENABLE_VFS_SFS_TRUE@am__append_12 = sfs/libvfs-sfs.la
+@ENABLE_VFS_TAR_TRUE@am__append_13 = tar
+@ENABLE_VFS_TAR_TRUE@am__append_14 = tar/libvfs-tar.la
+@ENABLE_VFS_UNDELFS_TRUE@am__append_15 = undelfs
+@ENABLE_VFS_UNDELFS_TRUE@am__append_16 = undelfs/libvfs-undelfs.la
+subdir = src/vfs
+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)
+libmc_vfs_la_DEPENDENCIES = local/libvfs-local.la $(am__append_2) \
+ $(am__append_4) $(am__append_6) $(am__append_8) \
+ $(am__append_10) $(am__append_12) $(am__append_14) \
+ $(am__append_16)
+am_libmc_vfs_la_OBJECTS = plugins_init.lo
+libmc_vfs_la_OBJECTS = $(am_libmc_vfs_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)/plugins_init.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 = $(libmc_vfs_la_SOURCES)
+DIST_SOURCES = $(libmc_vfs_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+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)`
+DIST_SUBDIRS = local cpio extfs fish ftpfs sftpfs sfs tar undelfs
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+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 = libmc-vfs.la
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+libmc_vfs_la_SOURCES = plugins_init.c plugins_init.h
+SUBDIRS = local $(am__append_1) $(am__append_3) $(am__append_5) \
+ $(am__append_7) $(am__append_9) $(am__append_11) \
+ $(am__append_13) $(am__append_15)
+libmc_vfs_la_LIBADD = local/libvfs-local.la $(am__append_2) \
+ $(am__append_4) $(am__append_6) $(am__append_8) \
+ $(am__append_10) $(am__append_12) $(am__append_14) \
+ $(am__append_16)
+all: all-recursive
+
+.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/vfs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/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}; \
+ }
+
+libmc-vfs.la: $(libmc_vfs_la_OBJECTS) $(libmc_vfs_la_DEPENDENCIES) $(EXTRA_libmc_vfs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmc_vfs_la_OBJECTS) $(libmc_vfs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/plugins_init.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
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(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-recursive
+
+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-recursive
+
+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
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+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-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/plugins_init.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/plugins_init.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) 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 \
+ installdirs-am 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/vfs/cpio/Makefile.am b/src/vfs/cpio/Makefile.am
new file mode 100644
index 0000000..a7806f8
--- /dev/null
+++ b/src/vfs/cpio/Makefile.am
@@ -0,0 +1,7 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-cpio.la
+
+libvfs_cpio_la_SOURCES = \
+ cpio.c cpio.h
diff --git a/src/vfs/cpio/Makefile.in b/src/vfs/cpio/Makefile.in
new file mode 100644
index 0000000..8534a52
--- /dev/null
+++ b/src/vfs/cpio/Makefile.in
@@ -0,0 +1,735 @@
+# 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/vfs/cpio
+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)
+libvfs_cpio_la_LIBADD =
+am_libvfs_cpio_la_OBJECTS = cpio.lo
+libvfs_cpio_la_OBJECTS = $(am_libvfs_cpio_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)/cpio.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 = $(libvfs_cpio_la_SOURCES)
+DIST_SOURCES = $(libvfs_cpio_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@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+noinst_LTLIBRARIES = libvfs-cpio.la
+libvfs_cpio_la_SOURCES = \
+ cpio.c cpio.h
+
+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/vfs/cpio/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/cpio/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}; \
+ }
+
+libvfs-cpio.la: $(libvfs_cpio_la_OBJECTS) $(libvfs_cpio_la_DEPENDENCIES) $(EXTRA_libvfs_cpio_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_cpio_la_OBJECTS) $(libvfs_cpio_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cpio.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)/cpio.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)/cpio.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/vfs/cpio/cpio.c b/src/vfs/cpio/cpio.c
new file mode 100644
index 0000000..447d1f6
--- /dev/null
+++ b/src/vfs/cpio/cpio.c
@@ -0,0 +1,905 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 2000-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Jan Hudec, 2000
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ 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
+ * \brief Source: Virtual File System: GNU Tar file system.
+ * \author Jan Hudec
+ * \date 2000
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+#include "lib/unixcompat.h"
+#include "lib/util.h"
+#include "lib/widget.h" /* message() */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/xdirentry.h"
+#include "lib/vfs/gc.h" /* vfs_rmstamp */
+
+#include "cpio.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define CPIO_SUPER(super) ((cpio_super_t *) (super))
+
+#define CPIO_POS(super) cpio_position
+/* If some time reentrancy should be needed change it to */
+/* #define CPIO_POS(super) (super)->u.arch.fd */
+
+#define CPIO_SEEK_SET(super, where) mc_lseek (CPIO_SUPER(super)->fd, CPIO_POS(super) = (where), SEEK_SET)
+#define CPIO_SEEK_CUR(super, where) mc_lseek (CPIO_SUPER(super)->fd, CPIO_POS(super) += (where), SEEK_SET)
+
+#define MAGIC_LENGTH (6) /* How many bytes we have to read ahead */
+#define SEEKBACK CPIO_SEEK_CUR(super, ptr - top)
+#define RETURN(x) return (CPIO_SUPER(super)->type = (x))
+#define TYPEIS(x) ((CPIO_SUPER(super)->type == CPIO_UNKNOWN) || (CPIO_SUPER(super)->type == (x)))
+
+#define HEAD_LENGTH (26)
+
+/*** file scope type declarations ****************************************************************/
+
+enum
+{
+ STATUS_START,
+ STATUS_OK,
+ STATUS_TRAIL,
+ STATUS_FAIL,
+ STATUS_EOF
+};
+
+enum
+{
+ CPIO_UNKNOWN = 0, /* Not determined yet */
+ CPIO_BIN, /* Binary format */
+ CPIO_BINRE, /* Binary format, reverse endianness */
+ CPIO_OLDC, /* Old ASCII format */
+ CPIO_NEWC, /* New ASCII format */
+ CPIO_CRC /* New ASCII format + CRC */
+};
+
+struct old_cpio_header
+{
+ unsigned short c_magic;
+ short c_dev;
+ unsigned short c_ino;
+ unsigned short c_mode;
+ unsigned short c_uid;
+ unsigned short c_gid;
+ unsigned short c_nlink;
+ short c_rdev;
+ unsigned short c_mtimes[2];
+ unsigned short c_namesize;
+ unsigned short c_filesizes[2];
+};
+
+struct new_cpio_header
+{
+ unsigned short c_magic;
+ unsigned long c_ino;
+ unsigned long c_mode;
+ unsigned long c_uid;
+ unsigned long c_gid;
+ unsigned long c_nlink;
+ unsigned long c_mtime;
+ unsigned long c_filesize;
+ long c_dev;
+ long c_devmin;
+ long c_rdev;
+ long c_rdevmin;
+ unsigned long c_namesize;
+ unsigned long c_chksum;
+};
+
+typedef struct
+{
+ unsigned long inumber;
+ dev_t device;
+ struct vfs_s_inode *inode;
+} defer_inode;
+
+typedef struct
+{
+ struct vfs_s_super base; /* base class */
+
+ int fd;
+ struct stat st;
+ int type; /* Type of the archive */
+ GSList *deferred; /* List of inodes for which another entries may appear */
+} cpio_super_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static ssize_t cpio_find_head (struct vfs_class *me, struct vfs_s_super *super);
+static ssize_t cpio_read_bin_head (struct vfs_class *me, struct vfs_s_super *super);
+static ssize_t cpio_read_oldc_head (struct vfs_class *me, struct vfs_s_super *super);
+static ssize_t cpio_read_crc_head (struct vfs_class *me, struct vfs_s_super *super);
+
+/*** file scope variables ************************************************************************/
+
+static struct vfs_s_subclass cpio_subclass;
+static struct vfs_class *vfs_cpiofs_ops = VFS_CLASS (&cpio_subclass);
+
+static off_t cpio_position;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+cpio_defer_find (const void *a, const void *b)
+{
+ const defer_inode *a1 = (const defer_inode *) a;
+ const defer_inode *b1 = (const defer_inode *) b;
+
+ return (a1->inumber == b1->inumber && a1->device == b1->device) ? 0 : 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+cpio_skip_padding (struct vfs_s_super *super)
+{
+ switch (CPIO_SUPER (super)->type)
+ {
+ case CPIO_BIN:
+ case CPIO_BINRE:
+ return CPIO_SEEK_CUR (super, (2 - (CPIO_POS (super) % 2)) % 2);
+ case CPIO_NEWC:
+ case CPIO_CRC:
+ return CPIO_SEEK_CUR (super, (4 - (CPIO_POS (super) % 4)) % 4);
+ case CPIO_OLDC:
+ return CPIO_POS (super);
+ default:
+ g_assert_not_reached ();
+ return 42; /* & the compiler is happy :-) */
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_super *
+cpio_new_archive (struct vfs_class *me)
+{
+ cpio_super_t *arch;
+
+ arch = g_new0 (cpio_super_t, 1);
+ arch->base.me = me;
+ arch->fd = -1; /* for now */
+ arch->type = CPIO_UNKNOWN;
+
+ return VFS_SUPER (arch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+cpio_free_archive (struct vfs_class *me, struct vfs_s_super *super)
+{
+ cpio_super_t *arch = CPIO_SUPER (super);
+
+ (void) me;
+
+ if (arch->fd != -1)
+ {
+ mc_close (arch->fd);
+ arch->fd = -1;
+ }
+
+ g_clear_slist (&arch->deferred, g_free);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+cpio_open_cpio_file (struct vfs_class *me, struct vfs_s_super *super, const vfs_path_t * vpath)
+{
+ int fd, type;
+ cpio_super_t *arch;
+ mode_t mode;
+ struct vfs_s_inode *root;
+
+ fd = mc_open (vpath, O_RDONLY);
+ if (fd == -1)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot open cpio archive\n%s"), vfs_path_as_str (vpath));
+ return -1;
+ }
+
+ super->name = g_strdup (vfs_path_as_str (vpath));
+ arch = CPIO_SUPER (super);
+ mc_stat (vpath, &arch->st);
+
+ type = get_compression_type (fd, super->name);
+ if (type == COMPRESSION_NONE)
+ mc_lseek (fd, 0, SEEK_SET);
+ else
+ {
+ char *s;
+ vfs_path_t *tmp_vpath;
+
+ mc_close (fd);
+ s = g_strconcat (super->name, decompress_extension (type), (char *) NULL);
+ tmp_vpath = vfs_path_from_str_flags (s, VPF_NO_CANON);
+ fd = mc_open (tmp_vpath, O_RDONLY);
+ vfs_path_free (tmp_vpath, TRUE);
+ if (fd == -1)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot open cpio archive\n%s"), s);
+ g_free (s);
+ MC_PTR_FREE (super->name);
+ return -1;
+ }
+ g_free (s);
+ }
+
+ arch->fd = fd;
+ mode = arch->st.st_mode & 07777;
+ mode |= (mode & 0444) >> 2; /* set eXec where Read is */
+ mode |= S_IFDIR;
+
+ root = vfs_s_new_inode (me, super, &arch->st);
+ root->st.st_mode = mode;
+ root->data_offset = -1;
+ root->st.st_nlink++;
+ root->st.st_dev = VFS_SUBCLASS (me)->rdev++;
+
+ super->root = root;
+
+ CPIO_SEEK_SET (super, 0);
+
+ return fd;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+cpio_read_head (struct vfs_class *me, struct vfs_s_super *super)
+{
+ switch (cpio_find_head (me, super))
+ {
+ case CPIO_UNKNOWN:
+ return -1;
+ case CPIO_BIN:
+ case CPIO_BINRE:
+ return cpio_read_bin_head (me, super);
+ case CPIO_OLDC:
+ return cpio_read_oldc_head (me, super);
+ case CPIO_NEWC:
+ case CPIO_CRC:
+ return cpio_read_crc_head (me, super);
+ default:
+ g_assert_not_reached ();
+ return 42; /* & the compiler is happy :-) */
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+cpio_find_head (struct vfs_class *me, struct vfs_s_super *super)
+{
+ cpio_super_t *arch = CPIO_SUPER (super);
+ char buf[BUF_SMALL * 2];
+ ssize_t ptr = 0;
+ ssize_t top;
+ ssize_t tmp;
+
+ top = mc_read (arch->fd, buf, sizeof (buf));
+ if (top > 0)
+ CPIO_POS (super) += top;
+
+ while (TRUE)
+ {
+ if (ptr + MAGIC_LENGTH >= top)
+ {
+ if (top > (ssize_t) (sizeof (buf) / 2))
+ {
+ memmove (buf, buf + top - sizeof (buf) / 2, sizeof (buf) / 2);
+ ptr -= top - sizeof (buf) / 2;
+ top = sizeof (buf) / 2;
+ }
+ tmp = mc_read (arch->fd, buf, top);
+ if (tmp == 0 || tmp == -1)
+ {
+ message (D_ERROR, MSG_ERROR, _("Premature end of cpio archive\n%s"), super->name);
+ cpio_free_archive (me, super);
+ return CPIO_UNKNOWN;
+ }
+ top += tmp;
+ }
+ if (TYPEIS (CPIO_BIN) && ((*(unsigned short *) (buf + ptr)) == 070707))
+ {
+ SEEKBACK;
+ RETURN (CPIO_BIN);
+ }
+ else if (TYPEIS (CPIO_BINRE)
+ && ((*(unsigned short *) (buf + ptr)) == GUINT16_SWAP_LE_BE_CONSTANT (070707)))
+ {
+ SEEKBACK;
+ RETURN (CPIO_BINRE);
+ }
+ else if (TYPEIS (CPIO_OLDC) && (strncmp (buf + ptr, "070707", 6) == 0))
+ {
+ SEEKBACK;
+ RETURN (CPIO_OLDC);
+ }
+ else if (TYPEIS (CPIO_NEWC) && (strncmp (buf + ptr, "070701", 6) == 0))
+ {
+ SEEKBACK;
+ RETURN (CPIO_NEWC);
+ }
+ else if (TYPEIS (CPIO_CRC) && (strncmp (buf + ptr, "070702", 6) == 0))
+ {
+ SEEKBACK;
+ RETURN (CPIO_CRC);
+ };
+ ptr++;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+cpio_create_entry (struct vfs_class *me, struct vfs_s_super *super, struct stat *st, char *name)
+{
+ cpio_super_t *arch = CPIO_SUPER (super);
+ struct vfs_s_inode *inode = NULL;
+ struct vfs_s_inode *root = super->root;
+ struct vfs_s_entry *entry = NULL;
+ char *tn;
+
+ switch (st->st_mode & S_IFMT)
+ { /* For case of HP/UX archives */
+ case S_IFCHR:
+ case S_IFBLK:
+#ifdef S_IFSOCK
+ /* cppcheck-suppress syntaxError */
+ case S_IFSOCK:
+#endif
+#ifdef S_IFIFO
+ /* cppcheck-suppress syntaxError */
+ case S_IFIFO:
+#endif
+#ifdef S_IFNAM
+ /* cppcheck-suppress syntaxError */
+ case S_IFNAM:
+#endif
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ if ((st->st_size != 0) && (st->st_rdev == 0x0001))
+ {
+ /* FIXME: representation of major/minor differs between */
+ /* different operating systems. */
+ st->st_rdev = (unsigned) st->st_size;
+ st->st_size = 0;
+ }
+#endif
+ break;
+ default:
+ break;
+ }
+
+ if ((st->st_nlink > 1) && ((arch->type == CPIO_NEWC) || (arch->type == CPIO_CRC)))
+ { /* For case of hardlinked files */
+ defer_inode i = { st->st_ino, st->st_dev, NULL };
+ GSList *l;
+
+ l = g_slist_find_custom (arch->deferred, &i, cpio_defer_find);
+ if (l != NULL)
+ {
+ inode = ((defer_inode *) l->data)->inode;
+ if (inode->st.st_size != 0 && st->st_size != 0 && (inode->st.st_size != st->st_size))
+ {
+ message (D_ERROR, MSG_ERROR,
+ _("Inconsistent hardlinks of\n%s\nin cpio archive\n%s"),
+ name, super->name);
+ inode = NULL;
+ }
+ else if (inode->st.st_size == 0)
+ inode->st.st_size = st->st_size;
+ }
+ }
+
+ /* remove trailing slashes */
+ for (tn = name + strlen (name) - 1; tn >= name && IS_PATH_SEP (*tn); tn--)
+ *tn = '\0';
+
+ tn = strrchr (name, PATH_SEP);
+ if (tn == NULL)
+ tn = name;
+ else if (tn == name + 1)
+ {
+ /* started with "./" -- directory in the root of archive */
+ tn++;
+ }
+ else
+ {
+ *tn = '\0';
+ root = vfs_s_find_inode (me, super, name, LINK_FOLLOW, FL_MKDIR);
+ *tn = PATH_SEP;
+ tn++;
+ }
+
+ entry = VFS_SUBCLASS (me)->find_entry (me, root, tn, LINK_FOLLOW, FL_NONE); /* In case entry is already there */
+
+ if (entry != NULL)
+ {
+ /* This shouldn't happen! (well, it can happen if there is a record for a
+ file and than a record for a directory it is in; cpio would die with
+ 'No such file or directory' is such case) */
+
+ if (!S_ISDIR (entry->ino->st.st_mode))
+ {
+ /* This can be considered archive inconsistency */
+ message (D_ERROR, MSG_ERROR,
+ _("%s contains duplicate entries! Skipping!"), super->name);
+ }
+ else
+ {
+ entry->ino->st.st_mode = st->st_mode;
+ entry->ino->st.st_uid = st->st_uid;
+ entry->ino->st.st_gid = st->st_gid;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ entry->ino->st.st_atim = st->st_atim;
+ entry->ino->st.st_mtim = st->st_mtim;
+ entry->ino->st.st_ctim = st->st_ctim;
+#else
+ entry->ino->st.st_atime = st->st_atime;
+ entry->ino->st.st_mtime = st->st_mtime;
+ entry->ino->st.st_ctime = st->st_ctime;
+#endif
+ }
+
+ g_free (name);
+ }
+ else
+ { /* !entry */
+ /* root == NULL can be in the following case:
+ * a/b/c -> d
+ * where 'a/b' is the stale link and therefore root of 'c' cannot be found in the archive
+ */
+ if (root != NULL)
+ {
+ if (inode == NULL)
+ {
+ inode = vfs_s_new_inode (me, super, st);
+ if ((st->st_nlink > 0) && ((arch->type == CPIO_NEWC) || (arch->type == CPIO_CRC)))
+ {
+ /* For case of hardlinked files */
+ defer_inode *i;
+
+ i = g_new (defer_inode, 1);
+ i->inumber = st->st_ino;
+ i->device = st->st_dev;
+ i->inode = inode;
+
+ arch->deferred = g_slist_prepend (arch->deferred, i);
+ }
+ }
+
+ if (st->st_size != 0)
+ inode->data_offset = CPIO_POS (super);
+
+ entry = vfs_s_new_entry (me, tn, inode);
+ vfs_s_insert_entry (me, root, entry);
+ }
+
+ g_free (name);
+
+ if (!S_ISLNK (st->st_mode))
+ CPIO_SEEK_CUR (super, st->st_size);
+ else
+ {
+ if (inode != NULL)
+ {
+ /* FIXME: do we must read from arch->fd in case of inode != NULL only or in any case? */
+
+ inode->linkname = g_malloc (st->st_size + 1);
+
+ if (mc_read (arch->fd, inode->linkname, st->st_size) < st->st_size)
+ {
+ inode->linkname[0] = '\0';
+ return STATUS_EOF;
+ }
+
+ inode->linkname[st->st_size] = '\0'; /* Linkname stored without terminating \0 !!! */
+ }
+
+ CPIO_POS (super) += st->st_size;
+ cpio_skip_padding (super);
+ }
+ } /* !entry */
+
+ return STATUS_OK;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+cpio_read_bin_head (struct vfs_class *me, struct vfs_s_super *super)
+{
+ union
+ {
+ struct old_cpio_header buf;
+ short shorts[HEAD_LENGTH >> 1];
+ } u;
+
+ cpio_super_t *arch = CPIO_SUPER (super);
+ ssize_t len;
+ char *name;
+ struct stat st;
+
+ len = mc_read (arch->fd, (char *) &u.buf, HEAD_LENGTH);
+ if (len < HEAD_LENGTH)
+ return STATUS_EOF;
+ CPIO_POS (super) += len;
+ if (arch->type == CPIO_BINRE)
+ {
+ int i;
+ for (i = 0; i < (HEAD_LENGTH >> 1); i++)
+ u.shorts[i] = GUINT16_SWAP_LE_BE_CONSTANT (u.shorts[i]);
+ }
+
+ if (u.buf.c_magic != 070707 || u.buf.c_namesize == 0 || u.buf.c_namesize > MC_MAXPATHLEN)
+ {
+ message (D_ERROR, MSG_ERROR, _("Corrupted cpio header encountered in\n%s"), super->name);
+ return STATUS_FAIL;
+ }
+ name = g_malloc (u.buf.c_namesize);
+ len = mc_read (arch->fd, name, u.buf.c_namesize);
+ if (len < u.buf.c_namesize)
+ {
+ g_free (name);
+ return STATUS_EOF;
+ }
+ name[u.buf.c_namesize - 1] = '\0';
+ CPIO_POS (super) += len;
+ cpio_skip_padding (super);
+
+ if (!strcmp ("TRAILER!!!", name))
+ { /* We got to the last record */
+ g_free (name);
+ return STATUS_TRAIL;
+ }
+
+ st.st_dev = u.buf.c_dev;
+ st.st_ino = u.buf.c_ino;
+ st.st_mode = u.buf.c_mode;
+ st.st_nlink = u.buf.c_nlink;
+ st.st_uid = u.buf.c_uid;
+ st.st_gid = u.buf.c_gid;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ st.st_rdev = u.buf.c_rdev;
+#endif
+ st.st_size = ((off_t) u.buf.c_filesizes[0] << 16) | u.buf.c_filesizes[1];
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ st.st_atim.tv_nsec = st.st_mtim.tv_nsec = st.st_ctim.tv_nsec = 0;
+#endif
+ st.st_atime = st.st_mtime = st.st_ctime =
+ ((time_t) u.buf.c_mtimes[0] << 16) | u.buf.c_mtimes[1];
+
+ return cpio_create_entry (me, super, &st, name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#undef HEAD_LENGTH
+#define HEAD_LENGTH (76)
+
+static ssize_t
+cpio_read_oldc_head (struct vfs_class *me, struct vfs_s_super *super)
+{
+ cpio_super_t *arch = CPIO_SUPER (super);
+ struct new_cpio_header hd;
+ union
+ {
+ struct stat st;
+ char buf[HEAD_LENGTH + 1];
+ } u;
+ ssize_t len;
+ char *name;
+
+ if (mc_read (arch->fd, u.buf, HEAD_LENGTH) != HEAD_LENGTH)
+ return STATUS_EOF;
+ CPIO_POS (super) += HEAD_LENGTH;
+ u.buf[HEAD_LENGTH] = 0;
+
+ if (sscanf (u.buf, "070707%6lo%6lo%6lo%6lo%6lo%6lo%6lo%11lo%6lo%11lo",
+ (unsigned long *) &hd.c_dev, &hd.c_ino, &hd.c_mode, &hd.c_uid, &hd.c_gid,
+ &hd.c_nlink, (unsigned long *) &hd.c_rdev, &hd.c_mtime,
+ &hd.c_namesize, &hd.c_filesize) < 10)
+ {
+ message (D_ERROR, MSG_ERROR, _("Corrupted cpio header encountered in\n%s"), super->name);
+ return STATUS_FAIL;
+ }
+
+ if (hd.c_namesize == 0 || hd.c_namesize > MC_MAXPATHLEN)
+ {
+ message (D_ERROR, MSG_ERROR, _("Corrupted cpio header encountered in\n%s"), super->name);
+ return STATUS_FAIL;
+ }
+ name = g_malloc (hd.c_namesize);
+ len = mc_read (arch->fd, name, hd.c_namesize);
+ if ((len == -1) || ((unsigned long) len < hd.c_namesize))
+ {
+ g_free (name);
+ return STATUS_EOF;
+ }
+ name[hd.c_namesize - 1] = '\0';
+ CPIO_POS (super) += len;
+ cpio_skip_padding (super);
+
+ if (!strcmp ("TRAILER!!!", name))
+ { /* We got to the last record */
+ g_free (name);
+ return STATUS_TRAIL;
+ }
+
+ u.st.st_dev = hd.c_dev;
+ u.st.st_ino = hd.c_ino;
+ u.st.st_mode = hd.c_mode;
+ u.st.st_nlink = hd.c_nlink;
+ u.st.st_uid = hd.c_uid;
+ u.st.st_gid = hd.c_gid;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ u.st.st_rdev = hd.c_rdev;
+#endif
+ u.st.st_size = hd.c_filesize;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ u.st.st_atim.tv_nsec = u.st.st_mtim.tv_nsec = u.st.st_ctim.tv_nsec = 0;
+#endif
+ u.st.st_atime = u.st.st_mtime = u.st.st_ctime = hd.c_mtime;
+
+ return cpio_create_entry (me, super, &u.st, name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#undef HEAD_LENGTH
+#define HEAD_LENGTH (110)
+
+static ssize_t
+cpio_read_crc_head (struct vfs_class *me, struct vfs_s_super *super)
+{
+ cpio_super_t *arch = CPIO_SUPER (super);
+ struct new_cpio_header hd;
+ union
+ {
+ struct stat st;
+ char buf[HEAD_LENGTH + 1];
+ } u;
+ ssize_t len;
+ char *name;
+
+ if (mc_read (arch->fd, u.buf, HEAD_LENGTH) != HEAD_LENGTH)
+ return STATUS_EOF;
+
+ CPIO_POS (super) += HEAD_LENGTH;
+ u.buf[HEAD_LENGTH] = '\0';
+
+ if (sscanf (u.buf, "%6ho%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx%8lx",
+ &hd.c_magic, &hd.c_ino, &hd.c_mode, &hd.c_uid, &hd.c_gid,
+ &hd.c_nlink, &hd.c_mtime, &hd.c_filesize,
+ (unsigned long *) &hd.c_dev, (unsigned long *) &hd.c_devmin,
+ (unsigned long *) &hd.c_rdev, (unsigned long *) &hd.c_rdevmin,
+ &hd.c_namesize, &hd.c_chksum) < 14)
+ {
+ message (D_ERROR, MSG_ERROR, _("Corrupted cpio header encountered in\n%s"), super->name);
+ return STATUS_FAIL;
+ }
+
+ if ((arch->type == CPIO_NEWC && hd.c_magic != 070701) ||
+ (arch->type == CPIO_CRC && hd.c_magic != 070702))
+ return STATUS_FAIL;
+
+ if (hd.c_namesize == 0 || hd.c_namesize > MC_MAXPATHLEN)
+ {
+ message (D_ERROR, MSG_ERROR, _("Corrupted cpio header encountered in\n%s"), super->name);
+ return STATUS_FAIL;
+ }
+
+ name = g_malloc (hd.c_namesize);
+ len = mc_read (arch->fd, name, hd.c_namesize);
+
+ if ((len == -1) || ((unsigned long) len < hd.c_namesize))
+ {
+ g_free (name);
+ return STATUS_EOF;
+ }
+ name[hd.c_namesize - 1] = '\0';
+ CPIO_POS (super) += len;
+ cpio_skip_padding (super);
+
+ if (strcmp ("TRAILER!!!", name) == 0)
+ { /* We got to the last record */
+ g_free (name);
+ return STATUS_TRAIL;
+ }
+
+ u.st.st_dev = makedev (hd.c_dev, hd.c_devmin);
+ u.st.st_ino = hd.c_ino;
+ u.st.st_mode = hd.c_mode;
+ u.st.st_nlink = hd.c_nlink;
+ u.st.st_uid = hd.c_uid;
+ u.st.st_gid = hd.c_gid;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ u.st.st_rdev = makedev (hd.c_rdev, hd.c_rdevmin);
+#endif
+ u.st.st_size = hd.c_filesize;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ u.st.st_atim.tv_nsec = u.st.st_mtim.tv_nsec = u.st.st_ctim.tv_nsec = 0;
+#endif
+ u.st.st_atime = u.st.st_mtime = u.st.st_ctime = hd.c_mtime;
+
+ return cpio_create_entry (me, super, &u.st, name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Need to CPIO_SEEK_CUR to skip the file at the end of add entry!!!! */
+
+static int
+cpio_open_archive (struct vfs_s_super *super, const vfs_path_t * vpath,
+ const vfs_path_element_t * vpath_element)
+{
+ (void) vpath_element;
+
+ if (cpio_open_cpio_file (vpath_element->class, super, vpath) == -1)
+ return -1;
+
+ while (TRUE)
+ {
+ ssize_t status;
+
+ status = cpio_read_head (vpath_element->class, super);
+ if (status < 0)
+ return (-1);
+
+ switch (status)
+ {
+ case STATUS_EOF:
+ {
+ message (D_ERROR, MSG_ERROR, _("Unexpected end of file\n%s"),
+ vfs_path_as_str (vpath));
+ return 0;
+ }
+ case STATUS_OK:
+ continue;
+ case STATUS_TRAIL:
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Remaining functions are exactly same as for tarfs (and were in fact just copied) */
+
+static void *
+cpio_super_check (const vfs_path_t * vpath)
+{
+ static struct stat sb;
+ int stat_result;
+
+ stat_result = mc_stat (vpath, &sb);
+ return (stat_result == 0 ? &sb : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+cpio_super_same (const vfs_path_element_t * vpath_element, struct vfs_s_super *parc,
+ const vfs_path_t * vpath, void *cookie)
+{
+ struct stat *archive_stat = cookie; /* stat of main archive */
+
+ (void) vpath_element;
+
+ if (strcmp (parc->name, vfs_path_as_str (vpath)))
+ return 0;
+
+ /* Has the cached archive been changed on the disk? */
+ if (parc != NULL && CPIO_SUPER (parc)->st.st_mtime < archive_stat->st_mtime)
+ {
+ /* Yes, reload! */
+ vfs_cpiofs_ops->free ((vfsid) parc);
+ vfs_rmstamp (vfs_cpiofs_ops, (vfsid) parc);
+ return 2;
+ }
+ /* Hasn't been modified, give it a new timeout */
+ vfs_stamp (vfs_cpiofs_ops, (vfsid) parc);
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+cpio_read (void *fh, char *buffer, size_t count)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ struct vfs_class *me = VFS_FILE_HANDLER_SUPER (fh)->me;
+ int fd = CPIO_SUPER (VFS_FILE_HANDLER_SUPER (fh))->fd;
+ off_t begin = file->ino->data_offset;
+ ssize_t res;
+
+ if (mc_lseek (fd, begin + file->pos, SEEK_SET) != begin + file->pos)
+ ERRNOR (EIO, -1);
+
+ count = MIN (count, (size_t) (file->ino->st.st_size - file->pos));
+
+ res = mc_read (fd, buffer, count);
+ if (res == -1)
+ ERRNOR (errno, -1);
+
+ file->pos += res;
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+cpio_fh_open (struct vfs_class *me, vfs_file_handler_t * fh, int flags, mode_t mode)
+{
+ (void) fh;
+ (void) mode;
+
+ if ((flags & O_ACCMODE) != O_RDONLY)
+ ERRNOR (EROFS, -1);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_cpiofs (void)
+{
+ /* FIXME: cpiofs used own temp files */
+ vfs_init_subclass (&cpio_subclass, "cpiofs", VFSF_READONLY, "ucpio");
+ vfs_cpiofs_ops->read = cpio_read;
+ vfs_cpiofs_ops->setctl = NULL;
+ cpio_subclass.archive_check = cpio_super_check;
+ cpio_subclass.archive_same = cpio_super_same;
+ cpio_subclass.new_archive = cpio_new_archive;
+ cpio_subclass.open_archive = cpio_open_archive;
+ cpio_subclass.free_archive = cpio_free_archive;
+ cpio_subclass.fh_open = cpio_fh_open;
+ vfs_register_class (vfs_cpiofs_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/cpio/cpio.h b/src/vfs/cpio/cpio.h
new file mode 100644
index 0000000..a00756a
--- /dev/null
+++ b/src/vfs/cpio/cpio.h
@@ -0,0 +1,18 @@
+#ifndef MC__VFS_CPIO_H
+#define MC__VFS_CPIO_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_cpiofs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_CPIO_H */
diff --git a/src/vfs/extfs/Makefile.am b/src/vfs/extfs/Makefile.am
new file mode 100644
index 0000000..39762a3
--- /dev/null
+++ b/src/vfs/extfs/Makefile.am
@@ -0,0 +1,12 @@
+SUBDIRS = helpers
+DIST_SUBDIRS = helpers
+
+AM_CPPFLAGS = \
+ -DLIBEXECDIR=\""$(libexecdir)/@PACKAGE@/"\" \
+ $(GLIB_CFLAGS) \
+ -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-extfs.la
+
+libvfs_extfs_la_SOURCES = \
+ extfs.c extfs.h
diff --git a/src/vfs/extfs/Makefile.in b/src/vfs/extfs/Makefile.in
new file mode 100644
index 0000000..317af30
--- /dev/null
+++ b/src/vfs/extfs/Makefile.in
@@ -0,0 +1,856 @@
+# 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/vfs/extfs
+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)
+libvfs_extfs_la_LIBADD =
+am_libvfs_extfs_la_OBJECTS = extfs.lo
+libvfs_extfs_la_OBJECTS = $(am_libvfs_extfs_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)/extfs.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 = $(libvfs_extfs_la_SOURCES)
+DIST_SOURCES = $(libvfs_extfs_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+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)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+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@
+SUBDIRS = helpers
+DIST_SUBDIRS = helpers
+AM_CPPFLAGS = \
+ -DLIBEXECDIR=\""$(libexecdir)/@PACKAGE@/"\" \
+ $(GLIB_CFLAGS) \
+ -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-extfs.la
+libvfs_extfs_la_SOURCES = \
+ extfs.c extfs.h
+
+all: all-recursive
+
+.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/vfs/extfs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/extfs/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}; \
+ }
+
+libvfs-extfs.la: $(libvfs_extfs_la_OBJECTS) $(libvfs_extfs_la_DEPENDENCIES) $(EXTRA_libvfs_extfs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_extfs_la_OBJECTS) $(libvfs_extfs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/extfs.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
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(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-recursive
+
+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-recursive
+
+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
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+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-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/extfs.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/extfs.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) 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 \
+ installdirs-am 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/vfs/extfs/extfs.c b/src/vfs/extfs/extfs.c
new file mode 100644
index 0000000..d6ef7af
--- /dev/null
+++ b/src/vfs/extfs/extfs.c
@@ -0,0 +1,1722 @@
+/*
+ Virtual File System: External file system.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Jakub Jelinek, 1995
+ Pavel Machek, 1998
+ Andrew T. Veliath, 1999
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ 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
+ * \brief Source: Virtual File System: External file system
+ * \author Jakub Jelinek
+ * \author Pavel Machek
+ * \author Andrew T. Veliath
+ * \date 1995, 1998, 1999
+ */
+
+/* Namespace: init_extfs */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+#include "lib/global.h"
+#include "lib/fileloc.h"
+#include "lib/mcconfig.h"
+#include "lib/util.h"
+#include "lib/widget.h" /* message() */
+
+#include "src/execute.h" /* For shell_execute */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/xdirentry.h"
+#include "lib/vfs/gc.h" /* vfs_rmstamp */
+
+#include "extfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#undef ERRNOR
+#define ERRNOR(x,y) do { my_errno = x; return y; } while(0)
+
+#define RECORDSIZE 512
+
+#define EXTFS_SUPER(a) ((struct extfs_super_t *) (a))
+
+/*** file scope type declarations ****************************************************************/
+
+struct extfs_super_t
+{
+ struct vfs_s_super base; /* base class */
+
+ int fstype;
+ char *local_name;
+ struct stat local_stat;
+ dev_t rdev;
+};
+
+typedef struct
+{
+ char *path;
+ char *prefix;
+ gboolean need_archive;
+} extfs_plugin_info_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static struct vfs_s_entry *extfs_resolve_symlinks_int (struct vfs_s_entry *entry, GSList * list);
+
+/*** file scope variables ************************************************************************/
+
+static GArray *extfs_plugins = NULL;
+
+static gboolean errloop;
+static gboolean notadir;
+
+static struct vfs_s_subclass extfs_subclass;
+static struct vfs_class *vfs_extfs_ops = VFS_CLASS (&extfs_subclass);
+
+static int my_errno = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static struct extfs_super_t *
+extfs_super_new (struct vfs_class *me, const char *name, const vfs_path_t * local_name_vpath,
+ int fstype)
+{
+ struct extfs_super_t *super;
+ struct vfs_s_super *vsuper;
+
+ super = g_new0 (struct extfs_super_t, 1);
+ vsuper = VFS_SUPER (super);
+
+ vsuper->me = me;
+ vsuper->name = g_strdup (name);
+
+ super->fstype = fstype;
+
+ if (local_name_vpath != NULL)
+ {
+ super->local_name = g_strdup (vfs_path_get_last_path_str (local_name_vpath));
+ mc_stat (local_name_vpath, &super->local_stat);
+ }
+
+ VFS_SUBCLASS (me)->supers = g_list_prepend (VFS_SUBCLASS (me)->supers, super);
+
+ return super;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* unlike vfs_s_new_entry(), inode->ent is kept */
+static struct vfs_s_entry *
+extfs_entry_new (struct vfs_class *me, const char *name, struct vfs_s_inode *inode)
+{
+ struct vfs_s_entry *entry;
+
+ (void) me;
+
+ entry = g_new0 (struct vfs_s_entry, 1);
+
+ entry->name = g_strdup (name);
+ entry->ino = inode;
+
+ return entry;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_fill_name (void *data, void *user_data)
+{
+ struct vfs_s_super *a = VFS_SUPER (data);
+ fill_names_f func = (fill_names_f) user_data;
+ extfs_plugin_info_t *info;
+ char *name;
+
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, EXTFS_SUPER (a)->fstype);
+ name =
+ g_strconcat (a->name != NULL ? a->name : "", PATH_SEP_STR, info->prefix,
+ VFS_PATH_URL_DELIMITER, (char *) NULL);
+ func (name);
+ g_free (name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gint
+extfs_cmp_archive (const void *a, const void *b)
+{
+ const struct vfs_s_super *ar = (const struct vfs_s_super *) a;
+ const char *archive_name = (const char *) b;
+
+ return (ar->name != NULL && strcmp (ar->name, archive_name) == 0) ? 0 : 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_generate_entry (struct extfs_super_t *archive, const char *name, struct vfs_s_inode *parent,
+ mode_t mode)
+{
+ struct vfs_class *me = VFS_SUPER (archive)->me;
+ struct stat st;
+ mode_t myumask;
+ struct vfs_s_inode *inode;
+ struct vfs_s_entry *entry;
+
+ memset (&st, 0, sizeof (st));
+ st.st_ino = VFS_SUPER (archive)->ino_usage++;
+ st.st_dev = archive->rdev;
+ myumask = umask (022);
+ umask (myumask);
+ st.st_mode = mode & ~myumask;
+ st.st_uid = getuid ();
+ st.st_gid = getgid ();
+ st.st_mtime = time (NULL);
+ st.st_atime = st.st_mtime;
+ st.st_ctime = st.st_mtime;
+ st.st_nlink = 1;
+
+ inode = vfs_s_new_inode (me, VFS_SUPER (archive), &st);
+ entry = vfs_s_new_entry (me, name, inode);
+ if (parent != NULL)
+ vfs_s_insert_entry (me, parent, entry);
+
+ return entry;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_find_entry_int (struct vfs_s_inode *dir, const char *name, GSList * list, int flags)
+{
+ struct vfs_s_entry *pent, *pdir;
+ const char *p, *name_end;
+ char *q;
+ char c = PATH_SEP;
+ struct extfs_super_t *super;
+
+ if (g_path_is_absolute (name))
+ {
+ /* Handle absolute paths */
+ name = g_path_skip_root (name);
+ dir = dir->super->root;
+ }
+
+ super = EXTFS_SUPER (dir->super);
+ pent = dir->ent;
+ p = name;
+ name_end = name + strlen (name);
+
+ while ((pent != NULL) && (c != '\0') && (*p != '\0'))
+ {
+ q = strchr (p, PATH_SEP);
+ if (q == NULL)
+ q = (char *) name_end;
+
+ c = *q;
+ *q = '\0';
+
+ if (DIR_IS_DOTDOT (p))
+ pent = pent->dir != NULL ? pent->dir->ent : NULL;
+ else
+ {
+ GList *pl;
+
+ pent = extfs_resolve_symlinks_int (pent, list);
+ if (pent == NULL)
+ {
+ *q = c;
+ return NULL;
+ }
+
+ if (!S_ISDIR (pent->ino->st.st_mode))
+ {
+ *q = c;
+ notadir = TRUE;
+ return NULL;
+ }
+
+ pdir = pent;
+ pl = g_queue_find_custom (pent->ino->subdir, p, vfs_s_entry_compare);
+ pent = pl != NULL ? VFS_ENTRY (pl->data) : NULL;
+ if (pent != NULL && q + 1 > name_end)
+ {
+ /* Hack: I keep the original semanthic unless q+1 would break in the strchr */
+ *q = c;
+ notadir = !S_ISDIR (pent->ino->st.st_mode);
+ return pent;
+ }
+
+ /* When we load archive, we create automagically non-existent directories */
+ if (pent == NULL && (flags & FL_MKDIR) != 0)
+ pent = extfs_generate_entry (super, p, pdir->ino, S_IFDIR | 0777);
+ if (pent == NULL && (flags & FL_MKFILE) != 0)
+ pent = extfs_generate_entry (super, p, pdir->ino, S_IFREG | 0666);
+ }
+
+ /* Next iteration */
+ *q = c;
+ if (c != '\0')
+ p = q + 1;
+ }
+ if (pent == NULL)
+ my_errno = ENOENT;
+ return pent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_find_entry (struct vfs_s_inode *dir, const char *name, int flags)
+{
+ struct vfs_s_entry *res;
+
+ errloop = FALSE;
+ notadir = FALSE;
+
+ res = extfs_find_entry_int (dir, name, NULL, flags);
+ if (res == NULL)
+ {
+ if (errloop)
+ my_errno = ELOOP;
+ else if (notadir)
+ my_errno = ENOTDIR;
+ }
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_fill_names (struct vfs_class *me, fill_names_f func)
+{
+ g_list_foreach (VFS_SUBCLASS (me)->supers, extfs_fill_name, func);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Create this function because VFSF_USETMP flag is not used in extfs */
+static void
+extfs_free_inode (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ (void) me;
+
+ if (ino->localname != NULL)
+ {
+ unlink (ino->localname);
+ MC_PTR_FREE (ino->localname);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_free_archive (struct vfs_class *me, struct vfs_s_super *psup)
+{
+ struct extfs_super_t *archive = EXTFS_SUPER (psup);
+
+ (void) me;
+
+ if (archive->local_name != NULL)
+ {
+ struct stat my;
+ vfs_path_t *local_name_vpath, *name_vpath;
+
+ local_name_vpath = vfs_path_from_str (archive->local_name);
+ name_vpath = vfs_path_from_str (psup->name);
+ mc_stat (local_name_vpath, &my);
+ mc_ungetlocalcopy (name_vpath, local_name_vpath,
+ archive->local_stat.st_mtime != my.st_mtime);
+ vfs_path_free (local_name_vpath, TRUE);
+ vfs_path_free (name_vpath, TRUE);
+ g_free (archive->local_name);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline char *
+extfs_skip_leading_dotslash (char *s)
+{
+ /* Skip leading "./" (if present).
+ * Some programs don't understand it:
+ *
+ * $ zip file.zip ./-file2.txt file1.txt
+ * adding: -file2.txt (stored 0%)
+ * adding: file1.txt (stored 0%)
+ * $ /usr/lib/mc/extfs.d/uzip copyout file.zip ./-file2.txt ./tmp-file2.txt
+ * caution: filename not matched: ./-file2.txt
+ */
+ if (s[0] == '.' && s[1] == PATH_SEP)
+ s += 2;
+
+ return s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_add_file (struct extfs_super_t *archive, const char *file_name)
+{
+ struct vfs_s_super *super = VFS_SUPER (archive);
+ struct stat hstat;
+ char *current_file_name = NULL, *current_link_name = NULL;
+ int ret = 0;
+
+ if (vfs_parse_ls_lga (file_name, &hstat, &current_file_name, &current_link_name, NULL))
+ {
+ char *cfn = current_file_name;
+
+ if (*cfn != '\0')
+ {
+ struct vfs_s_entry *entry;
+ struct vfs_s_entry *pent = NULL;
+ struct vfs_s_inode *inode;
+ char *p, *q;
+
+ cfn = extfs_skip_leading_dotslash (cfn);
+ if (IS_PATH_SEP (*cfn))
+ cfn++;
+ p = strchr (cfn, '\0');
+ if (p != cfn && IS_PATH_SEP (p[-1]))
+ p[-1] = '\0';
+ p = strrchr (cfn, PATH_SEP);
+ if (p == NULL)
+ {
+ p = cfn;
+ q = strchr (cfn, '\0');
+ }
+ else
+ {
+ *(p++) = '\0';
+ q = cfn;
+ }
+
+ if (*q != '\0')
+ {
+ pent = extfs_find_entry (super->root, q, FL_MKDIR);
+ if (pent == NULL)
+ {
+ ret = -1;
+ goto done;
+ }
+ }
+
+ if (pent != NULL)
+ {
+ entry = extfs_entry_new (super->me, p, pent->ino);
+ entry->dir = pent->ino;
+ g_queue_push_tail (pent->ino->subdir, entry);
+ }
+ else
+ {
+ entry = extfs_entry_new (super->me, p, super->root);
+ entry->dir = super->root;
+ g_queue_push_tail (super->root->subdir, entry);
+ }
+
+ if (!S_ISLNK (hstat.st_mode) && (current_link_name != NULL))
+ {
+ pent = extfs_find_entry (super->root, current_link_name, FL_NONE);
+ if (pent == NULL)
+ {
+ ret = -1;
+ goto done;
+ }
+
+ pent->ino->st.st_nlink++;
+ entry->ino = pent->ino;
+ }
+ else
+ {
+ struct stat st;
+
+ memset (&st, 0, sizeof (st));
+ st.st_ino = super->ino_usage++;
+ st.st_nlink = 1;
+ st.st_dev = archive->rdev;
+ st.st_mode = hstat.st_mode;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ st.st_rdev = hstat.st_rdev;
+#endif
+ st.st_uid = hstat.st_uid;
+ st.st_gid = hstat.st_gid;
+ st.st_size = hstat.st_size;
+ st.st_mtime = hstat.st_mtime;
+ st.st_atime = hstat.st_atime;
+ st.st_ctime = hstat.st_ctime;
+
+ if (current_link_name == NULL && S_ISLNK (hstat.st_mode))
+ st.st_mode &= ~S_IFLNK; /* You *DON'T* want to do this always */
+
+ inode = vfs_s_new_inode (super->me, super, &st);
+ inode->ent = entry;
+ entry->ino = inode;
+
+ if (current_link_name != NULL && S_ISLNK (hstat.st_mode))
+ {
+ inode->linkname = current_link_name;
+ current_link_name = NULL;
+ }
+ }
+ }
+
+ done:
+ g_free (current_file_name);
+ g_free (current_link_name);
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mc_pipe_t *
+extfs_open_archive (int fstype, const char *name, struct extfs_super_t **pparc, GError ** error)
+{
+ const extfs_plugin_info_t *info;
+ static dev_t archive_counter = 0;
+ mc_pipe_t *result = NULL;
+ mode_t mode;
+ char *cmd;
+ struct stat mystat;
+ struct extfs_super_t *current_archive;
+ struct vfs_s_entry *root_entry;
+ char *tmp = NULL;
+ vfs_path_t *local_name_vpath = NULL;
+ vfs_path_t *name_vpath;
+
+ memset (&mystat, 0, sizeof (mystat));
+
+ name_vpath = vfs_path_from_str (name);
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, fstype);
+
+ if (info->need_archive)
+ {
+ if (mc_stat (name_vpath, &mystat) == -1)
+ goto ret;
+
+ if (!vfs_file_is_local (name_vpath))
+ {
+ local_name_vpath = mc_getlocalcopy (name_vpath);
+ if (local_name_vpath == NULL)
+ goto ret;
+ }
+
+ tmp = name_quote (vfs_path_get_last_path_str (name_vpath), FALSE);
+ }
+
+ cmd = g_strconcat (info->path, info->prefix, " list ",
+ vfs_path_get_last_path_str (local_name_vpath) != NULL ?
+ vfs_path_get_last_path_str (local_name_vpath) : tmp, (char *) NULL);
+ g_free (tmp);
+
+ result = mc_popen (cmd, TRUE, TRUE, error);
+ g_free (cmd);
+
+ if (result == NULL)
+ {
+ if (local_name_vpath != NULL)
+ {
+ mc_ungetlocalcopy (name_vpath, local_name_vpath, FALSE);
+ vfs_path_free (local_name_vpath, TRUE);
+ }
+ goto ret;
+ }
+
+ current_archive = extfs_super_new (vfs_extfs_ops, name, local_name_vpath, fstype);
+ current_archive->rdev = archive_counter++;
+ vfs_path_free (local_name_vpath, TRUE);
+
+ mode = mystat.st_mode & 07777;
+ if (mode & 0400)
+ mode |= 0100;
+ if (mode & 0040)
+ mode |= 0010;
+ if (mode & 0004)
+ mode |= 0001;
+ mode |= S_IFDIR;
+
+ root_entry = extfs_generate_entry (current_archive, PATH_SEP_STR, NULL, mode);
+ root_entry->ino->st.st_uid = mystat.st_uid;
+ root_entry->ino->st.st_gid = mystat.st_gid;
+ root_entry->ino->st.st_atime = mystat.st_atime;
+ root_entry->ino->st.st_ctime = mystat.st_ctime;
+ root_entry->ino->st.st_mtime = mystat.st_mtime;
+ root_entry->ino->ent = root_entry;
+ VFS_SUPER (current_archive)->root = root_entry->ino;
+
+ *pparc = current_archive;
+
+ ret:
+ vfs_path_free (name_vpath, TRUE);
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Main loop for reading an archive.
+ * Return 0 on success, -1 on error.
+ */
+
+static int
+extfs_read_archive (mc_pipe_t * pip, struct extfs_super_t *archive, GError ** error)
+{
+ int ret = 0;
+ GString *buffer;
+ GString *err_msg = NULL;
+ GString *remain_file_name = NULL;
+
+ while (ret != -1)
+ {
+ /* init buffers before call of mc_pread() */
+ pip->out.len = MC_PIPE_BUFSIZE;
+ pip->err.len = MC_PIPE_BUFSIZE;
+
+ mc_pread (pip, error);
+
+ if (*error != NULL)
+ return (-1);
+
+ if (pip->err.len > 0)
+ {
+ /* join errors/warnings */
+ if (err_msg == NULL)
+ err_msg = g_string_new_len (pip->err.buf, pip->err.len);
+ else
+ {
+ if (err_msg->str[err_msg->len - 1] != '\n')
+ g_string_append_c (err_msg, '\n');
+ g_string_append_len (err_msg, pip->err.buf, pip->err.len);
+ }
+ }
+
+ if (pip->out.len == MC_PIPE_STREAM_EOF)
+ break;
+
+ if (pip->out.len == 0)
+ continue;
+
+ if (pip->out.len == MC_PIPE_ERROR_READ)
+ return (-1);
+
+ while (ret != -1 && (buffer = mc_pstream_get_string (&pip->out)) != NULL)
+ {
+ /* handle a \n-separated file list */
+
+ if (buffer->str[buffer->len - 1] == '\n')
+ {
+ /* entire file name or last chunk */
+
+ g_string_truncate (buffer, buffer->len - 1);
+
+ /* join filename chunks */
+ if (remain_file_name != NULL)
+ {
+ g_string_append_len (remain_file_name, buffer->str, buffer->len);
+ g_string_free (buffer, TRUE);
+ buffer = remain_file_name;
+ remain_file_name = NULL;
+ }
+ }
+ else
+ {
+ /* first or middle chunk of file name */
+
+ if (remain_file_name == NULL)
+ remain_file_name = buffer;
+ else
+ {
+ g_string_append_len (remain_file_name, buffer->str, buffer->len);
+ g_string_free (buffer, TRUE);
+ }
+
+ continue;
+ }
+
+ ret = extfs_add_file (archive, buffer->str);
+
+ g_string_free (buffer, TRUE);
+ }
+ }
+
+ if (remain_file_name != NULL)
+ g_string_free (remain_file_name, TRUE);
+
+ if (err_msg != NULL)
+ {
+ if (*error == NULL)
+ mc_propagate_error (error, 0, "%s", err_msg->str);
+
+ g_string_free (err_msg, TRUE);
+ }
+ else if (ret == -1)
+ mc_propagate_error (error, 0, "%s", _("Inconsistent archive"));
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_which (struct vfs_class *me, const char *path)
+{
+ size_t path_len;
+ size_t i;
+
+ (void) me;
+
+ path_len = strlen (path);
+
+ for (i = 0; i < extfs_plugins->len; i++)
+ {
+ extfs_plugin_info_t *info;
+
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, i);
+
+ if ((strncmp (path, info->prefix, path_len) == 0)
+ && ((info->prefix[path_len] == '\0') || (info->prefix[path_len] == '+')))
+ return i;
+ }
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_open_and_read_archive (int fstype, const char *name, struct extfs_super_t **archive)
+{
+ int result = -1;
+ struct extfs_super_t *a;
+ mc_pipe_t *pip;
+ GError *error = NULL;
+
+ pip = extfs_open_archive (fstype, name, archive, &error);
+
+ a = *archive;
+
+ if (pip == NULL)
+ {
+ const extfs_plugin_info_t *info;
+
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, fstype);
+ message (D_ERROR, MSG_ERROR, _("Cannot open %s archive\n%s:\n%s"), info->prefix, name,
+ error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ result = extfs_read_archive (pip, a, &error);
+
+ if (result != 0)
+ VFS_SUPER (a)->me->free (VFS_SUPER (a));
+
+ if (error != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message);
+ g_error_free (error);
+ }
+
+ mc_pclose (pip, NULL);
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Dissect the path and create corresponding superblock.
+ */
+static const char *
+extfs_get_path (const vfs_path_t * vpath, struct extfs_super_t **archive, int flags)
+{
+ char *archive_name;
+ int result = -1;
+ GList *parc;
+ int fstype;
+ const vfs_path_element_t *path_element;
+ struct extfs_super_t *a = NULL;
+
+ path_element = vfs_path_get_by_index (vpath, -1);
+
+ fstype = extfs_which (path_element->class, path_element->vfs_prefix);
+ if (fstype == -1)
+ return NULL;
+
+ archive_name = vfs_path_to_str_elements_count (vpath, -1);
+
+ /* All filesystems should have some local archive, at least it can be PATH_SEP ('/'). */
+ parc = g_list_find_custom (extfs_subclass.supers, archive_name, extfs_cmp_archive);
+ if (parc != NULL)
+ {
+ a = EXTFS_SUPER (parc->data);
+ vfs_stamp (vfs_extfs_ops, (vfsid) a);
+ g_free (archive_name);
+ }
+ else
+ {
+ if ((flags & FL_NO_OPEN) == 0)
+ result = extfs_open_and_read_archive (fstype, archive_name, &a);
+
+ g_free (archive_name);
+
+ if (result == -1)
+ {
+ path_element->class->verrno = EIO;
+ return NULL;
+ }
+ }
+
+ *archive = a;
+ return path_element->path;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Return allocated path (without leading slash) inside the archive */
+
+static char *
+extfs_get_path_from_entry (const struct vfs_s_entry *entry)
+{
+ const struct vfs_s_entry *e;
+ GString *localpath;
+
+ localpath = g_string_new ("");
+
+ for (e = entry; e->dir != NULL; e = e->dir->ent)
+ {
+ g_string_prepend (localpath, e->name);
+ if (e->dir->ent->dir != NULL)
+ g_string_prepend_c (localpath, PATH_SEP);
+ }
+
+ return g_string_free (localpath, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_resolve_symlinks_int (struct vfs_s_entry *entry, GSList * list)
+{
+ struct vfs_s_entry *pent = NULL;
+
+ if (!S_ISLNK (entry->ino->st.st_mode))
+ return entry;
+
+ if (g_slist_find (list, entry) != NULL)
+ {
+ /* Here we protect us against symlink looping */
+ errloop = TRUE;
+ }
+ else
+ {
+ GSList *looping;
+
+ looping = g_slist_prepend (list, entry);
+ pent = extfs_find_entry_int (entry->dir, entry->ino->linkname, looping, FL_NONE);
+ looping = g_slist_delete_link (looping, looping);
+
+ if (pent == NULL)
+ my_errno = ENOENT;
+ }
+
+ return pent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_resolve_symlinks (struct vfs_s_entry *entry)
+{
+ struct vfs_s_entry *res;
+
+ errloop = FALSE;
+ notadir = FALSE;
+ res = extfs_resolve_symlinks_int (entry, NULL);
+ if (res == NULL)
+ {
+ if (errloop)
+ my_errno = ELOOP;
+ else if (notadir)
+ my_errno = ENOTDIR;
+ }
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+extfs_get_archive_name (const struct extfs_super_t *archive)
+{
+ const char *archive_name;
+
+ if (archive->local_name != NULL)
+ archive_name = archive->local_name;
+ else
+ archive_name = CONST_VFS_SUPER (archive)->name;
+
+ if (archive_name == NULL || *archive_name == '\0')
+ return g_strdup ("no_archive_name");
+ else
+ {
+ char *ret_str;
+ vfs_path_t *vpath;
+ const char *path;
+
+ vpath = vfs_path_from_str (archive_name);
+ path = vfs_path_get_last_path_str (vpath);
+ ret_str = g_strdup (path);
+ vfs_path_free (vpath, TRUE);
+ return ret_str;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Don't pass localname as NULL */
+
+static int
+extfs_cmd (const char *str_extfs_cmd, const struct extfs_super_t *archive,
+ const struct vfs_s_entry *entry, const char *localname)
+{
+ char *file;
+ char *quoted_file;
+ char *quoted_localname;
+ char *archive_name, *quoted_archive_name;
+ const extfs_plugin_info_t *info;
+ char *cmd;
+ int retval = 0;
+ GError *error = NULL;
+ mc_pipe_t *pip;
+
+ file = extfs_get_path_from_entry (entry);
+ quoted_file = name_quote (file, FALSE);
+ g_free (file);
+
+ /* Skip leading "./" (if present) added in name_quote() */
+ file = extfs_skip_leading_dotslash (quoted_file);
+
+ archive_name = extfs_get_archive_name (archive);
+ quoted_archive_name = name_quote (archive_name, FALSE);
+ g_free (archive_name);
+ quoted_localname = name_quote (localname, FALSE);
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, archive->fstype);
+ cmd = g_strconcat (info->path, info->prefix, str_extfs_cmd,
+ quoted_archive_name, " ", file, " ", quoted_localname, (char *) NULL);
+ g_free (quoted_file);
+ g_free (quoted_localname);
+ g_free (quoted_archive_name);
+
+ /* don't read stdout */
+ pip = mc_popen (cmd, FALSE, TRUE, &error);
+ g_free (cmd);
+
+ if (pip == NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message);
+ g_error_free (error);
+ return (-1);
+ }
+
+ pip->err.null_term = TRUE;
+
+ mc_pread (pip, &error);
+ if (error != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message);
+ g_error_free (error);
+ retval = -1;
+ }
+ else if (pip->err.len > 0)
+ message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), pip->err.buf);
+
+ mc_pclose (pip, NULL);
+
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_run (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive = NULL;
+ const char *p;
+ char *q, *archive_name, *quoted_archive_name;
+ char *cmd;
+ const extfs_plugin_info_t *info;
+
+ p = extfs_get_path (vpath, &archive, FL_NONE);
+ if (p == NULL)
+ return;
+ q = name_quote (p, FALSE);
+
+ archive_name = extfs_get_archive_name (archive);
+ quoted_archive_name = name_quote (archive_name, FALSE);
+ g_free (archive_name);
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, archive->fstype);
+ cmd =
+ g_strconcat (info->path, info->prefix, " run ", quoted_archive_name, " ", q, (char *) NULL);
+ g_free (quoted_archive_name);
+ g_free (q);
+ shell_execute (cmd, 0);
+ g_free (cmd);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+extfs_open (const vfs_path_t * vpath, int flags, mode_t mode)
+{
+ vfs_file_handler_t *extfs_info;
+ struct extfs_super_t *archive = NULL;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int local_handle;
+ gboolean created = FALSE;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ return NULL;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if ((entry == NULL) && ((flags & O_CREAT) != 0))
+ {
+ /* Create new entry */
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_MKFILE);
+ created = (entry != NULL);
+ }
+
+ if (entry == NULL)
+ return NULL;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ return NULL;
+
+ if (S_ISDIR (entry->ino->st.st_mode))
+ ERRNOR (EISDIR, NULL);
+
+ if (entry->ino->localname == NULL)
+ {
+ vfs_path_t *local_filename_vpath;
+ const char *local_filename;
+
+ local_handle = vfs_mkstemps (&local_filename_vpath, "extfs", entry->name);
+
+ if (local_handle == -1)
+ return NULL;
+ close (local_handle);
+ local_filename = vfs_path_get_last_path_str (local_filename_vpath);
+
+ if (!created && ((flags & O_TRUNC) == 0)
+ && extfs_cmd (" copyout ", archive, entry, local_filename))
+ {
+ unlink (local_filename);
+ vfs_path_free (local_filename_vpath, TRUE);
+ my_errno = EIO;
+ return NULL;
+ }
+ entry->ino->localname = g_strdup (local_filename);
+ vfs_path_free (local_filename_vpath, TRUE);
+ }
+
+ local_handle = open (entry->ino->localname, NO_LINEAR (flags), mode);
+
+ if (local_handle == -1)
+ {
+ /* file exists(may be). Need to drop O_CREAT flag and truncate file content */
+ flags = ~O_CREAT & (NO_LINEAR (flags) | O_TRUNC);
+ local_handle = open (entry->ino->localname, flags, mode);
+ }
+
+ if (local_handle == -1)
+ ERRNOR (EIO, NULL);
+
+ extfs_info = g_new (vfs_file_handler_t, 1);
+ vfs_s_init_fh (extfs_info, entry->ino, created);
+ extfs_info->handle = local_handle;
+
+ /* i.e. we had no open files and now we have one */
+ vfs_rmstamp (vfs_extfs_ops, (vfsid) archive);
+ VFS_SUPER (archive)->fd_usage++;
+ return extfs_info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+extfs_read (void *fh, char *buffer, size_t count)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+
+ return read (file->handle, buffer, count);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_close (void *fh)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ int errno_code = 0;
+
+ close (file->handle);
+ file->handle = -1;
+
+ /* Commit the file if it has changed */
+ if (file->changed)
+ {
+ struct stat file_status;
+
+ if (extfs_cmd
+ (" copyin ", EXTFS_SUPER (VFS_FILE_HANDLER_SUPER (fh)), file->ino->ent,
+ file->ino->localname))
+ errno_code = EIO;
+
+ if (stat (file->ino->localname, &file_status) != 0)
+ errno_code = EIO;
+ else
+ file->ino->st.st_size = file_status.st_size;
+
+ file->ino->st.st_mtime = time (NULL);
+ }
+
+ if (--VFS_FILE_HANDLER_SUPER (fh)->fd_usage == 0)
+ vfs_stamp_create (vfs_extfs_ops, VFS_FILE_HANDLER_SUPER (fh));
+
+ g_free (fh);
+ if (errno_code != 0)
+ ERRNOR (EIO, -1);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_errno (struct vfs_class *me)
+{
+ (void) me;
+ return my_errno;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+extfs_opendir (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive = NULL;
+ const char *q;
+ struct vfs_s_entry *entry;
+ GList **info;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ return NULL;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ return NULL;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ return NULL;
+ if (!S_ISDIR (entry->ino->st.st_mode))
+ ERRNOR (ENOTDIR, NULL);
+
+ info = g_new (GList *, 1);
+ *info = g_queue_peek_head_link (entry->ino->subdir);
+
+ return info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_dirent *
+extfs_readdir (void *data)
+{
+ struct vfs_dirent *dir;
+ GList **info = (GList **) data;
+
+ if (*info == NULL)
+ return NULL;
+
+ dir = vfs_dirent_init (NULL, VFS_ENTRY ((*info)->data)->name, 0); /* FIXME: inode */
+
+ *info = g_list_next (*info);
+
+ return dir;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_closedir (void *data)
+{
+ g_free (data);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+
+static void
+extfs_stat_move (struct stat *buf, const struct vfs_s_inode *inode)
+{
+ *buf = inode->st;
+
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ buf->st_blksize = RECORDSIZE;
+#endif
+ vfs_adjust_stat (buf);
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ buf->st_atim.tv_nsec = buf->st_mtim.tv_nsec = buf->st_ctim.tv_nsec = 0;
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_internal_stat (const vfs_path_t * vpath, struct stat *buf, gboolean resolve)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int result = -1;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ goto cleanup;
+ if (resolve)
+ {
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ goto cleanup;
+ }
+ extfs_stat_move (buf, entry->ino);
+ result = 0;
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ return extfs_internal_stat (vpath, buf, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ return extfs_internal_stat (vpath, buf, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_fstat (void *fh, struct stat *buf)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+
+ extfs_stat_move (buf, file->ino);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_readlink (const vfs_path_t * vpath, char *buf, size_t size)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ size_t len;
+ struct vfs_s_entry *entry;
+ int result = -1;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ goto cleanup;
+ if (!S_ISLNK (entry->ino->st.st_mode))
+ {
+ VFS_CLASS (vfs_path_get_last_path_vfs (vpath))->verrno = EINVAL;
+ goto cleanup;
+ }
+ len = strlen (entry->ino->linkname);
+ if (size < len)
+ len = size;
+ /* readlink() does not append a NUL character to buf */
+ result = len;
+ memcpy (buf, entry->ino->linkname, result);
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_chown (const vfs_path_t * vpath, uid_t owner, gid_t group)
+{
+ (void) vpath;
+ (void) owner;
+ (void) group;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_chmod (const vfs_path_t * vpath, mode_t mode)
+{
+ (void) vpath;
+ (void) mode;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+extfs_write (void *fh, const char *buf, size_t nbyte)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+
+ file->changed = TRUE;
+ return write (file->handle, buf, nbyte);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_unlink (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int result = -1;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ goto cleanup;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ goto cleanup;
+ if (S_ISDIR (entry->ino->st.st_mode))
+ {
+ VFS_CLASS (vfs_path_get_last_path_vfs (vpath))->verrno = EISDIR;
+ goto cleanup;
+ }
+ if (extfs_cmd (" rm ", archive, entry, ""))
+ {
+ my_errno = EIO;
+ goto cleanup;
+ }
+ vfs_s_free_entry (VFS_SUPER (archive)->me, entry);
+ result = 0;
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_mkdir (const vfs_path_t * vpath, mode_t mode)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int result = -1;
+ struct vfs_class *me;
+
+ (void) mode;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry != NULL)
+ {
+ me->verrno = EEXIST;
+ goto cleanup;
+ }
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_MKDIR);
+ if (entry == NULL)
+ goto cleanup;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ goto cleanup;
+ if (!S_ISDIR (entry->ino->st.st_mode))
+ {
+ me->verrno = ENOTDIR;
+ goto cleanup;
+ }
+
+ if (extfs_cmd (" mkdir ", archive, entry, ""))
+ {
+ my_errno = EIO;
+ vfs_s_free_entry (VFS_SUPER (archive)->me, entry);
+ goto cleanup;
+ }
+ result = 0;
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_rmdir (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int result = -1;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ goto cleanup;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ goto cleanup;
+ if (!S_ISDIR (entry->ino->st.st_mode))
+ {
+ VFS_CLASS (vfs_path_get_last_path_vfs (vpath))->verrno = ENOTDIR;
+ goto cleanup;
+ }
+
+ if (extfs_cmd (" rmdir ", archive, entry, ""))
+ {
+ my_errno = EIO;
+ goto cleanup;
+ }
+ vfs_s_free_entry (VFS_SUPER (archive)->me, entry);
+ result = 0;
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_chdir (const vfs_path_t * vpath)
+{
+ void *data;
+
+ my_errno = ENOTDIR;
+ data = extfs_opendir (vpath);
+ if (data == NULL)
+ return (-1);
+ extfs_closedir (data);
+ my_errno = 0;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+extfs_lseek (void *fh, off_t offset, int whence)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+
+ return lseek (file->handle, offset, whence);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfsid
+extfs_getid (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive = NULL;
+ const char *p;
+
+ p = extfs_get_path (vpath, &archive, FL_NO_OPEN);
+ return (p == NULL ? NULL : (vfsid) archive);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+extfs_getlocalcopy (const vfs_path_t * vpath)
+{
+ vfs_file_handler_t *fh;
+ vfs_path_t *p;
+
+ fh = VFS_FILE_HANDLER (extfs_open (vpath, O_RDONLY, 0));
+ if (fh == NULL)
+ return NULL;
+ if (fh->ino->localname == NULL)
+ {
+ extfs_close ((void *) fh);
+ return NULL;
+ }
+ p = vfs_path_from_str (fh->ino->localname);
+ VFS_FILE_HANDLER_SUPER (fh)->fd_usage++;
+ extfs_close ((void *) fh);
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_ungetlocalcopy (const vfs_path_t * vpath, const vfs_path_t * local, gboolean has_changed)
+{
+ vfs_file_handler_t *fh;
+
+ fh = VFS_FILE_HANDLER (extfs_open (vpath, O_RDONLY, 0));
+ if (fh == NULL)
+ return 0;
+
+ if (strcmp (fh->ino->localname, vfs_path_get_last_path_str (local)) == 0)
+ {
+ VFS_FILE_HANDLER_SUPER (fh)->fd_usage--;
+ if (has_changed)
+ fh->changed = TRUE;
+ extfs_close ((void *) fh);
+ return 0;
+ }
+ else
+ {
+ /* Should not happen */
+ extfs_close ((void *) fh);
+ return 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+extfs_get_plugins (const char *where, gboolean silent)
+{
+ char *dirname;
+ GDir *dir;
+ const char *filename;
+
+ dirname = g_build_path (PATH_SEP_STR, where, MC_EXTFS_DIR, (char *) NULL);
+ dir = g_dir_open (dirname, 0, NULL);
+
+ /* We may not use vfs_die() message or message or similar,
+ * UI is not initialized at this time and message would not
+ * appear on screen. */
+ if (dir == NULL)
+ {
+ if (!silent)
+ fprintf (stderr, _("Warning: cannot open %s directory\n"), dirname);
+ g_free (dirname);
+ return FALSE;
+ }
+
+ if (extfs_plugins == NULL)
+ extfs_plugins = g_array_sized_new (FALSE, TRUE, sizeof (extfs_plugin_info_t), 32);
+
+ while ((filename = g_dir_read_name (dir)) != NULL)
+ {
+ char fullname[MC_MAXPATHLEN];
+ struct stat s;
+
+ g_snprintf (fullname, sizeof (fullname), "%s" PATH_SEP_STR "%s", dirname, filename);
+
+ if ((stat (fullname, &s) == 0) && S_ISREG (s.st_mode) && !S_ISDIR (s.st_mode)
+ && is_exe (s.st_mode))
+ {
+ int f;
+
+ f = open (fullname, O_RDONLY);
+
+ if (f >= 0)
+ {
+ size_t len, i;
+ extfs_plugin_info_t info;
+ gboolean found = FALSE;
+
+ close (f);
+
+ /* Handle those with a trailing '+', those flag that the
+ * file system does not require an archive to work
+ */
+ len = strlen (filename);
+ info.need_archive = (filename[len - 1] != '+');
+ info.path = g_strconcat (dirname, PATH_SEP_STR, (char *) NULL);
+ info.prefix = g_strndup (filename, len);
+
+ /* prepare to compare file names without trailing '+' */
+ if (!info.need_archive)
+ info.prefix[len - 1] = '\0';
+
+ /* don't overload already found plugin */
+ for (i = 0; i < extfs_plugins->len && !found; i++)
+ {
+ extfs_plugin_info_t *p;
+
+ p = &g_array_index (extfs_plugins, extfs_plugin_info_t, i);
+
+ /* 2 files with same names cannot be in a directory */
+ found = strcmp (info.path, p->path) != 0
+ && strcmp (info.prefix, p->prefix) == 0;
+ }
+
+ if (found)
+ {
+ g_free (info.path);
+ g_free (info.prefix);
+ }
+ else
+ {
+ /* restore file name */
+ if (!info.need_archive)
+ info.prefix[len - 1] = '+';
+ g_array_append_val (extfs_plugins, info);
+ }
+ }
+ }
+ }
+
+ g_dir_close (dir);
+ g_free (dirname);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_init (struct vfs_class *me)
+{
+ gboolean d1, d2;
+
+ (void) me;
+
+ /* 1st: scan user directory */
+ d1 = extfs_get_plugins (mc_config_get_data_path (), TRUE); /* silent about user dir */
+ /* 2nd: scan system dir */
+ d2 = extfs_get_plugins (LIBEXECDIR, d1);
+
+ return (d1 || d2 ? 1 : 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_done (struct vfs_class *me)
+{
+ size_t i;
+
+ while (VFS_SUBCLASS (me)->supers != NULL)
+ me->free ((vfsid) VFS_SUBCLASS (me)->supers->data);
+
+ if (extfs_plugins == NULL)
+ return;
+
+ for (i = 0; i < extfs_plugins->len; i++)
+ {
+ extfs_plugin_info_t *info;
+
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, i);
+ g_free (info->path);
+ g_free (info->prefix);
+ }
+
+ g_array_free (extfs_plugins, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_setctl (const vfs_path_t * vpath, int ctlop, void *arg)
+{
+ (void) arg;
+
+ if (ctlop == VFS_SETCTL_RUN)
+ {
+ extfs_run (vpath);
+ return 1;
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_extfs (void)
+{
+ vfs_init_subclass (&extfs_subclass, "extfs", VFSF_UNKNOWN, NULL);
+ vfs_extfs_ops->init = extfs_init;
+ vfs_extfs_ops->done = extfs_done;
+ vfs_extfs_ops->fill_names = extfs_fill_names;
+ vfs_extfs_ops->which = extfs_which;
+ vfs_extfs_ops->open = extfs_open;
+ vfs_extfs_ops->close = extfs_close;
+ vfs_extfs_ops->read = extfs_read;
+ vfs_extfs_ops->write = extfs_write;
+ vfs_extfs_ops->opendir = extfs_opendir;
+ vfs_extfs_ops->readdir = extfs_readdir;
+ vfs_extfs_ops->closedir = extfs_closedir;
+ vfs_extfs_ops->stat = extfs_stat;
+ vfs_extfs_ops->lstat = extfs_lstat;
+ vfs_extfs_ops->fstat = extfs_fstat;
+ vfs_extfs_ops->chmod = extfs_chmod;
+ vfs_extfs_ops->chown = extfs_chown;
+ vfs_extfs_ops->readlink = extfs_readlink;
+ vfs_extfs_ops->unlink = extfs_unlink;
+ vfs_extfs_ops->chdir = extfs_chdir;
+ vfs_extfs_ops->ferrno = extfs_errno;
+ vfs_extfs_ops->lseek = extfs_lseek;
+ vfs_extfs_ops->getid = extfs_getid;
+ vfs_extfs_ops->getlocalcopy = extfs_getlocalcopy;
+ vfs_extfs_ops->ungetlocalcopy = extfs_ungetlocalcopy;
+ vfs_extfs_ops->mkdir = extfs_mkdir;
+ vfs_extfs_ops->rmdir = extfs_rmdir;
+ vfs_extfs_ops->setctl = extfs_setctl;
+ extfs_subclass.free_inode = extfs_free_inode;
+ extfs_subclass.free_archive = extfs_free_archive;
+ vfs_register_class (vfs_extfs_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/extfs/extfs.h b/src/vfs/extfs/extfs.h
new file mode 100644
index 0000000..c576dc0
--- /dev/null
+++ b/src/vfs/extfs/extfs.h
@@ -0,0 +1,18 @@
+#ifndef MC__VFS_EXTFS_H
+#define MC__VFS_EXTFS_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_extfs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_CPIO_H */
diff --git a/src/vfs/extfs/helpers/Makefile.am b/src/vfs/extfs/helpers/Makefile.am
new file mode 100644
index 0000000..f1ea0ac
--- /dev/null
+++ b/src/vfs/extfs/helpers/Makefile.am
@@ -0,0 +1,76 @@
+extfsdir = $(libexecdir)/@PACKAGE@/extfs.d
+
+# Files to install and distribute other than extfs scripts
+EXTFS_MISC = README README.extfs
+
+# Scripts hat don't need adaptation to the local system
+EXTFS_CONST = bpp changesetfs gitfs+ patchsetfs rpm trpm u7z uc1541
+
+# Scripts that need adaptation to the local system - source files
+EXTFS_IN = \
+ a+.in \
+ apt+.in \
+ audio.in \
+ deb.in \
+ deba.in \
+ debd.in \
+ dpkg+.in \
+ iso9660.in \
+ hp48+.in \
+ lslR.in \
+ mailfs.in \
+ patchfs.in \
+ rpms+.in \
+ s3+.in \
+ uace.in \
+ ualz.in \
+ uar.in \
+ uarc.in \
+ uarj.in \
+ ucab.in \
+ uha.in \
+ ulha.in \
+ ulib.in \
+ unar.in \
+ urar.in \
+ uwim.in \
+ uzip.in \
+ uzoo.in
+
+# Scripts that need adaptation to the local system - files to install
+EXTFS_OUT = \
+ a+ \
+ apt+ \
+ audio \
+ deb \
+ deba \
+ debd \
+ dpkg+ \
+ iso9660 \
+ hp48+ \
+ lslR \
+ mailfs \
+ patchfs \
+ rpms+ \
+ s3+ \
+ uace \
+ ualz \
+ uar \
+ uarc \
+ uarj \
+ ucab \
+ uha \
+ ulha \
+ ulib \
+ unar \
+ urar \
+ uwim \
+ uzip \
+ uzoo
+
+if ENABLE_VFS_EXTFS
+extfs_DATA = $(EXTFS_MISC)
+extfs_SCRIPTS = $(EXTFS_CONST) $(EXTFS_OUT)
+endif
+
+EXTRA_DIST = $(EXTFS_MISC) $(EXTFS_CONST)
diff --git a/src/vfs/extfs/helpers/Makefile.in b/src/vfs/extfs/helpers/Makefile.in
new file mode 100644
index 0000000..0a240fb
--- /dev/null
+++ b/src/vfs/extfs/helpers/Makefile.in
@@ -0,0 +1,812 @@
+# 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/vfs/extfs/helpers
+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 = a+ apt+ audio deb deba debd dpkg+ iso9660 hp48+ \
+ lslR mailfs patchfs rpms+ s3+ uace ualz uar uarc uarj ucab uha \
+ ulha ulib unar urar uwim uzip uzoo
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(extfsdir)" "$(DESTDIR)$(extfsdir)"
+SCRIPTS = $(extfs_SCRIPTS)
+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 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+DATA = $(extfs_DATA)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/a+.in \
+ $(srcdir)/apt+.in $(srcdir)/audio.in $(srcdir)/deb.in \
+ $(srcdir)/deba.in $(srcdir)/debd.in $(srcdir)/dpkg+.in \
+ $(srcdir)/hp48+.in $(srcdir)/iso9660.in $(srcdir)/lslR.in \
+ $(srcdir)/mailfs.in $(srcdir)/patchfs.in $(srcdir)/rpms+.in \
+ $(srcdir)/s3+.in $(srcdir)/uace.in $(srcdir)/ualz.in \
+ $(srcdir)/uar.in $(srcdir)/uarc.in $(srcdir)/uarj.in \
+ $(srcdir)/ucab.in $(srcdir)/uha.in $(srcdir)/ulha.in \
+ $(srcdir)/ulib.in $(srcdir)/unar.in $(srcdir)/urar.in \
+ $(srcdir)/uwim.in $(srcdir)/uzip.in $(srcdir)/uzoo.in README
+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@
+extfsdir = $(libexecdir)/@PACKAGE@/extfs.d
+
+# Files to install and distribute other than extfs scripts
+EXTFS_MISC = README README.extfs
+
+# Scripts hat don't need adaptation to the local system
+EXTFS_CONST = bpp changesetfs gitfs+ patchsetfs rpm trpm u7z uc1541
+
+# Scripts that need adaptation to the local system - source files
+EXTFS_IN = \
+ a+.in \
+ apt+.in \
+ audio.in \
+ deb.in \
+ deba.in \
+ debd.in \
+ dpkg+.in \
+ iso9660.in \
+ hp48+.in \
+ lslR.in \
+ mailfs.in \
+ patchfs.in \
+ rpms+.in \
+ s3+.in \
+ uace.in \
+ ualz.in \
+ uar.in \
+ uarc.in \
+ uarj.in \
+ ucab.in \
+ uha.in \
+ ulha.in \
+ ulib.in \
+ unar.in \
+ urar.in \
+ uwim.in \
+ uzip.in \
+ uzoo.in
+
+
+# Scripts that need adaptation to the local system - files to install
+EXTFS_OUT = \
+ a+ \
+ apt+ \
+ audio \
+ deb \
+ deba \
+ debd \
+ dpkg+ \
+ iso9660 \
+ hp48+ \
+ lslR \
+ mailfs \
+ patchfs \
+ rpms+ \
+ s3+ \
+ uace \
+ ualz \
+ uar \
+ uarc \
+ uarj \
+ ucab \
+ uha \
+ ulha \
+ ulib \
+ unar \
+ urar \
+ uwim \
+ uzip \
+ uzoo
+
+@ENABLE_VFS_EXTFS_TRUE@extfs_DATA = $(EXTFS_MISC)
+@ENABLE_VFS_EXTFS_TRUE@extfs_SCRIPTS = $(EXTFS_CONST) $(EXTFS_OUT)
+EXTRA_DIST = $(EXTFS_MISC) $(EXTFS_CONST)
+all: all-am
+
+.SUFFIXES:
+$(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/vfs/extfs/helpers/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/extfs/helpers/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):
+a+: $(top_builddir)/config.status $(srcdir)/a+.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+apt+: $(top_builddir)/config.status $(srcdir)/apt+.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+audio: $(top_builddir)/config.status $(srcdir)/audio.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+deb: $(top_builddir)/config.status $(srcdir)/deb.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+deba: $(top_builddir)/config.status $(srcdir)/deba.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+debd: $(top_builddir)/config.status $(srcdir)/debd.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+dpkg+: $(top_builddir)/config.status $(srcdir)/dpkg+.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+iso9660: $(top_builddir)/config.status $(srcdir)/iso9660.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+hp48+: $(top_builddir)/config.status $(srcdir)/hp48+.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+lslR: $(top_builddir)/config.status $(srcdir)/lslR.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+mailfs: $(top_builddir)/config.status $(srcdir)/mailfs.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+patchfs: $(top_builddir)/config.status $(srcdir)/patchfs.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+rpms+: $(top_builddir)/config.status $(srcdir)/rpms+.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+s3+: $(top_builddir)/config.status $(srcdir)/s3+.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uace: $(top_builddir)/config.status $(srcdir)/uace.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+ualz: $(top_builddir)/config.status $(srcdir)/ualz.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uar: $(top_builddir)/config.status $(srcdir)/uar.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uarc: $(top_builddir)/config.status $(srcdir)/uarc.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uarj: $(top_builddir)/config.status $(srcdir)/uarj.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+ucab: $(top_builddir)/config.status $(srcdir)/ucab.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uha: $(top_builddir)/config.status $(srcdir)/uha.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+ulha: $(top_builddir)/config.status $(srcdir)/ulha.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+ulib: $(top_builddir)/config.status $(srcdir)/ulib.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+unar: $(top_builddir)/config.status $(srcdir)/unar.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+urar: $(top_builddir)/config.status $(srcdir)/urar.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uwim: $(top_builddir)/config.status $(srcdir)/uwim.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uzip: $(top_builddir)/config.status $(srcdir)/uzip.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+uzoo: $(top_builddir)/config.status $(srcdir)/uzoo.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+install-extfsSCRIPTS: $(extfs_SCRIPTS)
+ @$(NORMAL_INSTALL)
+ @list='$(extfs_SCRIPTS)'; test -n "$(extfsdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(extfsdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(extfsdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n' \
+ -e 'h;s|.*|.|' \
+ -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) { files[d] = files[d] " " $$1; \
+ if (++n[d] == $(am__install_max)) { \
+ print "f", d, files[d]; n[d] = 0; files[d] = "" } } \
+ else { print "f", d "/" $$4, $$1 } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(extfsdir)$$dir'"; \
+ $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(extfsdir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-extfsSCRIPTS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(extfs_SCRIPTS)'; test -n "$(extfsdir)" || exit 0; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 's,.*/,,;$(transform)'`; \
+ dir='$(DESTDIR)$(extfsdir)'; $(am__uninstall_files_from_dir)
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-extfsDATA: $(extfs_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(extfs_DATA)'; test -n "$(extfsdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(extfsdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(extfsdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(extfsdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(extfsdir)" || exit $$?; \
+ done
+
+uninstall-extfsDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(extfs_DATA)'; test -n "$(extfsdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(extfsdir)'; $(am__uninstall_files_from_dir)
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+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 $(SCRIPTS) $(DATA)
+installdirs:
+ for dir in "$(DESTDIR)$(extfsdir)" "$(DESTDIR)$(extfsdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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 mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-extfsDATA install-extfsSCRIPTS
+
+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 Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-extfsDATA uninstall-extfsSCRIPTS
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags-am distclean distclean-generic \
+ distclean-libtool 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-extfsDATA \
+ install-extfsSCRIPTS 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-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags-am uninstall \
+ uninstall-am uninstall-extfsDATA uninstall-extfsSCRIPTS
+
+.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/vfs/extfs/helpers/README b/src/vfs/extfs/helpers/README
new file mode 100644
index 0000000..64da3d6
--- /dev/null
+++ b/src/vfs/extfs/helpers/README
@@ -0,0 +1,200 @@
+ Writing scripts for Midnight Commander's external vfs
+
+IMPORTANT NOTE: There may be some bugs left in extfs. Enjoy.
+
+Starting with version 3.1, the Midnight Commander comes with so called
+extfs, which is one of the virtual filesystems. This system makes it
+possible to create new virtual filesystems for the GNU MC very easily.
+
+To handle requests, create a shell/perl/python/etc script/program
+(with executable permissions) in $(libexecdir)/mc/extfs.d
+or in ~/.local/share/mc/extfs.d/.
+
+(Note: $(libexecdir) should be substituted for actual libexecdir path
+stored when configured or compiled, like /usr/local/libexec or /usr/libexec).
+
+Assign a vfs suffix. For example, if you have .zip file, and would like
+to see what's inside it, path will be
+
+/anypath/my.zip/uzip://some_path/...
+
+In this example, .zip is suffix, but I call vfs 'uzip'. Why? Well,
+what this vfs essentially does is UNzip. UN is too long, so I chose
+U. Note that sometime in future filesystem like zip may exist: It will
+take whole tree and create .zip file from it. So /usr/zip:// will be
+zipfile containing whole /usr tree.
+
+If your vfs does not require file to work on, add '+' to the end of name.
+Note, that trailing '+' in file name is not a part of vfs name, it is
+just an vfs attribute. So you have not use it in vfs commands:
+
+cd rpms://
+
+is correct command, and
+
+cd rpms+://
+
+is incorrect command.
+
+
+* Commands that should be implemented by your shell script
+----------------------------------------------------------
+
+Return zero from your script upon completion of the command, otherwise
+nonzero for failure or in case of an unsupported command.
+
+$libdir/extfs/prefix command [arguments]
+
+* Command: list archivename
+
+This command should list the complete archive content in the following format
+(a little modified ls -l listing):
+
+AAAAAAA NNN OOOOOOOO GGGGGGGG SSSSSSSS DATETIME [PATH/]FILENAME [-> [PATH/]FILENAME[/]]]
+
+where (things in [] are optional):
+
+AAAAAAA is the permission string like in ls -l
+NNN is the number of links
+OOOOOOOO is the owner (either UID or name)
+GGGGGGGG is the group (either GID or name)
+SSSSSSSS is the file size
+FILENAME is the filename
+PATH is the path from the archive's root without the leading slash (/)
+DATETIME has one of the following formats:
+ Mon DD hh:mm[:ss], Mon DD YYYY, MM-DD-YYYY hh:mm[:ss]
+
+ where Mon is a three letter English month name, DD is day
+ 01-31 (can be 1-31, if following Mon), MM is month 01-12,
+ YYYY is four digit year, hh is hours, mm is minutes,
+ and ss is optional seconds.
+
+If the -> [PATH/]FILENAME part is present, it means:
+
+If permissions start with an l (ell), then it is the name that symlink
+points to. (If this PATH starts with a MC vfs prefix, then it is a symlink
+somewhere to the other virtual filesystem (if you want to specify path from
+the local root, use local:/path_name instead of /path_name, since /path_name
+means from root of the archive listed).
+
+If permissions do not start with l, but number of links is greater than one,
+then it says that this file should be a hardlinked with the other file.
+
+The result of list command must not contain "." and ".." items.
+
+* Command: copyout archivename storedfilename extractto
+
+This should extract from archive archivename the file called
+storedfilename (possibly with path if not located in archive's root
+[this is wrong. current extfs strips paths! -- pavel@ucw.cz])
+to file extractto.
+
+* Command: copyin archivename storedfilename sourcefile
+
+This should add to the archivename the sourcefile with the name
+storedfilename inside the archive.
+
+Important note: archivename in the above examples may not have the
+extension you are expecting to have, like it may happen that
+archivename will be something like /tmp/f43513254 or just
+anything. Some archivers do not like it, so you'll have to find some
+workaround.
+
+* Command: rm archivename storedfilename
+
+This should remove storedfilename from archivename.
+
+* Command: mkdir archivename dirname
+
+This should create a new directory called dirname inside archivename.
+
+* Command: rmdir archivename dirname
+
+This should remove an existing directory dirname. If the directory is
+not empty, mc will recursively delete it (possibly prompting).
+
+* Command: run
+
+Undocumented :-)
+
+---------------------------------------------------------
+
+Don't forget to mark this file executable (chmod 755 ThisFile, for example)
+
+For skeleton structure of executable, look at some of filesystems
+similar to yours.
+
+---------------------------------------------------------
+
+In constructing these routines, errors will be made, and mc will not display
+a malformed printing line. That can lead the programmer down many false
+trails in search of the bug. Since this routine is an executable shell script
+it can be run from the command line independently of mc, and its output will
+show on the console or can be redirected to a file.
+
+* Putting it to use
+----------------------------------------------------------
+The file .mc.ext in a home directory, and in mc's user directory (commonly
+/etc/mc), contains instructions for operations on files depending
+on filename extensions. It is well documented in other files in this
+distribution, so here are just a few notes specifically on use of the
+Virtual File System you just built.
+
+There are entries in .mc.ext defining a few operations that can be done on a
+file from an mc panel. Typically they are annotated with a hash mark and a
+file extension like this:
+
+# zip
+
+There must be a way to find the file by extension, so the next line does
+that. In essence it says "identify the string ".zip" or (|) ".ZIP" at the
+end ($) of a filename":
+
+regex/\.(zip|ZIP)$
+
+The operations themselves follow that. They must be indented by at least a
+space, and a tab works as well. In particular, the Open operation will
+now use your new virtual file system by cd'ing to it like this:
+
+ Open=%cd zip:%d/%p
+
+This is the line used when a file is highlighted in a panel and the user
+presses <Enter> or <Return>. The contents of the archive should show just
+as if they were in a real directory, and can be manipulated as such.
+The rest of the entry pertains to use of the F3 View key:
+
+ View=%view{ascii} unzip -v %f
+
+And perhaps an optional icon for X:
+
+ Icon=zip.xpm
+
+And perhaps an operation to extract the contents of the file, called from
+a menu selection:
+
+ Extract=unzip %f '*'
+
+This is just an example. The current entry for .zip files has a menu selection
+of 'Unzip' which could be used in place of 'Extract'. What goes here depends
+on what items you have in, or add to, the menu system, and that's another
+subject. The sum of this is the .mc.ext entry:
+
+# zip
+regex/\.(zip|ZIP)$
+ Open=%cd %p/uzip://
+ View=%view{ascii} unzip -v %f
+ Icon=zip.xpm
+ Extract=unzip %f '*'
+
+Add an entry like this to the .mc.ext file in a user's home directory, If you
+want others to have it, add it to the mc.ext file in the mc system directory,
+often /etc/mc/mc.ext. Notice this file is not prepended with a dot.
+
+Once all this is done, and things are in their proper places, exit mc if you
+were using it, and restart it so it picks up the new information.
+
+That's all there is to it. The hardest part is making a listing function
+that sorts the output of a system listing command and turns it into a form
+that mc can use. Currently awk (or gawk) is used because nearly all systems
+have it. If another scripting language is available, like perl, that could
+also be used.
diff --git a/src/vfs/extfs/helpers/README.extfs b/src/vfs/extfs/helpers/README.extfs
new file mode 100644
index 0000000..ce7d086
--- /dev/null
+++ b/src/vfs/extfs/helpers/README.extfs
@@ -0,0 +1,78 @@
+# Each external VFS type must be registered in extfs.d directory if you want to use it.
+# Trailing plus means that the filesystem is not tied to a certain file.
+
+# Popular PC archivers
+uzip
+uzoo
+ulha
+urar
+uha
+u7z
+ualz
+# FIXME: for arj usage you need a special patch to unarj (see unarj.diff)
+uarj
+uarc
+uace
+
+# For cab files
+ucab
+
+# ar is used for static libraries
+uar
+
+# Packages from popular Linux distributions
+rpm
+deb
+
+# a+ - mtools filesystem
+a+
+
+# For browsing lslR listings (found on many ftp sites)
+lslR
+
+# Hewlett Packard calculator
+hp48+
+
+# Commodore 64/128 d64/D64 files
+uc1541
+
+# Break patches into chunks
+patchfs
+
+# Represents a mailbox as a directory
+mailfs
+
+# List all installed RPM packages on the system
+rpms+
+trpm
+
+# dpkg frontend
+dpkg+
+debd
+
+# apt frontend
+apt+
+deba
+
+# Simple filesystem for audio cdroms. Use /dev/cdrom#audio (or /#audio)
+audio
+
+# Package of Bad Penguin (an Italian GNU/Linux distribution)
+bpp
+
+# ISO image
+iso9660
+
+# Amazon S3
+s3+
+
+# git frontend
+gitfs - browse the git repo
+changesetfs - list of versions of current file
+patchsetfs - list of patches of current file
+
+# Gputils lib archives.
+ulib
+
+# PAK Archive
+unar
diff --git a/src/vfs/extfs/helpers/a+.in b/src/vfs/extfs/helpers/a+.in
new file mode 100644
index 0000000..fe446f4
--- /dev/null
+++ b/src/vfs/extfs/helpers/a+.in
@@ -0,0 +1,126 @@
+#! @PERL@
+#
+# External filesystem for mc, using mtools
+# Written Ludek Brukner <lubr@barco.cz>, 1997
+# Much improved by Tom Perkins <968794022@noid.net>, 2000
+#
+# WARNING - This software is ALPHA - Absolutely NO WARRANTY
+#
+
+# These mtools components must be in PATH for this to work
+
+use warnings;
+
+sub quote {
+ $_ = shift(@_);
+ s/([^\w\/.+-])/\\$1/g;
+ return($_);
+}
+
+$mmd = "mmd";
+$mrd = "mrd";
+$mdel = "mdel";
+$mdir = "mdir -a";
+$mcopy = "mcopy -noQ";
+
+$0 =~ s|.*/||;
+$qdisk = quote($0);
+
+$ENV{MTOOLS_DATE_STRING} = "mm-dd-yyyy";
+$ENV{MTOOLS_TWENTY_FOUR_HOUR_CLOCK} = "1";
+
+SWITCH: for ( $ARGV[0] ) {
+ /list/ && do {
+ @dirs = get_dirs("");
+ while ($dir = shift(@dirs)) {
+ push @dirs, get_dirs("$dir/");
+ } exit 0; };
+ /mkdir/ && do {
+ shift; shift;
+ exit 1 if scalar(@ARGV) != 1;
+ $qname = quote($ARGV[0]);
+ system("$mmd $qdisk:/$qname >/dev/null");
+ exit 0; };
+ /rmdir/ && do {
+ shift; shift;
+ exit 1 if scalar(@ARGV) != 1;
+ $qname = quote($ARGV[0]);
+ system("$mrd $qdisk:/$qname >/dev/null");
+ exit 0; };
+ /rm/ && do {
+ shift; shift;
+ exit 1 if scalar(@ARGV) != 1;
+ $qname = quote($ARGV[0]);
+ system("$mdel $qdisk:/$qname >/dev/null");
+ exit 0; };
+ /copyout/ && do {
+ shift; shift;
+ exit 1 if scalar(@ARGV) != 2;
+ ( $qsrc, $qdest ) = @ARGV;
+ $qsrc = quote($qsrc);
+ $qdest = quote($qdest);
+ system("$mcopy $qdisk:/$qsrc $qdest >/dev/null");
+ exit 0; };
+ /copyin/ && do {
+ shift; shift;
+ exit 1 if scalar(@ARGV) != 2;
+ ( $qdest, $qsrc ) = @ARGV;
+ $qsrc = quote($qsrc);
+ $qdest = quote($qdest);
+ system("$mcopy $qsrc $qdisk:/$qdest >/dev/null");
+ exit 0; };
+ /.*/ && do { # an unfamiliar command
+ exit 1; };
+}
+
+sub get_dirs {
+ my ($path, $name, $size, $date, $time, $longname, @lst, @rv);
+ $path = shift(@_);
+ my $qpath = quote($path);
+ @rv = ();
+
+ open(FILE,"$mdir $qdisk:/$qpath |");
+ while ( <FILE> ) {
+ chomp();
+ /^ / && next; # ignore `non-file' lines
+ m{^Directory for $0:/}i && next; # ignore `non-file' lines
+ /^$/ && next; # ignore empty lines
+ /^\.\.?/ && next; # ignore `.' and `..'
+
+ $name = substr($_,0,12);
+ $name =~ s/^([^ ]*) +([^ ]+)[ \t]*$/$1.$2/;
+ $name =~ s/[ .]+$//;
+
+ $_ = substr($_,12);
+ s/^[ ]+//;
+
+ ($size,$date,$time,$longname) = split(/[ \t]+/, $_, 4);
+
+ defined $time || next;
+
+ # process "am" and "pm". Should not be needed if
+ # MTOOLS_TWENTY_FOUR_HOUR_CLOCK is respected.
+ @lst = split(/([:ap])/, $time);
+ $lst[0] += 12 if (defined $lst[3] && $lst[3] eq "p");
+
+ $time = sprintf("%02d:%02d", $lst[0], $lst[2]);
+ @lst = split(/-/, $date);
+ $lst[2] %= 100 if ($lst[2] > 100);
+ $date = sprintf ("%02d-%02d-%02d", @lst);
+
+ $name = $path . lc(($longname) ? $longname : $name);
+
+ if ($size =~ /DIR/) {
+ printf("drwxr-xr-x 1 %-8d %-8d %8d %s %s %s\n",
+ 0, 0, 0, $date, $time, $name);
+ push @rv, $name;
+ } else {
+ printf("-rw-r--r-- 1 %-8d %-8d %8d %s %s %s\n",
+ 0, 0, $size, $date, $time, $name);
+ }
+ }
+ close(FILE);
+ return @rv;
+}
+
+1;
diff --git a/src/vfs/extfs/helpers/apt+.in b/src/vfs/extfs/helpers/apt+.in
new file mode 100644
index 0000000..60011e6
--- /dev/null
+++ b/src/vfs/extfs/helpers/apt+.in
@@ -0,0 +1,359 @@
+#! @PERL@
+#
+# 1999 (c) Piotr Roszatycki <dexter@debian.org>
+# This software is under GNU license
+# last modification: 1999-12-08
+#
+# apt
+
+sub quote {
+ $_ = shift(@_);
+ s/([^\w\/.+-])/\\$1/g;
+ return($_);
+}
+
+sub bt
+{
+ my ($dt) = @_;
+ my (@time);
+ @time = localtime($dt);
+ $bt = sprintf "%02d-%02d-%d %02d:%02d", $time[4] + 1, $time[3],
+ $time[5] + 1900, $time[2], $time[1];
+ return $bt;
+}
+
+
+sub ft
+{
+ my ($f) = @_;
+ return "d" if -d $f;
+ return "l" if -l $f;
+ return "p" if -p $f;
+ return "S" if -S $f;
+ return "b" if -b $f;
+ return "c" if -c $f;
+ return "-";
+}
+
+sub fm
+{
+ my ($n) = @_;
+ my ($m);
+
+ if( $n & 0400 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0200 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 04000 ) {
+ $m .= "s";
+ } elsif( $n & 0100 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ if( $n & 0040 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0020 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 02000 ) {
+ $m .= "s";
+ } elsif( $n & 0010 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ if( $n & 0004 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0002 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 01000 ) {
+ $m .= "t";
+ } elsif( $n & 0001 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ return $m;
+}
+
+sub ls {
+ my ($file,$path,$mode) = @_;
+
+ if (-f $file) {
+ my @stat = stat(_);
+ # mode, nlink, uid, gid, size, mtime, filename
+ printf "%s %d %d %d %d %s %s\n", $mode || ft($file).fm($stat[2] & 07777),
+ $stat[3], $stat[4], $stat[5], $stat[7], bt($stat[9]), $path;
+ }
+}
+
+$DATE=bt(time());
+
+sub list
+{
+ my ($pkg, $fn, $dn, $sz, $bt);
+
+ my($check,$stats,$config);
+ chop($check = `apt-get -q check 2>/dev/null`);
+ chop($available = `apt-cache dumpavail 2>/dev/null`);
+ chop($stats = `apt-cache stats 2>/dev/null`);
+ chop($config = `apt-config dump 2>&1`);
+ $sz = length($check);
+ print "-r--r--r-- 1 root root $sz $DATE CHECK\n";
+ $sz = length($available);
+ print "-r--r--r-- 1 root root $sz $DATE AVAILABLE\n";
+ $sz = length($stats);
+ print "-r--r--r-- 1 root root $sz $DATE STATS\n";
+ $sz = length($config);
+ print "-r--r--r-- 1 root root $sz $DATE CONFIG\n";
+ $sz = length($pressupdate);
+ print "-r-xr--r-- 1 root root $sz $DATE UPDATE\n";
+ $sz = length($pressupgrade);
+ print "-r-xr--r-- 1 root root $sz $DATE UPGRADE\n";
+ print "-r-xr--r-- 1 root root $sz $DATE DIST-UPGRADE\n";
+
+ ls("/etc/apt/sources.list","sources.list");
+ ls('/etc/apt/apt.conf','apt.conf') if (-f '/etc/apt/apt.conf');
+
+ print "drwxr-xr-x 1 root root 0 $DATE all\n";
+
+ if ( open(PIPEIN, "find /var/cache/apt/archives -type f |") ) {
+ while(<PIPEIN>) {
+ chop;
+ next if /\/lock$/;
+ my $file = $_;
+ s%/var/cache/apt/archives/%CACHE/%;
+ ls($file, $_);
+ }
+ close PIPEIN;
+ }
+
+ my %sects = ();
+ my %debd = ();
+ my %deba = ();
+
+ open STAT, "/var/lib/dpkg/status"
+ or exit 1;
+ while( <STAT> ) {
+ chop;
+ if( /^([\w-]*): (.*)/ ) {
+ $pkg = $2 if( lc($1) eq 'package' );
+ $debd{$pkg}{lc($1)} = $2;
+ }
+ }
+ close STAT;
+
+ foreach $pkg (sort keys %debd) {
+ next if $debd{$pkg}{status} =~ /not-installed/;
+ $fn = $debd{$pkg}{package}. "_". $debd{$pkg}{version};
+ $dn = $debd{$pkg}{section};
+ if( ! $dn ) {
+ $dn = "unknown";
+ } elsif( $dn =~ /^(non-us)$/i ) {
+ $dn .= "/main";
+ } elsif( $dn !~ /\// ) {
+ $dn = "main/". $dn;
+ }
+ unless( $sects{$dn} ) {
+ my $sub = $dn;
+ while( $sub =~ s!^(.*)/[^/]*$!$1! ) {
+ unless( $sects{$sub} ) {
+ print "drwxr-xr-x 1 root root 0 $DATE $sub/\n";
+ $sects{$sub} = 1;
+ }
+ }
+ print "drwxr-xr-x 1 root root 0 $DATE $dn/\n";
+ $sects{$dn} = 1;
+ }
+ $sz = $debd{$pkg}{'status'} =~ /config-files/ ? 0 : $debd{$pkg}{'installed-size'} * 1024;
+ @stat = stat("/var/lib/dpkg/info/".$debd{$pkg}{package}.".list");
+ $bt = bt($stat[9]);
+ print "-rw-r--r-- 1 root root $sz $bt $dn/$fn.debd\n";
+ print "lrwxrwxrwx 1 root root $sz $bt all/$fn.debd -> ../$dn/$fn.debd\n";
+ }
+
+ open STAT, "apt-cache dumpavail |"
+ or exit 1;
+ while( <STAT> ) {
+ chop;
+ if( /^([\w-]*): (.*)/ ) {
+ $pkg = $2 if( lc($1) eq 'package' );
+ $deba{$pkg}{lc($1)} = $2;
+ }
+ }
+ close STAT;
+
+ foreach $pkg (sort keys %deba) {
+ next if $deba{$pkg}{version} eq $debd{$pkg}{version};
+ $fn = $deba{$pkg}{package}. "_". $deba{$pkg}{version};
+ $dn = $deba{$pkg}{section};
+ if( ! $dn ) {
+ $dn = "unknown";
+ } elsif( $dn =~ /^(non-us)$/i ) {
+ $dn .= "/main";
+ } elsif( $dn !~ /\// ) {
+ $dn = "main/". $dn;
+ }
+ unless( $sects{$dn} ) {
+ my $sub = $dn;
+ while( $sub =~ s!^(.*)/[^/]*$!$1! ) {
+ unless( $sects{$sub} ) {
+ print "drwxr-xr-x 1 root root 0 $DATE $sub/\n";
+ $sects{$sub} = 1;
+ }
+ }
+ print "drwxr-xr-x 1 root root 0 $DATE $dn/\n";
+ $sects{$dn} = 1;
+ }
+ $sz = $deba{$pkg}{'status'} =~ /config-files/ ? 0 : $deba{$pkg}{'installed-size'} * 1024;
+ print "-rw-r--r-- 1 root root $sz $DATE $dn/$fn.deba\n";
+ print "lrwxrwxrwx 1 root root $sz $DATE all/$fn.deba -> ../$dn/$fn.deba\n";
+ }
+}
+
+sub copyout
+{
+ my($archive,$filename) = @_;
+ my $qarchive = quote($archive);
+ my $qfilename = quote($filename);
+ if( $archive eq 'CHECK' ) {
+ system("apt-get -q check > $qfilename");
+ } elsif( $archive eq 'AVAILABLE' ) {
+ system("apt-cache dumpavail > $qfilename");
+ } elsif( $archive eq 'STATS' ) {
+ system("apt-cache stats > $qfilename");
+ } elsif( $archive eq 'CONFIG' ) {
+ system("(apt-config dump 2>&1) > $qfilename");
+ } elsif( $archive eq 'UPDATE' ) {
+ open O, ">$filename";
+ print O $pressupdate;
+ close O;
+ } elsif( $archive eq 'UPGRADE' || $archive eq 'DIST-UPGRADE' ) {
+ open O, ">$filename";
+ print O $pressupgrade;
+ close O;
+ } elsif( $archive eq 'apt.conf' ) {
+ system("cp /etc/apt/apt.conf $qfilename");
+ } elsif( $archive eq 'sources.list' ) {
+ system("cp /etc/apt/sources.list $qfilename");
+ } elsif( $archive =~ /^CACHE\// ) {
+ $archive =~ s%^CACHE/%/var/cache/apt/archives/%;
+ system("cp $qarchive $qfilename");
+ } else {
+ open O, ">$filename";
+ print O $archive, "\n";
+ close O;
+ }
+}
+
+sub copyin
+{
+ my($archive,$filename) = @_;
+ my $qarchive = quote($archive);
+ my $qfilename = quote($filename);
+ if( $archive =~ /\.deb$/ ) {
+ system("dpkg -i $qfilename>/dev/null");
+ } elsif( $archive eq 'apt.conf' ) {
+ system("cp $qfilename /etc/apt/apt.conf");
+ } elsif( $archive eq 'sources.list' ) {
+ system("cp $qfilename /etc/apt/sources.list");
+ } elsif( $archive =~ /^CACHE\// ) {
+ $qarchive =~ s%^CACHE/%/var/cache/apt/archives/%;
+ system("cp $qfilename $qarchive");
+ } else {
+ die "extfs: cannot create regular file \`$archive\': Permission denied\n";
+ }
+}
+
+sub run
+{
+ my($archive,$filename) = @_;
+ if( $archive eq 'UPDATE' ) {
+ system("apt-get update");
+ } elsif( $archive eq 'UPGRADE' ) {
+ system("apt-get upgrade -u");
+ } elsif( $archive eq 'DIST-UPGRADE' ) {
+ system("apt-get dist-upgrade -u");
+ } else {
+ die "extfs: $archive: command not found\n";
+ }
+}
+
+sub rm
+{
+ my($archive) = @_;
+ my $qarchive = quote($archive);
+ if( $archive =~ /^CACHE\// ) {
+ $qarchive =~ s%^CACHE/%/var/cache/apt/archives/%;
+ system("rm -f $qarchive");
+ } elsif( $archive eq 'apt.conf' ) {
+ system("rm -f /etc/apt/apt.conf");
+ } elsif( $archive eq 'sources.list' ) {
+ system("rm -f /etc/apt/sources.list");
+ } elsif( $archive =~ /\.debd?$/ ) {
+ # uncommented and changed to use dpkg - alpha
+ my $qname = $qarchive;
+ $qname =~ s%.*/%%g;
+ $qname =~ s%_.*%%g;
+ system("dpkg --remove $qname >/dev/null");
+ die("extfs: $archive: Operation not permitted\n") if $? != 0;
+ } else {
+ die "extfs: $archive: Operation not permitted\n";
+ }
+}
+
+
+$pressupdate=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you don't want to retrieve new lists of packages.
+ ==========================================================================
+
+This is not a real file. It is a way to retrieve new lists of packages.
+To update this information go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressupgrade=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to perform an upgrade.
+ ===================================================================
+
+This is not a real file. It is a way to perform an upgrade.
+To upgrade this information go back to the panel and press Enter on this file.
+
+EOInstall
+
+
+# override any locale for dates
+$ENV{"LC_ALL"}="C";
+
+if ($ARGV[0] eq "list") { list(); exit(0); }
+elsif ($ARGV[0] eq "copyout") { copyout($ARGV[2], $ARGV[3]); exit(0); }
+elsif ($ARGV[0] eq "copyin") { copyin($ARGV[2], $ARGV[3]); exit(0); }
+elsif ($ARGV[0] eq "run") { run($ARGV[2]); exit(0); }
+elsif ($ARGV[0] eq "rm") { rm($ARGV[2]); exit(0); }
+exit(1);
+
diff --git a/src/vfs/extfs/helpers/audio.in b/src/vfs/extfs/helpers/audio.in
new file mode 100755
index 0000000..05c8c65
--- /dev/null
+++ b/src/vfs/extfs/helpers/audio.in
@@ -0,0 +1,53 @@
+#! /bin/sh
+#
+# Written by Pavel Machek
+# CDDB support by Adam Byrtek
+#
+# (C) 2000 The Free Software Foundation.
+#
+
+set -e
+
+CDDB_SERVER="http://freedb.freedb.org"
+CDDB_HANDSHAKE="hello=user+localhost+mc+1.0&proto=1"
+CDDB_TIMEOUT=20 # in seconds
+
+audiofs_list()
+{
+ DATE=`date +"%b %d %H:%M"`
+ echo "-r--r--r-- 1 0 0 0 $DATE CDDB"
+ cdparanoia -Q -d "$1" 2>&1 | grep '^[ 0-9][ 0-9][ 0-9]\.' | while read A B C
+ do
+ A=`echo "$A" | sed -e 's/\.//' -e 's/^\(.\)$/0\1/'`
+ SIZE=`expr 44 + $B \* 2352`
+ echo "-r--r--r-- 1 0 0 $SIZE $DATE track-${A}.wav"
+ done
+}
+
+audiofs_copyout()
+{
+ if [ x"$2" = x"CDDB" ]; then
+ DISCID=`cd-discid "$1" | tr " " "+"`
+ if [ -z "$DISCID" ]; then
+ exit 1
+ fi
+ RESPONSE=`wget -q -T $CDDB_TIMEOUT -O - "$CDDB_SERVER/~cddb/cddb.cgi?cmd=cddb+query+$DISCID&$CDDB_HANDSHAKE" | tee "$3" | @AWK@ '/^200/ { print $2,$3; }'`
+ wget -q -T $CDDB_TIMEOUT -O - "$CDDB_SERVER/~cddb/cddb.cgi?cmd=cddb+read+$RESPONSE&$CDDB_HANDSHAKE" | grep -v "^#" >> "$3"
+ else
+ TRACK=`echo "$2" | sed 's/track-0*//' | sed 's/\.wav//'`
+ cdparanoia -q -d "$1" "$TRACK" "$3" >/dev/null
+ fi
+}
+
+if [ ! -b "$2" ]
+then
+ BASE="/dev/cdrom"
+else
+ BASE="$2"
+fi
+
+case "$1" in
+ list) audiofs_list "$BASE"; exit 0;;
+ copyout) audiofs_copyout "$BASE" "$3" "$4"; exit 0;;
+esac
+exit 1
diff --git a/src/vfs/extfs/helpers/bpp b/src/vfs/extfs/helpers/bpp
new file mode 100755
index 0000000..f71fe7e
--- /dev/null
+++ b/src/vfs/extfs/helpers/bpp
@@ -0,0 +1,50 @@
+#! /bin/sh
+#
+# Written by Marco Ciampa 2000
+# (a simple cut & paste from rpm vfs)
+# (C) 1996 The Free Software Foundation.
+#
+# Package of a new italian distribution: Bad Penguin
+# http://www.badpenguin.org/
+
+# override any locale for dates
+unset LC_ALL
+LC_TIME=C
+export LC_TIME
+
+mcbppfs_list ()
+{
+ FILEPREF="-r--r--r-- 1 root root "
+ FIEXPREF="-r-xr-xr-x 1 root root "
+ DATE=`date +"%b %d %H:%M"`
+ set x `ls -l "$1"`
+ size=$6
+ echo "$FILEPREF $size $DATE CONTENTS.tar.gz"
+ echo "$FIEXPREF 35 $DATE INSTALL"
+ echo "$FIEXPREF 35 $DATE UPGRADE"
+}
+
+mcbppfs_copyout ()
+{
+ case "$2" in
+ CONTENTS.tar.gz) cat "$1" > "$3"; exit 0;;
+ INSTALL) echo "# Run this to install this package" > "$3"; exit 0;;
+ UPGRADE) echo "# Run this to upgrade this package" > "$3"; exit 0;;
+ esac
+}
+
+mcbppfs_run ()
+{
+ case "$2" in
+ INSTALL) echo "Installing \"$1\""; package-setup --install "$1"; exit 0;;
+ UPGRADE) echo "Upgrading \"$1\""; package-setup --update "$1"; exit 0;;
+ esac
+}
+
+umask 077
+case "$1" in
+ list) mcbppfs_list "$2"; exit 0;;
+ copyout) mcbppfs_copyout "$2" "$3" "$4"; exit 0;;
+ run) mcbppfs_run "$2" "$3"; exit 1;;
+esac
+exit 1
diff --git a/src/vfs/extfs/helpers/changesetfs b/src/vfs/extfs/helpers/changesetfs
new file mode 100755
index 0000000..eebdf8c
--- /dev/null
+++ b/src/vfs/extfs/helpers/changesetfs
@@ -0,0 +1,109 @@
+#!/bin/sh
+
+LANG=C
+export LANG
+LC_TIME=C
+export LC_TIME
+
+# --- GIT -----------------------------------------------------------------------
+
+found_git_dir()
+{
+ work_dir=$1
+ while [ -n "$work_dir" -a "$work_dir" != "/" ]; do
+ [ -d "${work_dir}/.git" ] && {
+ echo "${work_dir}/.git/"
+ return
+ }
+ work_dir=`dirname "$work_dir"`
+ done
+ echo ''
+}
+
+changesetfs_list_git()
+{
+ WORK_DIR=$1; shift
+ fname=$1; shift
+ USER=$1; shift
+ DATE=$1; shift
+
+ GIT_DIR=`found_git_dir "$WORK_DIR"`
+ [ -z "$GIT_DIR" ] && GIT_DIR=$WORK_DIR
+ curr_year=`date +"%Y"`
+
+ git --git-dir="$GIT_DIR" log --abbrev=7 --pretty="format:%at %h %an" -- "$fname" | while read TIMESTAMP chset author
+ do
+ year=`date -d @"$TIMESTAMP" +"%Y"`
+ [ "$year" = "$curr_year" ] && {
+ DATE=`date -d @"$TIMESTAMP" +"%b %d %H:%M"`
+ } || {
+ DATE=`date -d @"$TIMESTAMP" +"%b %d %Y"`
+ }
+ NAME="$chset $author"
+ echo "-rw-rw-rw- 1 $USER 0 0 $DATE $NAME `basename $fname`"
+ done
+}
+
+changesetfs_copyout_git()
+{
+ WORK_DIR=$1; shift
+ fname=$1; shift
+ orig_fname=$1;shift
+ output_fname=$1;shift
+
+ chset=`echo "$orig_fname"| cut -f 1 -d " "`
+
+ GIT_DIR=`found_git_dir "$WORK_DIR"`
+ [ -z "$GIT_DIR" ] && GIT_DIR=$WORK_DIR
+
+ filecommit=`git --git-dir="$GIT_DIR" show --raw --pretty=tformat:%h "$chset" -- "$fname"| \
+ tail -n1 | \
+ sed 's@^::[0-9]*\s*[0-9]*\s*[0-9]*\s*@@' | \
+ sed 's@^:[0-9]*\s*[0-9]*\s*@@' | \
+ cut -d'.' -f 1`
+ git --git-dir="$GIT_DIR" show "$filecommit" > "$output_fname"
+}
+
+# --- COMMON --------------------------------------------------------------------
+
+changesetfs_list()
+{
+ VCS_type=$1; shift
+ WORK_DIR=$1; shift
+ fname=$1; shift
+
+ DATE=`date +"%b %d %H:%M"`
+ USER=`whoami`
+
+ case "$VCS_type" in
+ git) changesetfs_list_git "$WORK_DIR" "$fname" "$USER" "$DATE" ;;
+ esac
+}
+
+changesetfs_copyout()
+{
+ VCS_type=$1; shift
+ WORK_DIR=$1; shift
+ fname=$1; shift
+
+ case "$VCS_type" in
+ git) changesetfs_copyout_git "$WORK_DIR" "$fname" "$@" ;;
+ esac
+
+}
+
+# --- MAIN ----------------------------------------------------------------------
+
+command=$1; shift
+tmp_file=$1; shift
+
+WORK_DIR=`head -n1 $tmp_file`
+fname=`tail -n2 $tmp_file | head -n1`
+VCS_type=`tail -n1 $tmp_file`
+
+case "$command" in
+ list) changesetfs_list "$VCS_type" "$WORK_DIR" "$fname" ;;
+ copyout) changesetfs_copyout "$VCS_type" "$WORK_DIR" "$fname" "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/deb.in b/src/vfs/extfs/helpers/deb.in
new file mode 100644
index 0000000..abc98aa
--- /dev/null
+++ b/src/vfs/extfs/helpers/deb.in
@@ -0,0 +1,203 @@
+#! @PERL@
+#
+# Written by Fernando Alegre <alegre@debian.org> 1996
+#
+# Applied patch by Dimitri Maziuk <emaziuk@curtin.edu.au> 1997
+# (to handle new tar format)
+#
+# Modified by Fernando Alegre <alegre@debian.org> 1997
+# (to handle both new and old tar formats)
+#
+# Modified by Patrik Rak <prak@post.cz> 1998
+# (add by Michael Bramer Debian-mc-maintainer <grisu@debian.org>)
+# (to allow access to package control files)
+#
+# Modified by Martin Bialasinski <martinb@debian.org> 1999
+# (deal with change in tar format)
+#
+#
+# Copyright (C) 1997 Free Software Foundation
+#
+
+sub quote {
+ $_ = shift(@_);
+ s/([^\w\/.+-])/\\$1/g;
+ return($_);
+}
+
+sub mcdebfs_list
+{
+#
+# CAVEAT: Hard links are listed as if they were symlinks
+# Empty directories do not appear at all
+#
+ local($archivename)=@_;
+ local $qarchivename = quote($archivename);
+ chop($date=`LC_ALL=C date "+%b %d %H:%M"`);
+ chop($info_size=`dpkg -I $qarchivename | wc -c`);
+ $install_size=length($pressinstall);
+
+ print "dr-xr-xr-x 1 root root 0 $date CONTENTS\n";
+ print "dr-xr-xr-x 1 root root 0 $date DEBIAN\n";
+ print "-r--r--r-- 1 root root $info_size $date INFO\n";
+ print "-r-xr--r-- 1 root root $install_size $date INSTALL\n";
+
+ if ( open(PIPEIN, "LC_ALL=C dpkg-deb -c $qarchivename |") )
+ {
+ while(<PIPEIN>)
+ {
+ @_ = split;
+
+ $perm=$_[0]; $owgr=$_[1]; $size=$_[2];
+ if($_[3] =~ /^\d\d\d\d\-/) { # New tar format
+
+ ($year,$mon,$day) = split(/-/,$_[3]);
+ $month = ("Gee","Jan","Feb","Mar","Apr","May","Jun",
+ "Jul","Aug","Sep","Oct","Nov","Dec")[$mon] || "Gee";
+ $time=$_[4];
+ $pathindex=5;
+ }
+ else {
+ $mstring='GeeJanFebMarAprMayJunJulAugSepOctNovDec';
+ $month=$_[3];
+ $mon=index($mstring,$month) / 3;
+ $day=$_[4];
+ $time=$_[5];
+ $year=$_[6];
+ $pathindex=7;
+ }
+
+ $path=$_[$pathindex++];
+ # remove leading ./
+ $path=~s/^\.\///;
+ next if ($path eq '');
+ $arrow=$_[$pathindex++];
+ $link=$_[$pathindex++];
+ $link2=$_[$pathindex++];
+
+ $owgr=~s!/! !;
+ if($arrow eq 'link')
+ {
+# report hard links as soft links
+ $arrow='->'; $link="/$link2";
+ substr($perm, 0, 1) = "l";
+ }
+ if($arrow ne '')
+ {
+ $arrow=' ' . $arrow;
+ $link= ' ' . $link;
+ }
+ $now=`date "+%Y %m"`;
+ ($thisyear, $thismon) = split(/ /, $now);
+ # show time for files younger than 6 months
+ # but not for files with dates in the future
+ if ($year * 12 + $mon > $thisyear * 12 + $thismon - 6 &&
+ $year * 12 + $mon <= $thisyear * 12 + $thismon) {
+ print "$perm 1 $owgr $size $month $day $time CONTENTS/$path$arrow$link\n";
+ } else {
+ print "$perm 1 $owgr $size $month $day $year CONTENTS/$path$arrow$link\n";
+ }
+ }
+ }
+ if ( open(PIPEIN, "LC_ALL=C dpkg-deb -I $qarchivename |") )
+ {
+ while(<PIPEIN>)
+ {
+ @_ = split;
+ $size=$_[0];
+ last if $size =~ /:/;
+ next if $size !~ /\d+/;
+ if($_[4] eq '*')
+ {
+ $perm='-r-xr-xr-x';
+ $name=$_[5];
+ }
+ else
+ {
+ $perm='-r--r--r--';
+ $name=$_[4];
+ }
+ print "$perm 1 root root $size $date DEBIAN/$name\n";
+ }
+ }
+}
+
+sub mcdebfs_copyout
+{
+ local($archive,$filename,$destfile)=@_;
+ local $qarchive = quote($archive);
+ local $qfilename = quote($filename);
+ local $qdestfile = quote($destfile);
+
+ if($filename eq "INFO")
+ {
+ system("dpkg-deb -I $qarchive > $qdestfile");
+ }
+ elsif($filename =~ /^DEBIAN/)
+ {
+ $qfilename=~s!^DEBIAN/!!;
+ system("dpkg-deb -I $qarchive $qfilename > $qdestfile");
+ }
+ elsif($filename eq "INSTALL")
+ {
+ if ( open(FILEOUT,">$destfile") )
+ {
+ print FILEOUT $pressinstall;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ }
+ else
+ {
+ # files can be prepended with ./ or not, depending on the version of tar
+ $qfilename=~s!^CONTENTS/!!;
+ system("dpkg-deb --fsys-tarfile $qarchive | tar xOf - $qfilename ./$qfilename > $qdestfile 2>/dev/null");
+ }
+}
+
+sub mcdebfs_run
+{
+ local($archive,$filename)=@_;
+ local $qarchive = quote($archive);
+ if($filename eq "INSTALL")
+ {
+ print "Installing $archive\n";
+ system("dpkg -i $qarchive");
+ }
+ else
+ {
+ use File::Temp qw(mkdtemp);
+ my $template = "/tmp/mcdebfs.run.XXXXXX";
+ $template="$ENV{MC_TMPDIR}/mcdebfs.XXXXXX" if ($ENV{MC_TMPDIR});
+ $tmpdir = mkdtemp($template);
+ $tmpcmd="$tmpdir/run";
+ &mcdebfs_copyout($archive, $filename, $tmpcmd);
+ system("chmod u+x $tmpcmd");
+ system($tmpcmd);
+ unlink($tmpcmd);
+ rmdir($tmpdir);
+ }
+}
+
+$pressinstall=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to reinstall everything...
+
+This is not a real file. It is a way to install the package you are browsing.
+
+To install this package go back to the panel and press Enter on this file.
+
+In Debian systems, a package is automatically upgraded when you install a new
+version of it. There is no special upgrade option. Install always works.
+
+EOInstall
+
+umask 077;
+
+if($ARGV[0] eq "list") { shift; &mcdebfs_list(@ARGV); exit 0; }
+elsif($ARGV[0] eq "copyout") { shift; &mcdebfs_copyout(@ARGV); exit 0; }
+elsif($ARGV[0] eq "run") { shift; &mcdebfs_run(@ARGV); exit 0; }
+
+exit 1;
+
diff --git a/src/vfs/extfs/helpers/deba.in b/src/vfs/extfs/helpers/deba.in
new file mode 100644
index 0000000..3d1a552
--- /dev/null
+++ b/src/vfs/extfs/helpers/deba.in
@@ -0,0 +1,107 @@
+#! @PERL@
+#
+# 1999 (c) Piotr Roszatycki <dexter@debian.org>
+# This software is under GNU license
+# last modification: 1999-12-08
+#
+# deba
+
+sub quote {
+ $_ = shift(@_);
+ s/([^\w\/.+-])/\\$1/g;
+ return($_);
+}
+
+sub list
+{
+ my($qarchive)=@_;
+ $qarchive = quote($qarchive);
+ chop($date=`LC_ALL=C date "+%m-%d-%Y %H:%M"`);
+ chop($info_size=`apt-cache show $qarchive | wc -c`);
+ $install_size=length($pressinstall);
+ $upgrade_size=length($pressupgrade);
+
+ print "-r--r--r-- 1 root root $info_size $date INFO\n";
+
+ chop($debd = `dpkg -s $qarchive | grep -i ^Version | sed 's/^version: //i'`);
+ chop($deba = `apt-cache show $qarchive | grep -i ^Version | sed 's/^version: //i'`);
+ if( ! $debd ) {
+ print "-r-xr--r-- 1 root root $install_size $date INSTALL\n";
+ } elsif( $debd ne $deba ) {
+ print "-r-xr--r-- 1 root root $upgrade_size $date UPGRADE\n";
+ }
+}
+
+sub copyout
+{
+ my($archive,$filename,$destfile)=@_;
+ my $qarchive = quote($archive);
+ my $qdestfile = quote($destfile);
+ if($filename eq "INFO") {
+ system("apt-cache show $qarchive > $qdestfile");
+ } elsif($filename eq "INSTALL") {
+ if ( open(FILEOUT, "> $destfile") ) {
+ print FILEOUT $pressinstall;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ } elsif($filename eq "UPGRADE") {
+ if ( open(FILEOUT, ">, $destfile") ) {
+ print FILEOUT $pressupgrade;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ } else {
+ die "extfs: $filename: No such file or directory\n";
+ }
+}
+
+sub run
+{
+ my($archive,$filename)=@_;
+ my $qarchive = quote($archive);
+ if($filename eq "INSTALL") {
+ system("apt-get install $qarchive");
+ } elsif($filename eq "UPGRADE") {
+ system("apt-get install $qarchive");
+ } else {
+ die "extfs: $filename: Permission denied\n";
+ }
+}
+
+$pressinstall=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to install this package...
+
+This is not a real file. It is a way to install the package you are browsing.
+
+To install this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressupgrade=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to upgrade this package...
+
+This is not a real file. It is a way to upgrade the package you are browsing.
+
+To upgrade this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+
+umask 077;
+
+chop($name = `if [ -f "$ARGV[1]" ]; then cat $ARGV[1]; else echo $ARGV[1]; fi`);
+$name =~ s%.*/([0-9a-z.-]*)_.*%$1%;
+
+exit 1 unless $name;
+
+if($ARGV[0] eq "list") { &list($name); exit 0; }
+elsif($ARGV[0] eq "copyout") { &copyout($name,$ARGV[2],$ARGV[3]); exit 0; }
+elsif($ARGV[0] eq "run") { &run($name,$ARGV[2]); exit 0; }
+
+exit 1;
+
diff --git a/src/vfs/extfs/helpers/debd.in b/src/vfs/extfs/helpers/debd.in
new file mode 100644
index 0000000..858dadd
--- /dev/null
+++ b/src/vfs/extfs/helpers/debd.in
@@ -0,0 +1,362 @@
+#! @PERL@
+#
+# 1999 (c) Piotr Roszatycki <dexter@debian.org>
+# This software is under GNU license
+# last modification: 1999-12-08
+#
+# debd
+
+sub quote {
+ $_ = shift(@_);
+ s/([^\w\/.+-])/\\$1/g;
+ return($_);
+}
+
+sub bt
+{
+ my ($dt) = @_;
+ my (@time);
+ @time = localtime($dt);
+ $bt = sprintf "%02d-%02d-%d %02d:%02d", $time[4] + 1, $time[3],
+ $time[5] + 1900, $time[2], $time[1];
+ return $bt;
+}
+
+
+sub ft
+{
+ my ($f) = @_;
+ return "d" if -d $f;
+ return "l" if -l $f;
+ return "p" if -p $f;
+ return "S" if -S $f;
+ return "b" if -b $f;
+ return "c" if -c $f;
+ return "-";
+}
+
+sub fm
+{
+ my ($n) = @_;
+ my ($m);
+
+ if( $n & 0400 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0200 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 04000 ) {
+ $m .= "s";
+ } elsif( $n & 0100 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ if( $n & 0040 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0020 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 02000 ) {
+ $m .= "s";
+ } elsif( $n & 0010 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ if( $n & 0004 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0002 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 01000 ) {
+ $m .= "t";
+ } elsif( $n & 0001 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ return $m;
+}
+
+sub ls {
+ my ($file) = @_;
+ my @stat = stat($file);
+ # mode, nlink, uid, gid, size, mtime, filename
+ printf "%s%s %d %d %d %d %s CONTENTS%s\n", ft($file), fm($stat[2] & 07777),
+ $stat[3], $stat[4], $stat[5], $stat[7], bt($stat[9]), $file;
+}
+
+sub list
+{
+ my($archive)=@_;
+ my $qarchive = quote($archive);
+ chop($date=`LC_ALL=C date "+%m-%d-%Y %H:%M"`);
+ chop($info_size=`dpkg -s $qarchive | wc -c`);
+ $repack_size=length($pressrepack);
+ $reinstall_size=length($pressreinstall);
+ $remove_size=length($pressremove);
+ $purge_size=length($presspurge);
+ $reconfigure_size=length($pressreconfigure);
+ $reinstall_size=length($pressreinstall);
+ $select_size=length($pressselect);
+ $unselect_size=length($pressunselect);
+
+ print "dr-xr-xr-x 1 root root 0 $date CONTENTS\n";
+ print "dr-xr-xr-x 1 root root 0 $date DEBIAN\n";
+ print "-r--r--r-- 1 root root $info_size $date INFO\n";
+ print "-r-xr--r-- 1 root root $purge_size $date DPKG-PURGE\n";
+
+ chop($status = `dpkg -s $qarchive | grep ^Status`);
+ if( $status =~ /deinstall/ ) {
+ print "-r-xr--r-- 1 root root $select_size $date DPKG-SELECT\n";
+ } elsif( $status =~ /install/ ) {
+ print "-r-xr--r-- 1 root root $unselect_size $date DPKG-UNSELECT\n";
+ }
+ if( $status !~ /config-files/ ) {
+ if ( -x "/usr/bin/dpkg-repack" ) {
+ print "-r-xr--r-- 1 root root $repack_size $date DPKG-REPACK\n";
+ }
+ print "-r-xr--r-- 1 root root $remove_size $date DPKG-REMOVE\n";
+ if ( -x "/usr/bin/apt-get" ) {
+ print "-r-xr--r-- 1 root root $remove_size $date APT-REMOVE\n";
+ print "-r-xr--r-- 1 root root $reinstall_size $date APT-REINSTALL\n";
+ print "-r-xr--r-- 1 root root $purge_size $date APT-PURGE\n";
+ }
+ }
+ if( -x "/usr/bin/dpkg-reconfigure" && -x "/var/lib/dpkg/info/$archive.config" ) {
+ print "-r-xr--r-- 1 root root $reconfigure_size $date DPKG-RECONFIGURE\n";
+ }
+
+
+
+ if ( open(PIPEIN, "LC_TIME=C LANG=C ls -l /var/lib/dpkg/info/$qarchive.* |") ) {
+ while(<PIPEIN>) {
+ chop;
+ next if /\.list$/;
+ s%/var/lib/dpkg/info/$archive.%DEBIAN/%;
+ print $_, "\n";
+ }
+ close PIPEIN;
+ }
+
+ if ( open(LIST, "/var/lib/dpkg/info/$archive.list") ) {
+ while(<LIST>) {
+ chop;
+ ls($_);
+ }
+ close LIST;
+ }
+}
+
+sub copyout
+{
+ my($archive,$filename,$destfile)=@_;
+ my $qarchive = quote($archive);
+ my $qfilename = quote($filename);
+ my $qdestfile = quote($destfile);
+
+ if($filename eq "INFO") {
+ system("dpkg -s $qarchive > $qdestfile");
+ } elsif($filename eq "DPKG-REPACK") {
+ if ( open(FILEOUT,">$destfile") ) {
+ print FILEOUT $pressrepack;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ } elsif($filename =~ /^DEBIAN/) {
+ $qfilename=~s!^DEBIAN/!!;
+ system("cat /var/lib/dpkg/info/$qarchive.$qfilename > $qdestfile");
+ } elsif($filename eq "DPKG-REMOVE" || $filename eq "APT-REMOVE") {
+ if ( open(FILEOUT,">$destfile") ) {
+ print FILEOUT $pressremove;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ } elsif($filename eq "DPKG-PURGE" || $filename eq "APT-PURGE") {
+ if ( open(FILEOUT,">$destfile") ) {
+ print FILEOUT $presspurge;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ } elsif($filename eq "DPKG-RECONFIGURE") {
+ if ( open(FILEOUT,">$destfile") ) {
+ print FILEOUT $pressreconfigure;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ } elsif($filename eq "APT-REINSTALL") {
+ if ( open(FILEOUT,">$destfile") ) {
+ print FILEOUT $pressreinstall;
+ close FILEOUT;
+ system("chmod a+x $destfile");
+ }
+ } elsif($filename eq "DPKG-SELECT") {
+ if ( open(FILEOUT,">$destfile") ) {
+ print FILEOUT $pressselect;
+ close FILEOUT;
+ system("chmod a+x $destfile");
+ }
+ } elsif($filename eq "DPKG-UNSELECT") {
+ if ( open(FILEOUT,">$destfile") ) {
+ print FILEOUT $pressunselect;
+ close FILEOUT;
+ system("chmod a+x $qdestfile");
+ }
+ } else {
+ $qfilename=~s!^CONTENTS!!;
+ system("cat $qfilename > $qdestfile");
+ }
+}
+
+sub run
+{
+ my($archive,$filename)=@_;
+ my $qarchive = quote($archive);
+ my $qfilename = quote($filename);
+ if($filename eq "DPKG-REMOVE") {
+ system("dpkg --remove $qarchive");
+ } elsif($filename eq "APT-REMOVE") {
+ system("apt-get remove $qarchive");
+ } elsif($filename eq "DPKG-PURGE") {
+ system("dpkg --purge $qarchive");
+ } elsif($filename eq "APT-PURGE") {
+ system("apt-get --purge remove $qarchive");
+ } elsif($filename eq "DPKG-REPACK") {
+ system("dpkg-repack $qarchive");
+ } elsif($filename eq "DPKG-SELECT") {
+ system("echo $aqrchive install | dpkg --set-selections");
+ } elsif($filename eq "DPKG-UNSELECT") {
+ system("echo $qarchive deinstall | dpkg --set-selections");
+ } elsif($filename eq "APT-REINSTALL") {
+ system("apt-get -u --reinstall install $qarchive");
+ } elsif($filename eq "DPKG-RECONFIGURE") {
+ system("dpkg-reconfigure $qarchive");
+ } elsif($filename=~/^DEBIAN/) {
+ $qfilename=~s!^DEBIAN!!;
+ system("/var/lib/dpkg/info/$qarchive.$qfilename");
+ } else {
+ $qfilename=~s!^CONTENTS!!;
+ system($qfilename);
+ }
+}
+
+$pressrepack=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to repack this package...
+
+This is not a real file. It is a way to repack the package you are browsing.
+
+To repack this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressreinstall=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to reinstall this package...
+
+This is not a real file. It is a way to reinstall the package you are browsing.
+
+To reinstall this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressremove=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to remove this package...
+
+This is not a real file. It is a way to remove the package you are browsing.
+
+To remove this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$presspurge=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to purge this package...
+
+This is not a real file. It is a way to purge the package you are browsing.
+
+To purge this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressreconfigure=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to reconfigure this package...
+
+This is not a real file. It is a way to reconfigure the package you are browsing.
+
+To reconfigure this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressreinstall=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to reinstall this package...
+
+This is not a real file. It is a way to reinstall the package you are browsing.
+
+To reinstall this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressselect=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to select this package...
+
+This is not a real file. It is a way to select the package you are browsing.
+
+To select this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressunselect=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to unselect this package...
+
+This is not a real file. It is a way to unselect the package you are browsing.
+
+To unselect this package go back to the panel and press Enter on this file.
+
+EOInstall
+
+umask 077;
+
+chop($name = `if [ -f "$ARGV[1]" ]; then cat $ARGV[1]; else echo $ARGV[1]; fi`);
+$name =~ s%.*/([0-9a-z.-]*)_.*%$1%;
+
+exit 1 unless $name;
+
+if($ARGV[0] eq "list") { &list($name); exit 0; }
+elsif($ARGV[0] eq "copyout") { &copyout($name,$ARGV[2],$ARGV[3]); exit 0; }
+elsif($ARGV[0] eq "run") { &run($name,$ARGV[2]); exit 0; }
+
+exit 1;
+
diff --git a/src/vfs/extfs/helpers/dpkg+.in b/src/vfs/extfs/helpers/dpkg+.in
new file mode 100644
index 0000000..048862e
--- /dev/null
+++ b/src/vfs/extfs/helpers/dpkg+.in
@@ -0,0 +1,337 @@
+#! @PERL@
+#
+# 1999 (c) Piotr Roszatycki <dexter@debian.org>
+# This software is under GNU license
+# last modification: 1999-12-08
+#
+# dpkg
+
+sub quote {
+ $_ = shift(@_);
+ s/([^\w\/.+-])/\\$1/g;
+ return($_);
+}
+
+sub bt
+{
+ my ($dt) = @_;
+ my (@time);
+ @time = localtime($dt);
+ $bt = sprintf "%02d-%02d-%d %02d:%02d", $time[4] + 1, $time[3],
+ $time[5] + 1900, $time[2], $time[1];
+ return $bt;
+}
+
+
+sub ft
+{
+ my ($f) = @_;
+ return "d" if -d $f;
+ return "l" if -l $f;
+ return "p" if -p $f;
+ return "S" if -S $f;
+ return "b" if -b $f;
+ return "c" if -c $f;
+ return "-";
+}
+
+sub fm
+{
+ my ($n) = @_;
+ my ($m);
+
+ if( $n & 0400 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0200 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 04000 ) {
+ $m .= "s";
+ } elsif( $n & 0100 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ if( $n & 0040 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0020 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 02000 ) {
+ $m .= "s";
+ } elsif( $n & 0010 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ if( $n & 0004 ) {
+ $m .= "r";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 0002 ) {
+ $m .= "w";
+ } else {
+ $m .= "-";
+ }
+ if( $n & 01000 ) {
+ $m .= "t";
+ } elsif( $n & 0001 ) {
+ $m .= "x";
+ } else {
+ $m .= "-";
+ }
+
+ return $m;
+}
+
+sub ls {
+ my ($file,$path,$mode) = @_;
+
+ if (-f $file) {
+ my @stat = stat(_);
+ # mode, nlink, uid, gid, size, mtime, filename
+ printf "%s %d %d %d %d %s %s\n", $mode || ft($file).fm($stat[2] & 07777),
+ $stat[3], $stat[4], $stat[5], $stat[7], bt($stat[9]), $path;
+ }
+}
+
+$DATE=bt(time());
+
+sub list
+{
+ my ($pkg, $fn, $dn, $sz, $bt);
+ my %debs = ();
+ my %sects = ();
+
+ my($diversions,$architecture);
+ chop($diversions = `dpkg-divert --list 2>/dev/null`);
+ chop($architecture = `dpkg-architecture 2>/dev/null`);
+ chop($list = `dpkg -l '*' 2>/dev/null`);
+ chop($getselections = `dpkg --get-selections 2>/dev/null`);
+ chop($audit = `dpkg --audit 2>/dev/null`);
+ $sz = length($diversions);
+ print "-r--r--r-- 1 root root $sz $DATE DIVERSIONS\n";
+ $sz = length($architecture);
+ print "-r--r--r-- 1 root root $sz $DATE ARCHITECTURE\n";
+ $sz = length($list);
+ print "-r--r--r-- 1 root root $sz $DATE LIST\n";
+ $sz = length($getselections);
+ print "-r--r--r-- 1 root root $sz $DATE GET-SELECTIONS\n";
+ $sz = length($audit);
+ print "-r--r--r-- 1 root root $sz $DATE AUDIT\n";
+ $sz = length($pressconfigure);
+ print "-r-xr--r-- 1 root root $sz $DATE CONFIGURE\n";
+ $sz = length($pressremove);
+ print "-r-xr--r-- 1 root root $sz $DATE REMOVE\n";
+ $sz = length($pressclearavail);
+ print "-r-xr--r-- 1 root root $sz $DATE CLEAR-AVAIL\n";
+ $sz = length($pressforgetoldunavail);
+ print "-r-xr--r-- 1 root root $sz $DATE FORGET-OLD-UNAVAIL\n";
+ ls("/var/lib/dpkg/status","STATUS","-r--r--r--");
+ # ls("/var/lib/dpkg/available","AVAILABLE","-r--r--r--");
+
+ print "drwxr-xr-x 1 root root 0 $DATE all\n";
+
+ open STAT, "/var/lib/dpkg/status"
+ or exit 1;
+ while( <STAT> ) {
+ chop;
+ if( /^([\w-]*): (.*)/ ) {
+ $pkg = $2 if( lc($1) eq 'package' );
+ $debs{$pkg}{lc($1)} = $2;
+ }
+ }
+ close STAT;
+
+ foreach $pkg (sort keys %debs) {
+ next if $debs{$pkg}{status} =~ /not-installed/;
+ $fn = $debs{$pkg}{package}. "_". $debs{$pkg}{version};
+ $dn = $debs{$pkg}{section};
+ if( ! $dn ) {
+ $dn = "unknown";
+ } elsif( $dn =~ /^(non-us)$/i ) {
+ $dn .= "/main";
+ } elsif( $dn !~ /\// ) {
+ $dn = "main/". $dn;
+ }
+ unless( $sects{$dn} ) {
+ my $sub = $dn;
+ while( $sub =~ s!^(.*)/[^/]*$!$1! ) {
+ unless( $sects{$sub} ) {
+ print "drwxr-xr-x 1 root root 0 $DATE $sub/\n";
+ $sects{$sub} = 1;
+ }
+ }
+ print "drwxr-xr-x 1 root root 0 $DATE $dn/\n";
+ $sects{$dn} = 1;
+ }
+ $sz = $debs{$pkg}{'status'} =~ /config-files/ ? 0 : $debs{$pkg}{'installed-size'} * 1024;
+ @stat = stat("/var/lib/dpkg/info/".$debs{$pkg}{package}.".list");
+ $bt = bt($stat[9]);
+ print "-rw-r--r-- 1 root root $sz $bt $dn/$fn.debd\n";
+ print "lrwxrwxrwx 1 root root $sz $bt all/$fn.debd -> ../$dn/$fn.debd\n";
+ }
+}
+
+sub copyout
+{
+ my($archive,$filename) = @_;
+ my $qfilename = quote($filename);
+ if( $archive eq 'DIVERSIONS' ) {
+ system("dpkg-divert --list > $qfilename 2>/dev/null");
+ } elsif( $archive eq 'ARCHITECTURE' ) {
+ system("dpkg-architecture > $qfilename 2>/dev/null");
+ } elsif( $archive eq 'LIST' ) {
+ system("dpkg -l '*' > $qfilename 2>/dev/null");
+ } elsif( $archive eq 'AUDIT' ) {
+ system("dpkg --audit > $qfilename 2>/dev/null");
+ } elsif( $archive eq 'GET-SELECTIONS' ) {
+ system("dpkg --get-selections > $qfilename 2>/dev/null");
+ } elsif( $archive eq 'STATUS' ) {
+ system("cp /var/lib/dpkg/status $qfilename");
+ } elsif( $archive eq 'AVAILABLE' ) {
+ system("cp /var/lib/dpkg/available $qfilename");
+ } elsif( $archive eq 'CONFIGURE' ) {
+ open O, ">$filename";
+ print O $pressconfigure;
+ close O;
+ } elsif( $archive eq 'REMOVE' ) {
+ open O, ">$filename";
+ print O $pressremove;
+ close O;
+ } elsif( $archive eq 'CLEAR-AVAIL' ) {
+ open O, ">$filename";
+ print O $pressclearavail;
+ close O;
+ } elsif( $archive eq 'FORGET-OLD-UNAVAIL' ) {
+ open O, ">$filename";
+ print O $pressforgetoldunavail;
+ close O;
+ } else {
+ open O, ">$filename";
+ print O $archive, "\n";
+ close O;
+ }
+}
+
+# too noisy but less dangerouse
+sub copyin
+{
+ my($archive,$filename) = @_;
+ my $qfilename = quote($filename);
+ if( $archive =~ /\.deb$/ ) {
+ system("dpkg -i $qfilename>/dev/null");
+ } else {
+ die "extfs: cannot create regular file \`$archive\': Permission denied\n";
+ }
+}
+
+sub run
+{
+ my($archive,$filename) = @_;
+ if( $archive eq 'CONFIGURE' ) {
+ system("dpkg --pending --configure");
+ } elsif( $archive eq 'REMOVE' ) {
+ system("dpkg --pending --remove");
+ } elsif( $archive eq 'CLEAR-AVAIL' ) {
+ system("dpkg --clear-avail");
+ } elsif( $archive eq 'FORGET-OLD-UNAVAIL' ) {
+ system("dpkg --forget-old-unavail");
+ } else {
+ die "extfs: $filename: command not found\n";
+ }
+}
+
+# Disabled - too dangerous and too noisy
+sub rm_disabled
+{
+ my($archive) = @_;
+ if( $archive =~ /\.debd?$/ ) {
+ my $qname = quote($archive);
+ $qname =~ s%.*/%%g;
+ $qname =~ s%_.*%%g;
+ system("if dpkg -s $qname | grep ^Status | grep -qs config-files; \
+ then dpkg --purge $qname>/dev/null; \
+ else dpkg --remove $qname>/dev/null; fi");
+ die("extfs: $archive: Operation not permitted\n") if $? != 0;
+ } else {
+ die "extfs: $archive: Operation not permitted\n";
+ }
+}
+
+
+$pressconfigure=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to configure all
+ non configured packages.
+
+This is not a real file. It is a way to configure all non configured packages.
+
+To configure packages go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressremove=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to remove all
+ unselected packages.
+
+This is not a real file. It is a way to remove all unselected packages.
+
+To remove packages go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressforgetoldunavail=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to forget about
+ uninstalled unavailable packages.
+
+This is not a real file. It is a way to forget about uninstalled
+unavailable packages.
+
+To forget this information go back to the panel and press Enter on this file.
+
+EOInstall
+
+$pressclearavail=<<EOInstall;
+
+ WARNING
+ Don\'t use this method if you are not willing to erase the existing
+ information about what packages are available.
+
+This is not a real file. It is a way to erase the existing information
+about what packages are available.
+
+To clear this information go back to the panel and press Enter on this file.
+
+EOInstall
+
+
+
+# override any locale for dates
+$ENV{"LC_ALL"}="C";
+
+if ($ARGV[0] eq "list") { list(); exit(0); }
+elsif ($ARGV[0] eq "copyout") { copyout($ARGV[2], $ARGV[3]); exit(0); }
+elsif ($ARGV[0] eq "copyin") { copyin($ARGV[2], $ARGV[3]); exit(0); }
+elsif ($ARGV[0] eq "run") { run($ARGV[2],$ARGV[3]); exit(0); }
+#elsif ($ARGV[0] eq "rm") { rm($ARGV[2]); exit(0); }
+exit(1);
+
diff --git a/src/vfs/extfs/helpers/gitfs+ b/src/vfs/extfs/helpers/gitfs+
new file mode 100755
index 0000000..66861fb
--- /dev/null
+++ b/src/vfs/extfs/helpers/gitfs+
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+LANG=C
+export LANG
+LC_TIME=C
+export LC_TIME
+
+umask 077
+prefix='[git]'
+
+gitfs_list()
+{
+ DATE=`date +"%b %d %H:%M"`
+ GIT_DIR="$2/.git"
+ user=`whoami`
+ git ls-files -v -c -m -d | sort -k 2 | uniq -f 1 | while read status fname
+ do
+ [ "$status" = "H" ] && status=" "
+ [ "$status" = "C" ] && status="*"
+ echo "-r--r--r-- 1 $user 0 0 $DATE `dirname $fname`/$prefix$status`basename $fname`"
+ done
+}
+
+gitfs_copyout()
+{
+ printf "%s\n" "$2" > "$4"
+ b=`echo "$prefix"| wc -c`
+ b=`expr "$b" + 1`
+ # remove prefix from file name
+ echo "`dirname "$3"`/`basename "$3" | tail -c+"$b"`" >> "$4"
+ echo "git" >> "$4"
+}
+
+case "$1" in
+ list) gitfs_list "$@" ;;
+ copyout) gitfs_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/hp48+.in b/src/vfs/extfs/helpers/hp48+.in
new file mode 100644
index 0000000..17c03ab
--- /dev/null
+++ b/src/vfs/extfs/helpers/hp48+.in
@@ -0,0 +1,132 @@
+#!/bin/sh
+#
+# Written by Christofer Edvardsen <ce@earthling.net>, Feb 1998
+#
+# This script makes it possible to view and copy files to/from a hp48
+# (tested with a HP48G and the emulator x48)
+#
+# To use the hp48 external filesystem:
+# - read the relevant parts of your HP48 manual
+# - install kermit
+# - connect the HP48 to your computer or start x48
+# - below change the line which reflects the serial device you use
+# - configure your HP48 (<left shift> - i/o - iopar):
+# port: wire
+# baud: 9600
+# transfer format: binary (fast transfers) or
+# ascii (editable on the pc)
+# - start the server on the HP48: <left shift> - i/o - srvr - serve
+# or the shortcut <right shift> - <right arrow>
+# - on MC's commandline enter "cd hp48://"
+#
+# Make sure you have kermit installed and that it's using the right serial
+# device by changing /dev/ttyXX on the next line
+AWK=@AWK@
+KERMIT=${MC_TEST_EXTFS_LIST_CMD:-"kermit -l /dev/ttyS1 -b 9600"}
+
+NOW=`date +"%m-%d-%Y %H:%M"`
+
+hp48_cmd()
+{
+$KERMIT -C "SET EXIT WARNING OFF,REMOTE $1,QUIT"
+}
+
+hp48_cd()
+{
+(echo SET EXIT WARNING OFF;echo REMOTE HOST HOME
+for HP48_DIR in `echo "$1" | tr '/' ' '`;do
+ if [ "x$HP48_DIR" != "x." ];then echo REMOTE HOST "$HP48_DIR"; fi
+done
+echo QUIT)| $KERMIT -B >/dev/null
+}
+
+#
+# Parses the reply to the DIRECTORY command.
+#
+# Here's an example reply (taken from [1][2]):
+#
+# { HOME } 105617
+# STRAY 185.5 Directory 29225
+# YEN 30.5 Program 53391
+# JYTLIGHT 21848.5 String 62692
+# IOPAR 37.5 List 61074
+#
+# The meaning of the fields (according to [3][4]):
+#
+# { Current_directory } Free_space
+# Object_name Object_size_bytes Object_type Object_CRC
+# ...
+#
+# [1] http://newarea48.tripod.com/kermit.html
+# [2] http://www.hpmuseum.org/forum/thread-4684.html
+# [3] https://groups.google.com/d/msg/comp.sys.hp48/bYTCu9K3k20/YWQfF--W3EEJ
+# [4] http://www.columbia.edu/kermit/hp48.html (also has a link to the HP's user manual).
+#
+hp48_parser()
+{
+HP48_DIRS=
+
+read -r INPUT
+while [ "x$INPUT" != "xEOF" ]
+do
+ set -- $INPUT
+
+ obj_name=$1
+ obj_size=$2
+ obj_type=$3
+
+ obj_size=`echo $obj_size | $AWK '{ print int($0) }'` # Truncates floats to ints; anything else to "0".
+
+ if [ "$obj_size" != "0" ]; then # Skips the 1st reply line (purportedly there aren't zero-size files b/c, according to resource [4], the size is "including name").
+ case "$obj_type" in
+ Directory)
+ HP48_DIRS="$HP48_DIRS $obj_name"
+ printf "%crwxr-xr-x 1 %-8d %-8d %8d %s %s\n" 'd' \
+ 0 0 $obj_size "$NOW" "$HP48_CDIR/$obj_name"
+ ;;
+ *)
+ printf "%crw-r--r-- 1 %-8d %-8d %8d %s %s\n" '-' \
+ 0 0 $obj_size "$NOW" "$HP48_CDIR/$obj_name"
+ ;;
+ esac
+ fi
+
+ read -r INPUT
+done
+
+for HP48_DIR in $HP48_DIRS;
+do
+ HP48_PDIR="$HP48_CDIR"
+ HP48_CDIR="$HP48_CDIR/$HP48_DIR"; hp48_cmd "HOST $HP48_DIR" >/dev/null
+ hp48_list
+ HP48_CDIR="$HP48_PDIR"; hp48_cmd "HOST UPDIR" >/dev/null
+done
+}
+
+hp48_list()
+{
+# It's hard to see why this "EOF" thing is needed. The loop above can be changed to "while read -r obj_name ...". @TODO.
+{ hp48_cmd "DIRECTORY"; echo; echo EOF; } | hp48_parser
+}
+
+# override any locale for dates
+LC_ALL=C
+export LC_ALL
+
+case "$1" in
+list) HP48_CDIR=
+ hp48_cmd "HOST HOME" >/dev/null
+ hp48_list
+ exit 0;;
+copyout)
+ cd "`dirname "$4"`"
+ hp48_cd "`dirname "$3"`"
+ $KERMIT -B -g "`basename "$3"`" -a "$4" >/dev/null
+ exit 0;;
+copyin)
+ cd "`dirname "$4"`"
+ hp48_cd "`dirname "$3"`"
+ $KERMIT -B -s "$4" -a "`basename "$3"`" >/dev/null
+ exit 0;;
+esac
+exit 1
diff --git a/src/vfs/extfs/helpers/iso9660.in b/src/vfs/extfs/helpers/iso9660.in
new file mode 100644
index 0000000..5a6f1d5
--- /dev/null
+++ b/src/vfs/extfs/helpers/iso9660.in
@@ -0,0 +1,235 @@
+#! /bin/sh
+# Midnight Commander - ISO9660 VFS for MC
+# based on lslR by Tomas Novak <tnovak@ipex.cz> April 2000
+#
+# Copyright (C) 2000, 2003
+# The Free Software Foundation, Inc.
+#
+# Written by:
+# Michael Shigorin <mike@altlinux.org>,
+# Grigory Milev <week@altlinux.org>,
+# Kachalov Anton <mouse@linux.ru.net>, 2003
+# Victor Ananjevsky <ananasik@gmail.com>, 2013
+# slava zanko <slavazanko@gmail.com>, 2013
+#
+# 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 section (source functions, for example) *******************
+
+#*** file scope functions **********************************************
+
+XORRISO=$(which xorriso 2>/dev/null)
+
+xorriso_list() {
+ if test -z "$XORRISO"; then
+ return 1
+ fi
+ local dir attr ln usr gr sz dt1 dt2 dt3 nm len name lsl r
+ dir="${2:-/}"
+ lsl=$( $XORRISO -abort_on FATAL -dev stdio:"$1" -cd "$dir" -lsl 2> /dev/null )
+ r=$?
+ test $r -gt 0 && return $r
+
+ echo "$lsl" | grep "^[-d]" | \
+ while read attr ln usr gr sz dt1 dt2 dt3 nm ; do
+ len=$((${#nm} - 1))
+ name=$(printf -- "$nm" | cut -c2-$len) # remove quotes
+
+ if test $(printf -- "$attr" | cut -c1-1) != "d"; then
+ printf -- "%s %s %s %s %s %s %s %s %s/%s\n" "$attr" "$ln" "$usr" "$gr" "$sz" "$dt1" "$dt2" "$dt3" "$dir" "$name"
+ else
+ xorriso_list "$1" "$dir/$name"
+ fi
+ done
+}
+
+xorriso_copyout() {
+ if test -z "$XORRISO"; then
+ return 1
+ fi
+ $XORRISO -dev stdio:"$1" -osirrox on -extract "$2" "$3" >/dev/null 2>&1
+}
+
+xorriso_copyin() {
+ if test -z "$XORRISO"; then
+ return 1
+ fi
+ $XORRISO -dev stdio:"$1" -cpr "$3" "$2" >/dev/null 2>&1
+}
+
+xorriso_mkdir() {
+ if test -z "$XORRISO"; then
+ return 1
+ fi
+ $XORRISO -dev stdio:"$1" -mkdir "$2" >/dev/null 2>&1
+}
+
+xorriso_rmdir() {
+ if test -z "$XORRISO"; then
+ return 1
+ fi
+ $XORRISO -dev stdio:"$1" -rmdir "$2" >/dev/null 2>&1
+}
+
+xorriso_rm() {
+ if test -z "$XORRISO"; then
+ return 1
+ fi
+ $XORRISO -dev stdio:"$1" -rm "$2" >/dev/null 2>&1
+}
+
+# tested to comply with isoinfo 2.0's output
+test_iso () {
+ ISOINFO=$(which isoinfo 2>/dev/null)
+ if test -z "$ISOINFO"; then
+ echo "isoinfo not found" >&2
+ return 1
+ fi
+
+ CHARSET=$(locale charmap 2>/dev/null)
+ if test -z "$CHARSET"; then
+ CHARSET=$(locale 2>/dev/null | grep LC_CTYPE | sed -n -e 's/.*\.\(.*\)"$/\1/p')
+ fi
+ if test -n "$CHARSET"; then
+ CHARSET=$(echo "$CHARSET" | tr '[A-Z]' '[a-z]' | sed -e 's/^iso-/iso/')
+ $ISOINFO -j $CHARSET -i /dev/null 2>&1 | grep "Iconv not yet supported\|Unknown charset" >/dev/null && CHARSET=
+ fi
+ if test -n "$CHARSET"; then
+ JOLIET_OPT="-j $CHARSET -J"
+ else
+ JOLIET_OPT="-J"
+ fi
+
+ ISOINFO_D_I="$($ISOINFO -d -i "$1" 2>/dev/null)"
+ ISOINFO="$ISOINFO -R"
+
+ echo "$ISOINFO_D_I" | grep "UCS level 1\|NO Joliet" > /dev/null || ISOINFO="$ISOINFO $JOLIET_OPT"
+
+ if [ $(echo "$ISOINFO_D_I" | grep "Joliet with UCS level 3 found" | wc -l) = 1 \
+ -a $(echo "$ISOINFO_D_I" | grep "NO Rock Ridge" | wc -l) = 1 ] ; then
+ SEMICOLON="YES"
+ fi
+}
+
+mcisofs_list () {
+ local lsl r
+
+ # left as a reminder to implement compressed image support =)
+ case "$1" in
+ *.lz) MYCAT="lzip -dc";;
+ *.lz4) MYCAT="lz4 -dc";;
+ *.lzma) MYCAT="lzma -dc";;
+ *.xz) MYCAT="xz -dc";;
+ *.zst) MYCAT="zstd -dc";;
+ *.bz2) MYCAT="bzip2 -dc";;
+ *.gz) MYCAT="gzip -dc";;
+ *.z) MYCAT="gzip -dc";;
+ *.Z) MYCAT="gzip -dc";;
+ *) MYCAT="cat";;
+ esac
+
+ lsl=$($ISOINFO -l -i "$1" 2>/dev/null)
+ r=$?
+ test $r -gt 0 && return $r
+
+ echo "$lsl" | @AWK@ -v SEMICOLON=$SEMICOLON '
+BEGIN {
+ dir="";
+ # Pattern to match 8 first fields.
+ rx = "[^ ]+[ ]+";
+ rx = "^" rx rx rx rx rx rx rx rx;
+ irx = "^\\[ *-?[0-9]* *[0-9]+\\] +";
+}
+/^$/ { next }
+/^d---------/ { next }
+/^Directory listing of [^ ].*$/ {
+ dir=substr($0, 23);
+ next;
+}
+{ $11 != "" } {
+ name=$0
+ sub(rx, "", name)
+ attr=substr($0, 1, length($0)-length(name))
+ # strip inodes and extra dir entries; fix perms
+ sub(irx, "", name)
+ sub("^---------- 0 0 0", "-r--r--r-- 1 root root", attr)
+ sub(" $", "", name)
+ # for Joliet UCS level 3
+ if (SEMICOLON == "YES") sub(";1$", "", name);
+ ## sub(";[0-9]+$", "", name) ## would break copyout
+ # skip . and ..
+ if (name == ".") next;
+ if (name == "..") next;
+ printf "%s%s%s\n", attr, dir, name
+}'
+}
+
+mcisofs_copyout () {
+ if [ "x$SEMICOLON" = "xYES" ]; then
+ $ISOINFO -i "$1" -x "/$2;1" 2>/dev/null > "$3"
+ else
+ $ISOINFO -i "$1" -x "/$2" 2>/dev/null > "$3"
+ fi
+}
+
+#*** main code *********************************************************
+
+LC_ALL=C
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list)
+ xorriso_list "$@" || {
+ test_iso "$@" || exit 1
+ mcisofs_list "$@" || exit 1
+ }
+ exit 0
+ ;;
+ rm)
+ xorriso_rm "$@" || {
+ exit 1
+ }
+ exit 0
+ ;;
+ rmdir)
+ xorriso_rmdir "$@" || {
+ exit 1
+ }
+ exit 0
+ ;;
+ mkdir)
+ xorriso_mkdir "$@" || {
+ exit 1
+ }
+ exit 0
+ ;;
+ copyin)
+ xorriso_copyin "$@" || {
+ exit 1
+ }
+ exit 0
+ ;;
+ copyout)
+ xorriso_copyout "$@" || {
+ test_iso "$@" || exit 1
+ mcisofs_copyout "$@" || exit 1
+ }
+ exit 0
+ ;;
+esac
+exit 1
diff --git a/src/vfs/extfs/helpers/lslR.in b/src/vfs/extfs/helpers/lslR.in
new file mode 100644
index 0000000..69b663b
--- /dev/null
+++ b/src/vfs/extfs/helpers/lslR.in
@@ -0,0 +1,74 @@
+#! /bin/sh
+
+# Based on previous version of lslR
+# Modified by Tomas Novak <tnovak@ipex.cz> April 2000
+# (to allow spaces in filenames)
+#
+# It's assumed that lslR was generated in C locale.
+LC_ALL=C
+export LC_ALL=C
+
+AWK=@AWK@
+
+mclslRfs_list () {
+case "$1" in
+ *.lz) MYCAT="lzip -dc";;
+ *.lz4) MYCAT="lz4 -dc";;
+ *.lzma) MYCAT="lzma -dc";;
+ *.xz) MYCAT="xz -dc";;
+ *.zst) MYCAT="zstd -dc";;
+ *.bz2) MYCAT="bzip2 -dc";;
+ *.gz) MYCAT="gzip -dc";;
+ *.z) MYCAT="gzip -dc";;
+ *.Z) MYCAT="gzip -dc";;
+ *) MYCAT="cat";;
+esac
+
+MYCAT=${MC_TEST_EXTFS_LIST_CMD:-$MYCAT} # Let the test framework hook in.
+
+$MYCAT "$1" | $AWK '
+BEGIN {
+ dir="";
+ empty=1;
+ rx = "[^ ]+[ ]+";
+ # Pattern to match 7 first fields.
+ rx7 = "^" rx rx rx rx rx rx "[^ ]+[ ]";
+ # Pattern to match 8 first fields.
+ rx8 = "^" rx rx rx rx rx rx rx "[^ ]+[ ]";
+}
+/^total\ [0-9]*$/ { next }
+/^$/ { empty=1; next }
+empty==1 && /:$/ {
+ empty=0
+ if ($0 ~ /^\//) dir=substr($0, 2);
+ else dir=$0;
+ if (dir ~ /\/:$/) sub(/:$/, "", dir);
+ else sub(/:$/, "/", dir);
+ if (dir ~ /^[ ]/) dir="./"dir;
+ next;
+}
+( NF > 7 ) {
+ empty=0
+ # gensub() is not portable.
+ name=$0
+ i=index($6, "-")
+ if (i) {
+ sub(rx7, "", name)
+ NF = 7
+ $6=substr($6,i+1)"-"substr($6,1,i-1)
+ }
+ else {
+ sub(rx8, "", name)
+ NF = 8
+ }
+ printf "%s %s%s\n", $0, dir, name
+}
+ {
+ empty=0
+}'
+}
+
+case "$1" in
+ list) mclslRfs_list "$2"; exit 0;;
+esac
+exit 1
diff --git a/src/vfs/extfs/helpers/mailfs.in b/src/vfs/extfs/helpers/mailfs.in
new file mode 100644
index 0000000..5bb373b
--- /dev/null
+++ b/src/vfs/extfs/helpers/mailfs.in
@@ -0,0 +1,219 @@
+#! @PERL@
+
+use bytes;
+use warnings;
+
+# MC extfs for (possibly compressed) Berkeley style mailbox files
+# Peter Daum <gator@cs.tu-berlin.de> (Jan 1998, mc-4.1.24)
+
+$zcat="zcat"; # gunzip to stdout
+$bzcat="bzip2 -dc"; # bunzip2 to stdout
+$lzipcat="lzip -dc"; # unlzip to stdout
+$lz4cat="lz4 -dc"; # unlz4 to stdout
+$lzcat="lzma -dc"; # unlzma to stdout
+$xzcat="xz -dc"; # unxz to stdout
+$zstdcat="zstd -dc"; # unzstd to stdout
+$file="file"; # "file" command
+$TZ='GMT'; # default timezone (for Date module)
+
+if (eval "require Date::Parse") {
+ import Date::Parse;
+ $parse_date=
+ sub {
+ local $ftime = str2time($_[0],$TZ);
+ $_ = localtime($ftime);
+ /^(...) (...) ([ \d]\d) (\d\d:\d\d):\d\d (\d\d\d\d)$/;
+ if ($ftime + 6 * 30 * 24 * 60 * 60 < $now ||
+ $ftime + 60 * 60 > $now) {
+ return "$2 $3 $5";
+ } else {
+ return "$2 $3 $4";
+ }
+ }
+} elsif (eval "require Date::Manip") {
+ import Date::Manip;
+ $parse_date=
+ sub {
+ return UnixDate($_[0], "%l"); # "ls -l" format
+ }
+} else { # use "light" version
+ $parse_date= sub {
+ local $mstring='GeeJanFebMarAprMayJunJulAugSepOctNovDec';
+ # assumes something like: Mon, 5 Jan 1998 16:08:19 +0200 (GMT+0200)
+ # if you have mails with another date format, add it here
+ if (/(\d\d?) ([A-Z][a-z][a-z]) (\d\d\d\d) (\d\d?):(\d\d)/) {
+ $day = $1;
+ $month = $2;
+ $mon = index($mstring,$month) / 3;
+ $year = $3;
+ $hour = $4;
+ $min = $5;
+ # pass time not year for files younger than roughly 6 months
+ # but not for files with dates more than 1-2 hours in the future
+ if ($year * 12 + $mon > $thisyear * 12 + $thismon - 7 &&
+ $year * 12 + $mon <= $thisyear * 12 + $thismon &&
+ ! (($year * 12 + $mon) * 31 + $day ==
+ ($thisyear * 12 + $thismon) * 31 + $thisday &&
+ $hour > $thishour + 2)) {
+ return "$month $day $hour:$min";
+ } else {
+ return "$month $day $year";
+ }
+ }
+ # Y2K bug.
+ # Date: Mon, 27 Mar 100 16:30:47 +0000 (GMT)
+ if (/(\d\d?) ([A-Z][a-z][a-z]) (1?\d\d) (\d\d?):(\d\d)/) {
+ $day = $1;
+ $month = $2;
+ $mon = index($mstring,$month) / 3;
+ $year = 1900 + $3;
+ $hour = $4;
+ $min = $5;
+ if ($year < 1970) {
+ $year += 100;
+ }
+ if ($year * 12 + $mon > $thisyear * 12 + $thismon - 7 &&
+ $year * 12 + $mon <= $thisyear * 12 + $thismon &&
+ ! (($year * 12 + $mon) * 31 + $day ==
+ ($thisyear * 12 + $thismon) * 31 + $thisday &&
+ $hour > $thishour + 2)) {
+ return "$month $day $hour:$min";
+ } else {
+ return "$month $day $year";
+ }
+ }
+ # AOLMail(SM).
+ # Date: Sat Jul 01 10:06:06 2000
+ if (/([A-Z][a-z][a-z]) (\d\d?) (\d\d?):(\d\d)(:\d\d)? (\d\d\d\d)/) {
+ $month = $1;
+ $mon = index($mstring,$month) / 3;
+ $day = $2;
+ $hour = $3;
+ $min = $4;
+ $year = $6;
+ if ($year * 12 + $mon > $thisyear * 12 + $thismon - 7 &&
+ $year * 12 + $mon <= $thisyear * 12 + $thismon &&
+ ! (($year * 12 + $mon) * 31 + $day ==
+ ($thisyear * 12 + $thismon) * 31 + $thisday &&
+ $hour > $thishour + 2)) {
+ return "$month $day $hour:$min";
+ } else {
+ return "$month $day $year";
+ }
+ }
+ # Fallback
+ return $fallback;
+ }
+}
+
+sub process_header {
+ while (<IN>) {
+ $size+=length;
+ s/\r$//;
+ last if /^$/;
+ die "unexpected EOF\n" if eof;
+ if (/^date:\s(.*)$/i) {
+ $date=&$parse_date($1);
+ } elsif (/^subject:\s(.*)$/i) {
+ $subj=lc($1);
+ $subj=~ s/^(re:\s?)+//gi; # no leading Re:
+ $subj=~ tr/a-zA-Z0-9//cd; # strip all "special" characters
+ } elsif (/^from:\s.*?(\w+)\@/i) {
+ $from=$1;
+ } elsif (/^to:\s.*?(\w+)\@/i) {
+ $to=lc($1);
+ }
+ }
+}
+
+sub print_dir_line {
+ $from=$to if ($from eq $user); # otherwise, it would look pretty boring
+ $date=localtime(time) if (!defined $date);
+ printf "-r-------- 1 $< $< %d %s %3.3d_%.25s\n",
+ $size, $date, $msg_nr, "${from}_${subj}";
+
+}
+
+sub mailfs_list {
+ my $blank = 1;
+ $user=$ENV{USER}||getlogin||getpwuid($<) || "nobody";
+
+ while(<IN>) {
+ s/\r$//;
+ if($blank && /^from\s+\w+(\.\w+)*@/i) { # Start of header
+ print_dir_line unless (!$msg_nr);
+ $size=length;
+ $msg_nr++;
+ ($from,$to,$subj,$date)=("none","none","none", "01-01-80");
+ process_header;
+ $line=$blank=0;
+ } else {
+ $size+=length;
+ $line++;
+ $blank= /^$/;
+ }
+ }
+ print_dir_line unless (!$msg_nr);
+ exit 0;
+}
+
+sub mailfs_copyout {
+ my($source,$dest)=@_;
+ exit 1 unless (open STDOUT, ">$dest");
+ ($nr)= ($source =~ /^(\d+)/); # extract message number from "filename"
+
+ my $blank = 1;
+ while(<IN>) {
+ s/\r$//;
+ if($blank && /^from\s+\w+(\.\w+)*@/i) {
+ $msg_nr++;
+ exit(0) if ($msg_nr > $nr);
+ $blank= 0;
+ } else {
+ $blank= /^$/;
+ }
+ print if ($msg_nr == $nr);
+ }
+}
+
+# main {
+exit 1 unless ($#ARGV >= 1);
+$msg_nr=0;
+$cmd=shift;
+$mbox_name=shift;
+my $mbox_qname = quotemeta ($mbox_name);
+$_=`$file $mbox_qname`;
+
+if (/gzip/) {
+ exit 1 unless (open IN, "$zcat $mbox_qname|");
+} elsif (/bzip/) {
+ exit 1 unless (open IN, "$bzcat $mbox_qname|");
+} elsif (/lzip/) {
+ exit 1 unless (open IN, "$lzipcat $mbox_qname|");
+} elsif (/lz4/) {
+ exit 1 unless (open IN, "$lz4cat $mbox_qname|");
+} elsif (/lzma/) {
+ exit 1 unless (open IN, "$lzcat $mbox_qname|");
+} elsif (/xz/) {
+ exit 1 unless (open IN, "$xzcat $mbox_qname|");
+} elsif (/zst/) {
+ exit 1 unless (open IN, "$zstdcat $mbox_qname|");
+} else {
+ exit 1 unless (open IN, "<$mbox_name");
+}
+
+umask 077;
+
+if($cmd eq "list") {
+ $now = time;
+ $_ = localtime($now);
+ /^... (... [ \d]\d \d\d:\d\d):\d\d \d\d\d\d$/;
+ $fallback = $1;
+ $nowstring=`date "+%Y %m %d %H"`;
+ ($thisyear, $thismon, $thisday, $thishour) = split(/ /, $nowstring);
+ &mailfs_list;
+ exit 0;
+}
+elsif($cmd eq "copyout") { &mailfs_copyout(@ARGV); exit 0; }
+
+exit 1;
diff --git a/src/vfs/extfs/helpers/patchfs.in b/src/vfs/extfs/helpers/patchfs.in
new file mode 100644
index 0000000..ee1e651
--- /dev/null
+++ b/src/vfs/extfs/helpers/patchfs.in
@@ -0,0 +1,427 @@
+#! @PERL@
+#
+# Written by Adam Byrtek <alpha@debian.org>, 2002
+# Rewritten by David Sterba <dave@jikos.cz>, 2009
+#
+# Extfs to handle patches in context and unified diff format.
+# Known issues: When name of file to patch is modified during editing,
+# hunk is duplicated on copyin. It is unavoidable.
+
+use bytes;
+use strict;
+use warnings;
+use POSIX;
+use File::Temp 'tempfile';
+
+# standard binaries
+my $lzip = 'lzip';
+my $lz4 = 'lz4';
+my $lzma = 'lzma';
+my $xz = 'xz';
+my $zstd = 'zstd';
+my $bzip = 'bzip2';
+my $gzip = 'gzip';
+my $fileutil = 'file -b';
+
+# date parsing requires Date::Parse from TimeDate module
+my $parsedates = eval 'require Date::Parse';
+
+# regular expressions
+my $unified_header=qr/^--- .*\t.*\n\+\+\+ .*\t.*\n$/;
+my $unified_extract=qr/^--- ([^\t]+).*\n\+\+\+ ([^\t]+)\s*(.*)\n/;
+my $unified_header2=qr/^--- .*\n\+\+\+ .*\n$/;
+my $unified_extract2=qr/^--- ([^\s]+).*\n\+\+\+ ([^\s]+)\s*(.*)\n/;
+my $unified_contents=qr/^([+\-\\ \n]|@@ .* @@)/;
+my $unified_hunk=qr/@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@.*\n/;
+
+my $context_header=qr/^\*\*\* .*\t.*\n--- .*\t.*\n$/;
+my $context_extract=qr/^\*\*\* ([^\t]+).*\n--- ([^\t]+)\s*(.*)\n/;
+my $context_header2=qr/^\*\*\* .*\n--- .*\n$/;
+my $context_extract2=qr/^\*\*\* ([^\s]+).*\n--- ([^\s]+)\s*(.*)\n/;
+my $context_contents=qr/^([!+\-\\ \n]|-{3} .* -{4}|\*{3} .* \*{4}|\*{15})/;
+
+my $ls_extract_id=qr/^[^\s]+\s+[^\s]+\s+([^\s]+)\s+([^\s]+)/;
+my $basename=qr|^(.*/)*([^/]+)$|;
+
+sub patchfs_canonicalize_path ($) {
+ my ($fname) = @_;
+ $fname =~ s,/+,/,g;
+ $fname =~ s,(^|/)(?:\.?\./)+,$1,;
+ return $fname;
+}
+
+# output unix date in a mc-readable format
+sub timef
+{
+ my @time=localtime($_[0]);
+ return sprintf '%02d-%02d-%02d %02d:%02d', $time[4]+1, $time[3],
+ $time[5]+1900, $time[2], $time[1];
+}
+
+# parse given string as a date and return unix time
+sub datetime
+{
+ # in case of problems fall back to 0 in unix time
+ # note: str2time interprets some wrong values (eg. " ") as 'today'
+ if ($parsedates && defined (my $t=str2time($_[0]))) {
+ return timef($t);
+ }
+ return timef(time);
+}
+
+# print message on stderr and exit
+sub error
+{
+ print STDERR $_[0], "\n";
+ exit 1;
+}
+
+# (compressed) input
+sub myin
+{
+ my ($qfname)=(quotemeta $_[0]);
+
+ $_=`$fileutil $qfname`;
+ if (/^'*lz4/) {
+ return "$lz4 -dc $qfname";
+ } elsif (/^'*lzip/) {
+ return "$lzip -dc $qfname";
+ } elsif (/^'*lzma/) {
+ return "$lzma -dc $qfname";
+ } elsif (/^'*xz/) {
+ return "$xz -dc $qfname";
+ } elsif (/^'*zst/) {
+ return "$zstd -dc $qfname";
+ } elsif (/^'*bzip/) {
+ return "$bzip -dc $qfname";
+ } elsif (/^'*gzip/) {
+ return "$gzip -dc $qfname";
+ } else {
+ return "cat $qfname";
+ }
+}
+
+# (compressed) output
+sub myout
+{
+ my ($qfname,$append)=(quotemeta $_[0],$_[1]);
+ my ($sep) = $append ? '>>' : '>';
+
+ $_=`$fileutil $qfname`;
+ if (/^'*lz4/) {
+ return "$lz4 -c $sep $qfname";
+ } elsif (/^'*lzip/) {
+ return "$lzip -c $sep $qfname";
+ } elsif (/^'*lzma/) {
+ return "$lzma -c $sep $qfname";
+ } elsif (/^'*xz/) {
+ return "$xz -c $sep $qfname";
+ } elsif (/^'*zst/) {
+ return "$zstd -c $sep $qfname";
+ } elsif (/^'*bzip/) {
+ return "$bzip -c $sep $qfname";
+ } elsif (/^'*gzip/) {
+ return "$gzip -c $sep $qfname";
+ } else {
+ return "cat $sep $qfname";
+ }
+}
+
+# select diff filename conforming with rules found in diff.info
+sub diff_filename
+{
+ my ($fsrc,$fdst)= @_;
+ # TODO: can remove these two calls later
+ $fsrc = patchfs_canonicalize_path ($fsrc);
+ $fdst = patchfs_canonicalize_path ($fdst);
+ if (!$fdst && !$fsrc) {
+ error 'Index: not yet implemented';
+ } elsif (!$fsrc || $fsrc eq '/dev/null') {
+ return ($fdst,'PATCH-CREATE/');
+ } elsif (!$fdst || $fdst eq '/dev/null') {
+ return ($fsrc,'PATCH-REMOVE/');
+ } elsif (($fdst eq '/dev/null') && ($fsrc eq '/dev/null')) {
+ error 'Malformed diff, missing a sane filename';
+ } else {
+ # fewest path name components
+ if ($fdst=~s|/|/|g < $fsrc=~s|/|/|g) {
+ return ($fdst,'');
+ } elsif ($fdst=~s|/|/|g > $fsrc=~s|/|/|g) {
+ return ($fsrc,'');
+ } else {
+ # shorter base name
+ if (($fdst=~/$basename/o,length $2) < ($fsrc=~/$basename/o,length $2)) {
+ return ($fdst,'');
+ } elsif (($fdst=~/$basename/o,length $2) > ($fsrc=~/$basename/o,length $2)) {
+ return ($fsrc,'');
+ } else {
+ # shortest names
+ if (length $fdst < length $fsrc) {
+ return ($fdst,'');
+ } else {
+ return ($fsrc,'');
+ }
+ }
+ }
+ }
+}
+
+# IN: diff "archive" name
+# IN: file handle for output; STDIN for list, tempfile else
+# IN: filename to watch (for: copyout, rm), '' for: list
+# IN: remove the file?
+# true - ... and print out the rest
+# false - ie. copyout mode, print just the file
+sub parse($$$$)
+{
+ my $archive=shift;
+ my $fh=shift;
+ my $file=shift;
+ my $rmmod=shift;
+ my ($state,$fsize,$time);
+ my ($f,$fsrc,$fdst,$prefix);
+ my ($unified,$context);
+ my ($skipread, $filetoprint, $filefound);
+ my ($h_add,$h_del,$h_ctx); # hunk line counts
+ my ($h_r1,$h_r2); # hunk ranges
+ my @outsrc; # if desired ...
+ my @outdst;
+ my $line;
+ my %fmap_size=();
+ my %fmap_time=();
+
+ import Date::Parse if ($parsedates && $file eq '');
+
+ $line=1;
+ $state=0; $fsize=0; $f='';
+ $filefound=0;
+ while ($skipread || ($line++,$_=<I>)) {
+ $skipread=0;
+ if($state == 0) { # expecting comments
+ $unified=$context=0;
+ $unified=1 if (/^--- /);
+ $context=1 if (/^\*\*\* /);
+ if (!$unified && !$context) {
+ $filefound=0 if($file ne '' && $filetoprint);
+ # shortcut for rmmod xor filefound
+ # - in rmmod we print if not found
+ # - in copyout (!rmmod) we print if found
+ print $fh $_ if($rmmod != $filefound);
+ next;
+ }
+
+ if($file eq '' && $filetoprint) {
+ $fmap_size{"$prefix$f"}+=$fsize;
+ $fmap_time{"$prefix$f"}=$time;
+ }
+
+ # start of new file
+ $_ .=<I>; # steal next line, both formats
+ $line++;
+ if($unified) {
+ if(/$unified_header/o) {
+ ($fsrc,$fdst,$time) = /$unified_extract/o;
+ } elsif(/$unified_header2/o) {
+ ($fsrc,$fdst,$time) = /$unified_extract2/o;
+ } else {
+ error "Can't parse unified diff header";
+ }
+ } elsif($context) {
+ if(/$context_header/o) {
+ ($fsrc,$fdst,$time) = /$context_extract/o;
+ } elsif(/$context_header2/o) {
+ ($fsrc,$fdst,$time) = /$context_extract2/o;
+ } else {
+ error "Can't parse context diff header";
+ }
+ } else {
+ error "Unrecognized diff header";
+ }
+ $fsrc=patchfs_canonicalize_path($fsrc);
+ $fdst=patchfs_canonicalize_path($fdst);
+ if(wantarray) {
+ push @outsrc,$fsrc;
+ push @outdst,$fdst;
+ }
+ ($f,$prefix)=diff_filename($fsrc,$fdst);
+ $filefound=($f eq $file);
+
+ $f="$f.diff";
+ $filetoprint=1;
+ $fsize=length;
+ print $fh $_ if($rmmod != $filefound);
+
+ $state=1;
+ } elsif($state == 1) { # expecting diff hunk headers, end of file or comments
+ if($unified) {
+ my ($a,$b,$c,$d);
+ ($a,$b,$h_r1,$c,$d,$h_r2)=/$unified_hunk/o;
+ if(!defined($a) || !defined($c)) {
+ # hunk header does not come, a comment inside
+ # or maybe a new file, state 0 will decide
+ $skipread=1;
+ $state=0;
+ next;
+ }
+ $fsize+=length;
+ print $fh $_ if($rmmod != $filefound);
+ $h_r1=1 if(!defined($b));
+ $h_r2=1 if(!defined($d));
+ $h_add=$h_del=$h_ctx=0;
+ $state=2;
+ } elsif($context) {
+ if(!/$context_contents/o) {
+ $skipread=1;
+ $state=0;
+ next;
+ }
+ print $fh $_ if($rmmod != $filefound);
+ $fsize+=length;
+ }
+ } elsif($state == 2) { # expecting hunk contents
+ if($h_del + $h_ctx == $h_r1 && $h_add + $h_ctx == $h_r2) {
+ # hooray, end of hunk
+ # we optimistically ended with a hunk before but
+ # the line has been read already
+ $skipread=1;
+ $state=1;
+ next;
+ }
+ print $fh $_ if($rmmod != $filefound);
+ $fsize+=length;
+ my ($first)= /^(.)/;
+ if(ord($first) == ord('+')) { $h_add++; }
+ elsif(ord($first) == ord('-')) { $h_del++; }
+ elsif(ord($first) == ord(' ')) { $h_ctx++; }
+ elsif(ord($first) == ord('\\')) { 0; }
+ elsif(ord($first) == ord('@')) { error "Malformed hunk, header came too early"; }
+ else { error "$archive:$line: Unrecognized character '$first' in hunk"; }
+ }
+ }
+ if($file eq '' && $filetoprint) {
+ $fmap_size{"$prefix$f"}+=$fsize;
+ $fmap_time{"$prefix$f"}=$time;
+ }
+
+ # use uid and gid from file
+ my $qarchive = quotemeta $archive;
+ my ($uid,$gid)=(`ls -l $qarchive`=~/$ls_extract_id/o);
+
+ # flush all file names with cumulative file size
+ while(my ($fn, $fs) = each %fmap_size) {
+ printf $fh "-rw-r--r-- 1 %s %s %d %s %s\n", $uid, $gid, $fs, datetime($fmap_time{$fn}), $fn;
+ }
+
+ close($fh) if($file ne '');
+ return \(@outsrc, @outdst) if wantarray;
+}
+
+# list files affected by patch
+sub list($) {
+ parse($_[0], *STDOUT, '', 0);
+ close(I);
+}
+
+# extract diff from patch
+# IN: diff file to find
+# IN: output file name
+sub copyout($$) {
+ my ($file,$out)=@_;
+
+ $file=~s/^(PATCH-(CREATE|REMOVE)\/)?(.*)\.diff$/$3/;
+ $file = patchfs_canonicalize_path ($file);
+
+ open(FH, ">$out") or error("Cannot open output file");
+ parse('', *FH, $file, 0);
+}
+
+# remove diff(s) from patch
+# IN: archive
+# IN: file to delete
+sub rm($$) {
+ my $archive=shift;
+ my ($tmp,$tmpname)=tempfile();
+
+ @_=map {scalar(s/^(PATCH-(CREATE|REMOVE)\/)?(.*)\.diff$/$3/,$_)} @_;
+
+ # just the first file for now
+ parse($archive, $tmp, $_[0], 1);
+ close I;
+
+ # replace archive
+ system("cat \Q$tmpname\E | " . myout($archive,0))==0
+ or error "Can't write to archive";
+ system("rm -f -- \Q$tmpname\E");
+}
+
+# append diff to archive
+# IN: diff archive name
+# IN: newly created file name in archive
+# IN: the real source file
+sub copyin($$$) {
+ # TODO: seems to be tricky. what to do?
+ # copyin of file which is already there may:
+ # * delete the original and copy only the new
+ # * just append the new hunks to the same file
+ # problems: may not be a valid diff, unmerged hunks
+ # * try to merge the two together
+ # ... but we do not want write patchutils again, right?
+ error "Copying files into diff not supported";
+ return;
+
+ my ($archive,$name,$src)=@_;
+
+ # in case we are appending another diff, we have
+ # to delete/merge all the files
+ open(DEVNULL, ">/dev/null");
+ open I, myin($src).'|';
+ my ($srclist,$dstlist)=parse($archive, *DEVNULL, '', 0);
+ close(I);
+ close(DEVNULL);
+ foreach(@$srclist) {
+ print("SRC: del $_\n");
+ }
+ foreach(@$dstlist) {
+ print("DST: del $_\n");
+ }
+ return;
+
+ # remove overwritten file
+ open I, myin($archive).'|';
+ rm ($archive, $name);
+ close I;
+
+ my $cmd1=myin("$src.diff");
+ my $cmd2=myout($archive,1);
+ system("$cmd1 | $cmd2")==0
+ or error "Can't write to archive";
+}
+
+my $fin = $ARGV[1];
+
+# resolve symlink
+while (-l $fin) {
+ $fin = readlink $fin;
+}
+
+if ($ARGV[0] eq 'list') {
+ open I, myin($fin).'|';
+ list ($fin);
+ exit 0;
+} elsif ($ARGV[0] eq 'copyout') {
+ open I, myin($fin)."|";
+ copyout ($ARGV[2], $ARGV[3]);
+ exit 0;
+} elsif ($ARGV[0] eq 'rm') {
+ open I, myin($fin)."|";
+ rm ($fin, $ARGV[2]);
+ exit 0;
+} elsif ($ARGV[0] eq 'rmdir') {
+ exit 0;
+} elsif ($ARGV[0] eq 'mkdir') {
+ exit 0;
+} elsif ($ARGV[0] eq 'copyin') {
+ copyin ($fin, $ARGV[2], $ARGV[3]);
+ exit 0;
+}
+exit 1;
diff --git a/src/vfs/extfs/helpers/patchsetfs b/src/vfs/extfs/helpers/patchsetfs
new file mode 100755
index 0000000..9bbe9f9
--- /dev/null
+++ b/src/vfs/extfs/helpers/patchsetfs
@@ -0,0 +1,104 @@
+#!/bin/sh
+
+LANG=C
+export LANG
+LC_TIME=C
+export LC_TIME
+
+# --- GIT -----------------------------------------------------------------------
+
+found_git_dir()
+{
+ work_dir=$1
+ while [ -n "$work_dir" -a "$work_dir" != "/" ]; do
+ [ -d "${work_dir}/.git" ] && {
+ echo "${work_dir}/.git/"
+ return
+ }
+ work_dir=`dirname "$work_dir"`
+ done
+ echo ''
+}
+
+patchsetfs_list_git()
+{
+ WORK_DIR=$1; shift
+ fname=$1; shift
+ USER=$1; shift
+ DATE=$1; shift
+
+ GIT_DIR=`found_git_dir "$WORK_DIR"`
+ [ -z "$GIT_DIR" ] && GIT_DIR=$WORK_DIR
+ curr_year=`date +"%Y"`
+
+ git --git-dir="$GIT_DIR" log --abbrev=7 --pretty="format:%at %h %an" -- "$fname" | while read TIMESTAMP chset author
+ do
+ year=`date -d @"$TIMESTAMP" +"%Y"`
+ [ "$year" = "$curr_year" ] && {
+ DATE=`date -d @"$TIMESTAMP" +"%b %d %H:%M"`
+ } || {
+ DATE=`date -d @"$TIMESTAMP" +"%b %d %Y"`
+ }
+ NAME="$chset $author"
+ echo "-rw-rw-rw- 1 $USER 0 0 $DATE $NAME.diff"
+ done
+}
+
+patchsetfs_copyout_git()
+{
+ WORK_DIR=$1; shift
+ fname=$1; shift
+ orig_fname=$1;shift
+ output_fname=$1;shift
+
+ chset=`echo "$orig_fname"| cut -f 1 -d " "`
+
+ GIT_DIR=`found_git_dir "$WORK_DIR"`
+ [ -z "$GIT_DIR" ] && GIT_DIR=$WORK_DIR
+
+ git --git-dir="$GIT_DIR" show "$chset" -- "$fname" > "$output_fname"
+}
+
+# --- COMMON --------------------------------------------------------------------
+
+patchsetfs_list()
+{
+ VCS_type=$1; shift
+ WORK_DIR=$1; shift
+ fname=$1; shift
+
+ DATE=`date +"%b %d %H:%M"`
+ USER=`whoami`
+
+ case "$VCS_type" in
+ git) patchsetfs_list_git "$WORK_DIR" "$fname" "$USER" "$DATE" ;;
+ esac
+}
+
+patchsetfs_copyout()
+{
+ VCS_type=$1; shift
+ WORK_DIR=$1; shift
+ fname=$1; shift
+
+ case "$VCS_type" in
+ git) patchsetfs_copyout_git "$WORK_DIR" "$fname" "$@" ;;
+ esac
+
+}
+
+# --- MAIN ----------------------------------------------------------------------
+
+command=$1; shift
+tmp_file=$1; shift
+
+WORK_DIR=`head -n1 $tmp_file`
+fname=`tail -n2 $tmp_file | head -n1`
+VCS_type=`tail -n1 $tmp_file`
+
+case "$command" in
+ list) patchsetfs_list "$VCS_type" "$WORK_DIR" "$fname" ;;
+ copyout) patchsetfs_copyout "$VCS_type" "$WORK_DIR" "$fname" "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/rpm b/src/vfs/extfs/helpers/rpm
new file mode 100755
index 0000000..8fa9188
--- /dev/null
+++ b/src/vfs/extfs/helpers/rpm
@@ -0,0 +1,349 @@
+#! /bin/sh
+# VFS-wrapper for RPM (and src.rpm) files
+#
+# Copyright (C) 1996-2004,2009
+# Free Software Foundation, Inc.
+#
+# Written by
+# Erik Troan <ewt@redhat.com> 1996
+# Jakub Jelinek <jj@sunsite.mff.cuni.cz> 1996, 2004
+# Tomasz KÅ‚oczko <kloczek@rudy.mif.pg.gda.pl> 1997
+# Wojtek Pilorz <wpilorz@bdk.lublin.pl>
+# 1997: minor changes
+# Michele Marziani <marziani@fe.infn.it>
+# 1997: minor changes
+# Marc Merlin <marcsoft@merlins.org> 1998
+# 1998: bug files
+# Michal Svec <rebel@penguin.cz> 2000
+# 2000: locale bugfix
+# Andrew V. Samoilov <sav@bcs.zp.ua>
+# 2004: Whitespace(s) & single quote(s) in filename workaround
+# https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=64007
+# Slava Zanko <slavazanko@gmail.com>
+# 2009: Totally rewritten.
+# Alexander Chumachenko <ledest@gmail.com>
+# 2013: add dependency version output
+# Denis Silakov <denis.silakov@rosalab.ru>
+# 2013: tar payload support.
+# Arkadiusz Miśkiewicz <arekm@maven.pl>
+# 2013: improve support for EPOCH
+# add support for PREINPROG/POSTINPROG/PREUNPROG/POSTUNPROG
+# add support for VERIFYSCRIPTPROG
+# add support for TRIGGERSCRIPTS/TRIGGERSCRIPTPROG
+# Jiri Tyr <jiri.tyr@gmail.com>
+# 2016: add support for PRETRANS/PRETRANSPROG/POSTTRANS/POSTTRANSPROG
+#
+# This file is part of the Midnight Commander.
+#
+# This program 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.
+#
+# This program 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/>.
+
+
+# override any locale for dates
+unset LC_ALL
+LC_TIME=C
+export LC_TIME
+
+if rpmbuild --version >/dev/null 2>&1; then
+ RPMBUILD="rpmbuild"
+else
+ RPMBUILD="rpm"
+fi
+
+if rpm --nosignature --version >/dev/null 2>&1; then
+ RPM="rpm --nosignature"
+ RPMBUILD="$RPMBUILD --nosignature"
+else
+ RPM="rpm"
+fi
+RPM_QUERY_FMT="$RPM -qp --qf"
+RPM2CPIO="rpm2cpio"
+
+SED="sed"
+
+param=$1; shift
+rpm_filename=$1; shift
+
+FILEPREF="-r--r--r-- 1 root root "
+
+mcrpmfs_getDesription()
+{
+ $RPM -qip "${rpm_filename}"
+}
+
+mcrpmfs_getAllNeededTags()
+{
+ $RPM_QUERY_FMT \
+"|NAME=%{NAME}"\
+"|VERSION=%{VERSION}"\
+"|RELEASE=%{RELEASE}"\
+"|DISTRIBUTION=%{DISTRIBUTION}"\
+"|VENDOR=%{VENDOR}"\
+"|DESCRIPTION=%{DESCRIPTION}"\
+"|SUMMARY=%{SUMMARY}"\
+"|URL=%{URL}"\
+"|EPOCH=%{EPOCH}"\
+"|LICENSE=%{LICENSE}"\
+"|REQUIRES=%{REQUIRENAME} %{REQUIREFLAGS:depflags} %{REQUIREVERSION}"\
+"|OBSOLETES=%{OBSOLETES}"\
+"|PROVIDES=%{PROVIDES} %{PROVIDEFLAGS:depflags} %{PROVIDEVERSION}"\
+"|CONFLICTS=%{CONFLICTS}"\
+"|PACKAGER=%{PACKAGER}" \
+ "${rpm_filename}" \
+ | tr '\n' ' ' # The newlines in DESCRIPTION mess with the sed script in mcrpmfs_getOneTag().
+}
+
+mcrpmfs_getRawOneTag()
+{
+ $RPM_QUERY_FMT "$1" "${rpm_filename}"
+}
+
+mcrpmfs_getOneTag()
+{
+ # 'echo' can't be used for arbitrary data (see commit message).
+ printf "%s" "$AllTAGS" | $SED "s/.*|${1}=//" | cut -d '|' -f 1
+}
+
+mcrpmfs_printOneMetaInfo()
+{
+ if test "$3" = "raw"; then
+ metaInfo=`mcrpmfs_getRawOneTag "%{$2}"`
+ else
+ metaInfo=`mcrpmfs_getOneTag "$2"`
+ fi
+
+ if test -n "${metaInfo}" -a "${metaInfo}" != "(none)"; then
+ echo "${FILEPREF} 0 ${DATE} ${1}"
+ return 0
+ fi
+ return 1
+}
+
+mcrpmfs_list_fastRPM ()
+{
+ echo "$FILEPREF 0 $DATE INFO/DISTRIBUTION"
+ echo "$FILEPREF 0 $DATE INFO/VENDOR"
+ echo "$FILEPREF 0 $DATE INFO/DESCRIPTION"
+ echo "$FILEPREF 0 $DATE INFO/SUMMARY"
+ echo "dr-xr-xr-x 1 root root 0 $DATE INFO/SCRIPTS"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/PRETRANS"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/POSTTRANS"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/PREIN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/POSTIN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/PREUN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/POSTUN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/VERIFYSCRIPT"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/TRIGGERSCRIPTS"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/ALL"
+ echo "$FILEPREF 0 $DATE INFO/PACKAGER"
+ echo "$FILEPREF 0 $DATE INFO/URL"
+ echo "$FILEPREF 0 $DATE INFO/EPOCH"
+ echo "$FILEPREF 0 $DATE INFO/LICENSE"
+ echo "$FILEPREF 0 $DATE INFO/REQUIRES"
+ echo "$FILEPREF 0 $DATE INFO/OBSOLETES"
+ echo "$FILEPREF 0 $DATE INFO/PROVIDES"
+ echo "$FILEPREF 0 $DATE INFO/ENHANCES"
+ echo "$FILEPREF 0 $DATE INFO/SUGGESTS"
+ echo "$FILEPREF 0 $DATE INFO/RECOMMENDS"
+ echo "$FILEPREF 0 $DATE INFO/SUPPLEMENTS"
+ echo "$FILEPREF 0 $DATE INFO/CONFLICTS"
+ echo "$FILEPREF 0 $DATE INFO/CHANGELOG"
+}
+
+mcrpmfs_list_fullRPM ()
+{
+ mcrpmfs_printOneMetaInfo "INFO/DISTRIBUTION" "DISTRIBUTION"
+ mcrpmfs_printOneMetaInfo "INFO/VENDOR" "VENDOR"
+ mcrpmfs_printOneMetaInfo "INFO/DESCRIPTION" "DESCRIPTION"
+ mcrpmfs_printOneMetaInfo "INFO/SUMMARY" "SUMMARY"
+
+ if test "`mcrpmfs_getRawOneTag \"%{RPMTAG_PRETRANS}%{RPMTAG_POSTTRANS}%{RPMTAG_PREIN}%{RPMTAG_POSTIN}%{RPMTAG_PREUN}%{RPMTAG_POSTUN}%{VERIFYSCRIPT}%{TRIGGERSCRIPTS}\"`" != "(none)(none)(none)(none)(none)(none)(none)(none)"; then
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/PRETRANS" "RPMTAG_PRETRANS" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/POSTTRANS" "RPMTAG_POSTTRANS" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/PREIN" "RPMTAG_PREIN" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/POSTIN" "RPMTAG_POSTIN" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/PREUN" "RPMTAG_PREUN" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/POSTUN" "RPMTAG_POSTUN" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/VERIFYSCRIPT" "VERIFYSCRIPT" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/TRIGGERSCRIPTS" "TRIGGERSCRIPTS" "raw"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/ALL"
+ fi
+
+ if test "`mcrpmfs_getRawOneTag \"%{RPMTAG_PRETRANSPROG}%{RPMTAG_POSTTRANSPROG}%{RPMTAG_PREINPROG}%{RPMTAG_POSTINPROG}%{RPMTAG_PREUNPROG}%{RPMTAG_POSTUNPROG}%{VERIFYSCRIPTPROG}%{TRIGGERSCRIPTPROG}\"`" != "(none)(none)(none)(none)(none)(none)(none)(none)"; then
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/PRETRANSPROG" "RPMTAG_PRETRANSPROG" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/POSTTRANSPROG" "RPMTAG_POSTTRANSPROG" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/PREINPROG" "RPMTAG_PREINPROG" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/POSTINPROG" "RPMTAG_POSTINPROG" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/PREUNPROG" "RPMTAG_PREUNPROG" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/POSTUNPROG" "RPMTAG_POSTUNPROG" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/VERIFYSCRIPTPROG" "VERIFYSCRIPTPROG" "raw"
+ mcrpmfs_printOneMetaInfo "INFO/SCRIPTS/TRIGGERSCRIPTPROG" "TRIGGERSCRIPTPROG" "raw"
+ fi
+
+ mcrpmfs_printOneMetaInfo "INFO/PACKAGER" "PACKAGER"
+ mcrpmfs_printOneMetaInfo "INFO/URL" "URL"
+ mcrpmfs_printOneMetaInfo "INFO/EPOCH" "EPOCH"
+ mcrpmfs_printOneMetaInfo "INFO/LICENSE" "LICENSE"
+
+ mcrpmfs_printOneMetaInfo "INFO/REQUIRES" "REQUIRES"
+ mcrpmfs_printOneMetaInfo "INFO/OBSOLETES" "OBSOLETES"
+ mcrpmfs_printOneMetaInfo "INFO/PROVIDES" "PROVIDES"
+ mcrpmfs_printOneMetaInfo "INFO/CONFLICTS" "CONFLICTS"
+ mcrpmfs_printOneMetaInfo "INFO/CHANGELOG" "CHANGELOGTEXT" "raw"
+}
+
+mcrpmfs_list ()
+{
+ # set MCFASTRPM_DFLT to 1 for faster rpm files handling by default, to 0 for
+ # slower handling
+ MCFASTRPM_DFLT=0
+ if test -z "$MCFASTRPM"; then
+ MCFASTRPM=$MCFASTRPM_DFLT
+ fi
+
+ DESC=`mcrpmfs_getDesription 2>/dev/null` || {
+ echo "$FILEPREF 0 "`date +"%b %d %H:%M"`" ERROR"
+ exit 1
+ }
+ DATE=`mcrpmfs_getRawOneTag "%{BUILDTIME:date}\n" | cut -c 5-11,21-24`
+ PAYLOAD=`mcrpmfs_getRawOneTag "%{PAYLOADFORMAT}\n" | sed s/ustar/tar/`
+
+ HEADERSIZE=`printf '%s\n' "$DESC" | wc -c` # 'echo' can't be used for arbitrary data (see commit message).
+ printf '%s %s %s HEADER\n' "${FILEPREF}" "${HEADERSIZE}" "${DATE}"
+ echo "-r-xr-xr-x 1 root root 0 $DATE INSTALL"
+ case "${rpm_filename}" in
+ *.src.rpm)
+ echo "-r-xr-xr-x 1 root root 0 $DATE REBUILD"
+ ;;
+ *)
+ echo "-r-xr-xr-x 1 root root 0 $DATE UPGRADE"
+ ;;
+ esac
+
+ echo "dr-xr-xr-x 3 root root 0 $DATE INFO"
+ if [ `mcrpmfs_getRawOneTag "%{EPOCH}"` = "(none)" ]; then
+ echo "$FILEPREF 0 $DATE INFO/NAME-VERSION-RELEASE"
+ else
+ echo "$FILEPREF 0 $DATE INFO/NAME-EPOCH:VERSION-RELEASE"
+ fi
+ echo "$FILEPREF 0 $DATE INFO/GROUP"
+ echo "$FILEPREF 0 $DATE INFO/BUILDHOST"
+ echo "$FILEPREF 0 $DATE INFO/SOURCERPM"
+ echo "$FILEPREF 0 $DATE INFO/BUILDTIME"
+ echo "$FILEPREF 0 $DATE INFO/RPMVERSION"
+ echo "$FILEPREF 0 $DATE INFO/OS"
+ echo "$FILEPREF 0 $DATE INFO/SIZE"
+
+ if test "$MCFASTRPM" = 0 ; then
+ mcrpmfs_list_fullRPM
+ else
+ mcrpmfs_list_fastRPM
+ fi
+
+ echo "$FILEPREF 0 $DATE CONTENTS.$PAYLOAD"
+}
+
+mcrpmfs_copyout ()
+{
+ case "$1" in
+ HEADER) mcrpmfs_getDesription > "$2"; exit 0;;
+ INSTALL)
+ echo "# Run this to install this RPM package" > "$2"
+ exit 0
+ ;;
+ UPGRADE)
+ echo "# Run this to upgrade this RPM package" > "$2"
+ exit 0
+ ;;
+ REBUILD)
+ echo "# Run this to rebuild this RPM package" > "$2"
+ exit 0
+ ;;
+ ERROR) mcrpmfs_getDesription > /dev/null 2> "$2"; exit 0;;
+ INFO/NAME-VERSION-RELEASE)
+ echo `mcrpmfs_getOneTag "NAME"`-`mcrpmfs_getOneTag "VERSION"`-`mcrpmfs_getOneTag "RELEASE"` > "$2"
+ exit 0
+ ;;
+ INFO/NAME-EPOCH:VERSION-RELEASE)
+ echo `mcrpmfs_getOneTag "NAME"`-`mcrpmfs_getOneTag "EPOCH"`:`mcrpmfs_getOneTag "VERSION"`-`mcrpmfs_getOneTag "RELEASE"` > "$2"
+ exit 0
+ ;;
+ INFO/RELEASE) mcrpmfs_getOneTag "RELEASE" > "$2"; exit 0;;
+ INFO/GROUP) mcrpmfs_getRawOneTag "%{GROUP}\n" > "$2"; exit 0;;
+ INFO/DISTRIBUTION) mcrpmfs_getOneTag "DISTRIBUTION" > "$2"; exit 0;;
+ INFO/VENDOR) mcrpmfs_getOneTag "VENDOR" > "$2"; exit 0;;
+ INFO/BUILDHOST) mcrpmfs_getRawOneTag "%{BUILDHOST}\n" > "$2"; exit 0;;
+ INFO/SOURCERPM) mcrpmfs_getRawOneTag "%{SOURCERPM}\n" > "$2"; exit 0;;
+ INFO/DESCRIPTION) mcrpmfs_getRawOneTag "%{DESCRIPTION}\n" > "$2"; exit 0;;
+ INFO/PACKAGER) mcrpmfs_getOneTag "PACKAGER" > "$2"; exit 0;;
+ INFO/URL) mcrpmfs_getOneTag "URL" >"$2"; exit 0;;
+ INFO/BUILDTIME) mcrpmfs_getRawOneTag "%{BUILDTIME:date}\n" >"$2"; exit 0;;
+ INFO/EPOCH) mcrpmfs_getOneTag "EPOCH" >"$2"; exit 0;;
+ INFO/LICENSE) mcrpmfs_getOneTag "LICENSE" >"$2"; exit 0;;
+ INFO/RPMVERSION) mcrpmfs_getRawOneTag "%{RPMVERSION}\n" >"$2"; exit 0;;
+ INFO/REQUIRES) mcrpmfs_getRawOneTag "[%{REQUIRENAME} %{REQUIREFLAGS:depflags} %{REQUIREVERSION}\n]" >"$2"; exit 0;;
+ INFO/ENHANCES) mcrpmfs_getRawOneTag "[%|ENHANCESFLAGS:depflag_strong?{}:{%{ENHANCESNAME} %{ENHANCESFLAGS:depflags} %{ENHANCESVERSION}\n}|]" "$f" >"$3"; exit 0;;
+ INFO/SUGGESTS) mcrpmfs_getRawOneTag "[%|SUGGESTSFLAGS:depflag_strong?{}:{%{SUGGESTSNAME} %{SUGGESTSFLAGS:depflags} %{SUGGESTSVERSION}\n}|]" "$f" >"$3"; exit 0;;
+ INFO/RECOMMENDS) mcrpmfs_getRawOneTag "[%|SUGGESTSFLAGS:depflag_strong?{%{SUGGESTSNAME} %{SUGGESTSFLAGS:depflags} %{SUGGESTSVERSION}\n}|]" "$f" >"$3"; exit 0;;
+ INFO/SUPPLEMENTS) mcrpmfs_getRawOneTag "[%|ENHANCESFLAGS:depflag_strong?{%{ENHANCESNAME} %{ENHANCESFLAGS:depflags} %{ENHANCESVERSION}\n}|]" "$f" >"$3"; exit 0;;
+ INFO/PROVIDES) mcrpmfs_getRawOneTag "[%{PROVIDES} %{PROVIDEFLAGS:depflags} %{PROVIDEVERSION}\n]" >"$2"; exit 0;;
+ INFO/SCRIPTS/PRETRANS) mcrpmfs_getRawOneTag "%{RPMTAG_PRETRANS}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/PRETRANSPROG) mcrpmfs_getRawOneTag "%{RPMTAG_PRETRANSPROG}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/POSTTRANS) mcrpmfs_getRawOneTag "%{RPMTAG_POSTTRANS}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/POSTTRANSPROG) mcrpmfs_getRawOneTag "%{RPMTAG_POSTTRANSPROG}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/PREIN) mcrpmfs_getRawOneTag "%{RPMTAG_PREIN}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/PREINPROG) mcrpmfs_getRawOneTag "%{RPMTAG_PREINPROG}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/POSTIN) mcrpmfs_getRawOneTag "%{RPMTAG_POSTIN}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/POSTINPROG) mcrpmfs_getRawOneTag "%{RPMTAG_POSTINPROG}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/PREUN) mcrpmfs_getRawOneTag "%{RPMTAG_PREUN}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/PREUNPROG) mcrpmfs_getRawOneTag "%{RPMTAG_PREUNPROG}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/POSTUN) mcrpmfs_getRawOneTag "%{RPMTAG_POSTUN}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/POSTUNPROG) mcrpmfs_getRawOneTag "%{RPMTAG_POSTUNPROG}\n" >"$2"; exit 0;;
+ INFO/SCRIPTS/VERIFYSCRIPT) mcrpmfs_getRawOneTag "%{VERIFYSCRIPT}\n" > "$2"; exit 0;;
+ INFO/SCRIPTS/VERIFYSCRIPTPROG) mcrpmfs_getRawOneTag "%{VERIFYSCRIPTPROG}\n" > "$2"; exit 0;;
+ INFO/SCRIPTS/TRIGGERSCRIPTS) $RPM -qp --triggers "${rpm_filename}" > "$2"; exit 0;;
+ INFO/SCRIPTS/TRIGGERSCRIPTPROG) mcrpmfs_getRawOneTag "%{TRIGGERSCRIPTPROG}\n" > "$2"; exit 0;;
+ INFO/SCRIPTS/ALL) $RPM -qp --scripts "${rpm_filename}" > "$2"; exit 0;;
+ INFO/SUMMARY) mcrpmfs_getRawOneTag "%{SUMMARY}\n" > "$2"; exit 0;;
+ INFO/OS) mcrpmfs_getRawOneTag "%{OS}\n" > "$2"; exit 0;;
+ INFO/CHANGELOG) mcrpmfs_getRawOneTag "[* %{CHANGELOGTIME:date} %{CHANGELOGNAME}\n%{CHANGELOGTEXT}\n\n]\n" > "$2"; exit 0;;
+ INFO/SIZE) mcrpmfs_getRawOneTag "%{SIZE} bytes\n" > "$2"; exit 0;;
+ INFO/OBSOLETES) mcrpmfs_getRawOneTag "[%{OBSOLETENAME} %|OBSOLETEFLAGS?{%{OBSOLETEFLAGS:depflags} %{OBSOLETEVERSION}}:{}|\n]" > "$2"; exit 0;;
+ INFO/CONFLICTS) mcrpmfs_getRawOneTag "[%{CONFLICTNAME} %{CONFLICTFLAGS:depflags} %{CONFLICTVERSION}\n]" >"$2"; exit 0;;
+ CONTENTS.*) $RPM2CPIO "${rpm_filename}" > "$2"; exit 0;;
+ *)
+ ;;
+ esac
+}
+
+mcrpmfs_run ()
+{
+ case "$1" in
+ INSTALL) echo "Installing \"${rpm_filename}\""; $RPM -ivh "${rpm_filename}"; exit 0;;
+ UPGRADE) echo "Upgrading \"${rpm_filename}\""; $RPM -Uvh "${rpm_filename}"; exit 0;;
+ REBUILD) echo "Rebuilding \"${rpm_filename}\""; $RPMBUILD --rebuild "${rpm_filename}"; exit 0;;
+ esac
+}
+
+# Let the test framework override functions and variables.
+[ -n "$MC_TEST_RPM_REWRITE" ] && . "$MC_TEST_RPM_REWRITE"
+
+AllTAGS=`mcrpmfs_getAllNeededTags "$1"`
+
+umask 077
+case "${param}" in
+ list) mcrpmfs_list; exit 0;;
+ copyout) mcrpmfs_copyout "$1" "$2"; exit 0;;
+ run) mcrpmfs_run "$1"; exit 1;;
+esac
+exit 1
diff --git a/src/vfs/extfs/helpers/rpms+.in b/src/vfs/extfs/helpers/rpms+.in
new file mode 100644
index 0000000..9a8e7de
--- /dev/null
+++ b/src/vfs/extfs/helpers/rpms+.in
@@ -0,0 +1,66 @@
+#! @PERL@
+#
+# Written by Balazs Nagy (julian7@kva.hu) 1998
+# locale bugfix by Michal Svec (rebel@penguin.cz) 2000
+# (C) 1998 The Free Software Foundation.
+#
+#
+
+# override any locale for dates
+delete $ENV{"LC_ALL"};
+$ENV{"LC_TIME"}="C";
+
+#print $ENV{"LC_ALL"};
+#exit 0;
+
+sub gd
+{
+ my ($dt) = @_;
+ $dt =~ tr/ //s;
+ $dt =~ s/^\w+ (\w+) (\d+) (\d+:\d+):\d+ .+\n?$/$1 $2 $3/;
+ return $dt;
+}
+
+$DATE=gd(`date`);
+
+sub list
+{
+ my (@rpms, %files, $i, $fn, $dn, $sz, $bt);
+# @rpms = `rpm -qa --qf "\%{NAME}-\%{VERSION}-\%{RELEASE}:\%{GROUP}:\%{SIZE}:\%{BUILDTIME:date}\n"`;
+ @rpms = `rpm -qa --qf "\%{NAME}-\%{VERSION}:\%{GROUP}:\%{SIZE}:\%{BUILDTIME:date}\n"`;
+ print @trpms;
+ %files = ();
+ %sizes = ();
+ %dates = ();
+ for $i (@rpms) {
+ if ($i =~ /^([^:]+):([^:]+):([^:]+):(.+)$/) {
+ ($fn, $dn, $sz, $bt) = ($1, $2, $3, $4);
+ $dn =~ s/ /_/g;
+ if (defined $files{$dn}) {
+ push(@{$files{$dn}}, $fn);
+ } else {
+ @{$files{$dn}} = ($fn);
+ }
+ $sizes{$fn} = $sz;
+ $dates{$fn} = gd($bt);
+ }
+ }
+ for $i (sort keys %files) {
+ print "dr-xr-xr-x 1 root root 0 $DATE $i/\n";
+ for $fn (sort @{$files{$i}}) {
+ print "-r--r--r-- 1 root root $sizes{$fn} $dates{$fn} $i/$fn.trpm\n";
+ }
+ }
+}
+
+#open O, ">>/tmp/tt";
+#print O "RPMS: ";
+#for $i (@ARGV) {
+# print O "$i ";
+#}
+#print O "\n";
+#close O;
+
+if ($ARGV[0] eq "list") { list(); exit(0); }
+elsif ($ARGV[0] eq "copyout") { open O, ">$ARGV[3]"; print O $ARGV[2], "\n"; close O; exit(0); }
+exit(1);
diff --git a/src/vfs/extfs/helpers/s3+.in b/src/vfs/extfs/helpers/s3+.in
new file mode 100644
index 0000000..f5e4b90
--- /dev/null
+++ b/src/vfs/extfs/helpers/s3+.in
@@ -0,0 +1,490 @@
+#! @PYTHON@
+# -*- coding: utf-8 -*-
+
+#
+# Midnight Commander compatible EXTFS for accessing Amazon Web Services S3.
+# Written by Jakob Kemi <jakob.kemi@gmail.com> 2009
+#
+# This program 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.
+#
+# This program 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/>.
+#
+#
+# Notes:
+# This EXTFS exposes buckets as directories and keys as files
+# Due to EXTFS limitations all buckets & keys have to be read initially which might
+# take quite some time.
+# Tested on Debian with Python 2.4-2.6 and boto 1.4c and 1.6b
+# (Python 2.6 might need -W ignore::DeprecationWarning due to boto using
+# deprecated module Popen2)
+#
+#
+# Installation:
+# Make sure that boto <http://code.google.com/p/boto> (python-boto in Debian) is installed.
+# Preferably pytz (package python-tz in Debian) should be installed as well.
+#
+# Save as executable file /usr/libexec/mc/extfs/s3 (or wherever your mc expects to find extfs modules)
+#
+# Settings: (should be set via environment)
+# Required:
+# AWS_ACCESS_KEY_ID : Amazon AWS access key (required)
+# AWS_SECRET_ACCESS_KEY : Amazon AWS secret access key (required)
+# Optional:
+# MCVFS_EXTFS_S3_LOCATION : where to create new buckets: "EU" - default, "USWest", "APNortheast" etc.
+# MCVFS_EXTFS_S3_DEBUGFILE : write debug info to this file (no info by default)
+# MCVFS_EXTFS_S3_DEBUGLEVEL : debug messages level ("WARNING" - default, "DEBUG" - verbose)
+#
+#
+# Usage:
+# Open dialog "Quick cd" (<alt-c>) and type: s3:// <enter> (or simply type `cd s3://' in shell line)
+#
+#
+# History:
+#
+# 2015-07-22 Dmitry Koterov <dmitry.koterov@gmail.com>
+# - Support for non-ASCII characters in filenames (system encoding detection).
+#
+# 2015-05-21 Dmitry Koterov <dmitry.koterov@gmail.com>
+# - Resolve "Please use AWS4-HMAC-SHA256" error: enforce the new V4 authentication method.
+# It is required in many (if not all) locations nowadays.
+# - Now s3+ works with buckets in different regions: locations are auto-detected.
+# - Debug level specification support (MCVFS_EXTFS_S3_DEBUGLEVEL).
+#
+# 2009-02-07 Jakob Kemi <jakob.kemi@gmail.com>
+# - Updated instructions.
+# - Improved error reporting.
+#
+# 2009-02-06 Jakob Kemi <jakob.kemi@gmail.com>
+# - Threaded list command.
+# - Handle rm of empty "subdirectories" (as seen in mc).
+# - List most recent datetime and total size of keys as directory properties.
+# - List modification time in local time.
+#
+# 2009-02-05 Jakob Kemi <jakob.kemi@gmail.com>
+# - Initial version.
+#
+
+import sys
+import os
+import time
+import re
+import datetime
+
+
+import boto
+from boto.s3.connection import S3Connection
+from boto.exception import BotoServerError
+
+
+# Get settings from environment
+USER=os.getenv('USER','0')
+AWS_ACCESS_KEY_ID=os.getenv('AWS_ACCESS_KEY_ID')
+AWS_SECRET_ACCESS_KEY=os.getenv('AWS_SECRET_ACCESS_KEY')
+S3LOCATION=os.getenv('MCVFS_EXTFS_S3_LOCATION', 'EU')
+DEBUGFILE=os.getenv('MCVFS_EXTFS_S3_DEBUGFILE')
+DEBUGLEVEL=os.getenv('MCVFS_EXTFS_S3_DEBUGLEVEL', 'WARNING')
+
+if not AWS_ACCESS_KEY_ID or not AWS_SECRET_ACCESS_KEY:
+ sys.stderr.write('Missing AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY environment variables.\n')
+ sys.exit(1)
+
+# Setup logging
+if DEBUGFILE:
+ import logging
+ logging.basicConfig(
+ filename=DEBUGFILE,
+ level=logging.DEBUG,
+ format='%(asctime)s %(levelname)s %(message)s')
+ logging.getLogger('boto').setLevel(getattr(logging, DEBUGLEVEL))
+else:
+ class Void(object):
+ def __getattr__(self, attr):
+ return self
+ def __call__(self, *args, **kw):
+ return self
+ logging = Void()
+
+logger = logging.getLogger('s3extfs')
+
+
+def __fix_io_encoding(last_resort_default='UTF-8'):
+ """
+ The following code is needed to work with non-ASCII characters in filenames.
+ We're trying hard to detect the system encoding.
+ """
+ import codecs
+ import locale
+ for var in ('stdin', 'stdout', 'stderr'):
+ if getattr(sys, var).encoding is None:
+ enc = None
+ if enc is None:
+ try:
+ enc = locale.getpreferredencoding()
+ except:
+ pass
+ if enc is None:
+ try:
+ enc = sys.getfilesystemencoding()
+ except:
+ pass
+ if enc is None:
+ try:
+ enc = sys.stdout.encoding
+ except:
+ pass
+ if enc is None:
+ enc = last_resort_default
+ setattr(sys, var, codecs.getwriter(enc)(getattr(sys, var), 'strict'))
+__fix_io_encoding()
+
+
+def threadmap(fun, iterable, maxthreads=16):
+ """
+ Quick and dirty threaded version of builtin method map.
+ Propagates exception safely.
+ """
+ from threading import Thread
+ import Queue
+
+ items = list(iterable)
+ nitems = len(items)
+ if nitems < 2:
+ return map(fun, items)
+
+ # Create and fill input queue
+ input = Queue.Queue()
+ output = Queue.Queue()
+
+ for i,item in enumerate(items):
+ input.put( (i,item) )
+
+ class WorkThread(Thread):
+ """
+ Takes one item from input queue (thread terminates when input queue is empty),
+ performs fun, puts result in output queue
+ """
+ def run(self):
+ while True:
+ try:
+ (i,item) = input.get_nowait()
+ try:
+ result = fun(item)
+ output.put( (i,result) )
+ except:
+ output.put( (None,sys.exc_info()) )
+ except Queue.Empty:
+ return
+
+ # Start threads
+ for i in range( min(len(items), maxthreads) ):
+ t = WorkThread()
+ t.setDaemon(True)
+ t.start()
+
+ # Wait for all threads to finish & collate results
+ ret = []
+ for i in range(nitems):
+ try:
+ i,res = output.get()
+ if i == None:
+ raise res[0],res[1],res[2]
+ except Queue.Empty:
+ break
+ ret.append(res)
+
+ return ret
+
+logger.debug('started')
+
+if S3LOCATION.upper() == "EU":
+ S3LOCATION = "eu-central-1"
+if S3LOCATION.upper() == "US":
+ S3LOCATION = "us-east-1"
+for att in dir(boto.s3.connection.Location):
+ v = getattr(boto.s3.connection.Location, att)
+ if type(v) is str and att.lower() == S3LOCATION.lower():
+ S3LOCATION = v
+ break
+logger.debug('Using location %s for new buckets', S3LOCATION)
+
+
+def get_connection(location):
+ """
+ Creates a connection to the specified region.
+ """
+ os.environ['S3_USE_SIGV4'] = 'True' # only V4 method is supported in all locations.
+ return boto.s3.connect_to_region(
+ location,
+ aws_access_key_id=AWS_ACCESS_KEY_ID,
+ aws_secret_access_key=AWS_SECRET_ACCESS_KEY
+ )
+
+
+# Global S3 default connection.
+s3 = get_connection('us-east-1')
+
+
+def get_bucket(name):
+ """
+ Returns a bucket by its name, no matter what region is it in.
+ """
+ try:
+ b = s3.get_bucket(name, validate=False)
+ b.get_location() # just to raise an exception on error
+ return b
+ except boto.exception.S3ResponseError, e:
+ # Seems this is the only proper way to switch to the bucket's region.
+ # Requesting of the default region for "?location" does not work unfortunately.
+ m = re.search(r'<Region>(.*?)</Region>', e.body)
+ if m:
+ return get_connection(m.group(1)).get_bucket(name)
+ raise
+
+
+logger.debug('argv: ' + str(sys.argv))
+try:
+ cmd = sys.argv[1]
+ args = sys.argv[2:]
+except:
+ sys.stderr.write('This program should be called from within MC\n')
+ sys.exit(1)
+
+
+def handleServerError(msg):
+ e = sys.exc_info()
+ msg += ', reason: ' + e[1].reason
+ logger.error(msg, exc_info=e)
+ sys.stderr.write(msg+'\n')
+ sys.exit(1)
+
+#
+# Lists all S3 contents
+#
+if cmd == 'list':
+ if len(args) > 0:
+ path = args[0]
+ else:
+ path = ''
+
+ logger.info('list')
+
+ rs = s3.get_all_buckets()
+
+ # Import python timezones (pytz)
+ try:
+ import pytz
+ except:
+ logger.warning('Missing pytz module, timestamps will be off')
+ # A fallback UTC tz stub
+ class pytzutc(datetime.tzinfo):
+ def __init__(self):
+ datetime.tzinfo.__init__(self)
+ self.utc = self
+ self.zone = 'UTC'
+ def utcoffset(self, dt):
+ return datetime.timedelta(0)
+ def tzname(self, dt):
+ return "UTC"
+ def dst(self, dt):
+ return datetime.timedelta(0)
+ pytz = pytzutc()
+
+
+ # Find timezone
+ # (yes, timeZONE as in _geographic zone_ not EST/CEST or whatever crap we get from time.tzname)
+ # http://regebro.wordpress.com/2008/05/10/python-and-time-zones-part-2-the-beast-returns/
+ def getGuessedTimezone():
+ # 1. check TZ env. var
+ try:
+ tz = os.getenv('TZ', '')
+ return pytz.timezone(tz)
+ except:
+ pass
+ # 2. check if /etc/timezone exists (Debian at least)
+ try:
+ if os.path.isfile('/etc/timezone'):
+ tz = open('/etc/timezone', 'r').readline().strip()
+ return pytz.timezone(tz)
+ except:
+ pass
+ # 3. check if /etc/localtime is a _link_ to something useful
+ try:
+ if os.path.islink('/etc/localtime'):
+ link = os.readlink('/etc/localtime')
+ tz = '/'.join(link.split(os.path.sep)[-2:])
+ return pytz.timezone(tz)
+ except:
+ pass
+ # 4. use time.tzname which will probably be wrong by an hour 50% of the time.
+ try:
+ return pytz.timezone(time.tzname[0])
+ except:
+ pass
+ # 5. use plain UTC ...
+ return pytz.utc
+
+ tz=getGuessedTimezone()
+ logger.debug('Using timezone: ' + tz.zone)
+
+ # AWS time is on format: 2009-01-07T16:43:39.000Z
+ # we "want" MM-DD-YYYY hh:mm (in localtime)
+ expr = re.compile(r'^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.\d{3}Z$')
+ def convDate(awsdatetime):
+ m = expr.match(awsdatetime)
+ ye,mo,da,ho,mi,se = map(int,m.groups())
+
+ dt = datetime.datetime(ye,mo,da,ho,mi,se, tzinfo=pytz.utc)
+ return dt.astimezone(tz).strftime('%m-%d-%Y %H:%M')
+
+
+ def bucketList(b):
+ b = get_bucket(b.name) # get the bucket at its own region
+ totsz = 0
+ mostrecent = '1970-01-01T00:00:00.000Z'
+ ret = []
+ for k in b.list():
+ if k.name.endswith('/'):
+ # Sometimes someone create S3 keys which are ended with "/".
+ # Extfs cannot work with them as with files, and such keys may
+ # hide same-name directories, so we skip them.
+ continue
+ mostrecent = max(mostrecent, k.last_modified)
+ datetime = convDate(k.last_modified)
+ ret.append('%10s %3d %-8s %-8s %d %s %s\n' % (
+ '-rw-r--r--', 1, USER, USER, k.size, datetime, b.name+'/'+k.name)
+ )
+ totsz += k.size
+
+ datetime=convDate(mostrecent)
+ sys.stdout.write('%10s %3d %-8s %-8s %d %s %s\n' % (
+ 'drwxr-xr-x', 1, USER, USER, totsz, datetime, b.name)
+ )
+ for line in ret:
+ sys.stdout.write(line)
+
+ threadmap(bucketList, rs)
+
+#
+# Fetch file from S3
+#
+elif cmd == 'copyout':
+ archivename = args[0]
+ storedfilename = args[1]
+ extractto = args[2]
+
+ bucket,key = storedfilename.split('/', 1)
+ logger.info('copyout bucket: %s, key: %s'%(bucket, key))
+
+ try:
+ b = get_bucket(bucket)
+ k = b.get_key(key)
+
+ out = open(extractto, 'w')
+
+ k.open(mode='r')
+ for buf in k:
+ out.write(buf)
+ k.close()
+ out.close()
+ except BotoServerError:
+ handleServerError('Unable to fetch key "%s"'%(key))
+
+#
+# Upload file to S3
+#
+elif cmd == 'copyin':
+ archivename = args[0]
+ storedfilename = args[1]
+ sourcefile = args[2]
+
+ bucket,key = storedfilename.split('/', 1)
+ logger.info('copyin bucket: %s, key: %s'%(bucket, key))
+
+ try:
+ b = get_bucket(bucket)
+ k = b.new_key(key)
+ k.set_contents_from_file(fp=open(sourcefile,'r'))
+ except BotoServerError:
+ handleServerError('Unable to upload key "%s"' % (key))
+
+#
+# Remove file from S3
+#
+elif cmd == 'rm':
+ archivename = args[0]
+ storedfilename = args[1]
+
+ bucket,key = storedfilename.split('/', 1)
+ logger.info('rm bucket: %s, key: %s'%(bucket, key))
+
+ try:
+ b = get_bucket(bucket)
+ b.delete_key(key)
+ except BotoServerError:
+ handleServerError('Unable to remove key "%s"' % (key))
+
+#
+# Create directory
+#
+elif cmd == 'mkdir':
+ archivename = args[0]
+ dirname = args[1]
+
+ logger.info('mkdir dir: %s' %(dirname))
+ if '/' in dirname:
+ logger.warning('skipping mkdir')
+ pass
+ else:
+ bucket = dirname
+ try:
+ get_connection(S3LOCATION).create_bucket(bucket, location=S3LOCATION)
+ except BotoServerError:
+ handleServerError('Unable to create bucket "%s"' % (bucket))
+
+#
+# Remove directory
+#
+elif cmd == 'rmdir':
+ archivename = args[0]
+ dirname = args[1]
+
+ logger.info('rmdir dir: %s' %(dirname))
+ if '/' in dirname:
+ logger.warning('skipping rmdir')
+ pass
+ else:
+ bucket = dirname
+ try:
+ b = get_bucket(bucket)
+ b.connection.delete_bucket(b)
+ except BotoServerError:
+ handleServerError('Unable to delete bucket "%s"' % (bucket))
+
+#
+# Run from S3
+#
+elif cmd == 'run':
+ archivename = args[0]
+ storedfilename = args[1]
+ arguments = args[2:]
+
+ bucket,key = storedfilename.split('/', 1)
+ logger.info('run bucket: %s, key: %s'%(bucket, key))
+
+ os.execv(storedfilename, arguments)
+else:
+ logger.error('unhandled, bye')
+ sys.exit(1)
+
+logger.debug('command handled')
+sys.exit(0)
+
diff --git a/src/vfs/extfs/helpers/trpm b/src/vfs/extfs/helpers/trpm
new file mode 100755
index 0000000..d9a7930
--- /dev/null
+++ b/src/vfs/extfs/helpers/trpm
@@ -0,0 +1,176 @@
+#! /bin/sh
+#
+# Browse contents of an installed RPM package.
+# This filesystem works on the entries of the "rpms" filesystem.
+#
+# Written by Erik Troan (ewt@redhat.com) 1996
+# Jakub Jelinek (jj@sunsite.mff.cuni.cz) 1996
+# Tomasz K³oczko (kloczek@rudy.mif.pg.gda.pl) 1997
+# minor changes by Wojtek Pilorz (wpilorz@bdk.lublin.pl) 1997
+# minor changes by Michele Marziani (marziani@fe.infn.it) 1997
+# slight changes to put rpm to Trpm by Balazs Nagy (julian7@kva.hu) 1998
+# locale bugfix by Michal Svec (rebel@penguin.cz) 2000
+# (C) 1996 The Free Software Foundation.
+#
+#
+
+# override any locale for dates
+unset LC_ALL
+LC_TIME=C
+export LC_TIME
+
+if rpm --nosignature --version >/dev/null 2>&1; then
+ RPM="rpm --nosignature"
+else
+ RPM="rpm"
+fi
+
+mcrpmfs_list ()
+{
+ # set MCFASTRPM_DFLT to 1 for faster rpm files handling by default, to 0 for
+ # slower handling
+ MCFASTRPM_DFLT=0
+ if test -z "$MCFASTRPM"; then
+ MCFASTRPM=$MCFASTRPM_DFLT
+ fi
+ FILEPREF="-r--r--r-- 1 root root "
+ DESC=`$RPM -qi -- "$1"`
+ DATE=`$RPM -q --qf "%{BUILDTIME:date}" -- "$1" | cut -c 5-11,21-24`
+ HEADERSIZE=`echo "$DESC" | wc -c`
+ echo "-r--r--r-- 1 root root $HEADERSIZE $DATE HEADER"
+ echo "-r-xr-xr-x 1 root root 40 $DATE UNINSTALL"
+ echo "dr-xr-xr-x 3 root root 0 $DATE INFO"
+ echo "$FILEPREF 0 $DATE INFO/NAME-VERSION-RELEASE"
+ echo "$FILEPREF 0 $DATE INFO/GROUP"
+ echo "$FILEPREF 0 $DATE INFO/BUILDHOST"
+ echo "$FILEPREF 0 $DATE INFO/SOURCERPM"
+ if test "$MCFASTRPM" = 0 ; then
+ test "`$RPM -q --qf \"%{DISTRIBUTION}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/DISTRIBUTION"
+ test "`$RPM -q --qf \"%{VENDOR}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/VENDOR"
+ test "`$RPM -q --qf \"%{DESCRIPTION}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/DESCRIPTION"
+ test "`$RPM -q --qf \"%{SUMMARY}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/SUMMARY"
+ if test "`$RPM -q --qf \"%{RPMTAG_PREIN}%{RPMTAG_POSTIN}%{RPMTAG_PREUN}%{RPMTAG_POSTUN}%{VERIFYSCRIPT}\" -- "$1"`" != "(none)(none)(none)(none)(none)"; then
+ echo "dr-xr-xr-x 1 root root 0 $DATE INFO/SCRIPTS"
+ test "`$RPM -q --qf \"%{RPMTAG_PREIN}\" -- "$1"`" = '(none)' ||
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/PREIN"
+ test "`$RPM -q --qf \"%{RPMTAG_POSTIN}\" -- "$1"`" = '(none)' ||
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/POSTIN"
+ test "`$RPM -q --qf \"%{RPMTAG_PREUN}\" -- "$1"`" = '(none)' ||
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/PREUN"
+ test "`$RPM -q --qf \"%{RPMTAG_POSTUN}\" -- "$1"`" = '(none)' ||
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/POSTUN"
+ test "`$RPM -q --qf \"%{VERIFYSCRIPT}\" -- "$1"`" = '(none)' ||
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/VERIFYSCRIPT"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/ALL"
+ fi
+ else
+ echo "$FILEPREF 0 $DATE INFO/DISTRIBUTION"
+ echo "$FILEPREF 0 $DATE INFO/VENDOR"
+ echo "$FILEPREF 0 $DATE INFO/DESCRIPTION"
+ echo "$FILEPREF 0 $DATE INFO/SUMMARY"
+ echo "dr-xr-xr-x 1 root root 0 $DATE INFO/SCRIPTS"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/PREIN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/POSTIN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/PREUN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/POSTUN"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/VERIFYSCRIPT"
+ echo "$FILEPREF 0 $DATE INFO/SCRIPTS/ALL"
+ fi
+ if test "$MCFASTRPM" = 0 ; then
+ test "`$RPM -q --qf \"%{PACKAGER}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/PACKAGER"
+ test "`$RPM -q --qf \"%{URL}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/URL"
+ test "`$RPM -q --qf \"%{EPOCH}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/EPOCH"
+ test "`$RPM -q --qf \"%{LICENSE}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/LICENSE"
+ else
+ echo "$FILEPREF 0 $DATE INFO/PACKAGER"
+ echo "$FILEPREF 0 $DATE INFO/URL"
+ echo "$FILEPREF 0 $DATE INFO/EPOCH"
+ echo "$FILEPREF 0 $DATE INFO/LICENSE"
+ fi
+ echo "$FILEPREF 0 $DATE INFO/BUILDTIME"
+ echo "$FILEPREF 0 $DATE INFO/RPMVERSION"
+ echo "$FILEPREF 0 $DATE INFO/OS"
+ echo "$FILEPREF 0 $DATE INFO/SIZE"
+ if test "$MCFASTRPM" != 0 ; then
+ $RPM -q --qf "[%{REQUIRENAME}\n]" -- "$1" | grep "(none)" > /dev/null ||
+ echo "$FILEPREF 0 $DATE INFO/REQUIRENAME"
+ $RPM -q --qf "[%{OBSOLETES}\n]" -- "$1" | grep "(none)" > /dev/null ||
+ echo "$FILEPREF 0 $DATE INFO/OBSOLETES"
+ $RPM -q --qf "[%{PROVIDES}\n]" -- "$1" | grep "(none)" > /dev/null ||
+ echo "$FILEPREF 0 $DATE INFO/PROVIDES"
+ $RPM -q --qf "[%{CONFLICTS}\n]" -- "$1" | grep "(none)" > /dev/null ||
+ echo "$FILEPREF 0 $DATE INFO/CONFLICTS"
+ test "`$RPM -q --qf \"%{CHANGELOGTEXT}\" -- "$1"`" = "(none)" ||
+ echo "$FILEPREF 0 $DATE INFO/CHANGELOG"
+ else
+ echo "$FILEPREF 0 $DATE INFO/REQUIRENAME"
+ echo "$FILEPREF 0 $DATE INFO/OBSOLETES"
+ echo "$FILEPREF 0 $DATE INFO/PROVIDES"
+ echo "$FILEPREF 0 $DATE INFO/CONFLICTS"
+ echo "$FILEPREF 0 $DATE INFO/CHANGELOG"
+ fi
+
+ $RPM -qlv -- "$1" | grep '^[A-Za-z0-9-]'
+}
+
+mcrpmfs_copyout ()
+{
+ case "$2" in
+ HEADER) $RPM -qi -- "$1" > "$3"; exit 0;;
+ UNINSTALL) echo "# Run this to uninstall this RPM package" > "$3"; exit 0;;
+ INFO/NAME-VERSION-RELEASE) $RPM -q --qf "%{NAME}-%{VERSION}-%{RELEASE}\n" -- "$1" > "$3"; exit 0;;
+ INFO/RELEASE) $RPM -q --qf "%{RELEASE}\n" -- "$1" > "$3"; exit 0;;
+ INFO/GROUP) $RPM -q --qf "%{GROUP}\n" -- "$1" > "$3"; exit 0;;
+ INFO/DISTRIBUTION) $RPM -q --qf "%{DISTRIBUTION}\n" -- "$1" > "$3"; exit 0;;
+ INFO/VENDOR) $RPM -q --qf "%{VENDOR}\n" -- "$1" > "$3"; exit 0;;
+ INFO/BUILDHOST) $RPM -q --qf "%{BUILDHOST}\n" -- "$1" > "$3"; exit 0;;
+ INFO/SOURCERPM) $RPM -q --qf "%{SOURCERPM}\n" -- "$1" > "$3"; exit 0;;
+ INFO/DESCRIPTION) $RPM -q --qf "%{DESCRIPTION}\n" -- "$1" > "$3"; exit 0;;
+ INFO/PACKAGER) $RPM -q --qf "%{PACKAGER}\n" -- "$1" > "$3"; exit 0;;
+ INFO/URL) $RPM -q --qf "%{URL}\n" -- "$1" > "$3"; exit 0;;
+ INFO/BUILDTIME) $RPM -q --qf "%{BUILDTIME:date}\n" -- "$1" > "$3"; exit 0;;
+ INFO/EPOCH) $RPM -q --qf "%{EPOCH}\n" -- "$1" > "$3"; exit 0;;
+ INFO/LICENSE) $RPM -q --qf "%{LICENSE}\n" -- "$1" > "$3"; exit 0;;
+ INFO/RPMVERSION) $RPM -q --qf "%{RPMVERSION}\n" -- "$1" > "$3"; exit 0;;
+ INFO/REQUIRENAME) $RPM -q --qf "[%{REQUIRENAME} %{REQUIREFLAGS:depflags} %{REQUIREVERSION}\n]" -- "$1" > "$3"; exit 0;;
+ INFO/OBSOLETES) $RPM -q --qf "[%{OBSOLETENAME} %|OBSOLETEFLAGS?{%{OBSOLETEFLAGS:depflags} %{OBSOLETEVERSION}}:{}|\n]" -- "$1" > "$3"; exit 0;;
+ INFO/PROVIDES) $RPM -q --qf "[%{PROVIDES}\n]" -- "$1" > "$3"; exit 0;;
+ INFO/CONFLICTS) $RPM -q --qf "[%{CONFLICTS}\n]" -- "$1" > "$3"; exit 0;;
+ INFO/SCRIPTS/PREIN) $RPM -q --qf "%{RPMTAG_PREIN}\n" -- "$1" > "$3"; exit 0;;
+ INFO/SCRIPTS/POSTIN) $RPM -q --qf "%{RPMTAG_POSTIN}\n" -- "$1" > "$3"; exit 0;;
+ INFO/SCRIPTS/PREUN) $RPM -q --qf "%{RPMTAG_PREUN}\n" -- "$1" > "$3"; exit 0;;
+ INFO/SCRIPTS/POSTUN) $RPM -q --qf "%{RPMTAG_POSTUN}\n" -- "$1" > "$3"; exit 0;;
+ INFO/SCRIPTS/VERIFYSCRIPT) $RPM -q --qf "%{VERIFYSCRIPT}\n" -- "$1" > "$3"; exit 0;;
+ INFO/SCRIPTS/ALL) $RPM -q --scripts -- "$1" > "$3"; exit 0;;
+ INFO/SUMMARY) $RPM -q --qf "%{SUMMARY}\n" -- "$1" > "$3"; exit 0;;
+ INFO/OS) $RPM -q --qf "%{OS}\n" -- "$1" > "$3"; exit 0;;
+ INFO/CHANGELOG) $RPM -q --qf "[* %{CHANGELOGTIME:date} %{CHANGELOGNAME}\n%{CHANGELOGTEXT}\n\n]\n" -- "$1" > "$3"; exit 0;;
+ INFO/SIZE) $RPM -q --qf "%{SIZE} bytes\n" -- "$1" > "$3"; exit 0;;
+ *)
+ cp "/$2" "$3"
+ esac
+}
+
+mcrpmfs_run ()
+{
+ case "$2" in
+ UNINSTALL) echo "Uninstalling $1"; rpm -e -- "$1"; exit 0;;
+ esac
+}
+
+name=`sed 's/.*\///;s/\.trpm$//' "$2"`
+
+case "$1" in
+ list) mcrpmfs_list "$name"; exit 0;;
+ copyout) mcrpmfs_copyout "$name" "$3" "$4"; exit 0;;
+ run) mcrpmfs_run "$name" "$3"; exit 1;;
+esac
+exit 1
diff --git a/src/vfs/extfs/helpers/u7z b/src/vfs/extfs/helpers/u7z
new file mode 100755
index 0000000..91301c3
--- /dev/null
+++ b/src/vfs/extfs/helpers/u7z
@@ -0,0 +1,135 @@
+#! /bin/sh
+#
+# extfs support for p7zip
+# Written by Pavel Roskin <proski@gnu.org>
+# Some Bugfixes/workarounds by Sergiy Niskorodov <sgh@mail.zp.ua>
+#
+# This program 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.
+#
+# This program 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/>.
+
+P7ZIP=`which 7z 2>/dev/null` \
+ || P7ZIP=`which 7zz 2>/dev/null` \
+ || P7ZIP=`which 7za 2>/dev/null` \
+ || P7ZIP=`which 7zr 2>/dev/null` \
+ || P7ZIP=""
+
+# Let the test framework hook in:
+P7ZIP=${MC_TEST_EXTFS_LIST_CMD:-$P7ZIP}
+STAT=${MC_TEST_EXTFS_U7Z_STAT:-stat}
+
+mcu7zip_list ()
+{
+ # Symlinks are not shown - no idea how to distinguish them
+ # Read-only files are not shown as such - it's rarely useful
+
+ ugid="`id -nu` `id -ng`"
+
+ date_re='^\(....\)-\(..\)-\(..\) \(..:..:..\)' # 19 chars.
+ date_mc='\2-\3-\1 \4'
+ empty_date_re='^ \{19\}'
+
+ size_re='............' # 12 chars.
+ empty_size_re=' \{12\}'
+ zero_size=' 0'
+
+ # archive entries can have no datetime info, 7z will use archive file datetime
+ date_archive=`$STAT -c %y "$1" 2>/dev/null | sed -n "s/${date_re}.*/${date_mc}/p" 2>/dev/null`
+ [ "${date_archive}"x = x ] && date_archive=`ls -lan "$1" 2>/dev/null | awk '{print $6, $7, $8}' 2>/dev/null`
+ [ "${date_archive}"x = x ] && date_archive="01-01-1970 00:00:00"
+
+ $P7ZIP l "$1" | sed -n "
+
+ # If the uncompressed size is missing, we copy the compressed size onto it.
+ #
+ # But first, if the compressed size is missing too, set it to zero:
+ s/^\(.\{19\} [D.]....\) $empty_size_re $empty_size_re/\1 $zero_size $zero_size/
+ # Next, do the copy:
+ s/^\(.\{19\} [D.]....\) $empty_size_re \($size_re\)/\1 \2 \2/
+ #
+ # (We use '.\{19\}' as the date may be missing. It may give false positives
+ # but we don't mind: the printing commands that follow use strict patterns.).
+
+ # Handle directories.
+ s/$date_re D.... $size_re $size_re\(.*\)/drwxr-xr-x 1 $ugid 0 $date_mc \5/p
+ s/$empty_date_re D.... $size_re $size_re\(.*\)/drwxr-xr-x 1 $ugid 0 $date_archive \1/p
+
+ # Handle normal files.
+ s/$date_re \..... \($size_re\) $size_re\(.*\)/-rw-r--r-- 1 $ugid \5 $date_mc \6/p
+ s/$empty_date_re \..... \($size_re\) $size_re\(.*\)/-rw-r--r-- 1 $ugid \1 $date_archive \2/p
+ "
+}
+
+mcu7zip_copyout ()
+{
+ #first we check if we have old p7zip archive with prefix ./ in filename
+ $P7ZIP l "$1" "$2" | grep -q "0 files, 0 folders" && \
+ EXFNAME='*./'"$2" || EXFNAME="$2"
+ $P7ZIP e -so "$1" "$EXFNAME" > "$3" 2>/dev/null
+}
+
+mcu7zip_copyin ()
+{
+ $P7ZIP a -si"$2" "$1" <"$3" >/dev/null 2>&1
+}
+
+mcu7zip_mkdir ()
+{
+ dir=`mktemp -d "${MC_TMPDIR:-/tmp}/mctmpdir-u7z.XXXXXX"` || exit 1
+ mkdir -p "$dir"/"$2"
+ $P7ZIP a -w"$dir" "$1" "$dir"/"$2" >/dev/null 2>&1
+ rm -rf "$dir"
+}
+
+mcu7zip_rm ()
+{
+ # NOTE: Version 4.20 fails to delete files in subdirectories
+ #first we check if we have old p7zip archive with prefix ./ in filename
+ $P7ZIP l "$1" "$2" | grep -q "0 files, 0 folders" && \
+ EXFNAME='*./'"$2" || EXFNAME="$2"
+ $P7ZIP d "$1" "$EXFNAME" 2>&1 | grep -q E_NOTIMPL > /dev/null 2>&1 && \
+ { printf "Function not implemented...\n7z cannot delete from solid archive." >&2 ; exit 1 ; }
+}
+
+mcu7zip_rmdir ()
+{
+ #first we check if we have old p7zip archive with prefix ./ in filename
+ $P7ZIP l "$1" "$2" | grep -q "0 files, 0 folders" && \
+ EXFNAME='*./'"$2" || EXFNAME="$2"
+ $P7ZIP d "$1" "$EXFNAME"/ 2>&1 | grep -q E_NOTIMPL > /dev/null 2>&1 && \
+ { printf "Function not implemented...\n7z cannot delete from solid archive." >&2 ; exit 1 ; }
+}
+
+# override any locale for dates
+LC_DATE=C
+export LC_DATE
+
+umask 077
+
+if [ -z "$P7ZIP" ]; then
+ echo "Error: could not find p7zip (looked for 7z, 7za and 7zr)" >&2
+ exit 1
+fi
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list) mcu7zip_list "$@" | sort -k 8 ;;
+ copyout) mcu7zip_copyout "$@" ;;
+ copyin) mcu7zip_copyin "$@" ;;
+ mkdir) mcu7zip_mkdir "$@" ;;
+ rm) mcu7zip_rm "$@" ;;
+ rmdir) mcu7zip_rmdir "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/uace.in b/src/vfs/extfs/helpers/uace.in
new file mode 100644
index 0000000..22eae30
--- /dev/null
+++ b/src/vfs/extfs/helpers/uace.in
@@ -0,0 +1,67 @@
+#! /bin/sh
+
+#
+# ACE Virtual filesystem executive v0.1
+# Works with unace v2.5
+
+# Note: There are two packages for Debian: 'unace' (v1.2b) and
+# 'unace-nonfree' (v2.x). This script supports 'unace-nonfree' only.
+# 'unace', which supports only old versions of ACE archives (and is
+# therefore of little use), uses the pipe character to separate columns
+# in its listing format.
+
+# Copyright (C) 2008 Jacques Pelletier
+# May be distributed under the terms of the GNU Public License
+# <jpelletier@ieee.org>
+#
+
+# Define which archiver you are using with appropriate options
+ACE_LIST=${MC_TEST_EXTFS_LIST_CMD:-"unace l"}
+ACE_GET="unace x"
+# ACE_PUT="unace ?" not available
+
+# The 'list' command executive
+
+# Unace: DD.MM.YY HH:MM packed size ratio file
+# ls:
+mc_ace_fs_list()
+{
+ if [ "x$UID" = "x" ]; then
+ UID=`id -ru 2>/dev/null`
+ if [ "x$UID" = "x" ]; then
+ UID=0
+ fi
+ fi
+ $ACE_LIST "$1" | @AWK@ -v uid=$UID '
+/%/ {
+ split($1,date,".")
+
+ if (date[3] > 50)
+ date[3]=date[3] + 1900
+ else
+ date[3]=date[3] + 2000
+
+ printf "-rw-r--r-- 1 %-8d %-8d %8d %02d-%02d-%04d %s %s\n", uid, 0, $4, date[2], date[1], date[3], $2, $6
+}' 2>/dev/null
+ exit 0
+}
+
+# Command: copyout archivename storedfilename extractto
+mc_ace_fs_copyout()
+{
+ $ACE_GET "$1" "$2" > /dev/null 2>&1
+ mv "$2" "$3"
+}
+
+# The main routine
+umask 077
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list) mc_ace_fs_list "$@" ;;
+ copyout) mc_ace_fs_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/ualz.in b/src/vfs/extfs/helpers/ualz.in
new file mode 100644
index 0000000..d054553
--- /dev/null
+++ b/src/vfs/extfs/helpers/ualz.in
@@ -0,0 +1,68 @@
+#!/bin/sh
+#
+# Written by Pavel Roskin <proski@gnu.org>
+# (C) 2005 The Free Software Foundation.
+#
+#
+
+UNALZ=unalz
+
+mcualz_list ()
+{
+ $UNALZ -l "$1" | @AWK@ -v uid=`id -nu` -v gid=`id -ng` '
+{
+ if ($1 ~ /[0-9][0-9][:/][0-9][0-9][:/][0-9][0-9]$/)
+ {
+ # Kludge for non-POSIX date format in unalz 0.50
+ split($1, date, "[/:]")
+ if (length(date[1]) == 4) {
+ pdate = date[2] "/" date[3] "/" date[1]
+ } else {
+ pdate = date[1] "/" date[2] "/" date[3]
+ }
+
+ time=$2
+ perm=$3
+ size=$4
+ sub(/^ *[^ ]* *[^ ]* *[^ ]* *[^ ]* *[^ ]* */, "")
+ file=$0
+ gsub(/\\/, "/", file)
+ if (perm ~ /.D../)
+ perm = "drwxr-xr-x"
+ else
+ perm = "-rw-r--r--"
+ printf "%s 1 %s %s %d %s %s %s\n", perm, uid, gid, size, pdate, time, file
+ }
+}
+'
+}
+
+mcualz_copyout ()
+{
+ TMPDIR=`mktemp -d ${MC_TMPDIR:-/tmp}/mctmpdir-ualz.XXXXXX` || exit 1
+
+ # This is a workaround for a bug in unalz 0.50 - it crashes if the
+ # output directory is an absolute path.
+ dir=`dirname "$TMPDIR/$2"`
+ mkdir -p "$dir"
+
+ $UNALZ -d "$TMPDIR" "$1" "$2" >/dev/null
+ cat "$TMPDIR/$2" > "$3"
+ rm -rf "$TMPDIR"
+}
+
+# override any locale for dates
+LC_ALL=C
+export LC_ALL
+umask 077
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list) mcualz_list "$@" ;;
+ copyout) mcualz_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+
+exit 0
diff --git a/src/vfs/extfs/helpers/uar.in b/src/vfs/extfs/helpers/uar.in
new file mode 100644
index 0000000..269bdb6
--- /dev/null
+++ b/src/vfs/extfs/helpers/uar.in
@@ -0,0 +1,60 @@
+#!/bin/sh
+#
+# Written by Alex Kuchma <ask@bcs.zp.ua>
+# Alex Tkachenko <alex@bcs.zp.ua>
+# Updated by Vitezslav Samel <xsamel00@dcse.fee.vutbr.cz>
+#
+# (C) 1997, 1998 The Free Software Foundation.
+#
+#
+
+XAR=ar
+
+mcarfs_list ()
+{
+ # If $temp_replace string is part of the filename that part might get lost
+ temp_replace='Unique Separator String'
+ thisyear="`date +%Y`"
+ $XAR tv "$1" | sed 's,^,-,;s, , 1 ,;s,/, ,' |
+ sed -e "s/\( [0-2][0-9]\:[0-5][0-9]\)\( $thisyear \)\(.*\)/\1$temp_replace\3/" |
+ sed -e "s/\( [0-2][0-9]\:[0-5][0-9] \)\([12][0-9][0-9][0-9] \)\(.*\)/ \2\3/" |
+ sed -e "s/$temp_replace/ /"
+}
+
+mcarfs_copyout ()
+{
+ $XAR p "$1" "$2" > "$3"
+}
+
+mcarfs_copyin ()
+{
+ TMPDIR=`mktemp -d "${MC_TMPDIR:-/tmp}/mctmpdir-uar.XXXXXX"` || exit 1
+ name=`basename "$2"`
+ (cd "$TMPDIR" && cp -fp "$3" "$name" && $XAR r "$1" "$name")
+ rm -rf "$TMPDIR"
+}
+
+mcarfs_rm ()
+{
+ $XAR d "$1" "$2"
+}
+
+# override any locale for dates
+LC_ALL=C
+export LC_ALL
+
+umask 077
+case "$1" in
+ list) mcarfs_list "$2" ;;
+ copyout) shift; mcarfs_copyout "$@" ;;
+ copyin) shift; mcarfs_copyin "$@" ;;
+ rm) shift; mcarfs_rm "$@" ;;
+ mkdir|rmdir)
+ echo "mcarfs: ar archives cannot contain directories." 1>&2
+ exit 1;;
+ *)
+ echo "mcarfs: unknown command: \"$1\"." 1>&2
+ exit 1;;
+esac
+
+exit 0
diff --git a/src/vfs/extfs/helpers/uarc.in b/src/vfs/extfs/helpers/uarc.in
new file mode 100644
index 0000000..a81839a
--- /dev/null
+++ b/src/vfs/extfs/helpers/uarc.in
@@ -0,0 +1,92 @@
+#! /bin/sh
+
+#
+# ARC Virtual filesystem executive
+# Copyright (C) 2008 Jacques Pelletier
+# May be distributed under the terms of the GNU Public License
+# <jpelletier@ieee.org>
+#
+
+# Define which archiver you are using with appropriate options
+ARC_LIST=${MC_TEST_EXTFS_LIST_CMD:-"arc v"}
+ARC_GET="arc x"
+ARC_PUT="arc a"
+ARC_DEL="arc d"
+
+# The 'list' command executive
+
+mc_arc_fs_list()
+{
+ if [ "x$UID" = "x" ]; then
+ UID=`id -ru 2>/dev/null`
+ if [ "x$UID" = "x" ]; then
+ UID=0
+ fi
+ fi
+ $ARC_LIST "$1" | @AWK@ -v uid=$UID '
+BEGIN {
+ # Copied from uzoo.in.
+ split("Jan:Feb:Mar:Apr:May:Jun:Jul:Aug:Sep:Oct:Nov:Dec", month_list, ":")
+ for (i=1; i<=12; i++) {
+ month[month_list[i]] = i
+ }
+}
+/^Name/ { next }
+/===/ { next }
+/^Total/ { next }
+{
+ if ($8 > 50)
+ $8=$8 + 1900
+ else
+ $8=$8 + 2000
+
+ split($9, a, ":")
+
+ # convert AM/PM to 00-23
+ if (a[2] ~ /a$|p$/)
+ {
+ if (a[2] ~ /p$/)
+ a[1] = a[1]+12
+
+ a[2]=substr(a[2],1,2)
+ }
+
+ printf "-rw-r--r-- 1 %-8d %-8d %8d %02d-%02d-%04d %02d:%02d %s\n", uid, 0, $2, month[$7], $6, $8, a[1], a[2], $1
+}' 2>/dev/null
+ exit 0
+}
+
+# Command: copyout archivename storedfilename extractto
+mc_arc_fs_copyout()
+{
+ $ARC_GET "$1" "$2" 2> /dev/null
+ mv "$2" "$3"
+}
+
+# Command: copyin archivename storedfilename sourcefile
+mc_arc_fs_copyin()
+{
+ mv "$3" "$2"
+ $ARC_PUT "$1" "$2" 2> /dev/null
+}
+
+# Command: rm archivename storedfilename
+mc_arc_fs_rm()
+{
+ $ARC_DEL "$1" "$2" 2> /dev/null
+}
+
+# The main routine
+umask 077
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list) mc_arc_fs_list "$@" ;;
+ copyout) mc_arc_fs_copyout "$@" ;;
+ copyin) mc_arc_fs_copyin "$@" ;;
+ rm) mc_arc_fs_rm "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/uarj.in b/src/vfs/extfs/helpers/uarj.in
new file mode 100644
index 0000000..15549a0
--- /dev/null
+++ b/src/vfs/extfs/helpers/uarj.in
@@ -0,0 +1,75 @@
+#! /bin/sh
+#
+# Written by Viatcheslav Odintsov (2:5020/181)
+# (C) 2002 ARJ Software Russia.
+#
+# This is an updated parser for ARJ archives in Midnight Commander. You need
+# full ARJ rather than UNARJ. Open-source ARJ v 3.10 for Unix platforms can
+# be obtained here:
+#
+# - http://www.sourceforge.net/projects/arj/
+# - http://arj.sourceforge.net/
+
+
+ARJ="arj -+ -ja1"
+
+
+mcarjfs_list ()
+{
+ $ARJ v "$1" | @AWK@ -v uuid=$(id -ru) '
+ {
+ if (($0 ~ /^[0-9]+\) .*/)||($0 ~ /^------------ ---------- ---------- -----/)){
+ if (filestr ~ /^[0-9]+\) .*/) {
+ printf "%s 1 %-8d %-8d %8d %02d-%02d-%02d %02d:%02d %s%s\n", perm, uid, gid, size, date[2], date[3], date[1], time[1], time[2], file, symfile
+ perm=""
+ file=""
+ symfile=""
+ filestr=""
+ }
+ }
+
+ if ($0 ~ /^[0-9]+\) .*/) {
+ filestr=$0
+ sub(/^[0-9]*\) /, "")
+ file=$0
+ uid=uuid
+ gid=0
+ }
+
+ if ($0 ~ /^.* [0-9]+[\t ]+[0-9]+ [0-9]\.[0-9][0-9][0-9] [0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9].*/) {
+ size=$3
+ split($6, date, "-")
+ split($7, time, ":")
+ if ($8 ~ /^[rwx-]/) {perm=$8;}
+ else {perm="-rw-r--r--"}
+ }
+
+ if ($0 ~ /^[\t ]+SymLink -> .*/) {
+ symfile = " -> "$3
+ perm="l"substr(perm, 2)
+ }
+
+ if ($0 ~ /^[\t ]+Owner: UID [0-9]+\, GID [0-9]+/) {
+ uid=$3
+ gid=$5
+ owner=1
+ }
+ }'
+}
+
+
+mcarjfs_copyout ()
+{
+ $ARJ e -y "$1" "$2" -jw"$3" >/dev/null 2>/dev/null
+}
+
+
+umask 077
+cmd="$1"
+shift
+case "$cmd" in
+ list) mcarjfs_list "$@" ;;
+ copyout) mcarjfs_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/uc1541 b/src/vfs/extfs/helpers/uc1541
new file mode 100755
index 0000000..dc15b42
--- /dev/null
+++ b/src/vfs/extfs/helpers/uc1541
@@ -0,0 +1,702 @@
+#!/usr/bin/env python
+"""
+UC1541 Virtual filesystem
+
+Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
+Date: 2019-09-20
+Version: 3.3
+Licence: BSD
+source: https://bitbucket.org/gryf/uc1541
+mirror: https://github.com/gryf/uc1541
+"""
+
+import sys
+import re
+import os
+import gzip
+from subprocess import Popen, PIPE
+
+if os.getenv('UC1541_DEBUG'):
+ import logging
+ LOG = logging.getLogger('UC1541')
+ LOG.setLevel(logging.DEBUG)
+ FILE_HANDLER = logging.FileHandler("/tmp/uc1541.log")
+ FILE_FORMATTER = logging.Formatter("%(asctime)s %(levelname)-8s "
+ "%(lineno)s %(funcName)s - %(message)s")
+ FILE_HANDLER.setFormatter(FILE_FORMATTER)
+ FILE_HANDLER.setLevel(logging.DEBUG)
+ LOG.addHandler(FILE_HANDLER)
+else:
+ class LOG(object):
+ """
+ Dummy logger object. Does nothing.
+ """
+ @classmethod
+ def debug(*args, **kwargs):
+ pass
+
+ @classmethod
+ def info(*args, **kwargs):
+ pass
+
+ @classmethod
+ def warning(*args, **kwargs):
+ pass
+
+ @classmethod
+ def error(*args, **kwargs):
+ pass
+
+ @classmethod
+ def critical(*args, **kwargs):
+ pass
+
+
+SECLEN = 256
+
+
+def _ord(string_or_int):
+ """
+ Return an int value for the (possible) string passed in argument. This
+ function is for compatibility between python2 and python3, where single
+ element in byte string array is a string or an int respectively.
+ """
+ try:
+ return ord(string_or_int)
+ except TypeError:
+ return string_or_int
+
+
+def _get_raw(dimage):
+ """
+ Try to get contents of the D64 image either it's gzip compressed or not.
+ """
+ raw = None
+ with gzip.open(dimage, 'rb') as fobj:
+ # Although the common approach with gzipped files is to check the
+ # magic number, in this case there is no guarantee that first track
+ # does not contain exactly the same byte sequence as the magic number.
+ # So the only way left is to actually try to uncompress the file.
+ try:
+ raw = fobj.read()
+ except (IOError, OSError):
+ pass
+ if not raw:
+ with open(dimage, 'rb') as fobj:
+ raw = fobj.read()
+
+ return raw
+
+
+def _get_implementation(disk):
+ """
+ Check the file under fname and return right class for creating an object
+ corresponding for the file
+ """
+ len_map = {822400: D81, # 80 tracks
+ 819200: D81, # 80 tracks, 3200 error bytes
+ 349696: D71, # 70 tracks
+ 351062: D71, # 70 tracks, 1366 error bytes
+ 174848: D64, # usual d64 disc image, 35 tracks, no errors
+ 175531: D64, # 35 track, 683 error bytes
+ 196608: D64, # 40 track, no errors
+ 197376: D64} # 40 track, 768 error bytes
+
+ if disk[:32].startswith(b'C64'):
+ return # T64
+
+ return len_map.get(len(disk))(disk)
+
+
+class Disk(object):
+ """
+ Represent common disk interface
+ """
+ CHAR_MAP = {32: ' ', 33: '!', 34: '"', 35: '#', 37: '%', 38: '&', 39: "'",
+ 40: '(', 41: ')', 42: '*', 43: '+', 44: ',', 45: '-', 46: '.',
+ 47: '/', 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5',
+ 54: '6', 55: '7', 56: '8', 57: '9', 59: ';', 60: '<', 61: '=',
+ 62: '>', 63: '?', 64: '@', 65: 'a', 66: 'b', 67: 'c', 68: 'd',
+ 69: 'e', 70: 'f', 71: 'g', 72: 'h', 73: 'i', 74: 'j', 75: 'k',
+ 76: 'l', 77: 'm', 78: 'n', 79: 'o', 80: 'p', 81: 'q', 82: 'r',
+ 83: 's', 84: 't', 85: 'u', 86: 'v', 87: 'w', 88: 'x', 89: 'y',
+ 90: 'z', 91: '[', 93: ']', 97: 'A', 98: 'B', 99: 'C',
+ 100: 'D', 101: 'E', 102: 'F', 103: 'G', 104: 'H', 105: 'I',
+ 106: 'J', 107: 'K', 108: 'L', 109: 'M', 110: 'N', 111: 'O',
+ 112: 'P', 113: 'Q', 114: 'R', 115: 'S', 116: 'T', 117: 'U',
+ 118: 'V', 119: 'W', 120: 'X', 121: 'Y', 122: 'Z', 193: 'A',
+ 194: 'B', 195: 'C', 196: 'D', 197: 'E', 198: 'F', 199: 'G',
+ 200: 'H', 201: 'I', 202: 'J', 203: 'K', 204: 'L', 205: 'M',
+ 206: 'N', 207: 'O', 208: 'P', 209: 'Q', 210: 'R', 211: 'S',
+ 212: 'T', 213: 'U', 214: 'V', 215: 'W', 216: 'X', 217: 'Y',
+ 218: 'Z'}
+
+ FILE_TYPES = {0b000: 'del',
+ 0b001: 'seq',
+ 0b010: 'prg',
+ 0b011: 'usr',
+ 0b100: 'rel'}
+
+ DIR_TRACK = 18
+ DIR_SECTOR = 1
+
+ def __init__(self, raw):
+ """
+ Init
+ """
+ self.raw = raw
+ self.current_sector_data = None
+ self.next_sector = 0
+ self.next_track = None
+ self._dir_contents = []
+ self._already_done = []
+
+ def _map_filename(self, string):
+ """
+ Transcode filename to ASCII compatible. Replace not supported
+ characters with jokers.
+ """
+
+ filename = list()
+
+ for chr_ in string:
+ if _ord(chr_) == 160: # shift+space character; $a0
+ break
+
+ character = D64.CHAR_MAP.get(_ord(chr_), '?')
+ filename.append(character)
+
+ # special cases
+ if filename[0] == "-":
+ filename[0] = "?"
+
+ LOG.debug("string: ``%s'' mapped to: ``%s''", string,
+ "".join(filename))
+ return "".join(filename)
+
+ def _go_to_next_sector(self):
+ """
+ Fetch (if exist) next sector from a directory chain
+ Return False if the chain ends, True otherwise
+ """
+
+ # Well, self.next_sector _should_ have value $FF, but apparently there
+ # are the cases where it is not, therefore checking for that will not
+ # be performed and value of $00 on the next track will end the
+ # directory
+ if self.next_track == 0:
+ LOG.debug("End of directory")
+ return False
+
+ if self.next_track is None:
+ LOG.debug("Going to the track: %s, %s", self.DIR_TRACK,
+ self.DIR_SECTOR)
+ offset = self._get_offset(self.DIR_TRACK, self.DIR_SECTOR)
+ else:
+ offset = self._get_offset(self.next_track, self.next_sector)
+ LOG.debug("Going to the track: %s,%s", self.next_track,
+ self.next_sector)
+
+ self.current_sector_data = self.raw[offset:offset + SECLEN]
+
+ # Guard for reading data out of bound - that happened for discs which
+ # store only raw data, even on directory track
+ if not self.current_sector_data:
+ return False
+
+ self.next_track = _ord(self.current_sector_data[0])
+ self.next_sector = _ord(self.current_sector_data[1])
+
+ if (self.next_track, self.next_sector) in self._already_done:
+ # Just a failsafe. Endless loop is not what is expected.
+ LOG.debug("Loop in track/sector pointer at %d,%d",
+ self.next_track, self.next_sector)
+ self._already_done = []
+ return False
+
+ self._already_done.append((self.next_track, self.next_sector))
+ LOG.debug("Next track: %s,%s", self.next_track, self.next_sector)
+ return True
+
+ def _get_ftype(self, num):
+ """
+ Get filetype as a string
+ """
+ return D64.FILE_TYPES.get(int("%d%d%d" % (num & 4 and 1,
+ num & 2 and 1,
+ num & 1), 2), '???')
+
+ def _get_offset(self, track, sector):
+ """
+ Return offset (in bytes) for specified track and sector.
+ """
+ return 0
+
+ def _harvest_entries(self):
+ """
+ Traverse through sectors and store entries in _dir_contents
+ """
+ sector = self.current_sector_data
+ for dummy in range(8):
+ entry = sector[:32]
+ ftype = _ord(entry[2])
+
+ if ftype == 0: # deleted
+ sector = sector[32:]
+ continue
+
+ type_verbose = self._get_ftype(ftype)
+
+ protect = _ord(entry[2]) & 64 and "<" or " "
+ fname = entry[5:21]
+ if ftype == 'rel':
+ size = _ord(entry[23])
+ else:
+ size = _ord(entry[30]) + _ord(entry[31]) * 226
+
+ self._dir_contents.append({'fname': self._map_filename(fname),
+ 'ftype': type_verbose,
+ 'size': size,
+ 'protect': protect})
+ sector = sector[32:]
+
+ def list_dir(self):
+ """
+ Return directory list as list of dict with keys:
+ fname, ftype, protect and size
+ """
+ while self._go_to_next_sector():
+ self._harvest_entries()
+
+ return self._dir_contents
+
+
+class D64(Disk):
+ """
+ Implement d64 directory reader
+ """
+
+ def _get_offset(self, track, sector):
+ """
+ Return offset (in bytes) for specified track and sector.
+
+ Track Sectors/track # Tracks
+ ----- ------------- ---------
+ 1-17 21 17
+ 18-24 19 7
+ 25-30 18 6
+ 31-40 17 10
+ """
+ offset = 0
+ truncate_track = 0
+
+ if track > 17:
+ offset = 17 * 21 * SECLEN
+ truncate_track = 17
+
+ if track > 24:
+ offset += 7 * 19 * SECLEN
+ truncate_track = 24
+
+ if track > 30:
+ offset += 6 * 18 * SECLEN
+ truncate_track = 30
+
+ track = track - truncate_track
+ offset += track * sector * SECLEN
+
+ return offset
+
+
+class D71(Disk):
+ """
+ Implement d71 directory reader
+ """
+
+ def _get_offset(self, track, sector):
+ """
+ Return offset (in bytes) for specified track and sector.
+
+ Track Sec/trk # Tracks
+ -------------- ------- ---------
+ 1-17 (side 0) 21 17
+ 18-24 (side 0) 19 7
+ 25-30 (side 0) 18 6
+ 31-35 (side 0) 17 5
+ 36-52 (side 1) 21 17
+ 53-59 (side 1) 19 7
+ 60-65 (side 1) 18 6
+ 66-70 (side 1) 17 5
+ """
+ offset = 0
+ truncate_track = 0
+
+ if track > 17:
+ offset = 17 * 21 * SECLEN
+ truncate_track = 17
+
+ if track > 24:
+ offset += 7 * 19 * SECLEN
+ truncate_track = 24
+
+ if track > 30:
+ offset += 6 * 18 * SECLEN
+ truncate_track = 30
+
+ if track > 35:
+ offset += 5 * 17 * SECLEN
+ truncate_track = 35
+
+ if track > 52:
+ offset = 17 * 21 * SECLEN
+ truncate_track = 17
+
+ if track > 59:
+ offset += 7 * 19 * SECLEN
+ truncate_track = 24
+
+ if track > 65:
+ offset += 6 * 18 * SECLEN
+ truncate_track = 30
+
+ track = track - truncate_track
+ offset += track * sector * SECLEN
+
+ return offset
+
+
+class D81(Disk):
+ """
+ Implement d81 directory reader
+ """
+ DIR_TRACK = 40
+ DIR_SECTOR = 3
+ FILE_TYPES = {0b000: 'del',
+ 0b001: 'seq',
+ 0b010: 'prg',
+ 0b011: 'usr',
+ 0b100: 'rel',
+ 0b101: 'cbm'}
+
+ def _get_offset(self, track, sector):
+ """
+ Return offset (in bytes) for specified track and sector. In d81 is
+ easy, since we have 80 tracks with 40 sectors for 256 bytes each.
+ """
+ # we wan to go to the beginning (first sector) of the track, not it's
+ # max, so that we need to extract its amount.
+ return (track * 40 - 40) * SECLEN + sector * SECLEN
+
+
+class Uc1541(object):
+ """
+ Class for interact with c1541 program and MC
+ """
+ PRG = re.compile(r'(\d+)\s+"([^"]*)".+?\s(del|prg|rel|seq|usr)([\s<])')
+
+ def __init__(self, archname):
+ self.arch = archname
+ self.out = ''
+ self.err = ''
+ self._verbose = os.getenv("UC1541_VERBOSE", False)
+ self._hide_del = os.getenv("UC1541_HIDE_DEL", False)
+
+ self.dirlist = _get_implementation(_get_raw(archname)).list_dir()
+ self.file_map = {}
+ self.directory = []
+
+ def list(self):
+ """
+ Output list contents of D64 image.
+ Convert filenames to be Unix filesystem friendly
+ Add suffix to show user what kind of file do he dealing with.
+ """
+ LOG.info("List contents of %s", self.arch)
+ directory = self._get_dir()
+
+ # If there is an error reading directory, show the reason to the user
+ if self.out.startswith("Error"):
+ sys.stderr.write(self.out.split("\n")[0] + "\n")
+ return 2
+
+ for entry in directory:
+ sys.stdout.write("%(perms)s 1 %(uid)-8d %(gid)-8d %(size)8d "
+ "Jan 01 1980 %(display_name)s\n" % entry)
+ return 0
+
+ def rm(self, dst):
+ """
+ Remove file from D64 image
+ """
+ LOG.info("Removing file %s", dst)
+ dst = self._get_masked_fname(dst)
+
+ if not self._call_command('delete', dst=dst):
+ return self._show_error()
+
+ return 0
+
+ def copyin(self, dst, src):
+ """
+ Copy file to the D64 image. Destination filename has to be corrected.
+ """
+ LOG.info("Copy into D64 %s as %s", src, dst)
+ dst = self._correct_fname(dst)
+
+ if not self._call_command('write', src=src, dst=dst):
+ return self._show_error()
+
+ return 0
+
+ def copyout(self, src, dst):
+ """
+ Copy file form the D64 image. Source filename has to be corrected,
+ since it's representation differ from the real one inside D64 image.
+ """
+ LOG.info("Copy form D64 %s as %s", src, dst)
+ if not src.endswith(".prg"):
+ return "cannot read"
+
+ src = self._get_masked_fname(src)
+
+ if not self._call_command('read', src=src, dst=dst):
+ return self._show_error()
+
+ return 0
+
+ def mkdir(self, dirname):
+ """Not supported"""
+ self.err = "D64 format doesn't support directories"
+ return self._show_error()
+
+ def run(self, fname):
+ """Not supported"""
+ self.err = "Not supported, unless you are using MC on real C64 ;)"
+ return self._show_error()
+
+ def _correct_fname(self, fname):
+ """
+ Return filename with mapped characters, without .prg extension.
+ Characters like $, *, + in filenames are perfectly legal, but c1541
+ program seem to have issues with it while writing, so it will also be
+ replaced.
+ """
+ char_map = {'|': "/",
+ "\\": "/",
+ "~": " ",
+ "$": "?",
+ "*": "?"}
+
+ if fname.lower().endswith(".prg"):
+ fname = fname[:-4]
+
+ new_fname = []
+ for char in fname:
+ trans = char_map.get(char)
+ new_fname.append(trans if trans else char)
+
+ return "".join(new_fname)
+
+ def _get_masked_fname(self, fname):
+ """
+ Return masked filename with '?' jokers instead of non ASCII
+ characters, useful for copying or deleting files with c1541. In case
+ of several files with same name exists in directory, only first one
+ will be operative (first as appeared in directory).
+
+ Warning! If there are two different names but the only difference is in
+ non-ASCII characters (some PET ASCII or control characters) there is
+ a risk that one can remove both files.
+ """
+ directory = self._get_dir()
+
+ for entry in directory:
+ if entry['display_name'] == fname:
+ return entry['pattern_name']
+
+ def _get_dir(self):
+ """
+ Retrieve directory via c1541 program
+ """
+ directory = []
+
+ uid = os.getuid()
+ gid = os.getgid()
+
+ if not self._call_command('list'):
+ return self._show_error()
+
+ idx = 0
+ for line in self.out.split("\n"):
+ if Uc1541.PRG.match(line):
+ blocks, fname, ext, rw = Uc1541.PRG.match(line).groups()
+
+ if ext == 'del' and self._hide_del:
+ continue
+
+ display_name = ".".join([fname, ext])
+ pattern_name = self.dirlist[idx]['fname']
+
+ if '/' in display_name:
+ display_name = display_name.replace('/', '|')
+
+ # workaround for space and dash at the beginning of the
+ # filename
+ char_map = {' ': '~',
+ '-': '_'}
+ display_name = "".join([char_map.get(display_name[0],
+ display_name[0]),
+ display_name[1:]])
+
+ if ext == 'del':
+ perms = "----------"
+ else:
+ perms = "-r%s-r--r--" % (rw.strip() and "-" or "w")
+
+ directory.append({'pattern_name': pattern_name,
+ 'display_name': display_name,
+ 'uid': uid,
+ 'gid': gid,
+ 'size': int(blocks) * SECLEN,
+ 'perms': perms})
+ idx += 1
+ return directory
+
+ def _show_error(self):
+ """
+ Pass out error output from c1541 execution
+ """
+ if self._verbose:
+ return self.err
+ else:
+ return 1
+
+ def _call_command(self, cmd, src=None, dst=None):
+ """
+ Return status of the provided command, which can be one of:
+ write
+ read
+ delete
+ dir/list
+ """
+ command = ['c1541', '-attach', self.arch, '-%s' % cmd]
+ if src:
+ command.append(src)
+ if dst:
+ command.append(dst)
+
+ LOG.debug('executing command: %s', ' '.join(command))
+ # For some reason using write and delete commands and reading output
+ # confuses Python3 beneath MC and as a consequence MC report an
+ # error...therefore for those commands let's not use
+ # universal_newlines...
+ universal_newlines = True
+ if cmd in ['delete', 'write']:
+ universal_newlines = False
+ self.out, self.err = Popen(command,
+ universal_newlines=universal_newlines,
+ stdout=PIPE, stderr=PIPE).communicate()
+
+ if self.err:
+ LOG.debug('an err: %s', self.err)
+ return not self.err
+
+
+CALL_MAP = {'list': lambda a: Uc1541(a.arch).list(),
+ 'copyin': lambda a: Uc1541(a.arch).copyin(a.src, a.dst),
+ 'copyout': lambda a: Uc1541(a.arch).copyout(a.src, a.dst),
+ 'mkdir': lambda a: Uc1541(a.arch).mkdir(a.dst),
+ 'rm': lambda a: Uc1541(a.arch).rm(a.dst),
+ 'run': lambda a: Uc1541(a.arch).run(a.dst)}
+
+
+def parse_args():
+ """Use ArgumentParser to check for script arguments and execute."""
+ parser = ArgumentParser()
+ subparsers = parser.add_subparsers(help='supported commands',
+ dest='subcommand')
+ subparsers.required = True
+ parser_list = subparsers.add_parser('list', help="List contents of D64 "
+ "image")
+ parser_copyin = subparsers.add_parser('copyin', help="Copy file into D64 "
+ "image")
+ parser_copyout = subparsers.add_parser('copyout', help="Copy file out of "
+ "D64 image")
+ parser_rm = subparsers.add_parser('rm', help="Delete file from D64 image")
+ parser_mkdir = subparsers.add_parser('mkdir', help="Create directory in "
+ "archive")
+ parser_run = subparsers.add_parser('run', help="Execute archived file")
+
+ parser_list.add_argument('arch', help="D64 Image filename")
+ parser_list.set_defaults(func=CALL_MAP['list'])
+
+ parser_copyin.add_argument('arch', help="D64 Image filename")
+ parser_copyin.add_argument('src', help="Source filename")
+ parser_copyin.add_argument('dst', help="Destination filename (to be "
+ "written into D64 image)")
+ parser_copyin.set_defaults(func=CALL_MAP['copyin'])
+
+ parser_copyout.add_argument('arch', help="D64 Image filename")
+ parser_copyout.add_argument('src', help="Source filename (to be read from"
+ " D64 image")
+ parser_copyout.add_argument('dst', help="Destination filename")
+ parser_copyout.set_defaults(func=CALL_MAP['copyout'])
+
+ parser_rm.add_argument('arch', help="D64 Image filename")
+ parser_rm.add_argument('dst', help="File inside D64 image to be deleted")
+ parser_rm.set_defaults(func=CALL_MAP['rm'])
+
+ parser_mkdir.add_argument('arch', help="archive filename")
+ parser_mkdir.add_argument('dst', help="Directory name inside archive to "
+ "be created")
+ parser_mkdir.set_defaults(func=CALL_MAP['mkdir'])
+
+ parser_run.add_argument('arch', help="archive filename")
+ parser_run.add_argument('dst', help="File to be executed")
+ parser_run.set_defaults(func=CALL_MAP['run'])
+
+ args = parser.parse_args()
+ return args.func(args)
+
+
+def no_parse():
+ """Failsafe argument "parsing". Note, that it blindly takes positional
+ arguments without checking them. In case of wrong arguments it will
+ silently exit"""
+ try:
+ if sys.argv[1] not in ('list', 'copyin', 'copyout', 'rm', 'mkdir',
+ "run"):
+ sys.exit(2)
+ except IndexError:
+ sys.exit(2)
+
+ class Arg(object):
+ """Mimic argparse object"""
+ dst = None
+ src = None
+ arch = None
+
+ arg = Arg()
+
+ try:
+ arg.arch = sys.argv[2]
+ if sys.argv[1] in ('copyin', 'copyout'):
+ arg.src = sys.argv[3]
+ arg.dst = sys.argv[4]
+ elif sys.argv[1] in ('rm', 'run', 'mkdir'):
+ arg.dst = sys.argv[3]
+ except IndexError:
+ sys.exit(2)
+
+ return CALL_MAP[sys.argv[1]](arg)
+
+
+if __name__ == "__main__":
+ LOG.debug("Script params: %s", str(sys.argv))
+ try:
+ from argparse import ArgumentParser
+ PARSE_FUNC = parse_args
+ except ImportError:
+ PARSE_FUNC = no_parse
+
+ sys.exit(PARSE_FUNC())
diff --git a/src/vfs/extfs/helpers/ucab.in b/src/vfs/extfs/helpers/ucab.in
new file mode 100644
index 0000000..252c8ca
--- /dev/null
+++ b/src/vfs/extfs/helpers/ucab.in
@@ -0,0 +1,40 @@
+#! /bin/sh
+
+CAB=cabextract
+
+mccabfs_list ()
+{
+ $CAB -l "$1" | @AWK@ -v uid=`id -un` -v gid=`id -gn` '
+BEGIN { flag=0 }
+/^-------/ { flag++; if (flag > 1) exit 0; next }
+{
+if (flag == 0) next
+if (length($6) == 0) next
+pr="-rw-r--r--"
+split($3, a, ".")
+split($4, b, ":")
+printf "%s 1 %s %s %d %02d/%02d/%02d %02d:%02d %s\n", pr, uid, gid, $1, a[2], a[1], a[3], b[1], b[2], $6
+}'
+
+}
+
+mccabfs_copyout ()
+{
+ $CAB -F "$2" -p "$1" > "$3"
+}
+
+LC_ALL=C
+export LC_ALL
+
+umask 077
+
+cmd="$1"
+
+case "$cmd" in
+ # Workaround for a bug in mc - directories must precede files to
+ # avoid duplicate entries, so we sort output by filenames
+ list) mccabfs_list "$2" ;;
+ copyout) mccabfs_copyout "$2" "$3" "$4" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/uha.in b/src/vfs/extfs/helpers/uha.in
new file mode 100644
index 0000000..9dd0016
--- /dev/null
+++ b/src/vfs/extfs/helpers/uha.in
@@ -0,0 +1,52 @@
+#!/bin/sh
+#
+# It is the uhafs Valery Kornienkov vlk@st.simbirsk.su 2:5051/30@fidonet
+# ver 0.1 Thu Apr 6 12:05:08 2000
+#
+# Tested with HA 0.999. Source of ha can be found at
+# ftp://ftp.ibiblio.org/pub/Linux/utils/compress/
+
+HA=ha
+
+mchafs_list ()
+{
+ $HA lf "$1" 2>/dev/null | @AWK@ -v uid=$(id -ru) '
+/^===========/ {next}
+{
+ if ($5="%" && $8~/DIR|ASC|HSC|CPY/) {
+ split($6, a, "-")
+ split($7, t, ":")
+ filename=$1
+ filesize=$2
+ getline
+ if ($2=="(none)") $2=""
+ path=$2
+ getline
+ if ($1~/^d.*/) next
+ printf "%s %s %-8d %-8d %8d %s-%s-%s %s:%s %s%s\n",\
+ $1,1,0,0,filesize,a[3],a[2],a[1],t[1],t[2],path,filename
+ }
+}'
+}
+
+mchafs_copyout ()
+{
+ TMPDIR=`mktemp -d "${MC_TMPDIR:-/tmp}/mctmpdir-uha.XXXXXX"` || exit 1
+ cd "$TMPDIR"
+
+ $HA xyq "$1" "$2" >/dev/null
+ cat "$2" > "$3"
+
+ cd /
+ rm -rf "$TMPDIR"
+}
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list) mchafs_list "$@" ;;
+ copyout) mchafs_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/ulha.in b/src/vfs/extfs/helpers/ulha.in
new file mode 100644
index 0000000..0b5735c
--- /dev/null
+++ b/src/vfs/extfs/helpers/ulha.in
@@ -0,0 +1,142 @@
+#! /bin/sh
+
+#
+# LHa Virtual filesystem executive v0.1
+# Copyright (C) 1996, 1997 Joseph M. Hinkle
+# May be distributed under the terms of the GNU Public License
+# <jhinkle@rockisland.com>
+#
+
+# Code for mc_lha_fs_run() suggested by:
+# Jan 97 Zdenek Kabelac <kabi@informatics.muni.cz>
+
+# Tested with mc 3.5.18 and gawk 3.0.0 on Linux 2.0.0
+# Tested with lha v1.01 and lharc v1.02
+# Information and sources for other forms of lha/lzh appreciated
+
+# Nota bene:
+# There are several compression utilities which produce *.lha files.
+# LHArc and LHa in exist several versions, and their listing output varies.
+# Another variable is the architecture on which the compressed file was made.
+# This program attempts to sort out the variables known to me, but it is likely
+# to display an empty panel if it encounters a mystery.
+# In that case it will be useful to execute this file from the command line:
+# ./lha list Mystery.lha
+# to examine the output directly on the console. The output string must be
+# precisely in the format described in the README in this directory.
+# Caveat emptor.
+# Learn Latin.
+
+# Define your awk
+AWK=@AWK@
+
+# Define which archiver you are using with appropriate options
+LHA_LIST="lha lq"
+LHA_GET="lha pq"
+LHA_PUT="lha aq"
+
+# The 'list' command executive
+
+mc_lha_fs_list()
+{
+ # List the contents of the archive and sort it out
+ $LHA_LIST "$1" | $AWK -v uid=`id -nu` -v gid=`id -ng` '
+ # Strip a leading '/' if present in a filepath
+ $(NF) ~ /^\// { $(NF) = substr($NF,2) }
+ # Print the line this way if there is no permission string
+ $1 ~ /^\[.*\]/ {
+ # Invent a generic permission
+ $1 = ($NF ~ /\/$/) ? "drwxr-xr-x":"-rwxr--r--";
+ # Print it
+ printf "%s 1 %-8s %-8s %-8d %s %s %s %s\n",
+ $1, uid, gid, $2, $4, $5, $6, $7;
+ # Get the next line of the list
+ next;
+ }
+ # Do it this way for a defined permission
+ $1 !~ /^\[.*\]/ {
+ # If the permissions and UID run together
+ if ($1 ~ /\//) {
+ $8 = $7;
+ $7 = $6;
+ $6 = $5;
+ $5 = $4;
+ $3 = $2;
+ $2 = substr($1,10);
+ $1 = substr($1,1,9);
+ }
+ # If the permission string is missing a type
+ if (length($1) == 9) {
+ if ($NF ~ /\/$/)
+ $1 = ("d" $1);
+ else
+ $1 = ("-" $1);
+ }
+ # UID:GID might not be the same as on your system so print numbers
+ # Well, that is the intent. At the moment mc is translating them.
+ split($2, id, "/");
+ printf "%s 1 %-8d %-8d %-8d %s %s %s %s\n",
+ $1, id[1], id[2], $3, $5, $6, $7, $8;
+ # Get the next line of the list
+ next;
+ }
+
+ '
+}
+
+# The 'copyout' command executive to copy displayed files to a destination
+
+mc_lha_fs_copyout()
+{
+ $LHA_GET "$1" "$2" > "$3"
+}
+
+# The 'copyin' command executive to add something to the archive
+
+mc_lha_fs_copyin ()
+{
+ NAME2=`basename "$2"`; DIR2=${2%$NAME2}
+ NAME3=`basename "$3"`; DIR3=${3%$NAME3}
+
+ cd "${DIR3}"
+
+ ONE2=${2%%/*}
+ [ -n "${ONE2}" ] || exit 1
+ [ -e "${ONE2}" ] && exit 1
+
+ [ -e "${DIR2}" ] || mkdir -p "${DIR2}"
+ ln "$3" "$2" || exit 1
+
+ $LHA_PUT "$1" "$2"
+ rm -r "${ONE2}"
+}
+
+# The 'run' command executive to run a command from within an archive
+
+mc_lha_fs_run()
+{
+ TMPDIR=`mktemp -d "${MC_TMPDIR:-/tmp}/mctmpdir-ulha.XXXXXX"` || exit 1
+ trap "rm -rf \"$TMPDIR\"; exit 0" 1 2 3 4 15
+ TMPCMD=$TMPDIR/run
+ $LHA_GET "$1" "$2" > $TMPCMD
+ chmod a+x "$TMPCMD"
+ "$TMPCMD"
+ rm -rf "$TMPDIR"
+}
+
+
+# The main routine
+umask 077
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list) mc_lha_fs_list "$@" ;;
+ copyout) mc_lha_fs_copyout "$@" ;;
+ copyin) mc_lha_fs_copyin "$@" ;;
+ run) mc_lha_fs_run "$@" ;;
+ *) exit 1 ;;
+esac
+
+exit 0
diff --git a/src/vfs/extfs/helpers/ulib.in b/src/vfs/extfs/helpers/ulib.in
new file mode 100644
index 0000000..82c7ccf
--- /dev/null
+++ b/src/vfs/extfs/helpers/ulib.in
@@ -0,0 +1,146 @@
+#! @PERL@
+#
+# VFS to manage the gputils archives.
+# Written by Molnár Károly (proton7@freemail.hu) 2012
+#
+
+use warnings;
+
+my %month = ('jan' => '01', 'feb' => '02', 'mar' => '03',
+ 'apr' => '04', 'may' => '05', 'jun' => '06',
+ 'jul' => '07', 'aug' => '08', 'sep' => '09',
+ 'oct' => '10', 'nov' => '11', 'dec' => '12');
+
+my @PATHS = ('/usr/bin/gplib', '/usr/local/bin/gplib');
+
+my $gplib = '';
+
+foreach my $i (@PATHS)
+{
+ if (-x $i)
+ {
+ $gplib = $i;
+ last;
+ }
+}
+
+if ($gplib eq '')
+{
+ print STDERR "\a\t$0 : Gplib not found!\n";
+ exit(1);
+}
+
+my $cmd = shift;
+my $archive = shift;
+
+#-------------------------------------------------------------------------------
+
+sub mc_ulib_fs_list
+{
+ open(PIPE, "$gplib -tq $archive |") || die("Error in $gplib -tq");
+
+ my($dev, $inode, $mode, $nlink, $uid, $gid) = stat($archive);
+
+ while (<PIPE>)
+ {
+ chomp;
+ my @w = split(/\s+/o);
+ my $fname = $w[0];
+
+ $fname =~ s|\\|/|g;
+
+ printf("-rw-r--r-- 1 %s %s %d %s-%02u-%s %s %s\n",
+ $uid, $gid, int($w[1]), $month{lc($w[4])}, $w[5], $w[7], substr($w[6], 0, 5), $fname);
+ }
+ close (PIPE);
+}
+
+#-------------------------------------------------------------------------------
+
+sub mc_ulib_fs_copyin
+{
+ system("$gplib -r $archive $_[0]");
+ my $ret = $?;
+
+ if ($ret)
+ {
+ die("Error in: $gplib -r");
+ }
+}
+
+#-------------------------------------------------------------------------------
+
+sub mc_ulib_fs_copyout
+{
+ my($module, $fname) = @_;
+ my $tmpdir = $ENV{'TMPDIR'};
+
+ $tmpdir = '/tmp' if (! defined $tmpdir or $tmpdir eq '');
+
+ open(PIPE, "$gplib -tq $archive |") || die("Error in: $gplib -tq");
+
+ while (<PIPE>)
+ {
+ chomp;
+ my @w = split(/\s+/o);
+ my $module_orig = $w[0];
+ my $count = () = ($module_orig =~ /(\\)/g);
+ my $md = $module_orig;
+
+ $md =~ s|\\|/|g;
+
+ if ($module eq $md)
+ {
+ return if ($count);
+ }
+ }
+
+ close (PIPE);
+
+ chdir($tmpdir);
+ system("$gplib -x $archive $module");
+ my $ret = $?;
+
+ if ($ret)
+ {
+ die("Error in: $gplib -x");
+ }
+
+ rename($module, $fname) || die("Error in: rename($module, $fname)");
+}
+
+#-------------------------------------------------------------------------------
+
+sub mc_ulib_fs_rm
+{
+ system("$gplib -d $archive $_[0]");
+ my $ret = $?;
+
+ if ($ret)
+ {
+ die("Error in: $gplib -d");
+ }
+}
+
+################################################################################
+
+if ($cmd eq 'list')
+{
+ mc_ulib_fs_list(@ARGV);
+}
+elsif ($cmd eq 'copyin')
+{
+ mc_ulib_fs_copyin(@ARGV);
+}
+elsif ($cmd eq 'copyout')
+{
+ mc_ulib_fs_copyout(@ARGV);
+}
+elsif ($cmd eq 'rm')
+{
+ mc_ulib_fs_rm(@ARGV);
+}
+else
+{
+ exit(1);
+}
diff --git a/src/vfs/extfs/helpers/unar.in b/src/vfs/extfs/helpers/unar.in
new file mode 100644
index 0000000..e810307
--- /dev/null
+++ b/src/vfs/extfs/helpers/unar.in
@@ -0,0 +1,59 @@
+#! /bin/sh
+
+# Written by Ilia Maslakov <il.smind@gmail.com>
+#
+# (C) 2020 The Free Software Foundation.
+
+# Define awk
+AWK=@AWK@
+
+# Define which archiver you are using with appropriate options
+UNAR_LIST="lsar "
+UNAR_GET="unar "
+
+# The 'list' command executive
+mc_unar_fs_list()
+{
+ # List the contents of the archive and sort it out
+ $UNAR_LIST -l "$1" | $AWK -v uid=`id -nu` -v gid=`id -ng` '
+ BEGIN { flag = 0 }
+ /^\(Flags/ {next}
+ /^\(Mode/ {next}
+ {
+ flag++;
+ if (flag < 4)
+ next
+ pr="-r--r--r--"
+ if (index($2, "D") != 0)
+ pr="dr-xr-xr-x"
+ split($6, a, "-")
+ split($7, b, ":")
+ printf "%s 1 %s %s %d %02d/%02d/%02d %02d:%02d %s\n", pr, uid, gid, $3, a[3], a[2], a[1], b[1], b[2], $8
+ }'
+}
+
+# The 'copyout' command executive to copy displayed files to a destination
+mc_unar_fs_copyout ()
+{
+ TMPDIR=`mktemp -d "${MC_TMPDIR:-/tmp}/mctmpdir-uha.XXXXXX"` || exit 1
+
+ $UNAR_GET "$1" "$2" -o "$TMPDIR" >/dev/null
+ we=`basename "$1" | sed -E 's|^(.*?)\.\w+$|\1|'`
+ cat "$TMPDIR/$we/$2" > "$3"
+ cd /
+ rm -rf "$TMPDIR"
+}
+
+# The main routine
+umask 077
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ list) mc_unar_fs_list "$@" ;;
+ copyout) mc_unar_fs_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+
+exit 0
diff --git a/src/vfs/extfs/helpers/urar.in b/src/vfs/extfs/helpers/urar.in
new file mode 100644
index 0000000..684234c
--- /dev/null
+++ b/src/vfs/extfs/helpers/urar.in
@@ -0,0 +1,180 @@
+#! /bin/sh
+#
+# Written by andrey joukov
+# (C) 1996 2:5020/337.13@fidonet.org
+# Updated by christian.gennerat@alcatel.fr 1999
+# Andrew V. Samoilov <sav@bcs.zp.ua> 2000
+#
+# Andrew Borodin <aborodin@vmail.ru>
+# David Haller <dnh@opensuse.org>
+# 2013: support unrar5
+#
+# beta version 2.0
+#
+# rar and unrar can be found on http://www.rarlabs.com/
+
+RAR=rar
+
+# Prefer unrar (freeware).
+UNRAR=`which unrar 2>/dev/null`
+
+[ -z $UNRAR ] && UNRAR=$RAR
+[ ! -x $UNRAR -a -x $RAR ] && UNRAR=$RAR
+
+# Let the test framework hook in:
+UNRAR=${MC_TEST_EXTFS_LIST_CMD:-$UNRAR}
+
+# Determine the $UNRAR version
+if [ -n "$MC_TEST_EXTFS_UNRAR_VERSION" ]; then
+ # Let the test framework fool us:
+ UNRAR_VERSION=$MC_TEST_EXTFS_UNRAR_VERSION
+else
+ # Figure it out from rar itself:
+ UNRAR_VERSION=`$UNRAR -cfg- -? | grep "Copyright" | sed -e 's/.*\([0-9]\)\..*/\1/'`
+fi
+
+mcrar4fs_list ()
+{
+ $UNRAR v -c- -cfg- "$1" | @AWK@ -v uid=`id -u` -v gid=`id -g` '
+BEGIN { flag=0 }
+/^-------/ { flag++; if (flag > 1) exit 0; next }
+flag==1 {
+ str = substr($0, 2)
+ getline
+ split($4, a, "-")
+ if (index($6, "D") != 0)
+ $6="drwxr-xr-x"
+ else
+ if (index($6, ".") != 0)
+ $6="-rw-r--r--"
+ printf "%s 1 %s %s %d %02d/%02d/%02d %s ./%s\n", $6, uid, gid, $1, a[2], a[1], a[3], $5, str
+}'
+}
+
+mcrar5fs_list ()
+{
+ $UNRAR vt -c- -cfg- "$1" | @AWK@ -F ':' -v uid=`id -u` -v gid=`id -g` '
+ {
+ ### remove space after the ":" of the field name
+ sub ("^ ", "", $2);
+ }
+
+ $1 ~ /^ *Name$/ {
+ ### next file
+ name = mtime = size = attrs = "";
+ delete date;
+ name = $2;
+ ### if the name contains ":", append the rest of the fields
+ if (NF > 2) {
+ for (i = 3; i <= NF; i++) {
+ name = name ":" $i;
+ }
+ }
+ }
+ $1 ~ /^ *mtime$/ {
+ mtime = $2 ":" $3;
+ }
+ $1 ~ /^ *Size$/ {
+ size = $2;
+ }
+ $1 ~ /^ *Attributes$/ {
+ attrs = $2;
+ }
+
+ $1 ~ /^ *Compression$/ {
+ ### file done, using /^$/ is not so good you
+ ### would have to skip the version stuff first
+
+ ### get date and time
+ split (mtime, date, " ");
+ time = date[2];
+ ### cut off seconds from the time
+ sub (",[0-9]*$", "", time);
+ ### split for reordering of the date in the printf below
+ split (date[1], date, "-");
+ ### mc seems to be able to parse 4 digit years too, so remove if tested
+ # sub ("^..", "", date[1]); ### cut year to 2 digits only
+
+ ### check/adjust rights
+ if (index (attrs, "D") != 0) {
+ attrs = "drwxr-xr-x";
+ } else {
+ if (index (attrs, ".") != 0) {
+ attrs = "-rw-r--r--";
+ }
+ }
+
+ ### and finally
+ printf ("%s 1 %s %s %d %02d/%02d/%02d %s ./%s\n",
+ attrs, uid, gid, size, date[2], date[3], date[1], time, name);
+ }
+'
+}
+
+mcrarfs_list ()
+{
+ [ x$UNRAR_VERSION = x6 -o x$UNRAR_VERSION = x5 ] && mcrar5fs_list "$@" || mcrar4fs_list "$@"
+}
+
+mcrarfs_copyin ()
+{
+# copyin by christian.gennerat@alcatel.fr
+# preserve pwd. It is clean, but is it necessary?
+ pwd=`pwd`
+# Create a directory and copy in it the tmp file with the good name
+ mkdir "$3.dir"
+ cd "$3.dir"
+ di="${2%/*}"
+# if file is to be written upper in the archive tree, make fake dir
+ if test x"$di" != x"${2##*/}" ; then
+ mkdir -p "$di"
+ fi
+ cp -fp "$3" "$3.dir/$2"
+ $RAR a "$1" "$2" >/dev/null
+ cd "$pwd"
+ rm -rf "$3.dir"
+}
+
+mcrarfs_copyout ()
+{
+ $UNRAR p -p- -c- -cfg- -inul "$1" "$2" > "$3"
+}
+
+mcrarfs_mkdir ()
+{
+# preserve pwd. It is clean, but is it necessary?
+ pwd=`pwd`
+# Create a directory and create in it a tmp directory with the good name
+ dir=`mktemp -d "${MC_TMPDIR:-/tmp}/mctmpdir-urar.XXXXXX"` || exit 1
+ cd "$dir"
+ mkdir -p "$2"
+# rar cannot create an empty directory
+ touch "$2"/.rarfs
+ $RAR a -r "$1" "$2" >/dev/null
+ $RAR d "$1" "$2/.rarfs" >/dev/null
+ cd "$pwd"
+ rm -rf "$dir"
+}
+
+mcrarfs_rm ()
+{
+ $RAR d "$1" "$2" >/dev/null
+}
+
+umask 077
+
+cmd="$1"
+shift
+
+case "$cmd" in
+ # Workaround for a bug in mc - directories must precede files to
+ # avoid duplicate entries, so we sort output by filenames
+ list) mcrarfs_list "$@" | sort -k 8 ;;
+ rm) mcrarfs_rm "$@" ;;
+ rmdir) mcrarfs_rm "$@" ;;
+ mkdir) mcrarfs_mkdir "$@" ;;
+ copyin) mcrarfs_copyin "$@" ;;
+ copyout) mcrarfs_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/extfs/helpers/uwim.in b/src/vfs/extfs/helpers/uwim.in
new file mode 100644
index 0000000..76197f7
--- /dev/null
+++ b/src/vfs/extfs/helpers/uwim.in
@@ -0,0 +1,208 @@
+#! /bin/sh
+# Midnight Commander - WIM support
+#
+# Written by:
+# Vadim Kalinnikov <moose@ylsoftware.com>,
+#
+# This file is part of the Midnight Commander.
+#
+# It required wimtools: https://wimlib.net/
+# On Debian/Ubuntu wimtools can be installed via:
+# apt install wimtools
+
+which wimlib-imagex 2>/dev/null > /dev/null || exit 1
+WIM=`which wimlib-imagex`
+AWK=@AWK@
+
+[ -n "$2" ] || exit 1
+
+ACTION=$1
+WIMFILENAME=$2
+
+mcwim_list() {
+ # Here we can use "Image count" from output,
+ # but on some broken images we can get garbage, instead of number
+ IMAGECOUNT=`${WIM} info ${WIMFILENAME} | grep Index: | grep -v Boot | wc -l`
+ IMGNUM=1
+ VUID=`id -nu`
+ VGID=`id -ng`
+ while [ ${IMGNUM} -le ${IMAGECOUNT} ]; do
+ ${WIM} dir ${WIMFILENAME} ${IMGNUM} --detailed | \
+ ${AWK} -v uid=${VUID} -v gid=${VGID} -v imgnum=${IMGNUM} '
+ /^----------------------------------------------------------------------------/,/^$/ {
+ if (match($0, /^Full Path/)) {
+ split($0, namesrc, "\"");
+ name=namesrc[2];
+ }
+ if (match($0, /FILE_ATTRIBUTE_DIRECTORY is set/)) {
+ attr="drwxr-xr-x"
+ }
+ if (match($0, /^Uncompressed size/)) {
+ size=$4;
+ }
+ if (match($0, /^Last Write Time/)) {
+ months["Jan"] = "01";
+ months["Feb"] = "02";
+ months["Mar"] = "03";
+ months["Apr"] = "04";
+ months["May"] = "05";
+ months["Jun"] = "06";
+ months["Jul"] = "07";
+ months["Aug"] = "08";
+ months["Sep"] = "09";
+ months["Oct"] = "10";
+ months["Nov"] = "11";
+ months["Dec"] = "12";
+ split($0, mtimesrc, " ");
+ mtime=sprintf("%s/%s/%s %s", months[mtimesrc[6]], mtimesrc[7], mtimesrc[9], mtimesrc[8]);
+ }
+
+ if (match($0, /^$/)) {
+ printf("%s 1 %s %s % 20s % 24s IMAGE%s%s\n",
+ attr, uid, gid, size, mtime, imgnum, name);
+ name = size = mtime = "";
+ attr="-rw-r--r--";
+ }
+ }
+ '
+
+ IMGNUM=$((IMGNUM+1))
+ done
+
+ # Virtual files
+ echo "-r-xr-xr-x 1 ${VUID} ${VGID} 1 01/01/2020 00:00:00 OPTIMIZE"
+ echo "-r-xr-xr-x 1 ${VUID} ${VGID} 1 01/01/2020 00:00:00 VERIFY"
+}
+
+mcwim_copyout() {
+ # Virtual files
+ if [ "${FILENAMESRC}" = "OPTIMIZE" ]; then
+ echo "#/bin/sh" > ${FILENAMEDST}
+ echo "# Run this to optimize archive" >> ${FILENAMEDST}
+ echo "${WIM} optimize \"${WIMFILENAME}\"" >> ${FILENAMEDST}
+ chmod a+x ${FILENAMEDST}
+ exit 0;
+ elif [ "${FILENAMESRC}" = "VERIFY" ]; then
+ echo "#/bin/sh" > ${FILENAMEDST}
+ echo "# Run this to verify archive" >> ${FILENAMEDST}
+ echo "${WIM} verify \"${WIMFILENAME}\"" >> ${FILENAMEDST}
+ chmod a+x ${FILENAMEDST}
+ exit 0;
+ fi
+
+ # Filename must contain imgnum
+ echo ${FILENAMESRC} | grep -E '^IMAGE[0-9]+/' || exit 1
+
+ IMGNUM=`echo ${FILENAMESRC} | cut -d '/' -f1 | sed "s/IMAGE//"`
+ REALFILENAME=`echo ${FILENAMESRC} | sed "s/IMAGE${IMGNUM}//"`
+ ${WIM} extract ${WIMFILENAME} ${IMGNUM} ${REALFILENAME} --to-stdout > ${FILENAMEDST}
+}
+
+mcwim_copyin() {
+ # Skip virtual files
+ [ "${FILENAMEDST}" = "OPTIMIZE" ] && exit 1;
+ [ "${FILENAMEDST}" = "VERIFY" ] && exit 1;
+
+ # Filename must contain imgnum
+ echo ${FILENAMEDST} | grep -E '^IMAGE[0-9]+/' || exit 1
+
+ IMGNUM=`echo ${FILENAMEDST} | cut -d '/' -f1 | sed "s/IMAGE//"`
+ REALFILENAME=`echo ${FILENAMEDST} | sed "s/IMAGE${IMGNUM}//"`
+ echo "add \"${FILENAMESRC}\" \"${REALFILENAME}\"" | ${WIM} update ${WIMFILENAME} ${IMGNUM} > /dev/null
+}
+
+
+mcwim_rm() {
+ # Skip virtual files
+ [ "${FILENAMESRC}" = "OPTIMIZE" ] && exit 0;
+ [ "${FILENAMESRC}" = "VERIFY" ] && exit 0;
+
+ # Filename must contain imgnum
+ echo ${FILENAMESRC} | grep -E '^IMAGE[0-9]+/' || exit 1
+
+ IMGNUM=`echo ${FILENAMESRC} | cut -d '/' -f1 | sed "s/IMAGE//"`
+ REALFILENAME=`echo ${FILENAMESRC} | sed "s/IMAGE${IMGNUM}//"`
+
+ if [ -z "${REALFILENAME}" ]; then
+ # If user want to remove image
+ ${WIM} delete ${WIMFILENAME} ${IMGNUM}
+ else
+ # remove regular file or directory
+ echo "delete \"${REALFILENAME}\"" | ${WIM} update ${WIMFILENAME} ${IMGNUM} --force --recursive > /dev/null
+ fi
+}
+
+mcwim_run() {
+ case ${RUNFILENAME} in
+ OPTIMIZE)
+ ${WIM} optimize ${WIMFILENAME}
+ exit 0;
+ ;;
+ VERIFY)
+ ${WIM} verify ${WIMFILENAME}
+ exit 0;
+ ;;
+ esac
+ exit 1;
+}
+
+
+mcwim_mkdir() {
+ # New dirname must contain imgnum
+ echo ${NEWDIRNAME} | grep -E '^IMAGE[0-9]+/' || exit 1
+ IMGNUM=`echo ${NEWDIRNAME} | cut -d '/' -f1 | sed "s/IMAGE//"`
+ REALDIRNAME=`echo ${NEWDIRNAME} | sed "s/IMAGE${IMGNUM}//"`
+ [ -z "${REALDIRNAME}" ] && exit 1
+
+ TMPDIR=`mktemp -d`
+ DSTBASENAME=`basename ${REALDIRNAME}`
+ SRCDIRNAME="${TMPDIR}/${DSTBASENAME}"
+ mkdir -p ${SRCDIRNAME}
+ echo "add \"${SRCDIRNAME}\" \"${REALDIRNAME}\"" | ${WIM} update ${WIMFILENAME} ${IMGNUM} > /dev/null
+ rm -rf ${TMPDIR}
+}
+
+#echo "'$1' '$2' '$3' '$4' '$5'" >> /tmp/mcdebug
+
+case "$ACTION" in
+ list)
+ mcwim_list
+ ;;
+
+ copyout)
+ [ -n "$4" ] || exit 1
+ FILENAMESRC="$3"
+ FILENAMEDST="$4"
+ mcwim_copyout
+ ;;
+
+ copyin)
+ [ -n "$4" ] || exit 1
+ FILENAMEDST="$3"
+ FILENAMESRC="$4"
+ mcwim_copyin
+ ;;
+
+ rm|rmdir)
+ [ -n "$3" ] || exit 1
+ FILENAMESRC="$3"
+ mcwim_rm
+ ;;
+
+ run)
+ [ -n "$3" ] || exit 1
+ RUNFILENAME="$3"
+ mcwim_run
+ ;;
+
+ mkdir)
+ [ -n "$3" ] || exit 1
+ NEWDIRNAME="$3"
+ mcwim_mkdir
+ ;;
+
+
+ *)
+ exit 1
+ ;;
+esac
diff --git a/src/vfs/extfs/helpers/uzip.in b/src/vfs/extfs/helpers/uzip.in
new file mode 100644
index 0000000..ceffb53
--- /dev/null
+++ b/src/vfs/extfs/helpers/uzip.in
@@ -0,0 +1,483 @@
+#! @PERL@
+#
+# zip file archive Virtual File System for Midnight Commander
+# Version 1.4.0 (2001-08-07).
+#
+# (C) 2000-2001 Oskar Liljeblad <osk@hem.passagen.se>.
+#
+
+use POSIX;
+use File::Basename;
+use strict;
+use warnings;
+
+#
+# Configuration options
+#
+
+# Location of the zip program
+my $app_zip = "@ZIP@";
+# Location of the unzip program
+my $app_unzip = $ENV{MC_TEST_EXTFS_LIST_CMD} || "@UNZIP@";
+# Set this to 1 if zipinfo (unzip -Z) is to be used (recommended), otherwise 0.
+my $op_has_zipinfo = exists($ENV{MC_TEST_EXTFS_HAVE_ZIPINFO}) ? $ENV{MC_TEST_EXTFS_HAVE_ZIPINFO} : @HAVE_ZIPINFO@;
+
+# Command used to list archives (zipinfo mode)
+my $cmd_list_zi = "$app_unzip -Z -l -T";
+# Command used to list archives (non-zipinfo mode)
+my $cmd_list_nzi = "$app_unzip -qq -v";
+# Command used to add a file to the archive
+my $cmd_add = "$app_zip -g";
+# Command used to add a link file to the archive (unused)
+my $cmd_addlink = "$app_zip -g -y";
+# Command used to delete a file from the archive
+my $cmd_delete = "$app_zip -d";
+# Command used to extract a file to standard out
+my $cmd_extract = "$app_unzip -p";
+
+# -rw-r--r-- 2.2 unx 2891 tx 1435 defN 20000330.211927 ./edit.html
+# (perm) (?) (?) (size) (?) (zippedsize) (method) (yyyy)(mm)(dd).(HH)(MM)(SS) (fname)
+my $regex_zipinfo_line = qr"^(\S{7,10})\s+(\d+\.\d+)\s+(\S+)\s+(\d+)\s+(\S\S)\s+(\d+)\s+(\S{4})\s+(\d{4})(\d\d)(\d\d)\.(\d\d)(\d\d)(\d\d)\s(.*)$";
+
+# 2891 Defl:N 1435 50% 03-30-00 21:19 50cbaaf8 ./edit.html
+# (size) (method) (zippedsize) (zipratio) (mm)-(dd)-(yy|yyyy) (HH):(MM) (cksum) (fname)
+# or: (yyyy)-(mm)-(dd)
+my $regex_nonzipinfo_line = qr"^\s*(\d+)\s+(\S+)\s+(\d+)\s+(-?\d+\%)\s+(\d+)-(\d?\d)-(\d+)\s+(\d?\d):(\d\d)\s+([0-9a-f]+)\s\s(.*)$";
+
+#
+# Main code
+#
+
+die "uzip: missing command and/or archive arguments\n" if ($#ARGV < 1);
+
+# Initialization of some global variables
+my $cmd = shift;
+my %known = ( './' => 1 );
+my %pending = ();
+my $oldpwd = POSIX::getcwd();
+my $archive = shift;
+my $aarchive = absolutize($archive, $oldpwd);
+my $cmd_list = ($op_has_zipinfo ? $cmd_list_zi : $cmd_list_nzi);
+my ($qarchive, $aqarchive) = map (quotemeta, $archive, $aarchive);
+
+# Strip all "." and ".." path components from a pathname.
+sub zipfs_canonicalize_pathname($) {
+ my ($fname) = @_;
+ $fname =~ s,/+,/,g;
+ $fname =~ s,(^|/)(?:\.?\./)+,$1,;
+ return $fname;
+}
+
+# The Midnight Commander never calls this script with archive pathnames
+# starting with either "./" or "../". Some ZIP files contain such names,
+# so we need to build a translation table for them.
+my $zipfs_realpathname_table = undef;
+sub zipfs_realpathname($) {
+ my ($fname) = @_;
+
+ if (!defined($zipfs_realpathname_table)) {
+ $zipfs_realpathname_table = {};
+ if (!open(ZIP, "$cmd_list $qarchive |")) {
+ return $fname;
+ }
+ foreach my $line (<ZIP>) {
+ $line =~ s/\r*\n*$//;
+ if ($op_has_zipinfo) {
+ if ($line =~ $regex_zipinfo_line) {
+ my ($fname) = ($14);
+ $zipfs_realpathname_table->{zipfs_canonicalize_pathname($fname)} = $fname;
+ }
+ } else {
+ if ($line =~ $regex_nonzipinfo_line) {
+ my ($fname) = ($11);
+ $zipfs_realpathname_table->{zipfs_canonicalize_pathname($fname)} = $fname;
+ }
+ }
+ }
+ if (!close(ZIP)) {
+ return $fname;
+ }
+ }
+ if (exists($zipfs_realpathname_table->{$fname})) {
+ return $zipfs_realpathname_table->{$fname};
+ }
+ return $fname;
+}
+
+if ($cmd eq 'list') { &mczipfs_list(@ARGV); }
+if ($cmd eq 'rm') { &mczipfs_rm(@ARGV); }
+if ($cmd eq 'rmdir') { &mczipfs_rmdir(@ARGV); }
+if ($cmd eq 'mkdir') { &mczipfs_mkdir(@ARGV); }
+if ($cmd eq 'copyin') { &mczipfs_copyin(@ARGV); }
+if ($cmd eq 'copyout') { &mczipfs_copyout(@ARGV); }
+if ($cmd eq 'run') { &mczipfs_run(@ARGV); }
+#if ($cmd eq 'mklink') { &mczipfs_mklink(@ARGV); } # Not supported by MC extfs
+#if ($cmd eq 'linkout') { &mczipfs_linkout(@ARGV); } # Not supported by MC extfs
+exit 1;
+
+# Remove a file from the archive.
+sub mczipfs_rm {
+ my ($qfile) = map { &zipquotemeta(zipfs_realpathname($_)) } @_;
+
+ # "./" at the beginning of pathnames is stripped by Info-ZIP,
+ # so convert it to "[.]/" to prevent stripping.
+ $qfile =~ s/^\\\./[.]/;
+
+ &checkargs(1, 'archive file', @_);
+ &safesystem("$cmd_delete $qarchive $qfile >/dev/null");
+ exit;
+}
+
+# Remove an empty directory from the archive.
+# The only difference from mczipfs_rm is that we append an
+# additional slash to the directory name to remove. I am not
+# sure this is absolutely necessary, but it doesn't hurt.
+sub mczipfs_rmdir {
+ my ($qfile) = map { &zipquotemeta(zipfs_realpathname($_)) } @_;
+ &checkargs(1, 'archive directory', @_);
+ &safesystem("$cmd_delete $qarchive $qfile/ >/dev/null", 12);
+ exit;
+}
+
+# Extract a file from the archive.
+# Note that we don't need to check if the file is a link,
+# because mc apparently doesn't call copyout for symbolic links.
+sub mczipfs_copyout {
+ my ($qafile, $qfsfile) = map { &zipquotemeta(zipfs_realpathname($_)) } @_;
+ &checkargs(1, 'archive file', @_);
+ &checkargs(2, 'local file', @_);
+ &safesystem("$cmd_extract $qarchive $qafile > $qfsfile", 11);
+ exit;
+}
+
+# Add a file to the archive.
+# This is done by making a temporary directory, in which
+# we create a symlink the original file (with a new name).
+# Zip is then run to include the real file in the archive,
+# with the name of the symbolic link.
+# Here we also doesn't need to check for symbolic links,
+# because the mc extfs doesn't allow adding of symbolic
+# links.
+sub mczipfs_copyin {
+ my ($afile, $fsfile) = @_;
+ &checkargs(1, 'archive file', @_);
+ &checkargs(2, 'local file', @_);
+ my ($qafile) = quotemeta $afile;
+ $fsfile = &absolutize($fsfile, $oldpwd);
+ my $adir = File::Basename::dirname($afile);
+
+ my $tmpdir = &mktmpdir();
+ chdir $tmpdir || &croak("chdir $tmpdir failed");
+ &mkdirs($adir, 0700);
+ symlink ($fsfile, $afile) || &croak("link $afile failed");
+ &safesystem("$cmd_add $aqarchive $qafile >/dev/null");
+ unlink $afile || &croak("unlink $afile failed");
+ &rmdirs($adir);
+ chdir $oldpwd || &croak("chdir $oldpwd failed");
+ rmdir $tmpdir || &croak("rmdir $tmpdir failed");
+ exit;
+}
+
+# Add an empty directory the the archive.
+# This is similar to mczipfs_copyin, except that we don't need
+# to use symlinks.
+sub mczipfs_mkdir {
+ my ($dir) = @_;
+ &checkargs(1, 'directory', @_);
+ my ($qdir) = quotemeta $dir;
+
+ my $tmpdir = &mktmpdir();
+ chdir $tmpdir || &croak("chdir $tmpdir failed");
+ &mkdirs($dir, 0700);
+ &safesystem("$cmd_add $aqarchive $qdir >/dev/null");
+ &rmdirs($dir);
+ chdir $oldpwd || &croak("chdir $oldpwd failed");
+ rmdir $tmpdir || &croak("rmdir $tmpdir failed");
+ exit;
+}
+
+# Add a link to the archive. This operation is not used yet,
+# because it is not supported by the MC extfs.
+sub mczipfs_mklink {
+ my ($linkdest, $afile) = @_;
+ &checkargs(1, 'link destination', @_);
+ &checkargs(2, 'archive file', @_);
+ my ($qafile) = quotemeta $afile;
+ my $adir = File::Basename::dirname($afile);
+
+ my $tmpdir = &mktmpdir();
+ chdir $tmpdir || &croak("chdir $tmpdir failed");
+ &mkdirs($adir, 0700);
+ symlink ($linkdest, $afile) || &croak("link $afile failed");
+ &safesystem("$cmd_addlink $aqarchive $qafile >/dev/null");
+ unlink $afile || &croak("unlink $afile failed");
+ &rmdirs($adir);
+ chdir $oldpwd || &croak("chdir $oldpwd failed");
+ rmdir $tmpdir || &croak("rmdir $tmpdir failed");
+ exit;
+}
+
+# This operation is not used yet, because it is not
+# supported by the MC extfs.
+sub mczipfs_linkout {
+ my ($afile, $fsfile) = @_;
+ &checkargs(1, 'archive file', @_);
+ &checkargs(2, 'local file', @_);
+ my ($qafile) = map { &zipquotemeta($_) } $afile;
+
+ my $linkdest = &get_link_destination($afile);
+ symlink ($linkdest, $fsfile) || &croak("link $fsfile failed");
+ exit;
+}
+
+# Use unzip to find the link destination of a certain file in the
+# archive.
+sub get_link_destination {
+ my ($afile) = @_;
+ my ($qafile) = map { &zipquotemeta($_) } $afile;
+ my $linkdest = safeticks("$cmd_extract $qarchive $qafile");
+ &croak ("extract failed", "link destination of $afile not found")
+ if (!defined $linkdest || $linkdest eq '');
+ return $linkdest;
+}
+
+# List files in the archive.
+# Because mc currently doesn't allow a file's parent directory
+# to be listed after the file itself, we need to do some
+# rearranging of the output. Most of this is done in
+# checked_print_file.
+sub mczipfs_list {
+ open (PIPE, "$cmd_list $qarchive |") || &croak("$app_unzip failed");
+ if ($op_has_zipinfo) {
+ while (<PIPE>) {
+ chomp;
+ next if /^Archive:/;
+ next if /^\d+ file/;
+ next if /^Empty zipfile\.$/;
+ my @match = /$regex_zipinfo_line/;
+ next if ($#match != 13);
+ &checked_print_file(@match);
+ }
+ } else {
+ while (<PIPE>) {
+ chomp;
+ my @match = /$regex_nonzipinfo_line/;
+ next if ($#match != 10);
+
+ # Massage the date.
+ my ($year, $month, $day) = $match[4] > 12
+ ? ($match[4], $match[5], $match[6]) # 4,5,6 = Y,M,D
+ : ($match[6], $match[4], $match[5]); # 4,5,6 = M,D,Y
+ $year += ($year < 70 ? 2000 : 1900) if $year < 100; # Fix 2-digit year.
+
+ my @rmatch = ('', '', 'unknown', $match[0], '', $match[2], $match[1],
+ $year, $month, $day, $match[7], $match[8], "00", $match[10]);
+ &checked_print_file(@rmatch);
+ }
+ }
+ if (!close (PIPE)) {
+ &croak("$app_unzip failed") if ($! != 0);
+ &croak("$app_unzip failed", 'non-zero exit status ('.($? >> 8).')')
+ }
+
+ foreach my $key (sort keys %pending) {
+ foreach my $file (@{ $pending{$key} }) {
+ &print_file(@{ $file });
+ }
+ }
+
+ exit;
+}
+
+# Execute a file in the archive, by first extracting it to a
+# temporary directory. The name of the extracted file will be
+# the same as the name of it in the archive.
+sub mczipfs_run {
+ my ($afile) = @_;
+ &checkargs(1, 'archive file', @_);
+ my $qafile = &zipquotemeta(zipfs_realpathname($afile));
+ my $tmpdir = &mktmpdir();
+ my $tmpfile = File::Basename::basename($afile);
+
+ chdir $tmpdir || &croak("chdir $tmpdir failed");
+ &safesystem("$cmd_extract $aqarchive $qafile > $tmpfile");
+ chmod 0700, $tmpfile;
+ &safesystem("./$tmpfile");
+ unlink $tmpfile || &croak("rm $tmpfile failed");
+ chdir $oldpwd || &croak("chdir $oldpwd failed");
+ rmdir $tmpdir || &croak("rmdir $tmpdir failed");
+ exit;
+}
+
+# This is called prior to printing the listing of a file.
+# A check is done to see if the parent directory of the file has already
+# been printed or not. If it hasn't, we must cache it (in %pending) and
+# print it later once the parent directory has been listed. When all
+# files have been processed, there may still be some that haven't been
+# printed because their parent directories weren't listed at all. These
+# files are dealt with in mczipfs_list.
+sub checked_print_file {
+ my @waiting = ([ @_ ]);
+
+ while ($#waiting != -1) {
+ my $item = shift @waiting;
+ my $filename = ${$item}[13];
+ my $dirname = File::Basename::dirname($filename) . '/';
+
+ if (exists $known{$dirname}) {
+ &print_file(@{$item});
+ if ($filename =~ /\/$/) {
+ $known{$filename} = 1;
+ if (exists $pending{$filename}) {
+ push @waiting, @{ $pending{$filename} };
+ delete $pending{$filename};
+ }
+ }
+ } else {
+ push @{$pending{$dirname}}, $item;
+ }
+ }
+}
+
+# Print the mc extfs listing of a file from a set of parsed fields.
+# If the file is a link, we extract it from the zip archive and
+# include the output as the link destination. Because this output
+# is not newline terminated, we must execute unzip once for each
+# link file encountered.
+sub print_file {
+ my ($perms,$zipver,$platform,$realsize,$format,$cmpsize,$comp,$year,$mon,$day,$hours,$mins,$secs,$filename) = @_;
+ if ($platform ne 'unx') {
+ $perms = ($filename =~ /\/$/ ? 'drwxr-xr-x' : '-rw-r--r--');
+ }
+ # adjust abnormal perms on directory
+ if ($platform eq 'unx' && $filename =~ /\/$/ && $perms =~ /^\?(.*)$/) {
+ $perms = 'd'.$1;
+ }
+ printf "%-10s 1 %-8d %-8d %8s %s/%s/%s %s:%s:%s ./%s", $perms, $<,
+ $(, $realsize, $mon, $day, $year, $hours, $mins, $secs, $filename;
+ if ($platform eq 'unx' && $perms =~ /^l/) {
+ my $linkdest = &get_link_destination($filename);
+ print " -> $linkdest";
+ }
+ print "\n";
+}
+
+# Die with a reasonable error message.
+sub croak {
+ my ($command, $desc) = @_;
+ die "uzip ($cmd): $command - $desc\n" if (defined $desc);
+ die "uzip ($cmd): $command - $!\n";
+}
+
+# Make a set of directories, like the command `mkdir -p'.
+# This subroutine has been tailored for this script, and
+# because of that, it ignored the directory name '.'.
+sub mkdirs {
+ my ($dirs, $mode) = @_;
+ $dirs = &cleandirs($dirs);
+ return if ($dirs eq '.');
+
+ my $newpos = -1;
+ while (($newpos = index($dirs, '/', $newpos+1)) != -1) {
+ my $dir = substr($dirs, 0, $newpos);
+ mkdir ($dir, $mode) || &croak("mkdir $dir failed");
+ }
+ mkdir ($dirs, $mode) || &croak("mkdir $dirs failed");
+}
+
+# Remove a set of directories, failing if the directories
+# contain other files.
+# This subroutine has been tailored for this script, and
+# because of that, it ignored the directory name '.'.
+sub rmdirs {
+ my ($dirs) = @_;
+ $dirs = &cleandirs($dirs);
+ return if ($dirs eq '.');
+
+ rmdir $dirs || &croak("rmdir $dirs failed");
+ my $newpos = length($dirs);
+ while (($newpos = rindex($dirs, '/', $newpos-1)) != -1) {
+ my $dir = substr($dirs, 0, $newpos);
+ rmdir $dir || &croak("rmdir $dir failed");
+ }
+}
+
+# Return a semi-canonical directory name.
+sub cleandirs {
+ my ($dir) = @_;
+ $dir =~ s:/+:/:g;
+ $dir =~ s:/*$::;
+ return $dir;
+}
+
+# Make a temporary directory with mode 0700.
+sub mktmpdir {
+ use File::Temp qw(mkdtemp);
+ my $template = "/tmp/mcuzipfs.XXXXXX";
+ $template="$ENV{MC_TMPDIR}/mcuzipfs.XXXXXX" if ($ENV{MC_TMPDIR});
+ return mkdtemp($template);
+}
+
+# Make a filename absolute and return it.
+sub absolutize {
+ my ($file, $pwd) = @_;
+ return "$pwd/$file" if ($file !~ /^\//);
+ return $file;
+}
+
+# Like the system built-in function, but with error checking.
+# The other argument is an exit status to allow.
+sub safesystem {
+ my ($command, @allowrc) = @_;
+ my ($desc) = ($command =~ /^([^ ]*) */);
+ $desc = File::Basename::basename($desc);
+ system $command;
+ my $rc = $?;
+ &croak("`$desc' failed") if (($rc & 0xFF) != 0);
+ if ($rc != 0) {
+ $rc = $rc >> 8;
+ foreach my $arc (@allowrc) {
+ return if ($rc == $arc);
+ }
+ &croak("`$desc' failed", "non-zero exit status ($rc)");
+ }
+}
+
+# Like backticks built-in, but with error checking.
+sub safeticks {
+ my ($command, @allowrc) = @_;
+ my ($desc) = ($command =~ /^([^ ]*) /);
+ $desc = File::Basename::basename($desc);
+ my $out = `$command`;
+ my $rc = $?;
+ &croak("`$desc' failed") if (($rc & 0xFF) != 0);
+ if ($rc != 0) {
+ $rc = $rc >> 8;
+ foreach my $arc (@allowrc) {
+ return if ($rc == $arc);
+ }
+ &croak("`$desc' failed", "non-zero exit status ($rc)");
+ }
+ return $out;
+}
+
+# Make sure enough arguments are supplied, or die.
+sub checkargs {
+ my $count = shift;
+ my $desc = shift;
+ &croak('missing argument', $desc) if ($count-1 > $#_);
+}
+
+# Quote zip wildcard metacharacters. Unfortunately Info-ZIP zip and unzip
+# on unix interpret some wildcards in filenames, despite the fact that
+# the shell already does this. Thus this function.
+sub zipquotemeta {
+ my ($name) = @_;
+ my $out = '';
+ for (my $c = 0; $c < length $name; $c++) {
+ my $ch = substr($name, $c, 1);
+ $out .= '\\' if (index('*?[]\\', $ch) != -1);
+ $out .= $ch;
+ }
+ return quotemeta($out);
+}
diff --git a/src/vfs/extfs/helpers/uzoo.in b/src/vfs/extfs/helpers/uzoo.in
new file mode 100644
index 0000000..fb079a5
--- /dev/null
+++ b/src/vfs/extfs/helpers/uzoo.in
@@ -0,0 +1,69 @@
+#! /bin/sh
+#
+# Zoo file system
+#
+# Source of zoo can be found at
+# ftp://ftp.ibiblio.org/pub/Linux/utils/compress/
+
+ZOO=${MC_TEST_EXTFS_LIST_CMD:-zoo}
+
+# Stupid zoo won't work if the archive name has no .zoo extension, so we
+# have to make a symlink with a "better" name. Also, zoo can create
+# directories even if printing files to stdout, so it's safer to confine
+# it to a temporary directory.
+mklink ()
+{
+ TMPDIR=`mktemp -d ${MC_TMPDIR:-/tmp}/mctmpdir-uzoo.XXXXXX` || exit 1
+ trap 'cd /; rm -rf "$TMPDIR"' 0 1 2 3 5 13 15
+ ARCHIVE="$TMPDIR/tmp.zoo"
+ ln -sf "$1" "$ARCHIVE"
+ cd "$TMPDIR" || exit 1
+}
+
+mczoofs_list ()
+{
+ mklink "$1"
+ $ZOO lq "$ARCHIVE" | @AWK@ -v uid=$(id -ru) '
+/^[^\ ]/ { next }
+{
+if (NF < 8)
+ next
+if ($8 ~ /^\^/)
+ $8=substr($8, 2)
+if ($6 > 50)
+ $6=$6 + 1900
+else
+ $6=$6 + 2000
+split($7, a, ":")
+split("Jan:Feb:Mar:Apr:May:Jun:Jul:Aug:Sep:Oct:Nov:Dec", month_list, ":")
+for (i=1; i<=12; i++) {
+ month[month_list[i]] = i
+}
+if ($8 ~ /\/$/)
+ printf "drwxr-xr-x 1 %-8d %-8d %8d %02d-%02d-%4d %02d:%02d %s\n", uid, 0, $1, month[$5], $4, $6, a[1], a[2], $8
+else
+ printf "-rw-r--r-- 1 %-8d %-8d %8d %02d-%02d-%4d %02d:%02d %s\n", uid, 0, $1, month[$5], $4, $6, a[1], a[2], $8
+}' 2>/dev/null
+ exit 0
+}
+
+mczoofs_copyout ()
+{
+ mklink "$1"
+ # zoo only accepts name without directory as file to extract
+ base=`echo "$2" | sed 's,.*/,,'`
+ $ZOO xpq: "$ARCHIVE" "$base" > "$3"
+ cd /
+ exit 0
+}
+
+umask 077
+
+cmd="$1"
+shift
+case "$cmd" in
+ list) mczoofs_list "$@" ;;
+ copyout) mczoofs_copyout "$@" ;;
+ *) exit 1 ;;
+esac
+exit 0
diff --git a/src/vfs/fish/Makefile.am b/src/vfs/fish/Makefile.am
new file mode 100644
index 0000000..4f3ca87
--- /dev/null
+++ b/src/vfs/fish/Makefile.am
@@ -0,0 +1,13 @@
+SUBDIRS = helpers
+DIST_SUBDIRS = helpers
+
+AM_CPPFLAGS = \
+ -DLIBEXECDIR=\""$(libexecdir)/@PACKAGE@/"\" \
+ $(GLIB_CFLAGS) \
+ -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-fish.la
+
+libvfs_fish_la_SOURCES = \
+ fish.c fish.h \
+ fishdef.h
diff --git a/src/vfs/fish/Makefile.in b/src/vfs/fish/Makefile.in
new file mode 100644
index 0000000..cd952a8
--- /dev/null
+++ b/src/vfs/fish/Makefile.in
@@ -0,0 +1,857 @@
+# 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/vfs/fish
+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)
+libvfs_fish_la_LIBADD =
+am_libvfs_fish_la_OBJECTS = fish.lo
+libvfs_fish_la_OBJECTS = $(am_libvfs_fish_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)/fish.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 = $(libvfs_fish_la_SOURCES)
+DIST_SOURCES = $(libvfs_fish_la_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+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)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+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@
+SUBDIRS = helpers
+DIST_SUBDIRS = helpers
+AM_CPPFLAGS = \
+ -DLIBEXECDIR=\""$(libexecdir)/@PACKAGE@/"\" \
+ $(GLIB_CFLAGS) \
+ -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-fish.la
+libvfs_fish_la_SOURCES = \
+ fish.c fish.h \
+ fishdef.h
+
+all: all-recursive
+
+.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/vfs/fish/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/fish/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}; \
+ }
+
+libvfs-fish.la: $(libvfs_fish_la_OBJECTS) $(libvfs_fish_la_DEPENDENCIES) $(EXTRA_libvfs_fish_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_fish_la_OBJECTS) $(libvfs_fish_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fish.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
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(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-recursive
+
+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-recursive
+
+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
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+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-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/fish.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/fish.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) 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 \
+ installdirs-am 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/vfs/fish/fish.c b/src/vfs/fish/fish.c
new file mode 100644
index 0000000..ec71a41
--- /dev/null
+++ b/src/vfs/fish/fish.c
@@ -0,0 +1,1805 @@
+/*
+ Virtual File System: FISH implementation for transferring files over
+ shell connections.
+
+ Copyright (C) 1998-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Pavel Machek, 1998
+ Michal Svec, 2000
+ Andrew Borodin <aborodin@vmail.ru>, 2010-2022
+ Slava Zanko <slavazanko@gmail.com>, 2010, 2013
+ Ilia Maslakov <il.smind@gmail.com>, 2010
+
+ Derived from ftpfs.c.
+
+ 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
+ * \brief Source: Virtual File System: FISH implementation for transferring files over
+ * shell connections
+ * \author Pavel Machek
+ * \author Michal Svec
+ * \date 1998, 2000
+ *
+ * Derived from ftpfs.c
+ * Read README.fish for protocol specification.
+ *
+ * Syntax of path is: \verbatim sh://user@host[:Cr]/path \endverbatim
+ * where C means you want compressed connection,
+ * and r means you want to use rsh
+ *
+ * Namespace: fish_vfs_ops exported.
+ */
+
+/* Define this if your ssh can take -I option */
+
+#include <config.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+#include "lib/tty/tty.h" /* enable/disable interrupt key */
+#include "lib/strescape.h"
+#include "lib/unixcompat.h"
+#include "lib/fileloc.h"
+#include "lib/util.h" /* my_exit() */
+#include "lib/mcconfig.h"
+
+#include "src/execute.h" /* pre_exec, post_exec */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/netutil.h"
+#include "lib/vfs/xdirentry.h"
+#include "lib/vfs/gc.h" /* vfs_stamp_create */
+
+#include "fish.h"
+#include "fishdef.h"
+
+/*** global variables ****************************************************************************/
+
+int fish_directory_timeout = 900;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define DO_RESOLVE_SYMLINK 1
+#define DO_OPEN 2
+#define DO_FREE_RESOURCE 4
+
+#define FISH_FLAG_COMPRESSED 1
+#define FISH_FLAG_RSH 2
+
+#define OPT_FLUSH 1
+#define OPT_IGNORE_ERROR 2
+
+/*
+ * Reply codes.
+ */
+#define PRELIM 1 /* positive preliminary */
+#define COMPLETE 2 /* positive completion */
+#define CONTINUE 3 /* positive intermediate */
+#define TRANSIENT 4 /* transient negative completion */
+#define ERROR 5 /* permanent negative completion */
+
+/* command wait_flag: */
+#define NONE 0x00
+#define WAIT_REPLY 0x01
+#define WANT_STRING 0x02
+
+/* environment flags */
+#define FISH_HAVE_HEAD 1
+#define FISH_HAVE_SED 2
+#define FISH_HAVE_AWK 4
+#define FISH_HAVE_PERL 8
+#define FISH_HAVE_LSQ 16
+#define FISH_HAVE_DATE_MDYT 32
+#define FISH_HAVE_TAIL 64
+
+#define FISH_SUPER(super) ((fish_super_t *) (super))
+#define FISH_FILE_HANDLER(fh) ((fish_file_handler_t *) fh)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ struct vfs_s_super base; /* base class */
+
+ int sockr;
+ int sockw;
+ char *scr_ls;
+ char *scr_chmod;
+ char *scr_utime;
+ char *scr_exists;
+ char *scr_mkdir;
+ char *scr_unlink;
+ char *scr_chown;
+ char *scr_rmdir;
+ char *scr_ln;
+ char *scr_mv;
+ char *scr_hardlink;
+ char *scr_get;
+ char *scr_send;
+ char *scr_append;
+ char *scr_info;
+ int host_flags;
+ GString *scr_env;
+} fish_super_t;
+
+typedef struct
+{
+ vfs_file_handler_t base; /* base class */
+
+ off_t got;
+ off_t total;
+ gboolean append;
+} fish_file_handler_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static char reply_str[80];
+
+static struct vfs_s_subclass fish_subclass;
+static struct vfs_class *vfs_fish_ops = VFS_CLASS (&fish_subclass);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_set_blksize (struct stat *s)
+{
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ /* redefine block size */
+ s->st_blksize = 64 * 1024; /* FIXME */
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct stat *
+fish_default_stat (struct vfs_class *me)
+{
+ struct stat *s;
+
+ s = vfs_s_default_stat (me, S_IFDIR | 0755);
+ fish_set_blksize (s);
+ vfs_adjust_stat (s);
+
+ return s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+fish_load_script_from_file (const char *hostname, const char *script_name, const char *def_content)
+{
+ char *scr_filename = NULL;
+ char *scr_content;
+ gsize scr_len = 0;
+
+ /* 1st: scan user directory */
+ scr_filename = g_build_path (PATH_SEP_STR, mc_config_get_data_path (), FISH_PREFIX, hostname,
+ script_name, (char *) NULL);
+ /* silent about user dir */
+ g_file_get_contents (scr_filename, &scr_content, &scr_len, NULL);
+ g_free (scr_filename);
+ /* 2nd: scan system dir */
+ if (scr_content == NULL)
+ {
+ scr_filename =
+ g_build_path (PATH_SEP_STR, LIBEXECDIR, FISH_PREFIX, script_name, (char *) NULL);
+ g_file_get_contents (scr_filename, &scr_content, &scr_len, NULL);
+ g_free (scr_filename);
+ }
+
+ if (scr_content != NULL)
+ return scr_content;
+
+ return g_strdup (def_content);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_decode_reply (char *s, gboolean was_garbage)
+{
+ int code;
+
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (s, "%d", &code) == 0)
+ {
+ code = 500;
+ return 5;
+ }
+ if (code < 100)
+ return was_garbage ? ERROR : (code == 0 ? COMPLETE : PRELIM);
+ return code / 100;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Returns a reply code, check /usr/include/arpa/ftp.h for possible values */
+
+static int
+fish_get_reply (struct vfs_class *me, int sock, char *string_buf, int string_len)
+{
+ char answer[BUF_1K];
+ gboolean was_garbage = FALSE;
+
+ while (TRUE)
+ {
+ if (!vfs_s_get_line (me, sock, answer, sizeof (answer), '\n'))
+ {
+ if (string_buf != NULL)
+ *string_buf = '\0';
+ return 4;
+ }
+
+ if (strncmp (answer, "### ", 4) == 0)
+ return fish_decode_reply (answer + 4, was_garbage ? 1 : 0);
+
+ was_garbage = TRUE;
+ if (string_buf != NULL)
+ g_strlcpy (string_buf, answer, string_len);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_command (struct vfs_class *me, struct vfs_s_super *super, int wait_reply, const char *cmd,
+ size_t cmd_len)
+{
+ ssize_t status;
+ FILE *logfile = me->logfile;
+
+ if (cmd_len == (size_t) (-1))
+ cmd_len = strlen (cmd);
+
+ if (logfile != NULL)
+ {
+ size_t ret;
+
+ ret = fwrite (cmd, cmd_len, 1, logfile);
+ ret = fflush (logfile);
+ (void) ret;
+ }
+
+ tty_enable_interrupt_key ();
+ status = write (FISH_SUPER (super)->sockw, cmd, cmd_len);
+ tty_disable_interrupt_key ();
+
+ if (status < 0)
+ return TRANSIENT;
+
+ if (wait_reply)
+ return fish_get_reply (me, FISH_SUPER (super)->sockr,
+ (wait_reply & WANT_STRING) != 0 ? reply_str :
+ NULL, sizeof (reply_str) - 1);
+ return COMPLETE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+G_GNUC_PRINTF (5, 0)
+fish_command_va (struct vfs_class *me, struct vfs_s_super *super, int wait_reply, const char *scr,
+ const char *vars, va_list ap)
+{
+ int r;
+ GString *command;
+
+ command = mc_g_string_dup (FISH_SUPER (super)->scr_env);
+ g_string_append_vprintf (command, vars, ap);
+ g_string_append (command, scr);
+ r = fish_command (me, super, wait_reply, command->str, command->len);
+ g_string_free (command, TRUE);
+
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+G_GNUC_PRINTF (5, 6)
+fish_command_v (struct vfs_class *me, struct vfs_s_super *super, int wait_reply, const char *scr,
+ const char *vars, ...)
+{
+ int r;
+ va_list ap;
+
+ va_start (ap, vars);
+ r = fish_command_va (me, super, wait_reply, scr, vars, ap);
+ va_end (ap);
+
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+G_GNUC_PRINTF (5, 6)
+fish_send_command (struct vfs_class *me, struct vfs_s_super *super, int flags, const char *scr,
+ const char *vars, ...)
+{
+ int r;
+ va_list ap;
+
+ va_start (ap, vars);
+ r = fish_command_va (me, super, WAIT_REPLY, scr, vars, ap);
+ va_end (ap);
+ vfs_stamp_create (vfs_fish_ops, super);
+
+ if (r != COMPLETE)
+ ERRNOR (E_REMOTE, -1);
+ if ((flags & OPT_FLUSH) != 0)
+ vfs_s_invalidate (me, super);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_super *
+fish_new_archive (struct vfs_class *me)
+{
+ fish_super_t *arch;
+
+ arch = g_new0 (fish_super_t, 1);
+ arch->base.me = me;
+
+ return VFS_SUPER (arch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_free_archive (struct vfs_class *me, struct vfs_s_super *super)
+{
+ fish_super_t *fish_super = FISH_SUPER (super);
+
+ if ((fish_super->sockw != -1) || (fish_super->sockr != -1))
+ vfs_print_message (_("fish: Disconnecting from %s"), super->name ? super->name : "???");
+
+ if (fish_super->sockw != -1)
+ {
+ fish_command (me, super, NONE, "#BYE\nexit\n", -1);
+ close (fish_super->sockw);
+ fish_super->sockw = -1;
+ }
+
+ if (fish_super->sockr != -1)
+ {
+ close (fish_super->sockr);
+ fish_super->sockr = -1;
+ }
+
+ g_free (fish_super->scr_ls);
+ g_free (fish_super->scr_exists);
+ g_free (fish_super->scr_mkdir);
+ g_free (fish_super->scr_unlink);
+ g_free (fish_super->scr_chown);
+ g_free (fish_super->scr_chmod);
+ g_free (fish_super->scr_utime);
+ g_free (fish_super->scr_rmdir);
+ g_free (fish_super->scr_ln);
+ g_free (fish_super->scr_mv);
+ g_free (fish_super->scr_hardlink);
+ g_free (fish_super->scr_get);
+ g_free (fish_super->scr_send);
+ g_free (fish_super->scr_append);
+ g_free (fish_super->scr_info);
+ g_string_free (fish_super->scr_env, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_pipeopen (struct vfs_s_super *super, const char *path, const char *argv[])
+{
+ int fileset1[2], fileset2[2];
+ int res;
+
+ if ((pipe (fileset1) < 0) || (pipe (fileset2) < 0))
+ vfs_die ("Cannot pipe(): %m.");
+
+ res = fork ();
+
+ if (res != 0)
+ {
+ if (res < 0)
+ vfs_die ("Cannot fork(): %m.");
+ /* We are the parent */
+ close (fileset1[0]);
+ FISH_SUPER (super)->sockw = fileset1[1];
+ close (fileset2[1]);
+ FISH_SUPER (super)->sockr = fileset2[0];
+ }
+ else
+ {
+ res = dup2 (fileset1[0], STDIN_FILENO);
+ close (fileset1[0]);
+ close (fileset1[1]);
+ res = dup2 (fileset2[1], STDOUT_FILENO);
+ close (STDERR_FILENO);
+ /* stderr to /dev/null */
+ res = open ("/dev/null", O_WRONLY);
+ close (fileset2[0]);
+ close (fileset2[1]);
+ execvp (path, (char **) argv);
+ my_exit (3);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GString *
+fish_set_env (int flags)
+{
+ GString *ret;
+
+ ret = g_string_sized_new (256);
+
+ if ((flags & FISH_HAVE_HEAD) != 0)
+ g_string_append (ret, "FISH_HAVE_HEAD=1 export FISH_HAVE_HEAD; ");
+
+ if ((flags & FISH_HAVE_SED) != 0)
+ g_string_append (ret, "FISH_HAVE_SED=1 export FISH_HAVE_SED; ");
+
+ if ((flags & FISH_HAVE_AWK) != 0)
+ g_string_append (ret, "FISH_HAVE_AWK=1 export FISH_HAVE_AWK; ");
+
+ if ((flags & FISH_HAVE_PERL) != 0)
+ g_string_append (ret, "FISH_HAVE_PERL=1 export FISH_HAVE_PERL; ");
+
+ if ((flags & FISH_HAVE_LSQ) != 0)
+ g_string_append (ret, "FISH_HAVE_LSQ=1 export FISH_HAVE_LSQ; ");
+
+ if ((flags & FISH_HAVE_DATE_MDYT) != 0)
+ g_string_append (ret, "FISH_HAVE_DATE_MDYT=1 export FISH_HAVE_DATE_MDYT; ");
+
+ if ((flags & FISH_HAVE_TAIL) != 0)
+ g_string_append (ret, "FISH_HAVE_TAIL=1 export FISH_HAVE_TAIL; ");
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+fish_info (struct vfs_class *me, struct vfs_s_super *super)
+{
+ fish_super_t *fish_super = FISH_SUPER (super);
+
+ if (fish_command (me, super, NONE, fish_super->scr_info, -1) == COMPLETE)
+ {
+ while (TRUE)
+ {
+ int res;
+ char buffer[BUF_8K] = "";
+
+ res = vfs_s_get_line_interruptible (me, buffer, sizeof (buffer), fish_super->sockr);
+ if ((res == 0) || (res == EINTR))
+ ERRNOR (ECONNRESET, FALSE);
+ if (strncmp (buffer, "### ", 4) == 0)
+ break;
+ fish_super->host_flags = atol (buffer);
+ }
+ return TRUE;
+ }
+ ERRNOR (E_PROTO, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_open_archive_pipeopen (struct vfs_s_super *super)
+{
+ char gbuf[10];
+ const char *argv[10]; /* All of 10 is used now */
+ const char *xsh = (super->path_element->port == FISH_FLAG_RSH ? "rsh" : "ssh");
+ int i = 0;
+
+ argv[i++] = xsh;
+ if (super->path_element->port == FISH_FLAG_COMPRESSED)
+ argv[i++] = "-C";
+
+ if (super->path_element->port > FISH_FLAG_RSH)
+ {
+ argv[i++] = "-p";
+ g_snprintf (gbuf, sizeof (gbuf), "%d", super->path_element->port);
+ argv[i++] = gbuf;
+ }
+
+ /*
+ * Add the user name to the ssh command line only if it was explicitly
+ * set in vfs URL. rsh/ssh will get current user by default
+ * plus we can set convenient overrides in ~/.ssh/config (explicit -l
+ * option breaks it for some)
+ */
+
+ if (super->path_element->user != NULL)
+ {
+ argv[i++] = "-l";
+ argv[i++] = super->path_element->user;
+ }
+ else
+ {
+ /* The rest of the code assumes it to be a valid username */
+ super->path_element->user = vfs_get_local_username ();
+ }
+
+ argv[i++] = super->path_element->host;
+ argv[i++] = "echo FISH:; /bin/sh";
+ argv[i++] = NULL;
+
+ fish_pipeopen (super, xsh, argv);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+fish_open_archive_talk (struct vfs_class *me, struct vfs_s_super *super)
+{
+ fish_super_t *fish_super = FISH_SUPER (super);
+ char answer[2048];
+
+ printf ("\n%s\n", _("fish: Waiting for initial line..."));
+
+ if (vfs_s_get_line (me, fish_super->sockr, answer, sizeof (answer), ':') == 0)
+ return FALSE;
+
+ if (strstr (answer, "assword") != NULL)
+ {
+ /* Currently, this does not work. ssh reads passwords from
+ /dev/tty, not from stdin :-(. */
+
+ printf ("\n%s\n", _("Sorry, we cannot do password authenticated connections for now."));
+
+ return FALSE;
+#if 0
+ if (super->path_element->password == NULL)
+ {
+ char *p, *op;
+
+ p = g_strdup_printf (_("fish: Password is required for %s"), super->path_element->user);
+ op = vfs_get_password (p);
+ g_free (p);
+ if (op == NULL)
+ return FALSE;
+ super->path_element->password = op;
+ }
+
+ printf ("\n%s\n", _("fish: Sending password..."));
+
+ {
+ size_t str_len;
+
+ str_len = strlen (super->path_element->password);
+ if ((write (fish_super.sockw, super->path_element->password, str_len) !=
+ (ssize_t) str_len) || (write (fish_super->sockw, "\n", 1) != 1))
+ return FALSE;
+ }
+#endif
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_open_archive_int (struct vfs_class *me, struct vfs_s_super *super)
+{
+ gboolean ftalk;
+
+ /* hide panels */
+ pre_exec ();
+
+ /* open pipe */
+ fish_open_archive_pipeopen (super);
+
+ /* Start talk with ssh-server (password prompt, etc ) */
+ ftalk = fish_open_archive_talk (me, super);
+
+ /* show panels */
+ post_exec ();
+
+ if (!ftalk)
+ ERRNOR (E_PROTO, -1);
+
+ vfs_print_message ("%s", _("fish: Sending initial line..."));
+ /*
+ * Run 'start_fish_server'. If it doesn't exist - no problem,
+ * we'll talk directly to the shell.
+ */
+
+ if (fish_command
+ (me, super, WAIT_REPLY, "#FISH\necho; start_fish_server 2>&1; echo '### 200'\n",
+ -1) != COMPLETE)
+ ERRNOR (E_PROTO, -1);
+
+ vfs_print_message ("%s", _("fish: Handshaking version..."));
+ if (fish_command (me, super, WAIT_REPLY, "#VER 0.0.3\necho '### 000'\n", -1) != COMPLETE)
+ ERRNOR (E_PROTO, -1);
+
+ /* Set up remote locale to C, otherwise dates cannot be recognized */
+ if (fish_command
+ (me, super, WAIT_REPLY,
+ "LANG=C LC_ALL=C LC_TIME=C; export LANG LC_ALL LC_TIME;\n" "echo '### 200'\n",
+ -1) != COMPLETE)
+ ERRNOR (E_PROTO, -1);
+
+ vfs_print_message ("%s", _("fish: Getting host info..."));
+ if (fish_info (me, super))
+ FISH_SUPER (super)->scr_env = fish_set_env (FISH_SUPER (super)->host_flags);
+
+#if 0
+ super->name =
+ g_strconcat ("sh://", super->path_element->user, "@", super->path_element->host,
+ PATH_SEP_STR, (char *) NULL);
+#else
+ super->name = g_strdup (PATH_SEP_STR);
+#endif
+
+ super->root = vfs_s_new_inode (me, super, fish_default_stat (me));
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_open_archive (struct vfs_s_super *super,
+ const vfs_path_t * vpath, const vfs_path_element_t * vpath_element)
+{
+ fish_super_t *fish_super = FISH_SUPER (super);
+
+ (void) vpath;
+
+ super->path_element = vfs_path_element_clone (vpath_element);
+
+ if (strncmp (vpath_element->vfs_prefix, "rsh", 3) == 0)
+ super->path_element->port = FISH_FLAG_RSH;
+
+ fish_super->scr_ls =
+ fish_load_script_from_file (super->path_element->host, FISH_LS_FILE, FISH_LS_DEF_CONTENT);
+ fish_super->scr_exists =
+ fish_load_script_from_file (super->path_element->host, FISH_EXISTS_FILE,
+ FISH_EXISTS_DEF_CONTENT);
+ fish_super->scr_mkdir =
+ fish_load_script_from_file (super->path_element->host, FISH_MKDIR_FILE,
+ FISH_MKDIR_DEF_CONTENT);
+ fish_super->scr_unlink =
+ fish_load_script_from_file (super->path_element->host, FISH_UNLINK_FILE,
+ FISH_UNLINK_DEF_CONTENT);
+ fish_super->scr_chown =
+ fish_load_script_from_file (super->path_element->host, FISH_CHOWN_FILE,
+ FISH_CHOWN_DEF_CONTENT);
+ fish_super->scr_chmod =
+ fish_load_script_from_file (super->path_element->host, FISH_CHMOD_FILE,
+ FISH_CHMOD_DEF_CONTENT);
+ fish_super->scr_utime =
+ fish_load_script_from_file (super->path_element->host, FISH_UTIME_FILE,
+ FISH_UTIME_DEF_CONTENT);
+ fish_super->scr_rmdir =
+ fish_load_script_from_file (super->path_element->host, FISH_RMDIR_FILE,
+ FISH_RMDIR_DEF_CONTENT);
+ fish_super->scr_ln =
+ fish_load_script_from_file (super->path_element->host, FISH_LN_FILE, FISH_LN_DEF_CONTENT);
+ fish_super->scr_mv =
+ fish_load_script_from_file (super->path_element->host, FISH_MV_FILE, FISH_MV_DEF_CONTENT);
+ fish_super->scr_hardlink =
+ fish_load_script_from_file (super->path_element->host, FISH_HARDLINK_FILE,
+ FISH_HARDLINK_DEF_CONTENT);
+ fish_super->scr_get =
+ fish_load_script_from_file (super->path_element->host, FISH_GET_FILE, FISH_GET_DEF_CONTENT);
+ fish_super->scr_send =
+ fish_load_script_from_file (super->path_element->host, FISH_SEND_FILE,
+ FISH_SEND_DEF_CONTENT);
+ fish_super->scr_append =
+ fish_load_script_from_file (super->path_element->host, FISH_APPEND_FILE,
+ FISH_APPEND_DEF_CONTENT);
+ fish_super->scr_info =
+ fish_load_script_from_file (super->path_element->host, FISH_INFO_FILE,
+ FISH_INFO_DEF_CONTENT);
+
+ return fish_open_archive_int (vpath_element->class, super);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_archive_same (const vfs_path_element_t * vpath_element, struct vfs_s_super *super,
+ const vfs_path_t * vpath, void *cookie)
+{
+ vfs_path_element_t *path_element;
+ int result;
+
+ (void) vpath;
+ (void) cookie;
+
+ path_element = vfs_path_element_clone (vpath_element);
+
+ if (path_element->user == NULL)
+ path_element->user = vfs_get_local_username ();
+
+ result = ((strcmp (path_element->host, super->path_element->host) == 0)
+ && (strcmp (path_element->user, super->path_element->user) == 0)
+ && (path_element->port == super->path_element->port)) ? 1 : 0;
+
+ vfs_path_element_free (path_element);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_parse_ls (char *buffer, struct vfs_s_entry *ent)
+{
+#define ST ent->ino->st
+
+ buffer++;
+
+ switch (buffer[-1])
+ {
+ case ':':
+ {
+ char *filename;
+ char *filename_bound;
+ char *temp;
+
+ filename = buffer;
+
+ if (strcmp (filename, "\".\"") == 0 || strcmp (filename, "\"..\"") == 0)
+ break; /* We'll do "." and ".." ourselves */
+
+ filename_bound = filename + strlen (filename);
+
+ if (S_ISLNK (ST.st_mode))
+ {
+ char *linkname;
+ char *linkname_bound;
+ /* we expect: "escaped-name" -> "escaped-name"
+ // -> cannot occur in filenames,
+ // because it will be escaped to -\> */
+
+
+ linkname_bound = filename_bound;
+
+ if (*filename == '"')
+ ++filename;
+
+ linkname = strstr (filename, "\" -> \"");
+ if (linkname == NULL)
+ {
+ /* broken client, or smth goes wrong */
+ linkname = filename_bound;
+ if (filename_bound > filename && *(filename_bound - 1) == '"')
+ --filename_bound; /* skip trailing " */
+ }
+ else
+ {
+ filename_bound = linkname;
+ linkname += 6; /* strlen ("\" -> \"") */
+ if (*(linkname_bound - 1) == '"')
+ --linkname_bound; /* skip trailing " */
+ }
+
+ ent->name = g_strndup (filename, filename_bound - filename);
+ temp = ent->name;
+ ent->name = strutils_shell_unescape (ent->name);
+ g_free (temp);
+
+ ent->ino->linkname = g_strndup (linkname, linkname_bound - linkname);
+ temp = ent->ino->linkname;
+ ent->ino->linkname = strutils_shell_unescape (ent->ino->linkname);
+ g_free (temp);
+ }
+ else
+ {
+ /* we expect: "escaped-name" */
+ if (filename_bound - filename > 2)
+ {
+ /*
+ there is at least 2 "
+ and we skip them
+ */
+ if (*filename == '"')
+ ++filename;
+ if (*(filename_bound - 1) == '"')
+ --filename_bound;
+ }
+
+ ent->name = g_strndup (filename, filename_bound - filename);
+ temp = ent->name;
+ ent->name = strutils_shell_unescape (ent->name);
+ g_free (temp);
+ }
+ break;
+ }
+
+ case 'S':
+ ST.st_size = (off_t) g_ascii_strtoll (buffer, NULL, 10);
+ break;
+
+ case 'P':
+ {
+ size_t skipped;
+
+ vfs_parse_filemode (buffer, &skipped, &ST.st_mode);
+ break;
+ }
+
+ case 'R':
+ {
+ /*
+ raw filemode:
+ we expect: Roctal-filemode octal-filetype uid.gid
+ */
+ size_t skipped;
+
+ vfs_parse_raw_filemode (buffer, &skipped, &ST.st_mode);
+ break;
+ }
+
+ case 'd':
+ vfs_split_text (buffer);
+ if (vfs_parse_filedate (0, &ST.st_ctime) == 0)
+ break;
+ ST.st_atime = ST.st_mtime = ST.st_ctime;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ ST.st_atim.tv_nsec = ST.st_mtim.tv_nsec = ST.st_ctim.tv_nsec = 0;
+#endif
+ break;
+
+ case 'D':
+ {
+ struct tm tim;
+
+ memset (&tim, 0, sizeof (tim));
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (buffer, "%d %d %d %d %d %d", &tim.tm_year, &tim.tm_mon,
+ &tim.tm_mday, &tim.tm_hour, &tim.tm_min, &tim.tm_sec) != 6)
+ break;
+ ST.st_atime = ST.st_mtime = ST.st_ctime = mktime (&tim);
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ ST.st_atim.tv_nsec = ST.st_mtim.tv_nsec = ST.st_ctim.tv_nsec = 0;
+#endif
+ }
+ break;
+
+ case 'E':
+ {
+ int maj, min;
+
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (buffer, "%d,%d", &maj, &min) != 2)
+ break;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ ST.st_rdev = makedev (maj, min);
+#endif
+ }
+ break;
+
+ default:
+ break;
+ }
+
+#undef ST
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_dir_load (struct vfs_class *me, struct vfs_s_inode *dir, const char *remote_path)
+{
+ struct vfs_s_super *super = dir->super;
+ char buffer[BUF_8K] = "\0";
+ struct vfs_s_entry *ent = NULL;
+ char *quoted_path;
+ int reply_code;
+
+ /*
+ * Simple FISH debug interface :]
+ */
+#if 0
+ if (me->logfile == NULL)
+ me->logfile = fopen ("/tmp/mc-FISH.sh", "w");
+#endif
+
+ vfs_print_message (_("fish: Reading directory %s..."), remote_path);
+
+ dir->timestamp = g_get_monotonic_time () + fish_directory_timeout * G_USEC_PER_SEC;
+
+ quoted_path = strutils_shell_escape (remote_path);
+ (void) fish_command_v (me, super, NONE, FISH_SUPER (super)->scr_ls, "FISH_FILENAME=%s;\n",
+ quoted_path);
+ g_free (quoted_path);
+
+ ent = vfs_s_generate_entry (me, NULL, dir, 0);
+
+ while (TRUE)
+ {
+ int res;
+
+ res = vfs_s_get_line_interruptible (me, buffer, sizeof (buffer), FISH_SUPER (super)->sockr);
+
+ if ((res == 0) || (res == EINTR))
+ {
+ vfs_s_free_entry (me, ent);
+ me->verrno = ECONNRESET;
+ goto error;
+ }
+ if (me->logfile != NULL)
+ {
+ fputs (buffer, me->logfile);
+ fputs ("\n", me->logfile);
+ fflush (me->logfile);
+ }
+ if (strncmp (buffer, "### ", 4) == 0)
+ break;
+
+ if (buffer[0] != '\0')
+ fish_parse_ls (buffer, ent);
+ else if (ent->name != NULL)
+ {
+ vfs_s_insert_entry (me, dir, ent);
+ ent = vfs_s_generate_entry (me, NULL, dir, 0);
+ }
+ }
+
+ vfs_s_free_entry (me, ent);
+ reply_code = fish_decode_reply (buffer + 4, 0);
+ if (reply_code == COMPLETE)
+ {
+ vfs_print_message (_("%s: done."), me->name);
+ return 0;
+ }
+
+ me->verrno = reply_code == ERROR ? EACCES : E_REMOTE;
+
+ error:
+ vfs_print_message (_("%s: failure"), me->name);
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_file_store (struct vfs_class *me, vfs_file_handler_t * fh, char *name, char *localname)
+{
+ fish_file_handler_t *fish = FISH_FILE_HANDLER (fh);
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ fish_super_t *fish_super = FISH_SUPER (super);
+ int code;
+ off_t total = 0;
+ char buffer[BUF_8K];
+ struct stat s;
+ int h;
+ char *quoted_name;
+
+ h = open (localname, O_RDONLY);
+ if (h == -1)
+ ERRNOR (EIO, -1);
+ if (fstat (h, &s) < 0)
+ {
+ close (h);
+ ERRNOR (EIO, -1);
+ }
+
+ /* First, try this as stor:
+ *
+ * ( head -c number ) | ( cat > file; cat >/dev/null )
+ *
+ * If 'head' is not present on the remote system, 'dd' will be used.
+ * Unfortunately, we cannot trust most non-GNU 'head' implementations
+ * even if '-c' options is supported. Therefore, we separate GNU head
+ * (and other modern heads?) using '-q' and '-' . This causes another
+ * implementations to fail (because of "incorrect options").
+ *
+ * Fallback is:
+ *
+ * rest=<number>
+ * while [ $rest -gt 0 ]
+ * do
+ * cnt=`expr \( $rest + 255 \) / 256`
+ * n=`dd bs=256 count=$cnt | tee -a <target_file> | wc -c`
+ * rest=`expr $rest - $n`
+ * done
+ *
+ * 'dd' was not designed for full filling of input buffers,
+ * and does not report exact number of bytes (not blocks).
+ * Therefore a more complex shell script is needed.
+ *
+ * On some systems non-GNU head writes "Usage:" error report to stdout
+ * instead of stderr. It makes impossible the use of "head || dd"
+ * algorithm for file appending case, therefore just "dd" is used for it.
+ */
+
+ quoted_name = strutils_shell_escape (name);
+ vfs_print_message (_("fish: store %s: sending command..."), quoted_name);
+
+ /* FIXME: File size is limited to ULONG_MAX */
+ code =
+ fish_command_v (me, super, WAIT_REPLY,
+ fish->append ? fish_super->scr_append : fish_super->scr_send,
+ "FISH_FILENAME=%s FISH_FILESIZE=%" PRIuMAX ";\n", quoted_name,
+ (uintmax_t) s.st_size);
+ g_free (quoted_name);
+
+ if (code != PRELIM)
+ {
+ close (h);
+ ERRNOR (E_REMOTE, -1);
+ }
+
+ while (TRUE)
+ {
+ ssize_t n, t;
+
+ while ((n = read (h, buffer, sizeof (buffer))) < 0)
+ {
+ if ((errno == EINTR) && tty_got_interrupt ())
+ continue;
+ vfs_print_message ("%s", _("fish: Local read failed, sending zeros"));
+ close (h);
+ h = open ("/dev/zero", O_RDONLY);
+ }
+
+ if (n == 0)
+ break;
+
+ t = write (fish_super->sockw, buffer, n);
+ if (t != n)
+ {
+ if (t == -1)
+ me->verrno = errno;
+ else
+ me->verrno = EIO;
+ goto error_return;
+ }
+ tty_disable_interrupt_key ();
+ total += n;
+ vfs_print_message ("%s: %" PRIuMAX "/%" PRIuMAX, _("fish: storing file"),
+ (uintmax_t) total, (uintmax_t) s.st_size);
+ }
+ close (h);
+
+ if (fish_get_reply (me, fish_super->sockr, NULL, 0) != COMPLETE)
+ ERRNOR (E_REMOTE, -1);
+ return 0;
+
+ error_return:
+ close (h);
+ fish_get_reply (me, fish_super->sockr, NULL, 0);
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_linear_start (struct vfs_class *me, vfs_file_handler_t * fh, off_t offset)
+{
+ fish_file_handler_t *fish = FISH_FILE_HANDLER (fh);
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ char *name;
+ char *quoted_name;
+
+ name = vfs_s_fullpath (me, fh->ino);
+ if (name == NULL)
+ return 0;
+ quoted_name = strutils_shell_escape (name);
+ g_free (name);
+ fish->append = FALSE;
+
+ /*
+ * Check whether the remote file is readable by using 'dd' to copy
+ * a single byte from the remote file to /dev/null. If 'dd' completes
+ * with exit status of 0 use 'cat' to send the file contents to the
+ * standard output (i.e. over the network).
+ */
+
+ offset =
+ fish_command_v (me, super, WANT_STRING, FISH_SUPER (super)->scr_get,
+ "FISH_FILENAME=%s FISH_START_OFFSET=%" PRIuMAX ";\n", quoted_name,
+ (uintmax_t) offset);
+ g_free (quoted_name);
+
+ if (offset != PRELIM)
+ ERRNOR (E_REMOTE, 0);
+ fh->linear = LS_LINEAR_OPEN;
+ fish->got = 0;
+ errno = 0;
+#if SIZEOF_OFF_T == SIZEOF_LONG
+ fish->total = (off_t) strtol (reply_str, NULL, 10);
+#else
+ fish->total = (off_t) g_ascii_strtoll (reply_str, NULL, 10);
+#endif
+ if (errno != 0)
+ ERRNOR (E_REMOTE, 0);
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_linear_abort (struct vfs_class *me, vfs_file_handler_t * fh)
+{
+ fish_file_handler_t *fish = FISH_FILE_HANDLER (fh);
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ char buffer[BUF_8K];
+ ssize_t n;
+
+ vfs_print_message ("%s", _("Aborting transfer..."));
+
+ do
+ {
+ n = MIN ((off_t) sizeof (buffer), (fish->total - fish->got));
+ if (n != 0)
+ {
+ n = read (FISH_SUPER (super)->sockr, buffer, n);
+ if (n < 0)
+ return;
+ fish->got += n;
+ }
+ }
+ while (n != 0);
+
+ if (fish_get_reply (me, FISH_SUPER (super)->sockr, NULL, 0) != COMPLETE)
+ vfs_print_message ("%s", _("Error reported after abort."));
+ else
+ vfs_print_message ("%s", _("Aborted transfer would be successful."));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+fish_linear_read (struct vfs_class *me, vfs_file_handler_t * fh, void *buf, size_t len)
+{
+ fish_file_handler_t *fish = FISH_FILE_HANDLER (fh);
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ ssize_t n = 0;
+
+ len = MIN ((size_t) (fish->total - fish->got), len);
+ tty_disable_interrupt_key ();
+ while (len != 0 && ((n = read (FISH_SUPER (super)->sockr, buf, len)) < 0))
+ {
+ if ((errno == EINTR) && !tty_got_interrupt ())
+ continue;
+ break;
+ }
+ tty_enable_interrupt_key ();
+
+ if (n > 0)
+ fish->got += n;
+ else if (n < 0)
+ fish_linear_abort (me, fh);
+ else if (fish_get_reply (me, FISH_SUPER (super)->sockr, NULL, 0) != COMPLETE)
+ ERRNOR (E_REMOTE, -1);
+ ERRNOR (errno, n);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_linear_close (struct vfs_class *me, vfs_file_handler_t * fh)
+{
+ fish_file_handler_t *fish = FISH_FILE_HANDLER (fh);
+
+ if (fish->total != fish->got)
+ fish_linear_abort (me, fh);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_ctl (void *fh, int ctlop, void *arg)
+{
+ (void) arg;
+ (void) fh;
+ (void) ctlop;
+
+ return 0;
+
+#if 0
+ switch (ctlop)
+ {
+ case VFS_CTL_IS_NOTREADY:
+ {
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ int v;
+
+ if (file->linear == LS_NOT_LINEAR)
+ vfs_die ("You may not do this");
+ if (file->linear == LS_LINEAR_CLOSED || file->linear == LS_LINEAR_PREOPEN)
+ return 0;
+
+ v = vfs_s_select_on_two (VFS_FILE_HANDLER_SUPER (fh)->u.fish.sockr, 0);
+
+ return (((v < 0) && (errno == EINTR)) || v == 0) ? 1 : 0;
+ }
+ default:
+ return 0;
+ }
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ const char *crpath1, *crpath2;
+ char *rpath1, *rpath2;
+ struct vfs_s_super *super, *super2;
+ struct vfs_class *me;
+ int ret;
+
+ crpath1 = vfs_s_get_path (vpath1, &super, 0);
+ if (crpath1 == NULL)
+ return -1;
+
+ crpath2 = vfs_s_get_path (vpath2, &super2, 0);
+ if (crpath2 == NULL)
+ return -1;
+
+ rpath1 = strutils_shell_escape (crpath1);
+ rpath2 = strutils_shell_escape (crpath2);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath1));
+
+ ret =
+ fish_send_command (me, super2, OPT_FLUSH, FISH_SUPER (super)->scr_mv,
+ "FISH_FILEFROM=%s FISH_FILETO=%s;\n", rpath1, rpath2);
+
+ g_free (rpath1);
+ g_free (rpath2);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_link (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ const char *crpath1, *crpath2;
+ char *rpath1, *rpath2;
+ struct vfs_s_super *super, *super2;
+ struct vfs_class *me;
+ int ret;
+
+ crpath1 = vfs_s_get_path (vpath1, &super, 0);
+ if (crpath1 == NULL)
+ return -1;
+
+ crpath2 = vfs_s_get_path (vpath2, &super2, 0);
+ if (crpath2 == NULL)
+ return -1;
+
+ rpath1 = strutils_shell_escape (crpath1);
+ rpath2 = strutils_shell_escape (crpath2);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath1));
+
+ ret =
+ fish_send_command (me, super2, OPT_FLUSH, FISH_SUPER (super)->scr_hardlink,
+ "FISH_FILEFROM=%s FISH_FILETO=%s;\n", rpath1, rpath2);
+
+ g_free (rpath1);
+ g_free (rpath2);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ char *qsetto;
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ crpath = vfs_s_get_path (vpath2, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+ qsetto = strutils_shell_escape (vfs_path_get_last_path_str (vpath1));
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath2));
+
+ ret =
+ fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_ln,
+ "FISH_FILEFROM=%s FISH_FILETO=%s;\n", qsetto, rpath);
+
+ g_free (qsetto);
+ g_free (rpath);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ int ret;
+
+ ret = vfs_s_stat (vpath, buf);
+ fish_set_blksize (buf);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ int ret;
+
+ ret = vfs_s_lstat (vpath, buf);
+ fish_set_blksize (buf);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_fstat (void *vfs_info, struct stat *buf)
+{
+ int ret;
+
+ ret = vfs_s_fstat (vfs_info, buf);
+ fish_set_blksize (buf);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_chmod (const vfs_path_t * vpath, mode_t mode)
+{
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ crpath = vfs_s_get_path (vpath, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ret =
+ fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_chmod,
+ "FISH_FILENAME=%s FISH_FILEMODE=%4.4o;\n", rpath,
+ (unsigned int) (mode & 07777));
+
+ g_free (rpath);
+
+ return ret;;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_chown (const vfs_path_t * vpath, uid_t owner, gid_t group)
+{
+ char *sowner, *sgroup;
+ struct passwd *pw;
+ struct group *gr;
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ pw = getpwuid (owner);
+ if (pw == NULL)
+ return 0;
+
+ gr = getgrgid (group);
+ if (gr == NULL)
+ return 0;
+
+ sowner = pw->pw_name;
+ sgroup = gr->gr_name;
+
+ crpath = vfs_s_get_path (vpath, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ /* FIXME: what should we report if chgrp succeeds but chown fails? */
+ ret =
+ fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_chown,
+ "FISH_FILENAME=%s FISH_FILEOWNER=%s FISH_FILEGROUP=%s;\n", rpath, sowner,
+ sgroup);
+
+ g_free (rpath);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_get_atime (mc_timesbuf_t * times, time_t * sec, long *nsec)
+{
+#ifdef HAVE_UTIMENSAT
+ *sec = (*times)[0].tv_sec;
+ *nsec = (*times)[0].tv_nsec;
+#else
+ *sec = times->actime;
+ *nsec = 0;
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_get_mtime (mc_timesbuf_t * times, time_t * sec, long *nsec)
+{
+#ifdef HAVE_UTIMENSAT
+ *sec = (*times)[1].tv_sec;
+ *nsec = (*times)[1].tv_nsec;
+#else
+ *sec = times->modtime;
+ *nsec = 0;
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_utime (const vfs_path_t * vpath, mc_timesbuf_t * times)
+{
+ char utcatime[16], utcmtime[16];
+ char utcatime_w_nsec[30], utcmtime_w_nsec[30];
+ time_t atime, mtime;
+ long atime_nsec, mtime_nsec;
+ struct tm *gmt;
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ crpath = vfs_s_get_path (vpath, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+
+ fish_get_atime (times, &atime, &atime_nsec);
+ gmt = gmtime (&atime);
+ g_snprintf (utcatime, sizeof (utcatime), "%04d%02d%02d%02d%02d.%02d",
+ gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday,
+ gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
+ g_snprintf (utcatime_w_nsec, sizeof (utcatime_w_nsec), "%04d-%02d-%02d %02d:%02d:%02d.%09ld",
+ gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday,
+ gmt->tm_hour, gmt->tm_min, gmt->tm_sec, atime_nsec);
+
+ fish_get_mtime (times, &mtime, &mtime_nsec);
+ gmt = gmtime (&mtime);
+ g_snprintf (utcmtime, sizeof (utcmtime), "%04d%02d%02d%02d%02d.%02d",
+ gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday,
+ gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
+ g_snprintf (utcmtime_w_nsec, sizeof (utcmtime_w_nsec), "%04d-%02d-%02d %02d:%02d:%02d.%09ld",
+ gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday,
+ gmt->tm_hour, gmt->tm_min, gmt->tm_sec, mtime_nsec);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ret = fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_utime,
+ "FISH_FILENAME=%s FISH_FILEATIME=%ld FISH_FILEMTIME=%ld "
+ "FISH_TOUCHATIME=%s FISH_TOUCHMTIME=%s FISH_TOUCHATIME_W_NSEC=\"%s\" "
+ "FISH_TOUCHMTIME_W_NSEC=\"%s\";\n", rpath, (long) atime, (long) mtime,
+ utcatime, utcmtime, utcatime_w_nsec, utcmtime_w_nsec);
+
+ g_free (rpath);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_unlink (const vfs_path_t * vpath)
+{
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ crpath = vfs_s_get_path (vpath, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ret =
+ fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_unlink,
+ "FISH_FILENAME=%s;\n", rpath);
+
+ g_free (rpath);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_exists (const vfs_path_t * vpath)
+{
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ crpath = vfs_s_get_path (vpath, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ret =
+ fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_exists,
+ "FISH_FILENAME=%s;\n", rpath);
+
+ g_free (rpath);
+
+ return (ret == 0 ? 1 : 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_mkdir (const vfs_path_t * vpath, mode_t mode)
+{
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ (void) mode;
+
+ crpath = vfs_s_get_path (vpath, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ret =
+ fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_mkdir,
+ "FISH_FILENAME=%s;\n", rpath);
+ g_free (rpath);
+
+ if (ret != 0)
+ return ret;
+
+ if (fish_exists (vpath) == 0)
+ {
+ me->verrno = EACCES;
+ return -1;
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_rmdir (const vfs_path_t * vpath)
+{
+ const char *crpath;
+ char *rpath;
+ struct vfs_s_super *super;
+ struct vfs_class *me;
+ int ret;
+
+ crpath = vfs_s_get_path (vpath, &super, 0);
+ if (crpath == NULL)
+ return -1;
+
+ rpath = strutils_shell_escape (crpath);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ret =
+ fish_send_command (me, super, OPT_FLUSH, FISH_SUPER (super)->scr_rmdir,
+ "FISH_FILENAME=%s;\n", rpath);
+
+ g_free (rpath);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_file_handler_t *
+fish_fh_new (struct vfs_s_inode *ino, gboolean changed)
+{
+ fish_file_handler_t *fh;
+
+ fh = g_new0 (fish_file_handler_t, 1);
+ vfs_s_init_fh (VFS_FILE_HANDLER (fh), ino, changed);
+
+ return VFS_FILE_HANDLER (fh);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+fish_fh_open (struct vfs_class *me, vfs_file_handler_t * fh, int flags, mode_t mode)
+{
+ fish_file_handler_t *fish = FISH_FILE_HANDLER (fh);
+
+ (void) mode;
+
+ /* File will be written only, so no need to retrieve it */
+ if (((flags & O_WRONLY) == O_WRONLY) && ((flags & (O_RDONLY | O_RDWR)) == 0))
+ {
+ /* user pressed the button [ Append ] in the "Copy" dialog */
+ if ((flags & O_APPEND) != 0)
+ fish->append = TRUE;
+
+ if (fh->ino->localname == NULL)
+ {
+ vfs_path_t *vpath = NULL;
+ int tmp_handle;
+
+ tmp_handle = vfs_mkstemps (&vpath, me->name, fh->ino->ent->name);
+ if (tmp_handle == -1)
+ return (-1);
+
+ fh->ino->localname = vfs_path_free (vpath, FALSE);
+ close (tmp_handle);
+ }
+ return 0;
+ }
+
+ if (fh->ino->localname == NULL && vfs_s_retrieve_file (me, fh->ino) == -1)
+ return (-1);
+
+ if (fh->ino->localname == NULL)
+ vfs_die ("retrieve_file failed to fill in localname");
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fish_fill_names (struct vfs_class *me, fill_names_f func)
+{
+ GList *iter;
+
+ for (iter = VFS_SUBCLASS (me)->supers; iter != NULL; iter = g_list_next (iter))
+ {
+ const struct vfs_s_super *super = (const struct vfs_s_super *) iter->data;
+
+ char *name;
+ char gbuf[10];
+ const char *flags = "";
+
+ switch (super->path_element->port)
+ {
+ case FISH_FLAG_RSH:
+ flags = ":r";
+ break;
+ case FISH_FLAG_COMPRESSED:
+ flags = ":C";
+ break;
+ default:
+ if (super->path_element->port > FISH_FLAG_RSH)
+ {
+ g_snprintf (gbuf, sizeof (gbuf), ":%d", super->path_element->port);
+ flags = gbuf;
+ }
+ break;
+ }
+
+ name =
+ g_strconcat (vfs_fish_ops->prefix, VFS_PATH_URL_DELIMITER,
+ super->path_element->user, "@", super->path_element->host, flags,
+ PATH_SEP_STR, super->path_element->path, (char *) NULL);
+ func (name);
+ g_free (name);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+fish_open (const vfs_path_t * vpath, int flags, mode_t mode)
+{
+ /*
+ sorry, i've places hack here
+ cause fish don't able to open files with O_EXCL flag
+ */
+ flags &= ~O_EXCL;
+ return vfs_s_open (vpath, flags, mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_fish (void)
+{
+ tcp_init ();
+
+ vfs_init_subclass (&fish_subclass, "fish", VFSF_REMOTE | VFSF_USETMP, "sh");
+ vfs_fish_ops->fill_names = fish_fill_names;
+ vfs_fish_ops->stat = fish_stat;
+ vfs_fish_ops->lstat = fish_lstat;
+ vfs_fish_ops->fstat = fish_fstat;
+ vfs_fish_ops->chmod = fish_chmod;
+ vfs_fish_ops->chown = fish_chown;
+ vfs_fish_ops->utime = fish_utime;
+ vfs_fish_ops->open = fish_open;
+ vfs_fish_ops->symlink = fish_symlink;
+ vfs_fish_ops->link = fish_link;
+ vfs_fish_ops->unlink = fish_unlink;
+ vfs_fish_ops->rename = fish_rename;
+ vfs_fish_ops->mkdir = fish_mkdir;
+ vfs_fish_ops->rmdir = fish_rmdir;
+ vfs_fish_ops->ctl = fish_ctl;
+ fish_subclass.archive_same = fish_archive_same;
+ fish_subclass.new_archive = fish_new_archive;
+ fish_subclass.open_archive = fish_open_archive;
+ fish_subclass.free_archive = fish_free_archive;
+ fish_subclass.fh_new = fish_fh_new;
+ fish_subclass.fh_open = fish_fh_open;
+ fish_subclass.dir_load = fish_dir_load;
+ fish_subclass.file_store = fish_file_store;
+ fish_subclass.linear_start = fish_linear_start;
+ fish_subclass.linear_read = fish_linear_read;
+ fish_subclass.linear_close = fish_linear_close;
+ vfs_register_class (vfs_fish_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/fish/fish.h b/src/vfs/fish/fish.h
new file mode 100644
index 0000000..3c1fa06
--- /dev/null
+++ b/src/vfs/fish/fish.h
@@ -0,0 +1,28 @@
+
+/**
+ * \file
+ * \brief Header: Virtual File System: FISH implementation for transferring files over
+ * shell connections
+ */
+
+
+#ifndef MC__VFS_FISH_H
+#define MC__VFS_FISH_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern int fish_directory_timeout;
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_fish (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif
diff --git a/src/vfs/fish/fishdef.h b/src/vfs/fish/fishdef.h
new file mode 100644
index 0000000..129d2b9
--- /dev/null
+++ b/src/vfs/fish/fishdef.h
@@ -0,0 +1,236 @@
+
+/**
+ * \file
+ * \brief Header: FISH script defaults
+ */
+
+#ifndef MC__FISH_DEF_H
+#define MC__FISH_DEF_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* default 'ls' script */
+#define FISH_LS_DEF_CONTENT "" \
+"#LIST /${FISH_FILENAME}\n" \
+"export LC_TIME=C\n" \
+"ls -Qlan \"/${FISH_FILENAME}\" 2>/dev/null | grep '^[^cbt]' | (\n" \
+"while read p l u g s m d y n; do\n" \
+" echo \"P$p $u.$g\"\n" \
+" echo \"S$s\"\n" \
+" echo \"d$m $d $y\"\n" \
+" echo \":$n\"\n" \
+" echo\n" \
+"done\n" \
+")\n" \
+"ls -Qlan \"/${FISH_FILENAME}\" 2>/dev/null | grep '^[cb]' | (\n" \
+"while read p l u g a i m d y n; do\n" \
+" echo \"P$p $u.$g\"\n" \
+" echo \"E$a$i\"\n" \
+" echo \"d$m $d $y\"\n" \
+" echo \":$n\"\n" \
+" echo\n" \
+"done\n" \
+")\n" \
+"echo \"### 200\"\n"
+
+/* default file exists script */
+#define FISH_EXISTS_DEF_CONTENT "" \
+"#ISEXISTS $FISH_FILENAME\n" \
+"ls -l \"/${FISH_FILENAME}\" >/dev/null 2>/dev/null\n" \
+"echo '### '$?\n"
+
+/* default 'mkdir' script */
+#define FISH_MKDIR_DEF_CONTENT "" \
+"#MKD $FISH_FILENAME\n" \
+"if mkdir \"/${FISH_FILENAME}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'unlink' script */
+#define FISH_UNLINK_DEF_CONTENT "" \
+"#DELE $FISH_FILENAME\n" \
+"if rm -f \"/${FISH_FILENAME}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+/* default 'chown' script */
+#define FISH_CHOWN_DEF_CONTENT "" \
+"#CHOWN $FISH_FILEOWNER:$FISH_FILEGROUP $FISH_FILENAME\n" \
+"if chown ${FISH_FILEOWNER}:${FISH_FILEGROUP} \"/${FISH_FILENAME}\"; then\n"\
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'chmod' script */
+#define FISH_CHMOD_DEF_CONTENT "" \
+"#CHMOD $FISH_FILEMODE $FISH_FILENAME\n" \
+"if chmod ${FISH_FILEMODE} \"/${FISH_FILENAME}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'utime' script */
+#define FISH_UTIME_DEF_CONTENT "" \
+"#UTIME \"$FISH_TOUCHATIME_W_NSEC\" \"$FISH_TOUCHMTIME_W_NSEC\" $FISH_FILENAME\n" \
+"if TZ=UTC touch -h -m -d \"$FISH_TOUCHMTIME_W_NSEC\" \"/${FISH_FILENAME}\" 2>/dev/null && \\\n" \
+" TZ=UTC touch -h -a -d \"$FISH_TOUCHATIME_W_NSEC\" \"/${FISH_FILENAME}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"elif TZ=UTC touch -h -m -t $FISH_TOUCHMTIME \"/${FISH_FILENAME}\" 2>/dev/null && \\\n" \
+" TZ=UTC touch -h -a -t $FISH_TOUCHATIME \"/${FISH_FILENAME}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"elif [ -n \"$FISH_HAVE_PERL\" ] && \\\n" \
+" perl -e 'utime '$FISH_FILEATIME','$FISH_FILEMTIME',@ARGV;' \"/${FISH_FILENAME}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+
+/* default 'rmdir' script */
+#define FISH_RMDIR_DEF_CONTENT "" \
+"#RMD $FISH_FILENAME\n" \
+"if rmdir \"/${FISH_FILENAME}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'ln -s' symlink script */
+#define FISH_LN_DEF_CONTENT "" \
+"#SYMLINK $FISH_FILEFROM $FISH_FILETO\n" \
+"if ln -s \"/${FISH_FILEFROM}\" \"/${FISH_FILETO}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'mv' script */
+#define FISH_MV_DEF_CONTENT "" \
+"#RENAME $FISH_FILEFROM $FISH_FILETO\n" \
+"if mv \"/${FISH_FILEFROM}\" \"/${FISH_FILETO}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'ln' hardlink script */
+#define FISH_HARDLINK_DEF_CONTENT "" \
+"#LINK $FISH_FILEFROM $FISH_FILETO\n" \
+"if ln \"/${FISH_FILEFROM}\" \"/${FISH_FILETO}\" 2>/dev/null; then\n" \
+" echo \"### 000\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'retr' script */
+#define FISH_GET_DEF_CONTENT "" \
+"export LC_TIME=C\n" \
+"#RETR $FISH_FILENAME\n" \
+"if dd if=\"/${FISH_FILENAME}\" of=/dev/null bs=1 count=1 2>/dev/null ; then\n" \
+" ls -ln \"/${FISH_FILENAME}\" 2>/dev/null | (\n" \
+" read p l u g s r\n" \
+" echo $s\n" \
+" )\n" \
+" echo \"### 100\"\n" \
+" cat \"/${FISH_FILENAME}\"\n" \
+" echo \"### 200\"\n" \
+"else\n" \
+" echo \"### 500\"\n" \
+"fi\n"
+
+/* default 'stor' script */
+#define FISH_SEND_DEF_CONTENT "" \
+"FILENAME=\"/${FISH_FILENAME}\"\n" \
+"FILESIZE=${FISH_FILESIZE}\n" \
+"#STOR $FILESIZE $FILENAME\n" \
+"echo \"### 001\"\n" \
+"{\n" \
+" while [ $FILESIZE -gt 0 ]; do\n" \
+" cnt=`expr \\( $FILESIZE + 255 \\) / 256`\n" \
+" n=`dd bs=256 count=$cnt | tee -a \"${FILENAME}\" | wc -c`\n" \
+" FILESIZE=`expr $FILESIZE - $n`\n" \
+" done\n" \
+"}; echo \"### 200\"\n"
+
+/* default 'appe' script */
+#define FISH_APPEND_DEF_CONTENT "" \
+"FILENAME=\"/${FISH_FILENAME}\"\n" \
+"FILESIZE=${FISH_FILESIZE}\n" \
+"#APPE $FILESIZE $FILENAME\n" \
+"echo \"### 001\"\n" \
+"res=`exec 3>&1\n" \
+"(\n" \
+" head -c $FILESIZE -q - || echo DD >&3\n" \
+") 2>/dev/null | (\n" \
+" cat > \"${FILENAME}\"\n" \
+" cat > /dev/null\n" \
+")`; [ \"$res\" = DD ] && {\n" \
+" > \"${FILENAME}\"\n" \
+" while [ $FILESIZE -gt 0 ]\n" \
+" do\n" \
+" cnt=`expr \\( $FILESIZE + 255 \\) / 256`\n" \
+" n=`dd bs=256 count=$cnt | tee -a \"${FILENAME}\" | wc -c`\n" \
+" FILESIZE=`expr $FILESIZE - $n`\n" \
+" done\n" \
+"}; echo \"### 200\"\n"
+
+/* default 'info' script */
+#define FISH_INFO_DEF_CONTENT "" \
+"export LC_TIME=C\n" \
+"#FISH_HAVE_HEAD 1\n" \
+"#FISH_HAVE_SED 2\n" \
+"#FISH_HAVE_AWK 4\n" \
+"#FISH_HAVE_PERL 8\n" \
+"#FISH_HAVE_LSQ 16\n" \
+"#FISH_HAVE_DATE_MDYT 32\n" \
+"#FISH_HAVE_TAIL 64\n" \
+"res=0\n" \
+"if `echo yes| head -c 1 > /dev/null 2>&1` ; then\n" \
+" res=`expr $res + 1`\n" \
+"fi\n" \
+"if `sed --version >/dev/null 2>&1` ; then\n" \
+" res=`expr $res + 2`\n" \
+"fi\n" \
+"if `awk --version > /dev/null 2>&1` ; then\n" \
+" res=`expr $res + 4`\n" \
+"fi\n" \
+"if `perl -v > /dev/null 2>&1` ; then\n" \
+" res=`expr $res + 8`\n" \
+"fi\n" \
+"if `ls -Q / >/dev/null 2>&1` ; then\n" \
+" res=`expr $res + 16`\n" \
+"fi\n" \
+"dat=`ls -lan / 2>/dev/null | head -n 3 | tail -n 1 | (\n" \
+" while read p l u g s rec; do\n" \
+" if [ -n \"$g\" ]; then\n" \
+" if [ -n \"$l\" ]; then\n" \
+" echo \"$rec\"\n" \
+" fi\n" \
+" fi\n" \
+" done\n" \
+") | cut -c1 2>/dev/null`\n" \
+"r=`echo \"0123456789\"| grep \"$dat\"`\n" \
+"if [ -z \"$r\" ]; then\n" \
+" res=`expr $res + 32`\n" \
+"fi\n" \
+"if `echo yes| tail -c +1 - > /dev/null 2>&1` ; then\n" \
+" res=`expr $res + 64`\n" \
+"fi\n" \
+"echo $res\n" \
+"echo \"### 200\"\n"
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+#endif
diff --git a/src/vfs/fish/helpers/Makefile.am b/src/vfs/fish/helpers/Makefile.am
new file mode 100644
index 0000000..e3ba15d
--- /dev/null
+++ b/src/vfs/fish/helpers/Makefile.am
@@ -0,0 +1,10 @@
+fishdir = $(libexecdir)/@PACKAGE@/fish
+
+# Files to install and distribute other than fish scripts
+FISH_MISC = README.fish
+
+# Install and distribute FISH helper scripts w/o shebang & executable bit as data
+fish_DATA = $(FISH_MISC) ls mkdir fexists unlink chown chmod rmdir ln mv hardlink get send append info utime
+fishconfdir = $(sysconfdir)/@PACKAGE@
+
+EXTRA_DIST = $(fish_DATA)
diff --git a/src/vfs/fish/helpers/Makefile.in b/src/vfs/fish/helpers/Makefile.in
new file mode 100644
index 0000000..c17efbb
--- /dev/null
+++ b/src/vfs/fish/helpers/Makefile.in
@@ -0,0 +1,642 @@
+# 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/vfs/fish/helpers
+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 =
+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 =
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(fishdir)"
+DATA = $(fish_DATA)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+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@
+fishdir = $(libexecdir)/@PACKAGE@/fish
+
+# Files to install and distribute other than fish scripts
+FISH_MISC = README.fish
+
+# Install and distribute FISH helper scripts w/o shebang & executable bit as data
+fish_DATA = $(FISH_MISC) ls mkdir fexists unlink chown chmod rmdir ln mv hardlink get send append info utime
+fishconfdir = $(sysconfdir)/@PACKAGE@
+EXTRA_DIST = $(fish_DATA)
+all: all-am
+
+.SUFFIXES:
+$(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/vfs/fish/helpers/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/fish/helpers/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):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-fishDATA: $(fish_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(fish_DATA)'; test -n "$(fishdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(fishdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(fishdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(fishdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(fishdir)" || exit $$?; \
+ done
+
+uninstall-fishDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(fish_DATA)'; test -n "$(fishdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(fishdir)'; $(am__uninstall_files_from_dir)
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+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 $(DATA)
+installdirs:
+ for dir in "$(DESTDIR)$(fishdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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 mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-fishDATA
+
+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 Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-fishDATA
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ cscopelist-am ctags-am distclean distclean-generic \
+ distclean-libtool 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-fishDATA \
+ 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-generic mostlyclean-libtool pdf pdf-am \
+ ps ps-am tags-am uninstall uninstall-am uninstall-fishDATA
+
+.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/vfs/fish/helpers/README.fish b/src/vfs/fish/helpers/README.fish
new file mode 100644
index 0000000..ac319c8
--- /dev/null
+++ b/src/vfs/fish/helpers/README.fish
@@ -0,0 +1,217 @@
+
+ FIles transferred over SHell protocol (V 0.0.3)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This protocol was designed for transferring files over a remote shell
+connection (rsh and compatibles). It can be as well used for transfers over
+rsh, and there may be other uses.
+
+Client sends requests of following form:
+
+#FISH_COMMAND
+equivalent shell commands,
+which may be multiline
+
+Only fish commands are defined here, shell equivalents are for your
+information only and will probably vary from implementation to
+implementation. Fish commands always have priority: server is
+expected to execute fish command if it understands it. If it does not,
+however, it can try the luck and execute shell command.
+
+Since version 4.7.3, the scripts that FISH sends to host machines after
+a command is transmitted are no longer hardwired in the Midnight
+Commander source code.
+
+First, mc looks for system-wide set of scripts, then it checks whether
+current user has host-specific overrides in his per-user mc
+configuration directory. User-defined overrides take priority over
+sytem-wide scripts if they exist. The order in which the directories are
+traversed is as follows:
+
+ /usr/libexec/mc/fish
+ ~/.local/share/mc/fish/<hostname>/
+
+Server's reply is multiline, but always ends with
+
+### 000<optional text>
+
+line. ### is prefix to mark this line, 000 is return code. Return
+codes are superset to those used in ftp.
+
+There are few new exit codes defined:
+
+000 don't know; if there were no previous lines, this marks COMPLETE
+success, if they were, it marks failure.
+
+001 don't know; if there were no previous lines, this marks
+PRELIMinary success, if they were, it marks failure
+
+ Connecting
+ ~~~~~~~~~~
+Client uses "echo FISH:;/bin/sh" as command executed on remote
+machine. This should make it possible for server to distinguish FISH
+connections from normal rsh/ssh.
+
+ Commands
+ ~~~~~~~~
+#FISH
+echo; start_fish_server; echo '### 200'
+
+This command is sent at the beginning. It marks that client wishes to
+talk via FISH protocol. #VER command must follow. If server
+understands FISH protocol, it has option to put FISH server somewhere
+on system path and name it start_fish_server.
+
+#VER 0.0.2 <feature1> <feature2> <...>
+echo '### 000'
+
+This command is the second one. It sends client version and extensions
+to the server. Server should reply with protocol version to be used,
+and list of extensions accepted.
+
+VER 0.0.0 <feature2>
+### 200
+
+#PWD
+pwd; echo '### 200'
+
+Server should reply with current directory (in form /abc/def/ghi)
+followed by line indicating success.
+
+#LIST /directory
+ls -lLa $1 | grep '^[^cbt]' | ( while read p x u g s m d y n; do echo "P$p $u.$g
+S$s
+d$m $d $y
+:$n
+"; done )
+ls -lLa $1 | grep '^[cb]' | ( while read p x u g a i m d y n; do echo "P$p $u.$g
+E$a$i
+dD$m $d $y
+:$n
+"; done )
+echo '### 200'
+
+This allows client to list directory or get status information about
+single file. Output is in following form (any line except :<filename>
+may be omitted):
+
+P<unix permissions> <owner>.<group>
+S<size>
+d<3-letters month name> <day> <year or HH:MM>
+D<year> <month> <day> <hour> <minute> <second>[.1234]
+E<major-of-device>,<minor>
+:<filename>
+L<filename symlink points to>
+<blank line to separate items>
+
+Unix permissions are of form X--------- where X is type of
+file. Currently, '-' means regular file, 'd' means directory, 'c', 'b'
+means character and block device, 'l' means symbolic link, 'p' means
+FIFO and 's' means socket.
+
+'d' has three fields: month (one of strings Jan Feb Mar Apr May Jun
+Jul Aug Sep Oct Nov Dec), day of month, and third is either single
+number indicating year, or HH:MM field (assume current year in such
+case). As you've probably noticed, this is pretty broken; it is for
+compatibility with ls listing.
+
+#RETR /some/name
+ls -l /some/name | ( read a b c d x e; echo $x ); echo '### 100'; cat /some/name; echo '### 200'
+
+Server sends line with filesize on it, followed by line with ### 100
+indicating partial success, then it sends binary data (exactly
+filesize bytes) and follows them with (with no preceding newline) ###
+200.
+
+Note that there's no way to abort running RETR command - except
+closing the connection.
+
+#STOR <size> /file/name
+> /file/name; echo '### 001'; ( dd bs=4096 count=<size/4096>; dd bs=<size%4096> count=1 ) 2>/dev/null | ( cat > %s; cat > /dev/null ); echo '### 200'
+
+This command is for storing /file/name, which is exactly size bytes
+big. You probably think I went crazy. Well, I did not: that strange
+cat > /dev/null has purpose to discard any extra data which was not
+written to disk (due to for example out of space condition).
+
+[Why? Imagine uploading file with "rm -rf /" line in it.]
+
+#CWD /somewhere
+cd /somewhere; echo '### 000'
+
+It is specified here, but I'm not sure how wise idea is to use this
+one: it breaks stateless-ness of the protocol.
+
+Following commands should be rather self-explanatory:
+
+#CHMOD 1234 file
+chmod 1234 file; echo '### 000'
+
+#DELE /some/path
+rm -f /some/path; echo '### 000'
+
+#MKD /some/path
+mkdir /some/path; echo '### 000'
+
+#RMD /some/path
+rmdir /some/path; echo '### 000'
+
+#RENAME /path/a /path/b
+mv /path/a /path/b; echo '### 000'
+
+#LINK /path/a /path/b
+ln /path/a /path/b; echo '### 000'
+
+#SYMLINK /path/a /path/b
+ln -s /path/a /path/b; echo '### 000'
+
+#CHOWN user /file/name
+chown user /file/name; echo '### 000'
+
+#CHGRP group /file/name
+chgrp group /file/name; echo '### 000'
+
+#INFO
+...collect info about host into $result ...
+echo $result
+echo '### 200'
+
+#READ <offset> <size> /path/and/filename
+cat /path/and/filename | ( dd bs=4096 count=<offset/4096> > /dev/null;
+dd bs=<offset%4096> count=1 > /dev/null;
+dd bs=4096 count=<offset/4096>;
+dd bs=<offset%4096> count=1; )
+
+Returns ### 200 on successful exit, ### 291 on successful exit when
+reading ended at eof, ### 292 on successful exit when reading did not
+end at eof.
+
+#WRITE <offset> <size> /path/and/filename
+
+Hmm, shall we define these ones if we know our client is not going to
+use them?
+
+you can use follow parameters:
+FISH_FILESIZE
+FISH_FILENAME
+FISH_FILEMODE
+FISH_FILEOWNER
+FISH_FILEGROUPE
+FISH_FILEFROM
+FISH_FILETO
+
+NB:
+'FISH_FILESIZE' used if we operate with single file name in 'unlink', 'rmdir', 'chmod', etc...
+'FISH_FILEFROM','FISH_FILETO' used if we operate with two files in 'ln', 'hardlink', 'mv' etc...
+'FISH_FILEOWNER', 'FISH_FILEGROUPE' is a new user/group in chown
+
+also flags:
+FISH_HAVE_HEAD
+FISH_HAVE_SED
+FISH_HAVE_AWK
+FISH_HAVE_PERL
+FISH_HAVE_LSQ
+FISH_HAVE_DATE_MDYT
+
+That's all, folks!
+ pavel@ucw.cz
diff --git a/src/vfs/fish/helpers/append b/src/vfs/fish/helpers/append
new file mode 100644
index 0000000..81ded44
--- /dev/null
+++ b/src/vfs/fish/helpers/append
@@ -0,0 +1,16 @@
+#APPE $FISH_FILESIZE $FISH_FILENAME
+FILENAME="/${FISH_FILENAME}"
+echo "### 001"
+{
+ bss=4096
+ bsl=4095
+ if [ $FISH_FILESIZE -lt $bss ]; then
+ bss=1;
+ bsl=0;
+ fi
+ while [ $FISH_FILESIZE -gt 0 ]; do
+ cnt=`expr \\( $FISH_FILESIZE + $bsl \\) / $bss`
+ n=`dd bs=$bss count=$cnt | tee -a "${FILENAME}" | wc -c`
+ FISH_FILESIZE=`expr $FISH_FILESIZE - $n`
+ done
+}; echo "### 200"
diff --git a/src/vfs/fish/helpers/chmod b/src/vfs/fish/helpers/chmod
new file mode 100644
index 0000000..a5a88b4
--- /dev/null
+++ b/src/vfs/fish/helpers/chmod
@@ -0,0 +1,6 @@
+#CHMOD $FISH_FILEMODE $FISH_FILENAME
+if chmod ${FISH_FILEMODE} "/${FISH_FILENAME}" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/chown b/src/vfs/fish/helpers/chown
new file mode 100644
index 0000000..469fdc1
--- /dev/null
+++ b/src/vfs/fish/helpers/chown
@@ -0,0 +1,6 @@
+#CHOWN $FISH_FILEOWNER:$FISH_FILEGROUP $FISH_FILENAME
+if chown ${FISH_FILEOWNER}:${FISH_FILEGROUP} "/${FISH_FILENAME}" ; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/fexists b/src/vfs/fish/helpers/fexists
new file mode 100644
index 0000000..cf03b15
--- /dev/null
+++ b/src/vfs/fish/helpers/fexists
@@ -0,0 +1,3 @@
+#ISEXISTS $FISH_FILENAME
+ls -l "/${FISH_FILENAME}" >/dev/null 2>/dev/null
+echo '### '$?
diff --git a/src/vfs/fish/helpers/get b/src/vfs/fish/helpers/get
new file mode 100644
index 0000000..762267a
--- /dev/null
+++ b/src/vfs/fish/helpers/get
@@ -0,0 +1,105 @@
+#RETR $FISH_FILENAME $FISH_START_OFFSET
+LC_TIME=C
+export LC_TIME
+fish_get_perl ()
+{
+FILENAME=$1
+OFFSET=$2
+perl -e '
+use strict;
+use POSIX;
+use Fcntl;
+my $filename = $ARGV[0];
+my $pos = $ARGV[1];
+my $content;
+my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = lstat("$filename");
+my $n;
+if (open IFILE,$filename) {
+ if ($size<$pos) {
+ printf("0\n");
+ } else {
+ $size-=$pos;
+ printf("$size\n");
+ }
+ printf("### 100\n");
+ seek (IFILE, $pos, 0);
+ while ($n = read(IFILE,$content,$blksize)!= 0) {
+ print $content;
+ }
+ close IFILE;
+ printf("### 200\n");
+} else {
+ printf("### 500\n");
+}
+exit 0
+' "${FILENAME}" $OFFSET
+}
+
+fish_get_tail ()
+{
+FILENAME=$1
+OFFSET=$2
+LC_TIME=C
+export LC_TIME
+if dd if="${FILENAME}" of=/dev/null bs=1 count=1 2>/dev/null ; then
+ file_size=`ls -ln "${FILENAME}" 2>/dev/null | (
+ read p l u g s r
+ echo $s
+ )`
+ if [ $OFFSET -gt 0 ]; then
+ file_size=`expr $file_size - $OFFSET`
+ OFFSET=`expr $OFFSET + 1`
+ fi
+ if [ $file_size -gt 0 ]; then
+ echo $file_size
+ else
+ echo 0
+ fi
+ echo "### 100"
+ if [ $OFFSET -gt 0 ]; then
+ tail -c +${OFFSET} "${FILENAME}"
+ else
+ cat "${FILENAME}"
+ fi
+ echo "### 200"
+else
+ echo "### 500"
+fi
+}
+
+fish_get_dd ()
+{
+FILENAME=$1
+OFFSET=$2
+LC_TIME=C
+export LC_TIME
+if dd if="${FILENAME}" of=/dev/null bs=1 count=1 2>/dev/null ; then
+ file_size=`ls -ln "${FILENAME}" 2>/dev/null | (
+ read p l u g s r
+ echo $s
+ )`
+ file_size=`expr $file_size - $OFFSET`
+ if [ $file_size -gt 0 ]; then
+ echo $file_size
+ else
+ echo 0
+ fi
+ echo "### 100"
+ if [ $OFFSET -gt 0 ]; then
+ dd skip=$OFFSET ibs=1 if="${FILENAME}" 2>/dev/null
+ else
+ cat "${FILENAME}"
+ fi
+ echo "### 200"
+else
+ echo "### 500"
+fi
+}
+
+if [ -n "${FISH_HAVE_PERL}" ]; then
+ fish_get_perl "/${FISH_FILENAME}" ${FISH_START_OFFSET}
+elif [ -n "${FISH_HAVE_TAIL}" ]; then
+ fish_get_tail "/${FISH_FILENAME}" ${FISH_START_OFFSET}
+else
+ fish_get_dd "/${FISH_FILENAME}" ${FISH_START_OFFSET}
+fi
diff --git a/src/vfs/fish/helpers/hardlink b/src/vfs/fish/helpers/hardlink
new file mode 100644
index 0000000..4f36b3f
--- /dev/null
+++ b/src/vfs/fish/helpers/hardlink
@@ -0,0 +1,8 @@
+#LINK $FISH_FILEFROM $FISH_FILETO
+FILEFROM="/${FISH_FILEFROM}"
+FILETO="/${FISH_FILETO}"
+if ln "${FILEFROM}" "${FILETO}" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/info b/src/vfs/fish/helpers/info
new file mode 100644
index 0000000..b85b0a7
--- /dev/null
+++ b/src/vfs/fish/helpers/info
@@ -0,0 +1,44 @@
+LC_TIME=C
+export LC_TIME
+#FISH_HAVE_HEAD 1
+#FISH_HAVE_SED 2
+#FISH_HAVE_AWK 4
+#FISH_HAVE_PERL 8
+#FISH_HAVE_LSQ 16
+#FISH_HAVE_DATE_MDYT 32
+#FISH_HAVE_TAIL 64
+res=0
+if `echo yes| head -c 1 > /dev/null 2>&1` ; then
+ res=`expr $res + 1`
+fi
+if `echo 1 | sed 's/1/2/' >/dev/null 2>&1` ; then
+ res=`expr $res + 2`
+fi
+if `echo 1| awk '{print}' > /dev/null 2>&1` ; then
+ res=`expr $res + 4`
+fi
+if `perl -v > /dev/null 2>&1` ; then
+ res=`expr $res + 8`
+fi
+if `ls -Q / >/dev/null 2>&1` ; then
+ res=`expr $res + 16`
+fi
+dat=`ls -lan / 2>/dev/null | head -n 3 | (
+ while read p l u g s rec; do
+ if [ -n "$g" ]; then
+ if [ -n "$l" ]; then
+ echo "$rec"
+ fi
+ fi
+ done
+)`
+dat=`echo $dat | cut -c1 2>/dev/null`
+r=`echo "0123456789"| grep "$dat"`
+if [ -z "$r" ]; then
+ res=`expr $res + 32`
+fi
+if `echo yes| tail -c +1 - > /dev/null 2>&1` ; then
+ res=`expr $res + 64`
+fi
+echo $res
+echo "### 200"
diff --git a/src/vfs/fish/helpers/ln b/src/vfs/fish/helpers/ln
new file mode 100644
index 0000000..a8445d8
--- /dev/null
+++ b/src/vfs/fish/helpers/ln
@@ -0,0 +1,8 @@
+#SYMLINK $FISH_FILEFROM $FISH_FILETO
+FILEFROM="${FISH_FILEFROM}"
+FILETO="/${FISH_FILETO}"
+if ln -s "${FILEFROM}" "${FILETO}" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/ls b/src/vfs/fish/helpers/ls
new file mode 100644
index 0000000..7165b51
--- /dev/null
+++ b/src/vfs/fish/helpers/ls
@@ -0,0 +1,170 @@
+#LIST /${FISH_DIR}
+LC_TIME=C
+export LC_TIME
+perl_res="1"
+fish_list_lsq ()
+{
+FISH_DIR="$1"
+ls -Qlan "${FISH_DIR}" 2>/dev/null | grep '^[^cbt]' | (
+while read p l u g s m d y n; do
+ echo "P$p $u.$g"
+ echo "S$s"
+ echo "d$m $d $y"
+ echo ":$n"
+ echo
+done
+)
+
+ls -Qlan "${FISH_DIR}" 2>/dev/null | grep '^[cb]' | (
+while read p l u g a i m d y n; do
+ echo "P$p $u.$g"
+ echo "E$a$i"
+ echo "d$m $d $y"
+ echo ":$n"
+ echo
+done
+)
+echo '### 200'
+}
+
+fish_list_sed ()
+{
+FISH_DIR="$1"
+ls -lan "${FISH_DIR}" 2>/dev/null | grep '^[^cbt]' | (
+while read p l u g s rec; do
+ if [ -n "$g" ]; then
+ if [ -n "$FISH_HAVE_DATE_MDYT" ]; then
+ filename=`echo "$rec"| sed 's/[^[:space:]]\+ \+[^[:space:]]\+ \+[^[:space:]]\+ //'`
+ filedate=`echo "$rec"| sed 's/\([^[:space:]]\+ \+[^[:space:]]\+ \+[^[:space:]]\+\) .*/\1/'`
+ else
+ filename=`echo "$rec"| sed 's/[^[:space:]]\+ \+[^[:space:]]\+ //'`
+ filedate=`echo "$rec"| sed 's/\([^[:space:]]\+ \+[^[:space:]]\+\) .*/\1/'`
+ fi
+ pfile=\"`echo "$filename" | sed -e 's#^\(.*\) -> \(.*\)#\1" -> "\2#'`\"
+ echo "P$p $u.$g"
+ echo "S$s"
+ if [ -n "$FISH_HAVE_DATE_MDYT" ]; then
+ echo "d$filedate"
+ else
+ echo "D$filedate"
+ fi
+ echo ":$pfile"
+ echo
+ fi
+done
+)
+ls -lan "${FISH_DIR}" 2>/dev/null | grep '^[cb]' | (
+while read p l u g a i rec; do
+ if [ -n "$g" ]; then
+ if [ -n "$FISH_HAVE_DATE_MDYT" ]; then
+ filename=`echo "$rec"| sed 's/[^[:space:]]\+ \+[^[:space:]]\+ \+[^[:space:]]\+ //'`
+ filedate=`echo "$rec"| sed 's/\([^[:space:]]\+ \+[^[:space:]]\+ \+[^[:space:]]\+\) .*/\1/'`
+ else
+ filename=`echo "$rec"| sed 's/[^[:space:]]\+ \+[^[:space:]]\+ //'`
+ filedate=`echo "$rec"| sed 's/\([^[:space:]]\+ \+[^[:space:]]\+\) .*/\1/'`
+ fi
+ pfile=\"`echo "$filename" | sed -e 's#^\(.*\) -> \(.*\)#\1" -> "\2#'`\"
+ echo "P$p $u.$g"
+ echo "E$a$i"
+ if [ -n "$FISH_HAVE_DATE_MDYT" ]; then
+ echo "d$filedate"
+ else
+ echo "D$filedate"
+ fi
+ echo ":$pfile"
+ echo
+ fi
+done
+)
+echo '### 200'
+}
+
+fish_list_poor_ls ()
+{
+FISH_DIR="$1"
+ls -lan "${FISH_DIR}" 2>/dev/null | grep '^[^cbt]' | (
+while read p l u g s m d y n n2 n3; do
+ if [ -n "$g" ]; then
+ if [ "$m" = "0" ]; then
+ s=$d; m=$y; d=$n; y=$n2; n=$n3
+ else
+ n=$n" "$n2" "$n3
+ fi
+ echo "P$p $u $g"
+ echo "S$s"
+ echo "d$m $d $y"
+ echo ":"$n
+ echo
+ fi
+done
+)
+ls -lan "${FISH_DIR}" 2>/dev/null | grep '^[cb]' | (
+while read p l u g a i m d y n n2 n3; do
+ if [ -n "$g" ]; then
+ if [ "$a" = "0" ]; then
+ a=$m; i=$d; m=$y; d=$n; y=$n2; n=$n3
+ else
+ n=$n" "$n2" "$n3
+ fi
+ echo "P$p $u $g"
+ echo "E$a$i"
+ echo "d$m $d $y"
+ echo ":"$n
+ echo
+ fi
+done
+)
+echo '### 200'
+}
+
+fish_list_perl ()
+{
+FISH_DIR=$1
+perl -e '
+use strict;
+use POSIX;
+use Fcntl;
+use POSIX ":fcntl_h"; #S_ISLNK was here until 5.6
+import Fcntl ":mode" unless defined &S_ISLNK; #and is now here
+my $dirname = $ARGV[0];
+if (opendir (DIR, $dirname)) {
+while((my $filename = readdir (DIR))){
+ my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = lstat("$dirname/$filename");
+ my $mloctime= strftime("%m-%d-%Y %H:%M", localtime $mtime);
+ my $strutils_shell_escape_regex = s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'\''"\ \\])/\\$1/g;
+ my $e_filename = $filename;
+ $e_filename =~ $strutils_shell_escape_regex;
+ if (S_ISLNK ($mode)) {
+ my $linkname = readlink ("$dirname/$filename");
+ $linkname =~ $strutils_shell_escape_regex;
+ printf("R%o %o $uid.$gid\nS$size\nd$mloctime\n:\"%s\" -> \"%s\"\n\n", S_IMODE($mode), S_IFMT($mode), $e_filename, $linkname);
+ } elsif (S_ISCHR ($mode) || S_ISBLK ($mode)) {
+ my $minor = $rdev % 256;
+ my $major = int( $rdev / 256 );
+ printf("R%o %o $uid.$gid\nE$major,$minor\nd$mloctime\n:\"%s\"\n\n", S_IMODE($mode), S_IFMT($mode), $e_filename);
+ } else {
+ printf("R%o %o $uid.$gid\nS$size\nd$mloctime\n:\"%s\"\n\n", S_IMODE($mode), S_IFMT($mode), $e_filename);
+ }
+}
+ printf("### 200\n");
+ closedir(DIR);
+} else {
+ printf("### 500\n");
+}
+exit 0
+' "/${FISH_DIR}"
+perl_res=$?
+}
+
+if [ -n "${FISH_HAVE_PERL}" ]; then
+ fish_list_perl "/${FISH_FILENAME}"
+fi
+if [ "${perl_res}" != "0" ]; then
+ if [ -n "${FISH_HAVE_LSQ}" ]; then
+ fish_list_lsq "/${FISH_FILENAME}"
+ elif [ -n "${FISH_HAVE_SED}" ]; then
+ fish_list_sed "/${FISH_FILENAME}"
+ else
+ fish_list_poor_ls "/${FISH_FILENAME}"
+ fi
+fi
diff --git a/src/vfs/fish/helpers/mkdir b/src/vfs/fish/helpers/mkdir
new file mode 100644
index 0000000..b32e995
--- /dev/null
+++ b/src/vfs/fish/helpers/mkdir
@@ -0,0 +1,6 @@
+#MKD $FISH_FILENAME
+if mkdir "/$FISH_FILENAME" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/mv b/src/vfs/fish/helpers/mv
new file mode 100644
index 0000000..c8cf70c
--- /dev/null
+++ b/src/vfs/fish/helpers/mv
@@ -0,0 +1,6 @@
+#RENAME $FISH_FILEFROM $FISH_FILETO
+if mv "/${FISH_FILEFROM}" "/${FISH_FILETO}" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/rmdir b/src/vfs/fish/helpers/rmdir
new file mode 100644
index 0000000..0f99bf6
--- /dev/null
+++ b/src/vfs/fish/helpers/rmdir
@@ -0,0 +1,6 @@
+#RMD $FISH_FILENAME
+if rmdir "/${FISH_FILENAME}" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/send b/src/vfs/fish/helpers/send
new file mode 100644
index 0000000..80dd22b
--- /dev/null
+++ b/src/vfs/fish/helpers/send
@@ -0,0 +1,17 @@
+#STOR $FISH_FILESIZE $FISH_FILENAME
+FILENAME="/${FISH_FILENAME}"
+echo "### 001"
+{
+ > "${FILENAME}"
+ bss=4096
+ bsl=4095
+ if [ $FISH_FILESIZE -lt $bss ]; then
+ bss=1;
+ bsl=0;
+ fi
+ while [ $FISH_FILESIZE -gt 0 ]; do
+ cnt=`expr \\( $FISH_FILESIZE + $bsl \\) / $bss`
+ n=`dd bs=$bss count=$cnt | tee -a "${FILENAME}" | wc -c`
+ FISH_FILESIZE=`expr $FISH_FILESIZE - $n`
+ done
+}; echo "### 200"
diff --git a/src/vfs/fish/helpers/unlink b/src/vfs/fish/helpers/unlink
new file mode 100644
index 0000000..79b9ad0
--- /dev/null
+++ b/src/vfs/fish/helpers/unlink
@@ -0,0 +1,6 @@
+#DELE $FISH_FILENAME
+if rm -f "/${FISH_FILENAME}" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/fish/helpers/utime b/src/vfs/fish/helpers/utime
new file mode 100644
index 0000000..94395b4
--- /dev/null
+++ b/src/vfs/fish/helpers/utime
@@ -0,0 +1,13 @@
+#UTIME "$FISH_TOUCHATIME_W_NSEC" "$FISH_TOUCHMTIME_W_NSEC" "$FISH_FILENAME"
+if TZ=UTC touch -h -m -d "$FISH_TOUCHMTIME_W_NSEC" "/${FISH_FILENAME}" 2>/dev/null && \
+ TZ=UTC touch -h -a -d "$FISH_TOUCHATIME_W_NSEC" "/${FISH_FILENAME}" 2>/dev/null; then
+ echo "### 000"
+elif TZ=UTC touch -h -m -t $FISH_TOUCHMTIME "/${FISH_FILENAME}" 2>/dev/null && \
+ TZ=UTC touch -h -a -t $FISH_TOUCHATIME "/${FISH_FILENAME}" 2>/dev/null; then
+ echo "### 000"
+elif [ -n "$FISH_HAVE_PERL" ] &&
+ perl -e 'utime '$FISH_FILEATIME','$FISH_FILEMTIME',@ARGV;' "/${FISH_FILENAME}" 2>/dev/null; then
+ echo "### 000"
+else
+ echo "### 500"
+fi
diff --git a/src/vfs/ftpfs/Makefile.am b/src/vfs/ftpfs/Makefile.am
new file mode 100644
index 0000000..b581563
--- /dev/null
+++ b/src/vfs/ftpfs/Makefile.am
@@ -0,0 +1,8 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-ftpfs.la
+
+libvfs_ftpfs_la_SOURCES = \
+ ftpfs.c ftpfs.h \
+ ftpfs_parse_ls.c \ No newline at end of file
diff --git a/src/vfs/ftpfs/Makefile.in b/src/vfs/ftpfs/Makefile.in
new file mode 100644
index 0000000..e6e561f
--- /dev/null
+++ b/src/vfs/ftpfs/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/vfs/ftpfs
+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)
+libvfs_ftpfs_la_LIBADD =
+am_libvfs_ftpfs_la_OBJECTS = ftpfs.lo ftpfs_parse_ls.lo
+libvfs_ftpfs_la_OBJECTS = $(am_libvfs_ftpfs_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)/ftpfs.Plo \
+ ./$(DEPDIR)/ftpfs_parse_ls.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 = $(libvfs_ftpfs_la_SOURCES)
+DIST_SOURCES = $(libvfs_ftpfs_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@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+noinst_LTLIBRARIES = libvfs-ftpfs.la
+libvfs_ftpfs_la_SOURCES = \
+ ftpfs.c ftpfs.h \
+ ftpfs_parse_ls.c
+
+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/vfs/ftpfs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/ftpfs/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}; \
+ }
+
+libvfs-ftpfs.la: $(libvfs_ftpfs_la_OBJECTS) $(libvfs_ftpfs_la_DEPENDENCIES) $(EXTRA_libvfs_ftpfs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_ftpfs_la_OBJECTS) $(libvfs_ftpfs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ftpfs.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ftpfs_parse_ls.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)/ftpfs.Plo
+ -rm -f ./$(DEPDIR)/ftpfs_parse_ls.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)/ftpfs.Plo
+ -rm -f ./$(DEPDIR)/ftpfs_parse_ls.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/vfs/ftpfs/ftpfs.c b/src/vfs/ftpfs/ftpfs.c
new file mode 100644
index 0000000..549ba32
--- /dev/null
+++ b/src/vfs/ftpfs/ftpfs.c
@@ -0,0 +1,2784 @@
+/*
+ Virtual File System: FTP file system.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ching Hui, 1995
+ Jakub Jelinek, 1995
+ Miguel de Icaza, 1995, 1996, 1997
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Yury V. Zaytsev, 2010
+ Slava Zanko <slavazanko@gmail.com>, 2010, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2010-2022
+
+ 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
+ * \brief Source: Virtual File System: FTP file system
+ * \author Ching Hui
+ * \author Jakub Jelinek
+ * \author Miguel de Icaza
+ * \author Norbert Warmuth
+ * \author Pavel Machek
+ * \date 1995, 1997, 1998
+ *
+ * \todo
+- make it more robust - all the connects etc. should handle EADDRINUSE and
+ ERETRY (have I spelled these names correctly?)
+- make the user able to flush a connection - all the caches will get empty
+ etc., (tarfs as well), we should give there a user selectable timeout
+ and assign a key sequence.
+- use hash table instead of linklist to cache ftpfs directory.
+
+What to do with this?
+
+
+ * NOTE: Usage of tildes is deprecated, consider:
+ * \verbatim
+ cd ftp//:pavel@hobit
+ cd ~
+ \endverbatim
+ * And now: what do I want to do? Do I want to go to /home/pavel or to
+ * ftp://hobit/home/pavel? I think first has better sense...
+ *
+ \verbatim
+ {
+ int f = !strcmp( remote_path, "/~" );
+ if (f || !strncmp( remote_path, "/~/", 3 )) {
+ char *s;
+ s = mc_build_filename ( qhome (*bucket), remote_path +3-f, (char *) NULL );
+ g_free (remote_path);
+ remote_path = s;
+ }
+ }
+ \endverbatim
+ */
+
+/* \todo Fix: Namespace pollution: horrible */
+
+#include <config.h>
+#include <stdio.h> /* sscanf() */
+#include <stdlib.h> /* atoi() */
+#include <sys/types.h> /* POSIX-required by sys/socket.h and netdb.h */
+#include <netdb.h> /* struct hostent */
+#include <sys/socket.h> /* AF_INET */
+#include <netinet/in.h> /* struct in_addr */
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#include <arpa/ftp.h>
+#include <arpa/telnet.h>
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+#include "lib/file-entry.h"
+#include "lib/util.h"
+#include "lib/strutil.h" /* str_move() */
+#include "lib/mcconfig.h"
+
+#include "lib/tty/tty.h" /* enable/disable interrupt key */
+#include "lib/widget.h" /* message() */
+
+#include "src/history.h"
+#include "src/setup.h" /* for load_anon_passwd */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/netutil.h"
+#include "lib/vfs/xdirentry.h"
+#include "lib/vfs/gc.h" /* vfs_stamp_create */
+
+#include "ftpfs.h"
+
+/*** global variables ****************************************************************************/
+
+/* Delay to retry a connection */
+int ftpfs_retry_seconds = 30;
+
+/* Method to use to connect to ftp sites */
+gboolean ftpfs_use_passive_connections = TRUE;
+gboolean ftpfs_use_passive_connections_over_proxy = FALSE;
+
+/* Method used to get directory listings:
+ * 1: try 'LIST -la <path>', if it fails
+ * fall back to CWD <path>; LIST
+ * 0: always use CWD <path>; LIST
+ */
+gboolean ftpfs_use_unix_list_options = TRUE;
+
+/* First "CWD <path>", then "LIST -la ." */
+gboolean ftpfs_first_cd_then_ls = TRUE;
+
+/* Use the ~/.netrc */
+gboolean ftpfs_use_netrc = TRUE;
+
+/* Anonymous setup */
+char *ftpfs_anonymous_passwd = NULL;
+int ftpfs_directory_timeout = 900;
+
+/* Proxy host */
+char *ftpfs_proxy_host = NULL;
+
+/* whether we have to use proxy by default? */
+gboolean ftpfs_always_use_proxy = FALSE;
+
+gboolean ftpfs_ignore_chattr_errors = TRUE;
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 64
+#endif
+
+#define FTP_SUPER(super) ((ftp_super_t *) (super))
+#define FTP_FILE_HANDLER(fh) ((ftp_file_handler_t *) (fh))
+#define FH_SOCK FTP_FILE_HANDLER(fh)->sock
+
+#ifndef INADDR_NONE
+#define INADDR_NONE 0xffffffff
+#endif
+
+#define RFC_AUTODETECT 0
+#define RFC_DARING 1
+#define RFC_STRICT 2
+
+/* ftpfs_command wait_flag: */
+#define NONE 0x00
+#define WAIT_REPLY 0x01
+#define WANT_STRING 0x02
+
+#define FTP_COMMAND_PORT 21
+
+/* some defines only used by ftpfs_changetype */
+/* These two are valid values for the second parameter */
+#define TYPE_ASCII 0
+#define TYPE_BINARY 1
+
+/* This one is only used to initialize bucket->isbinary, don't use it as
+ second parameter to ftpfs_changetype. */
+#define TYPE_UNKNOWN -1
+
+#define ABORT_TIMEOUT (5 * G_USEC_PER_SEC)
+/*** file scope type declarations ****************************************************************/
+
+#ifndef HAVE_SOCKLEN_T
+typedef int socklen_t;
+#endif
+
+/* This should match the keywords[] array below */
+typedef enum
+{
+ NETRC_NONE = 0,
+ NETRC_DEFAULT,
+ NETRC_MACHINE,
+ NETRC_LOGIN,
+ NETRC_PASSWORD,
+ NETRC_PASSWD,
+ NETRC_ACCOUNT,
+ NETRC_MACDEF,
+ NETRC_UNKNOWN
+} keyword_t;
+
+typedef struct
+{
+ struct vfs_s_super base; /* base class */
+
+ int sock;
+
+ char *proxy; /* proxy server, NULL if no proxy */
+ gboolean failed_on_login; /* used to pass the failure reason to upper levels */
+ gboolean use_passive_connection;
+ gboolean remote_is_amiga; /* No leading slash allowed for AmiTCP (Amiga) */
+ int isbinary;
+ gboolean cwd_deferred; /* current_directory was changed but CWD command hasn't
+ been sent yet */
+ int strict; /* ftp server doesn't understand
+ * "LIST -la <path>"; use "CWD <path>"/
+ * "LIST" instead
+ */
+ gboolean ctl_connection_busy;
+ char *current_dir;
+} ftp_super_t;
+
+typedef struct
+{
+ vfs_file_handler_t base; /* base class */
+
+ int sock;
+ gboolean append;
+} ftp_file_handler_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static char *ftpfs_get_current_directory (struct vfs_class *me, struct vfs_s_super *super);
+static int ftpfs_chdir_internal (struct vfs_class *me, struct vfs_s_super *super,
+ const char *remote_path);
+static int ftpfs_open_socket (struct vfs_class *me, struct vfs_s_super *super);
+static gboolean ftpfs_login_server (struct vfs_class *me, struct vfs_s_super *super,
+ const char *netrcpass);
+static gboolean ftpfs_netrc_lookup (const char *host, char **login, char **pass);
+
+/*** file scope variables ************************************************************************/
+
+static int code;
+
+static char reply_str[80];
+
+static struct vfs_s_subclass ftpfs_subclass;
+static struct vfs_class *vfs_ftpfs_ops = VFS_CLASS (&ftpfs_subclass);
+
+static GSList *no_proxy = NULL;
+
+static char buffer[BUF_MEDIUM];
+static char *netrc = NULL;
+static const char *netrcp;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_set_blksize (struct stat *s)
+{
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ /* redefine block size */
+ s->st_blksize = 64 * 1024; /* FIXME */
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct stat *
+ftpfs_default_stat (struct vfs_class *me)
+{
+ struct stat *s;
+
+ s = vfs_s_default_stat (me, S_IFDIR | 0755);
+ ftpfs_set_blksize (s);
+ vfs_adjust_stat (s);
+
+ return s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Translate a Unix path, i.e. MC's internal path representation (e.g.
+ /somedir/somefile) to a path valid for the remote server. Every path
+ transferred to the remote server has to be mangled by this function
+ right prior to sending it.
+ Currently only Amiga ftp servers are handled in a special manner.
+
+ When the remote server is an amiga:
+ a) strip leading slash if necessary
+ b) replace first occurrence of ":/" with ":"
+ c) strip trailing "/."
+ */
+static char *
+ftpfs_translate_path (struct vfs_class *me, struct vfs_s_super *super, const char *remote_path)
+{
+ char *ret, *p;
+
+ if (!FTP_SUPER (super)->remote_is_amiga)
+ return g_strdup (remote_path);
+
+ if (me->logfile != NULL)
+ {
+ fprintf (me->logfile, "MC -- ftpfs_translate_path: %s\n", remote_path);
+ fflush (me->logfile);
+ }
+
+ /* strip leading slash(es) */
+ while (IS_PATH_SEP (*remote_path))
+ remote_path++;
+
+ /* Don't change "/" into "", e.g. "CWD " would be invalid. */
+ if (*remote_path == '\0')
+ return g_strdup (".");
+
+ ret = g_strdup (remote_path);
+
+ /* replace first occurrence of ":/" with ":" */
+ p = strchr (ret, ':');
+ if (p != NULL && IS_PATH_SEP (p[1]))
+ str_move (p + 1, p + 2);
+
+ /* strip trailing "/." */
+ p = strrchr (ret, PATH_SEP);
+ if ((p != NULL) && (*(p + 1) == '.') && (*(p + 2) == '\0'))
+ *p = '\0';
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Extract the hostname and username from the path */
+/*
+ * path is in the form: [user@]hostname:port/remote-dir, e.g.:
+ * ftp://sunsite.unc.edu/pub/linux
+ * ftp://miguel@sphinx.nuclecu.unam.mx/c/nc
+ * ftp://tsx-11.mit.edu:8192/
+ * ftp://joe@foo.edu:11321/private
+ * If the user is empty, e.g. ftp://@roxanne/private, then your login name
+ * is supplied.
+ */
+
+static vfs_path_element_t *
+ftpfs_correct_url_parameters (const vfs_path_element_t * velement)
+{
+ vfs_path_element_t *path_element = vfs_path_element_clone (velement);
+
+ if (path_element->port == 0)
+ path_element->port = FTP_COMMAND_PORT;
+
+ if (path_element->user == NULL)
+ {
+ /* Look up user and password in netrc */
+ if (ftpfs_use_netrc)
+ ftpfs_netrc_lookup (path_element->host, &path_element->user, &path_element->password);
+ }
+ if (path_element->user == NULL)
+ path_element->user = g_strdup ("anonymous");
+
+ /* Look up password in netrc for known user */
+ if (ftpfs_use_netrc && path_element->password == NULL)
+ {
+ char *new_user = NULL;
+ char *new_passwd = NULL;
+
+ ftpfs_netrc_lookup (path_element->host, &new_user, &new_passwd);
+
+ /* If user is different, remove password */
+ if (new_user != NULL && strcmp (path_element->user, new_user) != 0)
+ MC_PTR_FREE (path_element->password);
+
+ g_free (new_user);
+ g_free (new_passwd);
+ }
+
+ return path_element;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Returns a reply code, check /usr/include/arpa/ftp.h for possible values */
+
+static int
+ftpfs_get_reply (struct vfs_class *me, int sock, char *string_buf, int string_len)
+{
+ while (TRUE)
+ {
+ char answer[BUF_1K];
+
+ if (vfs_s_get_line (me, sock, answer, sizeof (answer), '\n') == 0)
+ {
+ if (string_buf != NULL)
+ *string_buf = '\0';
+ code = 421;
+ return 4;
+ }
+
+ /* cppcheck-suppress invalidscanf */
+ switch (sscanf (answer, "%d", &code))
+ {
+ case 0:
+ if (string_buf != NULL)
+ g_strlcpy (string_buf, answer, string_len);
+ code = 500;
+ return 5;
+ case 1:
+ if (answer[3] == '-')
+ {
+ while (TRUE)
+ {
+ int i;
+
+ if (vfs_s_get_line (me, sock, answer, sizeof (answer), '\n') == 0)
+ {
+ if (string_buf != NULL)
+ *string_buf = '\0';
+ code = 421;
+ return 4;
+ }
+ /* cppcheck-suppress invalidscanf */
+ if ((sscanf (answer, "%d", &i) > 0) && (code == i) && (answer[3] == ' '))
+ break;
+ }
+ }
+ if (string_buf != NULL)
+ g_strlcpy (string_buf, answer, string_len);
+ return code / 100;
+ default:
+ break;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+ftpfs_reconnect (struct vfs_class *me, struct vfs_s_super *super)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ int sock;
+
+ sock = ftpfs_open_socket (me, super);
+ if (sock != -1)
+ {
+ char *cwdir = ftp_super->current_dir;
+
+ close (ftp_super->sock);
+ ftp_super->sock = sock;
+ ftp_super->current_dir = NULL;
+
+ if (ftpfs_login_server (me, super, super->path_element->password))
+ {
+ if (cwdir == NULL)
+ return TRUE;
+
+ sock = ftpfs_chdir_internal (me, super, cwdir);
+ g_free (cwdir);
+ return (sock == COMPLETE);
+ }
+
+ ftp_super->current_dir = cwdir;
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+G_GNUC_PRINTF (4, 5)
+ftpfs_command (struct vfs_class *me, struct vfs_s_super *super, int wait_reply, const char *fmt,
+ ...)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ va_list ap;
+ GString *cmdstr;
+ int status;
+ static gboolean retry = FALSE;
+ static int level = 0; /* ftpfs_login_server() use ftpfs_command() */
+
+ cmdstr = g_string_sized_new (32);
+ va_start (ap, fmt);
+ g_string_vprintf (cmdstr, fmt, ap);
+ va_end (ap);
+ g_string_append (cmdstr, "\r\n");
+
+ if (me->logfile != NULL)
+ {
+ if (strncmp (cmdstr->str, "PASS ", 5) == 0)
+ fputs ("PASS <Password not logged>\r\n", me->logfile);
+ else
+ {
+ size_t ret;
+
+ ret = fwrite (cmdstr->str, cmdstr->len, 1, me->logfile);
+ (void) ret;
+ }
+
+ fflush (me->logfile);
+ }
+
+ got_sigpipe = 0;
+ tty_enable_interrupt_key ();
+ status = write (ftp_super->sock, cmdstr->str, cmdstr->len);
+
+ if (status < 0)
+ {
+ code = 421;
+
+ if (errno == EPIPE)
+ { /* Remote server has closed connection */
+ if (level == 0)
+ {
+ level = 1;
+ status = ftpfs_reconnect (me, super) ? 1 : 0;
+ level = 0;
+ if (status != 0 && (write (ftp_super->sock, cmdstr->str, cmdstr->len) > 0))
+ goto ok;
+
+ }
+ got_sigpipe = 1;
+ }
+ g_string_free (cmdstr, TRUE);
+ tty_disable_interrupt_key ();
+ return TRANSIENT;
+ }
+
+ retry = FALSE;
+
+ ok:
+ tty_disable_interrupt_key ();
+
+ if (wait_reply != NONE)
+ {
+ status = ftpfs_get_reply (me, ftp_super->sock,
+ (wait_reply & WANT_STRING) != 0 ? reply_str : NULL,
+ sizeof (reply_str) - 1);
+ if ((wait_reply & WANT_STRING) != 0 && !retry && level == 0 && code == 421)
+ {
+ retry = TRUE;
+ level = 1;
+ status = ftpfs_reconnect (me, super) ? 1 : 0;
+ level = 0;
+ if (status != 0 && (write (ftp_super->sock, cmdstr->str, cmdstr->len) > 0))
+ goto ok;
+ }
+ retry = FALSE;
+ g_string_free (cmdstr, TRUE);
+ return status;
+ }
+
+ g_string_free (cmdstr, TRUE);
+ return COMPLETE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_super *
+ftpfs_new_archive (struct vfs_class *me)
+{
+ ftp_super_t *arch;
+
+ arch = g_new0 (ftp_super_t, 1);
+ arch->base.me = me;
+ arch->base.name = g_strdup (PATH_SEP_STR);
+ arch->sock = -1;
+ arch->use_passive_connection = ftpfs_use_passive_connections;
+ arch->strict = ftpfs_use_unix_list_options ? RFC_AUTODETECT : RFC_STRICT;
+ arch->isbinary = TYPE_UNKNOWN;
+
+ return VFS_SUPER (arch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_free_archive (struct vfs_class *me, struct vfs_s_super *super)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+
+ if (ftp_super->sock != -1)
+ {
+ vfs_print_message (_("ftpfs: Disconnecting from %s"), super->path_element->host);
+ ftpfs_command (me, super, NONE, "%s", "QUIT");
+ close (ftp_super->sock);
+ }
+ g_free (ftp_super->current_dir);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_changetype (struct vfs_class *me, struct vfs_s_super *super, int binary)
+{
+ if (binary != FTP_SUPER (super)->isbinary)
+ {
+ if (ftpfs_command (me, super, WAIT_REPLY, "TYPE %c", binary ? 'I' : 'A') != COMPLETE)
+ ERRNOR (EIO, -1);
+ FTP_SUPER (super)->isbinary = binary;
+ }
+ return binary;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* This routine logs the user in */
+
+static int
+ftpfs_login_server (struct vfs_class *me, struct vfs_s_super *super, const char *netrcpass)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ char *pass;
+ char *op;
+ char *name; /* login user name */
+ gboolean anon = FALSE;
+ char reply_string[BUF_MEDIUM];
+
+ ftp_super->isbinary = TYPE_UNKNOWN;
+
+ if (super->path_element->password != NULL) /* explicit password */
+ op = g_strdup (super->path_element->password);
+ else if (netrcpass != NULL) /* password from netrc */
+ op = g_strdup (netrcpass);
+ else if (strcmp (super->path_element->user, "anonymous") == 0
+ || strcmp (super->path_element->user, "ftp") == 0)
+ {
+ if (ftpfs_anonymous_passwd == NULL) /* default anonymous password */
+ ftpfs_init_passwd ();
+ op = g_strdup (ftpfs_anonymous_passwd);
+ anon = TRUE;
+ }
+ else
+ { /* ask user */
+ char *p;
+
+ p = g_strdup_printf (_("FTP: Password required for %s"), super->path_element->user);
+ op = vfs_get_password (p);
+ g_free (p);
+ if (op == NULL)
+ ERRNOR (EPERM, 0);
+ super->path_element->password = g_strdup (op);
+ }
+
+ if (!anon || me->logfile != NULL)
+ pass = op;
+ else
+ {
+ pass = g_strconcat ("-", op, (char *) NULL);
+ wipe_password (op);
+ }
+
+ /* Proxy server accepts: username@host-we-want-to-connect */
+ if (ftp_super->proxy != NULL)
+ name =
+ g_strconcat (super->path_element->user, "@",
+ super->path_element->host[0] ==
+ '!' ? super->path_element->host + 1 : super->path_element->host,
+ (char *) NULL);
+ else
+ name = g_strdup (super->path_element->user);
+
+ if (ftpfs_get_reply (me, ftp_super->sock, reply_string, sizeof (reply_string) - 1) == COMPLETE)
+ {
+ char *reply_up;
+
+ reply_up = g_ascii_strup (reply_string, -1);
+ ftp_super->remote_is_amiga = strstr (reply_up, "AMIGA") != NULL;
+ if (strstr (reply_up, " SPFTP/1.0.0000 SERVER ") != NULL) /* handles `LIST -la` in a weird way */
+ ftp_super->strict = RFC_STRICT;
+ g_free (reply_up);
+
+ if (me->logfile != NULL)
+ {
+ fprintf (me->logfile, "MC -- remote_is_amiga = %s\n",
+ ftp_super->remote_is_amiga ? "yes" : "no");
+ fflush (me->logfile);
+ }
+
+ vfs_print_message ("%s", _("ftpfs: sending login name"));
+
+ switch (ftpfs_command (me, super, WAIT_REPLY, "USER %s", name))
+ {
+ case CONTINUE:
+ vfs_print_message ("%s", _("ftpfs: sending user password"));
+ code = ftpfs_command (me, super, WAIT_REPLY, "PASS %s", pass);
+ if (code == CONTINUE)
+ {
+ char *p;
+
+ p = g_strdup_printf (_("FTP: Account required for user %s"),
+ super->path_element->user);
+ op = input_dialog (p, _("Account:"), MC_HISTORY_FTPFS_ACCOUNT, "",
+ INPUT_COMPLETE_USERNAMES);
+ g_free (p);
+ if (op == NULL)
+ ERRNOR (EPERM, 0);
+ vfs_print_message ("%s", _("ftpfs: sending user account"));
+ code = ftpfs_command (me, super, WAIT_REPLY, "ACCT %s", op);
+ g_free (op);
+ }
+ if (code != COMPLETE)
+ break;
+
+ MC_FALLTHROUGH;
+
+ case COMPLETE:
+ vfs_print_message ("%s", _("ftpfs: logged in"));
+ wipe_password (pass);
+ g_free (name);
+ return TRUE;
+
+ default:
+ ftp_super->failed_on_login = TRUE;
+ wipe_password (super->path_element->password);
+ super->path_element->password = NULL;
+ goto login_fail;
+ }
+ }
+
+ message (D_ERROR, MSG_ERROR, _("ftpfs: Login incorrect for user %s "),
+ super->path_element->user);
+
+ login_fail:
+ wipe_password (pass);
+ g_free (name);
+ ERRNOR (EPERM, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_load_no_proxy_list (void)
+{
+ /* FixMe: shouldn't be hardcoded!!! */
+ char *mc_file;
+
+ mc_file = g_build_filename (mc_global.sysconfig_dir, "mc.no_proxy", (char *) NULL);
+ if (exist_file (mc_file))
+ {
+ FILE *npf;
+
+ npf = fopen (mc_file, "r");
+ if (npf != NULL)
+ {
+ char s[BUF_LARGE]; /* provide for BUF_LARGE characters */
+
+ while (fgets (s, sizeof (s), npf) != NULL)
+ {
+ char *p;
+
+ p = strchr (s, '\n');
+ if (p == NULL) /* skip bogus entries */
+ {
+ int c;
+
+ while ((c = fgetc (npf)) != EOF && c != '\n')
+ ;
+ }
+ else if (p != s)
+ {
+ *p = '\0';
+ no_proxy = g_slist_prepend (no_proxy, g_strdup (s));
+ }
+ }
+
+ fclose (npf);
+ }
+ }
+
+ g_free (mc_file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Return TRUE if FTP proxy should be used for this host, FALSE otherwise */
+
+static gboolean
+ftpfs_check_proxy (const char *host)
+{
+
+ if (ftpfs_proxy_host == NULL || *ftpfs_proxy_host == '\0' || host == NULL || *host == '\0')
+ return FALSE; /* sanity check */
+
+ if (*host == '!')
+ return TRUE;
+
+ if (!ftpfs_always_use_proxy)
+ return FALSE;
+
+ if (strchr (host, '.') == NULL)
+ return FALSE;
+
+ if (no_proxy == NULL)
+ {
+ GSList *npe;
+
+ ftpfs_load_no_proxy_list ();
+
+ for (npe = no_proxy; npe != NULL; npe = g_slist_next (npe))
+ {
+ const char *domain = (const char *) npe->data;
+
+ if (domain[0] == '.')
+ {
+ size_t ld, lh;
+
+ ld = strlen (domain);
+ lh = strlen (host);
+
+ while (ld != 0 && lh != 0 && host[lh - 1] == domain[ld - 1])
+ {
+ ld--;
+ lh--;
+ }
+
+ if (ld == 0)
+ return FALSE;
+ }
+ else if (g_ascii_strcasecmp (host, domain) == 0)
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_get_proxy_host_and_port (const char *proxy, char **host, int *port)
+{
+ vfs_path_element_t *path_element;
+
+ path_element = vfs_url_split (proxy, FTP_COMMAND_PORT, URL_USE_ANONYMOUS);
+ *host = path_element->host;
+ path_element->host = NULL;
+ *port = path_element->port;
+ vfs_path_element_free (path_element);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_open_socket (struct vfs_class *me, struct vfs_s_super *super)
+{
+ struct addrinfo hints, *res, *curr_res;
+ int my_socket = 0;
+ char *host = NULL;
+ char port[8];
+ int tmp_port = 0;
+ int e;
+
+ (void) me;
+
+ if (super->path_element->host == NULL || *super->path_element->host == '\0')
+ {
+ vfs_print_message ("%s", _("ftpfs: Invalid host name."));
+ me->verrno = EINVAL;
+ return (-1);
+ }
+
+ /* Use a proxy host? */
+ /* Hosts to connect to that start with a ! should use proxy */
+ if (FTP_SUPER (super)->proxy != NULL)
+ ftpfs_get_proxy_host_and_port (ftpfs_proxy_host, &host, &tmp_port);
+ else
+ {
+ host = g_strdup (super->path_element->host);
+ tmp_port = super->path_element->port;
+ }
+
+ g_snprintf (port, sizeof (port), "%hu", (unsigned short) tmp_port);
+
+ tty_enable_interrupt_key (); /* clear the interrupt flag */
+
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+#ifdef AI_ADDRCONFIG
+ /* By default, only look up addresses using address types for
+ * which a local interface is configured (i.e. no IPv6 if no IPv6
+ * interfaces, likewise for IPv4 (see RFC 3493 for details). */
+ hints.ai_flags = AI_ADDRCONFIG;
+#endif
+
+ e = getaddrinfo (host, port, &hints, &res);
+
+#ifdef AI_ADDRCONFIG
+ if (e == EAI_BADFLAGS)
+ {
+ /* Retry with no flags if AI_ADDRCONFIG was rejected. */
+ hints.ai_flags = 0;
+ e = getaddrinfo (host, port, &hints, &res);
+ }
+#endif
+
+ *port = '\0';
+
+ if (e != 0)
+ {
+ tty_disable_interrupt_key ();
+ vfs_print_message (_("ftpfs: %s"), gai_strerror (e));
+ g_free (host);
+ me->verrno = EINVAL;
+ return (-1);
+ }
+
+ for (curr_res = res; curr_res != NULL; curr_res = curr_res->ai_next)
+ {
+ my_socket = socket (curr_res->ai_family, curr_res->ai_socktype, curr_res->ai_protocol);
+
+ if (my_socket < 0)
+ {
+ if (curr_res->ai_next != NULL)
+ continue;
+
+ tty_disable_interrupt_key ();
+ vfs_print_message (_("ftpfs: %s"), unix_error_string (errno));
+ g_free (host);
+ freeaddrinfo (res);
+ me->verrno = errno;
+ return (-1);
+ }
+
+ vfs_print_message (_("ftpfs: making connection to %s"), host);
+ MC_PTR_FREE (host);
+
+ if (connect (my_socket, curr_res->ai_addr, curr_res->ai_addrlen) >= 0)
+ break;
+
+ me->verrno = errno;
+ close (my_socket);
+
+ if (errno == EINTR && tty_got_interrupt ())
+ vfs_print_message ("%s", _("ftpfs: connection interrupted by user"));
+ else if (res->ai_next == NULL)
+ vfs_print_message (_("ftpfs: connection to server failed: %s"),
+ unix_error_string (errno));
+ else
+ continue;
+
+ freeaddrinfo (res);
+ tty_disable_interrupt_key ();
+ return (-1);
+ }
+
+ freeaddrinfo (res);
+ tty_disable_interrupt_key ();
+ return my_socket;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_open_archive_int (struct vfs_class *me, struct vfs_s_super *super)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ int retry_seconds = 0;
+
+ /* We do not want to use the passive if we are using proxies */
+ if (ftp_super->proxy != NULL)
+ ftp_super->use_passive_connection = ftpfs_use_passive_connections_over_proxy;
+
+ do
+ {
+ ftp_super->failed_on_login = FALSE;
+
+ ftp_super->sock = ftpfs_open_socket (me, super);
+ if (ftp_super->sock == -1)
+ return (-1);
+
+ if (ftpfs_login_server (me, super, NULL))
+ {
+ /* Logged in, no need to retry the connection */
+ break;
+ }
+
+ if (!ftp_super->failed_on_login)
+ return (-1);
+
+ /* Close only the socket descriptor */
+ close (ftp_super->sock);
+
+ if (ftpfs_retry_seconds != 0)
+ {
+ int count_down;
+
+ retry_seconds = ftpfs_retry_seconds;
+ tty_enable_interrupt_key ();
+ for (count_down = retry_seconds; count_down != 0; count_down--)
+ {
+ vfs_print_message (_("Waiting to retry... %d (Control-G to cancel)"), count_down);
+ sleep (1);
+ if (tty_got_interrupt ())
+ {
+ /* me->verrno = E; */
+ tty_disable_interrupt_key ();
+ return 0;
+ }
+ }
+ tty_disable_interrupt_key ();
+ }
+ }
+ while (retry_seconds != 0);
+
+ ftp_super->current_dir = ftpfs_get_current_directory (me, super);
+ if (ftp_super->current_dir == NULL)
+ ftp_super->current_dir = g_strdup (PATH_SEP_STR);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_open_archive (struct vfs_s_super *super,
+ const vfs_path_t * vpath, const vfs_path_element_t * vpath_element)
+{
+ (void) vpath;
+
+ super->path_element = ftpfs_correct_url_parameters (vpath_element);
+ if (ftpfs_check_proxy (super->path_element->host))
+ FTP_SUPER (super)->proxy = ftpfs_proxy_host;
+ super->root =
+ vfs_s_new_inode (vpath_element->class, super, ftpfs_default_stat (vpath_element->class));
+
+ return ftpfs_open_archive_int (vpath_element->class, super);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_archive_same (const vfs_path_element_t * vpath_element, struct vfs_s_super *super,
+ const vfs_path_t * vpath, void *cookie)
+{
+ vfs_path_element_t *path_element;
+ int result;
+
+ (void) vpath;
+ (void) cookie;
+
+ path_element = ftpfs_correct_url_parameters (vpath_element);
+
+ result = ((strcmp (path_element->host, super->path_element->host) == 0)
+ && (strcmp (path_element->user, super->path_element->user) == 0)
+ && (path_element->port == super->path_element->port)) ? 1 : 0;
+
+ vfs_path_element_free (path_element);
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* The returned directory should always contain a trailing slash */
+
+static char *
+ftpfs_get_current_directory (struct vfs_class *me, struct vfs_s_super *super)
+{
+ char buf[MC_MAXPATHLEN + 1];
+
+ if (ftpfs_command (me, super, NONE, "%s", "PWD") == COMPLETE &&
+ ftpfs_get_reply (me, FTP_SUPER (super)->sock, buf, sizeof (buf)) == COMPLETE)
+ {
+ char *bufp = NULL;
+ char *bufq;
+
+ for (bufq = buf; *bufq != '\0'; bufq++)
+ if (*bufq == '"')
+ {
+ if (bufp == NULL)
+ bufp = bufq + 1;
+ else
+ {
+ *bufq = '\0';
+
+ if (*bufp != '\0')
+ {
+ if (!IS_PATH_SEP (bufq[-1]))
+ {
+ *bufq++ = PATH_SEP;
+ *bufq = '\0';
+ }
+
+ if (IS_PATH_SEP (*bufp))
+ return g_strdup (bufp);
+
+ /* If the remote server is an Amiga a leading slash
+ might be missing. MC needs it because it is used
+ as separator between hostname and path internally. */
+ return g_strconcat (PATH_SEP_STR, bufp, (char *) NULL);
+ }
+
+ break;
+ }
+ }
+ }
+
+ me->verrno = EIO;
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Setup Passive PASV FTP connection */
+
+static gboolean
+ftpfs_setup_passive_pasv (struct vfs_class *me, struct vfs_s_super *super,
+ int my_socket, struct sockaddr_storage *sa, socklen_t * salen)
+{
+ char *c;
+ char n[6];
+ int xa, xb, xc, xd, xe, xf;
+
+ if (ftpfs_command (me, super, WAIT_REPLY | WANT_STRING, "%s", "PASV") != COMPLETE)
+ return FALSE;
+
+ /* Parse remote parameters */
+ for (c = reply_str + 4; *c != '\0' && !isdigit ((unsigned char) *c); c++)
+ ;
+
+ if (*c == '\0' || !isdigit ((unsigned char) *c))
+ return FALSE;
+
+ /* cppcheck-suppress invalidscanf */
+ if (sscanf (c, "%d,%d,%d,%d,%d,%d", &xa, &xb, &xc, &xd, &xe, &xf) != 6)
+ return FALSE;
+
+ n[0] = (unsigned char) xa;
+ n[1] = (unsigned char) xb;
+ n[2] = (unsigned char) xc;
+ n[3] = (unsigned char) xd;
+ n[4] = (unsigned char) xe;
+ n[5] = (unsigned char) xf;
+
+ memcpy (&(((struct sockaddr_in *) sa)->sin_addr.s_addr), (void *) n, 4);
+ memcpy (&(((struct sockaddr_in *) sa)->sin_port), (void *) &n[4], 2);
+
+ return (connect (my_socket, (struct sockaddr *) sa, *salen) >= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Setup Passive EPSV FTP connection */
+
+static gboolean
+ftpfs_setup_passive_epsv (struct vfs_class *me, struct vfs_s_super *super,
+ int my_socket, struct sockaddr_storage *sa, socklen_t * salen)
+{
+ char *c;
+ int port;
+
+ if (ftpfs_command (me, super, WAIT_REPLY | WANT_STRING, "%s", "EPSV") != COMPLETE)
+ return FALSE;
+
+ /* (|||<port>|) */
+ c = strchr (reply_str, '|');
+ if (c == NULL || strlen (c) <= 3)
+ return FALSE;
+
+ c += 3;
+ port = atoi (c);
+ if (port < 0 || port > 65535)
+ return FALSE;
+
+ port = htons (port);
+
+ switch (sa->ss_family)
+ {
+ case AF_INET:
+ ((struct sockaddr_in *) sa)->sin_port = port;
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in6 *) sa)->sin6_port = port;
+ break;
+ default:
+ break;
+ }
+
+ return (connect (my_socket, (struct sockaddr *) sa, *salen) >= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Setup Passive ftp connection, we use it for source routed connections */
+
+static gboolean
+ftpfs_setup_passive (struct vfs_class *me, struct vfs_s_super *super,
+ int my_socket, struct sockaddr_storage *sa, socklen_t * salen)
+{
+ /* It's IPV4, so try PASV first, some servers and ALGs get confused by EPSV */
+ if (sa->ss_family == AF_INET)
+ {
+ if (!ftpfs_setup_passive_pasv (me, super, my_socket, sa, salen))
+ /* An IPV4 FTP server might support EPSV, so if PASV fails we can try EPSV anyway */
+ if (!ftpfs_setup_passive_epsv (me, super, my_socket, sa, salen))
+ return FALSE;
+ }
+ /* It's IPV6, so EPSV is our only hope */
+ else if (!ftpfs_setup_passive_epsv (me, super, my_socket, sa, salen))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Setup Active PORT or EPRT FTP connection */
+
+static int
+ftpfs_setup_active (struct vfs_class *me, struct vfs_s_super *super,
+ struct sockaddr_storage data_addr, socklen_t data_addrlen)
+{
+ unsigned short int port;
+ char *addr;
+ unsigned int af;
+ int res;
+
+ switch (data_addr.ss_family)
+ {
+ case AF_INET:
+ af = FTP_INET;
+ port = ((struct sockaddr_in *) &data_addr)->sin_port;
+ break;
+ case AF_INET6:
+ af = FTP_INET6;
+ port = ((struct sockaddr_in6 *) &data_addr)->sin6_port;
+ break;
+ default:
+ /* Not implemented */
+ return 0;
+ }
+
+ addr = g_try_malloc (NI_MAXHOST);
+ if (addr == NULL)
+ ERRNOR (ENOMEM, -1);
+
+ res =
+ getnameinfo ((struct sockaddr *) &data_addr, data_addrlen, addr, NI_MAXHOST, NULL, 0,
+ NI_NUMERICHOST);
+ if (res != 0)
+ {
+ const char *err_str;
+
+ g_free (addr);
+
+ if (res == EAI_SYSTEM)
+ {
+ me->verrno = errno;
+ err_str = unix_error_string (me->verrno);
+ }
+ else
+ {
+ me->verrno = EIO;
+ err_str = gai_strerror (res);
+ }
+
+ vfs_print_message (_("ftpfs: could not make address-to-name translation: %s"), err_str);
+
+ return (-1);
+ }
+
+ /* If we are talking to an IPV4 server, try PORT, and, only if it fails, go for EPRT */
+ if (af == FTP_INET)
+ {
+ unsigned char *a = (unsigned char *) &((struct sockaddr_in *) &data_addr)->sin_addr;
+ unsigned char *p = (unsigned char *) &port;
+
+ if (ftpfs_command (me, super, WAIT_REPLY,
+ "PORT %u,%u,%u,%u,%u,%u", a[0], a[1], a[2], a[3],
+ p[0], p[1]) == COMPLETE)
+ {
+ g_free (addr);
+ return 1;
+ }
+ }
+
+ /*
+ * Converts network MSB first order to host byte order (LSB
+ * first on i386). If we do it earlier, we will run into an
+ * endianness issue, because the server actually expects to see
+ * "PORT A,D,D,R,MSB,LSB" in the PORT command.
+ */
+ port = ntohs (port);
+
+ /* We are talking to an IPV6 server or PORT failed, so we can try EPRT anyway */
+ res =
+ (ftpfs_command (me, super, WAIT_REPLY, "EPRT |%u|%s|%hu|", af, addr, port) ==
+ COMPLETE) ? 1 : 0;
+ g_free (addr);
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Initialize a socket for FTP DATA connection */
+
+static int
+ftpfs_init_data_socket (struct vfs_class *me, struct vfs_s_super *super,
+ struct sockaddr_storage *data_addr, socklen_t * data_addrlen)
+{
+ const unsigned int attempts = 10;
+ unsigned int i;
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ int result;
+
+ for (i = 0; i < attempts; i++)
+ {
+ memset (data_addr, 0, sizeof (*data_addr));
+ *data_addrlen = sizeof (*data_addr);
+
+ if (ftp_super->use_passive_connection)
+ {
+ result = getpeername (ftp_super->sock, (struct sockaddr *) data_addr, data_addrlen);
+ if (result == 0)
+ break;
+
+ me->verrno = errno;
+
+ if (me->verrno == ENOTCONN)
+ {
+ vfs_print_message (_("ftpfs: try reconnect to server, attempt %u"), i);
+ if (ftpfs_reconnect (me, super))
+ continue; /* get name of new socket */
+ }
+ else
+ {
+ /* error -- stop loop */
+ vfs_print_message (_("ftpfs: could not get socket name: %s"),
+ unix_error_string (me->verrno));
+ }
+ }
+ else
+ {
+ result = getsockname (ftp_super->sock, (struct sockaddr *) data_addr, data_addrlen);
+ if (result == 0)
+ break;
+
+ me->verrno = errno;
+
+ vfs_print_message (_("ftpfs: try reconnect to server, attempt %u"), i);
+ if (ftpfs_reconnect (me, super))
+ continue; /* get name of new socket */
+
+ /* error -- stop loop */
+ vfs_print_message ("%s", _("ftpfs: could not reconnect to server"));
+ }
+
+ i = attempts;
+ }
+
+ if (i >= attempts)
+ return (-1);
+
+ switch (data_addr->ss_family)
+ {
+ case AF_INET:
+ ((struct sockaddr_in *) data_addr)->sin_port = 0;
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in6 *) data_addr)->sin6_port = 0;
+ break;
+ default:
+ vfs_print_message ("%s", _("ftpfs: invalid address family"));
+ ERRNOR (EINVAL, -1);
+ }
+
+ result = socket (data_addr->ss_family, SOCK_STREAM, IPPROTO_TCP);
+ if (result < 0)
+ {
+ me->verrno = errno;
+ vfs_print_message (_("ftpfs: could not create socket: %s"), unix_error_string (me->verrno));
+ result = -1;
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Initialize FTP DATA connection */
+
+static int
+ftpfs_initconn (struct vfs_class *me, struct vfs_s_super *super)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ struct sockaddr_storage data_addr;
+ socklen_t data_addrlen;
+
+ /*
+ * Don't factor socket initialization out of these conditionals,
+ * because ftpfs_init_data_socket initializes it in different way
+ * depending on use_passive_connection flag.
+ */
+
+ /* Try to establish a passive connection first (if requested) */
+ if (ftp_super->use_passive_connection)
+ {
+ int data_sock;
+
+ data_sock = ftpfs_init_data_socket (me, super, &data_addr, &data_addrlen);
+ if (data_sock < 0)
+ return (-1);
+
+ if (ftpfs_setup_passive (me, super, data_sock, &data_addr, &data_addrlen))
+ return data_sock;
+
+ vfs_print_message ("%s", _("ftpfs: could not setup passive mode"));
+ ftp_super->use_passive_connection = FALSE;
+
+ close (data_sock);
+ }
+
+ /* If passive setup is disabled or failed, fallback to active connections */
+ if (!ftp_super->use_passive_connection)
+ {
+ int data_sock;
+
+ data_sock = ftpfs_init_data_socket (me, super, &data_addr, &data_addrlen);
+ if (data_sock < 0)
+ return (-1);
+
+ if ((bind (data_sock, (struct sockaddr *) &data_addr, data_addrlen) != 0) ||
+ (getsockname (data_sock, (struct sockaddr *) &data_addr, &data_addrlen) != 0) ||
+ (listen (data_sock, 1) != 0))
+ {
+ close (data_sock);
+ ERRNOR (errno, -1);
+ }
+
+ if (ftpfs_setup_active (me, super, data_addr, data_addrlen) != 0)
+ return data_sock;
+
+ close (data_sock);
+ }
+
+ /* Restore the initial value of use_passive_connection (for subsequent retries) */
+ ftp_super->use_passive_connection =
+ ftp_super->proxy !=
+ NULL ? ftpfs_use_passive_connections_over_proxy : ftpfs_use_passive_connections;
+
+ me->verrno = EIO;
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_open_data_connection (struct vfs_class *me, struct vfs_s_super *super, const char *cmd,
+ const char *remote, int isbinary, int reget)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ int s, j, data;
+
+ /* FTP doesn't allow to open more than one file at a time */
+ if (ftp_super->ctl_connection_busy)
+ return (-1);
+
+ s = ftpfs_initconn (me, super);
+ if (s == -1)
+ return (-1);
+
+ if (ftpfs_changetype (me, super, isbinary) == -1)
+ {
+ close (s);
+ return (-1);
+ }
+
+ if (reget > 0)
+ {
+ j = ftpfs_command (me, super, WAIT_REPLY, "REST %d", reget);
+ if (j != CONTINUE)
+ {
+ close (s);
+ ERRNOR (EIO, -1);
+ }
+ }
+
+ if (remote == NULL)
+ j = ftpfs_command (me, super, WAIT_REPLY, "%s", cmd);
+ else
+ {
+ char *remote_path;
+
+ remote_path = ftpfs_translate_path (me, super, remote);
+ j = ftpfs_command (me, super, WAIT_REPLY, "%s /%s", cmd,
+ /* WarFtpD can't STORE //filename */
+ IS_PATH_SEP (*remote_path) ? remote_path + 1 : remote_path);
+ g_free (remote_path);
+ }
+
+ if (j != PRELIM)
+ {
+ close (s);
+ ERRNOR (EPERM, -1);
+ }
+
+ if (ftp_super->use_passive_connection)
+ data = s;
+ else
+ {
+ struct sockaddr_storage from;
+ socklen_t fromlen = sizeof (from);
+
+ tty_enable_interrupt_key ();
+ data = accept (s, (struct sockaddr *) &from, &fromlen);
+ if (data < 0)
+ me->verrno = errno;
+ tty_disable_interrupt_key ();
+ close (s);
+ if (data < 0)
+ return (-1);
+ }
+
+ ftp_super->ctl_connection_busy = TRUE;
+ return data;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_linear_abort (struct vfs_class *me, vfs_file_handler_t * fh)
+{
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ static unsigned char const ipbuf[3] = { IAC, IP, IAC };
+ fd_set mask;
+ int dsock = FH_SOCK;
+
+ FH_SOCK = -1;
+ ftp_super->ctl_connection_busy = FALSE;
+
+ vfs_print_message ("%s", _("ftpfs: aborting transfer."));
+
+ if (send (ftp_super->sock, ipbuf, sizeof (ipbuf), MSG_OOB) != sizeof (ipbuf))
+ {
+ vfs_print_message (_("ftpfs: abort error: %s"), unix_error_string (errno));
+ if (dsock != -1)
+ close (dsock);
+ return;
+ }
+
+ if (ftpfs_command (me, super, NONE, "%cABOR", DM) != COMPLETE)
+ {
+ vfs_print_message ("%s", _("ftpfs: abort failed"));
+ if (dsock != -1)
+ close (dsock);
+ return;
+ }
+
+ if (dsock != -1)
+ {
+ FD_ZERO (&mask);
+ FD_SET (dsock, &mask);
+
+ if (select (dsock + 1, &mask, NULL, NULL, NULL) > 0)
+ {
+ gint64 start_tim;
+ char buf[BUF_8K];
+
+ start_tim = g_get_monotonic_time ();
+
+ /* flush the remaining data */
+ while (read (dsock, buf, sizeof (buf)) > 0)
+ {
+ gint64 tim;
+
+ tim = g_get_monotonic_time ();
+
+ if (tim > start_tim + ABORT_TIMEOUT)
+ {
+ /* server keeps sending, drop the connection and ftpfs_reconnect */
+ close (dsock);
+ ftpfs_reconnect (me, super);
+ return;
+ }
+ }
+ }
+ close (dsock);
+ }
+
+ if ((ftpfs_get_reply (me, ftp_super->sock, NULL, 0) == TRANSIENT) && (code == 426))
+ ftpfs_get_reply (me, ftp_super->sock, NULL, 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static void
+resolve_symlink_without_ls_options (struct vfs_class *me, struct vfs_s_super *super,
+ struct vfs_s_inode *dir)
+{
+ struct linklist *flist;
+ struct direntry *fe, *fel;
+ char tmp[MC_MAXPATHLEN];
+
+ dir->symlink_status = FTPFS_RESOLVING_SYMLINKS;
+ for (flist = dir->file_list->next; flist != dir->file_list; flist = flist->next)
+ {
+ /* flist->data->l_stat is already initialized with 0 */
+ fel = flist->data;
+ if (S_ISLNK (fel->s.st_mode) && fel->linkname != NULL)
+ {
+ int depth;
+
+ if (IS_PATH_SEP (fel->linkname[0]))
+ {
+ if (strlen (fel->linkname) >= MC_MAXPATHLEN)
+ continue;
+ strcpy (tmp, fel->linkname);
+ }
+ else
+ {
+ if ((strlen (dir->remote_path) + strlen (fel->linkname)) >= MC_MAXPATHLEN)
+ continue;
+ strcpy (tmp, dir->remote_path);
+ if (tmp[1] != '\0')
+ strcat (tmp, PATH_SEP_STR);
+ strcat (tmp + 1, fel->linkname);
+ }
+
+ for (depth = 0; depth < 100; depth++)
+ { /* depth protects against recursive symbolic links */
+ canonicalize_pathname (tmp);
+ fe = _get_file_entry_t (bucket, tmp, 0, 0);
+ if (fe != NULL)
+ {
+ if (S_ISLNK (fe->s.st_mode) && fe->l_stat == 0)
+ {
+ /* Symlink points to link which isn't resolved, yet. */
+ if (IS_PATH_SEP (fe->linkname[0]))
+ {
+ if (strlen (fe->linkname) >= MC_MAXPATHLEN)
+ break;
+ strcpy (tmp, fe->linkname);
+ }
+ else
+ {
+ /* at this point tmp looks always like this
+ /directory/filename, i.e. no need to check
+ strrchr's return value */
+ *(strrchr (tmp, PATH_SEP) + 1) = '\0'; /* dirname */
+ if ((strlen (tmp) + strlen (fe->linkname)) >= MC_MAXPATHLEN)
+ break;
+ strcat (tmp, fe->linkname);
+ }
+ continue;
+ }
+ else
+ {
+ fel->l_stat = g_new (struct stat, 1);
+ if (S_ISLNK (fe->s.st_mode))
+ *fel->l_stat = *fe->l_stat;
+ else
+ *fel->l_stat = fe->s;
+ (*fel->l_stat).st_ino = bucket->__inode_counter++;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ dir->symlink_status = FTPFS_RESOLVED_SYMLINKS;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+resolve_symlink_with_ls_options (struct vfs_class *me, struct vfs_s_super *super,
+ struct vfs_s_inode *dir)
+{
+ char buffer[2048] = "", *filename;
+ int sock;
+ FILE *fp;
+ struct stat s;
+ struct linklist *flist;
+ struct direntry *fe;
+ int switch_method = 0;
+
+ dir->symlink_status = FTPFS_RESOLVED_SYMLINKS;
+ if (strchr (dir->remote_path, ' ') == NULL)
+ sock = ftpfs_open_data_connection (bucket, "LIST -lLa", dir->remote_path, TYPE_ASCII, 0);
+ else
+ {
+ if (ftpfs_chdir_internal (bucket, dir->remote_path) != COMPLETE)
+ {
+ vfs_print_message ("%s", _("ftpfs: CWD failed."));
+ return;
+ }
+
+ sock = ftpfs_open_data_connection (bucket, "LIST -lLa", ".", TYPE_ASCII, 0);
+ }
+
+ if (sock == -1)
+ {
+ vfs_print_message ("%s", _("ftpfs: couldn't resolve symlink"));
+ return;
+ }
+
+ fp = fdopen (sock, "r");
+ if (fp == NULL)
+ {
+ close (sock);
+ vfs_print_message ("%s", _("ftpfs: couldn't resolve symlink"));
+ return;
+ }
+ tty_enable_interrupt_key ();
+ flist = dir->file_list->next;
+
+ while (TRUE)
+ {
+ do
+ {
+ if (flist == dir->file_list)
+ goto done;
+
+ fe = flist->data;
+ flist = flist->next;
+ }
+ while (!S_ISLNK (fe->s.st_mode));
+
+ while (TRUE)
+ {
+ if (fgets (buffer, sizeof (buffer), fp) == NULL)
+ goto done;
+
+ if (me->logfile != NULL)
+ {
+ fputs (buffer, me->logfile);
+ fflush (me->logfile);
+ }
+
+ vfs_die ("This code should be commented out\n");
+
+ if (vfs_parse_ls_lga (buffer, &s, &filename, NULL))
+ {
+ int r;
+
+ r = strcmp (fe->name, filename);
+ g_free (filename);
+ if (r == 0)
+ {
+ if (S_ISLNK (s.st_mode))
+ {
+ /* This server doesn't understand LIST -lLa */
+ switch_method = 1;
+ goto done;
+ }
+
+ fe->l_stat = g_try_new (struct stat, 1);
+ if (fe->l_stat == NULL)
+ goto done;
+
+ *fe->l_stat = s;
+ (*fe->l_stat).st_ino = bucket->__inode_counter++;
+ break;
+ }
+
+ if (r < 0)
+ break;
+ }
+ }
+ }
+
+ done:
+ while (fgets (buffer, sizeof (buffer), fp) != NULL)
+ ;
+ tty_disable_interrupt_key ();
+ fclose (fp);
+ ftpfs_get_reply (me, FTP_SUPER (super)->sock, NULL, 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+resolve_symlink (struct vfs_class *me, struct vfs_s_super *super, struct vfs_s_inode *dir)
+{
+ vfs_print_message ("%s", _("Resolving symlink..."));
+
+ if (FTP_SUPER (super)->strict_rfc959_list_cmd)
+ resolve_symlink_without_ls_options (me, super, dir);
+ else
+ resolve_symlink_with_ls_options (me, super, dir);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_dir_load (struct vfs_class *me, struct vfs_s_inode *dir, const char *remote_path)
+{
+ struct vfs_s_super *super = dir->super;
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ int sock;
+ char lc_buffer[BUF_8K];
+ int res;
+ gboolean cd_first;
+ GSList *dirlist = NULL;
+ GSList *entlist;
+ GSList *iter;
+ int err_count = 0;
+
+ cd_first = ftpfs_first_cd_then_ls || (ftp_super->strict == RFC_STRICT)
+ || (strchr (remote_path, ' ') != NULL);
+
+ again:
+ vfs_print_message (_("ftpfs: Reading FTP directory %s... %s%s"),
+ remote_path,
+ ftp_super->strict ==
+ RFC_STRICT ? _("(strict rfc959)") : "", cd_first ? _("(chdir first)") : "");
+
+ if (cd_first && ftpfs_chdir_internal (me, super, remote_path) != COMPLETE)
+ {
+ me->verrno = ENOENT;
+ vfs_print_message ("%s", _("ftpfs: CWD failed."));
+ return (-1);
+ }
+
+ dir->timestamp = g_get_monotonic_time () + ftpfs_directory_timeout * G_USEC_PER_SEC;
+
+ if (ftp_super->strict == RFC_STRICT)
+ sock = ftpfs_open_data_connection (me, super, "LIST", 0, TYPE_ASCII, 0);
+ else if (cd_first)
+ /* Dirty hack to avoid autoprepending / to . */
+ /* Wu-ftpd produces strange output for '/' if 'LIST -la .' used */
+ sock = ftpfs_open_data_connection (me, super, "LIST -la", 0, TYPE_ASCII, 0);
+ else
+ {
+ char *path;
+
+ /* Trailing "/." is necessary if remote_path is a symlink */
+ path = g_strconcat (remote_path, PATH_SEP_STR ".", (char *) NULL);
+ sock = ftpfs_open_data_connection (me, super, "LIST -la", path, TYPE_ASCII, 0);
+ g_free (path);
+ }
+
+ if (sock == -1)
+ {
+ fallback:
+ if (ftp_super->strict == RFC_AUTODETECT)
+ {
+ /* It's our first attempt to get a directory listing from this
+ server (UNIX style LIST command) */
+ ftp_super->strict = RFC_STRICT;
+ /* I hate goto, but recursive call needs another 8K on stack */
+ /* return ftpfs_dir_load (me, dir, remote_path); */
+ cd_first = TRUE;
+ goto again;
+ }
+
+ vfs_print_message ("%s", _("ftpfs: failed; nowhere to fallback to"));
+ ERRNOR (EACCES, -1);
+ }
+
+ /* read full directory list, then parse it */
+ while ((res = vfs_s_get_line_interruptible (me, lc_buffer, sizeof (lc_buffer), sock)) != 0)
+ {
+ if (res == EINTR)
+ {
+ me->verrno = ECONNRESET;
+ close (sock);
+ ftp_super->ctl_connection_busy = FALSE;
+ ftpfs_get_reply (me, ftp_super->sock, NULL, 0);
+ g_slist_free_full (dirlist, g_free);
+ vfs_print_message (_("%s: failure"), me->name);
+ return (-1);
+ }
+
+ if (me->logfile != NULL)
+ {
+ fputs (lc_buffer, me->logfile);
+ fputs ("\n", me->logfile);
+ fflush (me->logfile);
+ }
+
+ dirlist = g_slist_prepend (dirlist, g_strdup (lc_buffer));
+ }
+
+ close (sock);
+ ftp_super->ctl_connection_busy = FALSE;
+ me->verrno = E_REMOTE;
+ if ((ftpfs_get_reply (me, ftp_super->sock, NULL, 0) != COMPLETE))
+ {
+ g_slist_free_full (dirlist, g_free);
+ goto fallback;
+ }
+
+ if (dirlist == NULL && !cd_first)
+ {
+ /* The LIST command may produce an empty output. In such scenario
+ it is not clear whether this is caused by 'remote_path' being
+ a non-existent path or for some other reason (listing empty
+ directory without the -a option, non-readable directory, etc.).
+
+ Since 'dir_load' is a crucial method, when it comes to determine
+ whether a given path is a _directory_, the code must try its best
+ to determine the type of 'remote_path'. The only reliable way to
+ achieve this is through issuing a CWD command. */
+
+ cd_first = TRUE;
+ goto again;
+ }
+
+ /* parse server's reply */
+ dirlist = g_slist_reverse (dirlist); /* restore order */
+ entlist = ftpfs_parse_long_list (me, dir, dirlist, &err_count);
+ g_slist_free_full (dirlist, g_free);
+
+ for (iter = entlist; iter != NULL; iter = g_slist_next (iter))
+ vfs_s_insert_entry (me, dir, VFS_ENTRY (iter->data));
+
+ g_slist_free (entlist);
+
+ if (ftp_super->strict == RFC_AUTODETECT)
+ ftp_super->strict = RFC_DARING;
+
+ vfs_print_message (_("%s: done."), me->name);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_file_store (struct vfs_class *me, vfs_file_handler_t * fh, char *name, char *localname)
+{
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ ftp_file_handler_t *ftp = FTP_FILE_HANDLER (fh);
+
+ int h, sock;
+ off_t n_stored = 0;
+#ifdef HAVE_STRUCT_LINGER_L_LINGER
+ struct linger li;
+#else
+ int flag_one = 1;
+#endif
+ char lc_buffer[BUF_8K];
+ struct stat s;
+ char *w_buf;
+
+ h = open (localname, O_RDONLY);
+ if (h == -1)
+ ERRNOR (EIO, -1);
+
+ if (fstat (h, &s) == -1)
+ {
+ me->verrno = errno;
+ close (h);
+ return (-1);
+ }
+
+ sock =
+ ftpfs_open_data_connection (me, super, ftp->append ? "APPE" : "STOR", name, TYPE_BINARY, 0);
+ if (sock < 0)
+ {
+ close (h);
+ return (-1);
+ }
+#ifdef HAVE_STRUCT_LINGER_L_LINGER
+ li.l_onoff = 1;
+ li.l_linger = 120;
+ setsockopt (sock, SOL_SOCKET, SO_LINGER, (char *) &li, sizeof (li));
+#else
+ setsockopt (sock, SOL_SOCKET, SO_LINGER, &flag_one, sizeof (flag_one));
+#endif
+
+ tty_enable_interrupt_key ();
+ while (TRUE)
+ {
+ ssize_t n_read, n_written;
+
+ while ((n_read = read (h, lc_buffer, sizeof (lc_buffer))) == -1)
+ {
+ if (errno != EINTR)
+ {
+ me->verrno = errno;
+ goto error_return;
+ }
+ if (tty_got_interrupt ())
+ {
+ me->verrno = EINTR;
+ goto error_return;
+ }
+ }
+ if (n_read == 0)
+ break;
+
+ n_stored += n_read;
+ w_buf = lc_buffer;
+
+ while ((n_written = write (sock, w_buf, n_read)) != n_read)
+ {
+ if (n_written == -1)
+ {
+ if (errno == EINTR && !tty_got_interrupt ())
+ continue;
+
+ me->verrno = errno;
+ goto error_return;
+ }
+
+ w_buf += n_written;
+ n_read -= n_written;
+ }
+
+ vfs_print_message ("%s: %" PRIuMAX "/%" PRIuMAX,
+ _("ftpfs: storing file"), (uintmax_t) n_stored, (uintmax_t) s.st_size);
+ }
+ tty_disable_interrupt_key ();
+
+ close (sock);
+ ftp_super->ctl_connection_busy = FALSE;
+ close (h);
+
+ if (ftpfs_get_reply (me, ftp_super->sock, NULL, 0) != COMPLETE)
+ ERRNOR (EIO, -1);
+ return 0;
+
+ error_return:
+ tty_disable_interrupt_key ();
+ close (sock);
+ ftp_super->ctl_connection_busy = FALSE;
+ close (h);
+
+ ftpfs_get_reply (me, ftp_super->sock, NULL, 0);
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_linear_start (struct vfs_class *me, vfs_file_handler_t * fh, off_t offset)
+{
+ char *name;
+
+ name = vfs_s_fullpath (me, fh->ino);
+ if (name == NULL)
+ return 0;
+
+ FH_SOCK =
+ ftpfs_open_data_connection (me, VFS_FILE_HANDLER_SUPER (fh), "RETR", name, TYPE_BINARY,
+ offset);
+ g_free (name);
+ if (FH_SOCK == -1)
+ ERRNOR (EACCES, 0);
+
+ fh->linear = LS_LINEAR_OPEN;
+ FTP_FILE_HANDLER (fh)->append = FALSE;
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+ftpfs_linear_read (struct vfs_class *me, vfs_file_handler_t * fh, void *buf, size_t len)
+{
+ ssize_t n;
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+
+ while ((n = read (FH_SOCK, buf, len)) < 0)
+ {
+ if ((errno == EINTR) && !tty_got_interrupt ())
+ continue;
+ break;
+ }
+
+ if (n < 0)
+ ftpfs_linear_abort (me, fh);
+ else if (n == 0)
+ {
+ FTP_SUPER (super)->ctl_connection_busy = FALSE;
+ close (FH_SOCK);
+ FH_SOCK = -1;
+ if ((ftpfs_get_reply (me, FTP_SUPER (super)->sock, NULL, 0) != COMPLETE))
+ ERRNOR (E_REMOTE, -1);
+ return 0;
+ }
+
+ ERRNOR (errno, n);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_linear_close (struct vfs_class *me, vfs_file_handler_t * fh)
+{
+ if (FH_SOCK != -1)
+ ftpfs_linear_abort (me, fh);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_ctl (void *fh, int ctlop, void *arg)
+{
+ (void) arg;
+
+ switch (ctlop)
+ {
+ case VFS_CTL_IS_NOTREADY:
+ {
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ int v;
+
+ if (file->linear == LS_NOT_LINEAR)
+ vfs_die ("You may not do this");
+ if (file->linear == LS_LINEAR_CLOSED || file->linear == LS_LINEAR_PREOPEN)
+ return 0;
+
+ v = vfs_s_select_on_two (FH_SOCK, 0);
+ return (((v < 0) && (errno == EINTR)) || v == 0) ? 1 : 0;
+ }
+ default:
+ return 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_send_command (const vfs_path_t * vpath, const char *cmd, int flags)
+{
+ const char *rpath;
+ char *p;
+ struct vfs_s_super *super;
+ int r;
+ struct vfs_class *me;
+ gboolean flush_directory_cache = (flags & OPT_FLUSH) != 0;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ rpath = vfs_s_get_path (vpath, &super, 0);
+ if (rpath == NULL)
+ return (-1);
+
+ p = ftpfs_translate_path (me, super, rpath);
+ r = ftpfs_command (me, super, WAIT_REPLY, cmd, p);
+ g_free (p);
+ vfs_stamp_create (vfs_ftpfs_ops, super);
+ if ((flags & OPT_IGNORE_ERROR) != 0)
+ r = COMPLETE;
+ if (r != COMPLETE)
+ {
+ me->verrno = EPERM;
+ return (-1);
+ }
+ if (flush_directory_cache)
+ vfs_s_invalidate (me, super);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ int ret;
+
+ ret = vfs_s_stat (vpath, buf);
+ ftpfs_set_blksize (buf);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ int ret;
+
+ ret = vfs_s_lstat (vpath, buf);
+ ftpfs_set_blksize (buf);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_fstat (void *vfs_info, struct stat *buf)
+{
+ int ret;
+
+ ret = vfs_s_fstat (vfs_info, buf);
+ ftpfs_set_blksize (buf);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_chmod (const vfs_path_t * vpath, mode_t mode)
+{
+ char buf[BUF_SMALL];
+ int ret;
+
+ g_snprintf (buf, sizeof (buf), "SITE CHMOD %4.4o /%%s", (unsigned int) (mode & 07777));
+ ret = ftpfs_send_command (vpath, buf, OPT_FLUSH);
+ return ftpfs_ignore_chattr_errors ? 0 : ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_chown (const vfs_path_t * vpath, uid_t owner, gid_t group)
+{
+#if 0
+ (void) vpath;
+ (void) owner;
+ (void) group;
+
+ me->verrno = EPERM;
+ return (-1);
+#else
+ /* Everyone knows it is not possible to chown remotely, so why bother them.
+ If someone's root, then copy/move will always try to chown it... */
+ (void) vpath;
+ (void) owner;
+ (void) group;
+ return 0;
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_unlink (const vfs_path_t * vpath)
+{
+ return ftpfs_send_command (vpath, "DELE /%s", OPT_FLUSH);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Return TRUE if path is the same directory as the one we are in now */
+static gboolean
+ftpfs_is_same_dir (struct vfs_class *me, struct vfs_s_super *super, const char *path)
+{
+ (void) me;
+
+ return (FTP_SUPER (super)->current_dir != NULL
+ && strcmp (path, FTP_SUPER (super)->current_dir) == 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_chdir_internal (struct vfs_class *me, struct vfs_s_super *super, const char *remote_path)
+{
+ ftp_super_t *ftp_super = FTP_SUPER (super);
+ int r;
+ char *p;
+
+ if (!ftp_super->cwd_deferred && ftpfs_is_same_dir (me, super, remote_path))
+ return COMPLETE;
+
+ p = ftpfs_translate_path (me, super, remote_path);
+ r = ftpfs_command (me, super, WAIT_REPLY, "CWD /%s", p);
+ g_free (p);
+
+ if (r != COMPLETE)
+ me->verrno = EIO;
+ else
+ {
+ g_free (ftp_super->current_dir);
+ ftp_super->current_dir = g_strdup (remote_path);
+ ftp_super->cwd_deferred = FALSE;
+ }
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ ftpfs_send_command (vpath1, "RNFR /%s", OPT_FLUSH);
+ return ftpfs_send_command (vpath2, "RNTO /%s", OPT_FLUSH);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_mkdir (const vfs_path_t * vpath, mode_t mode)
+{
+ (void) mode; /* FIXME: should be used */
+
+ return ftpfs_send_command (vpath, "MKD /%s", OPT_FLUSH);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_rmdir (const vfs_path_t * vpath)
+{
+ return ftpfs_send_command (vpath, "RMD /%s", OPT_FLUSH);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_file_handler_t *
+ftpfs_fh_new (struct vfs_s_inode *ino, gboolean changed)
+{
+ ftp_file_handler_t *fh;
+
+ fh = g_new0 (ftp_file_handler_t, 1);
+ vfs_s_init_fh (VFS_FILE_HANDLER (fh), ino, changed);
+ fh->sock = -1;
+
+ return VFS_FILE_HANDLER (fh);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_fh_open (struct vfs_class *me, vfs_file_handler_t * fh, int flags, mode_t mode)
+{
+ ftp_file_handler_t *ftp = FTP_FILE_HANDLER (fh);
+
+ (void) mode;
+
+ /* File will be written only, so no need to retrieve it from ftp server */
+ if (((flags & O_WRONLY) == O_WRONLY) && ((flags & (O_RDONLY | O_RDWR)) == 0))
+ {
+#ifdef HAVE_STRUCT_LINGER_L_LINGER
+ struct linger li;
+#else
+ int li = 1;
+#endif
+ char *name;
+
+ /* ftpfs_linear_start() called, so data will be written
+ * to local temporary file and stored to ftp server
+ * by vfs_s_close later
+ */
+ if (FTP_SUPER (VFS_FILE_HANDLER_SUPER (fh))->ctl_connection_busy)
+ {
+ if (fh->ino->localname == NULL)
+ {
+ vfs_path_t *vpath;
+ int handle;
+
+ handle = vfs_mkstemps (&vpath, me->name, fh->ino->ent->name);
+ if (handle == -1)
+ return (-1);
+
+ close (handle);
+ fh->ino->localname = vfs_path_free (vpath, FALSE);
+ ftp->append = (flags & O_APPEND) != 0;
+ }
+ return 0;
+ }
+ name = vfs_s_fullpath (me, fh->ino);
+ if (name == NULL)
+ return (-1);
+
+ fh->handle =
+ ftpfs_open_data_connection (me, VFS_FILE_HANDLER_SUPER (fh),
+ (flags & O_APPEND) != 0 ? "APPE" : "STOR", name,
+ TYPE_BINARY, 0);
+ g_free (name);
+
+ if (fh->handle < 0)
+ return (-1);
+
+#ifdef HAVE_STRUCT_LINGER_L_LINGER
+ li.l_onoff = 1;
+ li.l_linger = 120;
+#endif
+ setsockopt (fh->handle, SOL_SOCKET, SO_LINGER, &li, sizeof (li));
+
+ if (fh->ino->localname != NULL)
+ {
+ unlink (fh->ino->localname);
+ MC_PTR_FREE (fh->ino->localname);
+ }
+ return 0;
+ }
+
+ if (fh->ino->localname == NULL && vfs_s_retrieve_file (me, fh->ino) == -1)
+ return (-1);
+
+ if (fh->ino->localname == NULL)
+ vfs_die ("retrieve_file failed to fill in localname");
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+ftpfs_fh_close (struct vfs_class *me, vfs_file_handler_t * fh)
+{
+ if (fh->handle != -1 && fh->ino->localname == NULL)
+ {
+ ftp_super_t *ftp = FTP_SUPER (VFS_FILE_HANDLER_SUPER (fh));
+
+ close (fh->handle);
+ fh->handle = -1;
+ ftp->ctl_connection_busy = FALSE;
+ /* File is stored to destination already, so
+ * we prevent VFS_SUBCLASS (me)->ftpfs_file_store() call from vfs_s_close ()
+ */
+ fh->changed = FALSE;
+ if (ftpfs_get_reply (me, ftp->sock, NULL, 0) != COMPLETE)
+ ERRNOR (EIO, -1);
+ vfs_s_invalidate (me, VFS_FILE_HANDLER_SUPER (fh));
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_done (struct vfs_class *me)
+{
+ (void) me;
+
+ g_slist_free_full (no_proxy, g_free);
+
+ g_free (ftpfs_anonymous_passwd);
+ g_free (ftpfs_proxy_host);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_fill_names (struct vfs_class *me, fill_names_f func)
+{
+ GList *iter;
+
+ for (iter = VFS_SUBCLASS (me)->supers; iter != NULL; iter = g_list_next (iter))
+ {
+ const struct vfs_s_super *super = (const struct vfs_s_super *) iter->data;
+ GString *name;
+
+ name = vfs_path_element_build_pretty_path_str (super->path_element);
+
+ func (name->str);
+ g_string_free (name, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static keyword_t
+ftpfs_netrc_next (void)
+{
+ char *p;
+ keyword_t i;
+ static const char *const keywords[] = { "default", "machine",
+ "login", "password", "passwd", "account", "macdef", NULL
+ };
+
+ while (TRUE)
+ {
+ netrcp = skip_separators (netrcp);
+ if (*netrcp != '\n')
+ break;
+ netrcp++;
+ }
+ if (*netrcp == '\0')
+ return NETRC_NONE;
+
+ p = buffer;
+ if (*netrcp == '"')
+ {
+ for (netrcp++; *netrcp != '"' && *netrcp != '\0'; netrcp++)
+ {
+ if (*netrcp == '\\')
+ netrcp++;
+ *p++ = *netrcp;
+ }
+ }
+ else
+ {
+ for (; *netrcp != '\0' && !whiteness (*netrcp) && *netrcp != ','; netrcp++)
+ {
+ if (*netrcp == '\\')
+ netrcp++;
+ *p++ = *netrcp;
+ }
+ }
+
+ *p = '\0';
+ if (*buffer == '\0')
+ return NETRC_NONE;
+
+ for (i = NETRC_DEFAULT; keywords[i - 1] != NULL; i++)
+ if (strcmp (keywords[i - 1], buffer) == 0)
+ return i;
+
+ return NETRC_UNKNOWN;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+ftpfs_netrc_bad_mode (const char *netrcname)
+{
+ struct stat mystat;
+
+ if (stat (netrcname, &mystat) >= 0 && (mystat.st_mode & 077) != 0)
+ {
+ static gboolean be_angry = TRUE;
+
+ if (be_angry)
+ {
+ message (D_ERROR, MSG_ERROR,
+ _("~/.netrc file has incorrect mode\nRemove password or correct mode"));
+ be_angry = FALSE;
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Scan .netrc until we find matching "machine" or "default"
+ * domain is used for additional matching
+ * No search is done after "default" in compliance with "man netrc"
+ * Return TRUE if found, FALSE otherwise */
+
+static gboolean
+ftpfs_find_machine (const char *host, const char *domain)
+{
+ keyword_t keyword;
+
+ if (host == NULL)
+ host = "";
+ if (domain == NULL)
+ domain = "";
+
+ while ((keyword = ftpfs_netrc_next ()) != NETRC_NONE)
+ {
+ if (keyword == NETRC_DEFAULT)
+ return TRUE;
+
+ if (keyword == NETRC_MACDEF)
+ {
+ /* Scan for an empty line, which concludes "macdef" */
+ do
+ {
+ while (*netrcp != '\0' && *netrcp != '\n')
+ netrcp++;
+ if (*netrcp != '\n')
+ break;
+ netrcp++;
+ }
+ while (*netrcp != '\0' && *netrcp != '\n');
+
+ continue;
+ }
+
+ if (keyword != NETRC_MACHINE)
+ continue;
+
+ /* Take machine name */
+ if (ftpfs_netrc_next () == NETRC_NONE)
+ break;
+
+ if (g_ascii_strcasecmp (host, buffer) != 0)
+ {
+ const char *host_domain;
+
+ /* Try adding our domain to short names in .netrc */
+ host_domain = strchr (host, '.');
+ if (host_domain == NULL)
+ continue;
+
+ /* Compare domain part */
+ if (g_ascii_strcasecmp (host_domain, domain) != 0)
+ continue;
+
+ /* Compare local part */
+ if (g_ascii_strncasecmp (host, buffer, host_domain - host) != 0)
+ continue;
+ }
+
+ return TRUE;
+ }
+
+ /* end of .netrc */
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Extract login and password from .netrc for the host.
+ * pass may be NULL.
+ * Returns TRUE for success, FALSE for error */
+
+static gboolean
+ftpfs_netrc_lookup (const char *host, char **login, char **pass)
+{
+ char *netrcname;
+ char *tmp_pass = NULL;
+ char hostname[MAXHOSTNAMELEN];
+ const char *domain;
+ static struct rupcache
+ {
+ struct rupcache *next;
+ char *host;
+ char *login;
+ char *pass;
+ } *rup_cache = NULL, *rupp;
+
+ /* Initialize *login and *pass */
+ MC_PTR_FREE (*login);
+ MC_PTR_FREE (*pass);
+
+ /* Look up in the cache first */
+ for (rupp = rup_cache; rupp != NULL; rupp = rupp->next)
+ if (strcmp (host, rupp->host) == 0)
+ {
+ *login = g_strdup (rupp->login);
+ *pass = g_strdup (rupp->pass);
+ return TRUE;
+ }
+
+ /* Load current .netrc */
+ netrcname = g_build_filename (mc_config_get_home_dir (), ".netrc", (char *) NULL);
+ if (!g_file_get_contents (netrcname, &netrc, NULL, NULL))
+ {
+ g_free (netrcname);
+ return TRUE;
+ }
+
+ netrcp = netrc;
+
+ /* Find our own domain name */
+ if (gethostname (hostname, sizeof (hostname)) < 0)
+ *hostname = '\0';
+
+ domain = strchr (hostname, '.');
+ if (domain == NULL)
+ domain = "";
+
+ /* Scan for "default" and matching "machine" keywords */
+ ftpfs_find_machine (host, domain);
+
+ /* Scan for keywords following "default" and "machine" */
+ while (TRUE)
+ {
+ keyword_t keyword;
+
+ gboolean need_break = FALSE;
+ keyword = ftpfs_netrc_next ();
+
+ switch (keyword)
+ {
+ case NETRC_LOGIN:
+ if (ftpfs_netrc_next () == NETRC_NONE)
+ {
+ need_break = TRUE;
+ break;
+ }
+
+ /* We have another name already - should not happen */
+ if (*login != NULL)
+ {
+ need_break = TRUE;
+ break;
+ }
+
+ /* We have login name now */
+ *login = g_strdup (buffer);
+ break;
+
+ case NETRC_PASSWORD:
+ case NETRC_PASSWD:
+ if (ftpfs_netrc_next () == NETRC_NONE)
+ {
+ need_break = TRUE;
+ break;
+ }
+
+ /* Ignore unsafe passwords */
+ if (*login != NULL &&
+ strcmp (*login, "anonymous") != 0 && strcmp (*login, "ftp") != 0
+ && ftpfs_netrc_bad_mode (netrcname))
+ {
+ need_break = TRUE;
+ break;
+ }
+
+ /* Remember password. pass may be NULL, so use tmp_pass */
+ if (tmp_pass == NULL)
+ tmp_pass = g_strdup (buffer);
+ break;
+
+ case NETRC_ACCOUNT:
+ /* "account" is followed by a token which we ignore */
+ if (ftpfs_netrc_next () == NETRC_NONE)
+ {
+ need_break = TRUE;
+ break;
+ }
+
+ /* Ignore account, but warn user anyways */
+ ftpfs_netrc_bad_mode (netrcname);
+ break;
+
+ default:
+ /* Unexpected keyword or end of file */
+ need_break = TRUE;
+ break;
+ }
+
+ if (need_break)
+ break;
+ }
+
+ MC_PTR_FREE (netrc);
+ g_free (netrcname);
+
+ rupp = g_new (struct rupcache, 1);
+ rupp->host = g_strdup (host);
+ rupp->login = g_strdup (*login);
+ rupp->pass = g_strdup (tmp_pass);
+
+ rupp->next = rup_cache;
+ rup_cache = rupp;
+
+ *pass = tmp_pass;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** This routine is called as the last step in load_setup */
+void
+ftpfs_init_passwd (void)
+{
+ ftpfs_anonymous_passwd = load_anon_passwd ();
+
+ if (ftpfs_anonymous_passwd == NULL)
+ {
+ /* If there is no anonymous ftp password specified
+ * then we'll just use anonymous@
+ * We don't send any other thing because:
+ * - We want to remain anonymous
+ * - We want to stop SPAM
+ * - We don't want to let ftp sites to discriminate by the user,
+ * host or country.
+ */
+ ftpfs_anonymous_passwd = g_strdup ("anonymous@");
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_ftpfs (void)
+{
+ tcp_init ();
+
+ vfs_init_subclass (&ftpfs_subclass, "ftpfs", VFSF_NOLINKS | VFSF_REMOTE | VFSF_USETMP, "ftp");
+ vfs_ftpfs_ops->done = ftpfs_done;
+ vfs_ftpfs_ops->fill_names = ftpfs_fill_names;
+ vfs_ftpfs_ops->stat = ftpfs_stat;
+ vfs_ftpfs_ops->lstat = ftpfs_lstat;
+ vfs_ftpfs_ops->fstat = ftpfs_fstat;
+ vfs_ftpfs_ops->chmod = ftpfs_chmod;
+ vfs_ftpfs_ops->chown = ftpfs_chown;
+ vfs_ftpfs_ops->unlink = ftpfs_unlink;
+ vfs_ftpfs_ops->rename = ftpfs_rename;
+ vfs_ftpfs_ops->mkdir = ftpfs_mkdir;
+ vfs_ftpfs_ops->rmdir = ftpfs_rmdir;
+ vfs_ftpfs_ops->ctl = ftpfs_ctl;
+ ftpfs_subclass.archive_same = ftpfs_archive_same;
+ ftpfs_subclass.new_archive = ftpfs_new_archive;
+ ftpfs_subclass.open_archive = ftpfs_open_archive;
+ ftpfs_subclass.free_archive = ftpfs_free_archive;
+ ftpfs_subclass.fh_new = ftpfs_fh_new;
+ ftpfs_subclass.fh_open = ftpfs_fh_open;
+ ftpfs_subclass.fh_close = ftpfs_fh_close;
+ ftpfs_subclass.dir_load = ftpfs_dir_load;
+ ftpfs_subclass.file_store = ftpfs_file_store;
+ ftpfs_subclass.linear_start = ftpfs_linear_start;
+ ftpfs_subclass.linear_read = ftpfs_linear_read;
+ ftpfs_subclass.linear_close = ftpfs_linear_close;
+ vfs_register_class (vfs_ftpfs_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/ftpfs/ftpfs.h b/src/vfs/ftpfs/ftpfs.h
new file mode 100644
index 0000000..d00c0b5
--- /dev/null
+++ b/src/vfs/ftpfs/ftpfs.h
@@ -0,0 +1,46 @@
+/**
+ * \file
+ * \brief Header: Virtual File System: FTP file system
+ */
+
+#ifndef MC__VFS_FTPFS_H
+#define MC__VFS_FTPFS_H
+
+#include "lib/vfs/xdirentry.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define FTP_INET 1
+#define FTP_INET6 2
+
+#define OPT_FLUSH 1
+#define OPT_IGNORE_ERROR 2
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern gboolean ftpfs_use_netrc;
+extern char *ftpfs_anonymous_passwd;
+extern char *ftpfs_proxy_host;
+extern int ftpfs_directory_timeout;
+extern gboolean ftpfs_always_use_proxy;
+extern gboolean ftpfs_ignore_chattr_errors;
+
+extern int ftpfs_retry_seconds;
+extern gboolean ftpfs_use_passive_connections;
+extern gboolean ftpfs_use_passive_connections_over_proxy;
+extern gboolean ftpfs_use_unix_list_options;
+extern gboolean ftpfs_first_cd_then_ls;
+
+/*** declarations of public functions ************************************************************/
+
+void ftpfs_init_passwd (void);
+void vfs_init_ftpfs (void);
+GSList *ftpfs_parse_long_list (struct vfs_class *me, struct vfs_s_inode *dir, GSList * buf,
+ int *err_ret);
+
+/*** inline functions ****************************************************************************/
+#endif
diff --git a/src/vfs/ftpfs/ftpfs_parse_ls.c b/src/vfs/ftpfs/ftpfs_parse_ls.c
new file mode 100644
index 0000000..5db79e0
--- /dev/null
+++ b/src/vfs/ftpfs/ftpfs_parse_ls.c
@@ -0,0 +1,1236 @@
+/*
+ Virtual File System: FTP file system
+
+ Copyright (C) 2015-2023
+ The Free Software Foundation, Inc.
+
+ Written by: Andrew Borodin <aborodin@vmail.ru>, 2013
+
+ 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
+ * \brief Source: Virtual File System: FTP file system
+ * \author Andrew Borodin
+ * \date 2015
+ *
+ * Parser of ftp long file list (reply to "LIST -la" command).
+ * Borrowed from lftp project (http://http://lftp.yar.ru/).
+ * Author of original lftp code: Alexander V. Lukyanov (lav@yars.free.net)
+ */
+
+#include <config.h>
+
+#include <ctype.h> /* isdigit() */
+#include <stdio.h> /* sscanf() */
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h> /* mode_t */
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+
+#include "ftpfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define number_of_parsers 7
+
+#define NO_SIZE ((off_t) (-1L))
+#define NO_DATE ((time_t) (-1L))
+
+#define FIRST_TOKEN strtok (line, " \t")
+#define NEXT_TOKEN strtok (NULL, " \t")
+#define FIRST_TOKEN_R strtok_r (line, " \t", &next)
+#define NEXT_TOKEN_R strtok_r (NULL, " \t", &next)
+
+#define ERR2 do { (*err)++; return FALSE; } while (FALSE)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef enum
+{
+ UNKNOWN = 0,
+ DIRECTORY,
+ SYMLINK,
+ NORMAL
+} filetype;
+
+typedef gboolean (*ftpfs_line_parser) (char *line, struct stat * s, char **filename,
+ char **linkname, int *err);
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static gboolean ftpfs_parse_long_list_UNIX (char *line, struct stat *s, char **filename,
+ char **linkname, int *err);
+static gboolean ftpfs_parse_long_list_NT (char *line, struct stat *s, char **filename,
+ char **linkname, int *err);
+static gboolean ftpfs_parse_long_list_EPLF (char *line, struct stat *s, char **filename,
+ char **linkname, int *err);
+static gboolean ftpfs_parse_long_list_MLSD (char *line, struct stat *s, char **filename,
+ char **linkname, int *err);
+static gboolean ftpfs_parse_long_list_AS400 (char *line, struct stat *s, char **filename,
+ char **linkname, int *err);
+static gboolean ftpfs_parse_long_list_OS2 (char *line, struct stat *s, char **filename,
+ char **linkname, int *err);
+static gboolean ftpfs_parse_long_list_MacWebStar (char *line, struct stat *s, char **filename,
+ char **linkname, int *err);
+
+/*** file scope variables ************************************************************************/
+
+static time_t rawnow;
+static struct tm now;
+
+static ftpfs_line_parser line_parsers[number_of_parsers] = {
+ ftpfs_parse_long_list_UNIX,
+ ftpfs_parse_long_list_NT,
+ ftpfs_parse_long_list_EPLF,
+ ftpfs_parse_long_list_MLSD,
+ ftpfs_parse_long_list_AS400,
+ ftpfs_parse_long_list_OS2,
+ ftpfs_parse_long_list_MacWebStar
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline uid_t
+ftpfs_get_uid (const char *s)
+{
+ uid_t u;
+
+ if (*s < '0' || *s > '9')
+ u = vfs_finduid (s);
+ else
+ u = (uid_t) atol (s);
+
+ return u;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gid_t
+ftpfs_get_gid (const char *s)
+{
+ gid_t g;
+
+ if (*s < '0' || *s > '9')
+ g = vfs_findgid (s);
+ else
+ g = (gid_t) atol (s);
+
+ return g;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ftpfs_init_time (void)
+{
+ time (&rawnow);
+ now = *localtime (&rawnow);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+guess_year (int month, int day, int hour, int minute)
+{
+ int year;
+
+ (void) hour;
+ (void) minute;
+
+ year = now.tm_year + 1900;
+
+ if (month * 32 + day > now.tm_mon * 32 + now.tm_mday + 6)
+ year--;
+
+ return year;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+parse_year_or_time (const char *year_or_time, int *year, int *hour, int *minute)
+{
+ if (year_or_time[2] == ':')
+ {
+ if (sscanf (year_or_time, "%2d:%2d", hour, minute) != 2)
+ return FALSE;
+
+ *year = -1;
+ }
+ else
+ {
+ if (sscanf (year_or_time, "%d", year) != 1)
+ return FALSE;
+
+ *hour = *minute = 0;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Converts struct tm to time_t, assuming the data in tm is UTC rather
+ than local timezone (mktime assumes the latter).
+
+ Contributed by Roger Beeman <beeman@cisco.com>, with the help of
+ Mark Baushke <mdb@cisco.com> and the rest of the Gurus at CISCO. */
+static time_t
+mktime_from_utc (const struct tm *t)
+{
+ struct tm tc;
+ time_t tl, tb;
+
+ memcpy (&tc, t, sizeof (struct tm));
+
+ /* UTC times are never DST; if we say -1, we'll introduce odd localtime-
+ * dependent errors. */
+
+ tc.tm_isdst = 0;
+
+ tl = mktime (&tc);
+ if (tl == -1)
+ return (-1);
+
+ tb = mktime (gmtime (&tl));
+
+ return (tl <= tb ? (tl + (tl - tb)) : (tl - (tb - tl)));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static time_t
+ftpfs_convert_date (const char *s)
+{
+ struct tm tm;
+ int year, month, day, hour, minute, second;
+ int skip = 0;
+ int n;
+
+ memset (&tm, 0, sizeof (tm));
+
+ n = sscanf (s, "%4d%n", &year, &skip);
+
+ /* try to workaround server's y2k bug *
+ * I hope in the next 300 years the y2k bug will be finally fixed :) */
+ if (n == 1 && year >= 1910 && year <= 1930)
+ {
+ n = sscanf (s, "%5d%n", &year, &skip);
+ year = year - 19100 + 2000;
+ }
+
+ if (n != 1)
+ return NO_DATE;
+
+ n = sscanf (s + skip, "%2d%2d%2d%2d%2d", &month, &day, &hour, &minute, &second);
+
+ if (n != 5)
+ return NO_DATE;
+
+ tm.tm_year = year - 1900;
+ tm.tm_mon = month - 1;
+ tm.tm_mday = day;
+ tm.tm_hour = hour;
+ tm.tm_min = minute;
+ tm.tm_sec = second;
+
+ return mktime_from_utc (&tm);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ -rwxr-xr-x 1 lav root 4771 Sep 12 1996 install-sh
+ -rw-r--r-- 1 lav root 1349 Feb 2 14:10 lftp.lsm
+ drwxr-xr-x 4 lav root 1024 Feb 22 15:32 lib
+ lrwxrwxrwx 1 lav root 33 Feb 14 17:45 ltconfig -> /usr/share/libtool/ltconfig
+
+ NOTE: group may be missing.
+ */
+
+static gboolean
+parse_ls_line (char *line, struct stat *s, char **filename, char **linkname)
+{
+ char *next = NULL;
+ char *t;
+ mode_t type, mode = 0;
+ char *group_or_size;
+ struct tm date;
+ const char *day_of_month;
+ gboolean year_anomaly = FALSE;
+ char *name;
+
+ /* parse perms */
+ t = FIRST_TOKEN_R;
+ if (t == NULL)
+ return FALSE;
+
+ if (!vfs_parse_filetype (t, NULL, &type))
+ return FALSE;
+
+ if (vfs_parse_fileperms (t + 1, NULL, &mode))
+ mode |= type;
+
+ s->st_mode = mode;
+
+ /* link count */
+ t = NEXT_TOKEN_R;
+ if (t == NULL)
+ return FALSE;
+ s->st_nlink = atol (t);
+
+ /* user */
+ t = NEXT_TOKEN_R;
+ if (t == NULL)
+ return FALSE;
+
+ s->st_uid = ftpfs_get_uid (t);
+
+ /* group or size */
+ group_or_size = NEXT_TOKEN_R;
+
+ /* size or month */
+ t = NEXT_TOKEN_R;
+ if (t == NULL)
+ return FALSE;
+ if (isdigit ((unsigned char) *t))
+ {
+ /* it's size, so the previous was group: */
+ long long size;
+ int n;
+
+ s->st_gid = ftpfs_get_gid (group_or_size);
+
+ if (sscanf (t, "%lld%n", &size, &n) == 1 && t[n] == '\0')
+ s->st_size = (off_t) size;
+ t = NEXT_TOKEN_R;
+ if (t == NULL)
+ return FALSE;
+ }
+ else
+ {
+ /* it was month, so the previous was size: */
+ long long size;
+ int n;
+
+ if (sscanf (group_or_size, "%lld%n", &size, &n) == 1 && group_or_size[n] == '\0')
+ s->st_size = (off_t) size;
+ }
+
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ s->st_blksize = 512;
+#endif
+#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
+ s->st_blocks = (s->st_size + 511) / 512;
+#endif
+
+ memset (&date, 0, sizeof (date));
+
+ if (!vfs_parse_month (t, &date))
+ date.tm_mon = 0;
+
+ day_of_month = NEXT_TOKEN_R;
+ if (day_of_month == NULL)
+ return FALSE;
+ date.tm_mday = atoi (day_of_month);
+
+ /* time or year */
+ t = NEXT_TOKEN_R;
+ if (t == NULL)
+ return FALSE;
+ date.tm_isdst = -1;
+ date.tm_hour = date.tm_min = 0;
+ date.tm_sec = 30;
+
+ if (sscanf (t, "%2d:%2d", &date.tm_hour, &date.tm_min) == 2)
+ date.tm_year = guess_year (date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min) - 1900;
+ else
+ {
+ if (day_of_month + strlen (day_of_month) + 1 == t)
+ year_anomaly = TRUE;
+ date.tm_year = atoi (t) - 1900;
+ /* We don't know the hour. Set it to something other than 0, or
+ * DST -1 will end up changing the date. */
+ date.tm_hour = 12;
+ date.tm_min = 0;
+ date.tm_sec = 0;
+ }
+
+ s->st_mtime = mktime (&date);
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+
+ name = strtok_r (NULL, "", &next);
+ if (name == NULL)
+ return FALSE;
+
+ /* there are ls which output extra space after year. */
+ if (year_anomaly && *name == ' ')
+ name++;
+
+ if (!S_ISLNK (s->st_mode))
+ *linkname = NULL;
+ else
+ {
+ char *arrow;
+
+ for (arrow = name; (arrow = strstr (arrow, " -> ")) != NULL; arrow++)
+ if (arrow != name && arrow[4] != '\0')
+ {
+ *arrow = '\0';
+ *linkname = g_strdup (arrow + 4);
+ break;
+ }
+ }
+
+ *filename = g_strdup (name);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+ftpfs_parse_long_list_UNIX (char *line, struct stat *s, char **filename, char **linkname, int *err)
+{
+ int tmp;
+ gboolean ret;
+
+ if (sscanf (line, "total %d", &tmp) == 1)
+ return FALSE;
+
+ if (strncasecmp (line, "Status of ", 10) == 0)
+ return FALSE; /* STAT output. */
+
+ ret = parse_ls_line (line, s, filename, linkname);
+ if (!ret)
+ (*err)++;
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ 07-13-98 09:06PM <DIR> aix
+ 07-13-98 09:06PM <DIR> hpux
+ 07-13-98 09:06PM <DIR> linux
+ 07-13-98 09:06PM <DIR> ncr
+ 07-13-98 09:06PM <DIR> solaris
+ 03-18-98 06:01AM 2109440 nlxb318e.tar
+ 07-02-98 11:17AM 13844 Whatsnew.txt
+ */
+
+static gboolean
+ftpfs_parse_long_list_NT (char *line, struct stat *s, char **filename, char **linkname, int *err)
+{
+ char *t;
+ int month, day, year, hour, minute;
+ char am;
+ struct tm tms;
+ long long size;
+
+ t = FIRST_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (sscanf (t, "%2d-%2d-%2d", &month, &day, &year) != 3)
+ ERR2;
+ if (year >= 70)
+ year += 1900;
+ else
+ year += 2000;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ am = 'A'; /* AM/PM is optional */
+ if (sscanf (t, "%2d:%2d%c", &hour, &minute, &am) < 2)
+ ERR2;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+
+ if (am == 'P') /* PM - after noon */
+ {
+ hour += 12;
+ if (hour == 24)
+ hour = 0;
+ }
+
+ tms.tm_sec = 30; /* seconds after the minute [0, 61] */
+ tms.tm_min = minute; /* minutes after the hour [0, 59] */
+ tms.tm_hour = hour; /* hour since midnight [0, 23] */
+ tms.tm_mday = day; /* day of the month [1, 31] */
+ tms.tm_mon = month - 1; /* months since January [0, 11] */
+ tms.tm_year = year - 1900; /* years since 1900 */
+ tms.tm_isdst = -1;
+
+
+ s->st_mtime = mktime (&tms);
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+
+ if (strcmp (t, "<DIR>") == 0)
+ s->st_mode = S_IFDIR;
+ else
+ {
+ s->st_mode = S_IFREG;
+ if (sscanf (t, "%lld", &size) != 1)
+ ERR2;
+ s->st_size = (off_t) size;
+ }
+
+ t = strtok (NULL, "");
+ if (t == NULL)
+ ERR2;
+ while (*t == ' ')
+ t++;
+ if (*t == '\0')
+ ERR2;
+
+ *filename = g_strdup (t);
+ *linkname = NULL;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ +i774.71425,m951188401,/, users
+ +i774.49602,m917883130,r,s79126, jgr_www2.exe
+
+ starts with +
+ comma separated
+ first character of field is type:
+ i - ?
+ m - modification time
+ / - means directory
+ r - means plain file
+ s - size
+ up - permissions in octal
+ \t - file name follows.
+ */
+
+static gboolean
+ftpfs_parse_long_list_EPLF (char *line, struct stat *s, char **filename, char **linkname, int *err)
+{
+ size_t len;
+ const char *b;
+ const char *name = NULL;
+ size_t name_len = 0;
+ off_t size = NO_SIZE;
+ time_t date = NO_DATE;
+ long date_l;
+ long long size_ll;
+ gboolean dir = FALSE;
+ gboolean type_known = FALSE;
+ int perms = -1;
+ const char *scan;
+ ssize_t scan_len;
+
+ len = strlen (line);
+ b = line;
+
+ if (len < 2 || b[0] != '+')
+ ERR2;
+
+ scan = b + 1;
+ scan_len = len - 1;
+
+ while (scan != NULL && scan_len > 0)
+ {
+ const char *comma;
+
+ switch (*scan)
+ {
+ case '\t': /* the rest is file name. */
+ name = scan + 1;
+ name_len = scan_len - 1;
+ scan = NULL;
+ break;
+ case 's':
+ if (sscanf (scan + 1, "%lld", &size_ll) != 1)
+ break;
+ size = size_ll;
+ break;
+ case 'm':
+ if (sscanf (scan + 1, "%ld", &date_l) != 1)
+ break;
+ date = date_l;
+ break;
+ case '/':
+ dir = TRUE;
+ type_known = TRUE;
+ break;
+ case 'r':
+ dir = FALSE;
+ type_known = TRUE;
+ break;
+ case 'i':
+ break;
+ case 'u':
+ if (scan[1] == 'p') /* permissions. */
+ if (sscanf (scan + 2, "%o", (unsigned int *) &perms) != 1)
+ perms = -1;
+ break;
+ default:
+ name = NULL;
+ scan = NULL;
+ break;
+ }
+ if (scan == NULL || scan_len == 0)
+ break;
+
+ comma = (const char *) memchr (scan, ',', scan_len);
+ if (comma == NULL)
+ break;
+
+ scan_len -= comma + 1 - scan;
+ scan = comma + 1;
+ }
+
+ if (name == NULL || !type_known)
+ ERR2;
+
+ *filename = g_strndup (name, name_len);
+ *linkname = NULL;
+
+ if (size != NO_SIZE)
+ s->st_size = size;
+ if (date != NO_DATE)
+ {
+ s->st_mtime = date;
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+ }
+ if (type_known)
+ s->st_mode = dir ? S_IFDIR : S_IFREG;
+ if (perms != -1)
+ s->st_mode |= perms; /* FIXME */
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ Type=cdir;Modify=20021029173810;Perm=el;Unique=BP8AAjJufAA; /
+ Type=pdir;Modify=20021029173810;Perm=el;Unique=BP8AAjJufAA; ..
+ Type=dir;Modify=20010118144705;Perm=e;Unique=BP8AAjNufAA; bin
+ Type=dir;Modify=19981021003019;Perm=el;Unique=BP8AAlhufAA; pub
+ Type=file;Size=12303;Modify=19970124132601;Perm=r;Unique=BP8AAo9ufAA; mailserv.FAQ
+ modify=20161215062118;perm=flcdmpe;type=dir;UNIX.group=503;UNIX.mode=0700; directory-name
+ modify=20161213121618;perm=adfrw;size=6369064;type=file;UNIX.group=503;UNIX.mode=0644; file-name
+ modify=20120103123744;perm=adfrw;size=11;type=OS.unix=symlink;UNIX.group=0;UNIX.mode=0777; www
+ */
+
+static gboolean
+ftpfs_parse_long_list_MLSD (char *line, struct stat *s, char **filename, char **linkname, int *err)
+{
+ const char *name = NULL;
+ off_t size = NO_SIZE;
+ time_t date = NO_DATE;
+ const char *owner = NULL;
+ const char *group = NULL;
+ filetype type = UNKNOWN;
+ int perms = -1;
+ char *space;
+ char *tok;
+
+ space = strstr (line, "; ");
+ if (space != NULL)
+ {
+ name = space + 2;
+ *space = '\0';
+ }
+ else
+ {
+ /* NcFTPd does not put a semicolon after last fact, workaround it. */
+ space = strchr (line, ' ');
+ if (space == NULL)
+ ERR2;
+ name = space + 1;
+ *space = '\0';
+ }
+
+ for (tok = strtok (line, ";"); tok != NULL; tok = strtok (NULL, ";"))
+ {
+ if (strcasecmp (tok, "Type=cdir") == 0
+ || strcasecmp (tok, "Type=pdir") == 0 || strcasecmp (tok, "Type=dir") == 0)
+ {
+ type = DIRECTORY;
+ continue;
+ }
+ if (strcasecmp (tok, "Type=file") == 0)
+ {
+ type = NORMAL;
+ continue;
+ }
+ if (strcasecmp (tok, "Type=OS.unix=symlink") == 0)
+ {
+ type = SYMLINK;
+ continue;
+ }
+ if (strncasecmp (tok, "Modify=", 7) == 0)
+ {
+ date = ftpfs_convert_date (tok + 7);
+ continue;
+ }
+ if (strncasecmp (tok, "Size=", 5) == 0)
+ {
+ long long size_ll;
+
+ if (sscanf (tok + 5, "%lld", &size_ll) == 1)
+ size = size_ll;
+ continue;
+ }
+ if (strncasecmp (tok, "Perm=", 5) == 0)
+ {
+ perms = 0;
+ for (tok += 5; *tok != '\0'; tok++)
+ {
+ switch (g_ascii_tolower (*tok))
+ {
+ case 'e':
+ perms |= 0111;
+ break;
+ case 'l':
+ perms |= 0444;
+ break;
+ case 'r':
+ perms |= 0444;
+ break;
+ case 'c':
+ perms |= 0200;
+ break;
+ case 'w':
+ perms |= 0200;
+ break;
+ default:
+ break;
+ }
+ }
+ continue;
+ }
+ if (strncasecmp (tok, "UNIX.mode=", 10) == 0)
+ {
+ if (sscanf (tok + 10, "%o", (unsigned int *) &perms) != 1)
+ perms = -1;
+ continue;
+ }
+ if (strncasecmp (tok, "UNIX.owner=", 11) == 0)
+ {
+ owner = tok + 11;
+ continue;
+ }
+ if (strncasecmp (tok, "UNIX.group=", 11) == 0)
+ {
+ group = tok + 11;
+ continue;
+ }
+ if (strncasecmp (tok, "UNIX.uid=", 9) == 0)
+ {
+ if (owner == NULL)
+ owner = tok + 9;
+ continue;
+ }
+ if (strncasecmp (tok, "UNIX.gid=", 9) == 0)
+ {
+ if (group == NULL)
+ group = tok + 9;
+ continue;
+ }
+ }
+ if (name == NULL || name[0] == '\0' || type == UNKNOWN)
+ ERR2;
+
+ *filename = g_strdup (name);
+ *linkname = NULL;
+
+ if (size != NO_SIZE)
+ s->st_size = size;
+ if (date != NO_DATE)
+ {
+ s->st_mtime = date;
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+ }
+ switch (type)
+ {
+ case DIRECTORY:
+ s->st_mode = S_IFDIR;
+ break;
+ case SYMLINK:
+ s->st_mode = S_IFLNK;
+ break;
+ case NORMAL:
+ s->st_mode = S_IFREG;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ if (perms != -1)
+ s->st_mode |= perms; /* FIXME */
+ if (owner != NULL)
+ s->st_uid = ftpfs_get_uid (owner);
+ if (group != NULL)
+ s->st_gid = ftpfs_get_gid (group);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ ASUSER 8192 04/26/05 13:54:16 *DIR dir/
+ ASUSER 8192 04/26/05 13:57:34 *DIR dir1/
+ ASUSER 365255 02/28/01 15:41:40 *STMF readme.txt
+ ASUSER 8489625 03/18/03 09:37:00 *STMF saved.zip
+ ASUSER 365255 02/28/01 15:41:40 *STMF unist.old
+ */
+
+static gboolean
+ftpfs_parse_long_list_AS400 (char *line, struct stat *s, char **filename, char **linkname, int *err)
+{
+ char *t;
+ char *user;
+ long long size;
+ int month, day, year, hour, minute, second;
+ struct tm tms;
+ time_t mtime;
+ mode_t type;
+ char *slash;
+
+ t = FIRST_TOKEN;
+ if (t == NULL)
+ ERR2;
+ user = t;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (sscanf (t, "%lld", &size) != 1)
+ ERR2;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (sscanf (t, "%2d/%2d/%2d", &month, &day, &year) != 3)
+ ERR2;
+ if (year >= 70)
+ year += 1900;
+ else
+ year += 2000;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (sscanf (t, "%2d:%2d:%2d", &hour, &minute, &second) != 3)
+ ERR2;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+
+ tms.tm_sec = second; /* seconds after the minute [0, 61] */
+ tms.tm_min = minute; /* minutes after the hour [0, 59] */
+ tms.tm_hour = hour; /* hour since midnight [0, 23] */
+ tms.tm_mday = day; /* day of the month [1, 31] */
+ tms.tm_mon = month - 1; /* months since January [0, 11] */
+ tms.tm_year = year - 1900; /* years since 1900 */
+ tms.tm_isdst = -1;
+ mtime = mktime (&tms);
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (strcmp (t, "*DIR") == 0)
+ type = S_IFDIR;
+ else
+ type = S_IFREG;
+
+ t = strtok (NULL, "");
+ if (t == NULL)
+ ERR2;
+ while (*t == ' ')
+ t++;
+ if (*t == '\0')
+ ERR2;
+
+ *linkname = NULL;
+
+ slash = strchr (t, '/');
+ if (slash != NULL)
+ {
+ if (slash == t)
+ return FALSE;
+
+ *slash = '\0';
+ type = S_IFDIR;
+ if (slash[1] != '\0')
+ {
+ *filename = g_strdup (t);
+ s->st_mode = type; /* FIXME */
+ return TRUE;
+ }
+ }
+
+ *filename = g_strdup (t);
+ s->st_mode = type;
+ s->st_size = (off_t) size;
+ s->st_mtime = mtime;
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+ s->st_uid = ftpfs_get_uid (user);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ 0 DIR 06-27-96 11:57 PROTOCOL
+ 169 11-29-94 09:20 SYSLEVEL.MPT
+ */
+
+static gboolean
+ftpfs_parse_long_list_OS2 (char *line, struct stat *s, char **filename, char **linkname, int *err)
+{
+ char *t;
+ long long size;
+ int month, day, year, hour, minute;
+ struct tm tms;
+
+ t = FIRST_TOKEN;
+ if (t == NULL)
+ ERR2;
+
+ if (sscanf (t, "%lld", &size) != 1)
+ ERR2;
+ s->st_size = (off_t) size;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ s->st_mode = S_IFREG;
+ if (strcmp (t, "DIR") == 0)
+ {
+ s->st_mode = S_IFDIR;
+ t = NEXT_TOKEN;
+
+ if (t == NULL)
+ ERR2;
+ }
+
+ if (sscanf (t, "%2d-%2d-%2d", &month, &day, &year) != 3)
+ ERR2;
+ if (year >= 70)
+ year += 1900;
+ else
+ year += 2000;
+
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (sscanf (t, "%2d:%2d", &hour, &minute) != 3)
+ ERR2;
+
+ tms.tm_sec = 30; /* seconds after the minute [0, 61] */
+ tms.tm_min = minute; /* minutes after the hour [0, 59] */
+ tms.tm_hour = hour; /* hour since midnight [0, 23] */
+ tms.tm_mday = day; /* day of the month [1, 31] */
+ tms.tm_mon = month - 1; /* months since January [0, 11] */
+ tms.tm_year = year - 1900; /* years since 1900 */
+ tms.tm_isdst = -1;
+ s->st_mtime = mktime (&tms);
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+
+ t = strtok (NULL, "");
+ if (t == NULL)
+ ERR2;
+ while (*t == ' ')
+ t++;
+ if (*t == '\0')
+ ERR2;
+ *filename = g_strdup (t);
+ *linkname = NULL;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+ftpfs_parse_long_list_MacWebStar (char *line, struct stat *s, char **filename,
+ char **linkname, int *err)
+{
+ char *t;
+ mode_t type, mode;
+ struct tm date;
+ const char *day_of_month;
+ char *name;
+
+ t = FIRST_TOKEN;
+ if (t == NULL)
+ ERR2;
+
+ if (!vfs_parse_filetype (t, NULL, &type))
+ ERR2;
+
+ s->st_mode = type;
+
+ if (!vfs_parse_fileperms (t + 1, NULL, &mode))
+ ERR2;
+ /* permissions are meaningless here. */
+
+ /* "folder" or 0 */
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+
+ if (strcmp (t, "folder") != 0)
+ {
+ long long size;
+
+ /* size? */
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ /* size */
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (!isdigit ((unsigned char) *t))
+ ERR2;
+
+ if (sscanf (t, "%lld", &size) == 1)
+ s->st_size = (off_t) size;
+ }
+ else
+ {
+ /* ?? */
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ }
+
+ /* month */
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+
+ memset (&date, 0, sizeof (date));
+
+ if (!vfs_parse_month (t, &date))
+ ERR2;
+
+ day_of_month = NEXT_TOKEN;
+ if (day_of_month == NULL)
+ ERR2;
+
+ date.tm_mday = atoi (day_of_month);
+
+ /* time or year */
+ t = NEXT_TOKEN;
+ if (t == NULL)
+ ERR2;
+ if (!parse_year_or_time (t, &date.tm_year, &date.tm_hour, &date.tm_min))
+ ERR2;
+
+ date.tm_isdst = -1;
+ date.tm_sec = 30;
+ if (date.tm_year == -1)
+ date.tm_year = guess_year (date.tm_mon, date.tm_mday, date.tm_hour, date.tm_min) - 1900;
+ else
+ date.tm_hour = 12;
+
+ s->st_mtime = mktime (&date);
+ /* Use resulting time value */
+ s->st_atime = s->st_ctime = s->st_mtime;
+
+ name = strtok (NULL, "");
+ if (name == NULL)
+ ERR2;
+
+ /* no symlinks on Mac, but anyway. */
+ if (!S_ISLNK (s->st_mode))
+ *linkname = NULL;
+ else
+ {
+ char *arrow;
+
+ for (arrow = name; (arrow = strstr (arrow, " -> ")) != NULL; arrow++)
+ if (arrow != name && arrow[4] != '\0')
+ {
+ *arrow = '\0';
+ *linkname = g_strdup (arrow + 4);
+ break;
+ }
+ }
+
+ *filename = g_strdup (name);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+GSList *
+ftpfs_parse_long_list (struct vfs_class * me, struct vfs_s_inode * dir, GSList * buf, int *err_ret)
+{
+ int err[number_of_parsers];
+ GSList *set[number_of_parsers]; /* arrays of struct vfs_s_entry */
+ size_t i;
+ GSList *bufp;
+ ftpfs_line_parser guessed_parser = NULL;
+ GSList **the_set = NULL;
+ int *the_err = NULL;
+ int *best_err1 = &err[0];
+ int *best_err2 = &err[1];
+
+ ftpfs_init_time ();
+
+ if (err_ret != NULL)
+ *err_ret = 0;
+
+ memset (&err, 0, sizeof (err));
+ memset (&set, 0, sizeof (set));
+
+ for (bufp = buf; bufp != NULL; bufp = g_slist_next (bufp))
+ {
+ char *b = (char *) bufp->data;
+ size_t blen;
+
+ blen = strlen (b);
+
+ if (b[blen - 1] == '\r')
+ {
+ b[blen - 1] = '\0';
+ blen--;
+ }
+
+ if (blen == 0)
+ continue;
+
+ if (guessed_parser == NULL)
+ {
+ for (i = 0; i < number_of_parsers; i++)
+ {
+ struct vfs_s_entry *info;
+ gboolean ok;
+ char *tmp_line;
+ int nlink;
+
+ /* parser can clobber the line - work on a copy */
+ tmp_line = g_strndup (b, blen);
+
+ info = vfs_s_generate_entry (me, NULL, dir, 0);
+ nlink = info->ino->st.st_nlink;
+ ok = (*line_parsers[i]) (tmp_line, &info->ino->st, &info->name,
+ &info->ino->linkname, &err[i]);
+ if (ok && strchr (info->name, '/') == NULL)
+ {
+ info->ino->st.st_nlink = nlink; /* Ouch, we need to preserve our counts :-( */
+ set[i] = g_slist_prepend (set[i], info);
+ }
+ else
+ vfs_s_free_entry (me, info);
+
+ g_free (tmp_line);
+
+ if (*best_err1 > err[i])
+ best_err1 = &err[i];
+ if (*best_err2 > err[i] && best_err1 != &err[i])
+ best_err2 = &err[i];
+
+ if (*best_err1 > 16)
+ goto leave; /* too many errors with best parser. */
+ }
+
+ if (*best_err2 > (*best_err1 + 1) * 16)
+ {
+ i = (size_t) (best_err1 - err);
+ guessed_parser = line_parsers[i];
+ the_set = &set[i];
+ the_err = &err[i];
+ }
+ }
+ else
+ {
+ struct vfs_s_entry *info;
+ gboolean ok;
+ char *tmp_line;
+ int nlink;
+
+ /* parser can clobber the line - work on a copy */
+ tmp_line = g_strndup (b, blen);
+
+ info = vfs_s_generate_entry (me, NULL, dir, 0);
+ nlink = info->ino->st.st_nlink;
+ ok = guessed_parser (tmp_line, &info->ino->st, &info->name, &info->ino->linkname,
+ the_err);
+ if (ok && strchr (info->name, '/') == NULL)
+ {
+ info->ino->st.st_nlink = nlink; /* Ouch, we need to preserve our counts :-( */
+ *the_set = g_slist_prepend (*the_set, info);
+ }
+ else
+ vfs_s_free_entry (me, info);
+
+ g_free (tmp_line);
+ }
+ }
+
+ if (the_set == NULL)
+ {
+ i = best_err1 - err;
+ the_set = &set[i];
+ the_err = &err[i];
+ }
+
+ leave:
+ for (i = 0; i < number_of_parsers; i++)
+ if (&set[i] != the_set)
+ {
+ for (bufp = set[i]; bufp != NULL; bufp = g_slist_next (bufp))
+ vfs_s_free_entry (me, VFS_ENTRY (bufp->data));
+
+ g_slist_free (set[i]);
+ }
+
+ if (err_ret != NULL && the_err != NULL)
+ *err_ret = *the_err;
+
+ return the_set != NULL ? g_slist_reverse (*the_set) : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/local/Makefile.am b/src/vfs/local/Makefile.am
new file mode 100644
index 0000000..0176d46
--- /dev/null
+++ b/src/vfs/local/Makefile.am
@@ -0,0 +1,7 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-local.la
+
+libvfs_local_la_SOURCES = \
+ local.c local.h
diff --git a/src/vfs/local/Makefile.in b/src/vfs/local/Makefile.in
new file mode 100644
index 0000000..6d79948
--- /dev/null
+++ b/src/vfs/local/Makefile.in
@@ -0,0 +1,735 @@
+# 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/vfs/local
+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)
+libvfs_local_la_LIBADD =
+am_libvfs_local_la_OBJECTS = local.lo
+libvfs_local_la_OBJECTS = $(am_libvfs_local_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)/local.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 = $(libvfs_local_la_SOURCES)
+DIST_SOURCES = $(libvfs_local_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@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+noinst_LTLIBRARIES = libvfs-local.la
+libvfs_local_la_SOURCES = \
+ local.c local.h
+
+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/vfs/local/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/local/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}; \
+ }
+
+libvfs-local.la: $(libvfs_local_la_OBJECTS) $(libvfs_local_la_DEPENDENCIES) $(EXTRA_libvfs_local_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_local_la_OBJECTS) $(libvfs_local_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/local.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)/local.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)/local.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/vfs/local/local.c b/src/vfs/local/local.c
new file mode 100644
index 0000000..a777c84
--- /dev/null
+++ b/src/vfs/local/local.c
@@ -0,0 +1,523 @@
+/*
+ Virtual File System: local file system.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ 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
+ * \brief Source: local FS
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef ENABLE_EXT2FS_ATTR
+#include <e2p/e2p.h> /* fgetflags(), fsetflags() */
+#endif
+
+#include "lib/global.h"
+
+#include "lib/vfs/xdirentry.h" /* vfs_s_subclass */
+#include "lib/vfs/utilvfs.h"
+
+#include "local.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct vfs_s_subclass local_subclass;
+static struct vfs_class *vfs_local_ops = VFS_CLASS (&local_subclass);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+local_open (const vfs_path_t * vpath, int flags, mode_t mode)
+{
+ int *local_info;
+ int fd;
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ fd = open (path, NO_LINEAR (flags), mode);
+ if (fd == -1)
+ return 0;
+
+ local_info = g_new (int, 1);
+ *local_info = fd;
+
+ return local_info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+local_opendir (const vfs_path_t * vpath)
+{
+ DIR **local_info;
+ DIR *dir = NULL;
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+
+ /* On Linux >= 5.1, MC sometimes shows empty directories on mounted CIFS shares.
+ * Rereading directory restores the directory content.
+ *
+ * Reopen directory, if first readdir() returns NULL and errno == EINTR.
+ */
+ while (dir == NULL)
+ {
+ dir = opendir (path);
+ if (dir == NULL)
+ return NULL;
+
+ if (readdir (dir) == NULL && errno == EINTR)
+ {
+ closedir (dir);
+ dir = NULL;
+ }
+ else
+ rewinddir (dir);
+ }
+
+ local_info = (DIR **) g_new (DIR *, 1);
+ *local_info = dir;
+
+ return local_info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_dirent *
+local_readdir (void *data)
+{
+ struct dirent *d;
+
+ d = readdir (*(DIR **) data);
+
+ return (d != NULL ? vfs_dirent_init (NULL, d->d_name, d->d_ino) : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_closedir (void *data)
+{
+ int i;
+
+ i = closedir (*(DIR **) data);
+ g_free (data);
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return stat (path, buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+#ifndef HAVE_STATLSTAT
+ return lstat (path, buf);
+#else
+ return statlstat (path, buf);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_chmod (const vfs_path_t * vpath, mode_t mode)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return chmod (path, mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_chown (const vfs_path_t * vpath, uid_t owner, gid_t group)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return chown (path, owner, group);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_EXT2FS_ATTR
+
+static int
+local_fgetflags (const vfs_path_t * vpath, unsigned long *flags)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return fgetflags (path, flags);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_fsetflags (const vfs_path_t * vpath, unsigned long flags)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return fsetflags (path, flags);
+}
+
+#endif /* ENABLE_EXT2FS_ATTR */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_utime (const vfs_path_t * vpath, mc_timesbuf_t * times)
+{
+ int ret;
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+#ifdef HAVE_UTIMENSAT
+ ret = utimensat (AT_FDCWD, path, *times, AT_SYMLINK_NOFOLLOW);
+#else
+ ret = utime (path, times);
+#endif
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_readlink (const vfs_path_t * vpath, char *buf, size_t size)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return readlink (path, buf, size);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_unlink (const vfs_path_t * vpath)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return unlink (path);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ const char *path1, *path2;
+
+ path1 = vfs_path_get_last_path_str (vpath1);
+ path2 = vfs_path_get_last_path_str (vpath2);
+ return symlink (path1, path2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+local_write (void *data, const char *buf, size_t nbyte)
+{
+ int fd;
+ int n;
+
+ if (data == NULL)
+ return (-1);
+
+ fd = *(int *) data;
+
+ while ((n = write (fd, buf, nbyte)) == -1)
+ {
+#ifdef EAGAIN
+ if (errno == EAGAIN)
+ continue;
+#endif
+#ifdef EINTR
+ if (errno == EINTR)
+ continue;
+#endif
+ break;
+ }
+
+ return n;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ const char *path1, *path2;
+
+ path1 = vfs_path_get_last_path_str (vpath1);
+ path2 = vfs_path_get_last_path_str (vpath2);
+ return rename (path1, path2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_chdir (const vfs_path_t * vpath)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return chdir (path);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_mknod (const vfs_path_t * vpath, mode_t mode, dev_t dev)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return mknod (path, mode, dev);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_link (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ const char *path1, *path2;
+
+ path1 = vfs_path_get_last_path_str (vpath1);
+ path2 = vfs_path_get_last_path_str (vpath2);
+ return link (path1, path2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_mkdir (const vfs_path_t * vpath, mode_t mode)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return mkdir (path, mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_rmdir (const vfs_path_t * vpath)
+{
+ const char *path;
+
+ path = vfs_path_get_last_path_str (vpath);
+ return rmdir (path);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+local_getlocalcopy (const vfs_path_t * vpath)
+{
+ return vfs_path_clone (vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_ungetlocalcopy (const vfs_path_t * vpath, const vfs_path_t * local, gboolean has_changed)
+{
+ (void) vpath;
+ (void) local;
+ (void) has_changed;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+local_which (struct vfs_class *me, const char *path)
+{
+ (void) me;
+ (void) path;
+
+ return 0; /* Every path which other systems do not like is expected to be ours */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+ssize_t
+local_read (void *data, char *buffer, size_t count)
+{
+ int n;
+ int fd;
+
+ if (data == NULL)
+ return (-1);
+
+ fd = *(int *) data;
+
+ while ((n = read (fd, buffer, count)) == -1)
+ {
+#ifdef EAGAIN
+ if (errno == EAGAIN)
+ continue;
+#endif
+#ifdef EINTR
+ if (errno == EINTR)
+ continue;
+#endif
+ return (-1);
+ }
+
+ return n;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+local_close (void *data)
+{
+ int fd;
+
+ if (data == NULL)
+ return (-1);
+
+ fd = *(int *) data;
+ g_free (data);
+ return close (fd);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+local_errno (struct vfs_class *me)
+{
+ (void) me;
+ return errno;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+local_fstat (void *data, struct stat *buf)
+{
+ int fd = *(int *) data;
+
+ return fstat (fd, buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+off_t
+local_lseek (void *data, off_t offset, int whence)
+{
+ int fd = *(int *) data;
+
+ return lseek (fd, offset, whence);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+local_nothingisopen (vfsid id)
+{
+ (void) id;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_localfs (void)
+{
+ /* NULLize vfs_s_subclass members */
+ memset (&local_subclass, 0, sizeof (local_subclass));
+
+ vfs_init_class (vfs_local_ops, "localfs", VFSF_LOCAL, NULL);
+ vfs_local_ops->which = local_which;
+ vfs_local_ops->open = local_open;
+ vfs_local_ops->close = local_close;
+ vfs_local_ops->read = local_read;
+ vfs_local_ops->write = local_write;
+ vfs_local_ops->opendir = local_opendir;
+ vfs_local_ops->readdir = local_readdir;
+ vfs_local_ops->closedir = local_closedir;
+ vfs_local_ops->stat = local_stat;
+ vfs_local_ops->lstat = local_lstat;
+ vfs_local_ops->fstat = local_fstat;
+ vfs_local_ops->chmod = local_chmod;
+ vfs_local_ops->chown = local_chown;
+#ifdef ENABLE_EXT2FS_ATTR
+ vfs_local_ops->fgetflags = local_fgetflags;
+ vfs_local_ops->fsetflags = local_fsetflags;
+#endif
+ vfs_local_ops->utime = local_utime;
+ vfs_local_ops->readlink = local_readlink;
+ vfs_local_ops->symlink = local_symlink;
+ vfs_local_ops->link = local_link;
+ vfs_local_ops->unlink = local_unlink;
+ vfs_local_ops->rename = local_rename;
+ vfs_local_ops->chdir = local_chdir;
+ vfs_local_ops->ferrno = local_errno;
+ vfs_local_ops->lseek = local_lseek;
+ vfs_local_ops->mknod = local_mknod;
+ vfs_local_ops->getlocalcopy = local_getlocalcopy;
+ vfs_local_ops->ungetlocalcopy = local_ungetlocalcopy;
+ vfs_local_ops->mkdir = local_mkdir;
+ vfs_local_ops->rmdir = local_rmdir;
+ vfs_local_ops->nothingisopen = local_nothingisopen;
+ vfs_register_class (vfs_local_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/local/local.h b/src/vfs/local/local.h
new file mode 100644
index 0000000..8929d10
--- /dev/null
+++ b/src/vfs/local/local.h
@@ -0,0 +1,32 @@
+/**
+ * \file
+ * \brief Header: local FS
+ */
+
+#ifndef MC__VFS_LOCAL_H
+#define MC__VFS_LOCAL_H
+
+#include "lib/vfs/vfs.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+extern void vfs_init_localfs (void);
+
+/* these functions are used by other filesystems, so they are
+ * published here. */
+extern int local_close (void *data);
+extern ssize_t local_read (void *data, char *buffer, size_t count);
+extern int local_fstat (void *data, struct stat *buf);
+extern int local_errno (struct vfs_class *me);
+extern off_t local_lseek (void *data, off_t offset, int whence);
+
+/*** inline functions ****************************************************************************/
+#endif
diff --git a/src/vfs/plugins_init.c b/src/vfs/plugins_init.c
new file mode 100644
index 0000000..767e284
--- /dev/null
+++ b/src/vfs/plugins_init.c
@@ -0,0 +1,123 @@
+/*
+ Init VFS plugins.
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2011.
+
+ 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
+ * \brief This is a template file (here goes brief description).
+ * \author Author1
+ * \author Author2
+ * \date 20xx
+ *
+ * Detailed description.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+
+#include "local/local.h"
+
+#ifdef ENABLE_VFS_CPIO
+#include "cpio/cpio.h"
+#endif
+
+#ifdef ENABLE_VFS_EXTFS
+#include "extfs/extfs.h"
+#endif
+
+#ifdef ENABLE_VFS_FISH
+#include "fish/fish.h"
+#endif
+
+#ifdef ENABLE_VFS_FTP
+#include "ftpfs/ftpfs.h"
+#endif
+
+#ifdef ENABLE_VFS_SFTP
+#include "sftpfs/sftpfs.h"
+#endif
+
+#ifdef ENABLE_VFS_SFS
+#include "sfs/sfs.h"
+#endif
+
+#ifdef ENABLE_VFS_TAR
+#include "tar/tar.h"
+#endif
+
+#ifdef ENABLE_VFS_UNDELFS
+#include "undelfs/undelfs.h"
+#endif
+
+#include "plugins_init.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_plugins_init (void)
+{
+ /* localfs needs to be the first one */
+ vfs_init_localfs ();
+
+#ifdef ENABLE_VFS_CPIO
+ vfs_init_cpiofs ();
+#endif /* ENABLE_VFS_CPIO */
+#ifdef ENABLE_VFS_TAR
+ vfs_init_tarfs ();
+#endif /* ENABLE_VFS_TAR */
+#ifdef ENABLE_VFS_SFS
+ vfs_init_sfs ();
+#endif /* ENABLE_VFS_SFS */
+#ifdef ENABLE_VFS_EXTFS
+ vfs_init_extfs ();
+#endif /* ENABLE_VFS_EXTFS */
+#ifdef ENABLE_VFS_UNDELFS
+ vfs_init_undelfs ();
+#endif /* ENABLE_VFS_UNDELFS */
+
+#ifdef ENABLE_VFS_FTP
+ vfs_init_ftpfs ();
+#endif /* ENABLE_VFS_FTP */
+#ifdef ENABLE_VFS_SFTP
+ vfs_init_sftpfs ();
+#endif /* ENABLE_VFS_SFTP */
+#ifdef ENABLE_VFS_FISH
+ vfs_init_fish ();
+#endif /* ENABLE_VFS_FISH */
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/plugins_init.h b/src/vfs/plugins_init.h
new file mode 100644
index 0000000..9a36f18
--- /dev/null
+++ b/src/vfs/plugins_init.h
@@ -0,0 +1,18 @@
+#ifndef MC__VFS_PLUINS_INIT_H
+#define MC__VFS_PLUINS_INIT_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_plugins_init (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_PLUINS_INIT_H */
diff --git a/src/vfs/sfs/Makefile.am b/src/vfs/sfs/Makefile.am
new file mode 100644
index 0000000..7de97d0
--- /dev/null
+++ b/src/vfs/sfs/Makefile.am
@@ -0,0 +1,16 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-sfs.la
+
+libvfs_sfs_la_SOURCES = \
+ sfs.c sfs.h
+
+SFSCONFFILES = sfs.ini
+
+if ENABLE_VFS_SFS
+sfsconfdir = $(sysconfdir)/@PACKAGE@
+sfsconf_DATA = $(SFSCONFFILES)
+endif
+
+EXTRA_DIST = $(SFSCONFFILES)
diff --git a/src/vfs/sfs/Makefile.in b/src/vfs/sfs/Makefile.in
new file mode 100644
index 0000000..f9893eb
--- /dev/null
+++ b/src/vfs/sfs/Makefile.in
@@ -0,0 +1,794 @@
+# 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/vfs/sfs
+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)
+libvfs_sfs_la_LIBADD =
+am_libvfs_sfs_la_OBJECTS = sfs.lo
+libvfs_sfs_la_OBJECTS = $(am_libvfs_sfs_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)/sfs.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 = $(libvfs_sfs_la_SOURCES)
+DIST_SOURCES = $(libvfs_sfs_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__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(sfsconfdir)"
+DATA = $(sfsconf_DATA)
+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@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+noinst_LTLIBRARIES = libvfs-sfs.la
+libvfs_sfs_la_SOURCES = \
+ sfs.c sfs.h
+
+SFSCONFFILES = sfs.ini
+@ENABLE_VFS_SFS_TRUE@sfsconfdir = $(sysconfdir)/@PACKAGE@
+@ENABLE_VFS_SFS_TRUE@sfsconf_DATA = $(SFSCONFFILES)
+EXTRA_DIST = $(SFSCONFFILES)
+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/vfs/sfs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/sfs/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}; \
+ }
+
+libvfs-sfs.la: $(libvfs_sfs_la_OBJECTS) $(libvfs_sfs_la_DEPENDENCIES) $(EXTRA_libvfs_sfs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_sfs_la_OBJECTS) $(libvfs_sfs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sfs.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
+install-sfsconfDATA: $(sfsconf_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(sfsconf_DATA)'; test -n "$(sfsconfdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sfsconfdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sfsconfdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(sfsconfdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(sfsconfdir)" || exit $$?; \
+ done
+
+uninstall-sfsconfDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sfsconf_DATA)'; test -n "$(sfsconfdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(sfsconfdir)'; $(am__uninstall_files_from_dir)
+
+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) $(DATA)
+installdirs:
+ for dir in "$(DESTDIR)$(sfsconfdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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)/sfs.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-sfsconfDATA
+
+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)/sfs.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: uninstall-sfsconfDATA
+
+.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-sfsconfDATA \
+ 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 \
+ uninstall-sfsconfDATA
+
+.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/vfs/sfs/sfs.c b/src/vfs/sfs/sfs.c
new file mode 100644
index 0000000..fdcc823
--- /dev/null
+++ b/src/vfs/sfs/sfs.c
@@ -0,0 +1,604 @@
+/*
+ Single File fileSystem
+
+ Copyright (C) 1998-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ 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
+ * \brief Source: Single File fileSystem
+ *
+ * This defines whole class of filesystems which contain single file
+ * inside. It is somehow similar to extfs, except that extfs makes
+ * whole virtual trees and we do only single virtual files.
+ *
+ * If you want to gunzip something, you should open it with \verbatim #ugz \endverbatim
+ * suffix, DON'T try to gunzip it yourself.
+ *
+ * Namespace: exports vfs_sfs_ops
+ */
+
+#include <config.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/util.h"
+#include "lib/widget.h" /* D_ERROR, D_NORMAL */
+
+#include "src/execute.h" /* EXECUTE_AS_SHELL */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/xdirentry.h"
+#include "src/vfs/local/local.h"
+#include "lib/vfs/gc.h" /* vfs_stamp_create */
+
+#include "sfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define MAXFS 32
+
+typedef enum
+{
+ F_NONE = 0x0,
+ F_1 = 0x1,
+ F_2 = 0x2,
+ F_NOLOCALCOPY = 0x4,
+ F_FULLMATCH = 0x8
+} sfs_flags_t;
+
+#define COPY_CHAR \
+ if ((size_t) (t - pad) > sizeof (pad)) \
+ { \
+ g_free (pqname); \
+ return (-1); \
+ } \
+ else \
+ *t++ = *s_iter;
+
+#define COPY_STRING(a) \
+ if ((t - pad) + strlen (a) > sizeof (pad)) \
+ { \
+ g_free (pqname); \
+ return (-1); \
+ } \
+ else \
+ { \
+ strcpy (t, a); \
+ t += strlen (a); \
+ }
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct cachedfile
+{
+ char *name;
+ char *cache;
+} cachedfile;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static GSList *head = NULL;
+
+static struct vfs_s_subclass sfs_subclass;
+static struct vfs_class *vfs_sfs_ops = VFS_CLASS (&sfs_subclass);
+
+static int sfs_no = 0;
+static struct
+{
+ char *prefix;
+ char *command;
+ sfs_flags_t flags;
+} sfs_info[MAXFS];
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+cachedfile_compare (const void *a, const void *b)
+{
+ const cachedfile *cf = (const cachedfile *) a;
+ const char *name = (const char *) b;
+
+ return strcmp (name, cf->name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_vfmake (const vfs_path_t * vpath, vfs_path_t * cache_vpath)
+{
+ int w;
+ char pad[10240];
+ char *s_iter, *t = pad;
+ gboolean was_percent = FALSE;
+ vfs_path_t *pname; /* name of parent archive */
+ char *pqname; /* name of parent archive, quoted */
+ const vfs_path_element_t *path_element;
+ mc_pipe_t *pip;
+ GError *error = NULL;
+
+ path_element = vfs_path_get_by_index (vpath, -1);
+ pname = vfs_path_clone (vpath);
+ vfs_path_remove_element_by_index (pname, -1);
+
+ w = path_element->class->which (path_element->class, path_element->vfs_prefix);
+ if (w == -1)
+ vfs_die ("This cannot happen... Hopefully.\n");
+
+ if ((sfs_info[w].flags & F_1) == 0
+ && strcmp (vfs_path_get_last_path_str (pname), PATH_SEP_STR) != 0)
+ {
+ vfs_path_free (pname, TRUE);
+ return (-1);
+ }
+
+ /* if ((sfs_info[w].flags & F_2) || (!inpath) || (!*inpath)); else return -1; */
+ if ((sfs_info[w].flags & F_NOLOCALCOPY) != 0)
+ pqname = name_quote (vfs_path_as_str (pname), FALSE);
+ else
+ {
+ vfs_path_t *s;
+
+ s = mc_getlocalcopy (pname);
+ if (s == NULL)
+ {
+ vfs_path_free (pname, TRUE);
+ return (-1);
+ }
+
+ pqname = name_quote (vfs_path_get_last_path_str (s), FALSE);
+ vfs_path_free (s, TRUE);
+ }
+
+ vfs_path_free (pname, TRUE);
+
+ for (s_iter = sfs_info[w].command; *s_iter != '\0'; s_iter++)
+ {
+ if (was_percent)
+ {
+ const char *ptr = NULL;
+
+ was_percent = FALSE;
+
+ switch (*s_iter)
+ {
+ case '1':
+ ptr = pqname;
+ break;
+ case '2':
+ ptr = path_element->path;
+ break;
+ case '3':
+ ptr = vfs_path_get_last_path_str (cache_vpath);
+ break;
+ case '%':
+ COPY_CHAR;
+ continue;
+ default:
+ break;
+ }
+
+ if (ptr != NULL)
+ {
+ COPY_STRING (ptr);
+ }
+ }
+ else if (*s_iter == '%')
+ was_percent = TRUE;
+ else
+ {
+ COPY_CHAR;
+ }
+ }
+
+ g_free (pqname);
+
+ /* don't read stdout */
+ pip = mc_popen (pad, FALSE, TRUE, &error);
+ if (pip == NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("SFS virtual file system:\n%s"), error->message);
+ g_error_free (error);
+ return (-1);
+ }
+
+ pip->err.null_term = TRUE;
+
+ mc_pread (pip, &error);
+ if (error != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("SFS virtual file system:\n%s"), error->message);
+ g_error_free (error);
+ mc_pclose (pip, NULL);
+ return (-1);
+ }
+
+ if (pip->err.len > 0)
+ message (D_ERROR, MSG_ERROR, _("SFS virtual file system:\n%s"), pip->err.buf);
+
+ mc_pclose (pip, NULL);
+ return 0; /* OK */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+sfs_redirect (const vfs_path_t * vpath)
+{
+ GSList *cur;
+ cachedfile *cf;
+ vfs_path_t *cache_vpath;
+ int handle;
+
+ cur = g_slist_find_custom (head, vfs_path_as_str (vpath), cachedfile_compare);
+
+ if (cur != NULL)
+ {
+ cf = (cachedfile *) cur->data;
+ vfs_stamp (vfs_sfs_ops, cf);
+ return cf->cache;
+ }
+
+ handle = vfs_mkstemps (&cache_vpath, "sfs", vfs_path_get_last_path_str (vpath));
+
+ if (handle == -1)
+ return "/SOMEONE_PLAYING_DIRTY_TMP_TRICKS_ON_US";
+
+ close (handle);
+
+ if (sfs_vfmake (vpath, cache_vpath) == 0)
+ {
+ cf = g_new (cachedfile, 1);
+ cf->name = g_strdup (vfs_path_as_str (vpath));
+ cf->cache = vfs_path_free (cache_vpath, FALSE);
+ head = g_slist_prepend (head, cf);
+
+ vfs_stamp_create (vfs_sfs_ops, (cachedfile *) head->data);
+ return cf->cache;
+ }
+
+ mc_unlink (cache_vpath);
+ vfs_path_free (cache_vpath, TRUE);
+ return "/I_MUST_NOT_EXIST";
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+sfs_open (const vfs_path_t * vpath /*struct vfs_class *me, const char *path */ , int flags,
+ mode_t mode)
+{
+ int *info;
+ int fd;
+
+ fd = open (sfs_redirect (vpath), NO_LINEAR (flags), mode);
+ if (fd == -1)
+ return NULL;
+
+ info = g_new (int, 1);
+ *info = fd;
+
+ return info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ return stat (sfs_redirect (vpath), buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+#ifndef HAVE_STATLSTAT
+ return lstat (sfs_redirect (vpath), buf);
+#else
+ return statlstat (sfs_redirect (vpath), buf);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_chmod (const vfs_path_t * vpath, mode_t mode)
+{
+ return chmod (sfs_redirect (vpath), mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_chown (const vfs_path_t * vpath, uid_t owner, gid_t group)
+{
+ return chown (sfs_redirect (vpath), owner, group);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_utime (const vfs_path_t * vpath, mc_timesbuf_t * times)
+{
+#ifdef HAVE_UTIMENSAT
+ return utimensat (AT_FDCWD, sfs_redirect (vpath), *times, 0);
+#else
+ return utime (sfs_redirect (vpath), times);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_readlink (const vfs_path_t * vpath, char *buf, size_t size)
+{
+ return readlink (sfs_redirect (vpath), buf, size);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfsid
+sfs_getid (const vfs_path_t * vpath)
+{
+ GSList *cur;
+
+ cur = g_slist_find_custom (head, vfs_path_as_str (vpath), cachedfile_compare);
+
+ return (vfsid) (cur != NULL ? cur->data : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+sfs_free (vfsid id)
+{
+ struct cachedfile *which;
+ GSList *cur;
+
+ which = (struct cachedfile *) id;
+ cur = g_slist_find (head, which);
+ if (cur == NULL)
+ vfs_die ("Free of thing which is unknown to me\n");
+
+ which = (struct cachedfile *) cur->data;
+ unlink (which->cache);
+ g_free (which->cache);
+ g_free (which->name);
+ g_free (which);
+
+ head = g_slist_delete_link (head, cur);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+sfs_fill_names (struct vfs_class *me, fill_names_f func)
+{
+ GSList *cur;
+
+ (void) me;
+
+ for (cur = head; cur != NULL; cur = g_slist_next (cur))
+ func (((cachedfile *) cur->data)->name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sfs_nothingisopen (vfsid id)
+{
+ /* FIXME: Investigate whether have to guard this like in
+ the other VFSs (see fd_usage in extfs) -- Norbert */
+ (void) id;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+sfs_getlocalcopy (const vfs_path_t * vpath)
+{
+ return vfs_path_from_str (sfs_redirect (vpath));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_ungetlocalcopy (const vfs_path_t * vpath, const vfs_path_t * local, gboolean has_changed)
+{
+ (void) vpath;
+ (void) local;
+ (void) has_changed;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_init (struct vfs_class *me)
+{
+ char *mc_sfsini;
+ FILE *cfg;
+ char key[256];
+
+ (void) me;
+
+ mc_sfsini = g_build_filename (mc_global.sysconfig_dir, "sfs.ini", (char *) NULL);
+ cfg = fopen (mc_sfsini, "r");
+
+ if (cfg == NULL)
+ {
+ fprintf (stderr, _("%s: Warning: file %s not found\n"), "sfs_init()", mc_sfsini);
+ g_free (mc_sfsini);
+ return 0;
+ }
+ g_free (mc_sfsini);
+
+ sfs_no = 0;
+ while (sfs_no < MAXFS && fgets (key, sizeof (key), cfg) != NULL)
+ {
+ char *c, *semi = NULL;
+ sfs_flags_t flags = F_NONE;
+
+ if (*key == '#' || *key == '\n')
+ continue;
+
+ for (c = key; *c != '\0'; c++)
+ if (*c == ':' || IS_PATH_SEP (*c))
+ {
+ semi = c;
+ if (IS_PATH_SEP (*c))
+ {
+ *c = '\0';
+ flags |= F_FULLMATCH;
+ }
+ break;
+ }
+
+ if (semi == NULL)
+ {
+ invalid_line:
+ fprintf (stderr, _("Warning: Invalid line in %s:\n%s\n"), "sfs.ini", key);
+ continue;
+ }
+
+ for (c = semi + 1; *c != '\0' && !whitespace (*c); c++)
+ switch (*c)
+ {
+ case '1':
+ flags |= F_1;
+ break;
+ case '2':
+ flags |= F_2;
+ break;
+ case 'R':
+ flags |= F_NOLOCALCOPY;
+ break;
+ default:
+ fprintf (stderr, _("Warning: Invalid flag %c in %s:\n%s\n"), *c, "sfs.ini", key);
+ }
+
+ if (*c == '\0')
+ goto invalid_line;
+
+ c++;
+ *(semi + 1) = '\0';
+ semi = strchr (c, '\n');
+ if (semi != NULL)
+ *semi = '\0';
+
+ sfs_info[sfs_no].prefix = g_strdup (key);
+ sfs_info[sfs_no].command = g_strdup (c);
+ sfs_info[sfs_no].flags = flags;
+ sfs_no++;
+ }
+ fclose (cfg);
+
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+sfs_done (struct vfs_class *me)
+{
+ int i;
+
+ (void) me;
+
+ for (i = 0; i < sfs_no; i++)
+ {
+ MC_PTR_FREE (sfs_info[i].prefix);
+ MC_PTR_FREE (sfs_info[i].command);
+ }
+ sfs_no = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sfs_which (struct vfs_class *me, const char *path)
+{
+ int i;
+
+ (void) me;
+
+ for (i = 0; i < sfs_no; i++)
+ if ((sfs_info[i].flags & F_FULLMATCH) != 0)
+ {
+ if (strcmp (path, sfs_info[i].prefix) == 0)
+ return i;
+ }
+ else if (strncmp (path, sfs_info[i].prefix, strlen (sfs_info[i].prefix)) == 0)
+ return i;
+
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_sfs (void)
+{
+ /* NULLize vfs_s_subclass members */
+ memset (&sfs_subclass, 0, sizeof (sfs_subclass));
+
+ vfs_init_class (vfs_sfs_ops, "sfs", VFSF_UNKNOWN, NULL);
+ vfs_sfs_ops->init = sfs_init;
+ vfs_sfs_ops->done = sfs_done;
+ vfs_sfs_ops->fill_names = sfs_fill_names;
+ vfs_sfs_ops->which = sfs_which;
+ vfs_sfs_ops->open = sfs_open;
+ vfs_sfs_ops->close = local_close;
+ vfs_sfs_ops->read = local_read;
+ vfs_sfs_ops->stat = sfs_stat;
+ vfs_sfs_ops->lstat = sfs_lstat;
+ vfs_sfs_ops->fstat = local_fstat;
+ vfs_sfs_ops->chmod = sfs_chmod;
+ vfs_sfs_ops->chown = sfs_chown;
+ vfs_sfs_ops->utime = sfs_utime;
+ vfs_sfs_ops->readlink = sfs_readlink;
+ vfs_sfs_ops->ferrno = local_errno;
+ vfs_sfs_ops->lseek = local_lseek;
+ vfs_sfs_ops->getid = sfs_getid;
+ vfs_sfs_ops->nothingisopen = sfs_nothingisopen;
+ vfs_sfs_ops->free = sfs_free;
+ vfs_sfs_ops->getlocalcopy = sfs_getlocalcopy;
+ vfs_sfs_ops->ungetlocalcopy = sfs_ungetlocalcopy;
+ vfs_register_class (vfs_sfs_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/sfs/sfs.h b/src/vfs/sfs/sfs.h
new file mode 100644
index 0000000..c846c93
--- /dev/null
+++ b/src/vfs/sfs/sfs.h
@@ -0,0 +1,18 @@
+#ifndef MC__VFS_SFS_H
+#define MC__VFS_SFS_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_sfs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_SFS_H */
diff --git a/src/vfs/sfs/sfs.ini b/src/vfs/sfs/sfs.ini
new file mode 100644
index 0000000..d817dc9
--- /dev/null
+++ b/src/vfs/sfs/sfs.ini
@@ -0,0 +1,34 @@
+#
+# This is config for Single File fileSystem
+#
+# Notice that output files (%3) are pre-created atomically in /tmp
+# with 0600 rights, so it is safe to > %3
+#
+gz/1 gzip < %1 > %3
+ugz/1 gzip -cdf < %1 > %3
+bz/1 bzip < %1 > %3
+ubz/1 bzip -d < %1 > %3
+bz2/1 bzip2 < %1 > %3
+ubz2/1 bzip2 -d < %1 > %3
+lz/1 lzip < %1 > %3
+ulz/1 lzip -d < %1 > %3
+lz4/1 lz4 < %1 > %3
+ulz4/1 lz4 -d < %1 > %3
+lzma/1 lzma < %1 > %3
+ulzma/1 lzma -d < %1 > %3
+xz/1 xz < %1 > %3
+uxz/1 xz -d < %1 > %3
+zst/1 zstd < %1 > %3
+uzst/1 zstd -d < %1 > %3
+tar/1 tar cf %3 %1
+tgz/1 tar czf %3 %1
+uhtml/1 lynx -force_html -dump %1 > %3
+uman/1 groff -Tascii -man %1 > %3
+uue/1 uuenpipe < %1 > %3
+uude/1 uudepipe < %1 > %3
+crlf/1 todos < %1 > %3
+cr/1 fromdos < %1 > %3
+# Fixme: we need it to fail whenever it should
+url:2 lynx -source `echo "%2" | sed 's-|-/-g'` > %3
+nop/1 cat %1 > %3
+strings/1 strings %1 > %3
diff --git a/src/vfs/sftpfs/Makefile.am b/src/vfs/sftpfs/Makefile.am
new file mode 100644
index 0000000..12905d1
--- /dev/null
+++ b/src/vfs/sftpfs/Makefile.am
@@ -0,0 +1,12 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir) $(LIBSSH_CFLAGS)
+
+noinst_LTLIBRARIES = libvfs-sftpfs.la
+
+libvfs_sftpfs_la_SOURCES = \
+ config_parser.c \
+ connection.c \
+ dir.c \
+ file.c \
+ internal.c internal.h \
+ sftpfs.c sftpfs.h
diff --git a/src/vfs/sftpfs/Makefile.in b/src/vfs/sftpfs/Makefile.in
new file mode 100644
index 0000000..e59e875
--- /dev/null
+++ b/src/vfs/sftpfs/Makefile.in
@@ -0,0 +1,759 @@
+# 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/vfs/sftpfs
+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)
+libvfs_sftpfs_la_LIBADD =
+am_libvfs_sftpfs_la_OBJECTS = config_parser.lo connection.lo dir.lo \
+ file.lo internal.lo sftpfs.lo
+libvfs_sftpfs_la_OBJECTS = $(am_libvfs_sftpfs_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)/config_parser.Plo \
+ ./$(DEPDIR)/connection.Plo ./$(DEPDIR)/dir.Plo \
+ ./$(DEPDIR)/file.Plo ./$(DEPDIR)/internal.Plo \
+ ./$(DEPDIR)/sftpfs.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 = $(libvfs_sftpfs_la_SOURCES)
+DIST_SOURCES = $(libvfs_sftpfs_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@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir) $(LIBSSH_CFLAGS)
+noinst_LTLIBRARIES = libvfs-sftpfs.la
+libvfs_sftpfs_la_SOURCES = \
+ config_parser.c \
+ connection.c \
+ dir.c \
+ file.c \
+ internal.c internal.h \
+ sftpfs.c sftpfs.h
+
+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/vfs/sftpfs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/sftpfs/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}; \
+ }
+
+libvfs-sftpfs.la: $(libvfs_sftpfs_la_OBJECTS) $(libvfs_sftpfs_la_DEPENDENCIES) $(EXTRA_libvfs_sftpfs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_sftpfs_la_OBJECTS) $(libvfs_sftpfs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dir.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/internal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sftpfs.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)/config_parser.Plo
+ -rm -f ./$(DEPDIR)/connection.Plo
+ -rm -f ./$(DEPDIR)/dir.Plo
+ -rm -f ./$(DEPDIR)/file.Plo
+ -rm -f ./$(DEPDIR)/internal.Plo
+ -rm -f ./$(DEPDIR)/sftpfs.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)/config_parser.Plo
+ -rm -f ./$(DEPDIR)/connection.Plo
+ -rm -f ./$(DEPDIR)/dir.Plo
+ -rm -f ./$(DEPDIR)/file.Plo
+ -rm -f ./$(DEPDIR)/internal.Plo
+ -rm -f ./$(DEPDIR)/sftpfs.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/vfs/sftpfs/config_parser.c b/src/vfs/sftpfs/config_parser.c
new file mode 100644
index 0000000..d3e2287
--- /dev/null
+++ b/src/vfs/sftpfs/config_parser.c
@@ -0,0 +1,427 @@
+/* Virtual File System: SFTP file system.
+ The SSH config parser
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2011
+ Slava Zanko <slavazanko@gmail.com>, 2011, 2012, 2013
+
+ 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 <errno.h>
+#include <stddef.h>
+#include <stdlib.h> /* atoi() */
+
+#include "lib/global.h"
+
+#include "lib/search.h"
+#include "lib/util.h" /* tilde_expand() */
+#include "lib/vfs/utilvfs.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define SFTP_DEFAULT_PORT 22
+
+#ifndef SFTPFS_SSH_CONFIG
+#define SFTPFS_SSH_CONFIG "~/.ssh/config"
+#endif
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ char *real_host; /* host DNS name or ip address */
+ int port; /* port for connect to host */
+ char *user; /* the user to log in as */
+ gboolean password_auth; /* FALSE - no passwords allowed (default TRUE) */
+ gboolean identities_only; /* TRUE - no ssh agent (default FALSE) */
+ gboolean pubkey_auth; /* FALSE - disable public key authentication (default TRUE) */
+ char *identity_file; /* A file from which the user's DSA, ECDSA or DSA authentication identity is read. */
+} sftpfs_ssh_config_entity_t;
+
+enum config_var_type
+{
+ STRING,
+ INTEGER,
+ BOOLEAN,
+ FILENAME
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* *INDENT-OFF* */
+static struct
+{
+ const char *pattern;
+ mc_search_t *pattern_regexp;
+ enum config_var_type type;
+ size_t offset;
+} config_variables[] =
+{
+ {"^\\s*User\\s+(.*)$", NULL, STRING, offsetof (sftpfs_ssh_config_entity_t, user)},
+ {"^\\s*HostName\\s+(.*)$", NULL, STRING, offsetof (sftpfs_ssh_config_entity_t, real_host)},
+ {"^\\s*IdentitiesOnly\\s+(.*)$", NULL, BOOLEAN, offsetof (sftpfs_ssh_config_entity_t, identities_only)},
+ {"^\\s*IdentityFile\\s+(.*)$", NULL, FILENAME, offsetof (sftpfs_ssh_config_entity_t, identity_file)},
+ {"^\\s*Port\\s+(.*)$", NULL, INTEGER, offsetof (sftpfs_ssh_config_entity_t, port)},
+ {"^\\s*PasswordAuthentication\\s+(.*)$", NULL, BOOLEAN, offsetof (sftpfs_ssh_config_entity_t, password_auth)},
+ {"^\\s*PubkeyAuthentication\\s+(.*)$", NULL, STRING, offsetof (sftpfs_ssh_config_entity_t, pubkey_auth)},
+ {NULL, NULL, 0, 0}
+};
+/* *INDENT-ON* */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Free one config entity.
+ *
+ * @param config_entity config entity structure
+ */
+
+static void
+sftpfs_ssh_config_entity_free (sftpfs_ssh_config_entity_t * config_entity)
+{
+ g_free (config_entity->real_host);
+ g_free (config_entity->user);
+ g_free (config_entity->identity_file);
+ g_free (config_entity);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Transform tilda (~) to full home dirname.
+ *
+ * @param filename file name with tilda
+ * @return newly allocated file name with full home dirname
+ */
+
+static char *
+sftpfs_correct_file_name (const char *filename)
+{
+ vfs_path_t *vpath;
+ char *fn;
+
+ fn = tilde_expand (filename);
+ vpath = vfs_path_from_str (fn);
+ g_free (fn);
+ return vfs_path_free (vpath, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#define POINTER_TO_STRUCTURE_MEMBER(type) \
+ ((type) ((char *) config_entity + (size_t) config_variables[i].offset))
+
+/**
+ * Parse string and filling one config entity by parsed data.
+ *
+ * @param config_entity config entity structure
+ * @param buffer string for parce
+ */
+
+static void
+sftpfs_fill_config_entity_from_string (sftpfs_ssh_config_entity_t * config_entity, char *buffer)
+{
+ int i;
+
+ for (i = 0; config_variables[i].pattern != NULL; i++)
+ {
+ if (mc_search_run (config_variables[i].pattern_regexp, buffer, 0, strlen (buffer), NULL))
+ {
+ int value_offset;
+ char *value;
+
+ int *pointer_int;
+ char **pointer_str;
+ gboolean *pointer_bool;
+
+ /* Calculate start of value in string */
+ value_offset = mc_search_getstart_result_by_num (config_variables[i].pattern_regexp, 1);
+ value = &buffer[value_offset];
+
+ switch (config_variables[i].type)
+ {
+ case STRING:
+ pointer_str = POINTER_TO_STRUCTURE_MEMBER (char **);
+ *pointer_str = g_strdup (value);
+ break;
+ case FILENAME:
+ pointer_str = POINTER_TO_STRUCTURE_MEMBER (char **);
+ *pointer_str = sftpfs_correct_file_name (value);
+ break;
+ case INTEGER:
+ pointer_int = POINTER_TO_STRUCTURE_MEMBER (int *);
+ *pointer_int = atoi (value);
+ break;
+ case BOOLEAN:
+ pointer_bool = POINTER_TO_STRUCTURE_MEMBER (gboolean *);
+ *pointer_bool = strcasecmp (value, "True") == 0;
+ break;
+ default:
+ continue;
+ }
+ return;
+ }
+ }
+}
+
+#undef POINTER_TO_STRUCTURE_MEMBER
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Fill one config entity from config file.
+ *
+ * @param ssh_config_handler file descriptor for the ssh config file
+ * @param config_entity config entity structure
+ * @param vpath_element path element with host data (hostname, port)
+ * @param mcerror pointer to the error handler
+ * @return TRUE if config entity was filled successfully, FALSE otherwise
+ */
+
+static gboolean
+sftpfs_fill_config_entity_from_config (FILE * ssh_config_handler,
+ sftpfs_ssh_config_entity_t * config_entity,
+ const vfs_path_element_t * vpath_element, GError ** mcerror)
+{
+ char buffer[BUF_MEDIUM];
+ gboolean host_block_hit = FALSE;
+ gboolean pattern_block_hit = FALSE;
+ mc_search_t *host_regexp;
+ gboolean ok = TRUE;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ host_regexp = mc_search_new ("^\\s*host\\s+(.*)$", DEFAULT_CHARSET);
+ host_regexp->search_type = MC_SEARCH_T_REGEX;
+ host_regexp->is_case_sensitive = FALSE;
+
+ while (TRUE)
+ {
+ char *cr;
+
+ if (fgets (buffer, sizeof (buffer), ssh_config_handler) == NULL)
+ {
+ int e;
+
+ e = errno;
+
+ if (!feof (ssh_config_handler))
+ {
+ mc_propagate_error (mcerror, e,
+ _("sftp: an error occurred while reading %s: %s"),
+ SFTPFS_SSH_CONFIG, strerror (e));
+ ok = FALSE;
+ goto done;
+ }
+
+ break;
+ }
+
+ cr = strrchr (buffer, '\n');
+ if (cr != NULL)
+ *cr = '\0';
+
+ if (mc_search_run (host_regexp, buffer, 0, strlen (buffer), NULL))
+ {
+ const char *host_pattern;
+ int host_pattern_offset;
+
+ /* if previous host block exactly describe our connection */
+ if (host_block_hit)
+ goto done;
+
+ host_pattern_offset = mc_search_getstart_result_by_num (host_regexp, 1);
+ host_pattern = &buffer[host_pattern_offset];
+ if (strcmp (host_pattern, vpath_element->host) == 0)
+ {
+ /* current host block describe our connection */
+ host_block_hit = TRUE;
+ }
+ else
+ {
+ mc_search_t *pattern_regexp;
+
+ pattern_regexp = mc_search_new (host_pattern, DEFAULT_CHARSET);
+ pattern_regexp->search_type = MC_SEARCH_T_GLOB;
+ pattern_regexp->is_case_sensitive = FALSE;
+ pattern_regexp->is_entire_line = TRUE;
+ pattern_block_hit =
+ mc_search_run (pattern_regexp, vpath_element->host, 0,
+ strlen (vpath_element->host), NULL);
+ mc_search_free (pattern_regexp);
+ }
+ }
+ else if (pattern_block_hit || host_block_hit)
+ {
+ sftpfs_fill_config_entity_from_string (config_entity, buffer);
+ }
+ }
+
+ done:
+ mc_search_free (host_regexp);
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open the ssh config file and fill config entity.
+ *
+ * @param vpath_element path element with host data (hostname, port)
+ * @param mcerror pointer to the error handler
+ * @return newly allocated config entity structure
+ */
+
+static sftpfs_ssh_config_entity_t *
+sftpfs_get_config_entity (const vfs_path_element_t * vpath_element, GError ** mcerror)
+{
+ sftpfs_ssh_config_entity_t *config_entity;
+ FILE *ssh_config_handler;
+ char *config_filename;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ config_entity = g_new0 (sftpfs_ssh_config_entity_t, 1);
+ config_entity->password_auth = TRUE;
+ config_entity->identities_only = FALSE;
+ config_entity->pubkey_auth = TRUE;
+ config_entity->port = SFTP_DEFAULT_PORT;
+
+ config_filename = sftpfs_correct_file_name (SFTPFS_SSH_CONFIG);
+ ssh_config_handler = fopen (config_filename, "r");
+ g_free (config_filename);
+
+ if (ssh_config_handler != NULL)
+ {
+ gboolean ok;
+
+ ok = sftpfs_fill_config_entity_from_config
+ (ssh_config_handler, config_entity, vpath_element, mcerror);
+ fclose (ssh_config_handler);
+
+ if (!ok)
+ {
+ sftpfs_ssh_config_entity_free (config_entity);
+ return NULL;
+ }
+ }
+
+ if (config_entity->user == NULL)
+ {
+ config_entity->user = vfs_get_local_username ();
+ if (config_entity->user == NULL)
+ {
+ sftpfs_ssh_config_entity_free (config_entity);
+ config_entity = NULL;
+ mc_propagate_error (mcerror, EPERM, "%s", _("sftp: Unable to get current user name."));
+ }
+ }
+ return config_entity;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Reads data from the ssh config file related to connection.
+ *
+ * @param super connection data
+ * @param error pointer to the error handler
+ */
+
+void
+sftpfs_fill_connection_data_from_config (struct vfs_s_super *super, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ sftpfs_ssh_config_entity_t *config_entity;
+
+ mc_return_if_error (mcerror);
+
+ config_entity = sftpfs_get_config_entity (super->path_element, mcerror);
+ if (config_entity == NULL)
+ return;
+
+ sftpfs_super->config_auth_type = (config_entity->pubkey_auth) ? PUBKEY : 0;
+ sftpfs_super->config_auth_type |= (config_entity->identities_only) ? 0 : AGENT;
+ sftpfs_super->config_auth_type |= (config_entity->password_auth) ? PASSWORD : 0;
+
+ if (super->path_element->port == 0)
+ super->path_element->port = config_entity->port;
+
+ if (super->path_element->user == NULL)
+ super->path_element->user = g_strdup (config_entity->user);
+
+ if (config_entity->real_host != NULL)
+ {
+ g_free (super->path_element->host);
+ super->path_element->host = g_strdup (config_entity->real_host);
+ }
+
+ if (config_entity->identity_file != NULL)
+ {
+ sftpfs_super->privkey = g_strdup (config_entity->identity_file);
+ sftpfs_super->pubkey = g_strdup_printf ("%s.pub", config_entity->identity_file);
+ }
+
+ sftpfs_ssh_config_entity_free (config_entity);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Initialize the SSH config parser.
+ */
+
+void
+sftpfs_init_config_variables_patterns (void)
+{
+ int i;
+
+ for (i = 0; config_variables[i].pattern != NULL; i++)
+ {
+ config_variables[i].pattern_regexp =
+ mc_search_new (config_variables[i].pattern, DEFAULT_CHARSET);
+ config_variables[i].pattern_regexp->search_type = MC_SEARCH_T_REGEX;
+ config_variables[i].pattern_regexp->is_case_sensitive = FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Deinitialize the SSH config parser.
+ */
+
+void
+sftpfs_deinit_config_variables_patterns (void)
+{
+ int i;
+
+ for (i = 0; config_variables[i].pattern != NULL; i++)
+ {
+ mc_search_free (config_variables[i].pattern_regexp);
+ config_variables[i].pattern_regexp = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/sftpfs/connection.c b/src/vfs/sftpfs/connection.c
new file mode 100644
index 0000000..d2466de
--- /dev/null
+++ b/src/vfs/sftpfs/connection.c
@@ -0,0 +1,970 @@
+/* Virtual File System: SFTP file system.
+ The internal functions: connections
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2011
+ Slava Zanko <slavazanko@gmail.com>, 2011, 2012, 2013
+
+ 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 <errno.h>
+
+#include <netdb.h> /* struct hostent */
+#include <sys/socket.h> /* AF_INET */
+#include <netinet/in.h> /* struct in_addr */
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#include <libssh2.h>
+#include <libssh2_sftp.h>
+
+#include "lib/global.h"
+
+#include "lib/util.h"
+#include "lib/tty/tty.h" /* tty_enable_interrupt_key () */
+#include "lib/vfs/utilvfs.h"
+#include "lib/mcconfig.h" /* mc_config_get_home_dir () */
+#include "lib/widget.h" /* query_dialog () */
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define SHA1_DIGEST_LENGTH 20
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519
+static const char *const hostkey_method_ssh_ed25519 = "ssh-ed25519";
+#endif
+#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_521
+static const char *const hostkey_method_ssh_ecdsa_521 = "ecdsa-sha2-nistp521";
+#endif
+#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_384
+static const char *const hostkey_method_ssh_ecdsa_384 = "ecdsa-sha2-nistp384";
+#endif
+#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256
+static const char *const hostkey_method_ssh_ecdsa_256 = "ecdsa-sha2-nistp256";
+#endif
+static const char *const hostkey_method_ssh_rsa = "ssh-rsa";
+static const char *const hostkey_method_ssh_dss = "ssh-dss";
+
+/**
+ *
+ * The current implementation of know host key checking has following limitations:
+ *
+ * - Only plain-text entries are supported (`HashKnownHosts no` OpenSSH option)
+ * - Only HEX-encoded SHA1 fingerprint display is supported (`FingerprintHash` OpenSSH option)
+ * - Resolved IP addresses are *not* saved/validated along with the hostnames
+ *
+ */
+
+static const char *kbi_passwd = NULL;
+static const struct vfs_s_super *kbi_super = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create socket to host.
+ *
+ * @param super connection data
+ * @param mcerror pointer to the error handler
+ * @return socket descriptor number, -1 if any error was occurred
+ */
+
+static int
+sftpfs_open_socket (struct vfs_s_super *super, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ struct addrinfo hints, *res = NULL, *curr_res;
+ int my_socket = 0;
+ char port[BUF_TINY];
+ static char address_ipv4[INET_ADDRSTRLEN];
+ static char address_ipv6[INET6_ADDRSTRLEN];
+ int e;
+
+ mc_return_val_if_error (mcerror, LIBSSH2_INVALID_SOCKET);
+
+ if (super->path_element->host == NULL || *super->path_element->host == '\0')
+ {
+ mc_propagate_error (mcerror, 0, "%s", _("sftp: Invalid host name."));
+ return LIBSSH2_INVALID_SOCKET;
+ }
+
+ sprintf (port, "%hu", (unsigned short) super->path_element->port);
+
+ tty_enable_interrupt_key (); /* clear the interrupt flag */
+
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+#ifdef AI_ADDRCONFIG
+ /* By default, only look up addresses using address types for
+ * which a local interface is configured (i.e. no IPv6 if no IPv6
+ * interfaces, likewise for IPv4 (see RFC 3493 for details). */
+ hints.ai_flags = AI_ADDRCONFIG;
+#endif
+
+ e = getaddrinfo (super->path_element->host, port, &hints, &res);
+
+#ifdef AI_ADDRCONFIG
+ if (e == EAI_BADFLAGS)
+ {
+ /* Retry with no flags if AI_ADDRCONFIG was rejected. */
+ hints.ai_flags = 0;
+ e = getaddrinfo (super->path_element->host, port, &hints, &res);
+ }
+#endif
+
+ if (e != 0)
+ {
+ mc_propagate_error (mcerror, e, _("sftp: %s"), gai_strerror (e));
+ my_socket = LIBSSH2_INVALID_SOCKET;
+ goto ret;
+ }
+
+ for (curr_res = res; curr_res != NULL; curr_res = curr_res->ai_next)
+ {
+ int save_errno;
+
+ switch (curr_res->ai_addr->sa_family)
+ {
+ case AF_INET:
+ sftpfs_super->ip_address =
+ inet_ntop (AF_INET, &((struct sockaddr_in *) curr_res->ai_addr)->sin_addr,
+ address_ipv4, INET_ADDRSTRLEN);
+ break;
+ case AF_INET6:
+ sftpfs_super->ip_address =
+ inet_ntop (AF_INET6, &((struct sockaddr_in6 *) curr_res->ai_addr)->sin6_addr,
+ address_ipv6, INET6_ADDRSTRLEN);
+ break;
+ default:
+ sftpfs_super->ip_address = NULL;
+ }
+
+ if (sftpfs_super->ip_address == NULL)
+ {
+ mc_propagate_error (mcerror, 0, "%s",
+ _("sftp: failed to convert remote host IP address into text form"));
+ my_socket = LIBSSH2_INVALID_SOCKET;
+ goto ret;
+ }
+
+ my_socket = socket (curr_res->ai_family, curr_res->ai_socktype, curr_res->ai_protocol);
+
+ if (my_socket < 0)
+ {
+ if (curr_res->ai_next != NULL)
+ continue;
+
+ vfs_print_message (_("sftp: %s"), unix_error_string (errno));
+ my_socket = LIBSSH2_INVALID_SOCKET;
+ goto ret;
+ }
+
+ vfs_print_message (_("sftp: making connection to %s"), super->path_element->host);
+
+ if (connect (my_socket, curr_res->ai_addr, curr_res->ai_addrlen) >= 0)
+ break;
+
+ save_errno = errno;
+
+ close (my_socket);
+
+ if (save_errno == EINTR && tty_got_interrupt ())
+ mc_propagate_error (mcerror, 0, "%s", _("sftp: connection interrupted by user"));
+ else if (res->ai_next == NULL)
+ mc_propagate_error (mcerror, save_errno, _("sftp: connection to server failed: %s"),
+ unix_error_string (save_errno));
+ else
+ continue;
+
+ my_socket = LIBSSH2_INVALID_SOCKET;
+ break;
+ }
+
+ ret:
+ if (res != NULL)
+ freeaddrinfo (res);
+ tty_disable_interrupt_key ();
+ return my_socket;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Read ~/.ssh/known_hosts file.
+ *
+ * @param super connection data
+ * @param mcerror pointer to the error handler
+ * @return TRUE on success, FALSE otherwise
+ *
+ * Thanks the Curl project for the code used in this function.
+ */
+static gboolean
+sftpfs_read_known_hosts (struct vfs_s_super *super, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ struct libssh2_knownhost *store = NULL;
+ int rc;
+ gboolean found = FALSE;
+
+ sftpfs_super->known_hosts = libssh2_knownhost_init (sftpfs_super->session);
+ if (sftpfs_super->known_hosts == NULL)
+ goto err;
+
+ sftpfs_super->known_hosts_file =
+ mc_build_filename (mc_config_get_home_dir (), ".ssh", "known_hosts", (char *) NULL);
+ rc = libssh2_knownhost_readfile (sftpfs_super->known_hosts, sftpfs_super->known_hosts_file,
+ LIBSSH2_KNOWNHOST_FILE_OPENSSH);
+ if (rc > 0)
+ {
+ const char *kh_name_end = NULL;
+
+ while (!found && libssh2_knownhost_get (sftpfs_super->known_hosts, &store, store) == 0)
+ {
+ /* For non-standard ports, the name will be enclosed in
+ * square brackets, followed by a colon and the port */
+ if (store == NULL)
+ continue;
+
+ if (store->name == NULL)
+ found = TRUE;
+ else if (store->name[0] != '[')
+ found = strcmp (store->name, super->path_element->host) == 0;
+ else
+ {
+ int port;
+
+ kh_name_end = strstr (store->name, "]:");
+ if (kh_name_end == NULL)
+ /* Invalid host pattern */
+ continue;
+
+ port = (int) g_ascii_strtoll (kh_name_end + 2, NULL, 10);
+ if (port == super->path_element->port)
+ {
+ size_t kh_name_size;
+
+ kh_name_size = strlen (store->name) - 1 - strlen (kh_name_end);
+ found = strncmp (store->name + 1, super->path_element->host, kh_name_size) == 0;
+ }
+ }
+ }
+ }
+
+ if (found)
+ {
+ int mask;
+ const char *hostkey_method = NULL;
+
+ mask = store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK;
+
+ switch (mask)
+ {
+#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519
+ case LIBSSH2_KNOWNHOST_KEY_ED25519:
+ hostkey_method = hostkey_method_ssh_ed25519;
+ break;
+#endif
+#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_521
+ case LIBSSH2_KNOWNHOST_KEY_ECDSA_521:
+ hostkey_method = hostkey_method_ssh_ecdsa_521;
+ break;
+#endif
+#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_384
+ case LIBSSH2_KNOWNHOST_KEY_ECDSA_384:
+ hostkey_method = hostkey_method_ssh_ecdsa_384;
+ break;
+#endif
+#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256
+ case LIBSSH2_KNOWNHOST_KEY_ECDSA_256:
+ hostkey_method = hostkey_method_ssh_ecdsa_256;
+ break;
+#endif
+ case LIBSSH2_KNOWNHOST_KEY_SSHRSA:
+ hostkey_method = hostkey_method_ssh_rsa;
+ break;
+ case LIBSSH2_KNOWNHOST_KEY_SSHDSS:
+ hostkey_method = hostkey_method_ssh_dss;
+ break;
+ case LIBSSH2_KNOWNHOST_KEY_RSA1:
+ mc_propagate_error (mcerror, 0, "%s",
+ _("sftp: found host key of unsupported type: RSA1"));
+ return FALSE;
+ default:
+ mc_propagate_error (mcerror, 0, "%s 0x%x", _("sftp: unknown host key type:"),
+ (unsigned int) mask);
+ return FALSE;
+ }
+
+ rc = libssh2_session_method_pref (sftpfs_super->session, LIBSSH2_METHOD_HOSTKEY,
+ hostkey_method);
+ if (rc < 0)
+ goto err;
+ }
+
+ return TRUE;
+
+ err:
+ {
+ int sftp_errno;
+
+ sftp_errno = libssh2_session_last_errno (sftpfs_super->session);
+ sftpfs_ssherror_to_gliberror (sftpfs_super, sftp_errno, mcerror);
+ }
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Write new host + key pair to the ~/.ssh/known_hosts file.
+ *
+ * @param super connection data
+ * @param remote_key he key for the remote host
+ * @param remote_key_len length of @remote_key
+ * @param type_mask info about format of host name, key and key type
+ * @return 0 on success, regular libssh2 error code otherwise
+ *
+ * Thanks the Curl project for the code used in this function.
+ */
+static int
+sftpfs_update_known_hosts (struct vfs_s_super *super, const char *remote_key, size_t remote_key_len,
+ int type_mask)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ int rc;
+
+ /* add this host + key pair */
+ rc = libssh2_knownhost_addc (sftpfs_super->known_hosts, super->path_element->host, NULL,
+ remote_key, remote_key_len, NULL, 0, type_mask, NULL);
+ if (rc < 0)
+ return rc;
+
+ /* write the entire in-memory list of known hosts to the known_hosts file */
+ rc = libssh2_knownhost_writefile (sftpfs_super->known_hosts, sftpfs_super->known_hosts_file,
+ LIBSSH2_KNOWNHOST_FILE_OPENSSH);
+
+ if (rc < 0)
+ return rc;
+
+ (void) message (D_NORMAL, _("Information"),
+ _("Permanently added\n%s (%s)\nto the list of known hosts."),
+ super->path_element->host, sftpfs_super->ip_address);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Compute and return readable host key fingerprint hash.
+ *
+ * @param session libssh2 session handle
+ * @return pointer to static buffer on success, NULL otherwise
+ */
+static const char *
+sftpfs_compute_fingerprint_hash (LIBSSH2_SESSION * session)
+{
+ static char result[SHA1_DIGEST_LENGTH * 3 + 1]; /* "XX:" for each byte, and EOL */
+ const char *fingerprint;
+ size_t i;
+
+ /* The fingerprint points to static storage (!), don't free() it. */
+ fingerprint = libssh2_hostkey_hash (session, LIBSSH2_HOSTKEY_HASH_SHA1);
+ if (fingerprint == NULL)
+ return NULL;
+
+ for (i = 0; i < SHA1_DIGEST_LENGTH && i * 3 < sizeof (result) - 1; i++)
+ g_snprintf ((gchar *) (result + i * 3), 4, "%02x:", (guint8) fingerprint[i]);
+
+ /* remove last ":" */
+ result[i * 3 - 1] = '\0';
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Process host info found in ~/.ssh/known_hosts file.
+ *
+ * @param super connection data
+ * @param mcerror pointer to the error handler
+ * @return TRUE on success, FALSE otherwise
+ *
+ * Thanks the Curl project for the code used in this function.
+ */
+static gboolean
+sftpfs_process_known_host (struct vfs_s_super *super, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ const char *remote_key;
+ const char *key_type;
+ const char *fingerprint_hash;
+ size_t remote_key_len = 0;
+ int remote_key_type = LIBSSH2_HOSTKEY_TYPE_UNKNOWN;
+ int keybit = 0;
+ struct libssh2_knownhost *host = NULL;
+ int rc;
+ char *msg = NULL;
+ gboolean handle_query = FALSE;
+
+ remote_key = libssh2_session_hostkey (sftpfs_super->session, &remote_key_len, &remote_key_type);
+ if (remote_key == NULL || remote_key_len == 0
+ || remote_key_type == LIBSSH2_HOSTKEY_TYPE_UNKNOWN)
+ {
+ mc_propagate_error (mcerror, 0, "%s", _("sftp: cannot get the remote host key"));
+ return FALSE;
+ }
+
+ switch (remote_key_type)
+ {
+ case LIBSSH2_HOSTKEY_TYPE_RSA:
+ keybit = LIBSSH2_KNOWNHOST_KEY_SSHRSA;
+ key_type = "RSA";
+ break;
+ case LIBSSH2_HOSTKEY_TYPE_DSS:
+ keybit = LIBSSH2_KNOWNHOST_KEY_SSHDSS;
+ key_type = "DSS";
+ break;
+#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256
+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_256:
+ keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_256;
+ key_type = "ECDSA";
+ break;
+#endif
+#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_384
+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_384:
+ keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_384;
+ key_type = "ECDSA";
+ break;
+#endif
+#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_521
+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_521:
+ keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_521;
+ key_type = "ECDSA";
+ break;
+#endif
+#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
+ case LIBSSH2_HOSTKEY_TYPE_ED25519:
+ keybit = LIBSSH2_KNOWNHOST_KEY_ED25519;
+ key_type = "ED25519";
+ break;
+#endif
+ default:
+ mc_propagate_error (mcerror, 0, "%s",
+ _("sftp: unsupported key type, can't check remote host key"));
+ return FALSE;
+ }
+
+ fingerprint_hash = sftpfs_compute_fingerprint_hash (sftpfs_super->session);
+ if (fingerprint_hash == NULL)
+ {
+ mc_propagate_error (mcerror, 0, "%s", _("sftp: can't compute host key fingerprint hash"));
+ return FALSE;
+ }
+
+ rc = libssh2_knownhost_checkp (sftpfs_super->known_hosts, super->path_element->host,
+ super->path_element->port, remote_key, remote_key_len,
+ LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW |
+ keybit, &host);
+
+ switch (rc)
+ {
+ default:
+ case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
+ /* something prevented the check to be made */
+ goto err;
+
+ case LIBSSH2_KNOWNHOST_CHECK_MATCH:
+ /* host + key pair matched -- OK */
+ break;
+
+ case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
+ /* no host match was found -- add it to the known_hosts file */
+ msg = g_strdup_printf (_("The authenticity of host\n%s (%s)\ncan't be established!\n"
+ "%s key fingerprint hash is\nSHA1:%s.\n"
+ "Do you want to add it to the list of known hosts and continue connecting?"),
+ super->path_element->host, sftpfs_super->ip_address,
+ key_type, fingerprint_hash);
+ /* Select "No" initially */
+ query_set_sel (2);
+ rc = query_dialog (_("Warning"), msg, D_NORMAL, 3, _("&Yes"), _("&Ignore"), _("&No"));
+ g_free (msg);
+ handle_query = TRUE;
+ break;
+
+ case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
+ msg = g_strdup_printf (_("%s (%s)\nis found in the list of known hosts but\n"
+ "KEYS DO NOT MATCH! THIS COULD BE A MITM ATTACK!\n"
+ "Are you sure you want to add it to the list of known hosts and continue connecting?"),
+ super->path_element->host, sftpfs_super->ip_address);
+ /* Select "No" initially */
+ query_set_sel (2);
+ rc = query_dialog (MSG_ERROR, msg, D_ERROR, 3, _("&Yes"), _("&Ignore"), _("&No"));
+ g_free (msg);
+ handle_query = TRUE;
+ break;
+ }
+
+ if (handle_query)
+ switch (rc)
+ {
+ case 0:
+ /* Yes: add this host + key pair, continue connecting */
+ if (sftpfs_update_known_hosts (super, remote_key, remote_key_len,
+ LIBSSH2_KNOWNHOST_TYPE_PLAIN
+ | LIBSSH2_KNOWNHOST_KEYENC_RAW | keybit) < 0)
+ goto err;
+ break;
+ case 1:
+ /* Ignore: do not add this host + key pair, continue connecting anyway */
+ break;
+ case 2:
+ default:
+ mc_propagate_error (mcerror, 0, "%s", _("sftp: host key verification failed"));
+ /* No: abort connection */
+ goto err;
+ }
+
+ return TRUE;
+
+ err:
+ {
+ int sftp_errno;
+
+ sftp_errno = libssh2_session_last_errno (sftpfs_super->session);
+ sftpfs_ssherror_to_gliberror (sftpfs_super, sftp_errno, mcerror);
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Recognize authentication types supported by remote side and filling internal 'super' structure by
+ * proper enum's values.
+ *
+ * @param super connection data
+ * @return TRUE if some of authentication methods is available, FALSE otherwise
+ */
+static gboolean
+sftpfs_recognize_auth_types (struct vfs_s_super *super)
+{
+ char *userauthlist;
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+
+ /* check what authentication methods are available */
+ /* userauthlist is internally managed by libssh2 and freed by libssh2_session_free() */
+ userauthlist = libssh2_userauth_list (sftpfs_super->session, super->path_element->user,
+ strlen (super->path_element->user));
+
+ if (userauthlist == NULL)
+ return FALSE;
+
+ if ((strstr (userauthlist, "password") != NULL
+ || strstr (userauthlist, "keyboard-interactive") != NULL)
+ && (sftpfs_super->config_auth_type & PASSWORD) != 0)
+ sftpfs_super->auth_type |= PASSWORD;
+
+ if (strstr (userauthlist, "publickey") != NULL
+ && (sftpfs_super->config_auth_type & PUBKEY) != 0)
+ sftpfs_super->auth_type |= PUBKEY;
+
+ if ((sftpfs_super->config_auth_type & AGENT) != 0)
+ sftpfs_super->auth_type |= AGENT;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open connection to host using SSH-agent helper.
+ *
+ * @param super connection data
+ * @param mcerror pointer to the error handler
+ * @return TRUE if connection was successfully opened, FALSE otherwise
+ */
+
+static gboolean
+sftpfs_open_connection_ssh_agent (struct vfs_s_super *super, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ struct libssh2_agent_publickey *identity, *prev_identity = NULL;
+ int rc;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ sftpfs_super->agent = NULL;
+
+ if ((sftpfs_super->auth_type & AGENT) == 0)
+ return FALSE;
+
+ /* Connect to the ssh-agent */
+ sftpfs_super->agent = libssh2_agent_init (sftpfs_super->session);
+ if (sftpfs_super->agent == NULL)
+ return FALSE;
+
+ if (libssh2_agent_connect (sftpfs_super->agent) != 0)
+ return FALSE;
+
+ if (libssh2_agent_list_identities (sftpfs_super->agent) != 0)
+ return FALSE;
+
+ while (TRUE)
+ {
+ rc = libssh2_agent_get_identity (sftpfs_super->agent, &identity, prev_identity);
+ if (rc == 1)
+ break;
+
+ if (rc < 0)
+ return FALSE;
+
+ if (libssh2_agent_userauth (sftpfs_super->agent, super->path_element->user, identity) == 0)
+ break;
+
+ prev_identity = identity;
+ }
+
+ return (rc == 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open connection to host using SSH-keypair.
+ *
+ * @param super connection data
+ * @param mcerror pointer to the error handler
+ * @return TRUE if connection was successfully opened, FALSE otherwise
+ */
+
+static gboolean
+sftpfs_open_connection_ssh_key (struct vfs_s_super *super, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ char *p, *passwd;
+ gboolean ret_value = FALSE;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if ((sftpfs_super->auth_type & PUBKEY) == 0)
+ return FALSE;
+
+ if (sftpfs_super->privkey == NULL)
+ return FALSE;
+
+ if (libssh2_userauth_publickey_fromfile (sftpfs_super->session, super->path_element->user,
+ sftpfs_super->pubkey, sftpfs_super->privkey,
+ super->path_element->password) == 0)
+ return TRUE;
+
+ p = g_strdup_printf (_("sftp: Enter passphrase for %s "), super->path_element->user);
+ passwd = vfs_get_password (p);
+ g_free (p);
+
+ if (passwd == NULL)
+ mc_propagate_error (mcerror, 0, "%s", _("sftp: Passphrase is empty."));
+ else
+ {
+ ret_value = (libssh2_userauth_publickey_fromfile (sftpfs_super->session,
+ super->path_element->user,
+ sftpfs_super->pubkey,
+ sftpfs_super->privkey, passwd) == 0);
+ g_free (passwd);
+ }
+
+ return ret_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Keyboard-interactive password helper for opening connection to host by
+ * sftpfs_open_connection_ssh_password
+ *
+ * Uses global kbi_super (data with existing connection) and kbi_passwd (password)
+ *
+ * @param name username
+ * @param name_len length of @name
+ * @param instruction unused
+ * @param instruction_len unused
+ * @param num_prompts number of possible problems to process
+ * @param prompts array of prompts to process
+ * @param responses array of responses, one per prompt
+ * @param abstract unused
+ */
+
+static
+LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC (sftpfs_keyboard_interactive_helper)
+{
+ int i;
+ size_t len;
+
+ (void) instruction;
+ (void) instruction_len;
+ (void) abstract;
+
+ if (kbi_super == NULL || kbi_passwd == NULL)
+ return;
+
+ if (strncmp (name, kbi_super->path_element->user, name_len) != 0)
+ return;
+
+ /* assume these are password prompts */
+ len = strlen (kbi_passwd);
+
+ for (i = 0; i < num_prompts; ++i)
+ if (strncmp (prompts[i].text, "Password: ", prompts[i].length) == 0)
+ {
+ responses[i].text = strdup (kbi_passwd);
+ responses[i].length = len;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open connection to host using password.
+ *
+ * @param super connection data
+ * @param mcerror pointer to the error handler
+ * @return TRUE if connection was successfully opened, FALSE otherwise
+ */
+
+static gboolean
+sftpfs_open_connection_ssh_password (struct vfs_s_super *super, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ char *p, *passwd;
+ gboolean ret_value = FALSE;
+ int rc;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if ((sftpfs_super->auth_type & PASSWORD) == 0)
+ return FALSE;
+
+ if (super->path_element->password != NULL)
+ {
+ while ((rc = libssh2_userauth_password (sftpfs_super->session, super->path_element->user,
+ super->path_element->password)) ==
+ LIBSSH2_ERROR_EAGAIN);
+ if (rc == 0)
+ return TRUE;
+
+ kbi_super = super;
+ kbi_passwd = super->path_element->password;
+
+ while ((rc =
+ libssh2_userauth_keyboard_interactive (sftpfs_super->session,
+ super->path_element->user,
+ sftpfs_keyboard_interactive_helper)) ==
+ LIBSSH2_ERROR_EAGAIN)
+ ;
+
+ kbi_super = NULL;
+ kbi_passwd = NULL;
+
+ if (rc == 0)
+ return TRUE;
+ }
+
+ p = g_strdup_printf (_("sftp: Enter password for %s "), super->path_element->user);
+ passwd = vfs_get_password (p);
+ g_free (p);
+
+ if (passwd == NULL)
+ mc_propagate_error (mcerror, 0, "%s", _("sftp: Password is empty."));
+ else
+ {
+ while ((rc = libssh2_userauth_password (sftpfs_super->session, super->path_element->user,
+ passwd)) == LIBSSH2_ERROR_EAGAIN)
+ ;
+
+ if (rc != 0)
+ {
+ kbi_super = super;
+ kbi_passwd = passwd;
+
+ while ((rc =
+ libssh2_userauth_keyboard_interactive (sftpfs_super->session,
+ super->path_element->user,
+ sftpfs_keyboard_interactive_helper)) ==
+ LIBSSH2_ERROR_EAGAIN)
+ ;
+
+ kbi_super = NULL;
+ kbi_passwd = NULL;
+ }
+
+ if (rc == 0)
+ {
+ ret_value = TRUE;
+ g_free (super->path_element->password);
+ super->path_element->password = passwd;
+ }
+ else
+ g_free (passwd);
+ }
+
+ return ret_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open new connection.
+ *
+ * @param super connection data
+ * @param mcerror pointer to the error handler
+ * @return 0 if success, -1 otherwise
+ */
+
+int
+sftpfs_open_connection (struct vfs_s_super *super, GError ** mcerror)
+{
+ int rc;
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+
+ mc_return_val_if_error (mcerror, -1);
+
+ /*
+ * The application code is responsible for creating the socket
+ * and establishing the connection
+ */
+ sftpfs_super->socket_handle = sftpfs_open_socket (super, mcerror);
+ if (sftpfs_super->socket_handle == LIBSSH2_INVALID_SOCKET)
+ return (-1);
+
+ /* Create a session instance */
+ sftpfs_super->session = libssh2_session_init ();
+ if (sftpfs_super->session == NULL)
+ return (-1);
+
+ if (!sftpfs_read_known_hosts (super, mcerror))
+ return (-1);
+
+ /* ... start it up. This will trade welcome banners, exchange keys,
+ * and setup crypto, compression, and MAC layers
+ */
+ while ((rc =
+ libssh2_session_handshake (sftpfs_super->session,
+ (libssh2_socket_t) sftpfs_super->socket_handle)) ==
+ LIBSSH2_ERROR_EAGAIN)
+ ;
+ if (rc != 0)
+ {
+ mc_propagate_error (mcerror, rc, "%s", _("sftp: failure establishing SSH session"));
+ return (-1);
+ }
+
+ if (!sftpfs_process_known_host (super, mcerror))
+ return (-1);
+
+ if (!sftpfs_recognize_auth_types (super))
+ {
+ int sftp_errno;
+
+ sftp_errno = libssh2_session_last_errno (sftpfs_super->session);
+ sftpfs_ssherror_to_gliberror (sftpfs_super, sftp_errno, mcerror);
+ return (-1);
+ }
+
+ if (!sftpfs_open_connection_ssh_agent (super, mcerror)
+ && !sftpfs_open_connection_ssh_key (super, mcerror)
+ && !sftpfs_open_connection_ssh_password (super, mcerror))
+ return (-1);
+
+ sftpfs_super->sftp_session = libssh2_sftp_init (sftpfs_super->session);
+
+ if (sftpfs_super->sftp_session == NULL)
+ return (-1);
+
+ /* Since we have not set non-blocking, tell libssh2 we are blocking */
+ libssh2_session_set_blocking (sftpfs_super->session, 1);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Close connection.
+ *
+ * @param super connection data
+ * @param shutdown_message message for shutdown functions
+ * @param mcerror pointer to the error handler
+ */
+
+void
+sftpfs_close_connection (struct vfs_s_super *super, const char *shutdown_message, GError ** mcerror)
+{
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+
+ /* no mc_return_*_if_error() here because of abort open_connection handling too */
+ (void) mcerror;
+
+ if (sftpfs_super->sftp_session != NULL)
+ {
+ libssh2_sftp_shutdown (sftpfs_super->sftp_session);
+ sftpfs_super->sftp_session = NULL;
+ }
+
+ if (sftpfs_super->agent != NULL)
+ {
+ libssh2_agent_disconnect (sftpfs_super->agent);
+ libssh2_agent_free (sftpfs_super->agent);
+ sftpfs_super->agent = NULL;
+ }
+
+ if (sftpfs_super->known_hosts != NULL)
+ {
+ libssh2_knownhost_free (sftpfs_super->known_hosts);
+ sftpfs_super->known_hosts = NULL;
+ }
+
+ MC_PTR_FREE (sftpfs_super->known_hosts_file);
+
+ if (sftpfs_super->session != NULL)
+ {
+ libssh2_session_disconnect (sftpfs_super->session, shutdown_message);
+ libssh2_session_free (sftpfs_super->session);
+ sftpfs_super->session = NULL;
+ }
+
+ if (sftpfs_super->socket_handle != LIBSSH2_INVALID_SOCKET)
+ {
+ close (sftpfs_super->socket_handle);
+ sftpfs_super->socket_handle = LIBSSH2_INVALID_SOCKET;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/sftpfs/dir.c b/src/vfs/sftpfs/dir.c
new file mode 100644
index 0000000..a19a31f
--- /dev/null
+++ b/src/vfs/sftpfs/dir.c
@@ -0,0 +1,230 @@
+/* Virtual File System: SFTP file system.
+ The internal functions: dirs
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2011
+ Slava Zanko <slavazanko@gmail.com>, 2011, 2012
+
+ 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 <libssh2.h>
+#include <libssh2_sftp.h>
+
+#include "lib/global.h"
+#include "lib/util.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ LIBSSH2_SFTP_HANDLE *handle;
+ sftpfs_super_t *super;
+} sftpfs_dir_data_t;
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open a directory stream corresponding to the directory name.
+ *
+ * @param vpath path to directory
+ * @param mcerror pointer to the error handler
+ * @return directory data handler if success, NULL otherwise
+ */
+
+void *
+sftpfs_opendir (const vfs_path_t * vpath, GError ** mcerror)
+{
+ sftpfs_dir_data_t *sftpfs_dir;
+ sftpfs_super_t *sftpfs_super;
+ const vfs_path_element_t *path_element;
+ LIBSSH2_SFTP_HANDLE *handle;
+ const GString *fixfname;
+
+ if (!sftpfs_op_init (&sftpfs_super, &path_element, vpath, mcerror))
+ return NULL;
+
+ fixfname = sftpfs_fix_filename (path_element->path);
+
+ while (TRUE)
+ {
+ int libssh_errno;
+
+ handle =
+ libssh2_sftp_open_ex (sftpfs_super->sftp_session, fixfname->str, fixfname->len, 0, 0,
+ LIBSSH2_SFTP_OPENDIR);
+ if (handle != NULL)
+ break;
+
+ libssh_errno = libssh2_session_last_errno (sftpfs_super->session);
+ if (!sftpfs_waitsocket (sftpfs_super, libssh_errno, mcerror))
+ return NULL;
+ }
+
+ sftpfs_dir = g_new0 (sftpfs_dir_data_t, 1);
+ sftpfs_dir->handle = handle;
+ sftpfs_dir->super = sftpfs_super;
+
+ return (void *) sftpfs_dir;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get a pointer to a structure representing the next directory entry.
+ *
+ * @param data directory data handler
+ * @param mcerror pointer to the error handler
+ * @return information about direntry if success, NULL otherwise
+ */
+
+struct vfs_dirent *
+sftpfs_readdir (void *data, GError ** mcerror)
+{
+ char mem[BUF_MEDIUM];
+ LIBSSH2_SFTP_ATTRIBUTES attrs;
+ sftpfs_dir_data_t *sftpfs_dir = (sftpfs_dir_data_t *) data;
+ int rc;
+
+ mc_return_val_if_error (mcerror, NULL);
+
+ do
+ {
+ rc = libssh2_sftp_readdir (sftpfs_dir->handle, mem, sizeof (mem), &attrs);
+ if (rc >= 0)
+ break;
+
+ if (!sftpfs_waitsocket (sftpfs_dir->super, rc, mcerror))
+ return NULL;
+ }
+ while (rc == LIBSSH2_ERROR_EAGAIN);
+
+ return (rc != 0 ? vfs_dirent_init (NULL, mem, 0) : NULL); /* FIXME: inode */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Close the directory stream.
+ *
+ * @param data directory data handler
+ * @param mcerror pointer to the error handler
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_closedir (void *data, GError ** mcerror)
+{
+ int rc;
+ sftpfs_dir_data_t *sftpfs_dir = (sftpfs_dir_data_t *) data;
+
+ mc_return_val_if_error (mcerror, -1);
+
+ rc = libssh2_sftp_closedir (sftpfs_dir->handle);
+ g_free (sftpfs_dir);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create a new directory.
+ *
+ * @param vpath path directory
+ * @param mode mode (see man 2 open)
+ * @param mcerror pointer to the error handler
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_mkdir (const vfs_path_t * vpath, mode_t mode, GError ** mcerror)
+{
+ int res;
+ sftpfs_super_t *sftpfs_super;
+ const vfs_path_element_t *path_element;
+ const GString *fixfname;
+
+ if (!sftpfs_op_init (&sftpfs_super, &path_element, vpath, mcerror))
+ return -1;
+
+ fixfname = sftpfs_fix_filename (path_element->path);
+
+ do
+ {
+ res =
+ libssh2_sftp_mkdir_ex (sftpfs_super->sftp_session, fixfname->str, fixfname->len, mode);
+ if (res >= 0)
+ break;
+
+ if (!sftpfs_waitsocket (sftpfs_super, res, mcerror))
+ return -1;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Remove a directory.
+ *
+ * @param vpath path directory
+ * @param mcerror pointer to the error handler
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_rmdir (const vfs_path_t * vpath, GError ** mcerror)
+{
+ int res;
+ sftpfs_super_t *sftpfs_super;
+ const vfs_path_element_t *path_element;
+ const GString *fixfname;
+
+ if (!sftpfs_op_init (&sftpfs_super, &path_element, vpath, mcerror))
+ return -1;
+
+ fixfname = sftpfs_fix_filename (path_element->path);
+
+ do
+ {
+ res = libssh2_sftp_rmdir_ex (sftpfs_super->sftp_session, fixfname->str, fixfname->len);
+ if (res >= 0)
+ break;
+
+ if (!sftpfs_waitsocket (sftpfs_super, res, mcerror))
+ return -1;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/sftpfs/file.c b/src/vfs/sftpfs/file.c
new file mode 100644
index 0000000..4146239
--- /dev/null
+++ b/src/vfs/sftpfs/file.c
@@ -0,0 +1,424 @@
+/* Virtual File System: SFTP file system.
+ The internal functions: files
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2011
+ Slava Zanko <slavazanko@gmail.com>, 2011, 2012
+
+ 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 <errno.h> /* ENOENT, EACCES */
+
+#include <libssh2.h>
+#include <libssh2_sftp.h>
+
+#include "lib/global.h"
+#include "lib/util.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define SFTP_FILE_HANDLER(a) ((sftpfs_file_handler_t *) a)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ vfs_file_handler_t base; /* base class */
+
+ LIBSSH2_SFTP_HANDLE *handle;
+ int flags;
+ mode_t mode;
+} sftpfs_file_handler_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Reopen file by file handle.
+ *
+ * @param fh the file handler
+ * @param mcerror pointer to the error handler
+ */
+static void
+sftpfs_reopen (vfs_file_handler_t * fh, GError ** mcerror)
+{
+ sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh);
+ int flags;
+ mode_t mode;
+
+ g_return_if_fail (mcerror == NULL || *mcerror == NULL);
+
+ flags = file->flags;
+ mode = file->mode;
+
+ sftpfs_close_file (fh, mcerror);
+ sftpfs_open_file (fh, flags, mode, mcerror);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sftpfs_file__handle_error (sftpfs_super_t * super, int sftp_res, GError ** mcerror)
+{
+ if (sftpfs_is_sftp_error (super->sftp_session, sftp_res, LIBSSH2_FX_PERMISSION_DENIED))
+ return -EACCES;
+
+ if (sftpfs_is_sftp_error (super->sftp_session, sftp_res, LIBSSH2_FX_NO_SUCH_FILE))
+ return -ENOENT;
+
+ if (!sftpfs_waitsocket (super, sftp_res, mcerror))
+ return -1;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+vfs_file_handler_t *
+sftpfs_fh_new (struct vfs_s_inode * ino, gboolean changed)
+{
+ sftpfs_file_handler_t *fh;
+
+ fh = g_new0 (sftpfs_file_handler_t, 1);
+ vfs_s_init_fh (VFS_FILE_HANDLER (fh), ino, changed);
+
+ return VFS_FILE_HANDLER (fh);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Open new SFTP file.
+ *
+ * @param fh the file handler
+ * @param flags flags (see man 2 open)
+ * @param mode mode (see man 2 open)
+ * @param mcerror pointer to the error handler
+ * @return TRUE if connection was created successfully, FALSE otherwise
+ */
+
+gboolean
+sftpfs_open_file (vfs_file_handler_t * fh, int flags, mode_t mode, GError ** mcerror)
+{
+ unsigned long sftp_open_flags = 0;
+ int sftp_open_mode = 0;
+ gboolean do_append = FALSE;
+ sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh);
+ sftpfs_super_t *super = SFTP_SUPER (fh->ino->super);
+ char *name;
+ const GString *fixfname;
+
+ (void) mode;
+ mc_return_val_if_error (mcerror, FALSE);
+
+ name = vfs_s_fullpath (vfs_sftpfs_ops, fh->ino);
+ if (name == NULL)
+ return FALSE;
+
+ if ((flags & O_CREAT) != 0 || (flags & O_WRONLY) != 0)
+ {
+ sftp_open_flags = (flags & O_WRONLY) != 0 ? LIBSSH2_FXF_WRITE : 0;
+ sftp_open_flags |= (flags & O_CREAT) != 0 ? LIBSSH2_FXF_CREAT : 0;
+ if ((flags & O_APPEND) != 0)
+ {
+ sftp_open_flags |= LIBSSH2_FXF_APPEND;
+ do_append = TRUE;
+ }
+ sftp_open_flags |= (flags & O_TRUNC) != 0 ? LIBSSH2_FXF_TRUNC : 0;
+
+ sftp_open_mode = LIBSSH2_SFTP_S_IRUSR |
+ LIBSSH2_SFTP_S_IWUSR | LIBSSH2_SFTP_S_IRGRP | LIBSSH2_SFTP_S_IROTH;
+ }
+ else
+ sftp_open_flags = LIBSSH2_FXF_READ;
+
+ fixfname = sftpfs_fix_filename (name);
+
+ while (TRUE)
+ {
+ int libssh_errno;
+
+ file->handle =
+ libssh2_sftp_open_ex (super->sftp_session, fixfname->str, fixfname->len,
+ sftp_open_flags, sftp_open_mode, LIBSSH2_SFTP_OPENFILE);
+ if (file->handle != NULL)
+ break;
+
+ libssh_errno = libssh2_session_last_errno (super->session);
+ if (libssh_errno != LIBSSH2_ERROR_EAGAIN)
+ {
+ sftpfs_ssherror_to_gliberror (super, libssh_errno, mcerror);
+ g_free (name);
+ return FALSE;
+ }
+ }
+
+ g_free (name);
+
+ file->flags = flags;
+ file->mode = mode;
+
+ if (do_append)
+ {
+ struct stat file_info = {
+ .st_dev = 0
+ };
+ /* In case of
+
+ struct stat file_info = { 0 };
+
+ gcc < 4.7 [1] generates the following:
+
+ error: missing initializer [-Werror=missing-field-initializers]
+ error: (near initialization for 'file_info.st_dev') [-Werror=missing-field-initializers]
+
+ [1] http://stackoverflow.com/questions/13373695/how-to-remove-the-warning-in-gcc-4-6-missing-initializer-wmissing-field-initi/27461062#27461062
+ */
+
+ if (sftpfs_fstat (fh, &file_info, mcerror) == 0)
+ libssh2_sftp_seek64 (file->handle, file_info.st_size);
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Stats the file specified by the file descriptor.
+ *
+ * @param data file handler
+ * @param buf buffer for store stat-info
+ * @param mcerror pointer to the error handler
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_fstat (void *data, struct stat *buf, GError ** mcerror)
+{
+ int res;
+ LIBSSH2_SFTP_ATTRIBUTES attrs;
+ vfs_file_handler_t *fh = VFS_FILE_HANDLER (data);
+ sftpfs_file_handler_t *sftpfs_fh = (sftpfs_file_handler_t *) data;
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+
+ mc_return_val_if_error (mcerror, -1);
+
+ if (sftpfs_fh->handle == NULL)
+ return -1;
+
+ do
+ {
+ int err;
+
+ res = libssh2_sftp_fstat_ex (sftpfs_fh->handle, &attrs, 0);
+ if (res >= 0)
+ break;
+
+ err = sftpfs_file__handle_error (sftpfs_super, res, mcerror);
+ if (err < 0)
+ return err;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ sftpfs_attr_to_stat (&attrs, buf);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Read up to 'count' bytes from the file descriptor 'fh' to the buffer starting at 'buffer'.
+ *
+ * @param fh file handler
+ * @param buffer buffer for data
+ * @param count data size
+ * @param mcerror pointer to the error handler
+ *
+ * @return 0 on success, negative value otherwise
+ */
+
+ssize_t
+sftpfs_read_file (vfs_file_handler_t * fh, char *buffer, size_t count, GError ** mcerror)
+{
+ ssize_t rc;
+ sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh);
+ sftpfs_super_t *super;
+
+ mc_return_val_if_error (mcerror, -1);
+
+ if (fh == NULL)
+ {
+ mc_propagate_error (mcerror, 0, "%s",
+ _("sftp: No file handler data present for reading file"));
+ return -1;
+ }
+
+ super = SFTP_SUPER (VFS_FILE_HANDLER_SUPER (fh));
+
+ do
+ {
+ int err;
+
+ rc = libssh2_sftp_read (file->handle, buffer, count);
+ if (rc >= 0)
+ break;
+
+ err = sftpfs_file__handle_error (super, (int) rc, mcerror);
+ if (err < 0)
+ return err;
+ }
+ while (rc == LIBSSH2_ERROR_EAGAIN);
+
+ fh->pos = (off_t) libssh2_sftp_tell64 (file->handle);
+
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Write up to 'count' bytes from the buffer starting at 'buffer' to the descriptor 'fh'.
+ *
+ * @param fh file handler
+ * @param buffer buffer for data
+ * @param count data size
+ * @param mcerror pointer to the error handler
+ *
+ * @return 0 on success, negative value otherwise
+ */
+
+ssize_t
+sftpfs_write_file (vfs_file_handler_t * fh, const char *buffer, size_t count, GError ** mcerror)
+{
+ ssize_t rc;
+ sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh);
+ sftpfs_super_t *super = SFTP_SUPER (VFS_FILE_HANDLER_SUPER (fh));
+
+ mc_return_val_if_error (mcerror, -1);
+
+ fh->pos = (off_t) libssh2_sftp_tell64 (file->handle);
+
+ do
+ {
+ int err;
+
+ rc = libssh2_sftp_write (file->handle, buffer, count);
+ if (rc >= 0)
+ break;
+
+ err = sftpfs_file__handle_error (super, (int) rc, mcerror);
+ if (err < 0)
+ return err;
+ }
+ while (rc == LIBSSH2_ERROR_EAGAIN);
+
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Close a file descriptor.
+ *
+ * @param fh file handler
+ * @param mcerror pointer to the error handler
+ *
+ * @return 0 on success, negative value otherwise
+ */
+
+int
+sftpfs_close_file (vfs_file_handler_t * fh, GError ** mcerror)
+{
+ int ret;
+
+ mc_return_val_if_error (mcerror, -1);
+
+ ret = libssh2_sftp_close (SFTP_FILE_HANDLER (fh)->handle);
+
+ return ret == 0 ? 0 : -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Reposition the offset of the open file associated with the file descriptor.
+ *
+ * @param fh file handler
+ * @param offset file offset
+ * @param whence method of seek (at begin, at current, at end)
+ * @param mcerror pointer to the error handler
+ *
+ * @return 0 on success, negative value otherwise
+ */
+
+off_t
+sftpfs_lseek (vfs_file_handler_t * fh, off_t offset, int whence, GError ** mcerror)
+{
+ sftpfs_file_handler_t *file = SFTP_FILE_HANDLER (fh);
+
+ mc_return_val_if_error (mcerror, 0);
+
+ switch (whence)
+ {
+ case SEEK_SET:
+ /* Need reopen file because:
+ "You MUST NOT seek during writing or reading a file with SFTP, as the internals use
+ outstanding packets and changing the "file position" during transit will results in
+ badness." */
+ if (fh->pos > offset || offset == 0)
+ {
+ sftpfs_reopen (fh, mcerror);
+ mc_return_val_if_error (mcerror, 0);
+ }
+ fh->pos = offset;
+ break;
+ case SEEK_CUR:
+ fh->pos += offset;
+ break;
+ case SEEK_END:
+ if (fh->pos > fh->ino->st.st_size - offset)
+ {
+ sftpfs_reopen (fh, mcerror);
+ mc_return_val_if_error (mcerror, 0);
+ }
+ fh->pos = fh->ino->st.st_size - offset;
+ break;
+ default:
+ break;
+ }
+
+ libssh2_sftp_seek64 (file->handle, fh->pos);
+ fh->pos = (off_t) libssh2_sftp_tell64 (file->handle);
+
+ return fh->pos;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/sftpfs/internal.c b/src/vfs/sftpfs/internal.c
new file mode 100644
index 0000000..9faa76c
--- /dev/null
+++ b/src/vfs/sftpfs/internal.c
@@ -0,0 +1,621 @@
+/* Virtual File System: SFTP file system.
+ The internal functions
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2011
+ Slava Zanko <slavazanko@gmail.com>, 2011, 2012
+
+ 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 <errno.h>
+
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#else
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#include "lib/global.h"
+#include "lib/util.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+GString *sftpfs_filename_buffer = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Adjust block size and number of blocks */
+
+static void
+sftpfs_blksize (struct stat *s)
+{
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ s->st_blksize = LIBSSH2_CHANNEL_WINDOW_DEFAULT; /* FIXME */
+#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
+ vfs_adjust_stat (s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Awaiting for any activity on socket.
+ *
+ * @param super extra data for SFTP connection
+ * @param mcerror pointer to the error object
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_internal_waitsocket (sftpfs_super_t * super, GError ** mcerror)
+{
+ struct timeval timeout = { 10, 0 };
+ fd_set fd;
+ fd_set *writefd = NULL;
+ fd_set *readfd = NULL;
+ int dir, ret;
+
+ mc_return_val_if_error (mcerror, -1);
+
+ FD_ZERO (&fd);
+ FD_SET (super->socket_handle, &fd);
+
+ /* now make sure we wait in the correct direction */
+ dir = libssh2_session_block_directions (super->session);
+
+ if ((dir & LIBSSH2_SESSION_BLOCK_INBOUND) != 0)
+ readfd = &fd;
+
+ if ((dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) != 0)
+ writefd = &fd;
+
+ ret = select (super->socket_handle + 1, readfd, writefd, NULL, &timeout);
+ if (ret < 0)
+ {
+ int my_errno = errno;
+
+ mc_propagate_error (mcerror, my_errno, _("sftp: socket error: %s"),
+ unix_error_string (my_errno));
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sftpfs_stat_init (sftpfs_super_t ** super, const vfs_path_element_t ** path_element,
+ const vfs_path_t * vpath, GError ** mcerror, int stat_type,
+ LIBSSH2_SFTP_ATTRIBUTES * attrs)
+{
+ const GString *fixfname;
+ int res;
+
+ if (!sftpfs_op_init (super, path_element, vpath, mcerror))
+ return -1;
+
+ fixfname = sftpfs_fix_filename ((*path_element)->path);
+
+ do
+ {
+ res = libssh2_sftp_stat_ex ((*super)->sftp_session, fixfname->str, fixfname->len,
+ stat_type, attrs);
+ if (res >= 0)
+ break;
+
+ if (sftpfs_is_sftp_error ((*super)->sftp_session, res, LIBSSH2_FX_PERMISSION_DENIED))
+ return -EACCES;
+
+ if (sftpfs_is_sftp_error ((*super)->sftp_session, res, LIBSSH2_FX_NO_SUCH_FILE))
+ return -ENOENT;
+
+ if (!sftpfs_waitsocket (*super, res, mcerror))
+ return -1;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+sftpfs_waitsocket (sftpfs_super_t * super, int sftp_res, GError ** mcerror)
+{
+ if (sftp_res != LIBSSH2_ERROR_EAGAIN)
+ {
+ sftpfs_ssherror_to_gliberror (super, sftp_res, mcerror);
+ return FALSE;
+ }
+
+ sftpfs_internal_waitsocket (super, mcerror);
+
+ return (mcerror == NULL || *mcerror == NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+sftpfs_is_sftp_error (LIBSSH2_SFTP * sftp_session, int sftp_res, int sftp_error)
+{
+ return (sftp_res == LIBSSH2_ERROR_SFTP_PROTOCOL &&
+ libssh2_sftp_last_error (sftp_session) == (unsigned long) sftp_error);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Convert libssh error to GError object.
+ *
+ * @param super extra data for SFTP connection
+ * @param libssh_errno errno from libssh
+ * @param mcerror pointer to the error object
+ */
+
+void
+sftpfs_ssherror_to_gliberror (sftpfs_super_t * super, int libssh_errno, GError ** mcerror)
+{
+ char *err = NULL;
+ int err_len;
+
+ mc_return_if_error (mcerror);
+
+ libssh2_session_last_error (super->session, &err, &err_len, 1);
+ if (libssh_errno == LIBSSH2_ERROR_SFTP_PROTOCOL && super->sftp_session != NULL)
+ mc_propagate_error (mcerror, libssh_errno, "%s %lu", err,
+ libssh2_sftp_last_error (super->sftp_session));
+ else
+ mc_propagate_error (mcerror, libssh_errno, "%s", err);
+ g_free (err);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Fix filename for SFTP operations: add leading slash to file name.
+ *
+ * @param file_name file name
+ * @param length length of returned string
+ *
+ * @return pointer to string that contains the file name with leading slash
+ */
+
+const GString *
+sftpfs_fix_filename (const char *file_name)
+{
+ g_string_printf (sftpfs_filename_buffer, "%c%s", PATH_SEP, file_name);
+ return sftpfs_filename_buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+sftpfs_op_init (sftpfs_super_t ** super, const vfs_path_element_t ** path_element,
+ const vfs_path_t * vpath, GError ** mcerror)
+{
+ struct vfs_s_super *lc_super = NULL;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if (vfs_s_get_path (vpath, &lc_super, 0) == NULL)
+ return FALSE;
+
+ if (lc_super == NULL)
+ return FALSE;
+
+ *super = SFTP_SUPER (lc_super);
+ if ((*super)->sftp_session == NULL)
+ return FALSE;
+
+ *path_element = vfs_path_get_by_index (vpath, -1);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+sftpfs_attr_to_stat (const LIBSSH2_SFTP_ATTRIBUTES * attrs, struct stat *s)
+{
+ if ((attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) != 0)
+ {
+ s->st_uid = attrs->uid;
+ s->st_gid = attrs->gid;
+ }
+
+ if ((attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) != 0)
+ {
+ s->st_atime = attrs->atime;
+ s->st_mtime = attrs->mtime;
+ s->st_ctime = attrs->mtime;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ s->st_atim.tv_nsec = s->st_mtim.tv_nsec = s->st_ctim.tv_nsec = 0;
+#endif
+ }
+
+ if ((attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) != 0)
+ {
+ s->st_size = attrs->filesize;
+ sftpfs_blksize (s);
+ }
+
+ if ((attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) != 0)
+ s->st_mode = attrs->permissions;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Getting information about a symbolic link.
+ *
+ * @param vpath path to file, directory or symbolic link
+ * @param buf buffer for store stat-info
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_lstat (const vfs_path_t * vpath, struct stat *buf, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const vfs_path_element_t *path_element = NULL;
+ LIBSSH2_SFTP_ATTRIBUTES attrs;
+ int res;
+
+ res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_LSTAT, &attrs);
+ if (res >= 0)
+ {
+ sftpfs_attr_to_stat (&attrs, buf);
+ res = 0;
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Getting information about a file or directory.
+ *
+ * @param vpath path to file or directory
+ * @param buf buffer for store stat-info
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_stat (const vfs_path_t * vpath, struct stat *buf, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const vfs_path_element_t *path_element = NULL;
+ LIBSSH2_SFTP_ATTRIBUTES attrs;
+ int res;
+
+ res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_STAT, &attrs);
+ if (res >= 0)
+ {
+ buf->st_nlink = 1;
+ sftpfs_attr_to_stat (&attrs, buf);
+ res = 0;
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Read value of a symbolic link.
+ *
+ * @param vpath path to file or directory
+ * @param buf buffer for store stat-info
+ * @param size buffer size
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_readlink (const vfs_path_t * vpath, char *buf, size_t size, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const vfs_path_element_t *path_element = NULL;
+ const GString *fixfname;
+ int res;
+
+ if (!sftpfs_op_init (&super, &path_element, vpath, mcerror))
+ return -1;
+
+ fixfname = sftpfs_fix_filename (path_element->path);
+
+ do
+ {
+ res =
+ libssh2_sftp_symlink_ex (super->sftp_session, fixfname->str, fixfname->len, buf, size,
+ LIBSSH2_SFTP_READLINK);
+ if (res >= 0)
+ break;
+
+ if (!sftpfs_waitsocket (super, res, mcerror))
+ return -1;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create symlink to file or directory
+ *
+ * @param vpath1 path to file or directory
+ * @param vpath2 path to symlink
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const vfs_path_element_t *path_element2 = NULL;
+ const char *path1;
+ size_t path1_len;
+ const GString *ctmp_path;
+ char *tmp_path;
+ unsigned int tmp_path_len;
+ int res;
+
+ if (!sftpfs_op_init (&super, &path_element2, vpath2, mcerror))
+ return -1;
+
+ ctmp_path = sftpfs_fix_filename (path_element2->path);
+ tmp_path = g_strndup (ctmp_path->str, ctmp_path->len);
+ tmp_path_len = ctmp_path->len;
+
+ path1 = vfs_path_get_last_path_str (vpath1);
+ path1_len = strlen (path1);
+
+ do
+ {
+ res =
+ libssh2_sftp_symlink_ex (super->sftp_session, path1, path1_len, tmp_path, tmp_path_len,
+ LIBSSH2_SFTP_SYMLINK);
+ if (res >= 0)
+ break;
+
+ if (!sftpfs_waitsocket (super, res, mcerror))
+ {
+ g_free (tmp_path);
+ return -1;
+ }
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+ g_free (tmp_path);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Changes the times of the file.
+ *
+ * @param vpath path to file or directory
+ * @param atime access time
+ * @param mtime modification time
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_utime (const vfs_path_t * vpath, time_t atime, time_t mtime, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const vfs_path_element_t *path_element = NULL;
+ LIBSSH2_SFTP_ATTRIBUTES attrs;
+ const GString *fixfname;
+ int res;
+
+ res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_LSTAT, &attrs);
+ if (res < 0)
+ return res;
+
+ attrs.flags = LIBSSH2_SFTP_ATTR_ACMODTIME;
+ attrs.atime = atime;
+ attrs.mtime = mtime;
+
+ fixfname = sftpfs_fix_filename (path_element->path);
+
+ do
+ {
+ res =
+ libssh2_sftp_stat_ex (super->sftp_session, fixfname->str, fixfname->len,
+ LIBSSH2_SFTP_SETSTAT, &attrs);
+ if (res >= 0)
+ break;
+
+ if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_NO_SUCH_FILE))
+ return -ENOENT;
+
+ if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_FAILURE))
+ {
+ res = 0; /* need something like ftpfs_ignore_chattr_errors */
+ break;
+ }
+
+ if (!sftpfs_waitsocket (super, res, mcerror))
+ return -1;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Changes the permissions of the file.
+ *
+ * @param vpath path to file or directory
+ * @param mode mode (see man 2 open)
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_chmod (const vfs_path_t * vpath, mode_t mode, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const vfs_path_element_t *path_element = NULL;
+ LIBSSH2_SFTP_ATTRIBUTES attrs;
+ const GString *fixfname;
+ int res;
+
+ res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_LSTAT, &attrs);
+ if (res < 0)
+ return res;
+
+ attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS;
+ attrs.permissions = mode;
+
+ fixfname = sftpfs_fix_filename (path_element->path);
+
+ do
+ {
+ res =
+ libssh2_sftp_stat_ex (super->sftp_session, fixfname->str, fixfname->len,
+ LIBSSH2_SFTP_SETSTAT, &attrs);
+ if (res >= 0)
+ break;
+
+ if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_NO_SUCH_FILE))
+ return -ENOENT;
+
+ if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_FAILURE))
+ {
+ res = 0; /* need something like ftpfs_ignore_chattr_errors */
+ break;
+ }
+
+ if (!sftpfs_waitsocket (super, res, mcerror))
+ return -1;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Delete a name from the file system.
+ *
+ * @param vpath path to file or directory
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_unlink (const vfs_path_t * vpath, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const vfs_path_element_t *path_element = NULL;
+ const GString *fixfname;
+ int res;
+
+ if (!sftpfs_op_init (&super, &path_element, vpath, mcerror))
+ return -1;
+
+ fixfname = sftpfs_fix_filename (path_element->path);
+
+ do
+ {
+ res = libssh2_sftp_unlink_ex (super->sftp_session, fixfname->str, fixfname->len);
+ if (res >= 0)
+ break;
+
+ if (!sftpfs_waitsocket (super, res, mcerror))
+ return -1;
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Rename a file, moving it between directories if required.
+ *
+ * @param vpath1 path to source file or directory
+ * @param vpath2 path to destination file or directory
+ * @param mcerror pointer to error object
+ * @return 0 if success, negative value otherwise
+ */
+
+int
+sftpfs_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2, GError ** mcerror)
+{
+ sftpfs_super_t *super = NULL;
+ const char *path1;
+ const vfs_path_element_t *path_element2 = NULL;
+ const GString *ctmp_path;
+ char *tmp_path;
+ unsigned int tmp_path_len;
+ const GString *fixfname;
+ int res;
+
+ if (!sftpfs_op_init (&super, &path_element2, vpath2, mcerror))
+ return -1;
+
+ ctmp_path = sftpfs_fix_filename (path_element2->path);
+ tmp_path = g_strndup (ctmp_path->str, ctmp_path->len);
+ tmp_path_len = ctmp_path->len;
+
+ path1 = vfs_path_get_last_path_str (vpath1);
+ fixfname = sftpfs_fix_filename (path1);
+
+ do
+ {
+ res =
+ libssh2_sftp_rename_ex (super->sftp_session, fixfname->str, fixfname->len, tmp_path,
+ tmp_path_len, LIBSSH2_SFTP_SYMLINK);
+ if (res >= 0)
+ break;
+
+ if (!sftpfs_waitsocket (super, res, mcerror))
+ {
+ g_free (tmp_path);
+ return -1;
+ }
+ }
+ while (res == LIBSSH2_ERROR_EAGAIN);
+ g_free (tmp_path);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/sftpfs/internal.h b/src/vfs/sftpfs/internal.h
new file mode 100644
index 0000000..15c547d
--- /dev/null
+++ b/src/vfs/sftpfs/internal.h
@@ -0,0 +1,114 @@
+/**
+ * \file
+ * \brief Header: SFTP FS
+ */
+
+#ifndef MC__VFS_SFTPFS_INTERNAL_H
+#define MC__VFS_SFTPFS_INTERNAL_H
+
+#include <libssh2.h>
+#include <libssh2_sftp.h>
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/xdirentry.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define SFTP_DEFAULT_PORT 22
+
+/* LIBSSH2_INVALID_SOCKET is defined in libssh2 >= 1.4.1 */
+#ifndef LIBSSH2_INVALID_SOCKET
+#define LIBSSH2_INVALID_SOCKET -1
+#endif
+
+#define SFTP_SUPER(super) ((sftpfs_super_t *) (super))
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ NONE = 0,
+ PUBKEY = (1 << 0),
+ PASSWORD = (1 << 1),
+ AGENT = (1 << 2)
+} sftpfs_auth_type_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ struct vfs_s_super base;
+
+ sftpfs_auth_type_t auth_type;
+ sftpfs_auth_type_t config_auth_type;
+
+ LIBSSH2_KNOWNHOSTS *known_hosts;
+ char *known_hosts_file;
+
+ LIBSSH2_SESSION *session;
+ LIBSSH2_SFTP *sftp_session;
+
+ LIBSSH2_AGENT *agent;
+
+ char *pubkey;
+ char *privkey;
+
+ int socket_handle;
+ const char *ip_address;
+ vfs_path_element_t *original_connection_info;
+} sftpfs_super_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern GString *sftpfs_filename_buffer;
+extern struct vfs_s_subclass sftpfs_subclass;
+extern struct vfs_class *vfs_sftpfs_ops;
+
+/*** declarations of public functions ************************************************************/
+
+void sftpfs_init_config_variables_patterns (void);
+void sftpfs_deinit_config_variables_patterns (void);
+
+gboolean sftpfs_is_sftp_error (LIBSSH2_SFTP * sftp_session, int sftp_res, int sftp_error);
+void sftpfs_ssherror_to_gliberror (sftpfs_super_t * super, int libssh_errno, GError ** mcerror);
+gboolean sftpfs_waitsocket (sftpfs_super_t * super, int sftp_res, GError ** mcerror);
+
+const GString *sftpfs_fix_filename (const char *file_name);
+
+gboolean sftpfs_op_init (sftpfs_super_t ** super, const vfs_path_element_t ** path_element,
+ const vfs_path_t * vpath, GError ** mcerror);
+
+void sftpfs_attr_to_stat (const LIBSSH2_SFTP_ATTRIBUTES * attrs, struct stat *s);
+int sftpfs_lstat (const vfs_path_t * vpath, struct stat *buf, GError ** mcerror);
+int sftpfs_stat (const vfs_path_t * vpath, struct stat *buf, GError ** mcerror);
+int sftpfs_readlink (const vfs_path_t * vpath, char *buf, size_t size, GError ** mcerror);
+int sftpfs_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2, GError ** mcerror);
+int sftpfs_utime (const vfs_path_t * vpath, time_t atime, time_t mtime, GError ** mcerror);
+int sftpfs_chmod (const vfs_path_t * vpath, mode_t mode, GError ** mcerror);
+int sftpfs_unlink (const vfs_path_t * vpath, GError ** mcerror);
+int sftpfs_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2, GError ** mcerror);
+
+void sftpfs_fill_connection_data_from_config (struct vfs_s_super *super, GError ** mcerror);
+int sftpfs_open_connection (struct vfs_s_super *super, GError ** mcerror);
+void sftpfs_close_connection (struct vfs_s_super *super, const char *shutdown_message,
+ GError ** mcerror);
+
+vfs_file_handler_t *sftpfs_fh_new (struct vfs_s_inode *ino, gboolean changed);
+
+void *sftpfs_opendir (const vfs_path_t * vpath, GError ** mcerror);
+struct vfs_dirent *sftpfs_readdir (void *data, GError ** mcerror);
+int sftpfs_closedir (void *data, GError ** mcerror);
+int sftpfs_mkdir (const vfs_path_t * vpath, mode_t mode, GError ** mcerror);
+int sftpfs_rmdir (const vfs_path_t * vpath, GError ** mcerror);
+
+gboolean sftpfs_open_file (vfs_file_handler_t * fh, int flags, mode_t mode, GError ** mcerror);
+ssize_t sftpfs_read_file (vfs_file_handler_t * fh, char *buffer, size_t count, GError ** mcerror);
+ssize_t sftpfs_write_file (vfs_file_handler_t * fh, const char *buffer, size_t count,
+ GError ** mcerror);
+int sftpfs_close_file (vfs_file_handler_t * fh, GError ** mcerror);
+int sftpfs_fstat (void *data, struct stat *buf, GError ** mcerror);
+off_t sftpfs_lseek (vfs_file_handler_t * fh, off_t offset, int whence, GError ** mcerror);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_SFTPFS_INTERNAL_H */
diff --git a/src/vfs/sftpfs/sftpfs.c b/src/vfs/sftpfs/sftpfs.c
new file mode 100644
index 0000000..f2cc592
--- /dev/null
+++ b/src/vfs/sftpfs/sftpfs.c
@@ -0,0 +1,866 @@
+/* Virtual File System: SFTP file system.
+ The interface function
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2011
+ Slava Zanko <slavazanko@gmail.com>, 2011-2013
+ Andrew Borodin <aborodin@vmail.ru>, 2021-2022
+
+ 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 <errno.h>
+#include <stdlib.h>
+#include <string.h> /* memset() */
+
+#include "lib/global.h"
+#include "lib/vfs/netutil.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/gc.h"
+#include "lib/widget.h"
+#include "lib/tty/tty.h" /* tty_enable_interrupt_key () */
+
+#include "internal.h"
+
+#include "sftpfs.h"
+
+/*** global variables ****************************************************************************/
+
+struct vfs_s_subclass sftpfs_subclass;
+struct vfs_class *vfs_sftpfs_ops = VFS_CLASS (&sftpfs_subclass); /* used in file.c */
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for VFS-class init action.
+ *
+ * @param me structure of VFS class
+ */
+
+static int
+sftpfs_cb_init (struct vfs_class *me)
+{
+ (void) me;
+
+ if (libssh2_init (0) != 0)
+ return 0;
+
+ sftpfs_filename_buffer = g_string_new ("");
+ sftpfs_init_config_variables_patterns ();
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for VFS-class deinit action.
+ *
+ * @param me structure of VFS class
+ */
+
+static void
+sftpfs_cb_done (struct vfs_class *me)
+{
+ (void) me;
+
+ sftpfs_deinit_config_variables_patterns ();
+ g_string_free (sftpfs_filename_buffer, TRUE);
+ libssh2_exit ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for opening file.
+ *
+ * @param vpath path to file
+ * @param flags flags (see man 2 open)
+ * @param mode mode (see man 2 open)
+ * @return file data handler if success, NULL otherwise
+ */
+
+static void *
+sftpfs_cb_open (const vfs_path_t * vpath, int flags, mode_t mode)
+{
+ vfs_file_handler_t *fh;
+ struct vfs_class *me;
+ struct vfs_s_super *super;
+ const char *path_super;
+ struct vfs_s_inode *path_inode;
+ GError *mcerror = NULL;
+ gboolean is_changed = FALSE;
+
+ path_super = vfs_s_get_path (vpath, &super, 0);
+ if (path_super == NULL)
+ return NULL;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ path_inode = vfs_s_find_inode (me, super, path_super, LINK_FOLLOW, FL_NONE);
+ if (path_inode != NULL && ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)))
+ {
+ me->verrno = EEXIST;
+ return NULL;
+ }
+
+ if (path_inode == NULL)
+ {
+ char *name;
+ struct vfs_s_entry *ent;
+ struct vfs_s_inode *dir;
+
+ name = g_path_get_dirname (path_super);
+ dir = vfs_s_find_inode (me, super, name, LINK_FOLLOW, FL_DIR);
+ g_free (name);
+ if (dir == NULL)
+ return NULL;
+
+ name = g_path_get_basename (path_super);
+ ent = vfs_s_generate_entry (me, name, dir, 0755);
+ g_free (name);
+ path_inode = ent->ino;
+ vfs_s_insert_entry (me, dir, ent);
+ is_changed = TRUE;
+ }
+
+ if (S_ISDIR (path_inode->st.st_mode))
+ {
+ me->verrno = EISDIR;
+ return NULL;
+ }
+
+ fh = sftpfs_fh_new (path_inode, is_changed);
+
+ if (!sftpfs_open_file (fh, flags, mode, &mcerror))
+ {
+ mc_error_message (&mcerror, NULL);
+ g_free (fh);
+ return NULL;
+ }
+
+ vfs_rmstamp (me, (vfsid) super);
+ super->fd_usage++;
+ fh->ino->st.st_nlink++;
+ return fh;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for opening directory.
+ *
+ * @param vpath path to directory
+ * @return directory data handler if success, NULL otherwise
+ */
+
+static void *
+sftpfs_cb_opendir (const vfs_path_t * vpath)
+{
+ GError *mcerror = NULL;
+ void *ret_value;
+
+ /* reset interrupt flag */
+ tty_got_interrupt ();
+
+ ret_value = sftpfs_opendir (vpath, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return ret_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for reading directory entry.
+ *
+ * @param data directory data handler
+ * @return information about direntry if success, NULL otherwise
+ */
+
+static struct vfs_dirent *
+sftpfs_cb_readdir (void *data)
+{
+ GError *mcerror = NULL;
+ struct vfs_dirent *sftpfs_dirent;
+
+ if (tty_got_interrupt ())
+ {
+ tty_disable_interrupt_key ();
+ return NULL;
+ }
+
+ sftpfs_dirent = sftpfs_readdir (data, &mcerror);
+ if (!mc_error_message (&mcerror, NULL))
+ {
+ if (sftpfs_dirent != NULL)
+ vfs_print_message (_("sftp: (Ctrl-G break) Listing... %s"), sftpfs_dirent->d_name);
+ else
+ vfs_print_message ("%s", _("sftp: Listing done."));
+ }
+
+ return sftpfs_dirent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for closing directory.
+ *
+ * @param data directory data handler
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_closedir (void *data)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_closedir (data, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for lstat VFS-function.
+ *
+ * @param vpath path to file or directory
+ * @param buf buffer for store stat-info
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_lstat (vpath, buf, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for stat VFS-function.
+ *
+ * @param vpath path to file or directory
+ * @param buf buffer for store stat-info
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_stat (vpath, buf, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for fstat VFS-function.
+ *
+ * @param data file data handler
+ * @param buf buffer for store stat-info
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_fstat (void *data, struct stat *buf)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_fstat (data, buf, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for readlink VFS-function.
+ *
+ * @param vpath path to file or directory
+ * @param buf buffer for store stat-info
+ * @param size buffer size
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_readlink (const vfs_path_t * vpath, char *buf, size_t size)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_readlink (vpath, buf, size, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for utime VFS-function.
+ *
+ * @param vpath path to file or directory
+ * @param times access and modification time to set
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_utime (const vfs_path_t * vpath, mc_timesbuf_t * times)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+#ifdef HAVE_UTIMENSAT
+ time_t atime = (*times)[0].tv_sec;
+ time_t mtime = (*times)[1].tv_sec;
+#else
+ time_t atime = times->actime;
+ time_t mtime = times->modtime;
+#endif
+
+ rc = sftpfs_utime (vpath, atime, mtime, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for symlink VFS-function.
+ *
+ * @param vpath1 path to file or directory
+ * @param vpath2 path to symlink
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_symlink (vpath1, vpath2, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for symlink VFS-function.
+ *
+ * @param vpath unused
+ * @param mode unused
+ * @param dev unused
+ * @return always 0
+ */
+
+static int
+sftpfs_cb_mknod (const vfs_path_t * vpath, mode_t mode, dev_t dev)
+{
+ (void) vpath;
+ (void) mode;
+ (void) dev;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for link VFS-function.
+ *
+ * @param vpath1 unused
+ * @param vpath2 unused
+ * @return always 0
+ */
+
+static int
+sftpfs_cb_link (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ (void) vpath1;
+ (void) vpath2;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for chown VFS-function.
+ *
+ * @param vpath unused
+ * @param owner unused
+ * @param group unused
+ * @return always 0
+ */
+
+static int
+sftpfs_cb_chown (const vfs_path_t * vpath, uid_t owner, gid_t group)
+{
+ (void) vpath;
+ (void) owner;
+ (void) group;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for reading file content.
+ *
+ * @param data file data handler
+ * @param buffer buffer for data
+ * @param count data size
+ * @return 0 if success, negative value otherwise
+ */
+
+static ssize_t
+sftpfs_cb_read (void *data, char *buffer, size_t count)
+{
+ int rc;
+ GError *mcerror = NULL;
+ vfs_file_handler_t *fh = VFS_FILE_HANDLER (data);
+
+ if (tty_got_interrupt ())
+ {
+ tty_disable_interrupt_key ();
+ return 0;
+ }
+
+ rc = sftpfs_read_file (fh, buffer, count, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for writing file content.
+ *
+ * @param data file data handler
+ * @param buf buffer for data
+ * @param count data size
+ * @return 0 if success, negative value otherwise
+ */
+
+static ssize_t
+sftpfs_cb_write (void *data, const char *buf, size_t nbyte)
+{
+ int rc;
+ GError *mcerror = NULL;
+ vfs_file_handler_t *fh = VFS_FILE_HANDLER (data);
+
+ rc = sftpfs_write_file (fh, buf, nbyte, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for close file.
+ *
+ * @param data file data handler
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_close (void *data)
+{
+ int rc;
+ GError *mcerror = NULL;
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (data);
+ vfs_file_handler_t *fh = VFS_FILE_HANDLER (data);
+
+ super->fd_usage--;
+ if (super->fd_usage == 0)
+ vfs_stamp_create (vfs_sftpfs_ops, super);
+
+ rc = sftpfs_close_file (fh, &mcerror);
+ mc_error_message (&mcerror, NULL);
+
+ if (fh->handle != -1)
+ close (fh->handle);
+
+ vfs_s_free_inode (vfs_sftpfs_ops, fh->ino);
+
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for chmod VFS-function.
+ *
+ * @param vpath path to file or directory
+ * @param mode mode (see man 2 open)
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_chmod (const vfs_path_t * vpath, mode_t mode)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_chmod (vpath, mode, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for mkdir VFS-function.
+ *
+ * @param vpath path directory
+ * @param mode mode (see man 2 open)
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_mkdir (const vfs_path_t * vpath, mode_t mode)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_mkdir (vpath, mode, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for rmdir VFS-function.
+ *
+ * @param vpath path directory
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_rmdir (const vfs_path_t * vpath)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_rmdir (vpath, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for lseek VFS-function.
+ *
+ * @param data file data handler
+ * @param offset file offset
+ * @param whence method of seek (at begin, at current, at end)
+ * @return 0 if success, negative value otherwise
+ */
+
+static off_t
+sftpfs_cb_lseek (void *data, off_t offset, int whence)
+{
+ off_t ret_offset;
+ vfs_file_handler_t *fh = VFS_FILE_HANDLER (data);
+ GError *mcerror = NULL;
+
+ ret_offset = sftpfs_lseek (fh, offset, whence, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return ret_offset;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for unlink VFS-function.
+ *
+ * @param vpath path to file or directory
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_unlink (const vfs_path_t * vpath)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_unlink (vpath, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for rename VFS-function.
+ *
+ * @param vpath1 path to source file or directory
+ * @param vpath2 path to destination file or directory
+ * @return 0 if success, negative value otherwise
+ */
+
+static int
+sftpfs_cb_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2)
+{
+ int rc;
+ GError *mcerror = NULL;
+
+ rc = sftpfs_rename (vpath1, vpath2, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return rc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for errno VFS-function.
+ *
+ * @param me unused
+ * @return value of errno global variable
+ */
+
+static int
+sftpfs_cb_errno (struct vfs_class *me)
+{
+ (void) me;
+
+ return errno;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for fill_names VFS function.
+ * Add SFTP connections to the 'Active VFS connections' list
+ *
+ * @param me unused
+ * @param func callback function for adding SFTP-connection to list of active connections
+ */
+
+static void
+sftpfs_cb_fill_names (struct vfs_class *me, fill_names_f func)
+{
+ GList *iter;
+
+ (void) me;
+
+ for (iter = sftpfs_subclass.supers; iter != NULL; iter = g_list_next (iter))
+ {
+ const struct vfs_s_super *super = (const struct vfs_s_super *) iter->data;
+ GString *name;
+
+ name = vfs_path_element_build_pretty_path_str (super->path_element);
+
+ func (name->str);
+ g_string_free (name, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for checking if connection is equal to existing connection.
+ *
+ * @param vpath_element path element with connection data
+ * @param super data with exists connection
+ * @param vpath unused
+ * @param cookie unused
+ * @return TRUE if connections is equal, FALSE otherwise
+ */
+
+static gboolean
+sftpfs_archive_same (const vfs_path_element_t * vpath_element, struct vfs_s_super *super,
+ const vfs_path_t * vpath, void *cookie)
+{
+ int result;
+ vfs_path_element_t *orig_connect_info;
+
+ (void) vpath;
+ (void) cookie;
+
+ orig_connect_info = SFTP_SUPER (super)->original_connection_info;
+
+ result = ((g_strcmp0 (vpath_element->host, orig_connect_info->host) == 0)
+ && (g_strcmp0 (vpath_element->user, orig_connect_info->user) == 0)
+ && (vpath_element->port == orig_connect_info->port));
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_super *
+sftpfs_new_archive (struct vfs_class *me)
+{
+ sftpfs_super_t *arch;
+
+ arch = g_new0 (sftpfs_super_t, 1);
+ arch->base.me = me;
+ arch->base.name = g_strdup (PATH_SEP_STR);
+ arch->auth_type = NONE;
+ arch->config_auth_type = NONE;
+ arch->socket_handle = LIBSSH2_INVALID_SOCKET;
+
+ return VFS_SUPER (arch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for opening new connection.
+ *
+ * @param super connection data
+ * @param vpath unused
+ * @param vpath_element path element with connection data
+ * @return 0 if success, -1 otherwise
+ */
+
+static int
+sftpfs_open_archive (struct vfs_s_super *super, const vfs_path_t * vpath,
+ const vfs_path_element_t * vpath_element)
+{
+ GError *mcerror = NULL;
+ sftpfs_super_t *sftpfs_super = SFTP_SUPER (super);
+ int ret_value;
+
+ (void) vpath;
+
+ if (vpath_element->host == NULL || *vpath_element->host == '\0')
+ {
+ vfs_print_message ("%s", _("sftp: Invalid host name."));
+ vpath_element->class->verrno = EPERM;
+ return -1;
+ }
+
+ sftpfs_super->original_connection_info = vfs_path_element_clone (vpath_element);
+ super->path_element = vfs_path_element_clone (vpath_element);
+
+ sftpfs_fill_connection_data_from_config (super, &mcerror);
+ if (mc_error_message (&mcerror, &ret_value))
+ {
+ vpath_element->class->verrno = ret_value;
+ return -1;
+ }
+
+ super->root =
+ vfs_s_new_inode (vpath_element->class, super,
+ vfs_s_default_stat (vpath_element->class, S_IFDIR | 0755));
+
+ ret_value = sftpfs_open_connection (super, &mcerror);
+ mc_error_message (&mcerror, NULL);
+ return ret_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for closing connection.
+ *
+ * @param me unused
+ * @param super connection data
+ */
+
+static void
+sftpfs_free_archive (struct vfs_class *me, struct vfs_s_super *super)
+{
+ GError *mcerror = NULL;
+
+ (void) me;
+
+ sftpfs_close_connection (super, "Normal Shutdown", &mcerror);
+
+ vfs_path_element_free (SFTP_SUPER (super)->original_connection_info);
+
+ mc_error_message (&mcerror, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for getting directory content.
+ *
+ * @param me unused
+ * @param dir unused
+ * @param remote_path unused
+ * @return always 0
+ */
+
+static int
+sftpfs_cb_dir_load (struct vfs_class *me, struct vfs_s_inode *dir, const char *remote_path)
+{
+ (void) me;
+ (void) dir;
+ (void) remote_path;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Initialization of SFTP Virtual File Sysytem.
+ */
+
+void
+vfs_init_sftpfs (void)
+{
+ tcp_init ();
+
+ vfs_init_subclass (&sftpfs_subclass, "sftpfs", VFSF_NOLINKS | VFSF_REMOTE, "sftp");
+
+ vfs_sftpfs_ops->init = sftpfs_cb_init;
+ vfs_sftpfs_ops->done = sftpfs_cb_done;
+
+ vfs_sftpfs_ops->fill_names = sftpfs_cb_fill_names;
+
+ vfs_sftpfs_ops->opendir = sftpfs_cb_opendir;
+ vfs_sftpfs_ops->readdir = sftpfs_cb_readdir;
+ vfs_sftpfs_ops->closedir = sftpfs_cb_closedir;
+ vfs_sftpfs_ops->mkdir = sftpfs_cb_mkdir;
+ vfs_sftpfs_ops->rmdir = sftpfs_cb_rmdir;
+
+ vfs_sftpfs_ops->stat = sftpfs_cb_stat;
+ vfs_sftpfs_ops->lstat = sftpfs_cb_lstat;
+ vfs_sftpfs_ops->fstat = sftpfs_cb_fstat;
+ vfs_sftpfs_ops->readlink = sftpfs_cb_readlink;
+ vfs_sftpfs_ops->symlink = sftpfs_cb_symlink;
+ vfs_sftpfs_ops->link = sftpfs_cb_link;
+ vfs_sftpfs_ops->utime = sftpfs_cb_utime;
+ vfs_sftpfs_ops->mknod = sftpfs_cb_mknod;
+ vfs_sftpfs_ops->chown = sftpfs_cb_chown;
+ vfs_sftpfs_ops->chmod = sftpfs_cb_chmod;
+
+ vfs_sftpfs_ops->open = sftpfs_cb_open;
+ vfs_sftpfs_ops->read = sftpfs_cb_read;
+ vfs_sftpfs_ops->write = sftpfs_cb_write;
+ vfs_sftpfs_ops->close = sftpfs_cb_close;
+ vfs_sftpfs_ops->lseek = sftpfs_cb_lseek;
+ vfs_sftpfs_ops->unlink = sftpfs_cb_unlink;
+ vfs_sftpfs_ops->rename = sftpfs_cb_rename;
+ vfs_sftpfs_ops->ferrno = sftpfs_cb_errno;
+
+ sftpfs_subclass.archive_same = sftpfs_archive_same;
+ sftpfs_subclass.new_archive = sftpfs_new_archive;
+ sftpfs_subclass.open_archive = sftpfs_open_archive;
+ sftpfs_subclass.free_archive = sftpfs_free_archive;
+ sftpfs_subclass.fh_new = sftpfs_fh_new;
+ sftpfs_subclass.dir_load = sftpfs_cb_dir_load;
+
+ vfs_register_class (vfs_sftpfs_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/sftpfs/sftpfs.h b/src/vfs/sftpfs/sftpfs.h
new file mode 100644
index 0000000..d3a1935
--- /dev/null
+++ b/src/vfs/sftpfs/sftpfs.h
@@ -0,0 +1,23 @@
+/**
+ * \file
+ * \brief Header: SFTP FS
+ */
+
+#ifndef MC__VFS_SFTPFS_H
+#define MC__VFS_SFTPFS_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_sftpfs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_SFTPFS_H */
diff --git a/src/vfs/tar/Makefile.am b/src/vfs/tar/Makefile.am
new file mode 100644
index 0000000..16642f0
--- /dev/null
+++ b/src/vfs/tar/Makefile.am
@@ -0,0 +1,10 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-tar.la
+
+libvfs_tar_la_SOURCES = \
+ tar-internal.c tar-internal.h \
+ tar-sparse.c \
+ tar-xheader.c \
+ tar.c tar.h
diff --git a/src/vfs/tar/Makefile.in b/src/vfs/tar/Makefile.in
new file mode 100644
index 0000000..c89786e
--- /dev/null
+++ b/src/vfs/tar/Makefile.in
@@ -0,0 +1,750 @@
+# 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/vfs/tar
+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)
+libvfs_tar_la_LIBADD =
+am_libvfs_tar_la_OBJECTS = tar-internal.lo tar-sparse.lo \
+ tar-xheader.lo tar.lo
+libvfs_tar_la_OBJECTS = $(am_libvfs_tar_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)/tar-internal.Plo \
+ ./$(DEPDIR)/tar-sparse.Plo ./$(DEPDIR)/tar-xheader.Plo \
+ ./$(DEPDIR)/tar.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 = $(libvfs_tar_la_SOURCES)
+DIST_SOURCES = $(libvfs_tar_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@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+noinst_LTLIBRARIES = libvfs-tar.la
+libvfs_tar_la_SOURCES = \
+ tar-internal.c tar-internal.h \
+ tar-sparse.c \
+ tar-xheader.c \
+ tar.c tar.h
+
+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/vfs/tar/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/tar/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}; \
+ }
+
+libvfs-tar.la: $(libvfs_tar_la_OBJECTS) $(libvfs_tar_la_DEPENDENCIES) $(EXTRA_libvfs_tar_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_tar_la_OBJECTS) $(libvfs_tar_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tar-internal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tar-sparse.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tar-xheader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tar.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)/tar-internal.Plo
+ -rm -f ./$(DEPDIR)/tar-sparse.Plo
+ -rm -f ./$(DEPDIR)/tar-xheader.Plo
+ -rm -f ./$(DEPDIR)/tar.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)/tar-internal.Plo
+ -rm -f ./$(DEPDIR)/tar-sparse.Plo
+ -rm -f ./$(DEPDIR)/tar-xheader.Plo
+ -rm -f ./$(DEPDIR)/tar.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/vfs/tar/tar-internal.c b/src/vfs/tar/tar-internal.c
new file mode 100644
index 0000000..f77b1b3
--- /dev/null
+++ b/src/vfs/tar/tar-internal.c
@@ -0,0 +1,482 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2023
+
+ 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
+ * \brief Source: Virtual File System: GNU Tar file system
+ * \author Andrew Borodin
+ * \date 2022
+ */
+
+#include <config.h>
+
+#include <ctype.h> /* isspace() */
+#include <inttypes.h> /* uintmax_t */
+#include <stdint.h> /* UINTMAX_MAX, etc */
+
+#include "lib/global.h"
+#include "lib/widget.h" /* message() */
+#include "lib/vfs/vfs.h" /* mc_read() */
+
+#include "tar-internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/* Log base 2 of common values. */
+#define LG_8 3
+#define LG_64 6
+#define LG_256 8
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* Base 64 digits; see RFC 2045 Table 1. */
+static char const base_64_digits[64] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+};
+
+/* Table of base 64 digit values indexed by unsigned chars.
+ The value is 64 for unsigned chars that are not base 64 digits. */
+static char base64_map[1 + (unsigned char) (-1)];
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tar_short_read (size_t status, tar_super_t * archive)
+{
+ size_t left; /* bytes left */
+ char *more; /* pointer to next byte to read */
+
+ more = archive->record_start->buffer + status;
+ left = record_size - status;
+
+ while (left % BLOCKSIZE != 0 || (left != 0 && status != 0))
+ {
+ if (status != 0)
+ {
+ ssize_t r;
+
+ r = mc_read (archive->fd, more, left);
+ if (r == -1)
+ return FALSE;
+
+ status = (size_t) r;
+ }
+
+ if (status == 0)
+ break;
+
+ left -= status;
+ more += status;
+ }
+
+ record_end = archive->record_start + (record_size - left) / BLOCKSIZE;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tar_flush_read (tar_super_t * archive)
+{
+ size_t status;
+
+ status = mc_read (archive->fd, archive->record_start->buffer, record_size);
+ if (status == record_size)
+ return TRUE;
+
+ return tar_short_read (status, archive);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Flush the current buffer from the archive.
+ */
+static gboolean
+tar_flush_archive (tar_super_t * archive)
+{
+ record_start_block += record_end - archive->record_start;
+ current_block = archive->record_start;
+ record_end = archive->record_start + blocking_factor;
+
+ return tar_flush_read (archive);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+tar_seek_archive (tar_super_t * archive, off_t size)
+{
+ off_t start, offset;
+ off_t nrec, nblk;
+ off_t skipped;
+
+ skipped = (blocking_factor - (current_block - archive->record_start)) * BLOCKSIZE;
+ if (size <= skipped)
+ return 0;
+
+ /* Compute number of records to skip */
+ nrec = (size - skipped) / record_size;
+ if (nrec == 0)
+ return 0;
+
+ start = tar_current_block_ordinal (archive);
+
+ offset = mc_lseek (archive->fd, nrec * record_size, SEEK_CUR);
+ if (offset < 0)
+ return offset;
+
+#if 0
+ if ((offset % record_size) != 0)
+ {
+ message (D_ERROR, MSG_ERROR, _("tar: mc_lseek not stopped at a record boundary"));
+ return -1;
+ }
+#endif
+
+ /* Convert to number of records */
+ offset /= BLOCKSIZE;
+ /* Compute number of skipped blocks */
+ nblk = offset - start;
+
+ /* Update buffering info */
+ record_start_block = offset - blocking_factor;
+ current_block = record_end;
+
+ return nblk;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_base64_init (void)
+{
+ size_t i;
+
+ memset (base64_map, 64, sizeof base64_map);
+ for (i = 0; i < 64; i++)
+ base64_map[(int) base_64_digits[i]] = i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_assign_string (char **string, char *value)
+{
+ g_free (*string);
+ *string = value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_assign_string_dup (char **string, const char *value)
+{
+ g_free (*string);
+ *string = g_strdup (value);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_assign_string_dup_n (char **string, const char *value, size_t n)
+{
+ g_free (*string);
+ *string = g_strndup (value, n);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert buffer at @where0 of size @digs from external format to intmax_t.
+ * @digs must be positive.
+ * If @type is non-NULL, data are of type @type.
+ * The buffer must represent a value in the range -@minval through @maxval;
+ * if the mathematically correct result V would be greater than INTMAX_MAX,
+ * return a negative integer V such that (uintmax_t) V yields the correct result.
+ * If @octal_only, allow only octal numbers instead of the other GNU extensions.
+ *
+ * Result is -1 if the field is invalid.
+ */
+#if !(INTMAX_MAX <= UINTMAX_MAX && - (INTMAX_MIN + 1) <= UINTMAX_MAX)
+#error "tar_from_header() internally represents intmax_t as uintmax_t + sign"
+#endif
+#if !(UINTMAX_MAX / 2 <= INTMAX_MAX)
+#error "tar_from_header() returns intmax_t to represent uintmax_t"
+#endif
+intmax_t
+tar_from_header (const char *where0, size_t digs, char const *type, intmax_t minval,
+ uintmax_t maxval, gboolean octal_only)
+{
+ uintmax_t value = 0;
+ uintmax_t uminval = minval;
+ uintmax_t minus_minval = -uminval;
+ const char *where = where0;
+ char const *lim = where + digs;
+ gboolean negative = FALSE;
+
+ /* Accommodate buggy tar of unknown vintage, which outputs leading
+ NUL if the previous field overflows. */
+ if (*where == '\0')
+ where++;
+
+ /* Accommodate older tars, which output leading spaces. */
+ while (TRUE)
+ {
+ if (where == lim)
+ return (-1);
+
+ if (!isspace ((unsigned char) *where))
+ break;
+
+ where++;
+ }
+
+ if (isodigit (*where))
+ {
+ char const *where1 = where;
+ gboolean overflow = FALSE;
+
+ while (TRUE)
+ {
+ value += *where++ - '0';
+ if (where == lim || !isodigit (*where))
+ break;
+ overflow |= value != (value << LG_8 >> LG_8);
+ value <<= LG_8;
+ }
+
+ /* Parse the output of older, unportable tars, which generate
+ negative values in two's complement octal. If the leading
+ nonzero digit is 1, we can't recover the original value
+ reliably; so do this only if the digit is 2 or more. This
+ catches the common case of 32-bit negative time stamps. */
+ if ((overflow || maxval < value) && *where1 >= 2 && type != NULL)
+ {
+ /* Compute the negative of the input value, assuming two's complement. */
+ int digit;
+
+ digit = (*where1 - '0') | 4;
+ overflow = FALSE;
+ value = 0;
+ where = where1;
+
+ while (TRUE)
+ {
+ value += 7 - digit;
+ where++;
+ if (where == lim || !isodigit (*where))
+ break;
+ digit = *where - '0';
+ overflow |= value != (value << LG_8 >> LG_8);
+ value <<= LG_8;
+ }
+
+ value++;
+ overflow |= value == 0;
+
+ if (!overflow && value <= minus_minval)
+ negative = TRUE;
+ }
+
+ if (overflow)
+ return (-1);
+ }
+ else if (octal_only)
+ {
+ /* Suppress the following extensions. */
+ }
+ else if (*where == '-' || *where == '+')
+ {
+ /* Parse base-64 output produced only by tar test versions
+ 1.13.6 (1999-08-11) through 1.13.11 (1999-08-23).
+ Support for this will be withdrawn in future tar releases. */
+ int dig;
+
+ negative = *where++ == '-';
+
+ while (where != lim && (dig = base64_map[(unsigned char) *where]) < 64)
+ {
+ if (value << LG_64 >> LG_64 != value)
+ return (-1);
+ value = (value << LG_64) | dig;
+ where++;
+ }
+ }
+ else if (where <= lim - 2 && (*where == '\200' /* positive base-256 */
+ || *where == '\377' /* negative base-256 */ ))
+ {
+ /* Parse base-256 output. A nonnegative number N is
+ represented as (256**DIGS)/2 + N; a negative number -N is
+ represented as (256**DIGS) - N, i.e. as two's complement.
+ The representation guarantees that the leading bit is
+ always on, so that we don't confuse this format with the
+ others (assuming ASCII bytes of 8 bits or more). */
+
+ int signbit;
+ uintmax_t topbits;
+
+ signbit = *where & (1 << (LG_256 - 2));
+ topbits =
+ (((uintmax_t) - signbit) << (CHAR_BIT * sizeof (uintmax_t) - LG_256 - (LG_256 - 2)));
+
+ value = (*where++ & ((1 << (LG_256 - 2)) - 1)) - signbit;
+
+ while (TRUE)
+ {
+ value = (value << LG_256) + (unsigned char) *where++;
+ if (where == lim)
+ break;
+
+ if (((value << LG_256 >> LG_256) | topbits) != value)
+ return (-1);
+ }
+
+ negative = signbit != 0;
+ if (negative)
+ value = -value;
+ }
+
+ if (where != lim && *where != '\0' && !isspace ((unsigned char) *where))
+ return (-1);
+
+ if (value <= (negative ? minus_minval : maxval))
+ return tar_represent_uintmax (negative ? -value : value);
+
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+off_t
+off_from_header (const char *p, size_t s)
+{
+ /* Negative offsets are not allowed in tar files, so invoke
+ from_header with minimum value 0, not TYPE_MINIMUM (off_t). */
+ return tar_from_header (p, s, "off_t", 0, TYPE_MAXIMUM (off_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Return the location of the next available input or output block.
+ * Return NULL for EOF.
+ */
+union block *
+tar_find_next_block (tar_super_t * archive)
+{
+ if (current_block == record_end)
+ {
+ if (hit_eof)
+ return NULL;
+
+ if (!tar_flush_archive (archive))
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ return NULL;
+ }
+
+ if (current_block == record_end)
+ {
+ hit_eof = TRUE;
+ return NULL;
+ }
+ }
+
+ return current_block;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Indicate that we have used all blocks up thru @block.
+ */
+gboolean
+tar_set_next_block_after (union block * block)
+{
+ while (block >= current_block)
+ current_block++;
+
+ /* Do *not* flush the archive here. If we do, the same argument to tar_set_next_block_after()
+ could mean the next block (if the input record is exactly one block long), which is not
+ what is intended. */
+
+ return !(current_block > record_end);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Compute and return the block ordinal at current_block.
+ */
+off_t
+tar_current_block_ordinal (const tar_super_t * archive)
+{
+ return record_start_block + (current_block - archive->record_start);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Skip over @size bytes of data in blocks in the archive.
+ */
+gboolean
+tar_skip_file (tar_super_t * archive, off_t size)
+{
+ union block *x;
+ off_t nblk;
+
+ nblk = tar_seek_archive (archive, size);
+ if (nblk >= 0)
+ size -= nblk * BLOCKSIZE;
+
+ while (size > 0)
+ {
+ x = tar_find_next_block (archive);
+ if (x == NULL)
+ return FALSE;
+
+ tar_set_next_block_after (x);
+ size -= BLOCKSIZE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/tar/tar-internal.h b/src/vfs/tar/tar-internal.h
new file mode 100644
index 0000000..7b3bb53
--- /dev/null
+++ b/src/vfs/tar/tar-internal.h
@@ -0,0 +1,351 @@
+
+#ifndef MC__VFS_TAR_INTERNAL_H
+#define MC__VFS_TAR_INTERNAL_H
+
+#include <inttypes.h> /* (u)intmax_t */
+#include <limits.h> /* CHAR_BIT, INT_MAX, etc */
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "lib/vfs/xdirentry.h" /* vfs_s_super */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* tar files are made in basic blocks of this size. */
+#define BLOCKSIZE 512
+
+#define DEFAULT_BLOCKING 20
+
+/* Sparse files are not supported in POSIX ustar format. For sparse files
+ with a POSIX header, a GNU extra header is provided which holds overall
+ sparse information and a few sparse descriptors. When an old GNU header
+ replaces both the POSIX header and the GNU extra header, it holds some
+ sparse descriptors too. Whether POSIX or not, if more sparse descriptors
+ are still needed, they are put into as many successive sparse headers as
+ necessary. The following constants tell how many sparse descriptors fit
+ in each kind of header able to hold them. */
+
+#define SPARSES_IN_EXTRA_HEADER 16
+#define SPARSES_IN_OLDGNU_HEADER 4
+#define SPARSES_IN_SPARSE_HEADER 21
+
+#define SPARSES_IN_STAR_HEADER 4
+#define SPARSES_IN_STAR_EXT_HEADER 21
+
+/* *BEWARE* *BEWARE* *BEWARE* that the following information is still
+ boiling, and may change. Even if the OLDGNU format description should be
+ accurate, the so-called GNU format is not yet fully decided. It is
+ surely meant to use only extensions allowed by POSIX, but the sketch
+ below repeats some ugliness from the OLDGNU format, which should rather
+ go away. Sparse files should be saved in such a way that they do *not*
+ require two passes at archive creation time. Huge files get some POSIX
+ fields to overflow, alternate solutions have to be sought for this. */
+
+/* This is a dir entry that contains the names of files that were in the
+ dir at the time the dump was made. */
+#define GNUTYPE_DUMPDIR 'D'
+
+/* Identifies the *next* file on the tape as having a long linkname. */
+#define GNUTYPE_LONGLINK 'K'
+
+/* Identifies the *next* file on the tape as having a long name. */
+#define GNUTYPE_LONGNAME 'L'
+
+/* Solaris extended header */
+#define SOLARIS_XHDTYPE 'X'
+
+#define GNUTYPE_SPARSE 'S'
+
+
+/* These macros work even on ones'-complement hosts (!).
+ The extra casts work around some compiler bugs. */
+#define TYPE_SIGNED(t) (!((t) 0 < (t) (-1)))
+#define TYPE_MINIMUM(t) (TYPE_SIGNED (t) ? ~(t) 0 << (sizeof (t) * CHAR_BIT - 1) : (t) 0)
+#define TYPE_MAXIMUM(t) (~(t) 0 - TYPE_MINIMUM (t))
+
+#define OFF_FROM_HEADER(where) off_from_header (where, sizeof (where))
+
+#define isodigit(c) ( ((c) >= '0') && ((c) <= '7') )
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* *INDENT-OFF* */
+
+/* POSIX header */
+struct posix_header
+{ /* byte offset */
+ char name[100]; /* 0 */
+ char mode[8]; /* 100 */
+ char uid[8]; /* 108 */
+ char gid[8]; /* 116 */
+ char size[12]; /* 124 */
+ char mtime[12]; /* 136 */
+ char chksum[8]; /* 148 */
+ char typeflag; /* 156 */
+ char linkname[100]; /* 157 */
+ char magic[6]; /* 257 */
+ char version[2]; /* 263 */
+ char uname[32]; /* 265 */
+ char gname[32]; /* 297 */
+ char devmajor[8]; /* 329 */
+ char devminor[8]; /* 337 */
+ char prefix[155]; /* 345 */
+ /* 500 */
+};
+
+/* Descriptor for a single file hole */
+struct sparse
+{ /* byte offset */
+ /* cppcheck-suppress unusedStructMember */
+ char offset[12]; /* 0 */
+ /* cppcheck-suppress unusedStructMember */
+ char numbytes[12]; /* 12 */
+ /* 24 */
+};
+
+/* Extension header for sparse files, used immediately after the GNU extra
+ header, and used only if all sparse information cannot fit into that
+ extra header. There might even be many such extension headers, one after
+ the other, until all sparse information has been recorded. */
+struct sparse_header
+{ /* byte offset */
+ struct sparse sp[SPARSES_IN_SPARSE_HEADER];
+ /* 0 */
+ char isextended; /* 504 */
+ /* 505 */
+};
+
+/* The old GNU format header conflicts with POSIX format in such a way that
+ POSIX archives may fool old GNU tar's, and POSIX tar's might well be
+ fooled by old GNU tar archives. An old GNU format header uses the space
+ used by the prefix field in a POSIX header, and cumulates information
+ normally found in a GNU extra header. With an old GNU tar header, we
+ never see any POSIX header nor GNU extra header. Supplementary sparse
+ headers are allowed, however. */
+struct oldgnu_header
+{ /* byte offset */
+ char unused_pad1[345]; /* 0 */
+ char atime[12]; /* 345 Incr. archive: atime of the file */
+ char ctime[12]; /* 357 Incr. archive: ctime of the file */
+ char offset[12]; /* 369 Multivolume archive: the offset of start of this volume */
+ char longnames[4]; /* 381 Not used */
+ char unused_pad2; /* 385 */
+ struct sparse sp[SPARSES_IN_OLDGNU_HEADER];
+ /* 386 */
+ char isextended; /* 482 Sparse file: Extension sparse header follows */
+ char realsize[12]; /* 483 Sparse file: Real size */
+ /* 495 */
+};
+
+/* J@"org Schilling star header */
+struct star_header
+{ /* byte offset */
+ char name[100]; /* 0 */
+ char mode[8]; /* 100 */
+ char uid[8]; /* 108 */
+ char gid[8]; /* 116 */
+ char size[12]; /* 124 */
+ char mtime[12]; /* 136 */
+ char chksum[8]; /* 148 */
+ char typeflag; /* 156 */
+ char linkname[100]; /* 157 */
+ char magic[6]; /* 257 */
+ char version[2]; /* 263 */
+ char uname[32]; /* 265 */
+ char gname[32]; /* 297 */
+ char devmajor[8]; /* 329 */
+ char devminor[8]; /* 337 */
+ char prefix[131]; /* 345 */
+ char atime[12]; /* 476 */
+ char ctime[12]; /* 488 */
+ /* 500 */
+};
+
+struct star_in_header
+{
+ char fill[345]; /* 0 Everything that is before t_prefix */
+ char prefix[1]; /* 345 t_name prefix */
+ char fill2; /* 346 */
+ char fill3[8]; /* 347 */
+ char isextended; /* 355 */
+ struct sparse sp[SPARSES_IN_STAR_HEADER]; /* 356 */
+ char realsize[12]; /* 452 Actual size of the file */
+ char offset[12]; /* 464 Offset of multivolume contents */
+ char atime[12]; /* 476 */
+ char ctime[12]; /* 488 */
+ char mfill[8]; /* 500 */
+ char xmagic[4]; /* 508 "tar" */
+};
+
+struct star_ext_header
+{
+ struct sparse sp[SPARSES_IN_STAR_EXT_HEADER];
+ char isextended;
+};
+
+/* *INDENT-ON* */
+
+/* tar Header Block, overall structure */
+union block
+{
+ char buffer[BLOCKSIZE];
+ struct posix_header header;
+ struct star_header star_header;
+ struct oldgnu_header oldgnu_header;
+ struct sparse_header sparse_header;
+ struct star_in_header star_in_header;
+ struct star_ext_header star_ext_header;
+};
+
+/* Information about a sparse file */
+struct sp_array
+{
+ off_t offset; /* chunk offset in file */
+ off_t numbytes; /* length of chunk */
+ off_t arch_offset; /* chunk offset in archive */
+};
+
+enum dump_status
+{
+ dump_status_ok,
+ dump_status_short,
+ dump_status_fail,
+ dump_status_not_implemented
+};
+
+enum archive_format
+{
+ TAR_UNKNOWN = 0, /**< format to be decided later */
+ TAR_V7, /**< old V7 tar format */
+ TAR_OLDGNU, /**< GNU format as per before tar 1.12 */
+ TAR_USTAR, /**< POSIX.1-1988 (ustar) format */
+ TAR_POSIX, /**< POSIX.1-2001 format */
+ TAR_STAR, /**< star format defined in 1994 */
+ TAR_GNU /**< almost same as OLDGNU_FORMAT */
+};
+
+typedef struct
+{
+ struct vfs_s_super base; /* base class */
+
+ int fd;
+ struct stat st;
+ enum archive_format type; /**< type of the archive */
+ union block *record_start; /**< start of record of archive */
+} tar_super_t;
+
+struct xheader
+{
+ size_t size;
+ char *buffer;
+};
+
+struct tar_stat_info
+{
+ char *orig_file_name; /**< name of file read from the archive header */
+ char *file_name; /**< name of file for the current archive entry after being normalized */
+ char *link_name; /**< name of link for the current archive entry */
+#if 0
+ char *uname; /**< user name of owner */
+ char *gname; /**< group name of owner */
+#endif
+ struct stat stat; /**< regular filesystem stat */
+
+ /* stat() doesn't always have access, data modification, and status
+ change times in a convenient form, so store them separately. */
+ struct timespec atime;
+ struct timespec mtime;
+ struct timespec ctime;
+
+ off_t archive_file_size; /**< size of file as stored in the archive.
+ Equals stat.st_size for non-sparse files */
+ gboolean is_sparse; /**< is the file sparse */
+
+ /* For sparse files */
+ unsigned int sparse_major;
+ unsigned int sparse_minor;
+ GArray *sparse_map; /**< array of struct sp_array */
+
+ off_t real_size; /**< real size of sparse file */
+ gboolean real_size_set; /**< TRUE when GNU.sparse.realsize is set in archived file */
+
+ gboolean sparse_name_done; /**< TRUE if 'GNU.sparse.name' header was processed pax header parsing.
+ Following 'path' header (lower priority) will be ignored. */
+
+ /* Extended headers */
+ struct xheader xhdr;
+
+ /* For dumpdirs */
+ gboolean is_dumpdir; /**< is the member a dumpdir? */
+ gboolean skipped; /**< the member contents is already read (for GNUTYPE_DUMPDIR) */
+ char *dumpdir; /**< contents of the dump directory */
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+extern const int blocking_factor;
+extern const size_t record_size;
+
+extern union block *record_end; /* last+1 block of archive record */
+extern union block *current_block; /* current block of archive */
+extern off_t record_start_block; /* block ordinal at record_start */
+
+extern union block *current_header;
+
+/* Have we hit EOF yet? */
+extern gboolean hit_eof;
+
+extern struct tar_stat_info current_stat_info;
+
+/*** declarations of public functions ************************************************************/
+
+/* tar-internal.c */
+void tar_base64_init (void);
+void tar_assign_string (char **string, char *value);
+void tar_assign_string_dup (char **string, const char *value);
+void tar_assign_string_dup_n (char **string, const char *value, size_t n);
+intmax_t tar_from_header (const char *where0, size_t digs, char const *type, intmax_t minval,
+ uintmax_t maxval, gboolean octal_only);
+off_t off_from_header (const char *p, size_t s);
+union block *tar_find_next_block (tar_super_t * archive);
+gboolean tar_set_next_block_after (union block *block);
+off_t tar_current_block_ordinal (const tar_super_t * archive);
+gboolean tar_skip_file (tar_super_t * archive, off_t size);
+
+/* tar-sparse.c */
+gboolean tar_sparse_member_p (tar_super_t * archive, struct tar_stat_info *st);
+gboolean tar_sparse_fixup_header (tar_super_t * archive, struct tar_stat_info *st);
+enum dump_status tar_sparse_skip_file (tar_super_t * archive, struct tar_stat_info *st);
+
+/* tar-xheader.c */
+gboolean tar_xheader_decode (struct tar_stat_info *st);
+gboolean tar_xheader_read (tar_super_t * archive, struct xheader *xhdr, union block *header,
+ off_t size);
+gboolean tar_xheader_decode_global (struct xheader *xhdr);
+void tar_xheader_destroy (struct xheader *xhdr);
+
+/*** inline functions ****************************************************************************/
+
+/**
+ * Represent @n using a signed integer I such that (uintmax_t) I == @n.
+ With a good optimizing compiler, this is equivalent to (intmax_t) i
+ and requires zero machine instructions. */
+#if !(UINTMAX_MAX / 2 <= INTMAX_MAX)
+#error "tar_represent_uintmax() returns intmax_t to represent uintmax_t"
+#endif
+static inline intmax_t
+tar_represent_uintmax (uintmax_t n)
+{
+ intmax_t nd;
+
+ if (n <= INTMAX_MAX)
+ return n;
+
+ /* Avoid signed integer overflow on picky platforms. */
+ nd = n - INTMAX_MIN;
+ return nd + INTMAX_MIN;
+}
+
+#endif /* MC__VFS_TAR_INTERNAL_H */
diff --git a/src/vfs/tar/tar-sparse.c b/src/vfs/tar/tar-sparse.c
new file mode 100644
index 0000000..0bc169b
--- /dev/null
+++ b/src/vfs/tar/tar-sparse.c
@@ -0,0 +1,777 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 2003-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2023
+
+ 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
+ * \brief Source: Virtual File System: GNU Tar file system
+ */
+
+/*
+ * Avoid following error:
+ * comparison of unsigned expression < 0 is always false [-Werror=type-limits]
+ *
+ * https://www.boost.org/doc/libs/1_55_0/libs/integer/test/cstdint_test.cpp
+ * We can't suppress this warning on the command line as not all GCC versions support -Wno-type-limits
+ */
+#if defined(__GNUC__) && (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4))
+#pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+
+#include <config.h>
+
+#include <ctype.h> /* isdigit() */
+#include <errno.h>
+#include <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+
+#include "tar-internal.h"
+
+/* Old GNU Format.
+ The sparse file information is stored in the oldgnu_header in the following manner:
+
+ The header is marked with type 'S'. Its 'size' field contains the cumulative size
+ of all non-empty blocks of the file. The actual file size is stored in `realsize'
+ member of oldgnu_header.
+
+ The map of the file is stored in a list of 'struct sparse'. Each struct contains
+ offset to the block of data and its size (both as octal numbers). The first file
+ header contains at most 4 such structs (SPARSES_IN_OLDGNU_HEADER). If the map
+ contains more structs, then the field 'isextended' of the main header is set to
+ 1 (binary) and the 'struct sparse_header' header follows, containing at most
+ 21 following structs (SPARSES_IN_SPARSE_HEADER). If more structs follow, 'isextended'
+ field of the extended header is set and next next extension header follows, etc...
+ */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/* The width in bits of the integer type or expression T.
+ Do not evaluate T. T must not be a bit-field expression.
+ Padding bits are not supported; this is checked at compile-time below. */
+#define TYPE_WIDTH(t) (sizeof (t) * CHAR_BIT)
+
+/* Bound on length of the string representing an unsigned integer
+ value representable in B bits. log10 (2.0) < 146/485. The
+ smallest value of B where this bound is not tight is 2621. */
+#define INT_BITS_STRLEN_BOUND(b) (((b) * 146 + 484) / 485)
+
+/* Does the __typeof__ keyword work? This could be done by
+ 'configure', but for now it's easier to do it by hand. */
+#if (2 <= __GNUC__ \
+ || (4 <= __clang_major__) \
+ || (1210 <= __IBMC__ && defined __IBM__TYPEOF__) \
+ || (0x5110 <= __SUNPRO_C && !__STDC__))
+#define _GL_HAVE___TYPEOF__ 1
+#else
+#define _GL_HAVE___TYPEOF__ 0
+#endif
+
+/* Return 1 if the integer type or expression T might be signed. Return 0
+ if it is definitely unsigned. T must not be a bit-field expression.
+ This macro does not evaluate its argument, and expands to an
+ integer constant expression. */
+#if _GL_HAVE___TYPEOF__
+#define _GL_SIGNED_TYPE_OR_EXPR(t) TYPE_SIGNED (__typeof__ (t))
+#else
+#define _GL_SIGNED_TYPE_OR_EXPR(t) 1
+#endif
+
+/* Return a value with the common real type of E and V and the value of V.
+ Do not evaluate E. */
+#define _GL_INT_CONVERT(e, v) ((1 ? 0 : (e)) + (v))
+
+/* Act like _GL_INT_CONVERT (E, -V) but work around a bug in IRIX 6.5 cc; see
+ <https://lists.gnu.org/r/bug-gnulib/2011-05/msg00406.html>. */
+#define _GL_INT_NEGATE_CONVERT(e, v) ((1 ? 0 : (e)) - (v))
+
+/* Return 1 if the real expression E, after promotion, has a
+ signed or floating type. Do not evaluate E. */
+#define EXPR_SIGNED(e) (_GL_INT_NEGATE_CONVERT (e, 1) < 0)
+
+#define _GL_SIGNED_INT_MAXIMUM(e) \
+ (((_GL_INT_CONVERT (e, 1) << (TYPE_WIDTH (+ (e)) - 2)) - 1) * 2 + 1)
+
+/* The maximum and minimum values for the type of the expression E,
+ after integer promotion. E is not evaluated. */
+#define _GL_INT_MINIMUM(e) \
+ (EXPR_SIGNED (e) \
+ ? ~_GL_SIGNED_INT_MAXIMUM (e) \
+ : _GL_INT_CONVERT (e, 0))
+#define _GL_INT_MAXIMUM(e) \
+ (EXPR_SIGNED (e) \
+ ? _GL_SIGNED_INT_MAXIMUM (e) \
+ : _GL_INT_NEGATE_CONVERT (e, 1))
+
+/* Return 1 if the expression A <op> B would overflow,
+ where OP_RESULT_OVERFLOW (A, B, MIN, MAX) does the actual test,
+ assuming MIN and MAX are the minimum and maximum for the result type.
+ Arguments should be free of side effects. */
+#define _GL_BINARY_OP_OVERFLOW(a, b, op_result_overflow) \
+ op_result_overflow (a, b, \
+ _GL_INT_MINIMUM (_GL_INT_CONVERT (a, b)), \
+ _GL_INT_MAXIMUM (_GL_INT_CONVERT (a, b)))
+
+#define INT_ADD_RANGE_OVERFLOW(a, b, min, max) \
+ ((b) < 0 \
+ ? (a) < (min) - (b) \
+ : (max) - (b) < (a))
+
+
+/* True if __builtin_add_overflow_p (A, B, C) works, and similarly for
+ __builtin_sub_overflow_p and __builtin_mul_overflow_p. */
+#if defined __clang__ || defined __ICC
+/* Clang 11 lacks __builtin_mul_overflow_p, and even if it did it
+ would presumably run afoul of Clang bug 16404. ICC 2021.1's
+ __builtin_add_overflow_p etc. are not treated as integral constant
+ expressions even when all arguments are. */
+#define _GL_HAS_BUILTIN_OVERFLOW_P 0
+#elif defined __has_builtin
+#define _GL_HAS_BUILTIN_OVERFLOW_P __has_builtin (__builtin_mul_overflow_p)
+#else
+#define _GL_HAS_BUILTIN_OVERFLOW_P (7 <= __GNUC__)
+#endif
+
+/* The _GL*_OVERFLOW macros have the same restrictions as the
+ *_RANGE_OVERFLOW macros, except that they do not assume that operands
+ (e.g., A and B) have the same type as MIN and MAX. Instead, they assume
+ that the result (e.g., A + B) has that type. */
+#if _GL_HAS_BUILTIN_OVERFLOW_P
+#define _GL_ADD_OVERFLOW(a, b, min, max) \
+ __builtin_add_overflow_p (a, b, (__typeof__ ((a) + (b))) 0)
+#else
+#define _GL_ADD_OVERFLOW(a, b, min, max) \
+ ((min) < 0 ? INT_ADD_RANGE_OVERFLOW (a, b, min, max) \
+ : (a) < 0 ? (b) <= (a) + (b) \
+ : (b) < 0 ? (a) <= (a) + (b) \
+ : (a) + (b) < (b))
+#endif
+
+/* Bound on length of the string representing an integer type or expression T.
+ T must not be a bit-field expression.
+
+ Subtract 1 for the sign bit if T is signed, and then add 1 more for
+ a minus sign if needed.
+
+ Because _GL_SIGNED_TYPE_OR_EXPR sometimes returns 1 when its argument is
+ unsigned, this macro may overestimate the true bound by one byte when
+ applied to unsigned types of size 2, 4, 16, ... bytes. */
+#define INT_STRLEN_BOUND(t) \
+ (INT_BITS_STRLEN_BOUND (TYPE_WIDTH (t) - _GL_SIGNED_TYPE_OR_EXPR (t)) \
+ + _GL_SIGNED_TYPE_OR_EXPR (t))
+
+/* Bound on buffer size needed to represent an integer type or expression T,
+ including the terminating null. T must not be a bit-field expression. */
+#define INT_BUFSIZE_BOUND(t) (INT_STRLEN_BOUND (t) + 1)
+
+#define UINTMAX_STRSIZE_BOUND INT_BUFSIZE_BOUND (uintmax_t)
+
+#define INT_ADD_OVERFLOW(a, b) \
+ _GL_BINARY_OP_OVERFLOW (a, b, _GL_ADD_OVERFLOW)
+
+#define SPARSES_INIT_COUNT SPARSES_IN_SPARSE_HEADER
+
+#define COPY_BUF(arch,b,buf,src) \
+do \
+{ \
+ char *endp = b->buffer + BLOCKSIZE; \
+ char *dst = buf; \
+ do \
+ { \
+ if (dst == buf + UINTMAX_STRSIZE_BOUND - 1) \
+ /* numeric overflow in sparse archive member */ \
+ return FALSE; \
+ if (src == endp) \
+ { \
+ tar_set_next_block_after (b); \
+ b = tar_find_next_block (arch); \
+ if (b == NULL) \
+ /* unexpected EOF in archive */ \
+ return FALSE; \
+ src = b->buffer; \
+ endp = b->buffer + BLOCKSIZE; \
+ } \
+ *dst = *src++; \
+ } \
+ while (*dst++ != '\n'); \
+ dst[-1] = '\0'; \
+} \
+while (FALSE)
+
+/*** file scope type declarations ****************************************************************/
+
+struct tar_sparse_file;
+
+struct tar_sparse_optab
+{
+ gboolean (*init) (struct tar_sparse_file * file);
+ gboolean (*done) (struct tar_sparse_file * file);
+ gboolean (*sparse_member_p) (struct tar_sparse_file * file);
+ gboolean (*fixup_header) (struct tar_sparse_file * file);
+ gboolean (*decode_header) (tar_super_t * archive, struct tar_sparse_file * file);
+};
+
+struct tar_sparse_file
+{
+ int fd; /**< File descriptor */
+ off_t dumped_size; /**< Number of bytes actually written to the archive */
+ struct tar_stat_info *stat_info; /**< Information about the file */
+ struct tar_sparse_optab const *optab;
+ void *closure; /**< Any additional data optab calls might reqiure */
+};
+
+enum oldgnu_add_status
+{
+ add_ok,
+ add_finish,
+ add_fail
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static gboolean oldgnu_sparse_member_p (struct tar_sparse_file *file);
+static gboolean oldgnu_fixup_header (struct tar_sparse_file *file);
+static gboolean oldgnu_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file);
+
+static gboolean star_sparse_member_p (struct tar_sparse_file *file);
+static gboolean star_fixup_header (struct tar_sparse_file *file);
+static gboolean star_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file);
+
+static gboolean pax_sparse_member_p (struct tar_sparse_file *file);
+static gboolean pax_decode_header (tar_super_t * archive, struct tar_sparse_file *file);
+
+/*** file scope variables ************************************************************************/
+
+/* *INDENT-OFF* */
+static struct tar_sparse_optab const oldgnu_optab =
+{
+ .init = NULL, /* No init function */
+ .done = NULL, /* No done function */
+ .sparse_member_p = oldgnu_sparse_member_p,
+ .fixup_header = oldgnu_fixup_header,
+ .decode_header = oldgnu_get_sparse_info
+};
+/* *INDENT-ON* */
+
+/* *INDENT-OFF* */
+static struct tar_sparse_optab const star_optab =
+{
+ .init = NULL, /* No init function */
+ .done = NULL, /* No done function */
+ .sparse_member_p = star_sparse_member_p,
+ .fixup_header = star_fixup_header,
+ .decode_header = star_get_sparse_info
+};
+/* *INDENT-ON* */
+
+/* GNU PAX sparse file format. There are several versions:
+ * 0.0
+
+ The initial version of sparse format used by tar 1.14-1.15.1.
+ The sparse file map is stored in x header:
+
+ GNU.sparse.size Real size of the stored file
+ GNU.sparse.numblocks Number of blocks in the sparse map repeat numblocks time
+ GNU.sparse.offset Offset of the next data block
+ GNU.sparse.numbytes Size of the next data block end repeat
+
+ This has been reported as conflicting with the POSIX specs. The reason is
+ that offsets and sizes of non-zero data blocks were stored in multiple instances
+ of GNU.sparse.offset/GNU.sparse.numbytes variables, whereas POSIX requires the
+ latest occurrence of the variable to override all previous occurrences.
+
+ To avoid this incompatibility two following versions were introduced.
+
+ * 0.1
+
+ Used by tar 1.15.2 -- 1.15.91 (alpha releases).
+
+ The sparse file map is stored in x header:
+
+ GNU.sparse.size Real size of the stored file
+ GNU.sparse.numblocks Number of blocks in the sparse map
+ GNU.sparse.map Map of non-null data chunks. A string consisting of comma-separated
+ values "offset,size[,offset,size]..."
+
+ The resulting GNU.sparse.map string can be *very* long. While POSIX does not impose
+ any limit on the length of a x header variable, this can confuse some tars.
+
+ * 1.0
+
+ Starting from this version, the exact sparse format version is specified explicitly
+ in the header using the following variables:
+
+ GNU.sparse.major Major version
+ GNU.sparse.minor Minor version
+
+ X header keeps the following variables:
+
+ GNU.sparse.name Real file name of the sparse file
+ GNU.sparse.realsize Real size of the stored file (corresponds to the old GNU.sparse.size
+ variable)
+
+ The name field of the ustar header is constructed using the pattern "%d/GNUSparseFile.%p/%f".
+
+ The sparse map itself is stored in the file data block, preceding the actual file data.
+ It consists of a series of octal numbers of arbitrary length, delimited by newlines.
+ The map is padded with nulls to the nearest block boundary.
+
+ The first number gives the number of entries in the map. Following are map entries, each one
+ consisting of two numbers giving the offset and size of the data block it describes.
+
+ The format is designed in such a way that non-posix aware tars and tars not supporting
+ GNU.sparse.* keywords will extract each sparse file in its condensed form with the file map
+ attached and will place it into a separate directory. Then, using a simple program it would be
+ possible to expand the file to its original form even without GNU tar.
+
+ Bu default, v.1.0 archives are created. To use other formats, --sparse-version option is provided.
+ Additionally, v.0.0 can be obtained by deleting GNU.sparse.map from 0.1 format:
+ --sparse-version 0.1 --pax-option delete=GNU.sparse.map
+ */
+
+static struct tar_sparse_optab const pax_optab = {
+ .init = NULL, /* No init function */
+ .done = NULL, /* No done function */
+ .sparse_member_p = pax_sparse_member_p,
+ .fixup_header = NULL, /* No fixup_header function */
+ .decode_header = pax_decode_header
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_num (uintmax_t * num, const char *arg, uintmax_t maxval)
+{
+ uintmax_t u;
+ char *arg_lim;
+
+ if (!isdigit (*arg))
+ return FALSE;
+
+ errno = 0;
+ u = (uintmax_t) g_ascii_strtoll (arg, &arg_lim, 10);
+
+ if (!(u <= maxval && errno != ERANGE) || *arg_lim != '\0')
+ return FALSE;
+
+ *num = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_select_optab (const tar_super_t * archive, struct tar_sparse_file *file)
+{
+ switch (archive->type)
+ {
+ case TAR_V7:
+ case TAR_USTAR:
+ return FALSE;
+
+ case TAR_OLDGNU:
+ case TAR_GNU: /* FIXME: This one should disappear? */
+ file->optab = &oldgnu_optab;
+ break;
+
+ case TAR_POSIX:
+ file->optab = &pax_optab;
+ break;
+
+ case TAR_STAR:
+ file->optab = &star_optab;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_init (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ memset (file, 0, sizeof (*file));
+
+ if (!sparse_select_optab (archive, file))
+ return FALSE;
+
+ if (file->optab->init != NULL)
+ return file->optab->init (file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_done (struct tar_sparse_file *file)
+{
+ if (file->optab->done != NULL)
+ return file->optab->done (file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_member_p (struct tar_sparse_file *file)
+{
+ if (file->optab->sparse_member_p != NULL)
+ return file->optab->sparse_member_p (file);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_fixup_header (struct tar_sparse_file *file)
+{
+ if (file->optab->fixup_header != NULL)
+ return file->optab->fixup_header (file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_decode_header (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ if (file->optab->decode_header != NULL)
+ return file->optab->decode_header (archive, file);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+sparse_add_map (struct tar_stat_info *st, struct sp_array *sp)
+{
+ if (st->sparse_map == NULL)
+ st->sparse_map = g_array_sized_new (FALSE, FALSE, sizeof (struct sp_array), 1);
+ g_array_append_val (st->sparse_map, *sp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Add a sparse item to the sparse file
+ */
+static enum oldgnu_add_status
+oldgnu_add_sparse (struct tar_sparse_file *file, struct sparse *s)
+{
+ struct sp_array sp;
+
+ if (s->numbytes[0] == '\0')
+ return add_finish;
+
+ sp.offset = OFF_FROM_HEADER (s->offset);
+ sp.numbytes = OFF_FROM_HEADER (s->numbytes);
+
+ if (sp.offset < 0 || sp.numbytes < 0
+ || INT_ADD_OVERFLOW (sp.offset, sp.numbytes)
+ || file->stat_info->stat.st_size < sp.offset + sp.numbytes
+ || file->stat_info->archive_file_size < 0)
+ return add_fail;
+
+ sparse_add_map (file->stat_info, &sp);
+
+ return add_ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+oldgnu_sparse_member_p (struct tar_sparse_file *file)
+{
+ (void) file;
+
+ return current_header->header.typeflag == GNUTYPE_SPARSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+oldgnu_fixup_header (struct tar_sparse_file *file)
+{
+ /* NOTE! st_size was initialized from the header which actually contains archived size.
+ The following fixes it */
+ off_t realsize;
+
+ realsize = OFF_FROM_HEADER (current_header->oldgnu_header.realsize);
+ file->stat_info->archive_file_size = file->stat_info->stat.st_size;
+ file->stat_info->stat.st_size = MAX (0, realsize);
+
+ return (realsize >= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert old GNU format sparse data to internal representation.
+ */
+static gboolean
+oldgnu_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ size_t i;
+ union block *h = current_header;
+ int ext_p;
+ enum oldgnu_add_status rc;
+
+ if (file->stat_info->sparse_map != NULL)
+ g_array_set_size (file->stat_info->sparse_map, 0);
+
+ for (i = 0; i < SPARSES_IN_OLDGNU_HEADER; i++)
+ {
+ rc = oldgnu_add_sparse (file, &h->oldgnu_header.sp[i]);
+ if (rc != add_ok)
+ break;
+ }
+
+ for (ext_p = h->oldgnu_header.isextended ? 1 : 0; rc == add_ok && ext_p != 0;
+ ext_p = h->sparse_header.isextended ? 1 : 0)
+ {
+ h = tar_find_next_block (archive);
+ if (h == NULL)
+ return FALSE;
+
+ tar_set_next_block_after (h);
+
+ for (i = 0; i < SPARSES_IN_SPARSE_HEADER && rc == add_ok; i++)
+ rc = oldgnu_add_sparse (file, &h->sparse_header.sp[i]);
+ }
+
+ return (rc != add_fail);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+star_sparse_member_p (struct tar_sparse_file *file)
+{
+ (void) file;
+
+ return current_header->header.typeflag == GNUTYPE_SPARSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+star_fixup_header (struct tar_sparse_file *file)
+{
+ /* NOTE! st_size was initialized from the header which actually contains archived size.
+ The following fixes it */
+ off_t realsize;
+
+ realsize = OFF_FROM_HEADER (current_header->star_in_header.realsize);
+ file->stat_info->archive_file_size = file->stat_info->stat.st_size;
+ file->stat_info->stat.st_size = MAX (0, realsize);
+
+ return (realsize >= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert STAR format sparse data to internal representation
+ */
+static gboolean
+star_get_sparse_info (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ size_t i;
+ union block *h = current_header;
+ int ext_p = 1;
+ enum oldgnu_add_status rc = add_ok;
+
+ if (file->stat_info->sparse_map != NULL)
+ g_array_set_size (file->stat_info->sparse_map, 0);
+
+ if (h->star_in_header.prefix[0] == '\0' && h->star_in_header.sp[0].offset[10] != '\0')
+ {
+ /* Old star format */
+ for (i = 0; i < SPARSES_IN_STAR_HEADER; i++)
+ {
+ rc = oldgnu_add_sparse (file, &h->star_in_header.sp[i]);
+ if (rc != add_ok)
+ break;
+ }
+
+ ext_p = h->star_in_header.isextended ? 1 : 0;
+ }
+
+ for (; rc == add_ok && ext_p != 0; ext_p = h->star_ext_header.isextended ? 1 : 0)
+ {
+ h = tar_find_next_block (archive);
+ if (h == NULL)
+ return FALSE;
+
+ tar_set_next_block_after (h);
+
+ for (i = 0; i < SPARSES_IN_STAR_EXT_HEADER && rc == add_ok; i++)
+ rc = oldgnu_add_sparse (file, &h->star_ext_header.sp[i]);
+
+ file->dumped_size += BLOCKSIZE;
+ }
+
+ return (rc != add_fail);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+pax_sparse_member_p (struct tar_sparse_file *file)
+{
+ return file->stat_info->sparse_map != NULL && file->stat_info->sparse_map->len > 0
+ && file->stat_info->sparse_major > 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+pax_decode_header (tar_super_t * archive, struct tar_sparse_file *file)
+{
+ if (file->stat_info->sparse_major > 0)
+ {
+ uintmax_t u;
+ char nbuf[UINTMAX_STRSIZE_BOUND];
+ union block *blk;
+ char *p;
+ size_t sparse_map_len;
+ size_t i;
+ off_t start;
+
+ start = tar_current_block_ordinal (archive);
+ tar_set_next_block_after (current_header);
+ blk = tar_find_next_block (archive);
+ if (blk == NULL)
+ /* unexpected EOF in archive */
+ return FALSE;
+ p = blk->buffer;
+ COPY_BUF (archive, blk, nbuf, p);
+
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)))
+ {
+ /* malformed sparse archive member */
+ return FALSE;
+ }
+
+ if (file->stat_info->sparse_map == NULL)
+ file->stat_info->sparse_map =
+ g_array_sized_new (FALSE, FALSE, sizeof (struct sp_array), u);
+ else
+ g_array_set_size (file->stat_info->sparse_map, u);
+
+ sparse_map_len = u;
+
+ for (i = 0; i < sparse_map_len; i++)
+ {
+ struct sp_array sp;
+
+ COPY_BUF (archive, blk, nbuf, p);
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (off_t)))
+ {
+ /* malformed sparse archive member */
+ return FALSE;
+ }
+ sp.offset = u;
+ COPY_BUF (archive, blk, nbuf, p);
+ if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)) || INT_ADD_OVERFLOW (sp.offset, u)
+ || (uintmax_t) file->stat_info->stat.st_size < sp.offset + u)
+ {
+ /* malformed sparse archive member */
+ return FALSE;
+ }
+ sp.numbytes = u;
+ sparse_add_map (file->stat_info, &sp);
+ }
+
+ tar_set_next_block_after (blk);
+
+ file->dumped_size += BLOCKSIZE * (tar_current_block_ordinal (archive) - start);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_sparse_member_p (tar_super_t * archive, struct tar_stat_info * st)
+{
+ struct tar_sparse_file file;
+
+ if (!sparse_init (archive, &file))
+ return FALSE;
+
+ file.stat_info = st;
+ return sparse_member_p (&file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_sparse_fixup_header (tar_super_t * archive, struct tar_stat_info * st)
+{
+ struct tar_sparse_file file;
+
+ if (!sparse_init (archive, &file))
+ return FALSE;
+
+ file.stat_info = st;
+ return sparse_fixup_header (&file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+enum dump_status
+tar_sparse_skip_file (tar_super_t * archive, struct tar_stat_info *st)
+{
+ gboolean rc = TRUE;
+ struct tar_sparse_file file;
+
+ if (!sparse_init (archive, &file))
+ return dump_status_not_implemented;
+
+ file.stat_info = st;
+ file.fd = -1;
+
+ rc = sparse_decode_header (archive, &file);
+ (void) tar_skip_file (archive, file.stat_info->archive_file_size - file.dumped_size);
+ return (sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/tar/tar-xheader.c b/src/vfs/tar/tar-xheader.c
new file mode 100644
index 0000000..5062ed1
--- /dev/null
+++ b/src/vfs/tar/tar-xheader.c
@@ -0,0 +1,1051 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2023
+
+ 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
+ * \brief Source: Virtual File System: GNU Tar file system
+ */
+
+#include <config.h>
+
+#include <ctype.h> /* isdigit() */
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/util.h" /* MC_PTR_FREE */
+
+#include "tar-internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define XHDR_PROTECTED 0x01
+#define XHDR_GLOBAL 0x02
+
+/*** file scope type declarations ****************************************************************/
+
+/* General Interface */
+
+/* Since tar VFS is read-only, inplement decodes only */
+/* *INDENT-OFF* */
+struct xhdr_tab
+{
+ const char *keyword;
+ gboolean (*decoder) (struct tar_stat_info * st, const char *keyword, const char *arg, size_t size);
+ int flags;
+};
+/* *INDENT-ON* */
+
+/* Keyword options */
+struct keyword_item
+{
+ char *pattern;
+ char *value;
+};
+
+enum decode_record_status
+{
+ decode_record_ok,
+ decode_record_finish,
+ decode_record_fail
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static gboolean dummy_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean atime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean gid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#if 0
+static gboolean gname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#endif
+static gboolean linkpath_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean mtime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean ctime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean uid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#if 0
+static gboolean uname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+#endif
+static gboolean sparse_path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean sparse_major_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_minor_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean sparse_numblocks_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_offset_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_numbytes_decoder (struct tar_stat_info *st, const char *keyword,
+ const char *arg, size_t size);
+static gboolean sparse_map_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+static gboolean dumpdir_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size);
+
+/*** file scope variables ************************************************************************/
+
+enum
+{
+ BILLION = 1000000000,
+ LOG10_BILLION = 9
+};
+
+/* *INDENT-OFF* */
+static struct xhdr_tab xhdr_tab[] =
+{
+ { "atime", atime_decoder, 0 },
+ { "comment", dummy_decoder, 0 },
+ { "charset", dummy_decoder, 0 },
+ { "ctime", ctime_decoder, 0 },
+ { "gid", gid_decoder, 0 },
+#if 0
+ { "gname", gname_decoder, 0 },
+#endif
+ { "linkpath", linkpath_decoder, 0 },
+ { "mtime", mtime_decoder, 0 },
+ { "path", path_decoder, 0 },
+ { "size", size_decoder, 0 },
+ { "uid", uid_decoder, 0 },
+#if 0
+ { "uname", uname_decoder, 0 },
+#endif
+
+ /* Sparse file handling */
+ { "GNU.sparse.name", sparse_path_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.major", sparse_major_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.minor", sparse_minor_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.realsize", sparse_size_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.numblocks", sparse_numblocks_decoder, XHDR_PROTECTED },
+
+ { "GNU.sparse.size", sparse_size_decoder, XHDR_PROTECTED },
+ /* tar 1.14 - 1.15.1 keywords. Multiple instances of these appeared in 'x'
+ headers, and each of them was meaningful. It confilcted with POSIX specs,
+ which requires that "when extended header records conflict, the last one
+ given in the header shall take precedence." */
+ { "GNU.sparse.offset", sparse_offset_decoder, XHDR_PROTECTED },
+ { "GNU.sparse.numbytes", sparse_numbytes_decoder, XHDR_PROTECTED },
+ /* tar 1.15.90 keyword, introduced to remove the above-mentioned conflict. */
+ { "GNU.sparse.map", sparse_map_decoder, 0 },
+
+ { "GNU.dumpdir", dumpdir_decoder, XHDR_PROTECTED },
+
+ { NULL, NULL, 0 }
+};
+/* *INDENT-ON* */
+
+/* List of keyword/value pairs decoded from the last 'g' type header */
+static GSList *global_header_override_list = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Convert a prefix of the string @arg to a system integer type whose minimum value is @minval
+ and maximum @maxval. If @minval is negative, negative integers @minval .. -1 are assumed
+ to be represented using leading '-' in the usual way. If the represented value exceeds INTMAX_MAX,
+ return a negative integer V such that (uintmax_t) V yields the represented value. If @arglim is
+ nonnull, store into *@arglim a pointer to the first character after the prefix.
+
+ This is the inverse of sysinttostr.
+
+ On a normal return, set errno = 0.
+ On conversion error, return 0 and set errno = EINVAL.
+ On overflow, return an extreme value and set errno = ERANGE.
+ */
+#if ! (INTMAX_MAX <= UINTMAX_MAX)
+#error "strtosysint: nonnegative intmax_t does not fit in uintmax_t"
+#endif
+static intmax_t
+strtosysint (const char *arg, char **arglim, intmax_t minval, uintmax_t maxval)
+{
+ errno = 0;
+
+ if (maxval <= INTMAX_MAX)
+ {
+ if (isdigit (arg[*arg == '-' ? 1 : 0]))
+ {
+ gint64 i;
+
+ i = g_ascii_strtoll (arg, arglim, 10);
+ if ((gint64) minval <= i && i <= (gint64) maxval)
+ return (intmax_t) i;
+
+ errno = ERANGE;
+ return i < (gint64) minval ? minval : (intmax_t) maxval;
+ }
+ }
+ else
+ {
+ if (isdigit (*arg))
+ {
+ guint64 i;
+
+ i = g_ascii_strtoull (arg, arglim, 10);
+ if (i <= (guint64) maxval)
+ return tar_represent_uintmax ((uintmax_t) i);
+
+ errno = ERANGE;
+ return maxval;
+ }
+ }
+
+ errno = EINVAL;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct xhdr_tab *
+locate_handler (const char *keyword)
+{
+ struct xhdr_tab *p;
+
+ for (p = xhdr_tab; p->keyword != NULL; p++)
+ if (strcmp (p->keyword, keyword) == 0)
+ return p;
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+keyword_item_run (gpointer data, gpointer user_data)
+{
+ struct keyword_item *kp = (struct keyword_item *) data;
+ struct tar_stat_info *st = (struct tar_stat_info *) user_data;
+ struct xhdr_tab const *t;
+
+ t = locate_handler (kp->pattern);
+ if (t != NULL)
+ return t->decoder (st, t->keyword, kp->value, strlen (kp->value));
+
+ return TRUE; /* FIXME */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+keyword_item_free (gpointer data)
+{
+ struct keyword_item *kp = (struct keyword_item *) data;
+
+ g_free (kp->pattern);
+ g_free (kp->value);
+ g_free (kp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+xheader_list_append (GSList ** root, const char *kw, const char *value)
+{
+ struct keyword_item *kp;
+
+ kp = g_new (struct keyword_item, 1);
+ kp->pattern = g_strdup (kw);
+ kp->value = g_strdup (value);
+ *root = g_slist_prepend (*root, kp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+xheader_list_destroy (GSList ** root)
+{
+ g_slist_free_full (*root, keyword_item_free);
+ *root = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+run_override_list (GSList * kp, struct tar_stat_info *st)
+{
+ g_slist_foreach (kp, (GFunc) keyword_item_run, st);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct timespec
+decode_timespec (const char *arg, char **arg_lim, gboolean parse_fraction)
+{
+ time_t s = TYPE_MINIMUM (time_t);
+ int ns = -1;
+ const char *p = arg;
+ gboolean negative = *arg == '-';
+ struct timespec r;
+
+ if (!isdigit (arg[negative]))
+ errno = EINVAL;
+ else
+ {
+ errno = 0;
+
+ if (negative)
+ {
+ gint64 i;
+
+ i = g_ascii_strtoll (arg, arg_lim, 10);
+ if (TYPE_SIGNED (time_t) ? TYPE_MINIMUM (time_t) <= i : 0 <= i)
+ s = (intmax_t) i;
+ else
+ errno = ERANGE;
+ }
+ else
+ {
+ guint64 i;
+
+ i = g_ascii_strtoull (arg, arg_lim, 10);
+ if (i <= TYPE_MAXIMUM (time_t))
+ s = (uintmax_t) i;
+ else
+ errno = ERANGE;
+ }
+
+ p = *arg_lim;
+ ns = 0;
+
+ if (parse_fraction && *p == '.')
+ {
+ int digits = 0;
+ gboolean trailing_nonzero = FALSE;
+
+ while (isdigit (*++p))
+ if (digits < LOG10_BILLION)
+ {
+ digits++;
+ ns = 10 * ns + (*p - '0');
+ }
+ else if (*p != '0')
+ trailing_nonzero = TRUE;
+
+ while (digits < LOG10_BILLION)
+ {
+ digits++;
+ ns *= 10;
+ }
+
+ if (negative)
+ {
+ /* Convert "-1.10000000000001" to s == -2, ns == 89999999.
+ I.e., truncate time stamps towards minus infinity while
+ converting them to internal form. */
+ if (trailing_nonzero)
+ ns++;
+ if (ns != 0)
+ {
+ if (s == TYPE_MINIMUM (time_t))
+ ns = -1;
+ else
+ {
+ s--;
+ ns = BILLION - ns;
+ }
+ }
+ }
+ }
+
+ if (errno == ERANGE)
+ ns = -1;
+ }
+
+ *arg_lim = (char *) p;
+ r.tv_sec = s;
+ r.tv_nsec = ns;
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_time (struct timespec *ts, const char *arg, const char *keyword)
+{
+ char *arg_lim;
+ struct timespec t;
+
+ (void) keyword;
+
+ t = decode_timespec (arg, &arg_lim, TRUE);
+
+ if (t.tv_nsec < 0)
+ /* Malformed extended header */
+ return FALSE;
+
+ if (*arg_lim != '\0')
+ /* Malformed extended header */
+ return FALSE;
+
+ *ts = t;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_signed_num (intmax_t * num, const char *arg, intmax_t minval, uintmax_t maxval,
+ const char *keyword)
+{
+ char *arg_lim;
+ intmax_t u;
+
+ (void) keyword;
+
+ if (!isdigit (*arg))
+ return FALSE; /* malformed extended header */
+
+ u = strtosysint (arg, &arg_lim, minval, maxval);
+
+ if (errno == EINVAL || *arg_lim != '\0')
+ return FALSE; /* malformed extended header */
+
+ if (errno == ERANGE)
+ return FALSE; /* out of range */
+
+ *num = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decode_num (uintmax_t * num, const char *arg, uintmax_t maxval, const char *keyword)
+{
+ intmax_t i;
+
+ if (!decode_signed_num (&i, arg, 0, maxval, keyword))
+ return FALSE;
+
+ *num = i;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+raw_path_decoder (struct tar_stat_info *st, const char *arg)
+{
+ if (*arg != '\0')
+ {
+ tar_assign_string_dup (&st->orig_file_name, arg);
+ tar_assign_string_dup (&st->file_name, arg);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+dummy_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) st;
+ (void) keyword;
+ (void) arg;
+ (void) size;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+atime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ struct timespec ts;
+
+ (void) size;
+
+ if (!decode_time (&ts, arg, keyword))
+ return FALSE;
+
+ st->atime = ts;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+gid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ intmax_t u;
+
+ (void) size;
+
+ if (!decode_signed_num (&u, arg, TYPE_MINIMUM (gid_t), TYPE_MINIMUM (gid_t), keyword))
+ return FALSE;
+
+ st->stat.st_gid = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static gboolean
+gname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ tar_assign_string_dup (&st->gname, arg);
+ return TRUE;
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+linkpath_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ tar_assign_string_dup (&st->link_name, arg);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+ctime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ struct timespec ts;
+
+ (void) size;
+
+ if (!decode_time (&ts, arg, keyword))
+ return FALSE;
+
+ st->ctime = ts;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mtime_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ struct timespec ts;
+
+ (void) size;
+
+ if (!decode_time (&ts, arg, keyword))
+ return FALSE;
+
+ st->mtime = ts;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ if (!st->sparse_name_done)
+ return raw_path_decoder (st, arg);
+
+ return TRUE; /* FIXME */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ return FALSE;
+
+ st->stat.st_size = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+uid_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ intmax_t u;
+
+ (void) size;
+
+ if (!decode_signed_num (&u, arg, TYPE_MINIMUM (uid_t), TYPE_MAXIMUM (uid_t), keyword))
+ return FALSE;
+
+ st->stat.st_uid = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static gboolean
+uname_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ tar_assign_string_dup (&st->uname, arg);
+ return TRUE;
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+dumpdir_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+
+#if GLIB_CHECK_VERSION (2, 68, 0)
+ st->dumpdir = g_memdup2 (arg, size);
+#else
+ st->dumpdir = g_memdup (arg, size);
+#endif
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Decodes a single extended header record, advancing @ptr to the next record.
+ *
+ * @param p pointer to extended header record
+ * @param st stat info
+ *
+ * @return decode_record_ok or decode_record_finish on success, decode_record_fail otherwize
+ */
+static enum decode_record_status
+decode_record (struct xheader *xhdr, char **ptr,
+ gboolean (*handler) (void *data, const char *keyword, const char *value,
+ size_t size), void *data)
+{
+ char *start = *ptr;
+ char *p = start;
+ size_t len;
+ char *len_lim;
+ const char *keyword;
+ char *nextp;
+ size_t len_max;
+ gboolean ret;
+
+ len_max = xhdr->buffer + xhdr->size - start;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (!isdigit (*p))
+ return (*p != '\0' ? decode_record_fail : decode_record_finish);
+
+ len = (uintmax_t) g_ascii_strtoull (p, &len_lim, 10);
+ if (len_max < len)
+ return decode_record_fail;
+
+ nextp = start + len;
+
+ for (p = len_lim; *p == ' ' || *p == '\t'; p++)
+ ;
+
+ if (p == len_lim)
+ return decode_record_fail;
+
+ keyword = p;
+ p = strchr (p, '=');
+ if (!(p != NULL && p < nextp))
+ return decode_record_fail;
+
+ if (nextp[-1] != '\n')
+ return decode_record_fail;
+
+ *p = nextp[-1] = '\0';
+ ret = handler (data, keyword, p + 1, nextp - p - 2); /* '=' + trailing '\n' */
+ *p = '=';
+ nextp[-1] = '\n';
+ *ptr = nextp;
+
+ return (ret ? decode_record_ok : decode_record_fail);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decg (void *data, const char *keyword, const char *value, size_t size)
+{
+ GSList **kwl = (GSList **) data;
+ struct xhdr_tab const *tab;
+
+ (void) size;
+
+ tab = locate_handler (keyword);
+ if (tab != NULL && (tab->flags & XHDR_GLOBAL) != 0)
+ {
+ if (!tab->decoder (data, keyword, value, size))
+ return FALSE;
+ }
+ else
+ xheader_list_append (kwl, keyword, value);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+decx (void *data, const char *keyword, const char *value, size_t size)
+{
+ struct keyword_item kp = {
+ .pattern = (char *) keyword,
+ .value = (char *) value
+ };
+
+ (void) size;
+
+ return keyword_item_run (&kp, data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_path_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ (void) keyword;
+ (void) size;
+
+ st->sparse_name_done = TRUE;
+ return raw_path_decoder (st, arg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_major_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (unsigned), keyword))
+ return FALSE;
+
+ st->sparse_major = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_minor_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (unsigned), keyword))
+ return FALSE;
+
+ st->sparse_minor = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_size_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ return FALSE;
+
+ st->real_size_set = TRUE;
+ st->real_size = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_numblocks_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size)
+{
+ uintmax_t u;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, SIZE_MAX, keyword))
+ return FALSE;
+
+ if (st->sparse_map == NULL)
+ st->sparse_map = g_array_sized_new (FALSE, FALSE, sizeof (struct sp_array), u);
+ else
+ g_array_set_size (st->sparse_map, u);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_offset_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ uintmax_t u;
+ struct sp_array *s;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, TYPE_MAXIMUM (off_t), keyword))
+ return FALSE;
+
+ s = &g_array_index (st->sparse_map, struct sp_array, st->sparse_map->len - 1);
+ s->offset = u;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_numbytes_decoder (struct tar_stat_info *st, const char *keyword, const char *arg,
+ size_t size)
+{
+ uintmax_t u;
+ struct sp_array s;
+
+ (void) size;
+
+ if (!decode_num (&u, arg, SIZE_MAX, keyword))
+ return FALSE;
+
+ s.offset = 0;
+ s.numbytes = u;
+ g_array_append_val (st->sparse_map, s);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+sparse_map_decoder (struct tar_stat_info *st, const char *keyword, const char *arg, size_t size)
+{
+ gboolean offset = TRUE;
+ struct sp_array e;
+
+ (void) keyword;
+ (void) size;
+
+ if (st->sparse_map != NULL)
+ g_array_set_size (st->sparse_map, 0);
+
+ while (TRUE)
+ {
+ gint64 u;
+ char *delim;
+
+ if (!isdigit (*arg))
+ {
+ /* malformed extended header */
+ return FALSE;
+ }
+
+ errno = 0;
+ u = g_ascii_strtoll (arg, &delim, 10);
+ if (TYPE_MAXIMUM (off_t) < u)
+ {
+ u = TYPE_MAXIMUM (off_t);
+ errno = ERANGE;
+ }
+ if (offset)
+ {
+ e.offset = u;
+ if (errno == ERANGE)
+ {
+ /* out of range */
+ return FALSE;
+ }
+ }
+ else
+ {
+ e.numbytes = u;
+ if (errno == ERANGE)
+ {
+ /* out of range */
+ return FALSE;
+ }
+
+ g_array_append_val (st->sparse_map, e);
+ }
+
+ offset = !offset;
+
+ if (*delim == '\0')
+ break;
+ if (*delim != ',')
+ {
+ /* malformed extended header */
+ return FALSE;
+ }
+
+ arg = delim + 1;
+ }
+
+ if (!offset)
+ {
+ /* malformed extended header */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Decodes an extended header.
+ *
+ * @param st stat info
+ *
+ * @return TRUE on success, FALSE otherwize
+ */
+gboolean
+tar_xheader_decode (struct tar_stat_info * st)
+{
+ char *p;
+ enum decode_record_status status;
+
+ run_override_list (global_header_override_list, st);
+
+ p = st->xhdr.buffer + BLOCKSIZE;
+
+ while ((status = decode_record (&st->xhdr, &p, decx, st)) == decode_record_ok)
+ ;
+
+ if (status == decode_record_fail)
+ return FALSE;
+
+ /* The archived (effective) file size is always set directly in tar header
+ field, possibly overridden by "size" extended header - in both cases,
+ result is now decoded in st->stat.st_size */
+ st->archive_file_size = st->stat.st_size;
+
+ /* The real file size (given by stat()) may be redefined for sparse
+ files in "GNU.sparse.realsize" extended header */
+ if (st->real_size_set)
+ st->stat.st_size = st->real_size;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_xheader_read (tar_super_t * archive, struct xheader * xhdr, union block * p, off_t size)
+{
+ size_t j = 0;
+
+ size = MAX (0, size);
+ size += BLOCKSIZE;
+
+ xhdr->size = size;
+ xhdr->buffer = g_malloc (size + 1);
+ xhdr->buffer[size] = '\0';
+
+ do
+ {
+ size_t len;
+
+ if (p == NULL)
+ return FALSE; /* Unexpected EOF in archive */
+
+ len = MIN (size, BLOCKSIZE);
+
+ memcpy (xhdr->buffer + j, p->buffer, len);
+ tar_set_next_block_after (p);
+ p = tar_find_next_block (archive);
+
+ j += len;
+ size -= len;
+ }
+ while (size > 0);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+tar_xheader_decode_global (struct xheader * xhdr)
+{
+ char *p;
+ gboolean ret;
+
+ p = xhdr->buffer + BLOCKSIZE;
+
+ xheader_list_destroy (&global_header_override_list);
+
+ while ((ret = decode_record (xhdr, &p, decg, &global_header_override_list)) == decode_record_ok)
+ ;
+
+ return (ret == decode_record_finish);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tar_xheader_destroy (struct xheader *xhdr)
+{
+ MC_PTR_FREE (xhdr->buffer);
+ xhdr->size = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/tar/tar.c b/src/vfs/tar/tar.c
new file mode 100644
index 0000000..2d32111
--- /dev/null
+++ b/src/vfs/tar/tar.c
@@ -0,0 +1,1302 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Jakub Jelinek, 1995
+ Pavel Machek, 1998
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ 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
+ * \brief Source: Virtual File System: GNU Tar file system
+ * \author Jakub Jelinek
+ * \author Pavel Machek
+ * \date 1995, 1998
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <string.h> /* memset() */
+
+#ifdef hpux
+/* major() and minor() macros (among other things) defined here for hpux */
+#include <sys/mknod.h>
+#endif
+
+#include "lib/global.h"
+#include "lib/util.h"
+#include "lib/unixcompat.h" /* makedev() */
+#include "lib/widget.h" /* message() */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/gc.h" /* vfs_rmstamp */
+
+#include "tar-internal.h"
+#include "tar.h"
+
+/*** global variables ****************************************************************************/
+
+/* Size of each record, once in blocks, once in bytes. Those two variables are always related,
+ the second being BLOCKSIZE times the first. */
+const int blocking_factor = DEFAULT_BLOCKING;
+const size_t record_size = DEFAULT_BLOCKING * BLOCKSIZE;
+
+/* As we open one archive at a time, it is safe to have these static */
+union block *record_end; /* last+1 block of archive record */
+union block *current_block; /* current block of archive */
+off_t record_start_block; /* block ordinal at record_start */
+
+union block *current_header;
+
+/* Have we hit EOF yet? */
+gboolean hit_eof;
+
+struct tar_stat_info current_stat_info;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define TAR_SUPER(super) ((tar_super_t *) (super))
+
+/* tar Header Block, from POSIX 1003.1-1990. */
+
+/* The magic field is filled with this if uname and gname are valid. */
+#define TMAGIC "ustar" /* ustar and a null */
+
+#define XHDTYPE 'x' /* Extended header referring to the next file in the archive */
+#define XGLTYPE 'g' /* Global extended header */
+
+/* Values used in typeflag field. */
+#define LNKTYPE '1' /* link */
+#define SYMTYPE '2' /* symbolic link */
+#define CHRTYPE '3' /* character special */
+#define BLKTYPE '4' /* block special */
+#define DIRTYPE '5' /* directory */
+#define FIFOTYPE '6' /* FIFO special */
+
+
+/* OLDGNU_MAGIC uses both magic and version fields, which are contiguous.
+ Found in an archive, it indicates an old GNU header format, which will be
+ hopefully become obsolescent. With OLDGNU_MAGIC, uname and gname are
+ valid, though the header is not truly POSIX conforming. */
+#define OLDGNU_MAGIC "ustar " /* 7 chars and a null */
+
+
+/* Bits used in the mode field, values in octal. */
+#define TSUID 04000 /* set UID on execution */
+#define TSGID 02000 /* set GID on execution */
+#define TSVTX 01000 /* reserved */
+ /* file permissions */
+#define TUREAD 00400 /* read by owner */
+#define TUWRITE 00200 /* write by owner */
+#define TUEXEC 00100 /* execute/search by owner */
+#define TGREAD 00040 /* read by group */
+#define TGWRITE 00020 /* write by group */
+#define TGEXEC 00010 /* execute/search by group */
+#define TOREAD 00004 /* read by other */
+#define TOWRITE 00002 /* write by other */
+#define TOEXEC 00001 /* execute/search by other */
+
+#define GID_FROM_HEADER(where) gid_from_header (where, sizeof (where))
+#define MAJOR_FROM_HEADER(where) major_from_header (where, sizeof (where))
+#define MINOR_FROM_HEADER(where) minor_from_header (where, sizeof (where))
+#define MODE_FROM_HEADER(where,hbits) mode_from_header (where, sizeof (where), hbits)
+#define TIME_FROM_HEADER(where) time_from_header (where, sizeof (where))
+#define UID_FROM_HEADER(where) uid_from_header (where, sizeof (where))
+#define UINTMAX_FROM_HEADER(where) uintmax_from_header (where, sizeof (where))
+
+/*** file scope type declarations ****************************************************************/
+
+typedef enum
+{
+ HEADER_STILL_UNREAD, /* for when read_header has not been called */
+ HEADER_SUCCESS, /* header successfully read and checksummed */
+ HEADER_ZERO_BLOCK, /* zero block where header expected */
+ HEADER_END_OF_FILE, /* true end of file while header expected */
+ HEADER_FAILURE /* ill-formed header, or bad checksum */
+} read_header;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct vfs_s_subclass tarfs_subclass;
+static struct vfs_class *vfs_tarfs_ops = VFS_CLASS (&tarfs_subclass);
+
+static struct timespec start_time;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_stat_destroy (struct tar_stat_info *st)
+{
+ g_free (st->orig_file_name);
+ g_free (st->file_name);
+ g_free (st->link_name);
+#if 0
+ g_free (st->uname);
+ g_free (st->gname);
+#endif
+ if (st->sparse_map != NULL)
+ {
+ g_array_free (st->sparse_map, TRUE);
+ st->sparse_map = NULL;
+ }
+ tar_xheader_destroy (&st->xhdr);
+ memset (st, 0, sizeof (*st));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gid_t
+gid_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "gid_t", TYPE_MINIMUM (gid_t), TYPE_MAXIMUM (gid_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline major_t
+major_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "major_t", TYPE_MINIMUM (major_t), TYPE_MAXIMUM (major_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline minor_t
+minor_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "minor_t", TYPE_MINIMUM (minor_t), TYPE_MAXIMUM (minor_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert @p to the file mode, as understood by tar.
+ * Store unrecognized mode bits (from 10th up) in @hbits.
+ * Set *hbits if there are any unrecognized bits.
+ * */
+static inline mode_t
+mode_from_header (const char *p, size_t s, gboolean * hbits)
+{
+ unsigned int u;
+ mode_t mode;
+
+ /* Do not complain about unrecognized mode bits. */
+ u = tar_from_header (p, s, "mode_t", INTMAX_MIN, UINTMAX_MAX, FALSE);
+
+ /* *INDENT-OFF* */
+ mode = ((u & TSUID ? S_ISUID : 0)
+ | (u & TSGID ? S_ISGID : 0)
+ | (u & TSVTX ? S_ISVTX : 0)
+ | (u & TUREAD ? S_IRUSR : 0)
+ | (u & TUWRITE ? S_IWUSR : 0)
+ | (u & TUEXEC ? S_IXUSR : 0)
+ | (u & TGREAD ? S_IRGRP : 0)
+ | (u & TGWRITE ? S_IWGRP : 0)
+ | (u & TGEXEC ? S_IXGRP : 0)
+ | (u & TOREAD ? S_IROTH : 0)
+ | (u & TOWRITE ? S_IWOTH : 0)
+ | (u & TOEXEC ? S_IXOTH : 0));
+ /* *INDENT-ON* */
+
+ if (hbits != NULL)
+ *hbits = (u & ~07777) != 0;
+
+ return mode;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline time_t
+time_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "time_t", TYPE_MINIMUM (time_t), TYPE_MAXIMUM (time_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline uid_t
+uid_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "uid_t", TYPE_MINIMUM (uid_t), TYPE_MAXIMUM (uid_t), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline uintmax_t
+uintmax_from_header (const char *p, size_t s)
+{
+ return tar_from_header (p, s, "uintmax_t", 0, UINTMAX_MAX, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_calc_sparse_offsets (struct vfs_s_inode *inode)
+{
+ off_t begin = inode->data_offset;
+ GArray *sm = (GArray *) inode->user_data;
+ size_t i;
+
+ for (i = 0; i < sm->len; i++)
+ {
+ struct sp_array *sp;
+
+ sp = &g_array_index (sm, struct sp_array, i);
+ sp->arch_offset = begin;
+ begin += BLOCKSIZE * (sp->numbytes / BLOCKSIZE + sp->numbytes % BLOCKSIZE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tar_skip_member (tar_super_t * archive, struct vfs_s_inode *inode)
+{
+ char save_typeflag;
+
+ if (current_stat_info.skipped)
+ return TRUE;
+
+ save_typeflag = current_header->header.typeflag;
+
+ tar_set_next_block_after (current_header);
+
+ if (current_stat_info.is_sparse)
+ {
+ if (inode != NULL)
+ inode->data_offset = BLOCKSIZE * tar_current_block_ordinal (archive);
+
+ (void) tar_sparse_skip_file (archive, &current_stat_info);
+
+ if (inode != NULL)
+ {
+ /* use vfs_s_inode::user_data to keep the sparse map */
+ inode->user_data = current_stat_info.sparse_map;
+ current_stat_info.sparse_map = NULL;
+
+ tar_calc_sparse_offsets (inode);
+ }
+ }
+ else if (save_typeflag != DIRTYPE)
+ {
+ if (inode != NULL)
+ inode->data_offset = BLOCKSIZE * tar_current_block_ordinal (archive);
+
+ return tar_skip_file (archive, current_stat_info.stat.st_size);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Return the number of bytes comprising the space between @pointer through the end
+ * of the current buffer of blocks. This space is available for filling with data,
+ * or taking data from. @pointer is usually (but not always) the result previous
+ * tar_find_next_block() call.
+ */
+static inline size_t
+tar_available_space_after (const union block *pointer)
+{
+ return record_end->buffer - pointer->buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Check header checksum.
+ */
+static read_header
+tar_checksum (const union block *header)
+{
+ size_t i;
+ int unsigned_sum = 0; /* the POSIX one :-) */
+ int signed_sum = 0; /* the Sun one :-( */
+ int recorded_sum;
+ int parsed_sum;
+ const char *p = header->buffer;
+
+ for (i = sizeof (*header); i-- != 0;)
+ {
+ unsigned_sum += (unsigned char) *p;
+ signed_sum += (signed char) (*p++);
+ }
+
+ if (unsigned_sum == 0)
+ return HEADER_ZERO_BLOCK;
+
+ /* Adjust checksum to count the "chksum" field as blanks. */
+ for (i = sizeof (header->header.chksum); i-- != 0;)
+ {
+ unsigned_sum -= (unsigned char) header->header.chksum[i];
+ signed_sum -= (signed char) (header->header.chksum[i]);
+ }
+
+ unsigned_sum += ' ' * sizeof (header->header.chksum);
+ signed_sum += ' ' * sizeof (header->header.chksum);
+
+ parsed_sum =
+ tar_from_header (header->header.chksum, sizeof (header->header.chksum), NULL, 0,
+ INT_MAX, TRUE);
+ if (parsed_sum < 0)
+ return HEADER_FAILURE;
+
+ recorded_sum = parsed_sum;
+
+ if (unsigned_sum != recorded_sum && signed_sum != recorded_sum)
+ return HEADER_FAILURE;
+
+ return HEADER_SUCCESS;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_decode_header (union block *header, tar_super_t * arch)
+{
+ gboolean hbits = FALSE;
+
+ current_stat_info.stat.st_mode = MODE_FROM_HEADER (header->header.mode, &hbits);
+
+ /*
+ * Try to determine the archive format.
+ */
+ if (arch->type == TAR_UNKNOWN)
+ {
+ if (strcmp (header->header.magic, TMAGIC) == 0)
+ {
+ if (header->star_header.prefix[130] == 0 && isodigit (header->star_header.atime[0])
+ && header->star_header.atime[11] == ' ' && isodigit (header->star_header.ctime[0])
+ && header->star_header.ctime[11] == ' ')
+ arch->type = TAR_STAR;
+ else if (current_stat_info.xhdr.buffer != NULL)
+ arch->type = TAR_POSIX;
+ else
+ arch->type = TAR_USTAR;
+ }
+ else if (strcmp (header->buffer + offsetof (struct posix_header, magic), OLDGNU_MAGIC) == 0)
+ arch->type = hbits ? TAR_OLDGNU : TAR_GNU;
+ else
+ arch->type = TAR_V7;
+ }
+
+ /*
+ * typeflag on BSDI tar (pax) always '\000'
+ */
+ if (header->header.typeflag == '\000')
+ {
+ size_t len;
+
+ if (header->header.name[sizeof (header->header.name) - 1] != '\0')
+ len = sizeof (header->header.name);
+ else
+ len = strlen (header->header.name);
+
+ if (len != 0 && IS_PATH_SEP (header->header.name[len - 1]))
+ header->header.typeflag = DIRTYPE;
+ }
+
+ if (header->header.typeflag == GNUTYPE_DUMPDIR)
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_GNU;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_fill_stat (struct vfs_s_super *archive, union block *header)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+
+ /* Adjust current_stat_info.stat.st_mode because there are tar-files with
+ * typeflag==SYMTYPE and S_ISLNK(mod)==0. I don't
+ * know about the other modes but I think I cause no new
+ * problem when I adjust them, too. -- Norbert.
+ */
+ if (header->header.typeflag == DIRTYPE || header->header.typeflag == GNUTYPE_DUMPDIR)
+ current_stat_info.stat.st_mode |= S_IFDIR;
+ else if (header->header.typeflag == SYMTYPE)
+ current_stat_info.stat.st_mode |= S_IFLNK;
+ else if (header->header.typeflag == CHRTYPE)
+ current_stat_info.stat.st_mode |= S_IFCHR;
+ else if (header->header.typeflag == BLKTYPE)
+ current_stat_info.stat.st_mode |= S_IFBLK;
+ else if (header->header.typeflag == FIFOTYPE)
+ current_stat_info.stat.st_mode |= S_IFIFO;
+ else
+ current_stat_info.stat.st_mode |= S_IFREG;
+
+ current_stat_info.stat.st_dev = 0;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ current_stat_info.stat.st_rdev = 0;
+#endif
+
+ switch (arch->type)
+ {
+ case TAR_USTAR:
+ case TAR_POSIX:
+ case TAR_GNU:
+ case TAR_OLDGNU:
+ /* *INDENT-OFF* */
+ current_stat_info.stat.st_uid = *header->header.uname != '\0'
+ ? (uid_t) vfs_finduid (header->header.uname)
+ : UID_FROM_HEADER (header->header.uid);
+ current_stat_info.stat.st_gid = *header->header.gname != '\0'
+ ? (gid_t) vfs_findgid (header->header.gname)
+ : GID_FROM_HEADER (header->header.gid);
+ /* *INDENT-ON* */
+
+ switch (header->header.typeflag)
+ {
+ case BLKTYPE:
+ case CHRTYPE:
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ current_stat_info.stat.st_rdev =
+ makedev (MAJOR_FROM_HEADER (header->header.devmajor),
+ MINOR_FROM_HEADER (header->header.devminor));
+#endif
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ current_stat_info.stat.st_uid = UID_FROM_HEADER (header->header.uid);
+ current_stat_info.stat.st_gid = GID_FROM_HEADER (header->header.gid);
+ break;
+ }
+
+ current_stat_info.atime.tv_nsec = 0;
+ current_stat_info.mtime.tv_nsec = 0;
+ current_stat_info.ctime.tv_nsec = 0;
+
+ current_stat_info.mtime.tv_sec = TIME_FROM_HEADER (header->header.mtime);
+ if (arch->type == TAR_GNU || arch->type == TAR_OLDGNU)
+ {
+ current_stat_info.atime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.atime);
+ current_stat_info.ctime.tv_sec = TIME_FROM_HEADER (header->oldgnu_header.ctime);
+ }
+ else if (arch->type == TAR_STAR)
+ {
+ current_stat_info.atime.tv_sec = TIME_FROM_HEADER (header->star_header.atime);
+ current_stat_info.ctime.tv_sec = TIME_FROM_HEADER (header->star_header.ctime);
+ }
+ else
+ current_stat_info.atime = current_stat_info.ctime = start_time;
+
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ current_stat_info.stat.st_blksize = 8 * 1024; /* FIXME */
+#endif
+ vfs_adjust_stat (&current_stat_info.stat);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_free_inode (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ (void) me;
+
+ /* free sparse_map */
+ if (ino->user_data != NULL)
+ g_array_free ((GArray *) ino->user_data, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static read_header
+tar_insert_entry (struct vfs_class *me, struct vfs_s_super *archive, union block *header,
+ struct vfs_s_inode **inode)
+{
+ char *p, *q;
+ char *file_name = current_stat_info.file_name;
+ char *link_name = current_stat_info.link_name;
+ size_t len;
+ struct vfs_s_inode *parent;
+ struct vfs_s_entry *entry;
+
+ p = strrchr (file_name, PATH_SEP);
+ if (p == NULL)
+ {
+ len = strlen (file_name);
+ p = file_name;
+ q = file_name + len; /* "" */
+ }
+ else
+ {
+ *(p++) = '\0';
+ q = file_name;
+ }
+
+ parent = vfs_s_find_inode (me, archive, q, LINK_NO_FOLLOW, FL_MKDIR);
+ if (parent == NULL)
+ return HEADER_FAILURE;
+
+ *inode = NULL;
+
+ if (header->header.typeflag == LNKTYPE)
+ {
+ if (*link_name != '\0')
+ {
+ len = strlen (link_name);
+ if (IS_PATH_SEP (link_name[len - 1]))
+ link_name[len - 1] = '\0';
+
+ *inode = vfs_s_find_inode (me, archive, link_name, LINK_NO_FOLLOW, FL_NONE);
+ }
+
+ if (*inode == NULL)
+ return HEADER_FAILURE;
+ }
+ else
+ {
+ if (S_ISDIR (current_stat_info.stat.st_mode))
+ {
+ entry = VFS_SUBCLASS (me)->find_entry (me, parent, p, LINK_NO_FOLLOW, FL_NONE);
+ if (entry != NULL)
+ return HEADER_SUCCESS;
+ }
+
+ *inode = vfs_s_new_inode (me, archive, &current_stat_info.stat);
+ /* assgin timestamps after decoding of extended headers */
+ (*inode)->st.st_mtime = current_stat_info.mtime.tv_sec;
+ (*inode)->st.st_atime = current_stat_info.atime.tv_sec;
+ (*inode)->st.st_ctime = current_stat_info.ctime.tv_sec;
+ (*inode)->data_offset = BLOCKSIZE * tar_current_block_ordinal (TAR_SUPER (archive));
+
+ if (link_name != NULL && *link_name != '\0')
+ (*inode)->linkname = g_strdup (link_name);
+ }
+
+ entry = vfs_s_new_entry (me, p, *inode);
+ vfs_s_insert_entry (me, parent, entry);
+
+ return HEADER_SUCCESS;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static read_header
+tar_read_header (struct vfs_class *me, struct vfs_s_super *archive)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+ union block *header;
+ union block *next_long_name = NULL, *next_long_link = NULL;
+ read_header status = HEADER_SUCCESS;
+
+ while (TRUE)
+ {
+ header = tar_find_next_block (arch);
+ current_header = header;
+ if (header == NULL)
+ {
+ status = HEADER_END_OF_FILE;
+ goto ret;
+ }
+
+ status = tar_checksum (header);
+ if (status != HEADER_SUCCESS)
+ goto ret;
+
+ if (header->header.typeflag == LNKTYPE || header->header.typeflag == DIRTYPE)
+ current_stat_info.stat.st_size = 0; /* Links 0 size on tape */
+ else
+ {
+ current_stat_info.stat.st_size = OFF_FROM_HEADER (header->header.size);
+ if (current_stat_info.stat.st_size < 0)
+ {
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+ }
+
+ tar_decode_header (header, arch);
+ tar_fill_stat (archive, header);
+
+ if (header->header.typeflag == GNUTYPE_LONGNAME
+ || header->header.typeflag == GNUTYPE_LONGLINK)
+ {
+ size_t name_size = current_stat_info.stat.st_size;
+ size_t n;
+ off_t size;
+ union block *header_copy;
+ char *bp;
+ size_t written;
+
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_GNU;
+
+ n = name_size % BLOCKSIZE;
+ size = name_size + BLOCKSIZE;
+ if (n != 0)
+ size += BLOCKSIZE - n;
+ if ((off_t) name_size != current_stat_info.stat.st_size || size < (off_t) name_size)
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+
+ header_copy = g_malloc (size + 1);
+
+ if (header->header.typeflag == GNUTYPE_LONGNAME)
+ {
+ g_free (next_long_name);
+ next_long_name = header_copy;
+ }
+ else
+ {
+ g_free (next_long_link);
+ next_long_link = header_copy;
+ }
+
+ tar_set_next_block_after (header);
+ *header_copy = *header;
+ bp = header_copy->buffer + BLOCKSIZE;
+
+ for (size -= BLOCKSIZE; size > 0; size -= written)
+ {
+ union block *data_block;
+
+ data_block = tar_find_next_block (arch);
+ if (data_block == NULL)
+ {
+ g_free (header_copy);
+ message (D_ERROR, MSG_ERROR, _("Unexpected EOF on archive file"));
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+
+ written = tar_available_space_after (data_block);
+ if ((off_t) written > size)
+ written = (size_t) size;
+
+ memcpy (bp, data_block->buffer, written);
+ bp += written;
+ tar_set_next_block_after ((union block *) (data_block->buffer + written - 1));
+ }
+
+ *bp = '\0';
+ }
+ else if (header->header.typeflag == XHDTYPE || header->header.typeflag == SOLARIS_XHDTYPE)
+ {
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_POSIX;
+ if (!tar_xheader_read
+ (arch, &current_stat_info.xhdr, header, OFF_FROM_HEADER (header->header.size)))
+ {
+ message (D_ERROR, MSG_ERROR, _("Unexpected EOF on archive file"));
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+ }
+ else if (header->header.typeflag == XGLTYPE)
+ {
+ struct xheader xhdr;
+ gboolean ok;
+
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_POSIX;
+
+ memset (&xhdr, 0, sizeof (xhdr));
+ tar_xheader_read (arch, &xhdr, header, OFF_FROM_HEADER (header->header.size));
+ ok = tar_xheader_decode_global (&xhdr);
+ tar_xheader_destroy (&xhdr);
+
+ if (!ok)
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+ }
+ else
+ break;
+ }
+
+ {
+ static union block *recent_long_name = NULL, *recent_long_link = NULL;
+ struct posix_header const *h = &header->header;
+ char *file_name = NULL;
+ char *link_name;
+ struct vfs_s_inode *inode = NULL;
+
+ g_free (recent_long_name);
+
+ if (next_long_name != NULL)
+ {
+ file_name = g_strdup (next_long_name->buffer + BLOCKSIZE);
+ recent_long_name = next_long_name;
+ }
+ else
+ {
+ /* Accept file names as specified by POSIX.1-1996 section 10.1.1. */
+ char *s1 = NULL;
+ char *s2;
+
+ /* Don't parse TAR_OLDGNU incremental headers as POSIX prefixes. */
+ if (h->prefix[0] != '\0' && strcmp (h->magic, TMAGIC) == 0)
+ s1 = g_strndup (h->prefix, sizeof (h->prefix));
+
+ s2 = g_strndup (h->name, sizeof (h->name));
+
+ if (s1 == NULL)
+ file_name = s2;
+ else
+ {
+ file_name = g_strconcat (s1, PATH_SEP_STR, s2, (char *) NULL);
+ g_free (s1);
+ g_free (s2);
+ }
+
+ recent_long_name = NULL;
+ }
+
+ tar_assign_string_dup (&current_stat_info.orig_file_name, file_name);
+ canonicalize_pathname (file_name);
+ tar_assign_string (&current_stat_info.file_name, file_name);
+
+ g_free (recent_long_link);
+
+ if (next_long_link != NULL)
+ {
+ link_name = g_strdup (next_long_link->buffer + BLOCKSIZE);
+ recent_long_link = next_long_link;
+ }
+ else
+ {
+ link_name = g_strndup (h->linkname, sizeof (h->linkname));
+ recent_long_link = NULL;
+ }
+
+ tar_assign_string (&current_stat_info.link_name, link_name);
+
+ if (current_stat_info.xhdr.buffer != NULL && !tar_xheader_decode (&current_stat_info))
+ {
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+
+ if (tar_sparse_member_p (arch, &current_stat_info))
+ {
+ if (!tar_sparse_fixup_header (arch, &current_stat_info))
+ {
+ status = HEADER_FAILURE;
+ goto ret;
+ }
+
+ current_stat_info.is_sparse = TRUE;
+ }
+ else
+ {
+ current_stat_info.is_sparse = FALSE;
+
+ if (((arch->type == TAR_GNU || arch->type == TAR_OLDGNU)
+ && current_header->header.typeflag == GNUTYPE_DUMPDIR)
+ || current_stat_info.dumpdir != NULL)
+ current_stat_info.is_dumpdir = TRUE;
+ }
+
+ status = tar_insert_entry (me, archive, header, &inode);
+ if (status != HEADER_SUCCESS)
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ goto ret;
+ }
+
+ if (recent_long_name == next_long_name)
+ recent_long_name = NULL;
+
+ if (recent_long_link == next_long_link)
+ recent_long_link = NULL;
+
+ if (tar_skip_member (arch, inode))
+ status = HEADER_SUCCESS;
+ else if (hit_eof)
+ status = HEADER_END_OF_FILE;
+ else
+ status = HEADER_FAILURE;
+ }
+
+ ret:
+ g_free (next_long_name);
+ g_free (next_long_link);
+
+ return status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_super *
+tar_new_archive (struct vfs_class *me)
+{
+ tar_super_t *arch;
+ gint64 usec;
+
+ arch = g_new0 (tar_super_t, 1);
+ arch->base.me = me;
+ arch->fd = -1;
+ arch->type = TAR_UNKNOWN;
+
+ /* Prepare global data needed for tar_find_next_block: */
+ record_start_block = 0;
+ arch->record_start = g_malloc (record_size);
+ record_end = arch->record_start; /* set up for 1st record = # 0 */
+ current_block = arch->record_start;
+ hit_eof = FALSE;
+
+ /* time in microseconds */
+ usec = g_get_real_time ();
+ /* time in seconds and nanoseconds */
+ start_time.tv_sec = usec / G_USEC_PER_SEC;
+ start_time.tv_nsec = (usec % G_USEC_PER_SEC) * 1000;
+
+ return VFS_SUPER (arch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_free_archive (struct vfs_class *me, struct vfs_s_super *archive)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+
+ (void) me;
+
+ if (arch->fd != -1)
+ {
+ mc_close (arch->fd);
+ arch->fd = -1;
+ }
+
+ g_free (arch->record_start);
+ tar_stat_destroy (&current_stat_info);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Returns status of the tar archive open */
+static gboolean
+tar_open_archive_int (struct vfs_class *me, const vfs_path_t * vpath, struct vfs_s_super *archive)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+ int result, type;
+ mode_t mode;
+ struct vfs_s_inode *root;
+
+ result = mc_open (vpath, O_RDONLY);
+ if (result == -1)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot open tar archive\n%s"), vfs_path_as_str (vpath));
+ ERRNOR (ENOENT, FALSE);
+ }
+
+ archive->name = g_strdup (vfs_path_as_str (vpath));
+ mc_stat (vpath, &arch->st);
+
+ /* Find out the method to handle this tar file */
+ type = get_compression_type (result, archive->name);
+ if (type == COMPRESSION_NONE)
+ mc_lseek (result, 0, SEEK_SET);
+ else
+ {
+ char *s;
+ vfs_path_t *tmp_vpath;
+
+ mc_close (result);
+ s = g_strconcat (archive->name, decompress_extension (type), (char *) NULL);
+ tmp_vpath = vfs_path_from_str_flags (s, VPF_NO_CANON);
+ result = mc_open (tmp_vpath, O_RDONLY);
+ vfs_path_free (tmp_vpath, TRUE);
+ if (result == -1)
+ message (D_ERROR, MSG_ERROR, _("Cannot open tar archive\n%s"), s);
+ g_free (s);
+ if (result == -1)
+ {
+ MC_PTR_FREE (archive->name);
+ ERRNOR (ENOENT, FALSE);
+ }
+ }
+
+ arch->fd = result;
+ mode = arch->st.st_mode & 07777;
+ if (mode & 0400)
+ mode |= 0100;
+ if (mode & 0040)
+ mode |= 0010;
+ if (mode & 0004)
+ mode |= 0001;
+ mode |= S_IFDIR;
+
+ root = vfs_s_new_inode (me, archive, &arch->st);
+ root->st.st_mode = mode;
+ root->data_offset = -1;
+ root->st.st_nlink++;
+ root->st.st_dev = VFS_SUBCLASS (me)->rdev++;
+
+ archive->root = root;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Main loop for reading an archive.
+ * Returns 0 on success, -1 on error.
+ */
+static int
+tar_open_archive (struct vfs_s_super *archive, const vfs_path_t * vpath,
+ const vfs_path_element_t * vpath_element)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+ /* Initial status at start of archive */
+ read_header status = HEADER_STILL_UNREAD;
+
+ /* Open for reading */
+ if (!tar_open_archive_int (vpath_element->class, vpath, archive))
+ return -1;
+
+ tar_find_next_block (arch);
+
+ while (TRUE)
+ {
+ read_header prev_status;
+
+ prev_status = status;
+ tar_stat_destroy (&current_stat_info);
+ status = tar_read_header (vpath_element->class, archive);
+
+ switch (status)
+ {
+ case HEADER_STILL_UNREAD:
+ message (D_ERROR, MSG_ERROR, _("%s\ndoesn't look like a tar archive"),
+ vfs_path_as_str (vpath));
+ return -1;
+
+ case HEADER_SUCCESS:
+ continue;
+
+ /* Record of zeroes */
+ case HEADER_ZERO_BLOCK:
+ tar_set_next_block_after (current_header);
+ (void) tar_read_header (vpath_element->class, archive);
+ status = prev_status;
+ continue;
+
+ case HEADER_END_OF_FILE:
+ break;
+
+ /* Invalid header:
+ * If the previous header was good, tell them that we are skipping bad ones. */
+ case HEADER_FAILURE:
+ tar_set_next_block_after (current_header);
+
+ switch (prev_status)
+ {
+ case HEADER_STILL_UNREAD:
+ message (D_ERROR, MSG_ERROR, _("%s\ndoesn't look like a tar archive"),
+ vfs_path_as_str (vpath));
+ return -1;
+
+ case HEADER_ZERO_BLOCK:
+ case HEADER_SUCCESS:
+ /* Skipping to next header. */
+ break; /* AB: FIXME */
+
+ case HEADER_END_OF_FILE:
+ case HEADER_FAILURE:
+ /* We are in the middle of a cascade of errors. */
+ /* AB: FIXME: TODO: show an error message here */
+ return -1;
+
+ default:
+ break;
+ }
+ continue;
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+tar_super_check (const vfs_path_t * vpath)
+{
+ static struct stat stat_buf;
+ int stat_result;
+
+ stat_result = mc_stat (vpath, &stat_buf);
+
+ return (stat_result != 0) ? NULL : &stat_buf;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+tar_super_same (const vfs_path_element_t * vpath_element, struct vfs_s_super *parc,
+ const vfs_path_t * vpath, void *cookie)
+{
+ struct stat *archive_stat = cookie; /* stat of main archive */
+
+ (void) vpath_element;
+
+ if (strcmp (parc->name, vfs_path_as_str (vpath)) != 0)
+ return 0;
+
+ /* Has the cached archive been changed on the disk? */
+ if (parc != NULL && TAR_SUPER (parc)->st.st_mtime < archive_stat->st_mtime)
+ {
+ /* Yes, reload! */
+ vfs_tarfs_ops->free ((vfsid) parc);
+ vfs_rmstamp (vfs_tarfs_ops, (vfsid) parc);
+ return 2;
+ }
+ /* Hasn't been modified, give it a new timeout */
+ vfs_stamp (vfs_tarfs_ops, (vfsid) parc);
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Get indes of current data chunk in a sparse file.
+ *
+ * @param sparse_map map of the sparse file
+ * @param offset offset in the sparse file
+ *
+ * @return an index of ahole or a data chunk
+ * positive: pointer to the data chunk;
+ * negative: pointer to the hole before data chunk;
+ * zero: pointer to the hole after last data chunk
+ *
+ * +--------+--------+-------+--------+-----+-------+--------+---------+
+ * | hole1 | chunk1 | hole2 | chunk2 | ... | holeN | chunkN | holeN+1 |
+ * +--------+--------+-------+--------+-----+-------+--------+---------+
+ * -1 1 -2 2 -N N 0
+ */
+
+static ssize_t
+tar_get_sparse_chunk_idx (const GArray * sparse_map, off_t offset)
+{
+ size_t k;
+
+ for (k = 1; k <= sparse_map->len; k++)
+ {
+ const struct sp_array *chunk;
+
+ chunk = &g_array_index (sparse_map, struct sp_array, k - 1);
+
+ /* are we in the current chunk? */
+ if (offset >= chunk->offset && offset < chunk->offset + chunk->numbytes)
+ return k;
+
+ /* are we before the current chunk? */
+ if (offset < chunk->offset)
+ return -k;
+ }
+
+ /* after the last chunk */
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+tar_read_sparse (vfs_file_handler_t * fh, char *buffer, size_t count)
+{
+ int fd = TAR_SUPER (fh->ino->super)->fd;
+ const GArray *sm = (const GArray *) fh->ino->user_data;
+ ssize_t chunk_idx;
+ const struct sp_array *chunk;
+ off_t remain;
+ ssize_t res;
+
+ chunk_idx = tar_get_sparse_chunk_idx (sm, fh->pos);
+ if (chunk_idx > 0)
+ {
+ /* we are in the chunk -- read data until chunk end */
+ chunk = &g_array_index (sm, struct sp_array, chunk_idx - 1);
+ remain = MIN ((off_t) count, chunk->offset + chunk->numbytes - fh->pos);
+ res = mc_read (fd, buffer, (size_t) remain);
+ }
+ else
+ {
+ if (chunk_idx == 0)
+ {
+ /* we are in the hole after last chunk -- return zeros until file end */
+ remain = MIN ((off_t) count, fh->ino->st.st_size - fh->pos);
+ /* FIXME: can remain be negative? */
+ remain = MAX (remain, 0);
+ }
+ else /* chunk_idx < 0 */
+ {
+ /* we are in the hole -- return zeros until next chunk start */
+ chunk = &g_array_index (sm, struct sp_array, -chunk_idx - 1);
+ remain = MIN ((off_t) count, chunk->offset - fh->pos);
+ }
+
+ memset (buffer, 0, (size_t) remain);
+ res = (ssize_t) remain;
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+tar_lseek_sparse (vfs_file_handler_t * fh, off_t offset)
+{
+ off_t saved_offset = offset;
+ int fd = TAR_SUPER (fh->ino->super)->fd;
+ const GArray *sm = (const GArray *) fh->ino->user_data;
+ ssize_t chunk_idx;
+ const struct sp_array *chunk;
+ off_t res;
+
+ chunk_idx = tar_get_sparse_chunk_idx (sm, offset);
+ if (chunk_idx > 0)
+ {
+ /* we are in the chunk */
+
+ chunk = &g_array_index (sm, struct sp_array, chunk_idx - 1);
+ /* offset in the chunk */
+ offset -= chunk->offset;
+ /* offset in the archive */
+ offset += chunk->arch_offset;
+ }
+ else
+ {
+ /* we are in the hole */
+
+ /* we cannot lseek in hole so seek to the hole begin or end */
+ switch (chunk_idx)
+ {
+ case -1:
+ offset = fh->ino->data_offset;
+ break;
+
+ case 0:
+ chunk = &g_array_index (sm, struct sp_array, sm->len - 1);
+ /* FIXME: can we seek beyond tar archive EOF here? */
+ offset = chunk->arch_offset + chunk->numbytes;
+ break;
+
+ default:
+ chunk = &g_array_index (sm, struct sp_array, -chunk_idx - 1);
+ offset = chunk->arch_offset + chunk->numbytes;
+ break;
+ }
+ }
+
+ res = mc_lseek (fd, offset, SEEK_SET);
+ /* return requested offset in success */
+ if (res == offset)
+ res = saved_offset;
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+tar_read (void *fh, char *buffer, size_t count)
+{
+ struct vfs_class *me = VFS_FILE_HANDLER_SUPER (fh)->me;
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ int fd = TAR_SUPER (VFS_FILE_HANDLER_SUPER (fh))->fd;
+ off_t begin = file->pos;
+ ssize_t res;
+
+ if (file->ino->user_data != NULL)
+ {
+ if (tar_lseek_sparse (file, begin) != begin)
+ ERRNOR (EIO, -1);
+
+ res = tar_read_sparse (file, buffer, count);
+ }
+ else
+ {
+ begin += file->ino->data_offset;
+
+ if (mc_lseek (fd, begin, SEEK_SET) != begin)
+ ERRNOR (EIO, -1);
+
+ count = (size_t) MIN ((off_t) count, file->ino->st.st_size - file->pos);
+ res = mc_read (fd, buffer, count);
+ }
+
+ if (res == -1)
+ ERRNOR (errno, -1);
+
+ file->pos += res;
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+tar_fh_open (struct vfs_class *me, vfs_file_handler_t * fh, int flags, mode_t mode)
+{
+ (void) fh;
+ (void) mode;
+
+ if ((flags & O_ACCMODE) != O_RDONLY)
+ ERRNOR (EROFS, -1);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_tarfs (void)
+{
+ /* FIXME: tarfs used own temp files */
+ vfs_init_subclass (&tarfs_subclass, "tarfs", VFSF_READONLY, "utar");
+ vfs_tarfs_ops->read = tar_read;
+ vfs_tarfs_ops->setctl = NULL;
+ tarfs_subclass.archive_check = tar_super_check;
+ tarfs_subclass.archive_same = tar_super_same;
+ tarfs_subclass.new_archive = tar_new_archive;
+ tarfs_subclass.open_archive = tar_open_archive;
+ tarfs_subclass.free_archive = tar_free_archive;
+ tarfs_subclass.free_inode = tar_free_inode;
+ tarfs_subclass.fh_open = tar_fh_open;
+ vfs_register_class (vfs_tarfs_ops);
+
+ tar_base64_init ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/tar/tar.h b/src/vfs/tar/tar.h
new file mode 100644
index 0000000..5ad11b5
--- /dev/null
+++ b/src/vfs/tar/tar.h
@@ -0,0 +1,18 @@
+#ifndef MC__VFS_TAR_H
+#define MC__VFS_TAR_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_tarfs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_TAR_H */
diff --git a/src/vfs/undelfs/Makefile.am b/src/vfs/undelfs/Makefile.am
new file mode 100644
index 0000000..4e7a77d
--- /dev/null
+++ b/src/vfs/undelfs/Makefile.am
@@ -0,0 +1,7 @@
+
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+
+noinst_LTLIBRARIES = libvfs-undelfs.la
+
+libvfs_undelfs_la_SOURCES = \
+ undelfs.c undelfs.h
diff --git a/src/vfs/undelfs/Makefile.in b/src/vfs/undelfs/Makefile.in
new file mode 100644
index 0000000..4f258d7
--- /dev/null
+++ b/src/vfs/undelfs/Makefile.in
@@ -0,0 +1,735 @@
+# 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/vfs/undelfs
+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)
+libvfs_undelfs_la_LIBADD =
+am_libvfs_undelfs_la_OBJECTS = undelfs.lo
+libvfs_undelfs_la_OBJECTS = $(am_libvfs_undelfs_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)/undelfs.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 = $(libvfs_undelfs_la_SOURCES)
+DIST_SOURCES = $(libvfs_undelfs_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@
+AM_CPPFLAGS = $(GLIB_CFLAGS) -I$(top_srcdir)
+noinst_LTLIBRARIES = libvfs-undelfs.la
+libvfs_undelfs_la_SOURCES = \
+ undelfs.c undelfs.h
+
+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/vfs/undelfs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/vfs/undelfs/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}; \
+ }
+
+libvfs-undelfs.la: $(libvfs_undelfs_la_OBJECTS) $(libvfs_undelfs_la_DEPENDENCIES) $(EXTRA_libvfs_undelfs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libvfs_undelfs_la_OBJECTS) $(libvfs_undelfs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/undelfs.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)/undelfs.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)/undelfs.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/vfs/undelfs/undelfs.c b/src/vfs/undelfs/undelfs.c
new file mode 100644
index 0000000..de54440
--- /dev/null
+++ b/src/vfs/undelfs/undelfs.c
@@ -0,0 +1,844 @@
+/*
+ UnDel File System: Midnight Commander file system.
+
+ This file system is intended to be used together with the
+ ext2fs library to recover files from ext2fs file systems.
+
+ Parts of this program were taken from the lsdel.c and dump.c files
+ written by Ted Ts'o (tytso@mit.edu) for the ext2fs package.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1995
+ Norbert Warmuth, 1997
+ Pavel Machek, 2000
+
+ 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
+ * \brief Source: UnDel File System
+ *
+ * Assumptions:
+ *
+ * 1. We don't handle directories (thus undelfs_get_path is easy to write).
+ * 2. Files are on the local file system (we do not support vfs files
+ * because we would have to provide an io_manager for the ext2fs tools,
+ * and I don't think it would be too useful to undelete files
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h> /* memset() */
+#include <ext2fs/ext2_fs.h>
+#include <ext2fs/ext2fs.h>
+#include <ctype.h>
+
+#include "lib/global.h"
+
+#include "lib/util.h"
+#include "lib/widget.h" /* message() */
+#include "lib/vfs/xdirentry.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/vfs.h"
+
+#include "undelfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/* To generate the . and .. entries use -2 */
+#define READDIR_PTR_INIT 0
+
+#define undelfs_stat undelfs_lstat
+
+/*** file scope type declarations ****************************************************************/
+
+struct deleted_info
+{
+ ext2_ino_t ino;
+ unsigned short mode;
+ unsigned short uid;
+ unsigned short gid;
+ unsigned long size;
+ time_t dtime;
+ int num_blocks;
+ int free_blocks;
+};
+
+struct lsdel_struct
+{
+ ext2_ino_t inode;
+ int num_blocks;
+ int free_blocks;
+ int bad_blocks;
+};
+
+typedef struct
+{
+ int f_index; /* file index into delarray */
+ char *buf;
+ int error_code; /* */
+ off_t pos; /* file position */
+ off_t current; /* used to determine current position in itereate */
+ gboolean finished;
+ ext2_ino_t inode;
+ int bytes_read;
+ off_t size;
+
+ /* Used by undelfs_read: */
+ char *dest_buffer; /* destination buffer */
+ size_t count; /* bytes to read */
+} undelfs_file;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* We only allow one opened ext2fs */
+static char *ext2_fname;
+static ext2_filsys fs = NULL;
+static struct lsdel_struct lsd;
+static struct deleted_info *delarray;
+static int num_delarray, max_delarray;
+static char *block_buf;
+static const char *undelfserr = N_("undelfs: error");
+static int readdir_ptr;
+static int undelfs_usage;
+
+static struct vfs_s_subclass undelfs_subclass;
+static struct vfs_class *vfs_undelfs_ops = VFS_CLASS (&undelfs_subclass);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+undelfs_shutdown (void)
+{
+ if (fs)
+ ext2fs_close (fs);
+ fs = NULL;
+ MC_PTR_FREE (ext2_fname);
+ MC_PTR_FREE (delarray);
+ MC_PTR_FREE (block_buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+undelfs_get_path (const vfs_path_t * vpath, char **fsname, char **file)
+{
+ const char *p, *dirname;
+
+ dirname = vfs_path_get_last_path_str (vpath);
+
+ /* To look like filesystem, we have virtual directories
+ undel://XXX, which have no subdirectories. XXX is replaced with
+ hda5, sdb8 etc, which is assumed to live under /dev.
+ -- pavel@ucw.cz */
+
+ *fsname = NULL;
+
+ if (strncmp (dirname, "undel://", 8) != 0)
+ return;
+
+ dirname += 8;
+
+ /* Since we don't allow subdirectories, it's easy to get a filename,
+ * just scan backwards for a slash */
+ if (*dirname == '\0')
+ return;
+
+ p = dirname + strlen (dirname);
+#if 0
+ /* Strip trailing ./
+ */
+ if (p - dirname > 2 && IS_PATH_SEP (p[-1]) && p[-2] == '.')
+ *(p = p - 2) = 0;
+#endif
+
+ while (p > dirname)
+ {
+ if (IS_PATH_SEP (*p))
+ {
+ char *tmp;
+
+ *file = g_strdup (p + 1);
+ tmp = g_strndup (dirname, p - dirname);
+ *fsname = g_strconcat ("/dev/", tmp, (char *) NULL);
+ g_free (tmp);
+ return;
+ }
+ p--;
+ }
+ *file = g_strdup ("");
+ *fsname = g_strconcat ("/dev/", dirname, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_lsdel_proc (ext2_filsys _fs, blk_t * block_nr, int blockcnt, void *private)
+{
+ struct lsdel_struct *_lsd = (struct lsdel_struct *) private;
+ (void) blockcnt;
+ _lsd->num_blocks++;
+
+ if (*block_nr < _fs->super->s_first_data_block || *block_nr >= _fs->super->s_blocks_count)
+ {
+ _lsd->bad_blocks++;
+ return BLOCK_ABORT;
+ }
+
+ if (!ext2fs_test_block_bitmap (_fs->block_map, *block_nr))
+ _lsd->free_blocks++;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load information about deleted files.
+ * Don't abort if there is not enough memory - load as much as we can.
+ */
+
+static int
+undelfs_loaddel (void)
+{
+ int retval, count;
+ ext2_ino_t ino;
+ struct ext2_inode inode;
+ ext2_inode_scan scan;
+
+ max_delarray = 100;
+ num_delarray = 0;
+ delarray = g_try_malloc (sizeof (struct deleted_info) * max_delarray);
+ if (!delarray)
+ {
+ message (D_ERROR, undelfserr, "%s", _("not enough memory"));
+ return 0;
+ }
+ block_buf = g_try_malloc (fs->blocksize * 3);
+ if (!block_buf)
+ {
+ message (D_ERROR, undelfserr, "%s", _("while allocating block buffer"));
+ goto free_delarray;
+ }
+ retval = ext2fs_open_inode_scan (fs, 0, &scan);
+ if (retval != 0)
+ {
+ message (D_ERROR, undelfserr, _("open_inode_scan: %d"), retval);
+ goto free_block_buf;
+ }
+ retval = ext2fs_get_next_inode (scan, &ino, &inode);
+ if (retval != 0)
+ {
+ message (D_ERROR, undelfserr, _("while starting inode scan %d"), retval);
+ goto error_out;
+ }
+ count = 0;
+ while (ino)
+ {
+ if ((count++ % 1024) == 0)
+ vfs_print_message (_("undelfs: loading deleted files information %d inodes"), count);
+ if (inode.i_dtime == 0)
+ goto next;
+
+ if (S_ISDIR (inode.i_mode))
+ goto next;
+
+ lsd.inode = ino;
+ lsd.num_blocks = 0;
+ lsd.free_blocks = 0;
+ lsd.bad_blocks = 0;
+
+ retval = ext2fs_block_iterate (fs, ino, 0, block_buf, undelfs_lsdel_proc, &lsd);
+ if (retval)
+ {
+ message (D_ERROR, undelfserr, _("while calling ext2_block_iterate %d"), retval);
+ goto next;
+ }
+ if (lsd.free_blocks && !lsd.bad_blocks)
+ {
+ if (num_delarray >= max_delarray)
+ {
+ struct deleted_info *delarray_new = g_try_realloc (delarray,
+ sizeof (struct deleted_info) *
+ (max_delarray + 50));
+ if (!delarray_new)
+ {
+ message (D_ERROR, undelfserr, "%s",
+ _("no more memory while reallocating array"));
+ goto error_out;
+ }
+ delarray = delarray_new;
+ max_delarray += 50;
+ }
+
+ delarray[num_delarray].ino = ino;
+ delarray[num_delarray].mode = inode.i_mode;
+ delarray[num_delarray].uid = inode.i_uid;
+ delarray[num_delarray].gid = inode.i_gid;
+ delarray[num_delarray].size = inode.i_size;
+ delarray[num_delarray].dtime = inode.i_dtime;
+ delarray[num_delarray].num_blocks = lsd.num_blocks;
+ delarray[num_delarray].free_blocks = lsd.free_blocks;
+ num_delarray++;
+ }
+
+ next:
+ retval = ext2fs_get_next_inode (scan, &ino, &inode);
+ if (retval)
+ {
+ message (D_ERROR, undelfserr, _("while doing inode scan %d"), retval);
+ goto error_out;
+ }
+ }
+ readdir_ptr = READDIR_PTR_INIT;
+ ext2fs_close_inode_scan (scan);
+ return 1;
+
+ error_out:
+ ext2fs_close_inode_scan (scan);
+ free_block_buf:
+ MC_PTR_FREE (block_buf);
+ free_delarray:
+ MC_PTR_FREE (delarray);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+undelfs_opendir (const vfs_path_t * vpath)
+{
+ char *file, *f = NULL;
+ const char *class_name;
+
+ class_name = vfs_path_get_last_path_vfs (vpath)->name;
+ undelfs_get_path (vpath, &file, &f);
+ if (file == NULL)
+ {
+ g_free (f);
+ return 0;
+ }
+
+ /* We don't use the file name */
+ g_free (f);
+
+ if (!ext2_fname || strcmp (ext2_fname, file))
+ {
+ undelfs_shutdown ();
+ ext2_fname = file;
+ }
+ else
+ {
+ /* To avoid expensive re-scannings */
+ readdir_ptr = READDIR_PTR_INIT;
+ g_free (file);
+ return fs;
+ }
+
+ if (ext2fs_open (ext2_fname, 0, 0, 0, unix_io_manager, &fs))
+ {
+ message (D_ERROR, undelfserr, _("Cannot open file %s"), ext2_fname);
+ return 0;
+ }
+ vfs_print_message ("%s", _("undelfs: reading inode bitmap..."));
+ if (ext2fs_read_inode_bitmap (fs))
+ {
+ message (D_ERROR, undelfserr, _("Cannot load inode bitmap from:\n%s"), ext2_fname);
+ goto quit_opendir;
+ }
+ vfs_print_message ("%s", _("undelfs: reading block bitmap..."));
+ if (ext2fs_read_block_bitmap (fs))
+ {
+ message (D_ERROR, undelfserr, _("Cannot load block bitmap from:\n%s"), ext2_fname);
+ goto quit_opendir;
+ }
+ /* Now load the deleted information */
+ if (!undelfs_loaddel ())
+ goto quit_opendir;
+ vfs_print_message (_("%s: done."), class_name);
+ return fs;
+ quit_opendir:
+ vfs_print_message (_("%s: failure"), class_name);
+ ext2fs_close (fs);
+ fs = NULL;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_dirent *
+undelfs_readdir (void *vfs_info)
+{
+ struct vfs_dirent *dirent;
+
+ if (vfs_info != fs)
+ {
+ message (D_ERROR, undelfserr, "%s", _("vfs_info is not fs!"));
+ return NULL;
+ }
+ if (readdir_ptr == num_delarray)
+ return NULL;
+ if (readdir_ptr < 0)
+ dirent = vfs_dirent_init (NULL, readdir_ptr == -2 ? "." : "..", 0); /* FIXME: inode */
+ else
+ {
+ char dirent_dest[MC_MAXPATHLEN];
+
+ g_snprintf (dirent_dest, MC_MAXPATHLEN, "%ld:%d",
+ (long) delarray[readdir_ptr].ino, delarray[readdir_ptr].num_blocks);
+ dirent = vfs_dirent_init (NULL, dirent_dest, 0); /* FIXME: inode */
+ }
+ readdir_ptr++;
+
+ return dirent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_closedir (void *vfs_info)
+{
+ (void) vfs_info;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* We do not support lseek */
+
+static void *
+undelfs_open (const vfs_path_t * vpath, int flags, mode_t mode)
+{
+ char *file, *f = NULL;
+ ext2_ino_t inode, i;
+ undelfs_file *p = NULL;
+ (void) flags;
+ (void) mode;
+
+ /* Only allow reads on this file system */
+ undelfs_get_path (vpath, &file, &f);
+ if (file == NULL)
+ {
+ g_free (f);
+ return 0;
+ }
+
+ if (!ext2_fname || strcmp (ext2_fname, file))
+ {
+ message (D_ERROR, undelfserr, "%s", _("You have to chdir to extract files first"));
+ g_free (file);
+ g_free (f);
+ return 0;
+ }
+ inode = atol (f);
+
+ /* Search the file into delarray */
+ for (i = 0; i < (ext2_ino_t) num_delarray; i++)
+ {
+ if (inode != delarray[i].ino)
+ continue;
+
+ /* Found: setup all the structures needed by read */
+ p = (undelfs_file *) g_try_malloc (((gsize) sizeof (undelfs_file)));
+ if (!p)
+ {
+ g_free (file);
+ g_free (f);
+ return 0;
+ }
+ p->buf = g_try_malloc (fs->blocksize);
+ if (!p->buf)
+ {
+ g_free (p);
+ g_free (file);
+ g_free (f);
+ return 0;
+ }
+ p->inode = inode;
+ p->finished = FALSE;
+ p->f_index = i;
+ p->error_code = 0;
+ p->pos = 0;
+ p->size = delarray[i].size;
+ }
+ g_free (file);
+ g_free (f);
+ undelfs_usage++;
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_close (void *vfs_info)
+{
+ undelfs_file *p = vfs_info;
+ g_free (p->buf);
+ g_free (p);
+ undelfs_usage--;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_dump_read (ext2_filsys param_fs, blk_t * blocknr, int blockcnt, void *private)
+{
+ int copy_count;
+ undelfs_file *p = (undelfs_file *) private;
+
+ if (blockcnt < 0)
+ return 0;
+
+ if (*blocknr)
+ {
+ p->error_code = io_channel_read_blk (param_fs->io, *blocknr, 1, p->buf);
+ if (p->error_code)
+ return BLOCK_ABORT;
+ }
+ else
+ memset (p->buf, 0, param_fs->blocksize);
+
+ if (p->pos + (off_t) p->count < p->current)
+ {
+ p->finished = TRUE;
+ return BLOCK_ABORT;
+ }
+ if (p->pos > p->current + param_fs->blocksize)
+ {
+ p->current += param_fs->blocksize;
+ return 0; /* we have not arrived yet */
+ }
+
+ /* Now, we know we have to extract some data */
+ if (p->pos >= p->current)
+ {
+
+ /* First case: starting pointer inside this block */
+ if (p->pos + (off_t) p->count <= p->current + param_fs->blocksize)
+ {
+ /* Fully contained */
+ copy_count = p->count;
+ p->finished = (p->count != 0);
+ }
+ else
+ {
+ /* Still some more data */
+ copy_count = param_fs->blocksize - (p->pos - p->current);
+ }
+ memcpy (p->dest_buffer, p->buf + (p->pos - p->current), copy_count);
+ }
+ else
+ {
+ /* Second case: we already have passed p->pos */
+ if (p->pos + (off_t) p->count < p->current + param_fs->blocksize)
+ {
+ copy_count = (p->pos + p->count) - p->current;
+ p->finished = (p->count != 0);
+ }
+ else
+ {
+ copy_count = param_fs->blocksize;
+ }
+ memcpy (p->dest_buffer, p->buf, copy_count);
+ }
+ p->dest_buffer += copy_count;
+ p->current += param_fs->blocksize;
+ if (p->finished)
+ {
+ return BLOCK_ABORT;
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+undelfs_read (void *vfs_info, char *buffer, size_t count)
+{
+ undelfs_file *p = vfs_info;
+ int retval;
+
+ p->dest_buffer = buffer;
+ p->current = 0;
+ p->finished = FALSE;
+ p->count = count;
+
+ if (p->pos + (off_t) p->count > p->size)
+ {
+ p->count = p->size - p->pos;
+ }
+ retval = ext2fs_block_iterate (fs, p->inode, 0, NULL, undelfs_dump_read, p);
+ if (retval)
+ {
+ message (D_ERROR, undelfserr, "%s", _("while iterating over blocks"));
+ return -1;
+ }
+ if (p->error_code && !p->finished)
+ return 0;
+ p->pos = p->pos + (p->dest_buffer - buffer);
+ return p->dest_buffer - buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static long
+undelfs_getindex (char *path)
+{
+ ext2_ino_t inode = atol (path);
+ int i;
+
+ for (i = 0; i < num_delarray; i++)
+ {
+ if (delarray[i].ino == inode)
+ return i;
+ }
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_stat_int (int inode_index, struct stat *buf)
+{
+ buf->st_dev = 0;
+ buf->st_ino = delarray[inode_index].ino;
+ buf->st_mode = delarray[inode_index].mode;
+ buf->st_nlink = 1;
+ buf->st_uid = delarray[inode_index].uid;
+ buf->st_gid = delarray[inode_index].gid;
+ buf->st_size = delarray[inode_index].size;
+ buf->st_atime = delarray[inode_index].dtime;
+ buf->st_ctime = delarray[inode_index].dtime;
+ buf->st_mtime = delarray[inode_index].dtime;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ buf->st_atim.tv_nsec = buf->st_mtim.tv_nsec = buf->st_ctim.tv_nsec = 0;
+#endif
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ int inode_index;
+ char *file, *f = NULL;
+
+ undelfs_get_path (vpath, &file, &f);
+ if (file == NULL)
+ {
+ g_free (f);
+ return 0;
+ }
+
+ /* When called from save_cwd_stats we get an incorrect file and f here:
+ e.g. incorrect correct
+ path = "undel:/dev/sda1" path="undel:/dev/sda1/401:1"
+ file = "/dev" file="/dev/sda1"
+ f = "sda1" f ="401:1"
+ If the first char in f is no digit -> return error */
+ if (!isdigit (*f))
+ {
+ g_free (file);
+ g_free (f);
+ return -1;
+ }
+
+ if (!ext2_fname || strcmp (ext2_fname, file))
+ {
+ g_free (file);
+ g_free (f);
+ message (D_ERROR, undelfserr, "%s", _("You have to chdir to extract files first"));
+ return 0;
+ }
+ inode_index = undelfs_getindex (f);
+ g_free (file);
+ g_free (f);
+
+ if (inode_index == -1)
+ return -1;
+
+ return undelfs_stat_int (inode_index, buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_fstat (void *vfs_info, struct stat *buf)
+{
+ undelfs_file *p = vfs_info;
+
+ return undelfs_stat_int (p->f_index, buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+undelfs_chdir (const vfs_path_t * vpath)
+{
+ char *file, *f = NULL;
+ int fd;
+
+ undelfs_get_path (vpath, &file, &f);
+ if (file == NULL)
+ {
+ g_free (f);
+ return (-1);
+ }
+
+ /* We may use access because ext2 file systems are local */
+ /* this could be fixed by making an ext2fs io manager to use */
+ /* our vfs, but that is left as an exercise for the reader */
+ fd = open (file, O_RDONLY);
+ if (fd == -1)
+ {
+ message (D_ERROR, undelfserr, _("Cannot open file \"%s\""), file);
+ g_free (f);
+ g_free (file);
+ return -1;
+ }
+ close (fd);
+ g_free (f);
+ g_free (file);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* this has to stay here for now: vfs layer does not know how to emulate it */
+static off_t
+undelfs_lseek (void *vfs_info, off_t offset, int whence)
+{
+ (void) vfs_info;
+ (void) offset;
+ (void) whence;
+
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfsid
+undelfs_getid (const vfs_path_t * vpath)
+{
+ char *fname = NULL, *fsname;
+ gboolean ok;
+
+ undelfs_get_path (vpath, &fsname, &fname);
+ ok = fsname != NULL;
+
+ g_free (fname);
+ g_free (fsname);
+
+ return ok ? (vfsid) fs : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+undelfs_nothingisopen (vfsid id)
+{
+ (void) id;
+
+ return (undelfs_usage == 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+undelfs_free (vfsid id)
+{
+ (void) id;
+
+ undelfs_shutdown ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_NLS
+static int
+undelfs_init (struct vfs_class *me)
+{
+ (void) me;
+
+ undelfserr = _(undelfserr);
+ return 1;
+}
+#else
+#define undelfs_init NULL
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * This function overrides com_err() from libcom_err library.
+ * It is used in libext2fs to report errors.
+ */
+
+void
+com_err (const char *whoami, long err_code, const char *fmt, ...)
+{
+ va_list ap;
+ char *str;
+
+ va_start (ap, fmt);
+ str = g_strdup_vprintf (fmt, ap);
+ va_end (ap);
+
+ message (D_ERROR, _("Ext2lib error"), "%s (%s: %ld)", str, whoami, err_code);
+ g_free (str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_undelfs (void)
+{
+ /* NULLize vfs_s_subclass members */
+ memset (&undelfs_subclass, 0, sizeof (undelfs_subclass));
+
+ vfs_init_class (vfs_undelfs_ops, "undelfs", VFSF_UNKNOWN, "undel");
+ vfs_undelfs_ops->init = undelfs_init;
+ vfs_undelfs_ops->open = undelfs_open;
+ vfs_undelfs_ops->close = undelfs_close;
+ vfs_undelfs_ops->read = undelfs_read;
+ vfs_undelfs_ops->opendir = undelfs_opendir;
+ vfs_undelfs_ops->readdir = undelfs_readdir;
+ vfs_undelfs_ops->closedir = undelfs_closedir;
+ vfs_undelfs_ops->stat = undelfs_stat;
+ vfs_undelfs_ops->lstat = undelfs_lstat;
+ vfs_undelfs_ops->fstat = undelfs_fstat;
+ vfs_undelfs_ops->chdir = undelfs_chdir;
+ vfs_undelfs_ops->lseek = undelfs_lseek;
+ vfs_undelfs_ops->getid = undelfs_getid;
+ vfs_undelfs_ops->nothingisopen = undelfs_nothingisopen;
+ vfs_undelfs_ops->free = undelfs_free;
+ vfs_register_class (vfs_undelfs_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/vfs/undelfs/undelfs.h b/src/vfs/undelfs/undelfs.h
new file mode 100644
index 0000000..9e32458
--- /dev/null
+++ b/src/vfs/undelfs/undelfs.h
@@ -0,0 +1,18 @@
+#ifndef MC__VFS_UNDELFS_H
+#define MC__VFS_UNDELFS_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void vfs_init_undelfs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__VFS_UNDELFS_H */
diff --git a/src/viewer/Makefile.am b/src/viewer/Makefile.am
new file mode 100644
index 0000000..9bf1648
--- /dev/null
+++ b/src/viewer/Makefile.am
@@ -0,0 +1,21 @@
+
+noinst_LTLIBRARIES = libmcviewer.la
+
+libmcviewer_la_SOURCES = \
+ actions_cmd.c \
+ ascii.c \
+ coord_cache.c \
+ datasource.c \
+ dialogs.c \
+ display.c \
+ growbuf.c \
+ hex.c \
+ internal.h \
+ lib.c \
+ mcviewer.c \
+ mcviewer.h \
+ move.c \
+ nroff.c \
+ search.c
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
diff --git a/src/viewer/Makefile.in b/src/viewer/Makefile.in
new file mode 100644
index 0000000..26ff9c6
--- /dev/null
+++ b/src/viewer/Makefile.in
@@ -0,0 +1,793 @@
+# 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/viewer
+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)
+libmcviewer_la_LIBADD =
+am_libmcviewer_la_OBJECTS = actions_cmd.lo ascii.lo coord_cache.lo \
+ datasource.lo dialogs.lo display.lo growbuf.lo hex.lo lib.lo \
+ mcviewer.lo move.lo nroff.lo search.lo
+libmcviewer_la_OBJECTS = $(am_libmcviewer_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)/actions_cmd.Plo \
+ ./$(DEPDIR)/ascii.Plo ./$(DEPDIR)/coord_cache.Plo \
+ ./$(DEPDIR)/datasource.Plo ./$(DEPDIR)/dialogs.Plo \
+ ./$(DEPDIR)/display.Plo ./$(DEPDIR)/growbuf.Plo \
+ ./$(DEPDIR)/hex.Plo ./$(DEPDIR)/lib.Plo \
+ ./$(DEPDIR)/mcviewer.Plo ./$(DEPDIR)/move.Plo \
+ ./$(DEPDIR)/nroff.Plo ./$(DEPDIR)/search.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 = $(libmcviewer_la_SOURCES)
+DIST_SOURCES = $(libmcviewer_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 = libmcviewer.la
+libmcviewer_la_SOURCES = \
+ actions_cmd.c \
+ ascii.c \
+ coord_cache.c \
+ datasource.c \
+ dialogs.c \
+ display.c \
+ growbuf.c \
+ hex.c \
+ internal.h \
+ lib.c \
+ mcviewer.c \
+ mcviewer.h \
+ move.c \
+ nroff.c \
+ search.c
+
+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/viewer/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/viewer/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}; \
+ }
+
+libmcviewer.la: $(libmcviewer_la_OBJECTS) $(libmcviewer_la_DEPENDENCIES) $(EXTRA_libmcviewer_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmcviewer_la_OBJECTS) $(libmcviewer_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/actions_cmd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ascii.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/coord_cache.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/datasource.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dialogs.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/display.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/growbuf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hex.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lib.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mcviewer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/move.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nroff.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/search.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)/actions_cmd.Plo
+ -rm -f ./$(DEPDIR)/ascii.Plo
+ -rm -f ./$(DEPDIR)/coord_cache.Plo
+ -rm -f ./$(DEPDIR)/datasource.Plo
+ -rm -f ./$(DEPDIR)/dialogs.Plo
+ -rm -f ./$(DEPDIR)/display.Plo
+ -rm -f ./$(DEPDIR)/growbuf.Plo
+ -rm -f ./$(DEPDIR)/hex.Plo
+ -rm -f ./$(DEPDIR)/lib.Plo
+ -rm -f ./$(DEPDIR)/mcviewer.Plo
+ -rm -f ./$(DEPDIR)/move.Plo
+ -rm -f ./$(DEPDIR)/nroff.Plo
+ -rm -f ./$(DEPDIR)/search.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)/actions_cmd.Plo
+ -rm -f ./$(DEPDIR)/ascii.Plo
+ -rm -f ./$(DEPDIR)/coord_cache.Plo
+ -rm -f ./$(DEPDIR)/datasource.Plo
+ -rm -f ./$(DEPDIR)/dialogs.Plo
+ -rm -f ./$(DEPDIR)/display.Plo
+ -rm -f ./$(DEPDIR)/growbuf.Plo
+ -rm -f ./$(DEPDIR)/hex.Plo
+ -rm -f ./$(DEPDIR)/lib.Plo
+ -rm -f ./$(DEPDIR)/mcviewer.Plo
+ -rm -f ./$(DEPDIR)/move.Plo
+ -rm -f ./$(DEPDIR)/nroff.Plo
+ -rm -f ./$(DEPDIR)/search.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/viewer/actions_cmd.c b/src/viewer/actions_cmd.c
new file mode 100644
index 0000000..465f0f0
--- /dev/null
+++ b/src/viewer/actions_cmd.c
@@ -0,0 +1,790 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Callback function for some actions (hotkeys, menu)
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ 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/>.
+ */
+
+/*
+ The functions in this section can be bound to hotkeys. They are all
+ of the same type (taking a pointer to WView as parameter and
+ returning void). TODO: In the not-too-distant future, these commands
+ will become fully configurable, like they already are in the
+ internal editor. By convention, all the function names end in
+ "_cmd".
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* is_idle() */
+#include "lib/lock.h" /* lock_file() */
+#include "lib/file-entry.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+#include "lib/event.h" /* mc_event_raise() */
+#include "lib/mcconfig.h" /* mc_config_history_get() */
+
+#include "src/filemanager/layout.h"
+#include "src/filemanager/filemanager.h" /* current_panel */
+#include "src/filemanager/ext.h" /* regex_command_for() */
+
+#include "src/history.h"
+#include "src/file_history.h" /* show_file_history() */
+#include "src/execute.h"
+#include "src/keymap.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_remove_ext_script (WView * view)
+{
+ if (view->ext_script != NULL)
+ {
+ mc_unlink (view->ext_script);
+ vfs_path_free (view->ext_script, TRUE);
+ view->ext_script = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Both views */
+static void
+mcview_search (WView * view, gboolean start_search)
+{
+ off_t want_search_start = view->search_start;
+
+ if (start_search)
+ {
+ if (mcview_dialog_search (view))
+ {
+ if (view->mode_flags.hex)
+ want_search_start = view->hex_cursor;
+
+ mcview_do_search (view, want_search_start);
+ }
+ }
+ else
+ {
+ if (view->mode_flags.hex)
+ {
+ if (!mcview_search_options.backwards)
+ want_search_start = view->hex_cursor + 1;
+ else if (view->hex_cursor > 0)
+ want_search_start = view->hex_cursor - 1;
+ else
+ want_search_start = 0;
+ }
+
+ mcview_do_search (view, want_search_start);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_continue_search_cmd (WView * view)
+{
+ if (view->last_search_string != NULL)
+ mcview_search (view, FALSE);
+ else
+ {
+ /* find last search string in history */
+ GList *history;
+
+ history = mc_config_history_get (MC_HISTORY_SHARED_SEARCH);
+ if (history != NULL)
+ {
+ /* FIXME: is it possible that history->data == NULL? */
+ view->last_search_string = (gchar *) history->data;
+ history->data = NULL;
+ history = g_list_first (history);
+ g_list_free_full (history, g_free);
+
+ if (mcview_search_init (view))
+ {
+ mcview_search (view, FALSE);
+ return;
+ }
+
+ /* found, but cannot init search */
+ MC_PTR_FREE (view->last_search_string);
+ }
+
+ /* if not... then ask for an expression */
+ mcview_search (view, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_hook (void *v)
+{
+ WView *view = (WView *) v;
+ WPanel *panel;
+
+ /* If the user is busy typing, wait until he finishes to update the
+ screen */
+ if (!is_idle ())
+ {
+ if (!hook_present (idle_hook, mcview_hook))
+ add_hook (&idle_hook, mcview_hook, v);
+ return;
+ }
+
+ delete_hook (&idle_hook, mcview_hook);
+
+ if (get_current_type () == view_listing)
+ panel = current_panel;
+ else if (get_other_type () == view_listing)
+ panel = other_panel;
+ else
+ return;
+
+ mcview_done (view);
+ mcview_init (view);
+ mcview_load (view, 0, panel_current_entry (panel)->fname->str, 0, 0, 0);
+ mcview_display (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+mcview_handle_editkey (WView * view, int key)
+{
+ struct hexedit_change_node *node;
+ int byte_val = -1;
+
+ /* Has there been a change at this position? */
+ node = view->change_list;
+ while ((node != NULL) && (node->offset != view->hex_cursor))
+ node = node->next;
+
+ if (!view->hexview_in_text)
+ {
+ /* Hex editing */
+ unsigned int hexvalue = 0;
+
+ if (key >= '0' && key <= '9')
+ hexvalue = 0 + (key - '0');
+ else if (key >= 'A' && key <= 'F')
+ hexvalue = 10 + (key - 'A');
+ else if (key >= 'a' && key <= 'f')
+ hexvalue = 10 + (key - 'a');
+ else
+ return MSG_NOT_HANDLED;
+
+ if (node != NULL)
+ byte_val = node->value;
+ else
+ mcview_get_byte (view, view->hex_cursor, &byte_val);
+
+ if (view->hexedit_lownibble)
+ byte_val = (byte_val & 0xf0) | (hexvalue);
+ else
+ byte_val = (byte_val & 0x0f) | (hexvalue << 4);
+ }
+ else
+ {
+ /* Text editing */
+ if (key < 256 && key != '\t')
+ byte_val = key;
+ else
+ return MSG_NOT_HANDLED;
+ }
+
+ if ((view->filename_vpath != NULL)
+ && (*(vfs_path_get_last_path_str (view->filename_vpath)) != '\0')
+ && (view->change_list == NULL))
+ view->locked = lock_file (view->filename_vpath);
+
+ if (node == NULL)
+ {
+ node = g_new (struct hexedit_change_node, 1);
+ node->offset = view->hex_cursor;
+ node->value = byte_val;
+ mcview_enqueue_change (&view->change_list, node);
+ }
+ else
+ node->value = byte_val;
+
+ view->dirty++;
+ mcview_move_right (view, 1);
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_load_next_prev_init (WView * view)
+{
+ if (mc_global.mc_run_mode != MC_RUN_VIEWER)
+ {
+ /* get file list from current panel. Update it each time */
+ view->dir = &current_panel->dir;
+ view->dir_idx = &current_panel->current;
+ }
+ else if (view->dir == NULL)
+ {
+ /* Run from command line */
+ /* Run 1st time. Load/get directory */
+
+ /* TODO: check mtime of directory to reload it */
+
+ dir_sort_options_t sort_op = { FALSE, TRUE, FALSE };
+
+ /* load directory where requested file is */
+ view->dir = g_new0 (dir_list, 1);
+ view->dir_idx = g_new (int, 1);
+
+ if (dir_list_load
+ (view->dir, view->workdir_vpath, (GCompareFunc) sort_name, &sort_op, NULL))
+ {
+ const char *fname;
+ size_t fname_len;
+ int i;
+
+ fname = x_basename (vfs_path_as_str (view->filename_vpath));
+ fname_len = strlen (fname);
+
+ /* search current file in the list */
+ for (i = 0; i != view->dir->len; i++)
+ {
+ const file_entry_t *fe = &view->dir->list[i];
+
+ if (fname_len == fe->fname->len && strncmp (fname, fe->fname->str, fname_len) == 0)
+ break;
+ }
+
+ *view->dir_idx = i;
+ }
+ else
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
+ MC_PTR_FREE (view->dir);
+ MC_PTR_FREE (view->dir_idx);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_scan_for_file (WView * view, int direction)
+{
+ int i;
+
+ for (i = *view->dir_idx + direction; i != *view->dir_idx; i += direction)
+ {
+ if (i < 0)
+ i = view->dir->len - 1;
+ if (i == view->dir->len)
+ i = 0;
+ if (!S_ISDIR (view->dir->list[i].st.st_mode))
+ break;
+ }
+
+ *view->dir_idx = i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_load_next_prev (WView * view, int direction)
+{
+ dir_list *dir;
+ int *dir_idx;
+ vfs_path_t *vfile;
+ vfs_path_t *ext_script = NULL;
+
+ mcview_load_next_prev_init (view);
+ mcview_scan_for_file (view, direction);
+
+ /* reinit view */
+ dir = view->dir;
+ dir_idx = view->dir_idx;
+ view->dir = NULL;
+ view->dir_idx = NULL;
+ vfile =
+ vfs_path_append_new (view->workdir_vpath, dir->list[*dir_idx].fname->str, (char *) NULL);
+ mcview_done (view);
+ mcview_remove_ext_script (view);
+ mcview_init (view);
+ if (regex_command_for (view, vfile, "View", &ext_script) == 0)
+ mcview_load (view, NULL, vfs_path_as_str (vfile), 0, 0, 0);
+ vfs_path_free (vfile, TRUE);
+ view->dir = dir;
+ view->dir_idx = dir_idx;
+ view->ext_script = ext_script;
+
+ view->dpy_bbar_dirty = FALSE; /* FIXME */
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_load_file_from_history (WView * view)
+{
+ char *filename;
+ int action;
+
+ filename = show_file_history (CONST_WIDGET (view), &action);
+
+ if (filename != NULL && (action == CK_View || action == CK_Enter))
+ {
+ mcview_done (view);
+ mcview_init (view);
+
+ mcview_load (view, NULL, filename, 0, 0, 0);
+
+ view->dpy_bbar_dirty = FALSE; /* FIXME */
+ view->dirty++;
+ }
+
+ g_free (filename);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+mcview_execute_cmd (WView * view, long command)
+{
+ int res = MSG_HANDLED;
+
+ switch (command)
+ {
+ case CK_Help:
+ {
+ ev_help_t event_data = { NULL, "[Internal File Viewer]" };
+ mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
+ }
+ break;
+ case CK_HexMode:
+ /* Toggle between hex view and text view */
+ mcview_toggle_hex_mode (view);
+ break;
+ case CK_HexEditMode:
+ /* Toggle between hexview and hexedit mode */
+ mcview_toggle_hexedit_mode (view);
+ break;
+ case CK_ToggleNavigation:
+ view->hexview_in_text = !view->hexview_in_text;
+ view->dirty++;
+ break;
+ case CK_LeftQuick:
+ if (!view->mode_flags.hex)
+ mcview_move_left (view, 10);
+ break;
+ case CK_RightQuick:
+ if (!view->mode_flags.hex)
+ mcview_move_right (view, 10);
+ break;
+ case CK_Goto:
+ {
+ off_t addr;
+
+ if (mcview_dialog_goto (view, &addr))
+ {
+ if (addr >= 0)
+ mcview_moveto_offset (view, addr);
+ else
+ {
+ message (D_ERROR, _("Warning"), "%s", _("Invalid value"));
+ view->dirty++;
+ }
+ }
+ break;
+ }
+ case CK_Save:
+ mcview_hexedit_save_changes (view);
+ break;
+ case CK_Search:
+ mcview_search (view, TRUE);
+ break;
+ case CK_SearchContinue:
+ mcview_continue_search_cmd (view);
+ break;
+ case CK_SearchForward:
+ mcview_search_options.backwards = FALSE;
+ mcview_search (view, TRUE);
+ break;
+ case CK_SearchForwardContinue:
+ mcview_search_options.backwards = FALSE;
+ mcview_continue_search_cmd (view);
+ break;
+ case CK_SearchBackward:
+ mcview_search_options.backwards = TRUE;
+ mcview_search (view, TRUE);
+ break;
+ case CK_SearchBackwardContinue:
+ mcview_search_options.backwards = TRUE;
+ mcview_continue_search_cmd (view);
+ break;
+ case CK_SearchOppositeContinue:
+ {
+ gboolean direction;
+
+ direction = mcview_search_options.backwards;
+ mcview_search_options.backwards = !direction;
+ mcview_continue_search_cmd (view);
+ mcview_search_options.backwards = direction;
+ }
+ break;
+ case CK_WrapMode:
+ /* Toggle between wrapped and unwrapped view */
+ mcview_toggle_wrap_mode (view);
+ break;
+ case CK_MagicMode:
+ mcview_toggle_magic_mode (view);
+ break;
+ case CK_NroffMode:
+ mcview_toggle_nroff_mode (view);
+ break;
+ case CK_Home:
+ mcview_moveto_bol (view);
+ break;
+ case CK_End:
+ mcview_moveto_eol (view);
+ break;
+ case CK_Left:
+ mcview_move_left (view, 1);
+ break;
+ case CK_Right:
+ mcview_move_right (view, 1);
+ break;
+ case CK_Up:
+ mcview_move_up (view, 1);
+ break;
+ case CK_Down:
+ mcview_move_down (view, 1);
+ break;
+ case CK_HalfPageUp:
+ mcview_move_up (view, (view->data_area.lines + 1) / 2);
+ break;
+ case CK_HalfPageDown:
+ mcview_move_down (view, (view->data_area.lines + 1) / 2);
+ break;
+ case CK_PageUp:
+ mcview_move_up (view, view->data_area.lines);
+ break;
+ case CK_PageDown:
+ mcview_move_down (view, view->data_area.lines);
+ break;
+ case CK_Top:
+ mcview_moveto_top (view);
+ break;
+ case CK_Bottom:
+ mcview_moveto_bottom (view);
+ break;
+ case CK_Shell:
+ toggle_subshell ();
+ break;
+ case CK_Ruler:
+ mcview_display_toggle_ruler (view);
+ break;
+ case CK_Bookmark:
+ view->dpy_start = view->marks[view->marker];
+ view->dpy_paragraph_skip_lines = 0; /* TODO: remember this value in the marker? */
+ view->dpy_wrap_dirty = TRUE;
+ view->dirty++;
+ break;
+ case CK_BookmarkGoto:
+ view->marks[view->marker] = view->dpy_start;
+ break;
+#ifdef HAVE_CHARSET
+ case CK_SelectCodepage:
+ mcview_select_encoding (view);
+ view->dirty++;
+ break;
+#endif
+ case CK_FileNext:
+ case CK_FilePrev:
+ /* Does not work in panel mode */
+ if (!mcview_is_in_panel (view))
+ mcview_load_next_prev (view, command == CK_FileNext ? 1 : -1);
+ break;
+ case CK_History:
+ mcview_load_file_from_history (view);
+ break;
+ case CK_Quit:
+ if (!mcview_is_in_panel (view))
+ dlg_close (DIALOG (WIDGET (view)->owner));
+ break;
+ case CK_Cancel:
+ /* don't close viewer due to SIGINT */
+ break;
+ default:
+ res = MSG_NOT_HANDLED;
+ }
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static long
+mcview_lookup_key (WView * view, int key)
+{
+ if (view->mode_flags.hex)
+ return keybind_lookup_keymap_command (view->hex_keymap, key);
+
+ return widget_lookup_key (WIDGET (view), key);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Both views */
+static cb_ret_t
+mcview_handle_key (WView * view, int key)
+{
+ long command;
+
+#ifdef HAVE_CHARSET
+ key = convert_from_input_c (key);
+#endif
+
+ if (view->hexedit_mode && view->mode_flags.hex
+ && mcview_handle_editkey (view, key) == MSG_HANDLED)
+ return MSG_HANDLED;
+
+ command = mcview_lookup_key (view, key);
+ if (command != CK_IgnoreKey && mcview_execute_cmd (view, command) == MSG_HANDLED)
+ return MSG_HANDLED;
+
+#ifdef MC_ENABLE_DEBUGGING_CODE
+ if (key == 't')
+ { /* mnemonic: "test" */
+ mcview_ccache_dump (view);
+ return MSG_HANDLED;
+ }
+#endif
+ if (key >= '0' && key <= '9')
+ view->marker = key - '0';
+
+ /* Key not used */
+ return MSG_NOT_HANDLED;
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+mcview_resize (WView * view)
+{
+ view->dpy_wrap_dirty = TRUE;
+ mcview_compute_areas (view);
+ mcview_update_bytes_per_line (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_ok_to_quit (WView * view)
+{
+ int r;
+
+ if (view->change_list == NULL)
+ return TRUE;
+
+ if (!mc_global.midnight_shutdown)
+ {
+ query_set_sel (2);
+ r = query_dialog (_("Quit"),
+ _("File was modified. Save with exit?"), D_NORMAL, 3,
+ _("&Yes"), _("&No"), _("&Cancel quit"));
+ }
+ else
+ {
+ r = query_dialog (_("Quit"),
+ _("Midnight Commander is being shut down.\nSave modified file?"),
+ D_NORMAL, 2, _("&Yes"), _("&No"));
+ /* Esc is No */
+ if (r == -1)
+ r = 1;
+ }
+
+ switch (r)
+ {
+ case 0: /* Yes */
+ return mcview_hexedit_save_changes (view) || mc_global.midnight_shutdown;
+ case 1: /* No */
+ mcview_hexedit_free_change_list (view);
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+mcview_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WView *view = (WView *) w;
+ cb_ret_t i;
+
+ mcview_compute_areas (view);
+ mcview_update_bytes_per_line (view);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ if (mcview_is_in_panel (view))
+ add_hook (&select_file_hook, mcview_hook, view);
+ else
+ view->dpy_bbar_dirty = TRUE;
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ mcview_display (view);
+ return MSG_HANDLED;
+
+ case MSG_CURSOR:
+ if (view->mode_flags.hex)
+ mcview_place_cursor (view);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ i = mcview_handle_key (view, parm);
+ mcview_update (view);
+ return i;
+
+ case MSG_ACTION:
+ i = mcview_execute_cmd (view, parm);
+ mcview_update (view);
+ return i;
+
+ case MSG_FOCUS:
+ view->dpy_bbar_dirty = TRUE;
+ /* TODO: get rid of draw here before MSG_DRAW */
+ mcview_update (view);
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ widget_default_callback (w, NULL, MSG_RESIZE, 0, data);
+ mcview_resize (view);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ if (mcview_is_in_panel (view))
+ {
+ delete_hook (&select_file_hook, mcview_hook);
+
+ /*
+ * In some cases when mc startup is very slow and one panel is in quick view mode,
+ * @view is registered in two hook lists at the same time:
+ * mcview_callback (MSG_INIT) -> add_hook (&select_file_hook)
+ * mcview_hook () -> add_hook (&idle_hook).
+ * If initialization of file manager is not completed yet, but user switches
+ * panel mode from qick view to another one (by pressing C-x q), the following
+ * occurs:
+ * view hook is deleted from select_file_hook list via following call chain:
+ * create_panel (view_listing) -> widget_replace () ->
+ * send_message (MSG_DESTROY) -> mcview_callback (MSG_DESTROY) ->
+ * delete_hook (&select_file_hook);
+ * @view object is free'd:
+ * create_panel (view_listing) -> g_free (old_widget);
+ * but @view still is in idle_hook list and tried to be executed:
+ * frontend_dlg_run () -> execute_hooks (idle_hook).
+ * Thus here we have access to free'd @view object. To prevent this, remove view hook
+ * from idle_hook list.
+ */
+ delete_hook (&idle_hook, mcview_hook);
+
+ if (mc_global.midnight_shutdown)
+ mcview_ok_to_quit (view);
+ }
+ mcview_done (view);
+ mcview_remove_ext_script (view);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+mcview_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDialog *h = DIALOG (w);
+ WView *view;
+
+ switch (msg)
+ {
+ case MSG_ACTION:
+ /* Handle shortcuts. */
+
+ /* Note: the buttonbar sends messages directly to the the WView, not to
+ * here, which is why we can pass NULL in the following call. */
+ return mcview_execute_cmd (NULL, parm);
+
+ case MSG_VALIDATE:
+ view = (WView *) widget_find_by_type (w, mcview_callback);
+ /* don't stop the dialog before final decision */
+ widget_set_state (w, WST_ACTIVE, TRUE);
+ if (mcview_ok_to_quit (view))
+ dlg_close (h);
+ else
+ mcview_update (view);
+ return MSG_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/ascii.c b/src/viewer/ascii.c
new file mode 100644
index 0000000..f786dcc
--- /dev/null
+++ b/src/viewer/ascii.c
@@ -0,0 +1,1051 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for plain view
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+ Rewritten almost from scratch by:
+ Egmont Koblinger <egmont@gmail.com>, 2014
+
+ 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/>.
+
+ ------------------------------------------------------------------------------------------------
+
+ The viewer is implemented along the following design principles:
+
+ Goals: Always display simple scripts, double wide (CJK), combining accents and spacing marks
+ (often used e.g. in Devanagari) perfectly. Make the arrow keys always work correctly.
+
+ Absolutely non-goal: RTL.
+
+ Terminology:
+
+ - A "paragraph" is the text between two adjacent newline characters. A "line" or "row" is a
+ visual row on the screen. In wrap mode, the viewer formats a paragraph into one or more lines.
+
+ - The Unicode glossary <http://www.unicode.org/glossary/> doesn't seem to have a notion of "base
+ character followed by zero or more combining characters". The closest matches are "Combining
+ Character Sequence" meaning a base character followed by one or more combining characters, or
+ "Grapheme" which seems to exclude non-printable characters such as newline. In this file,
+ "combining character sequence" (or any obvious abbreviation thereof) means a base character
+ followed by zero or more (up to a current limit of 4) combining characters.
+
+ ------------------------------------------------------------------------------------------------
+
+ The parser-formatter is designed to be stateless across paragraphs. This is so that we can walk
+ backwards without having to reparse the whole file (although we still need to reparse and
+ reformat the whole paragraph, but it's a lot better). This principle needs to be changed if we
+ ever get to address tickets 1849/2977, but then we can still store (for efficiency) the parser
+ state at the beginning of the paragraph, and safely walk backwards if we don't cross an escape
+ character.
+
+ The parser-formatter, however, definitely needs to carry a state across lines. Currently this
+ state contains:
+
+ - The logical column (as if we didn't wrap). This is used for handling TAB characters after a
+ wordwrap consistently with less.
+
+ - Whether the last nroff character was bold or underlined. This is used for displaying the
+ ambiguous _\b_ sequence consistently with less.
+
+ - Whether the desired way of displaying a lonely combining accent or spacing mark is to place it
+ over a dotted circle (we do this at the beginning of the paragraph of after a TAB), or to ignore
+ the combining char and show replacement char for the spacing mark (we do this if e.g. too many
+ of these were encountered and hence we don't glue them with their base character).
+
+ - (This state needs to be expanded if e.g. we decide to print verbose replacement characters
+ (e.g. "<U+0080>") and allow these to wrap around lines.)
+
+ The state also contains the file offset, as it doesn't make sense to ever know the state without
+ knowing the corresponding offset.
+
+ The state depends on various settings (viewer width, encoding, nroff mode, charwrap or wordwrap
+ mode (if we'll have that one day) etc.), needs to be recomputed if any of these changes.
+
+ Walking forwards is usually relatively easy both in the file and on the screen. Walking
+ backwards within a paragraph would only be possible in some special cases and even then it would
+ be painful, so we always walk back to the beginning of the paragraph and reparse-reformat from
+ there.
+
+ (Walking back within a line in the file would have at least the following difficulties: handling
+ the parser state; processing invalid UTF-8; processing invalid nroff (e.g. what is "_\bA\bA"?).
+ Walking back on the display: we wouldn't know where to display the last line of a paragraph, or
+ where to display a line if its following line starts with a wide (CJK or Tab) character. Long
+ story short: just forget this approach.)
+
+ Most important variables:
+
+ - dpy_start: Both in unwrap and wrap modes this points to the beginning of the topmost displayed
+ paragraph.
+
+ - dpy_text_column: Only in unwrap mode, an additional horizontal scroll.
+
+ - dpy_paragraph_skip_lines: Only in wrap mode, an additional vertical scroll (the number of
+ lines that are scrolled off at the top from the topmost paragraph).
+
+ - dpy_state_top: Only in wrap mode, the offset and parser-formatter state at the line where
+ displaying the file begins is cached here.
+
+ - dpy_wrap_dirty: If some parameter has changed that makes it necessary to reparse-redisplay the
+ topmost paragraph.
+
+ In wrap mode, the three variables "dpy_start", "dpy_paragraph_skip_lines" and "dpy_state_top"
+ are kept consistent. Think of the first two as the ones describing the position, and the third
+ as a cached value for better performance so that we don't need to wrap the invisible beginning
+ of the topmost paragraph over and over again. The third value needs to be recomputed each time a
+ parameter that influences parsing or displaying the file (e.g. width of screen, encoding, nroff
+ mode) changes, this is signaled by "dpy_wrap_dirty" to force recomputing "dpy_state_top" (and
+ clamp "dpy_paragraph_skip_lines" if necessary).
+
+ ------------------------------------------------------------------------------------------------
+
+ Help integration
+
+ I'm planning to port the help viewer to this codebase.
+
+ Splitting at sections would still happen in the help viewer. It would either copy a section, or
+ set force_max and a similar force_min to limit displaying to one section only.
+
+ Parsing the help format would go next to the nroff parser. The colors, alternate character set,
+ and emitting the version number would go to the "state". (The version number would be
+ implemented by emitting remaining characters of a buffer in the "state" one by one, without
+ advancing in the file position.)
+
+ The active link would be drawn similarly to the search highlight. Other than that, the viewer
+ wouldn't care about links (except for their color). help.c would keep track of which one is
+ highlighted, how to advance to the next/prev on an arrow, how the scroll offset needs to be
+ adjusted when moving, etc.
+
+ Add wrapping at word boundaries to where wrapping at char boundaries happens now.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/util.h" /* is_printable() */
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "src/setup.h" /* option_tab_spacing */
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/* The Unicode standard recommends that lonely combining characters are printed over a dotted
+ * circle. If the terminal is not UTF-8, this will be replaced by a dot anyway. */
+#define BASE_CHARACTER_FOR_LONELY_COMBINING 0x25CC /* dotted circle */
+#define MAX_COMBINING_CHARS 4 /* both slang and ncurses support exactly 4 */
+
+/* I think anything other than space (e.g. arrows) just introduce visual clutter without actually
+ * adding value. */
+#define PARTIAL_CJK_AT_LEFT_MARGIN ' '
+#define PARTIAL_CJK_AT_RIGHT_MARGIN ' '
+
+/*
+ * Wrap mode: This is for safety so that jumping to the end of file (which already includes
+ * scrolling back by a page) and then walking backwards is reasonably fast, even if the file is
+ * extremely large and consists of maybe full zeros or something like that. If there's no newline
+ * found within this limit, just start displaying from there and see what happens. We might get
+ * some displaying parameters (most importantly the columns) incorrect, but at least will show the
+ * file without spinning the CPU for ages. When scrolling back to that point, the user might see a
+ * garbled first line (even starting with an invalid partial UTF-8), but then walking back by yet
+ * another line should fix it.
+ *
+ * Unwrap mode: This is not used, we wouldn't be able to do anything reasonable without walking
+ * back a whole paragraph (well, view->data_area.height paragraphs actually).
+ */
+#define MAX_BACKWARDS_WALK_IN_PARAGRAPH (100 * 1000)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* TODO: These methods shouldn't be necessary, see ticket 3257 */
+
+static int
+mcview_wcwidth (const WView * view, int c)
+{
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ {
+ if (g_unichar_iswide (c))
+ return 2;
+ if (g_unichar_iszerowidth (c))
+ return 0;
+ }
+#else
+ (void) view;
+ (void) c;
+#endif /* HAVE_CHARSET */
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_ismark (const WView * view, int c)
+{
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ return g_unichar_ismark (c);
+#else
+ (void) view;
+ (void) c;
+#endif /* HAVE_CHARSET */
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* actually is_non_spacing_mark_or_enclosing_mark */
+static gboolean
+mcview_is_non_spacing_mark (const WView * view, int c)
+{
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ {
+ GUnicodeType type;
+
+ type = g_unichar_type (c);
+
+ return type == G_UNICODE_NON_SPACING_MARK || type == G_UNICODE_ENCLOSING_MARK;
+ }
+#else
+ (void) view;
+ (void) c;
+#endif /* HAVE_CHARSET */
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static gboolean
+mcview_is_spacing_mark (const WView * view, int c)
+{
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ return g_unichar_type (c) == G_UNICODE_SPACING_MARK;
+#else
+ (void) view;
+ (void) c;
+#endif /* HAVE_CHARSET */
+ return FALSE;
+}
+#endif /* 0 */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_isprint (const WView * view, int c)
+{
+#ifdef HAVE_CHARSET
+ if (!view->utf8)
+ c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
+ return g_unichar_isprint (c);
+#else
+ (void) view;
+ /* TODO this is very-very buggy by design: ticket 3257 comments 0-1 */
+ return is_printable (c);
+#endif /* HAVE_CHARSET */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+mcview_char_display (const WView * view, int c, char *s)
+{
+#ifdef HAVE_CHARSET
+ if (mc_global.utf8_display)
+ {
+ if (!view->utf8)
+ c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
+ if (!g_unichar_isprint (c))
+ c = '.';
+ return g_unichar_to_utf8 (c, s);
+ }
+ if (view->utf8)
+ {
+ if (g_unichar_iswide (c))
+ {
+ s[0] = s[1] = '.';
+ return 2;
+ }
+ if (g_unichar_iszerowidth (c))
+ return 0;
+ /* TODO the is_printable check below will be broken for this */
+ c = convert_from_utf_to_current_c (c, view->converter);
+ }
+ else
+ {
+ /* TODO the is_printable check below will be broken for this */
+ c = convert_to_display_c (c);
+ }
+#else
+ (void) view;
+#endif /* HAVE_CHARSET */
+ /* TODO this is very-very buggy by design: ticket 3257 comments 0-1 */
+ if (!is_printable (c))
+ c = '.';
+ *s = c;
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Just for convenience, a common interface in front of mcview_get_utf and mcview_get_byte, so that
+ * the caller doesn't have to care about utf8 vs 8-bit modes.
+ *
+ * Normally: stores c, updates state, returns TRUE.
+ * At EOF: state is unchanged, c is undefined, returns FALSE.
+ *
+ * Just as with mcview_get_utf(), invalid UTF-8 is reported using negative integers.
+ *
+ * Also, temporary hack: handle force_max here.
+ * TODO: move it to lower layers (datasource.c)?
+ */
+static gboolean
+mcview_get_next_char (WView * view, mcview_state_machine_t * state, int *c)
+{
+ /* Pretend EOF if we reached force_max */
+ if (view->force_max >= 0 && state->offset >= view->force_max)
+ return FALSE;
+
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ {
+ int char_length = 0;
+
+ if (!mcview_get_utf (view, state->offset, c, &char_length))
+ return FALSE;
+ /* Pretend EOF if we crossed force_max */
+ if (view->force_max >= 0 && state->offset + char_length > view->force_max)
+ return FALSE;
+
+ state->offset += char_length;
+ return TRUE;
+ }
+#endif /* HAVE_CHARSET */
+ if (!mcview_get_byte (view, state->offset, c))
+ return FALSE;
+ state->offset++;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * This function parses the next nroff character and gives it to you along with its desired color,
+ * so you never have to care about nroff again.
+ *
+ * The nroff mode does the backspace trick for every single character (Unicode codepoint). At least
+ * that's what the GNU groff 1.22 package produces, and that's what less 458 expects. For
+ * double-wide characters (CJK), still only a single backspace is emitted. For combining accents
+ * and such, the print-backspace-print step is repeated for the base character and then for each
+ * accent separately.
+ *
+ * So, the right place for this layer is after the bytes are interpreted in UTF-8, but before
+ * joining a base character with its combining accents.
+ *
+ * Normally: stores c and color, updates state, returns TRUE.
+ * At EOF: state is unchanged, c and color are undefined, returns FALSE.
+ *
+ * color can be null if the caller doesn't care.
+ */
+static gboolean
+mcview_get_next_maybe_nroff_char (WView * view, mcview_state_machine_t * state, int *c, int *color)
+{
+ mcview_state_machine_t state_after_nroff;
+ int c2, c3;
+
+ if (color != NULL)
+ *color = VIEW_NORMAL_COLOR;
+
+ if (!view->mode_flags.nroff)
+ return mcview_get_next_char (view, state, c);
+
+ if (!mcview_get_next_char (view, state, c))
+ return FALSE;
+ /* Don't allow nroff formatting around CR, LF, TAB or other special chars */
+ if (!mcview_isprint (view, *c))
+ return TRUE;
+
+ state_after_nroff = *state;
+
+ if (!mcview_get_next_char (view, &state_after_nroff, &c2))
+ return TRUE;
+ if (c2 != '\b')
+ return TRUE;
+
+ if (!mcview_get_next_char (view, &state_after_nroff, &c3))
+ return TRUE;
+ if (!mcview_isprint (view, c3))
+ return TRUE;
+
+ if (*c == '_' && c3 == '_')
+ {
+ *state = state_after_nroff;
+ if (color != NULL)
+ *color =
+ state->nroff_underscore_is_underlined ? VIEW_UNDERLINED_COLOR : VIEW_BOLD_COLOR;
+ }
+ else if (*c == c3)
+ {
+ *state = state_after_nroff;
+ state->nroff_underscore_is_underlined = FALSE;
+ if (color != NULL)
+ *color = VIEW_BOLD_COLOR;
+ }
+ else if (*c == '_')
+ {
+ *c = c3;
+ *state = state_after_nroff;
+ state->nroff_underscore_is_underlined = TRUE;
+ if (color != NULL)
+ *color = VIEW_UNDERLINED_COLOR;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get one base character, along with its combining or spacing mark characters.
+ *
+ * (A spacing mark is a character that extends the base character's width 1 into a combined
+ * character of width 2, yet these two character cells should not be separated. E.g. Devanagari
+ * <U+0939><U+094B>.)
+ *
+ * This method exists mainly for two reasons. One is to be able to tell if we fit on the current
+ * line or need to wrap to the next one. The other is that both slang and ncurses seem to require
+ * that the character and its combining marks are printed in a single call (or is it just a
+ * limitation of mc's wrapper to them?).
+ *
+ * For convenience, this method takes care of converting CR or CR+LF into LF.
+ * TODO this should probably happen later, when displaying the file?
+ *
+ * Normally: stores cs and color, updates state, returns >= 1 (entries in cs).
+ * At EOF: state is unchanged, cs and color are undefined, returns 0.
+ *
+ * @param view ...
+ * @param state the parser-formatter state machine's state, updated
+ * @param cs store the characters here
+ * @param clen the room available in cs (that is, at most clen-1 combining marks are allowed), must
+ * be at least 2
+ * @param color if non-NULL, store the color here, taken from the first codepoint's color
+ * @return the number of entries placed in cs, or 0 on EOF
+ */
+static int
+mcview_next_combining_char_sequence (WView * view, mcview_state_machine_t * state, int *cs,
+ int clen, int *color)
+{
+ int i = 1;
+
+ if (!mcview_get_next_maybe_nroff_char (view, state, cs, color))
+ return 0;
+
+ /* Process \r and \r\n newlines. */
+ if (cs[0] == '\r')
+ {
+ int cnext;
+
+ mcview_state_machine_t state_after_crlf = *state;
+ if (mcview_get_next_maybe_nroff_char (view, &state_after_crlf, &cnext, NULL)
+ && cnext == '\n')
+ *state = state_after_crlf;
+ cs[0] = '\n';
+ return 1;
+ }
+
+ /* We don't want combining over non-printable characters. This includes '\n' and '\t' too. */
+ if (!mcview_isprint (view, cs[0]))
+ return 1;
+
+ if (mcview_ismark (view, cs[0]))
+ {
+ if (!state->print_lonely_combining)
+ {
+ /* First character is combining. Either just return it, ... */
+ return 1;
+ }
+ else
+ {
+ /* or place this (and subsequent combining ones) over a dotted circle. */
+ cs[1] = cs[0];
+ cs[0] = BASE_CHARACTER_FOR_LONELY_COMBINING;
+ i = 2;
+ }
+ }
+
+ if (mcview_wcwidth (view, cs[0]) == 2)
+ {
+ /* Don't allow combining or spacing mark for wide characters, is this okay? */
+ return 1;
+ }
+
+ /* Look for more combining chars. Either at most clen-1 zero-width combining chars,
+ * or at most 1 spacing mark. Is this logic correct? */
+ for (; i < clen; i++)
+ {
+ mcview_state_machine_t state_after_combining;
+
+ state_after_combining = *state;
+ if (!mcview_get_next_maybe_nroff_char (view, &state_after_combining, &cs[i], NULL))
+ return i;
+ if (!mcview_ismark (view, cs[i]) || !mcview_isprint (view, cs[i]))
+ return i;
+ if (g_unichar_type (cs[i]) == G_UNICODE_SPACING_MARK)
+ {
+ /* Only allow as the first combining char. Stop processing in either case. */
+ if (i == 1)
+ {
+ *state = state_after_combining;
+ i++;
+ }
+ return i;
+ }
+ *state = state_after_combining;
+ }
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Parse, format and possibly display one visual line of text.
+ *
+ * Formatting starts at the given "state" (which encodes the file offset and parser and formatter's
+ * internal state). In unwrap mode, this should point to the beginning of the paragraph with the
+ * default state, the additional horizontal scrolling is added here. In wrap mode, this should
+ * point to the beginning of the line, with the proper state at that point.
+ *
+ * In wrap mode, if a line ends in a newline, it is consumed, even if it's exactly at the right
+ * edge. In unwrap mode, the whole remaining line, including the newline is consumed. Displaying
+ * the next line should start at "state"'s new value, or if we displayed the bottom line then
+ * state->offset tells the file offset to be shown in the top bar.
+ *
+ * If "row" is offscreen, don't actually display the line but still update "state" and return the
+ * proper value. This is used by mcview_wrap_move_down to advance in the file.
+ *
+ * @param view ...
+ * @param state the parser-formatter state machine's state, updated
+ * @param row print to this row
+ * @param paragraph_ended store TRUE if paragraph ended by newline or EOF, FALSE if wraps to next
+ * line
+ * @param linewidth store the width of the line here
+ * @return the number of rows, that is, 0 if we were already at EOF, otherwise 1
+ */
+static int
+mcview_display_line (WView * view, mcview_state_machine_t * state, int row,
+ gboolean * paragraph_ended, off_t * linewidth)
+{
+ const WRect *r = &view->data_area;
+ off_t dpy_text_column = view->mode_flags.wrap ? 0 : view->dpy_text_column;
+ int col = 0;
+ int cs[1 + MAX_COMBINING_CHARS];
+ char str[(1 + MAX_COMBINING_CHARS) * UTF8_CHAR_LEN + 1];
+ int i, j;
+
+ if (paragraph_ended != NULL)
+ *paragraph_ended = TRUE;
+
+ if (!view->mode_flags.wrap && (row < 0 || row >= r->lines) && linewidth == NULL)
+ {
+ /* Optimization: Fast forward to the end of the line, rather than carefully
+ * parsing and then not actually displaying it. */
+ off_t eol;
+ int retval;
+
+ eol = mcview_eol (view, state->offset);
+ retval = (eol > state->offset) ? 1 : 0;
+
+ mcview_state_machine_init (state, eol);
+ return retval;
+ }
+
+ while (TRUE)
+ {
+ int charwidth = 0;
+ mcview_state_machine_t state_saved;
+ int n;
+ int color;
+
+ state_saved = *state;
+ n = mcview_next_combining_char_sequence (view, state, cs, 1 + MAX_COMBINING_CHARS, &color);
+ if (n == 0)
+ {
+ if (linewidth != NULL)
+ *linewidth = col;
+ return (col > 0) ? 1 : 0;
+ }
+
+ if (view->search_start <= state->offset && state->offset < view->search_end)
+ color = VIEW_SELECTED_COLOR;
+
+ if (cs[0] == '\n')
+ {
+ /* New line: reset all formatting state for the next paragraph. */
+ mcview_state_machine_init (state, state->offset);
+ if (linewidth != NULL)
+ *linewidth = col;
+ return 1;
+ }
+
+ if (mcview_is_non_spacing_mark (view, cs[0]))
+ {
+ /* Lonely combining character. Probably leftover after too many combining chars. Just ignore. */
+ continue;
+ }
+
+ /* Nonprintable, or lonely spacing mark */
+ if ((!mcview_isprint (view, cs[0]) || mcview_ismark (view, cs[0])) && cs[0] != '\t')
+ cs[0] = '.';
+
+ for (i = 0; i < n; i++)
+ charwidth += mcview_wcwidth (view, cs[i]);
+
+ /* Adjust the width for TAB. It's handled below along with the normal characters,
+ * so that it's wrapped consistently with them, and is painted with the proper
+ * attributes (although currently it can't have a special color). */
+ if (cs[0] == '\t')
+ {
+ charwidth = option_tab_spacing - state->unwrapped_column % option_tab_spacing;
+ state->print_lonely_combining = TRUE;
+ }
+ else
+ state->print_lonely_combining = FALSE;
+
+ /* In wrap mode only: We're done with this row if the character sequence wouldn't fit.
+ * Except if at the first column, because then it wouldn't fit in the next row either.
+ * In this extreme case let the unwrapped code below do its best to display it. */
+ if (view->mode_flags.wrap && (off_t) col + charwidth > dpy_text_column + (off_t) r->cols
+ && col > 0)
+ {
+ *state = state_saved;
+ if (paragraph_ended != NULL)
+ *paragraph_ended = FALSE;
+ if (linewidth != NULL)
+ *linewidth = col;
+ return 1;
+ }
+
+ /* Display, unless outside of the viewport. */
+ if (row >= 0 && row < r->lines)
+ {
+ if ((off_t) col >= dpy_text_column &&
+ (off_t) col + charwidth <= dpy_text_column + (off_t) r->cols)
+ {
+ /* The combining character sequence fits entirely in the viewport. Print it. */
+ tty_setcolor (color);
+ widget_gotoyx (view, r->y + row, r->x + ((off_t) col - dpy_text_column));
+ if (cs[0] == '\t')
+ {
+ for (i = 0; i < charwidth; i++)
+ tty_print_char (' ');
+ }
+ else
+ {
+ j = 0;
+ for (i = 0; i < n; i++)
+ j += mcview_char_display (view, cs[i], str + j);
+ str[j] = '\0';
+ /* This is probably a bug in our tty layer, but tty_print_string
+ * normalizes the string, whereas tty_printf doesn't. Don't normalize,
+ * since we handle combining characters ourselves correctly, it's
+ * better if they are copy-pasted correctly. Ticket 3255. */
+ tty_printf ("%s", str);
+ }
+ }
+ else if ((off_t) col < dpy_text_column && (off_t) col + charwidth > dpy_text_column)
+ {
+ /* The combining character sequence would cross the left edge of the viewport.
+ * This cannot happen with wrap mode. Print replacement character(s),
+ * or spaces with the correct attributes for partial Tabs. */
+ tty_setcolor (color);
+ for (i = dpy_text_column;
+ i < (off_t) col + charwidth && i < dpy_text_column + (off_t) r->cols; i++)
+ {
+ widget_gotoyx (view, r->y + row, r->x + (i - dpy_text_column));
+ tty_print_anychar ((cs[0] == '\t') ? ' ' : PARTIAL_CJK_AT_LEFT_MARGIN);
+ }
+ }
+ else if ((off_t) col < dpy_text_column + (off_t) r->cols &&
+ (off_t) col + charwidth > dpy_text_column + (off_t) r->cols)
+ {
+ /* The combining character sequence would cross the right edge of the viewport
+ * and we're not wrapping. Print replacement character(s),
+ * or spaces with the correct attributes for partial Tabs. */
+ tty_setcolor (color);
+ for (i = col; i < dpy_text_column + (off_t) r->cols; i++)
+ {
+ widget_gotoyx (view, r->y + row, r->x + (i - dpy_text_column));
+ tty_print_anychar ((cs[0] == '\t') ? ' ' : PARTIAL_CJK_AT_RIGHT_MARGIN);
+ }
+ }
+ }
+
+ col += charwidth;
+ state->unwrapped_column += charwidth;
+
+ if (!view->mode_flags.wrap && (off_t) col >= dpy_text_column + (off_t) r->cols
+ && linewidth == NULL)
+ {
+ /* Optimization: Fast forward to the end of the line, rather than carefully
+ * parsing and then not actually displaying it. */
+ off_t eol;
+
+ eol = mcview_eol (view, state->offset);
+ mcview_state_machine_init (state, eol);
+ return 1;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Parse, format and possibly display one paragraph (perhaps not from the beginning).
+ *
+ * Formatting starts at the given "state" (which encodes the file offset and parser and formatter's
+ * internal state). In unwrap mode, this should point to the beginning of the paragraph with the
+ * default state, the additional horizontal scrolling is added here. In wrap mode, this may point
+ * to the beginning of the line within a paragraph (to display the partial paragraph at the top),
+ * with the proper state at that point.
+ *
+ * Displaying the next paragraph should start at "state"'s new value, or if we displayed the bottom
+ * line then state->offset tells the file offset to be shown in the top bar.
+ *
+ * If "row" is negative, don't display the first abs(row) lines and display the rest from the top.
+ * This was a nice idea but it's now unused :)
+ *
+ * If "row" is too large, don't display the paragraph at all but still return the number of lines.
+ * This is used when moving upwards.
+ *
+ * @param view ...
+ * @param state the parser-formatter state machine's state, updated
+ * @param row print starting at this row
+ * @return the number of rows the paragraphs is wrapped to, that is, 0 if we were already at EOF,
+ * otherwise 1 in unwrap mode, >= 1 in wrap mode. We stop when reaching the bottom of the
+ * viewport, it's not counted how many more lines the paragraph would occupy
+ */
+static int
+mcview_display_paragraph (WView * view, mcview_state_machine_t * state, int row)
+{
+ int lines = 0;
+
+ while (TRUE)
+ {
+ gboolean paragraph_ended;
+
+ lines += mcview_display_line (view, state, row, &paragraph_ended, NULL);
+ if (paragraph_ended)
+ return lines;
+
+ if (row < view->data_area.lines)
+ {
+ row++;
+ /* stop if bottom of screen reached */
+ if (row >= view->data_area.lines)
+ return lines;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Recompute dpy_state_top from dpy_start and dpy_paragraph_skip_lines. Clamp
+ * dpy_paragraph_skip_lines if necessary.
+ *
+ * This method should be called in wrap mode after changing one of the parsing or formatting
+ * properties (e.g. window width, encoding, nroff), or when switching to wrap mode from unwrap or
+ * hex.
+ *
+ * If we stayed within the same paragraph then try to keep the vertical offset within that
+ * paragraph as well. It might happen though that the paragraph became shorter than our desired
+ * vertical position, in that case move to its last row.
+ */
+static void
+mcview_wrap_fixup (WView * view)
+{
+ int lines = view->dpy_paragraph_skip_lines;
+
+ if (!view->dpy_wrap_dirty)
+ return;
+ view->dpy_wrap_dirty = FALSE;
+
+ view->dpy_paragraph_skip_lines = 0;
+ mcview_state_machine_init (&view->dpy_state_top, view->dpy_start);
+
+ while (lines-- != 0)
+ {
+ mcview_state_machine_t state_prev;
+ gboolean paragraph_ended;
+
+ state_prev = view->dpy_state_top;
+ if (mcview_display_line (view, &view->dpy_state_top, -1, &paragraph_ended, NULL) == 0)
+ break;
+ if (paragraph_ended)
+ {
+ view->dpy_state_top = state_prev;
+ break;
+ }
+ view->dpy_paragraph_skip_lines++;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * In both wrap and unwrap modes, dpy_start points to the beginning of the paragraph.
+ *
+ * In unwrap mode, start displaying from this position, probably applying an additional horizontal
+ * scroll.
+ *
+ * In wrap mode, an additional dpy_paragraph_skip_lines lines are skipped from the top of this
+ * paragraph. dpy_state_top contains the position and parser-formatter state corresponding to the
+ * top left corner so we can just start rendering from here. Unless dpy_wrap_dirty is set in which
+ * case dpy_state_top is invalid and we need to recompute first.
+ */
+void
+mcview_display_text (WView * view)
+{
+ const WRect *r = &view->data_area;
+ int row;
+ mcview_state_machine_t state;
+ gboolean again;
+
+ do
+ {
+ int n;
+
+ again = FALSE;
+
+ mcview_display_clean (view);
+ mcview_display_ruler (view);
+
+ if (!view->mode_flags.wrap)
+ mcview_state_machine_init (&state, view->dpy_start);
+ else
+ {
+ mcview_wrap_fixup (view);
+ state = view->dpy_state_top;
+ }
+
+ for (row = 0; row < r->lines; row += n)
+ {
+ n = mcview_display_paragraph (view, &state, row);
+ if (n == 0)
+ {
+ /* In the rare case that displaying didn't start at the beginning
+ * of the file, yet there are some empty lines at the bottom,
+ * scroll the file and display again. This happens when e.g. the
+ * window is made bigger, or the file becomes shorter due to
+ * charset change or enabling nroff. */
+ if ((view->mode_flags.wrap ? view->dpy_state_top.offset : view->dpy_start) > 0)
+ {
+ mcview_ascii_move_up (view, r->lines - row);
+ again = TRUE;
+ }
+ break;
+ }
+ }
+ }
+ while (again);
+
+ view->dpy_end = state.offset;
+ view->dpy_state_bottom = state;
+
+ tty_setcolor (VIEW_NORMAL_COLOR);
+ if (mcview_show_eof != NULL && mcview_show_eof[0] != '\0')
+ while (row < r->lines)
+ {
+ widget_gotoyx (view, r->y + row, r->x);
+ /* TODO: should make it no wider than the viewport */
+ tty_print_string (mcview_show_eof);
+ row++;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Move down.
+ *
+ * It's very simple. Just invisibly format the next "lines" lines, carefully carrying the formatter
+ * state in wrap mode. But before each step we need to check if we've already hit the end of the
+ * file, in that case we can no longer move. This is done by walking from dpy_state_bottom.
+ *
+ * Note that this relies on mcview_display_text() setting dpy_state_bottom to its correct value
+ * upon rendering the screen contents. So don't call this function from other functions (e.g. at
+ * the bottom of mcview_ascii_move_up()) which invalidate this value.
+ */
+void
+mcview_ascii_move_down (WView * view, off_t lines)
+{
+ while (lines-- != 0)
+ {
+ gboolean paragraph_ended;
+
+ /* See if there's still data below the bottom line, by imaginarily displaying one
+ * more line. This takes care of reading more data into growbuf, if required.
+ * If the end position didn't advance, we're at EOF and hence bail out. */
+ if (mcview_display_line (view, &view->dpy_state_bottom, -1, &paragraph_ended, NULL) == 0)
+ break;
+
+ /* Okay, there's enough data. Move by 1 row at the top, too. No need to check for
+ * EOF, that can't happen. */
+ if (!view->mode_flags.wrap)
+ {
+ view->dpy_start = mcview_eol (view, view->dpy_start);
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+ else
+ {
+ mcview_display_line (view, &view->dpy_state_top, -1, &paragraph_ended, NULL);
+ if (!paragraph_ended)
+ view->dpy_paragraph_skip_lines++;
+ else
+ {
+ view->dpy_start = view->dpy_state_top.offset;
+ view->dpy_paragraph_skip_lines = 0;
+ }
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Move up.
+ *
+ * Unwrap mode: Piece of cake. Wrap mode: If we'd walk back more than the current line offset
+ * within the paragraph, we need to jump back to the previous paragraph and compute its height to
+ * see if we start from that paragraph, and repeat this if necessary. Once we're within the desired
+ * paragraph, we still need to format it from its beginning to know the state.
+ *
+ * See the top of this file for comments about MAX_BACKWARDS_WALK_IN_PARAGRAPH.
+ *
+ * force_max is a nice protection against the rare extreme case that the file underneath us
+ * changes, we don't want to endlessly consume a file of maybe full of zeros upon moving upwards.
+ */
+void
+mcview_ascii_move_up (WView * view, off_t lines)
+{
+ if (!view->mode_flags.wrap)
+ {
+ while (lines-- != 0)
+ view->dpy_start = mcview_bol (view, view->dpy_start - 1, 0);
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+ else
+ {
+ int i;
+
+ while (lines > view->dpy_paragraph_skip_lines)
+ {
+ /* We need to go back to the previous paragraph. */
+ if (view->dpy_start == 0)
+ {
+ /* Oops, we're already in the first paragraph. */
+ view->dpy_paragraph_skip_lines = 0;
+ mcview_state_machine_init (&view->dpy_state_top, 0);
+ return;
+ }
+ lines -= view->dpy_paragraph_skip_lines;
+ view->force_max = view->dpy_start;
+ view->dpy_start =
+ mcview_bol (view, view->dpy_start - 1,
+ view->dpy_start - MAX_BACKWARDS_WALK_IN_PARAGRAPH);
+ mcview_state_machine_init (&view->dpy_state_top, view->dpy_start);
+ /* This is a tricky way of denoting that we're at the end of the paragraph.
+ * Normally we'd jump to the next paragraph and reset paragraph_skip_lines. But for
+ * walking backwards this is exactly what we need. */
+ view->dpy_paragraph_skip_lines =
+ mcview_display_paragraph (view, &view->dpy_state_top, view->data_area.lines);
+ view->force_max = -1;
+ }
+
+ /* Okay, we have have dpy_start pointing to the desired paragraph, and we still need to
+ * walk back "lines" lines from the current "dpy_paragraph_skip_lines" offset. We can't do
+ * that, so walk from the beginning of the paragraph. */
+ mcview_state_machine_init (&view->dpy_state_top, view->dpy_start);
+ view->dpy_paragraph_skip_lines -= lines;
+ for (i = 0; i < view->dpy_paragraph_skip_lines; i++)
+ mcview_display_line (view, &view->dpy_state_top, -1, NULL, NULL);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_ascii_moveto_bol (WView * view)
+{
+ if (!view->mode_flags.wrap)
+ view->dpy_text_column = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_ascii_moveto_eol (WView * view)
+{
+ if (!view->mode_flags.wrap)
+ {
+ mcview_state_machine_t state;
+ off_t linewidth;
+
+ /* Get the width of the topmost paragraph. */
+ mcview_state_machine_init (&state, view->dpy_start);
+ mcview_display_line (view, &state, -1, NULL, &linewidth);
+ view->dpy_text_column = DOZ (linewidth, (off_t) view->data_area.cols);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_state_machine_init (mcview_state_machine_t * state, off_t offset)
+{
+ memset (state, 0, sizeof (*state));
+ state->offset = offset;
+ state->print_lonely_combining = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/coord_cache.c b/src/viewer/coord_cache.c
new file mode 100644
index 0000000..190dbd5
--- /dev/null
+++ b/src/viewer/coord_cache.c
@@ -0,0 +1,395 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for work with coordinate cache (ccache)
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ 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/>.
+ */
+
+/*
+ This cache provides you with a fast lookup to map file offsets into
+ line/column pairs and vice versa. The interface to the mapping is
+ provided by the functions mcview_coord_to_offset() and
+ mcview_offset_to_coord().
+
+ The cache is implemented as a simple sorted array holding entries
+ that map some of the offsets to their line/column pair. Entries that
+ are not cached themselves are interpolated (exactly) from their
+ neighbor entries. The algorithm used for determining the line/column
+ for a specific offset needs to be kept synchronized with the one used
+ in display().
+ */
+
+#include <config.h>
+
+#include <string.h> /* memset() */
+#ifdef MC_ENABLE_DEBUGGING_CODE
+#include <inttypes.h> /* uintmax_t */
+#endif
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define VIEW_COORD_CACHE_GRANUL 1024
+#define CACHE_CAPACITY_DELTA 64
+
+#define coord_cache_index(c, i) ((coord_cache_entry_t *) g_ptr_array_index ((c), (i)))
+
+/*** file scope type declarations ****************************************************************/
+
+typedef gboolean (*cmp_func_t) (const coord_cache_entry_t * a, const coord_cache_entry_t * b);
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* insert new cache entry into the cache */
+static inline void
+mcview_ccache_add_entry (GPtrArray * cache, const coord_cache_entry_t * entry)
+{
+#if GLIB_CHECK_VERSION (2, 68, 0)
+ g_ptr_array_add (cache, g_memdup2 (entry, sizeof (*entry)));
+#else
+ g_ptr_array_add (cache, g_memdup (entry, sizeof (*entry)));
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_coord_cache_entry_less_offset (const coord_cache_entry_t * a, const coord_cache_entry_t * b)
+{
+ return (a->cc_offset < b->cc_offset);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_coord_cache_entry_less_plain (const coord_cache_entry_t * a, const coord_cache_entry_t * b)
+{
+ if (a->cc_line < b->cc_line)
+ return TRUE;
+
+ if (a->cc_line == b->cc_line)
+ return (a->cc_column < b->cc_column);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_coord_cache_entry_less_nroff (const coord_cache_entry_t * a, const coord_cache_entry_t * b)
+{
+ if (a->cc_line < b->cc_line)
+ return TRUE;
+
+ if (a->cc_line == b->cc_line)
+ return (a->cc_nroff_column < b->cc_nroff_column);
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Find and return the index of the last cache entry that is
+ * smaller than ''coord'', according to the criterion ''sort_by''. */
+
+static inline size_t
+mcview_ccache_find (WView * view, const coord_cache_entry_t * coord, cmp_func_t cmp_func)
+{
+ size_t base = 0;
+ size_t limit = view->coord_cache->len;
+
+ g_assert (limit != 0);
+
+ while (limit > 1)
+ {
+ size_t i;
+
+ i = base + limit / 2;
+ if (cmp_func (coord, coord_cache_index (view->coord_cache, i)))
+ {
+ /* continue the search in the lower half of the cache */
+ ;
+ }
+ else
+ {
+ /* continue the search in the upper half of the cache */
+ base = i;
+ }
+
+ limit = (limit + 1) / 2;
+ }
+
+ return base;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef MC_ENABLE_DEBUGGING_CODE
+
+void
+mcview_ccache_dump (WView * view)
+{
+ FILE *f;
+ off_t offset, line, column, nextline_offset, filesize;
+ guint i;
+ const GPtrArray *cache = view->coord_cache;
+
+ g_assert (cache != NULL);
+
+ filesize = mcview_get_filesize (view);
+
+ f = fopen ("mcview-ccache.out", "w");
+ if (f == NULL)
+ return;
+
+ (void) setvbuf (f, NULL, _IONBF, 0);
+
+ /* cache entries */
+ for (i = 0; i < cache->len; i++)
+ {
+ coord_cache_entry_t *e;
+
+ e = coord_cache_index (cache, i);
+ (void) fprintf (f,
+ "entry %8u offset %8" PRIuMAX
+ " line %8" PRIuMAX " column %8" PRIuMAX
+ " nroff_column %8" PRIuMAX "\n",
+ (unsigned int) i,
+ (uintmax_t) e->cc_offset, (uintmax_t) e->cc_line, (uintmax_t) e->cc_column,
+ (uintmax_t) e->cc_nroff_column);
+ }
+ (void) fprintf (f, "\n");
+
+ /* offset -> line/column translation */
+ for (offset = 0; offset < filesize; offset++)
+ {
+ mcview_offset_to_coord (view, &line, &column, offset);
+ (void) fprintf (f,
+ "offset %8" PRIuMAX " line %8" PRIuMAX " column %8" PRIuMAX "\n",
+ (uintmax_t) offset, (uintmax_t) line, (uintmax_t) column);
+ }
+
+ /* line/column -> offset translation */
+ for (line = 0; TRUE; line++)
+ {
+ mcview_coord_to_offset (view, &nextline_offset, line + 1, 0);
+ (void) fprintf (f, "nextline_offset %8" PRIuMAX "\n", (uintmax_t) nextline_offset);
+
+ for (column = 0; TRUE; column++)
+ {
+ mcview_coord_to_offset (view, &offset, line, column);
+ if (offset >= nextline_offset)
+ break;
+
+ (void) fprintf (f,
+ "line %8" PRIuMAX " column %8" PRIuMAX " offset %8" PRIuMAX "\n",
+ (uintmax_t) line, (uintmax_t) column, (uintmax_t) offset);
+ }
+
+ if (nextline_offset >= filesize - 1)
+ break;
+ }
+
+ (void) fclose (f);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+/** Look up the missing components of ''coord'', which are given by
+ * ''lookup_what''. The function returns the smallest value that
+ * matches the existing components of ''coord''.
+ */
+
+void
+mcview_ccache_lookup (WView * view, coord_cache_entry_t * coord, enum ccache_type lookup_what)
+{
+ size_t i;
+ GPtrArray *cache;
+ coord_cache_entry_t current, next, entry;
+ enum ccache_type sorter;
+ off_t limit;
+ cmp_func_t cmp_func;
+
+ enum
+ {
+ NROFF_START,
+ NROFF_BACKSPACE,
+ NROFF_CONTINUATION
+ } nroff_state;
+
+ if (view->coord_cache == NULL)
+ view->coord_cache = g_ptr_array_new_full (CACHE_CAPACITY_DELTA, g_free);
+
+ cache = view->coord_cache;
+
+ if (cache->len == 0)
+ {
+ memset (&current, 0, sizeof (current));
+ mcview_ccache_add_entry (cache, &current);
+ }
+
+ sorter = (lookup_what == CCACHE_OFFSET) ? CCACHE_LINECOL : CCACHE_OFFSET;
+
+ if (sorter == CCACHE_OFFSET)
+ cmp_func = mcview_coord_cache_entry_less_offset;
+ else if (view->mode_flags.nroff)
+ cmp_func = mcview_coord_cache_entry_less_nroff;
+ else
+ cmp_func = mcview_coord_cache_entry_less_plain;
+
+ tty_enable_interrupt_key ();
+
+ retry:
+ /* find the two neighbor entries in the cache */
+ i = mcview_ccache_find (view, coord, cmp_func);
+ /* now i points to the lower neighbor in the cache */
+
+ current = *coord_cache_index (cache, i);
+ if (i + 1 < view->coord_cache->len)
+ limit = coord_cache_index (cache, i + 1)->cc_offset;
+ else
+ limit = current.cc_offset + VIEW_COORD_CACHE_GRANUL;
+
+ entry = current;
+ nroff_state = NROFF_START;
+ for (; current.cc_offset < limit; current = next)
+ {
+ int c;
+
+ if (!mcview_get_byte (view, current.cc_offset, &c))
+ break;
+
+ if (!cmp_func (&current, coord) &&
+ (lookup_what != CCACHE_OFFSET || !view->mode_flags.nroff || nroff_state == NROFF_START))
+ break;
+
+ /* Provide useful default values for 'next' */
+ next.cc_offset = current.cc_offset + 1;
+ next.cc_line = current.cc_line;
+ next.cc_column = current.cc_column + 1;
+ next.cc_nroff_column = current.cc_nroff_column + 1;
+
+ /* and override some of them as necessary. */
+ if (c == '\r')
+ {
+ int nextc = -1;
+
+ mcview_get_byte_indexed (view, current.cc_offset, 1, &nextc);
+
+ /* Ignore '\r' if it is followed by '\r' or '\n'. If it is
+ * followed by anything else, it is a Mac line ending and
+ * produces a line break.
+ */
+ if (nextc == '\r' || nextc == '\n')
+ {
+ next.cc_column = current.cc_column;
+ next.cc_nroff_column = current.cc_nroff_column;
+ }
+ else
+ {
+ next.cc_line = current.cc_line + 1;
+ next.cc_column = 0;
+ next.cc_nroff_column = 0;
+ }
+ }
+ else if (nroff_state == NROFF_BACKSPACE)
+ next.cc_nroff_column = current.cc_nroff_column - 1;
+ else if (c == '\t')
+ {
+ next.cc_column = mcview_offset_rounddown (current.cc_column, 8) + 8;
+ next.cc_nroff_column = mcview_offset_rounddown (current.cc_nroff_column, 8) + 8;
+ }
+ else if (c == '\n')
+ {
+ next.cc_line = current.cc_line + 1;
+ next.cc_column = 0;
+ next.cc_nroff_column = 0;
+ }
+ else
+ {
+ ; /* Use all default values from above */
+ }
+
+ switch (nroff_state)
+ {
+ case NROFF_START:
+ case NROFF_CONTINUATION:
+ nroff_state = mcview_is_nroff_sequence (view, current.cc_offset)
+ ? NROFF_BACKSPACE : NROFF_START;
+ break;
+ case NROFF_BACKSPACE:
+ nroff_state = NROFF_CONTINUATION;
+ break;
+ default:
+ break;
+ }
+
+ /* Cache entries must guarantee that for each i < j,
+ * line[i] <= line[j] and column[i] < column[j]. In the case of
+ * nroff sequences and '\r' characters, this is not guaranteed,
+ * so we cannot save them. */
+ if (nroff_state == NROFF_START && c != '\r')
+ entry = next;
+ }
+
+ if (i + 1 == cache->len && entry.cc_offset != coord_cache_index (cache, i)->cc_offset)
+ {
+ mcview_ccache_add_entry (cache, &entry);
+
+ if (!tty_got_interrupt ())
+ goto retry;
+ }
+
+ tty_disable_interrupt_key ();
+
+ if (lookup_what == CCACHE_OFFSET)
+ coord->cc_offset = current.cc_offset;
+ else
+ {
+ coord->cc_line = current.cc_line;
+ coord->cc_column = current.cc_column;
+ coord->cc_nroff_column = current.cc_nroff_column;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/datasource.c b/src/viewer/datasource.c
new file mode 100644
index 0000000..ea4199c
--- /dev/null
+++ b/src/viewer/datasource.c
@@ -0,0 +1,434 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Functions for datasources
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ 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/>.
+ */
+
+/*
+ The data source provides the viewer with data from either a file, a
+ string or the output of a command. The mcview_get_byte() function can be
+ used to get the value of a byte at a specific offset. If the offset
+ is out of range, -1 is returned. The function mcview_get_byte_indexed(a,b)
+ returns the byte at the offset a+b, or -1 if a+b is out of range.
+
+ The mcview_set_byte() function has the effect that later calls to
+ mcview_get_byte() will return the specified byte for this offset. This
+ function is designed only for use by the hexedit component after
+ saving its changes. Inspect the source before you want to use it for
+ other purposes.
+
+ The mcview_get_filesize() function returns the current size of the
+ data source. If the growing buffer is used, this size may increase
+ later on. Use the mcview_may_still_grow() function when you want to
+ know if the size can change later.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/vfs/vfs.h"
+#include "lib/util.h"
+#include "lib/widget.h" /* D_NORMAL, D_ERROR */
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_set_datasource_stdio_pipe (WView * view, mc_pipe_t * p)
+{
+ p->out.len = MC_PIPE_BUFSIZE;
+ p->out.null_term = FALSE;
+ p->err.len = MC_PIPE_BUFSIZE;
+ p->err.null_term = TRUE;
+ view->datasource = DS_STDIO_PIPE;
+ view->ds_stdio_pipe = p;
+ view->pipe_first_err_msg = TRUE;
+
+ mcview_growbuf_init (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_set_datasource_none (WView * view)
+{
+ view->datasource = DS_NONE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+off_t
+mcview_get_filesize (WView * view)
+{
+ switch (view->datasource)
+ {
+ case DS_STDIO_PIPE:
+ case DS_VFS_PIPE:
+ return mcview_growbuf_filesize (view);
+ case DS_FILE:
+ return view->ds_file_filesize;
+ case DS_STRING:
+ return view->ds_string_len;
+ default:
+ return 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_update_filesize (WView * view)
+{
+ if (view->datasource == DS_FILE)
+ {
+ struct stat st;
+ if (mc_fstat (view->ds_file_fd, &st) != -1)
+ view->ds_file_filesize = st.st_size;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+mcview_get_ptr_file (WView * view, off_t byte_index)
+{
+ g_assert (view->datasource == DS_FILE);
+
+ mcview_file_load_data (view, byte_index);
+ if (mcview_already_loaded (view->ds_file_offset, byte_index, view->ds_file_datalen))
+ return (char *) (view->ds_file_data + (byte_index - view->ds_file_offset));
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Invalid UTF-8 is reported as negative integers (one for each byte),
+ * see ticket 3783. */
+gboolean
+mcview_get_utf (WView * view, off_t byte_index, int *ch, int *ch_len)
+{
+ gchar *str = NULL;
+ int res;
+ gchar utf8buf[UTF8_CHAR_LEN + 1];
+
+ switch (view->datasource)
+ {
+ case DS_STDIO_PIPE:
+ case DS_VFS_PIPE:
+ str = mcview_get_ptr_growing_buffer (view, byte_index);
+ break;
+ case DS_FILE:
+ str = mcview_get_ptr_file (view, byte_index);
+ break;
+ case DS_STRING:
+ str = mcview_get_ptr_string (view, byte_index);
+ break;
+ case DS_NONE:
+ default:
+ break;
+ }
+
+ *ch = 0;
+
+ if (str == NULL)
+ return FALSE;
+
+ res = g_utf8_get_char_validated (str, -1);
+
+ if (res < 0)
+ {
+ /* Retry with explicit bytes to make sure it's not a buffer boundary */
+ int i;
+
+ for (i = 0; i < UTF8_CHAR_LEN; i++)
+ {
+ if (mcview_get_byte (view, byte_index + i, &res))
+ utf8buf[i] = res;
+ else
+ {
+ utf8buf[i] = '\0';
+ break;
+ }
+ }
+ utf8buf[UTF8_CHAR_LEN] = '\0';
+ str = utf8buf;
+ res = g_utf8_get_char_validated (str, -1);
+ }
+
+ if (res < 0)
+ {
+ /* Implicit conversion from signed char to signed int keeps negative values. */
+ *ch = *str;
+ *ch_len = 1;
+ }
+ else
+ {
+ gchar *next_ch = NULL;
+
+ *ch = res;
+ /* Calculate UTF-8 char length */
+ next_ch = g_utf8_next_char (str);
+ *ch_len = next_ch - str;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+mcview_get_ptr_string (WView * view, off_t byte_index)
+{
+ g_assert (view->datasource == DS_STRING);
+
+ if (byte_index >= 0 && byte_index < (off_t) view->ds_string_len)
+ return (char *) (view->ds_string_data + byte_index);
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_get_byte_string (WView * view, off_t byte_index, int *retval)
+{
+ char *p;
+
+ if (retval != NULL)
+ *retval = -1;
+
+ p = mcview_get_ptr_string (view, byte_index);
+ if (p == NULL)
+ return FALSE;
+
+ if (retval != NULL)
+ *retval = (unsigned char) (*p);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_get_byte_none (WView * view, off_t byte_index, int *retval)
+{
+ (void) &view;
+ (void) byte_index;
+
+ g_assert (view->datasource == DS_NONE);
+
+ if (retval != NULL)
+ *retval = -1;
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_set_byte (WView * view, off_t offset, byte b)
+{
+ (void) &b;
+
+ g_assert (offset < mcview_get_filesize (view));
+ g_assert (view->datasource == DS_FILE);
+
+ view->ds_file_datalen = 0; /* just force reloading */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*static */
+void
+mcview_file_load_data (WView * view, off_t byte_index)
+{
+ off_t blockoffset;
+ ssize_t res;
+ size_t bytes_read;
+
+ g_assert (view->datasource == DS_FILE);
+
+ if (mcview_already_loaded (view->ds_file_offset, byte_index, view->ds_file_datalen))
+ return;
+
+ if (byte_index >= view->ds_file_filesize)
+ return;
+
+ blockoffset = mcview_offset_rounddown (byte_index, view->ds_file_datasize);
+ if (mc_lseek (view->ds_file_fd, blockoffset, SEEK_SET) == -1)
+ goto error;
+
+ bytes_read = 0;
+ while (bytes_read < view->ds_file_datasize)
+ {
+ res =
+ mc_read (view->ds_file_fd, view->ds_file_data + bytes_read,
+ view->ds_file_datasize - bytes_read);
+ if (res == -1)
+ goto error;
+ if (res == 0)
+ break;
+ bytes_read += (size_t) res;
+ }
+ view->ds_file_offset = blockoffset;
+ if ((off_t) bytes_read > view->ds_file_filesize - view->ds_file_offset)
+ {
+ /* the file has grown in the meantime -- stick to the old size */
+ view->ds_file_datalen = view->ds_file_filesize - view->ds_file_offset;
+ }
+ else
+ {
+ view->ds_file_datalen = bytes_read;
+ }
+ return;
+
+ error:
+ view->ds_file_datalen = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_close_datasource (WView * view)
+{
+ switch (view->datasource)
+ {
+ case DS_NONE:
+ break;
+ case DS_STDIO_PIPE:
+ if (view->ds_stdio_pipe != NULL)
+ {
+ mcview_growbuf_done (view);
+ mcview_display (view);
+ }
+ mcview_growbuf_free (view);
+ break;
+ case DS_VFS_PIPE:
+ if (view->ds_vfs_pipe != -1)
+ mcview_growbuf_done (view);
+ mcview_growbuf_free (view);
+ break;
+ case DS_FILE:
+ (void) mc_close (view->ds_file_fd);
+ view->ds_file_fd = -1;
+ MC_PTR_FREE (view->ds_file_data);
+ break;
+ case DS_STRING:
+ MC_PTR_FREE (view->ds_string_data);
+ break;
+ default:
+ break;
+ }
+ view->datasource = DS_NONE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_set_datasource_file (WView * view, int fd, const struct stat *st)
+{
+ view->datasource = DS_FILE;
+ view->ds_file_fd = fd;
+ view->ds_file_filesize = st->st_size;
+ view->ds_file_offset = 0;
+ view->ds_file_data = g_malloc (4096);
+ view->ds_file_datalen = 0;
+ view->ds_file_datasize = 4096;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_load_command_output (WView * view, const char *command)
+{
+ mc_pipe_t *p;
+ GError *error = NULL;
+
+ mcview_close_datasource (view);
+
+ p = mc_popen (command, TRUE, TRUE, &error);
+ if (p == NULL)
+ {
+ mcview_display (view);
+ mcview_show_error (view, error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ /* Check if filter produced any output */
+ mcview_set_datasource_stdio_pipe (view, p);
+ if (!mcview_get_byte (view, 0, NULL))
+ {
+ mcview_close_datasource (view);
+ mcview_display (view);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_set_datasource_vfs_pipe (WView * view, int fd)
+{
+ g_assert (fd != -1);
+
+ view->datasource = DS_VFS_PIPE;
+ view->ds_vfs_pipe = fd;
+
+ mcview_growbuf_init (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_set_datasource_string (WView * view, const char *s)
+{
+ view->datasource = DS_STRING;
+ view->ds_string_len = strlen (s);
+ view->ds_string_data = (byte *) g_strndup (s, view->ds_string_len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/dialogs.c b/src/viewer/dialogs.c
new file mode 100644
index 0000000..f15c2ff
--- /dev/null
+++ b/src/viewer/dialogs.c
@@ -0,0 +1,263 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for paint dialogs
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ 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 <stdlib.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+#include "lib/search.h"
+#include "lib/strutil.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "src/history.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_dialog_search (WView * view)
+{
+ char *exp = NULL;
+ int qd_result;
+ size_t num_of_types = 0;
+ gchar **list_of_types;
+
+ list_of_types = mc_search_get_types_strings_array (&num_of_types);
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (N_("Enter search string:"), input_label_above,
+ INPUT_LAST_TEXT, MC_HISTORY_SHARED_SEARCH, &exp,
+ NULL, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_START_COLUMNS,
+ QUICK_RADIO (num_of_types, (const char **) list_of_types,
+ (int *) &mcview_search_options.type, NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("Cas&e sensitive"), &mcview_search_options.case_sens, NULL),
+ QUICK_CHECKBOX (N_("&Backwards"), &mcview_search_options.backwards, NULL),
+ QUICK_CHECKBOX (N_("&Whole words"), &mcview_search_options.whole_words, NULL),
+#ifdef HAVE_CHARSET
+ QUICK_CHECKBOX (N_("&All charsets"), &mcview_search_options.all_codepages, NULL),
+#endif
+ QUICK_STOP_COLUMNS,
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 58 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Search"), "[Input Line Keys]",
+ quick_widgets, NULL, NULL
+ };
+
+ qd_result = quick_dialog (&qdlg);
+ }
+
+ g_strfreev (list_of_types);
+
+ if (qd_result == B_CANCEL || exp[0] == '\0')
+ {
+ g_free (exp);
+ return FALSE;
+ }
+
+#ifdef HAVE_CHARSET
+ {
+ GString *tmp;
+
+ tmp = str_convert_to_input (exp);
+ g_free (exp);
+ if (tmp != NULL)
+ exp = g_string_free (tmp, FALSE);
+ else
+ exp = g_strdup ("");
+ }
+#endif
+
+ mcview_search_deinit (view);
+ view->last_search_string = exp;
+
+ return mcview_search_init (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_dialog_goto (WView * view, off_t * offset)
+{
+ typedef enum
+ {
+ MC_VIEW_GOTO_LINENUM = 0,
+ MC_VIEW_GOTO_PERCENT = 1,
+ MC_VIEW_GOTO_OFFSET_DEC = 2,
+ MC_VIEW_GOTO_OFFSET_HEX = 3
+ } mcview_goto_type_t;
+
+ const char *mc_view_goto_str[] = {
+ N_("&Line number"),
+ N_("Pe&rcents"),
+ N_("&Decimal offset"),
+ N_("He&xadecimal offset")
+ };
+
+ static mcview_goto_type_t current_goto_type = MC_VIEW_GOTO_LINENUM;
+
+ size_t num_of_types;
+ char *exp = NULL;
+ int qd_result;
+ gboolean res;
+
+ num_of_types = G_N_ELEMENTS (mc_view_goto_str);
+
+#ifdef ENABLE_NLS
+ {
+ size_t i;
+
+ for (i = 0; i < num_of_types; i++)
+ mc_view_goto_str[i] = _(mc_view_goto_str[i]);
+ }
+#endif
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_INPUT (INPUT_LAST_TEXT, MC_HISTORY_VIEW_GOTO, &exp, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_RADIO (num_of_types, (const char **) mc_view_goto_str, (int *) &current_goto_type,
+ NULL),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 40 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Goto"), "[Input Line Keys]",
+ quick_widgets, NULL, NULL
+ };
+
+ /* run dialog */
+ qd_result = quick_dialog (&qdlg);
+ }
+
+ *offset = -1;
+
+ /* check input line value */
+ res = (qd_result != B_CANCEL && exp[0] != '\0');
+ if (res)
+ {
+ int base = (current_goto_type == MC_VIEW_GOTO_OFFSET_HEX) ? 16 : 10;
+ off_t addr;
+ char *error;
+
+ addr = (off_t) g_ascii_strtoll (exp, &error, base);
+ if ((*error == '\0') && (addr >= 0))
+ {
+ switch (current_goto_type)
+ {
+ case MC_VIEW_GOTO_LINENUM:
+ /* Line number entered by user is 1-based. */
+ if (addr > 0)
+ addr--;
+ mcview_coord_to_offset (view, offset, addr, 0);
+ *offset = mcview_bol (view, *offset, 0);
+ break;
+ case MC_VIEW_GOTO_PERCENT:
+ if (addr > 100)
+ addr = 100;
+ /* read all data from pipe to get real size */
+ if (view->growbuf_in_use)
+ mcview_growbuf_read_all_data (view);
+ *offset = addr * mcview_get_filesize (view) / 100;
+ if (!view->mode_flags.hex)
+ *offset = mcview_bol (view, *offset, 0);
+ break;
+ case MC_VIEW_GOTO_OFFSET_DEC:
+ case MC_VIEW_GOTO_OFFSET_HEX:
+ if (!view->mode_flags.hex)
+ {
+ if (view->growbuf_in_use)
+ mcview_growbuf_read_until (view, addr);
+
+ *offset = mcview_bol (view, addr, 0);
+ }
+ else
+ {
+ /* read all data from pipe to get real size */
+ if (view->growbuf_in_use)
+ mcview_growbuf_read_all_data (view);
+
+ *offset = addr;
+ addr = mcview_get_filesize (view);
+ if (*offset > addr)
+ *offset = addr;
+ }
+ break;
+ default:
+ *offset = 0;
+ break;
+ }
+ }
+ }
+
+ g_free (exp);
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/display.c b/src/viewer/display.c
new file mode 100644
index 0000000..e76c4dd
--- /dev/null
+++ b/src/viewer/display.c
@@ -0,0 +1,404 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for whow info on display
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009, 2010
+
+ 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 <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+#include "lib/skin.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "src/setup.h" /* panels_options */
+#include "src/keymap.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define BUF_TRUNC_LEN 5 /* The length of the line displays the file size */
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* If set, show a ruler */
+static enum ruler_type
+{
+ RULER_NONE,
+ RULER_TOP,
+ RULER_BOTTOM
+} ruler = RULER_NONE;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** Define labels and handlers for functional keys */
+
+static void
+mcview_set_buttonbar (WView * view)
+{
+ Widget *w = WIDGET (view);
+ WDialog *h = DIALOG (w->owner);
+ WButtonBar *b;
+ const global_keymap_t *keymap = view->mode_flags.hex ? view->hex_keymap : w->keymap;
+
+ b = buttonbar_find (h);
+ buttonbar_set_label (b, 1, Q_ ("ButtonBar|Help"), keymap, w);
+
+ if (view->mode_flags.hex)
+ {
+ if (view->hexedit_mode)
+ buttonbar_set_label (b, 2, Q_ ("ButtonBar|View"), keymap, w);
+ else if (view->datasource == DS_FILE)
+ buttonbar_set_label (b, 2, Q_ ("ButtonBar|Edit"), keymap, w);
+ else
+ buttonbar_set_label (b, 2, "", keymap, WIDGET (view));
+
+ buttonbar_set_label (b, 4, Q_ ("ButtonBar|Ascii"), keymap, w);
+ buttonbar_set_label (b, 6, Q_ ("ButtonBar|Save"), keymap, w);
+ buttonbar_set_label (b, 7, Q_ ("ButtonBar|HxSrch"), keymap, w);
+
+ }
+ else
+ {
+ buttonbar_set_label (b, 2, view->mode_flags.wrap ? Q_ ("ButtonBar|UnWrap")
+ : Q_ ("ButtonBar|Wrap"), keymap, w);
+ buttonbar_set_label (b, 4, Q_ ("ButtonBar|Hex"), keymap, w);
+ buttonbar_set_label (b, 6, "", keymap, WIDGET (view));
+ buttonbar_set_label (b, 7, Q_ ("ButtonBar|Search"), keymap, w);
+ }
+
+ buttonbar_set_label (b, 5, Q_ ("ButtonBar|Goto"), keymap, w);
+ buttonbar_set_label (b, 8, view->mode_flags.magic ? Q_ ("ButtonBar|Raw")
+ : Q_ ("ButtonBar|Parse"), keymap, w);
+
+ if (!mcview_is_in_panel (view)) /* don't override some panel buttonbar keys */
+ {
+ buttonbar_set_label (b, 3, Q_ ("ButtonBar|Quit"), keymap, w);
+ buttonbar_set_label (b, 9, view->mode_flags.nroff ? Q_ ("ButtonBar|Unform")
+ : Q_ ("ButtonBar|Format"), keymap, w);
+ buttonbar_set_label (b, 10, Q_ ("ButtonBar|Quit"), keymap, w);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_display_percent (WView * view, off_t p)
+{
+ int percent;
+
+ percent = mcview_calc_percent (view, p);
+ if (percent >= 0)
+ {
+ int top = view->status_area.y;
+ int right;
+
+ right = view->status_area.x + view->status_area.cols;
+ widget_gotoyx (view, top, right - 4);
+ tty_printf ("%3d%%", percent);
+ /* avoid cursor wrapping in NCurses-base MC */
+ widget_gotoyx (view, top, right - 1);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_display_status (WView * view)
+{
+ const WRect *r = &view->status_area;
+ const char *file_label;
+
+ if (r->lines < 1)
+ return;
+
+ tty_setcolor (STATUSBAR_COLOR);
+ tty_draw_hline (WIDGET (view)->rect.y + r->y, WIDGET (view)->rect.x + r->x, ' ', r->cols);
+
+ file_label =
+ view->filename_vpath != NULL ?
+ vfs_path_get_last_path_str (view->filename_vpath) : view->command != NULL ?
+ view->command : "";
+
+ if (r->cols > 40)
+ {
+ widget_gotoyx (view, r->y, r->cols - 32);
+ if (view->mode_flags.hex)
+ tty_printf ("0x%08" PRIxMAX, (uintmax_t) view->hex_cursor);
+ else
+ {
+ char buffer[BUF_TRUNC_LEN + 1];
+
+ size_trunc_len (buffer, BUF_TRUNC_LEN, mcview_get_filesize (view), 0,
+ panels_options.kilobyte_si);
+ tty_printf ("%9" PRIuMAX "/%s%s %s", (uintmax_t) view->dpy_end,
+ buffer, mcview_may_still_grow (view) ? "+" : " ",
+#ifdef HAVE_CHARSET
+ mc_global.source_codepage >= 0 ?
+ get_codepage_id (mc_global.source_codepage) :
+#endif
+ "");
+ }
+ }
+ widget_gotoyx (view, r->y, r->x);
+ if (r->cols > 40)
+ tty_print_string (str_fit_to_term (file_label, r->cols - 34, J_LEFT_FIT));
+ else
+ tty_print_string (str_fit_to_term (file_label, r->cols - 5, J_LEFT_FIT));
+ if (r->cols > 26)
+ mcview_display_percent (view, view->mode_flags.hex ? view->hex_cursor : view->dpy_end);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_update (WView * view)
+{
+ static int dirt_limit = 1;
+
+ if (view->dpy_bbar_dirty)
+ {
+ view->dpy_bbar_dirty = FALSE;
+ mcview_set_buttonbar (view);
+ widget_draw (WIDGET (buttonbar_find (DIALOG (WIDGET (view)->owner))));
+ }
+
+ if (view->dirty > dirt_limit)
+ {
+ /* Too many updates skipped -> force a update */
+ mcview_display (view);
+ view->dirty = 0;
+ /* Raise the update skipping limit */
+ dirt_limit++;
+ if (dirt_limit > mcview_max_dirt_limit)
+ dirt_limit = mcview_max_dirt_limit;
+ }
+ else if (view->dirty > 0)
+ {
+ if (is_idle ())
+ {
+ /* We have time to update the screen properly */
+ mcview_display (view);
+ view->dirty = 0;
+ if (dirt_limit > 1)
+ dirt_limit--;
+ }
+ else
+ {
+ /* We are busy -> skipping full update,
+ only the status line is updated */
+ mcview_display_status (view);
+ }
+ /* Here we had a refresh, if fast scrolling does not work
+ restore the refresh, although this should not happen */
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Displays as much data from view->dpy_start as fits on the screen */
+
+void
+mcview_display (WView * view)
+{
+ if (view->mode_flags.hex)
+ mcview_display_hex (view);
+ else
+ mcview_display_text (view);
+ mcview_display_status (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_compute_areas (WView * view)
+{
+ WRect view_area;
+ int height, rest, y;
+
+ /* The viewer is surrounded by a frame of size view->dpy_frame_size.
+ * Inside that frame, there are: The status line (at the top),
+ * the data area and an optional ruler, which is shown above or
+ * below the data area. */
+
+ view_area.y = view->dpy_frame_size;
+ view_area.x = view->dpy_frame_size;
+ view_area.lines = DOZ (WIDGET (view)->rect.lines, 2 * view->dpy_frame_size);
+ view_area.cols = DOZ (WIDGET (view)->rect.cols, 2 * view->dpy_frame_size);
+
+ /* Most coordinates of the areas equal those of the whole viewer */
+ view->status_area = view_area;
+ view->ruler_area = view_area;
+ view->data_area = view_area;
+
+ /* Compute the heights of the areas */
+ rest = view_area.lines;
+
+ height = MIN (rest, 1);
+ view->status_area.lines = height;
+ rest -= height;
+
+ height = (ruler == RULER_NONE || view->mode_flags.hex) ? 0 : 2;
+ height = MIN (rest, height);
+ view->ruler_area.lines = height;
+ rest -= height;
+
+ view->data_area.lines = rest;
+
+ /* Compute the position of the areas */
+ y = view_area.y;
+
+ view->status_area.y = y;
+ y += view->status_area.lines;
+
+ if (ruler == RULER_TOP)
+ {
+ view->ruler_area.y = y;
+ y += view->ruler_area.lines;
+ }
+
+ view->data_area.y = y;
+ y += view->data_area.lines;
+
+ if (ruler == RULER_BOTTOM)
+ view->ruler_area.y = y;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_update_bytes_per_line (WView * view)
+{
+ int cols = view->data_area.cols;
+ int bytes;
+
+ if (cols < 9 + 17)
+ bytes = 4;
+ else
+ bytes = 4 * ((cols - 9) / ((cols <= 80) ? 17 : 18));
+
+ g_assert (bytes != 0);
+
+ view->bytes_per_line = bytes;
+ view->dirty = mcview_max_dirt_limit + 1; /* To force refresh */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_display_toggle_ruler (WView * view)
+{
+ static const enum ruler_type next[3] =
+ {
+ RULER_TOP,
+ RULER_BOTTOM,
+ RULER_NONE
+ };
+
+ g_assert ((size_t) ruler < 3);
+
+ ruler = next[(size_t) ruler];
+ mcview_compute_areas (view);
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_display_clean (WView * view)
+{
+ Widget *w = WIDGET (view);
+
+ tty_setcolor (VIEW_NORMAL_COLOR);
+ widget_erase (w);
+ if (view->dpy_frame_size != 0)
+ tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_display_ruler (WView * view)
+{
+ static const char ruler_chars[] = "|----*----";
+ const WRect *r = &view->ruler_area;
+ const int line_row = (ruler == RULER_TOP) ? 0 : 1;
+ const int nums_row = (ruler == RULER_TOP) ? 1 : 0;
+
+ char r_buff[10];
+ off_t cl;
+ int c;
+
+ if (ruler == RULER_NONE || r->lines < 1)
+ return;
+
+ tty_setcolor (VIEW_BOLD_COLOR);
+ for (c = 0; c < r->cols; c++)
+ {
+ cl = view->dpy_text_column + c;
+ if (line_row < r->lines)
+ {
+ widget_gotoyx (view, r->y + line_row, r->x + c);
+ tty_print_char (ruler_chars[cl % 10]);
+ }
+
+ if ((cl != 0) && (cl % 10) == 0)
+ {
+ g_snprintf (r_buff, sizeof (r_buff), "%" PRIuMAX, (uintmax_t) cl);
+ if (nums_row < r->lines)
+ {
+ widget_gotoyx (view, r->y + nums_row, r->x + c - 1);
+ tty_print_string (r_buff);
+ }
+ }
+ }
+ tty_setcolor (VIEW_NORMAL_COLOR);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/growbuf.c b/src/viewer/growbuf.c
new file mode 100644
index 0000000..e18a527
--- /dev/null
+++ b/src/viewer/growbuf.c
@@ -0,0 +1,297 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for work with growing buffers
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009, 2014
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ 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 <errno.h>
+
+#include "lib/global.h"
+#include "lib/vfs/vfs.h"
+#include "lib/util.h"
+#include "lib/widget.h" /* D_NORMAL */
+
+#include "internal.h"
+
+/* Block size for reading files in parts */
+#define VIEW_PAGE_SIZE ((size_t) 8192)
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_growbuf_init (WView * view)
+{
+ view->growbuf_in_use = TRUE;
+ view->growbuf_blockptr = g_ptr_array_new_with_free_func (g_free);
+ view->growbuf_lastindex = VIEW_PAGE_SIZE;
+ view->growbuf_finished = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_growbuf_done (WView * view)
+{
+ view->growbuf_finished = TRUE;
+
+ if (view->datasource == DS_STDIO_PIPE)
+ {
+ mc_pclose (view->ds_stdio_pipe, NULL);
+ view->ds_stdio_pipe = NULL;
+ }
+ else /* view->datasource == DS_VFS_PIPE */
+ {
+ (void) mc_close (view->ds_vfs_pipe);
+ view->ds_vfs_pipe = -1;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_growbuf_free (WView * view)
+{
+ g_assert (view->growbuf_in_use);
+
+ g_ptr_array_free (view->growbuf_blockptr, TRUE);
+ view->growbuf_blockptr = NULL;
+ view->growbuf_in_use = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+off_t
+mcview_growbuf_filesize (WView * view)
+{
+ g_assert (view->growbuf_in_use);
+
+ if (view->growbuf_blockptr->len == 0)
+ return 0;
+ else
+ return ((off_t) view->growbuf_blockptr->len - 1) * VIEW_PAGE_SIZE + view->growbuf_lastindex;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Copies the output from the pipe to the growing buffer, until either
+ * the end-of-pipe is reached or the interval [0..ofs) of the growing
+ * buffer is completely filled.
+ */
+
+void
+mcview_growbuf_read_until (WView * view, off_t ofs)
+{
+ gboolean short_read = FALSE;
+
+ g_assert (view->growbuf_in_use);
+
+ if (view->growbuf_finished)
+ return;
+
+ while (mcview_growbuf_filesize (view) < ofs || short_read)
+ {
+ ssize_t nread = 0;
+ byte *p;
+ size_t bytesfree;
+
+ if (view->growbuf_lastindex == VIEW_PAGE_SIZE)
+ {
+ /* Append a new block to the growing buffer */
+ byte *newblock = g_try_malloc (VIEW_PAGE_SIZE);
+ if (newblock == NULL)
+ return;
+
+ g_ptr_array_add (view->growbuf_blockptr, newblock);
+ view->growbuf_lastindex = 0;
+ }
+
+ p = (byte *) g_ptr_array_index (view->growbuf_blockptr,
+ view->growbuf_blockptr->len - 1) + view->growbuf_lastindex;
+
+ bytesfree = VIEW_PAGE_SIZE - view->growbuf_lastindex;
+
+ if (view->datasource == DS_STDIO_PIPE)
+ {
+ mc_pipe_t *sp = view->ds_stdio_pipe;
+ GError *error = NULL;
+
+ if (bytesfree > MC_PIPE_BUFSIZE)
+ bytesfree = MC_PIPE_BUFSIZE;
+
+ sp->out.len = bytesfree;
+ sp->err.len = MC_PIPE_BUFSIZE;
+
+ mc_pread (sp, &error);
+
+ if (error != NULL)
+ {
+ mcview_show_error (view, error->message);
+ g_error_free (error);
+ mcview_growbuf_done (view);
+ return;
+ }
+
+ if (view->pipe_first_err_msg && sp->err.len > 0)
+ {
+ /* ignore possible following errors */
+ /* reset this flag before call of mcview_show_error() to break
+ * endless recursion: mcview_growbuf_read_until() -> mcview_show_error() ->
+ * MSG_DRAW -> mcview_display() -> mcview_get_byte() -> mcview_growbuf_read_until()
+ */
+ view->pipe_first_err_msg = FALSE;
+
+ mcview_show_error (view, sp->err.buf);
+
+ /* when switch from parse to raw mode and back,
+ * do not close the already closed pipe (see call to mcview_growbuf_done below).
+ * return from here since (sp == view->ds_stdio_pipe) would now be invalid.
+ * NOTE: this check was removed by ticket #4103 but the above call to
+ * mcview_show_error triggers the stdio pipe handle to be closed:
+ * mcview_close_datasource -> mcview_growbuf_done
+ */
+ if (view->ds_stdio_pipe == NULL)
+ return;
+ }
+
+ if (sp->out.len > 0)
+ {
+ memmove (p, sp->out.buf, sp->out.len);
+ nread = sp->out.len;
+ }
+ else if (sp->out.len == MC_PIPE_STREAM_EOF || sp->out.len == MC_PIPE_ERROR_READ)
+ {
+ if (sp->out.len == MC_PIPE_ERROR_READ)
+ {
+ char *err_msg;
+
+ err_msg = g_strdup_printf (_("Failed to read data from child stdout:\n%s"),
+ unix_error_string (sp->out.error));
+ mcview_show_error (view, err_msg);
+ g_free (err_msg);
+ }
+
+ /* when switch from parse to raw mode and back,
+ * do not close the already closed pipe after following loop:
+ * mcview_growbuf_read_until() -> mcview_show_error() ->
+ * MSG_DRAW -> mcview_display() -> mcview_get_byte() -> mcview_growbuf_read_until()
+ */
+ mcview_growbuf_done (view);
+
+ mcview_display (view);
+ return;
+ }
+ }
+ else
+ {
+ g_assert (view->datasource == DS_VFS_PIPE);
+ do
+ {
+ nread = mc_read (view->ds_vfs_pipe, p, bytesfree);
+ }
+ while (nread == -1 && errno == EINTR);
+
+ if (nread <= 0)
+ {
+ mcview_growbuf_done (view);
+ return;
+ }
+ }
+ short_read = ((size_t) nread < bytesfree);
+ view->growbuf_lastindex += nread;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_get_byte_growing_buffer (WView * view, off_t byte_index, int *retval)
+{
+ char *p;
+
+ g_assert (view->growbuf_in_use);
+
+ if (retval != NULL)
+ *retval = -1;
+
+ if (byte_index < 0)
+ return FALSE;
+
+ p = mcview_get_ptr_growing_buffer (view, byte_index);
+ if (p == NULL)
+ return FALSE;
+
+ if (retval != NULL)
+ *retval = (unsigned char) (*p);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+mcview_get_ptr_growing_buffer (WView * view, off_t byte_index)
+{
+ off_t pageno, pageindex;
+
+ g_assert (view->growbuf_in_use);
+
+ if (byte_index < 0)
+ return NULL;
+
+ pageno = byte_index / VIEW_PAGE_SIZE;
+ pageindex = byte_index % VIEW_PAGE_SIZE;
+
+ mcview_growbuf_read_until (view, byte_index + 1);
+ if (view->growbuf_blockptr->len == 0)
+ return NULL;
+ if (pageno < (off_t) view->growbuf_blockptr->len - 1)
+ return ((char *) g_ptr_array_index (view->growbuf_blockptr, pageno) + pageindex);
+ if (pageno == (off_t) view->growbuf_blockptr->len - 1
+ && pageindex < (off_t) view->growbuf_lastindex)
+ return ((char *) g_ptr_array_index (view->growbuf_blockptr, pageno) + pageindex);
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/hex.c b/src/viewer/hex.c
new file mode 100644
index 0000000..c0cf7d0
--- /dev/null
+++ b/src/viewer/hex.c
@@ -0,0 +1,484 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for hex view
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ 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 <errno.h>
+#include <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/vfs/vfs.h"
+#include "lib/lock.h" /* lock_file() and unlock_file() */
+#include "lib/util.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+typedef enum
+{
+ MARK_NORMAL,
+ MARK_SELECTED,
+ MARK_CURSOR,
+ MARK_CHANGED
+} mark_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static const char hex_char[] = "0123456789ABCDEF";
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/** Determine the state of the current byte.
+ *
+ * @param view viewer object
+ * @param from offset
+ * @param curr current node
+ */
+
+static mark_t
+mcview_hex_calculate_boldflag (WView * view, off_t from, struct hexedit_change_node *curr,
+ gboolean force_changed)
+{
+ return (from == view->hex_cursor) ? MARK_CURSOR
+ : ((curr != NULL && from == curr->offset) || force_changed) ? MARK_CHANGED
+ : (view->search_start <= from && from < view->search_end) ? MARK_SELECTED : MARK_NORMAL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_display_hex (WView * view)
+{
+ const WRect *r = &view->data_area;
+ int ngroups = view->bytes_per_line / 4;
+ /* 8 characters are used for the file offset, and every hex group
+ * takes 13 characters. Starting at width of 80 columns, the groups
+ * are separated by an extra vertical line. Starting at width of 81,
+ * there is an extra space before the text column. There is always a
+ * mostly empty column on the right, to allow overflowing CJKs.
+ */
+ int text_start;
+
+ int row = 0;
+ off_t from;
+ mark_t boldflag_byte = MARK_NORMAL;
+ mark_t boldflag_char = MARK_NORMAL;
+ struct hexedit_change_node *curr = view->change_list;
+#ifdef HAVE_CHARSET
+ int cont_bytes = 0; /* number of continuation bytes remanining from current UTF-8 */
+ gboolean cjk_right = FALSE; /* whether the second byte of a CJK is to be processed */
+#endif /* HAVE_CHARSET */
+ gboolean utf8_changed = FALSE; /* whether any of the bytes in the UTF-8 were changed */
+
+ char hex_buff[10]; /* A temporary buffer for sprintf and mvwaddstr */
+
+ text_start = 8 + 13 * ngroups +
+ ((r->cols < 80) ? 0 : (r->cols == 80) ? (ngroups - 1) : (ngroups - 1 + 1));
+
+ mcview_display_clean (view);
+
+ /* Find the first displayable changed byte */
+ /* In UTF-8 mode, go back by 1 or maybe 2 lines to handle continuation bytes properly. */
+ from = view->dpy_start;
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ {
+ if (from >= view->bytes_per_line)
+ {
+ row--;
+ from -= view->bytes_per_line;
+ }
+ if (view->bytes_per_line == 4 && from >= view->bytes_per_line)
+ {
+ row--;
+ from -= view->bytes_per_line;
+ }
+ }
+#endif /* HAVE_CHARSET */
+ while (curr && (curr->offset < from))
+ {
+ curr = curr->next;
+ }
+
+ for (; mcview_get_byte (view, from, NULL) && row < r->lines; row++)
+ {
+ int col = 0;
+ int bytes; /* Number of bytes already printed on the line */
+
+ /* Print the hex offset */
+ if (row >= 0)
+ {
+ int i;
+
+ g_snprintf (hex_buff, sizeof (hex_buff), "%08" PRIXMAX " ", (uintmax_t) from);
+ widget_gotoyx (view, r->y + row, r->x);
+ tty_setcolor (VIEW_BOLD_COLOR);
+ for (i = 0; col < r->cols && hex_buff[i] != '\0'; col++, i++)
+ tty_print_char (hex_buff[i]);
+ tty_setcolor (VIEW_NORMAL_COLOR);
+ }
+
+ for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++)
+ {
+ int c;
+#ifdef HAVE_CHARSET
+ int ch = 0;
+
+ if (view->utf8)
+ {
+ struct hexedit_change_node *corr = curr;
+
+ if (cont_bytes != 0)
+ {
+ /* UTF-8 continuation bytes, print a space (with proper attributes)... */
+ cont_bytes--;
+ ch = ' ';
+ if (cjk_right)
+ {
+ /* ... except when it'd wipe out the right half of a CJK, then print nothing */
+ cjk_right = FALSE;
+ ch = -1;
+ }
+ }
+ else
+ {
+ int j;
+ gchar utf8buf[UTF8_CHAR_LEN + 1];
+ int res;
+ int first_changed = -1;
+
+ for (j = 0; j < UTF8_CHAR_LEN; j++)
+ {
+ if (mcview_get_byte (view, from + j, &res))
+ utf8buf[j] = res;
+ else
+ {
+ utf8buf[j] = '\0';
+ break;
+ }
+ if (curr != NULL && from + j == curr->offset)
+ {
+ utf8buf[j] = curr->value;
+ if (first_changed == -1)
+ first_changed = j;
+ }
+ if (curr != NULL && from + j >= curr->offset)
+ curr = curr->next;
+ }
+ utf8buf[UTF8_CHAR_LEN] = '\0';
+
+ /* Determine the state of the current multibyte char */
+ ch = g_utf8_get_char_validated (utf8buf, -1);
+ if (ch == -1 || ch == -2)
+ {
+ ch = '.';
+ }
+ else
+ {
+ gchar *next_ch;
+
+ next_ch = g_utf8_next_char (utf8buf);
+ cont_bytes = next_ch - utf8buf - 1;
+ if (g_unichar_iswide (ch))
+ cjk_right = TRUE;
+ }
+
+ utf8_changed = (first_changed >= 0 && first_changed <= cont_bytes);
+ curr = corr;
+ }
+ }
+#endif /* HAVE_CHARSET */
+
+ /* For negative rows, the only thing we care about is overflowing
+ * UTF-8 continuation bytes which were handled above. */
+ if (row < 0)
+ {
+ if (curr != NULL && from == curr->offset)
+ curr = curr->next;
+ continue;
+ }
+
+ if (!mcview_get_byte (view, from, &c))
+ break;
+
+ /* Save the cursor position for mcview_place_cursor() */
+ if (from == view->hex_cursor && !view->hexview_in_text)
+ {
+ view->cursor_row = row;
+ view->cursor_col = col;
+ }
+
+ /* Determine the state of the current byte */
+ boldflag_byte = mcview_hex_calculate_boldflag (view, from, curr, FALSE);
+ boldflag_char = mcview_hex_calculate_boldflag (view, from, curr, utf8_changed);
+
+ /* Determine the value of the current byte */
+ if (curr != NULL && from == curr->offset)
+ {
+ c = curr->value;
+ curr = curr->next;
+ }
+
+ /* Select the color for the hex number */
+ tty_setcolor (boldflag_byte == MARK_NORMAL ? VIEW_NORMAL_COLOR :
+ boldflag_byte == MARK_SELECTED ? VIEW_BOLD_COLOR :
+ boldflag_byte == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
+ /* boldflag_byte == MARK_CURSOR */
+ view->hexview_in_text ? VIEW_SELECTED_COLOR : VIEW_UNDERLINED_COLOR);
+
+ /* Print the hex number */
+ widget_gotoyx (view, r->y + row, r->x + col);
+ if (col < r->cols)
+ {
+ tty_print_char (hex_char[c / 16]);
+ col += 1;
+ }
+ if (col < r->cols)
+ {
+ tty_print_char (hex_char[c % 16]);
+ col += 1;
+ }
+
+ /* Print the separator */
+ tty_setcolor (VIEW_NORMAL_COLOR);
+ if (bytes != view->bytes_per_line - 1)
+ {
+ if (col < r->cols)
+ {
+ tty_print_char (' ');
+ col += 1;
+ }
+
+ /* After every four bytes, print a group separator */
+ if (bytes % 4 == 3)
+ {
+ if (view->data_area.cols >= 80 && col < r->cols)
+ {
+ tty_print_one_vline (TRUE);
+ col += 1;
+ }
+ if (col < r->cols)
+ {
+ tty_print_char (' ');
+ col += 1;
+ }
+ }
+ }
+
+ /* Select the color for the character; this differs from the
+ * hex color when boldflag == MARK_CURSOR */
+ tty_setcolor (boldflag_char == MARK_NORMAL ? VIEW_NORMAL_COLOR :
+ boldflag_char == MARK_SELECTED ? VIEW_BOLD_COLOR :
+ boldflag_char == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
+ /* boldflag_char == MARK_CURSOR */
+ view->hexview_in_text ? VIEW_SELECTED_COLOR : MARKED_SELECTED_COLOR);
+
+
+#ifdef HAVE_CHARSET
+ if (mc_global.utf8_display)
+ {
+ if (!view->utf8)
+ {
+ c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
+ }
+ if (!g_unichar_isprint (c))
+ c = '.';
+ }
+ else if (view->utf8)
+ ch = convert_from_utf_to_current_c (ch, view->converter);
+ else
+#endif
+ {
+#ifdef HAVE_CHARSET
+ c = convert_to_display_c (c);
+#endif
+
+ if (!is_printable (c))
+ c = '.';
+ }
+
+ /* Print corresponding character on the text side */
+ if (text_start + bytes < r->cols)
+ {
+ widget_gotoyx (view, r->y + row, r->x + text_start + bytes);
+#ifdef HAVE_CHARSET
+ if (view->utf8)
+ tty_print_anychar (ch);
+ else
+#endif
+ tty_print_char (c);
+ }
+
+ /* Save the cursor position for mcview_place_cursor() */
+ if (from == view->hex_cursor && view->hexview_in_text)
+ {
+ view->cursor_row = row;
+ view->cursor_col = text_start + bytes;
+ }
+ }
+ }
+
+ /* Be polite to the other functions */
+ tty_setcolor (VIEW_NORMAL_COLOR);
+
+ mcview_place_cursor (view);
+ view->dpy_end = from;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_hexedit_save_changes (WView * view)
+{
+ int answer = 0;
+
+ if (view->change_list == NULL)
+ return TRUE;
+
+ while (answer == 0)
+ {
+ int fp;
+ char *text;
+ struct hexedit_change_node *curr, *next;
+
+ g_assert (view->filename_vpath != NULL);
+
+ fp = mc_open (view->filename_vpath, O_WRONLY);
+ if (fp != -1)
+ {
+ for (curr = view->change_list; curr != NULL; curr = next)
+ {
+ next = curr->next;
+
+ if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
+ || mc_write (fp, &(curr->value), 1) != 1)
+ goto save_error;
+
+ /* delete the saved item from the change list */
+ view->change_list = next;
+ view->dirty++;
+ mcview_set_byte (view, curr->offset, curr->value);
+ g_free (curr);
+ }
+
+ view->change_list = NULL;
+
+ if (view->locked)
+ view->locked = unlock_file (view->filename_vpath);
+
+ if (mc_close (fp) == -1)
+ message (D_ERROR, _("Save file"),
+ _("Error while closing the file:\n%s\n"
+ "Data may have been written or not"), unix_error_string (errno));
+
+ view->dirty++;
+ return TRUE;
+ }
+
+ save_error:
+ text = g_strdup_printf (_("Cannot save file:\n%s"), unix_error_string (errno));
+ (void) mc_close (fp);
+
+ answer = query_dialog (_("Save file"), text, D_ERROR, 2, _("&Retry"), _("&Cancel"));
+ g_free (text);
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_toggle_hexedit_mode (WView * view)
+{
+ view->hexedit_mode = !view->hexedit_mode;
+ view->dpy_bbar_dirty = TRUE;
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_hexedit_free_change_list (WView * view)
+{
+ struct hexedit_change_node *curr, *next;
+
+ for (curr = view->change_list; curr != NULL; curr = next)
+ {
+ next = curr->next;
+ g_free (curr);
+ }
+ view->change_list = NULL;
+
+ if (view->locked)
+ view->locked = unlock_file (view->filename_vpath);
+
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
+{
+ /* chnode always either points to the head of the list or
+ * to one of the ->next fields in the list. The value at
+ * this location will be overwritten with the new node. */
+ struct hexedit_change_node **chnode = head;
+
+ while (*chnode != NULL && (*chnode)->offset < node->offset)
+ chnode = &((*chnode)->next);
+
+ node->next = *chnode;
+ *chnode = node;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/internal.h b/src/viewer/internal.h
new file mode 100644
index 0000000..566b07c
--- /dev/null
+++ b/src/viewer/internal.h
@@ -0,0 +1,472 @@
+#ifndef MC__VIEWER_INTERNAL_H
+#define MC__VIEWER_INTERNAL_H
+
+#include <limits.h> /* CHAR_BIT */
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+
+#include "lib/search.h"
+#include "lib/widget.h"
+#include "lib/vfs/vfs.h" /* vfs_path_t */
+#include "lib/util.h" /* mc_pipe_t */
+
+#include "src/keymap.h" /* global_keymap_t */
+#include "src/filemanager/dir.h" /* dir_list */
+
+#include "mcviewer.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define OFF_T_BITWIDTH ((unsigned int) (sizeof (off_t) * CHAR_BIT - 1))
+#define OFFSETTYPE_MAX (((off_t) 1 << (OFF_T_BITWIDTH - 1)) - 1)
+
+typedef unsigned char byte;
+
+/*** enums ***************************************************************************************/
+
+/* data sources of the view */
+enum view_ds
+{
+ DS_NONE, /* No data available */
+ DS_STDIO_PIPE, /* Data comes from a pipe using popen/pclose */
+ DS_VFS_PIPE, /* Data comes from a piped-in VFS file */
+ DS_FILE, /* Data comes from a VFS file */
+ DS_STRING /* Data comes from a string in memory */
+};
+
+enum ccache_type
+{
+ CCACHE_OFFSET,
+ CCACHE_LINECOL
+};
+
+typedef enum
+{
+ NROFF_TYPE_NONE = 0,
+ NROFF_TYPE_BOLD = 1,
+ NROFF_TYPE_UNDERLINE = 2
+} nroff_type_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* A node for building a change list on change_list */
+struct hexedit_change_node
+{
+ struct hexedit_change_node *next;
+ off_t offset;
+ byte value;
+};
+
+/* A cache entry for mapping offsets into line/column pairs and vice versa.
+ * cc_offset, cc_line, and cc_column are the 0-based values of the offset,
+ * line and column of that cache entry. cc_nroff_column is the column
+ * corresponding to cc_offset in nroff mode.
+ */
+typedef struct
+{
+ off_t cc_offset;
+ off_t cc_line;
+ off_t cc_column;
+ off_t cc_nroff_column;
+} coord_cache_entry_t;
+
+/* TODO: find a better name. This is not actually a "state machine",
+ * but a "state machine's state", but that sounds silly.
+ * Could be parser_state, formatter_state... */
+typedef struct
+{
+ off_t offset; /* The file offset at which this is the state. */
+ off_t unwrapped_column; /* Columns if the paragraph wasn't wrapped, */
+ /* used for positioning TABs in wrapped lines */
+ gboolean nroff_underscore_is_underlined; /* whether _\b_ is underlined rather than bold */
+ gboolean print_lonely_combining; /* whether lonely combining marks are printed on a dotted circle */
+} mcview_state_machine_t;
+
+struct mcview_nroff_struct;
+
+struct WView
+{
+ Widget widget;
+
+ vfs_path_t *filename_vpath; /* Name of the file */
+ vfs_path_t *workdir_vpath; /* Name of the working directory */
+ char *command; /* Command used to pipe data in */
+
+ enum view_ds datasource; /* Where the displayed data comes from */
+
+ /* stdio pipe data source */
+ mc_pipe_t *ds_stdio_pipe; /* Output of a shell command */
+ gboolean pipe_first_err_msg; /* Show only 1st message from stderr */
+
+ /* vfs pipe data source */
+ int ds_vfs_pipe; /* Non-seekable vfs file descriptor */
+
+ /* vfs file data source */
+ int ds_file_fd; /* File with random access */
+ off_t ds_file_filesize; /* Size of the file */
+ off_t ds_file_offset; /* Offset of the currently loaded data */
+ byte *ds_file_data; /* Currently loaded data */
+ size_t ds_file_datalen; /* Number of valid bytes in file_data */
+ size_t ds_file_datasize; /* Number of allocated bytes in file_data */
+
+ /* string data source */
+ byte *ds_string_data; /* The characters of the string */
+ size_t ds_string_len; /* The length of the string */
+
+ /* Growing buffers information */
+ gboolean growbuf_in_use; /* Use the growing buffers? */
+ GPtrArray *growbuf_blockptr; /* Pointer to the block pointers */
+ size_t growbuf_lastindex; /* Number of bytes in the last page of the
+ growing buffer */
+ gboolean growbuf_finished; /* TRUE when all data has been read. */
+
+ mcview_mode_flags_t mode_flags;
+
+ /* Hex editor modes */
+ gboolean hexedit_mode; /* Hexview or Hexedit */
+ const global_keymap_t *hex_keymap;
+ gboolean hexview_in_text; /* Is the hexview cursor in the text area? */
+ int bytes_per_line; /* Number of bytes per line in hex mode */
+ off_t hex_cursor; /* Hexview cursor position in file */
+ gboolean hexedit_lownibble; /* Are we editing the last significant nibble? */
+ gboolean locked; /* We hold lock on current file */
+
+#ifdef HAVE_CHARSET
+ gboolean utf8; /* It's multibyte file codeset */
+#endif
+
+ GPtrArray *coord_cache; /* Cache for mapping offsets to cursor positions */
+
+ /* Display information */
+ int dpy_frame_size; /* Size of the frame surrounding the real viewer */
+ off_t dpy_start; /* Offset of the displayed data (start of the paragraph in non-hex mode) */
+ off_t dpy_end; /* Offset after the displayed data */
+ off_t dpy_paragraph_skip_lines; /* Extra lines to skip in wrap mode */
+ mcview_state_machine_t dpy_state_top; /* Parser-formatter state at the topmost visible line in wrap mode */
+ mcview_state_machine_t dpy_state_bottom; /* Parser-formatter state after the bottomvisible line in wrap mode */
+ gboolean dpy_wrap_dirty; /* dpy_state_top needs to be recomputed */
+ off_t dpy_text_column; /* Number of skipped columns in non-wrap
+ * text mode */
+ int cursor_col; /* Cursor column */
+ int cursor_row; /* Cursor row */
+ struct hexedit_change_node *change_list; /* Linked list of changes */
+ WRect status_area; /* Where the status line is displayed */
+ WRect ruler_area; /* Where the ruler is displayed */
+ WRect data_area; /* Where the data is displayed */
+
+ ssize_t force_max; /* Force a max offset, or -1 */
+
+ int dirty; /* Number of skipped updates */
+ gboolean dpy_bbar_dirty; /* Does the button bar need to be updated? */
+
+
+ /* handle of search engine */
+ mc_search_t *search;
+ gchar *last_search_string;
+ struct mcview_nroff_struct *search_nroff_seq;
+ off_t search_start; /* First character to start searching from */
+ off_t search_end; /* Length of found string or 0 if none was found */
+ int search_numNeedSkipChar;
+
+ /* Markers */
+ int marker; /* mark to use */
+ off_t marks[10]; /* 10 marks: 0..9 */
+
+ off_t update_steps; /* The number of bytes between percent increments */
+ off_t update_activate; /* Last point where we updated the status */
+
+ /* converter for translation of text */
+ GIConv converter;
+
+ GArray *saved_bookmarks;
+
+ dir_list *dir; /* List of current directory files
+ * to handle CK_FileNext and CK_FilePrev commands */
+ int *dir_idx; /* Index of current file in dir structure.
+ * Pointer is used here as reference to WPanel::dir::count */
+ vfs_path_t *ext_script; /* Temporary script file created by regex_command_for() */
+};
+
+typedef struct mcview_nroff_struct
+{
+ WView *view;
+ off_t index;
+ int char_length;
+ int current_char;
+ nroff_type_t type;
+ nroff_type_t prev_type;
+} mcview_nroff_t;
+
+typedef struct mcview_search_options_t
+{
+ mc_search_type_t type;
+ gboolean case_sens;
+ gboolean backwards;
+ gboolean whole_words;
+ gboolean all_codepages;
+} mcview_search_options_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern mcview_search_options_t mcview_search_options;
+
+/*** declarations of public functions ************************************************************/
+
+/* actions_cmd.c: */
+cb_ret_t mcview_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data);
+cb_ret_t mcview_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm,
+ void *data);
+
+/* ascii.c: */
+void mcview_display_text (WView * view);
+void mcview_state_machine_init (mcview_state_machine_t *, off_t);
+void mcview_ascii_move_down (WView * view, off_t lines);
+void mcview_ascii_move_up (WView * view, off_t lines);
+void mcview_ascii_moveto_bol (WView * view);
+void mcview_ascii_moveto_eol (WView * view);
+
+#ifdef MC_ENABLE_DEBUGGING_CODE
+void mcview_ccache_dump (WView * view);
+#endif
+
+void mcview_ccache_lookup (WView * view, coord_cache_entry_t * coord, enum ccache_type lookup_what);
+
+/* datasource.c: */
+void mcview_set_datasource_none (WView * view);
+off_t mcview_get_filesize (WView * view);
+void mcview_update_filesize (WView * view);
+char *mcview_get_ptr_file (WView * view, off_t byte_index);
+char *mcview_get_ptr_string (WView * view, off_t byte_index);
+gboolean mcview_get_utf (WView * view, off_t byte_index, int *ch, int *ch_len);
+gboolean mcview_get_byte_string (WView * view, off_t byte_index, int *retval);
+gboolean mcview_get_byte_none (WView * view, off_t byte_index, int *retval);
+void mcview_set_byte (WView * view, off_t offset, byte b);
+void mcview_file_load_data (WView * view, off_t byte_index);
+void mcview_close_datasource (WView * view);
+void mcview_set_datasource_file (WView * view, int fd, const struct stat *st);
+gboolean mcview_load_command_output (WView * view, const char *command);
+void mcview_set_datasource_vfs_pipe (WView * view, int fd);
+void mcview_set_datasource_string (WView * view, const char *s);
+
+/* dialog.c: */
+gboolean mcview_dialog_search (WView * view);
+gboolean mcview_dialog_goto (WView * view, off_t * offset);
+
+/* display.c: */
+void mcview_update (WView * view);
+void mcview_display (WView * view);
+void mcview_compute_areas (WView * view);
+void mcview_update_bytes_per_line (WView * view);
+void mcview_display_toggle_ruler (WView * view);
+void mcview_display_clean (WView * view);
+void mcview_display_ruler (WView * view);
+
+/* growbuf.c: */
+void mcview_growbuf_init (WView * view);
+void mcview_growbuf_done (WView * view);
+void mcview_growbuf_free (WView * view);
+off_t mcview_growbuf_filesize (WView * view);
+void mcview_growbuf_read_until (WView * view, off_t ofs);
+gboolean mcview_get_byte_growing_buffer (WView * view, off_t byte_index, int *retval);
+char *mcview_get_ptr_growing_buffer (WView * view, off_t byte_index);
+
+/* hex.c: */
+void mcview_display_hex (WView * view);
+gboolean mcview_hexedit_save_changes (WView * view);
+void mcview_toggle_hexedit_mode (WView * view);
+void mcview_hexedit_free_change_list (WView * view);
+void mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node);
+
+/* lib.c: */
+void mcview_toggle_magic_mode (WView * view);
+void mcview_toggle_wrap_mode (WView * view);
+void mcview_toggle_nroff_mode (WView * view);
+void mcview_toggle_hex_mode (WView * view);
+void mcview_init (WView * view);
+void mcview_done (WView * view);
+#ifdef HAVE_CHARSET
+void mcview_select_encoding (WView * view);
+void mcview_set_codeset (WView * view);
+#endif
+void mcview_show_error (WView * view, const char *error);
+off_t mcview_bol (WView * view, off_t current, off_t limit);
+off_t mcview_eol (WView * view, off_t current);
+char *mcview_get_title (const WDialog * h, size_t len);
+int mcview_calc_percent (WView * view, off_t p);
+
+/* move.c */
+void mcview_move_up (WView * view, off_t lines);
+void mcview_move_down (WView * view, off_t lines);
+void mcview_move_left (WView * view, off_t columns);
+void mcview_move_right (WView * view, off_t columns);
+void mcview_moveto_top (WView * view);
+void mcview_moveto_bottom (WView * view);
+void mcview_moveto_bol (WView * view);
+void mcview_moveto_eol (WView * view);
+void mcview_moveto_offset (WView * view, off_t offset);
+void mcview_moveto (WView * view, off_t, off_t col);
+void mcview_coord_to_offset (WView * view, off_t * ret_offset, off_t line, off_t column);
+void mcview_offset_to_coord (WView * view, off_t * ret_line, off_t * ret_column, off_t offset);
+void mcview_place_cursor (WView * view);
+void mcview_moveto_match (WView * view);
+
+/* nroff.c: */
+int mcview__get_nroff_real_len (WView * view, off_t start, off_t length);
+mcview_nroff_t *mcview_nroff_seq_new_num (WView * view, off_t lc_index);
+mcview_nroff_t *mcview_nroff_seq_new (WView * view);
+void mcview_nroff_seq_free (mcview_nroff_t ** nroff);
+nroff_type_t mcview_nroff_seq_info (mcview_nroff_t * nroff);
+int mcview_nroff_seq_next (mcview_nroff_t * nroff);
+int mcview_nroff_seq_prev (mcview_nroff_t * nroff);
+
+/* search.c: */
+gboolean mcview_search_init (WView * view);
+void mcview_search_deinit (WView * view);
+mc_search_cbret_t mcview_search_cmd_callback (const void *user_data, gsize char_offset,
+ int *current_char);
+mc_search_cbret_t mcview_search_update_cmd_callback (const void *user_data, gsize char_offset);
+void mcview_do_search (WView * view, off_t want_search_start);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline off_t
+mcview_offset_rounddown (off_t a, off_t b)
+{
+ g_assert (b != 0);
+ return a - a % b;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* {{{ Simple Primitive Functions for WView }}} */
+static inline gboolean
+mcview_is_in_panel (WView * view)
+{
+ return (view->dpy_frame_size != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+mcview_may_still_grow (WView * view)
+{
+ return (view->growbuf_in_use && !view->growbuf_finished);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* returns TRUE if the idx lies in the half-open interval
+ * [offset; offset + size), FALSE otherwise.
+ */
+static inline gboolean
+mcview_already_loaded (off_t offset, off_t idx, size_t size)
+{
+ return (offset <= idx && idx - offset < (off_t) size);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+mcview_get_byte_file (WView * view, off_t byte_index, int *retval)
+{
+ g_assert (view->datasource == DS_FILE);
+
+ mcview_file_load_data (view, byte_index);
+ if (mcview_already_loaded (view->ds_file_offset, byte_index, view->ds_file_datalen))
+ {
+ if (retval)
+ *retval = view->ds_file_data[byte_index - view->ds_file_offset];
+ return TRUE;
+ }
+ if (retval)
+ *retval = -1;
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+mcview_get_byte (WView * view, off_t offset, int *retval)
+{
+ switch (view->datasource)
+ {
+ case DS_STDIO_PIPE:
+ case DS_VFS_PIPE:
+ return mcview_get_byte_growing_buffer (view, offset, retval);
+ case DS_FILE:
+ return mcview_get_byte_file (view, offset, retval);
+ case DS_STRING:
+ return mcview_get_byte_string (view, offset, retval);
+ case DS_NONE:
+ return mcview_get_byte_none (view, offset, retval);
+ default:
+ return FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+mcview_get_byte_indexed (WView * view, off_t base, off_t ofs, int *retval)
+{
+ if (base <= OFFSETTYPE_MAX - ofs)
+ return mcview_get_byte (view, base + ofs, retval);
+
+ if (retval != NULL)
+ *retval = -1;
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+mcview_count_backspaces (WView * view, off_t offset)
+{
+ int backspaces = 0;
+ int c;
+
+ while (offset >= 2 * backspaces && mcview_get_byte (view, offset - 2 * backspaces, &c)
+ && c == '\b')
+ backspaces++;
+
+ return backspaces;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+mcview_is_nroff_sequence (WView * view, off_t offset)
+{
+ int c0, c1, c2;
+
+ /* The following commands are ordered to speed up the calculation. */
+
+ if (!mcview_get_byte_indexed (view, offset, 1, &c1) || c1 != '\b')
+ return FALSE;
+
+ if (!mcview_get_byte_indexed (view, offset, 0, &c0) || !g_ascii_isprint (c0))
+ return FALSE;
+
+ if (!mcview_get_byte_indexed (view, offset, 2, &c2) || !g_ascii_isprint (c2))
+ return FALSE;
+
+ return (c0 == c2 || c0 == '_' || (c0 == '+' && c2 == 'o'));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+mcview_growbuf_read_all_data (WView * view)
+{
+ mcview_growbuf_read_until (view, OFFSETTYPE_MAX);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC__VIEWER_INTERNAL_H */
diff --git a/src/viewer/lib.c b/src/viewer/lib.c
new file mode 100644
index 0000000..5f2eb52
--- /dev/null
+++ b/src/viewer/lib.c
@@ -0,0 +1,437 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Common finctions (used from some other mcviewer functions)
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ 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 <string.h> /* memset() */
+#include <sys/types.h>
+
+#include "lib/global.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/util.h" /* save_file_position() */
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#ifdef HAVE_CHARSET
+#include "src/selcodepage.h"
+#endif
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_toggle_magic_mode (WView * view)
+{
+ char *filename, *command;
+ dir_list *dir;
+ int *dir_idx;
+
+ mcview_altered_flags.magic = TRUE;
+ view->mode_flags.magic = !view->mode_flags.magic;
+
+ /* reinit view */
+ filename = g_strdup (vfs_path_as_str (view->filename_vpath));
+ command = g_strdup (view->command);
+ dir = view->dir;
+ dir_idx = view->dir_idx;
+ view->dir = NULL;
+ view->dir_idx = NULL;
+ mcview_done (view);
+ mcview_init (view);
+ mcview_load (view, command, filename, 0, 0, 0);
+ view->dir = dir;
+ view->dir_idx = dir_idx;
+ g_free (filename);
+ g_free (command);
+
+ view->dpy_bbar_dirty = TRUE;
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_toggle_wrap_mode (WView * view)
+{
+ view->mode_flags.wrap = !view->mode_flags.wrap;
+ view->dpy_wrap_dirty = TRUE;
+ view->dpy_bbar_dirty = TRUE;
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_toggle_nroff_mode (WView * view)
+{
+ view->mode_flags.nroff = !view->mode_flags.nroff;
+ mcview_altered_flags.nroff = TRUE;
+ view->dpy_wrap_dirty = TRUE;
+ view->dpy_bbar_dirty = TRUE;
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_toggle_hex_mode (WView * view)
+{
+ view->mode_flags.hex = !view->mode_flags.hex;
+
+ if (view->mode_flags.hex)
+ {
+ view->hex_cursor = view->dpy_start;
+ view->dpy_start = mcview_offset_rounddown (view->dpy_start, view->bytes_per_line);
+ widget_want_cursor (WIDGET (view), TRUE);
+ }
+ else
+ {
+ view->dpy_start = mcview_bol (view, view->hex_cursor, 0);
+ view->hex_cursor = view->dpy_start;
+ widget_want_cursor (WIDGET (view), FALSE);
+ }
+ mcview_altered_flags.hex = TRUE;
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ view->dpy_bbar_dirty = TRUE;
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_init (WView * view)
+{
+ size_t i;
+
+ view->filename_vpath = NULL;
+ view->workdir_vpath = NULL;
+ view->command = NULL;
+ view->search_nroff_seq = NULL;
+
+ mcview_set_datasource_none (view);
+
+ view->growbuf_in_use = FALSE;
+ /* leave the other growbuf fields uninitialized */
+
+ view->hexedit_lownibble = FALSE;
+ view->locked = FALSE;
+ view->coord_cache = NULL;
+
+ view->dpy_start = 0;
+ view->dpy_paragraph_skip_lines = 0;
+ mcview_state_machine_init (&view->dpy_state_top, 0);
+ view->dpy_wrap_dirty = FALSE;
+ view->force_max = -1;
+ view->dpy_text_column = 0;
+ view->dpy_end = 0;
+ view->hex_cursor = 0;
+ view->cursor_col = 0;
+ view->cursor_row = 0;
+ view->change_list = NULL;
+
+ /* {status,ruler,data}_area are left uninitialized */
+
+ view->dirty = 0;
+ view->dpy_bbar_dirty = TRUE;
+ view->bytes_per_line = 1;
+
+ view->search_start = 0;
+ view->search_end = 0;
+
+ view->marker = 0;
+ for (i = 0; i < G_N_ELEMENTS (view->marks); i++)
+ view->marks[i] = 0;
+
+ view->update_steps = 0;
+ view->update_activate = 0;
+
+ view->saved_bookmarks = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_done (WView * view)
+{
+ /* Save current file position */
+ if (mcview_remember_file_position && view->filename_vpath != NULL)
+ {
+ save_file_position (view->filename_vpath, -1, 0,
+ view->mode_flags.hex ? view->hex_cursor : view->dpy_start,
+ view->saved_bookmarks);
+ view->saved_bookmarks = NULL;
+ }
+
+ /* Write back the global viewer mode */
+ mcview_global_flags = view->mode_flags;
+
+ /* Free memory used by the viewer */
+ /* view->widget needs no destructor */
+ vfs_path_free (view->filename_vpath, TRUE);
+ view->filename_vpath = NULL;
+ vfs_path_free (view->workdir_vpath, TRUE);
+ view->workdir_vpath = NULL;
+ MC_PTR_FREE (view->command);
+
+ mcview_close_datasource (view);
+ /* the growing buffer is freed with the datasource */
+
+ if (view->coord_cache != NULL)
+ {
+ g_ptr_array_free (view->coord_cache, TRUE);
+ view->coord_cache = NULL;
+ }
+
+ if (view->converter == INVALID_CONV)
+ view->converter = str_cnv_from_term;
+
+ if (view->converter != str_cnv_from_term)
+ {
+ str_close_conv (view->converter);
+ view->converter = str_cnv_from_term;
+ }
+
+ mcview_search_deinit (view);
+ view->search = NULL;
+ view->last_search_string = NULL;
+ mcview_hexedit_free_change_list (view);
+
+ if (mc_global.mc_run_mode == MC_RUN_VIEWER && view->dir != NULL)
+ {
+ /* mcviewer is the owner of file list */
+ dir_list_free_list (view->dir);
+ g_free (view->dir);
+ g_free (view->dir_idx);
+ }
+
+ view->dir = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+void
+mcview_set_codeset (WView * view)
+{
+ const char *cp_id = NULL;
+
+ view->utf8 = TRUE;
+ cp_id =
+ get_codepage_id (mc_global.source_codepage >=
+ 0 ? mc_global.source_codepage : mc_global.display_codepage);
+ if (cp_id != NULL)
+ {
+ GIConv conv;
+ conv = str_crt_conv_from (cp_id);
+ if (conv != INVALID_CONV)
+ {
+ if (view->converter != str_cnv_from_term)
+ str_close_conv (view->converter);
+ view->converter = conv;
+ }
+ view->utf8 = (gboolean) str_isutf8 (cp_id);
+ view->dpy_wrap_dirty = TRUE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_select_encoding (WView * view)
+{
+ if (do_select_codepage ())
+ mcview_set_codeset (view);
+}
+#endif /* HAVE_CHARSET */
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_show_error (WView * view, const char *msg)
+{
+ if (mcview_is_in_panel (view))
+ mcview_set_datasource_string (view, msg);
+ else
+ message (D_ERROR, MSG_ERROR, "%s", msg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns index of the first char in the line
+ * it is constant for all line characters
+ */
+
+off_t
+mcview_bol (WView * view, off_t current, off_t limit)
+{
+ int c;
+ off_t filesize;
+ filesize = mcview_get_filesize (view);
+ if (current <= 0)
+ return 0;
+ if (current > filesize)
+ return filesize;
+ if (!mcview_get_byte (view, current, &c))
+ return current;
+ if (c == '\n')
+ {
+ if (!mcview_get_byte (view, current - 1, &c))
+ return current;
+ if (c == '\r')
+ current--;
+ }
+ while (current > 0 && current > limit)
+ {
+ if (!mcview_get_byte (view, current - 1, &c))
+ break;
+ if (c == '\r' || c == '\n')
+ break;
+ current--;
+ }
+ return current;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** returns index of last char on line + width EOL
+ * mcview_eol of the current line == mcview_bol next line
+ */
+
+off_t
+mcview_eol (WView * view, off_t current)
+{
+ int c, prev_ch = 0;
+
+ if (current < 0)
+ return 0;
+
+ while (TRUE)
+ {
+ if (!mcview_get_byte (view, current, &c))
+ break;
+ if (c == '\n')
+ {
+ current++;
+ break;
+ }
+ else if (prev_ch == '\r')
+ {
+ break;
+ }
+ current++;
+ prev_ch = c;
+ }
+ return current;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+mcview_get_title (const WDialog * h, size_t len)
+{
+ const WView *view;
+ const char *modified;
+ const char *file_label;
+ const char *view_filename;
+ char *ret_str;
+
+ view = (const WView *) widget_find_by_type (CONST_WIDGET (h), mcview_callback);
+ modified = view->hexedit_mode && (view->change_list != NULL) ? "(*) " : " ";
+ view_filename = vfs_path_as_str (view->filename_vpath);
+
+ len -= 4;
+
+ file_label = view_filename != NULL ? view_filename : view->command != NULL ? view->command : "";
+ file_label = str_term_trim (file_label, len - str_term_width1 (_("View: ")));
+
+ ret_str = g_strconcat (_("View: "), modified, file_label, (char *) NULL);
+ return ret_str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mcview_calc_percent (WView * view, off_t p)
+{
+ off_t filesize;
+ int percent;
+
+ if (view->status_area.cols < 1 || (view->status_area.x + view->status_area.cols) < 4)
+ return (-1);
+ if (mcview_may_still_grow (view))
+ return (-1);
+
+ filesize = mcview_get_filesize (view);
+ if (view->mode_flags.hex && filesize > 0)
+ {
+ /* p can't be beyond the last char, only over that. Compensate for this. */
+ filesize--;
+ }
+
+ if (filesize == 0 || p >= filesize)
+ percent = 100;
+ else if (p > (INT_MAX / 100))
+ percent = p / (filesize / 100);
+ else
+ percent = p * 100 / filesize;
+
+ return percent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_clear_mode_flags (mcview_mode_flags_t * flags)
+{
+ memset (flags, 0, sizeof (*flags));
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/mcviewer.c b/src/viewer/mcviewer.c
new file mode 100644
index 0000000..36d31c0
--- /dev/null
+++ b/src/viewer/mcviewer.c
@@ -0,0 +1,469 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Interface functions
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ 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 <errno.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/util.h" /* load_file_position() */
+#include "lib/widget.h"
+
+#include "src/filemanager/layout.h"
+#include "src/filemanager/filemanager.h" /* the_menubar */
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+mcview_mode_flags_t mcview_global_flags = {
+ .wrap = TRUE,
+ .hex = FALSE,
+ .magic = TRUE,
+ .nroff = FALSE
+};
+
+mcview_mode_flags_t mcview_altered_flags = {
+ .wrap = FALSE,
+ .hex = FALSE,
+ .magic = FALSE,
+ .nroff = FALSE
+};
+
+gboolean mcview_remember_file_position = FALSE;
+
+/* Maxlimit for skipping updates */
+int mcview_max_dirt_limit = 10;
+
+/* Scrolling is done in pages or line increments */
+gboolean mcview_mouse_move_pages = TRUE;
+
+/* end of file will be showen from mcview_show_eof */
+char *mcview_show_eof = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WView *view = (WView *) w;
+ const WRect *r = &view->data_area;
+ gboolean ok = TRUE;
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ if (mcview_is_in_panel (view))
+ {
+ if (event->y == WIDGET (w->owner)->rect.y)
+ {
+ /* return MOU_UNHANDLED */
+ event->result.abort = TRUE;
+ /* don't draw viewer over menu */
+ ok = FALSE;
+ break;
+ }
+
+ if (!widget_get_state (w, WST_FOCUSED))
+ {
+ /* Grab focus */
+ (void) change_panel ();
+ }
+ }
+ MC_FALLTHROUGH;
+
+ case MSG_MOUSE_CLICK:
+ if (!view->mode_flags.wrap)
+ {
+ /* Scrolling left and right */
+ int x;
+
+ x = event->x + 1; /* FIXME */
+
+ if (x < r->cols * 1 / 4)
+ {
+ mcview_move_left (view, 1);
+ event->result.repeat = msg == MSG_MOUSE_DOWN;
+ }
+ else if (x < r->cols * 3 / 4)
+ {
+ /* ignore the click */
+ ok = FALSE;
+ }
+ else
+ {
+ mcview_move_right (view, 1);
+ event->result.repeat = msg == MSG_MOUSE_DOWN;
+ }
+ }
+ else
+ {
+ /* Scrolling up and down */
+ int y;
+
+ y = event->y + 1; /* FIXME */
+
+ if (y < r->y + r->lines * 1 / 3)
+ {
+ if (mcview_mouse_move_pages)
+ mcview_move_up (view, r->lines / 2);
+ else
+ mcview_move_up (view, 1);
+
+ event->result.repeat = msg == MSG_MOUSE_DOWN;
+ }
+ else if (y < r->y + r->lines * 2 / 3)
+ {
+ /* ignore the click */
+ ok = FALSE;
+ }
+ else
+ {
+ if (mcview_mouse_move_pages)
+ mcview_move_down (view, r->lines / 2);
+ else
+ mcview_move_down (view, 1);
+
+ event->result.repeat = msg == MSG_MOUSE_DOWN;
+ }
+ }
+ break;
+
+ case MSG_MOUSE_SCROLL_UP:
+ mcview_move_up (view, 2);
+ break;
+
+ case MSG_MOUSE_SCROLL_DOWN:
+ mcview_move_down (view, 2);
+ break;
+
+ default:
+ ok = FALSE;
+ break;
+ }
+
+ if (ok)
+ mcview_update (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WView *
+mcview_new (int y, int x, int lines, int cols, gboolean is_panel)
+{
+ WRect r = { y, x, lines, cols };
+ WView *view;
+ Widget *w;
+
+ view = g_new0 (WView, 1);
+ w = WIDGET (view);
+
+ widget_init (w, &r, mcview_callback, mcview_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
+ w->keymap = viewer_map;
+
+ mcview_clear_mode_flags (&view->mode_flags);
+ view->hexedit_mode = FALSE;
+ view->hex_keymap = viewer_hex_map;
+ view->hexview_in_text = FALSE;
+ view->locked = FALSE;
+
+ view->dpy_frame_size = is_panel ? 1 : 0;
+ view->converter = str_cnv_from_term;
+
+ mcview_init (view);
+
+ if (mcview_global_flags.hex)
+ mcview_toggle_hex_mode (view);
+ if (mcview_global_flags.nroff)
+ mcview_toggle_nroff_mode (view);
+ if (mcview_global_flags.wrap)
+ mcview_toggle_wrap_mode (view);
+ if (mcview_global_flags.magic)
+ mcview_toggle_magic_mode (view);
+
+ return view;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Real view only */
+
+gboolean
+mcview_viewer (const char *command, const vfs_path_t * file_vpath, int start_line,
+ off_t search_start, off_t search_end)
+{
+ gboolean succeeded;
+ WView *lc_mcview;
+ WDialog *view_dlg;
+ Widget *vw, *b;
+ WGroup *g;
+
+ /* Create dialog and widgets, put them on the dialog */
+ view_dlg = dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, NULL, mcview_dialog_callback,
+ NULL, "[Internal File Viewer]", NULL);
+ vw = WIDGET (view_dlg);
+ widget_want_tab (vw, TRUE);
+
+ g = GROUP (view_dlg);
+
+ lc_mcview = mcview_new (vw->rect.y, vw->rect.x, vw->rect.lines - 1, vw->rect.cols, FALSE);
+ group_add_widget_autopos (g, lc_mcview, WPOS_KEEP_ALL, NULL);
+
+ b = WIDGET (buttonbar_new ());
+ group_add_widget_autopos (g, b, b->pos_flags, NULL);
+
+ view_dlg->get_title = mcview_get_title;
+
+ succeeded =
+ mcview_load (lc_mcview, command, vfs_path_as_str (file_vpath), start_line, search_start,
+ search_end);
+
+ if (succeeded)
+ dlg_run (view_dlg);
+ else
+ dlg_close (view_dlg);
+
+ if (widget_get_state (vw, WST_CLOSED))
+ widget_destroy (vw);
+
+ return succeeded;
+}
+
+/* {{{ Miscellaneous functions }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_load (WView * view, const char *command, const char *file, int start_line,
+ off_t search_start, off_t search_end)
+{
+ gboolean retval = FALSE;
+ vfs_path_t *vpath = NULL;
+
+ g_assert (view->bytes_per_line != 0);
+
+ view->filename_vpath = vfs_path_from_str (file);
+
+ /* get working dir */
+ if (file != NULL && file[0] != '\0')
+ {
+ vfs_path_free (view->workdir_vpath, TRUE);
+
+ if (!g_path_is_absolute (file))
+ {
+ vfs_path_t *p;
+
+ p = vfs_path_clone (vfs_get_raw_current_dir ());
+ view->workdir_vpath = vfs_path_append_new (p, file, (char *) NULL);
+ vfs_path_free (p, TRUE);
+ }
+ else
+ {
+ /* try extract path from filename */
+ const char *fname;
+ char *dir;
+
+ fname = x_basename (file);
+ dir = g_strndup (file, (size_t) (fname - file));
+ view->workdir_vpath = vfs_path_from_str (dir);
+ g_free (dir);
+ }
+ }
+
+ if (!mcview_is_in_panel (view))
+ view->dpy_text_column = 0;
+
+#ifdef HAVE_CHARSET
+ mcview_set_codeset (view);
+#endif
+
+ if (command != NULL && (view->mode_flags.magic || file == NULL || file[0] == '\0'))
+ retval = mcview_load_command_output (view, command);
+ else if (file != NULL && file[0] != '\0')
+ {
+ int fd;
+ char tmp[BUF_MEDIUM];
+ struct stat st;
+
+ /* Open the file */
+ vpath = vfs_path_from_str (file);
+ fd = mc_open (vpath, O_RDONLY | O_NONBLOCK);
+ if (fd == -1)
+ {
+ g_snprintf (tmp, sizeof (tmp), _("Cannot open \"%s\"\n%s"),
+ file, unix_error_string (errno));
+ mcview_close_datasource (view);
+ mcview_show_error (view, tmp);
+ vfs_path_free (view->filename_vpath, TRUE);
+ view->filename_vpath = NULL;
+ vfs_path_free (view->workdir_vpath, TRUE);
+ view->workdir_vpath = NULL;
+ goto finish;
+ }
+
+ /* Make sure we are working with a regular file */
+ if (mc_fstat (fd, &st) == -1)
+ {
+ mc_close (fd);
+ g_snprintf (tmp, sizeof (tmp), _("Cannot stat \"%s\"\n%s"),
+ file, unix_error_string (errno));
+ mcview_close_datasource (view);
+ mcview_show_error (view, tmp);
+ vfs_path_free (view->filename_vpath, TRUE);
+ view->filename_vpath = NULL;
+ vfs_path_free (view->workdir_vpath, TRUE);
+ view->workdir_vpath = NULL;
+ goto finish;
+ }
+
+ if (!S_ISREG (st.st_mode))
+ {
+ mc_close (fd);
+ mcview_close_datasource (view);
+ mcview_show_error (view, _("Cannot view: not a regular file"));
+ vfs_path_free (view->filename_vpath, TRUE);
+ view->filename_vpath = NULL;
+ vfs_path_free (view->workdir_vpath, TRUE);
+ view->workdir_vpath = NULL;
+ goto finish;
+ }
+
+ if (st.st_size == 0 || mc_lseek (fd, 0, SEEK_SET) == -1)
+ {
+ /* Must be one of those nice files that grow (/proc) */
+ mcview_set_datasource_vfs_pipe (view, fd);
+ }
+ else
+ {
+ if (view->mode_flags.magic)
+ {
+ int type;
+
+ type = get_compression_type (fd, file);
+
+ if (type != COMPRESSION_NONE)
+ {
+ char *tmp_filename;
+ vfs_path_t *vpath1;
+ int fd1;
+
+ tmp_filename = g_strconcat (file, decompress_extension (type), (char *) NULL);
+ vpath1 = vfs_path_from_str (tmp_filename);
+ g_free (tmp_filename);
+ fd1 = mc_open (vpath1, O_RDONLY | O_NONBLOCK);
+ vfs_path_free (vpath1, TRUE);
+
+ if (fd1 == -1)
+ {
+ g_snprintf (tmp, sizeof (tmp), _("Cannot open \"%s\" in parse mode\n%s"),
+ file, unix_error_string (errno));
+ mcview_close_datasource (view);
+ mcview_show_error (view, tmp);
+ }
+ else
+ {
+ mc_close (fd);
+ fd = fd1;
+ mc_fstat (fd, &st);
+ }
+ }
+ }
+
+ mcview_set_datasource_file (view, fd, &st);
+ }
+ retval = TRUE;
+ }
+
+ finish:
+ view->command = g_strdup (command);
+ view->dpy_start = 0;
+ view->dpy_paragraph_skip_lines = 0;
+ mcview_state_machine_init (&view->dpy_state_top, 0);
+ view->dpy_wrap_dirty = FALSE;
+ view->force_max = -1;
+ view->dpy_text_column = 0;
+
+ mcview_compute_areas (view);
+ mcview_update_bytes_per_line (view);
+
+ if (mcview_remember_file_position && view->filename_vpath != NULL && start_line == 0)
+ {
+ long line, col;
+ off_t new_offset, max_offset;
+
+ load_file_position (view->filename_vpath, &line, &col, &new_offset, &view->saved_bookmarks);
+ max_offset = mcview_get_filesize (view) - 1;
+ if (max_offset < 0)
+ new_offset = 0;
+ else
+ new_offset = MIN (new_offset, max_offset);
+ if (!view->mode_flags.hex)
+ {
+ view->dpy_start = mcview_bol (view, new_offset, 0);
+ view->dpy_wrap_dirty = TRUE;
+ }
+ else
+ {
+ view->dpy_start = new_offset - new_offset % view->bytes_per_line;
+ view->hex_cursor = new_offset;
+ }
+ }
+ else if (start_line > 0)
+ mcview_moveto (view, start_line - 1, 0);
+
+ view->search_start = search_start;
+ view->search_end = search_end;
+ view->hexedit_lownibble = FALSE;
+ view->hexview_in_text = FALSE;
+ view->change_list = NULL;
+ vfs_path_free (vpath, TRUE);
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/mcviewer.h b/src/viewer/mcviewer.h
new file mode 100644
index 0000000..d90716c
--- /dev/null
+++ b/src/viewer/mcviewer.h
@@ -0,0 +1,57 @@
+/** \file mcviewer.h
+ * \brief Header: internal file viewer
+ */
+
+#ifndef MC__VIEWER_H
+#define MC__VIEWER_H
+
+#include "lib/global.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct WView;
+typedef struct WView WView;
+
+typedef struct
+{
+ gboolean wrap; /* Wrap text lines to fit them on the screen */
+ gboolean hex; /* Plainview or hexview */
+ gboolean magic; /* Preprocess the file using external programs */
+ gboolean nroff; /* Nroff-style highlighting */
+} mcview_mode_flags_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern mcview_mode_flags_t mcview_global_flags;
+extern mcview_mode_flags_t mcview_altered_flags;
+
+extern gboolean mcview_remember_file_position;
+extern int mcview_max_dirt_limit;
+
+extern gboolean mcview_mouse_move_pages;
+extern char *mcview_show_eof;
+
+/*** declarations of public functions ************************************************************/
+
+/* Creates a new WView object with the given properties. Caveat: the
+ * origin is in y-x order, while the extent is in x-y order. */
+extern WView *mcview_new (int y, int x, int lines, int cols, gboolean is_panel);
+
+
+/* Shows {file} or the output of {command} in the internal viewer,
+ * starting in line {start_line}.
+ */
+extern gboolean mcview_viewer (const char *command, const vfs_path_t * file_vpath, int start_line,
+ off_t search_start, off_t search_end);
+
+extern gboolean mcview_load (WView * view, const char *command, const char *file, int start_line,
+ off_t search_start, off_t search_end);
+
+extern void mcview_clear_mode_flags (mcview_mode_flags_t * flags);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__VIEWER_H */
diff --git a/src/viewer/move.c b/src/viewer/move.c
new file mode 100644
index 0000000..4f15b7c
--- /dev/null
+++ b/src/viewer/move.c
@@ -0,0 +1,415 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Functions for handle cursor movement
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009, 2010
+
+ 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/>.
+ */
+
+/*
+ The following variables have to do with the current position and are
+ updated by the cursor movement functions.
+
+ In hex view and wrapped text view mode, dpy_start marks the offset of
+ the top-left corner on the screen, in non-wrapping text mode it is
+ the beginning of the current line. In hex mode, hex_cursor is the
+ offset of the cursor. In non-wrapping text mode, dpy_text_column is
+ the number of columns that are hidden on the left side on the screen.
+
+ In hex mode, dpy_start is updated by the view_fix_cursor_position()
+ function in order to keep the other functions simple. In
+ non-wrapping text mode dpy_start and dpy_text_column are normalized
+ such that dpy_text_column < view_get_datacolumns().
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_scroll_to_cursor (WView * view)
+{
+ if (view->mode_flags.hex)
+ {
+ off_t bytes = view->bytes_per_line;
+ off_t cursor = view->hex_cursor;
+ off_t topleft = view->dpy_start;
+ off_t displaysize;
+
+ displaysize = view->data_area.lines * bytes;
+ if (topleft + displaysize <= cursor)
+ topleft = mcview_offset_rounddown (cursor, bytes) - (displaysize - bytes);
+ if (cursor < topleft)
+ topleft = mcview_offset_rounddown (cursor, bytes);
+ view->dpy_start = topleft;
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_movement_fixups (WView * view, gboolean reset_search)
+{
+ mcview_scroll_to_cursor (view);
+
+ if (reset_search)
+ {
+ view->search_start = view->mode_flags.hex ? view->hex_cursor : view->dpy_start;
+ view->search_end = view->search_start;
+ }
+
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_move_up (WView * view, off_t lines)
+{
+ if (!view->mode_flags.hex)
+ mcview_ascii_move_up (view, lines);
+ else
+ {
+ off_t bytes;
+
+ bytes = lines * view->bytes_per_line;
+
+ if (view->hex_cursor < bytes)
+ view->hex_cursor %= view->bytes_per_line;
+ else
+ {
+ view->hex_cursor -= bytes;
+ if (view->hex_cursor < view->dpy_start)
+ {
+ view->dpy_start = DOZ (view->dpy_start, bytes);
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+ }
+ }
+
+ mcview_movement_fixups (view, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_move_down (WView * view, off_t lines)
+{
+ off_t last_byte;
+
+ last_byte = mcview_get_filesize (view);
+
+ if (!view->mode_flags.hex)
+ mcview_ascii_move_down (view, lines);
+ else
+ {
+ off_t i, limit;
+
+ limit = DOZ (last_byte, (off_t) view->bytes_per_line);
+
+ for (i = 0; i < lines && view->hex_cursor < limit; i++)
+ {
+ view->hex_cursor += view->bytes_per_line;
+
+ if (lines != 1)
+ {
+ view->dpy_start += view->bytes_per_line;
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+ }
+ }
+
+ mcview_movement_fixups (view, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_move_left (WView * view, off_t columns)
+{
+ if (view->mode_flags.hex)
+ {
+ off_t old_cursor = view->hex_cursor;
+
+ g_assert (columns == 1);
+
+ if (view->hexview_in_text || !view->hexedit_lownibble)
+ if (view->hex_cursor > 0)
+ view->hex_cursor--;
+
+ if (!view->hexview_in_text)
+ if (old_cursor > 0 || view->hexedit_lownibble)
+ view->hexedit_lownibble = !view->hexedit_lownibble;
+ }
+ else if (!view->mode_flags.wrap)
+ view->dpy_text_column = DOZ (view->dpy_text_column, columns);
+
+ mcview_movement_fixups (view, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_move_right (WView * view, off_t columns)
+{
+ if (view->mode_flags.hex)
+ {
+ off_t last_byte;
+ off_t old_cursor = view->hex_cursor;
+
+ last_byte = mcview_get_filesize (view);
+ last_byte = DOZ (last_byte, 1);
+
+ g_assert (columns == 1);
+
+ if (view->hexview_in_text || view->hexedit_lownibble)
+ if (view->hex_cursor < last_byte)
+ view->hex_cursor++;
+
+ if (!view->hexview_in_text)
+ if (old_cursor < last_byte || !view->hexedit_lownibble)
+ view->hexedit_lownibble = !view->hexedit_lownibble;
+ }
+ else if (!view->mode_flags.wrap)
+ view->dpy_text_column += columns;
+
+ mcview_movement_fixups (view, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_moveto_top (WView * view)
+{
+ view->dpy_start = 0;
+ view->dpy_paragraph_skip_lines = 0;
+ mcview_state_machine_init (&view->dpy_state_top, 0);
+ view->hex_cursor = 0;
+ view->dpy_text_column = 0;
+ mcview_movement_fixups (view, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_moveto_bottom (WView * view)
+{
+ off_t filesize;
+
+ mcview_update_filesize (view);
+
+ if (view->growbuf_in_use)
+ mcview_growbuf_read_all_data (view);
+
+ filesize = mcview_get_filesize (view);
+
+ if (view->mode_flags.hex)
+ {
+ view->hex_cursor = DOZ (filesize, 1);
+ mcview_movement_fixups (view, TRUE);
+ }
+ else
+ {
+ view->dpy_start = filesize;
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ mcview_move_up (view, view->data_area.lines);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_moveto_bol (WView * view)
+{
+ if (!view->mode_flags.hex)
+ mcview_ascii_moveto_bol (view);
+ else
+ {
+ view->hex_cursor -= view->hex_cursor % view->bytes_per_line;
+ view->dpy_text_column = 0;
+ }
+
+ mcview_movement_fixups (view, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_moveto_eol (WView * view)
+{
+ off_t bol;
+
+ if (!view->mode_flags.hex)
+ mcview_ascii_moveto_eol (view);
+ else
+ {
+ off_t filesize;
+
+ bol = mcview_offset_rounddown (view->hex_cursor, view->bytes_per_line);
+
+ if (mcview_get_byte_indexed (view, bol, view->bytes_per_line - 1, NULL))
+ view->hex_cursor = bol + view->bytes_per_line - 1;
+ else
+ {
+ filesize = mcview_get_filesize (view);
+ view->hex_cursor = DOZ (filesize, 1);
+ }
+ }
+
+ mcview_movement_fixups (view, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_moveto_offset (WView * view, off_t offset)
+{
+ if (view->mode_flags.hex)
+ {
+ view->hex_cursor = offset;
+ view->dpy_start = offset - offset % view->bytes_per_line;
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+ else
+ {
+ view->dpy_start = offset;
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+
+ mcview_movement_fixups (view, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_moveto (WView * view, off_t line, off_t col)
+{
+ off_t offset;
+
+ mcview_coord_to_offset (view, &offset, line, col);
+ mcview_moveto_offset (view, offset);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_coord_to_offset (WView * view, off_t * ret_offset, off_t line, off_t column)
+{
+ coord_cache_entry_t coord;
+
+ coord.cc_line = line;
+ coord.cc_column = column;
+ coord.cc_nroff_column = column;
+ mcview_ccache_lookup (view, &coord, CCACHE_OFFSET);
+ *ret_offset = coord.cc_offset;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_offset_to_coord (WView * view, off_t * ret_line, off_t * ret_column, off_t offset)
+{
+ coord_cache_entry_t coord;
+
+ coord.cc_offset = offset;
+ mcview_ccache_lookup (view, &coord, CCACHE_LINECOL);
+
+ *ret_line = coord.cc_line;
+ *ret_column = view->mode_flags.nroff ? coord.cc_nroff_column : coord.cc_column;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_place_cursor (WView * view)
+{
+ const WRect *r = &view->data_area;
+ int col = view->cursor_col;
+
+ if (!view->hexview_in_text && view->hexedit_lownibble)
+ col++;
+
+ widget_gotoyx (view, r->y + view->cursor_row, r->x + col);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** we have set view->search_start and view->search_end and must set
+ * view->dpy_text_column and view->dpy_start
+ * try to display maximum of match */
+
+void
+mcview_moveto_match (WView * view)
+{
+ if (view->mode_flags.hex)
+ {
+ view->hex_cursor = view->search_start;
+ view->hexedit_lownibble = FALSE;
+ view->dpy_start = view->search_start - view->search_start % view->bytes_per_line;
+ view->dpy_end = view->search_end - view->search_end % view->bytes_per_line;
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+ else
+ {
+ view->dpy_start = mcview_bol (view, view->search_start, 0);
+ view->dpy_paragraph_skip_lines = 0;
+ view->dpy_wrap_dirty = TRUE;
+ }
+
+ mcview_scroll_to_cursor (view);
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/nroff.c b/src/viewer/nroff.c
new file mode 100644
index 0000000..14dacd5
--- /dev/null
+++ b/src/viewer/nroff.c
@@ -0,0 +1,287 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Functions for searching in nroff-like view
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ 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 "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#endif
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_nroff_get_char (mcview_nroff_t * nroff, int *ret_val, off_t nroff_index)
+{
+ int c = 0;
+
+#ifdef HAVE_CHARSET
+ if (nroff->view->utf8)
+ {
+ if (!mcview_get_utf (nroff->view, nroff_index, &c, &nroff->char_length))
+ {
+ /* we need got symbol in any case */
+ nroff->char_length = 1;
+ if (!mcview_get_byte (nroff->view, nroff_index, &c) || !g_ascii_isprint (c))
+ return FALSE;
+ }
+ }
+ else
+#endif
+ {
+ nroff->char_length = 1;
+ if (!mcview_get_byte (nroff->view, nroff_index, &c))
+ return FALSE;
+ }
+
+ *ret_val = c;
+
+ return g_unichar_isprint (c);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mcview__get_nroff_real_len (WView * view, off_t start, off_t length)
+{
+ mcview_nroff_t *nroff;
+ int ret = 0;
+ off_t i = 0;
+
+ if (!view->mode_flags.nroff)
+ return 0;
+
+ nroff = mcview_nroff_seq_new_num (view, start);
+ if (nroff == NULL)
+ return 0;
+ while (i < length)
+ {
+ switch (nroff->type)
+ {
+ case NROFF_TYPE_BOLD:
+ ret += 1 + nroff->char_length; /* real char length and 0x8 */
+ break;
+ case NROFF_TYPE_UNDERLINE:
+ ret += 2; /* underline symbol and ox8 */
+ break;
+ default:
+ break;
+ }
+ i += nroff->char_length;
+ mcview_nroff_seq_next (nroff);
+ }
+
+ mcview_nroff_seq_free (&nroff);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+mcview_nroff_t *
+mcview_nroff_seq_new_num (WView * view, off_t lc_index)
+{
+ mcview_nroff_t *nroff;
+
+ nroff = g_try_malloc0 (sizeof (mcview_nroff_t));
+ if (nroff != NULL)
+ {
+ nroff->index = lc_index;
+ nroff->view = view;
+ mcview_nroff_seq_info (nroff);
+ }
+ return nroff;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+mcview_nroff_t *
+mcview_nroff_seq_new (WView * view)
+{
+ return mcview_nroff_seq_new_num (view, (off_t) 0);
+
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_nroff_seq_free (mcview_nroff_t ** nroff)
+{
+ if (nroff == NULL || *nroff == NULL)
+ return;
+ MC_PTR_FREE (*nroff);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+nroff_type_t
+mcview_nroff_seq_info (mcview_nroff_t * nroff)
+{
+ int next, next2;
+
+ if (nroff == NULL)
+ return NROFF_TYPE_NONE;
+ nroff->type = NROFF_TYPE_NONE;
+
+ if (!mcview_nroff_get_char (nroff, &nroff->current_char, nroff->index))
+ return nroff->type;
+
+ if (!mcview_get_byte (nroff->view, nroff->index + nroff->char_length, &next) || next != '\b')
+ return nroff->type;
+
+ if (!mcview_nroff_get_char (nroff, &next2, nroff->index + 1 + nroff->char_length))
+ return nroff->type;
+
+ if (nroff->current_char == '_' && next2 == '_')
+ {
+ nroff->type = (nroff->prev_type == NROFF_TYPE_BOLD)
+ ? NROFF_TYPE_BOLD : NROFF_TYPE_UNDERLINE;
+
+ }
+ else if (nroff->current_char == next2)
+ {
+ nroff->type = NROFF_TYPE_BOLD;
+ }
+ else if (nroff->current_char == '_')
+ {
+ nroff->current_char = next2;
+ nroff->type = NROFF_TYPE_UNDERLINE;
+ }
+ else if (nroff->current_char == '+' && next2 == 'o')
+ {
+ /* ??? */
+ }
+ return nroff->type;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mcview_nroff_seq_next (mcview_nroff_t * nroff)
+{
+ if (nroff == NULL)
+ return -1;
+
+ nroff->prev_type = nroff->type;
+
+ switch (nroff->type)
+ {
+ case NROFF_TYPE_BOLD:
+ nroff->index += 1 + nroff->char_length;
+ break;
+ case NROFF_TYPE_UNDERLINE:
+ nroff->index += 2;
+ break;
+ default:
+ break;
+ }
+
+ nroff->index += nroff->char_length;
+
+ mcview_nroff_seq_info (nroff);
+ return nroff->current_char;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+mcview_nroff_seq_prev (mcview_nroff_t * nroff)
+{
+ int prev;
+ off_t prev_index, prev_index2;
+
+ if (nroff == NULL)
+ return -1;
+
+ nroff->prev_type = NROFF_TYPE_NONE;
+
+ if (nroff->index == 0)
+ return -1;
+
+ prev_index = nroff->index - 1;
+
+ while (prev_index != 0)
+ {
+ if (mcview_nroff_get_char (nroff, &nroff->current_char, prev_index))
+ break;
+ prev_index--;
+ }
+ if (prev_index == 0)
+ {
+ nroff->index--;
+ mcview_nroff_seq_info (nroff);
+ return nroff->current_char;
+ }
+
+ prev_index--;
+
+ if (!mcview_get_byte (nroff->view, prev_index, &prev) || prev != '\b')
+ {
+ nroff->index = prev_index;
+ mcview_nroff_seq_info (nroff);
+ return nroff->current_char;
+ }
+ prev_index2 = prev_index - 1;
+
+ while (prev_index2 != 0)
+ {
+ if (mcview_nroff_get_char (nroff, &prev, prev_index))
+ break;
+ prev_index2--;
+ }
+
+ nroff->index = (prev_index2 == 0) ? prev_index : prev_index2;
+ mcview_nroff_seq_info (nroff);
+ return nroff->current_char;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/viewer/search.c b/src/viewer/search.c
new file mode 100644
index 0000000..f470a36
--- /dev/null
+++ b/src/viewer/search.c
@@ -0,0 +1,491 @@
+/*
+ Internal file viewer for the Midnight Commander
+ Function for search data
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1998
+ Janne Kukonlehto, 1994, 1995
+ Jakub Jelinek, 1995
+ Joseph M. Hinkle, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Roland Illig <roland.illig@gmx.de>, 2004, 2005
+ Slava Zanko <slavazanko@google.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+
+ 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 "lib/global.h"
+#include "lib/strutil.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h" /* cp_source */
+#endif
+#include "lib/widget.h"
+
+#include "src/setup.h"
+
+#include "internal.h"
+
+/*** global variables ****************************************************************************/
+
+mcview_search_options_t mcview_search_options = {
+ .type = MC_SEARCH_T_NORMAL,
+ .case_sens = FALSE,
+ .backwards = FALSE,
+ .whole_words = FALSE,
+ .all_codepages = FALSE
+};
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ simple_status_msg_t status_msg; /* base class */
+
+ gboolean first;
+ WView *view;
+ off_t offset;
+} mcview_search_status_msg_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static int search_cb_char_curr_index = -1;
+static char search_cb_char_buffer[6];
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+mcview_search_status_update_cb (status_msg_t * sm)
+{
+ simple_status_msg_t *ssm = SIMPLE_STATUS_MSG (sm);
+ mcview_search_status_msg_t *vsm = (mcview_search_status_msg_t *) sm;
+ Widget *wd = WIDGET (sm->dlg);
+ int percent = -1;
+
+ if (verbose)
+ percent = mcview_calc_percent (vsm->view, vsm->offset);
+
+ if (percent >= 0)
+ label_set_textv (ssm->label, _("Searching %s: %3d%%"), vsm->view->last_search_string,
+ percent);
+ else
+ label_set_textv (ssm->label, _("Searching %s"), vsm->view->last_search_string);
+
+ if (vsm->first)
+ {
+ Widget *lw = WIDGET (ssm->label);
+ WRect r;
+
+ r = wd->rect;
+ r.cols = MAX (r.cols, lw->rect.cols + 6);
+ widget_set_size_rect (wd, &r);
+ r = lw->rect;
+ r.x = wd->rect.x + (wd->rect.cols - r.cols) / 2;
+ widget_set_size_rect (lw, &r);
+ vsm->first = FALSE;
+ }
+
+ return status_msg_common_update (sm);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_search_update_steps (WView * view)
+{
+ off_t filesize;
+
+ filesize = mcview_get_filesize (view);
+
+ if (filesize != 0)
+ view->update_steps = filesize / 100;
+ else /* viewing a data stream, not a file */
+ view->update_steps = 40000;
+
+ /* Do not update the percent display but every 20 kb */
+ if (view->update_steps < 20000)
+ view->update_steps = 20000;
+
+ /* Make interrupt more responsive */
+ if (view->update_steps > 40000)
+ view->update_steps = 40000;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+mcview_find (mcview_search_status_msg_t * ssm, off_t search_start, off_t search_end, gsize * len)
+{
+ WView *view = ssm->view;
+
+ view->search_numNeedSkipChar = 0;
+ search_cb_char_curr_index = -1;
+
+ if (mcview_search_options.backwards)
+ {
+ search_end = mcview_get_filesize (view);
+ while (search_start >= 0)
+ {
+ gboolean ok;
+
+ view->search_nroff_seq->index = search_start;
+ mcview_nroff_seq_info (view->search_nroff_seq);
+
+ if (search_end > search_start + (off_t) view->search->original.str->len
+ && mc_search_is_fixed_search_str (view->search))
+ search_end = search_start + view->search->original.str->len;
+
+ ok = mc_search_run (view->search, (void *) ssm, search_start, search_end, len);
+ if (ok && view->search->normal_offset == search_start)
+ {
+ if (view->mode_flags.nroff)
+ view->search->normal_offset++;
+ return TRUE;
+ }
+
+ /* We abort the search in case of a pattern error, or if the user aborts
+ the search. In other words: in all cases except "string not found". */
+ if (!ok && view->search->error != MC_SEARCH_E_NOTFOUND)
+ return FALSE;
+
+ search_start--;
+ }
+
+ mc_search_set_error (view->search, MC_SEARCH_E_NOTFOUND, "%s", _(STR_E_NOTFOUND));
+ return FALSE;
+ }
+ view->search_nroff_seq->index = search_start;
+ mcview_nroff_seq_info (view->search_nroff_seq);
+
+ return mc_search_run (view->search, (void *) ssm, search_start, search_end, len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mcview_search_show_result (WView * view, size_t match_len)
+{
+ int nroff_len;
+
+ nroff_len =
+ view->mode_flags.nroff
+ ? mcview__get_nroff_real_len (view, view->search->start_buffer,
+ view->search->normal_offset - view->search->start_buffer) : 0;
+ view->search_start = view->search->normal_offset + nroff_len;
+
+ if (!view->mode_flags.hex)
+ view->search_start++;
+
+ nroff_len =
+ view->mode_flags.nroff ? mcview__get_nroff_real_len (view, view->search_start - 1,
+ match_len) : 0;
+ view->search_end = view->search_start + match_len + nroff_len;
+
+ mcview_moveto_match (view);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mcview_search_init (WView * view)
+{
+#ifdef HAVE_CHARSET
+ view->search = mc_search_new (view->last_search_string, cp_source);
+#else
+ view->search = mc_search_new (view->last_search_string, NULL);
+#endif
+
+ view->search_nroff_seq = mcview_nroff_seq_new (view);
+
+ if (view->search == NULL)
+ return FALSE;
+
+ view->search->search_type = mcview_search_options.type;
+#ifdef HAVE_CHARSET
+ view->search->is_all_charsets = mcview_search_options.all_codepages;
+#endif
+ view->search->is_case_sensitive = mcview_search_options.case_sens;
+ view->search->whole_words = mcview_search_options.whole_words;
+ view->search->search_fn = mcview_search_cmd_callback;
+ view->search->update_fn = mcview_search_update_cmd_callback;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_search_deinit (WView * view)
+{
+ mc_search_free (view->search);
+ g_free (view->last_search_string);
+ mcview_nroff_seq_free (&view->search_nroff_seq);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+mc_search_cbret_t
+mcview_search_cmd_callback (const void *user_data, gsize char_offset, int *current_char)
+{
+ WView *view = ((const mcview_search_status_msg_t *) user_data)->view;
+
+ /* view_read_continue (view, &view->search_onechar_info); *//* AB:FIXME */
+ if (!view->mode_flags.nroff)
+ {
+ mcview_get_byte (view, char_offset, current_char);
+ return MC_SEARCH_CB_OK;
+ }
+
+ if (view->search_numNeedSkipChar != 0)
+ {
+ view->search_numNeedSkipChar--;
+ return MC_SEARCH_CB_SKIP;
+ }
+
+ if (search_cb_char_curr_index == -1
+ || search_cb_char_curr_index >= view->search_nroff_seq->char_length)
+ {
+ if (search_cb_char_curr_index != -1)
+ mcview_nroff_seq_next (view->search_nroff_seq);
+
+ search_cb_char_curr_index = 0;
+ if (view->search_nroff_seq->char_length > 1)
+ g_unichar_to_utf8 (view->search_nroff_seq->current_char, search_cb_char_buffer);
+ else
+ search_cb_char_buffer[0] = (char) view->search_nroff_seq->current_char;
+
+ if (view->search_nroff_seq->type != NROFF_TYPE_NONE)
+ {
+ switch (view->search_nroff_seq->type)
+ {
+ case NROFF_TYPE_BOLD:
+ view->search_numNeedSkipChar = 1 + view->search_nroff_seq->char_length; /* real char length and 0x8 */
+ break;
+ case NROFF_TYPE_UNDERLINE:
+ view->search_numNeedSkipChar = 2; /* underline symbol and ox8 */
+ break;
+ default:
+ break;
+ }
+ }
+ return MC_SEARCH_CB_INVALID;
+ }
+
+ *current_char = search_cb_char_buffer[search_cb_char_curr_index];
+ search_cb_char_curr_index++;
+
+ return (*current_char != -1) ? MC_SEARCH_CB_OK : MC_SEARCH_CB_INVALID;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+mc_search_cbret_t
+mcview_search_update_cmd_callback (const void *user_data, gsize char_offset)
+{
+ status_msg_t *sm = STATUS_MSG (user_data);
+ mcview_search_status_msg_t *vsm = (mcview_search_status_msg_t *) user_data;
+ WView *view = vsm->view;
+ gboolean do_update = FALSE;
+ mc_search_cbret_t result = MC_SEARCH_CB_OK;
+
+ vsm->offset = (off_t) char_offset;
+
+ if (mcview_search_options.backwards)
+ {
+ if (vsm->offset <= view->update_activate)
+ {
+ view->update_activate -= view->update_steps;
+
+ do_update = TRUE;
+ }
+ }
+ else
+ {
+ if (vsm->offset >= view->update_activate)
+ {
+ view->update_activate += view->update_steps;
+
+ do_update = TRUE;
+ }
+ }
+
+ if (do_update && sm->update (sm) == B_CANCEL)
+ result = MC_SEARCH_CB_ABORT;
+
+ /* may be in future return from this callback will change current position in searching block. */
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mcview_do_search (WView * view, off_t want_search_start)
+{
+ mcview_search_status_msg_t vsm;
+
+ off_t search_start = 0;
+ off_t orig_search_start = view->search_start;
+ gboolean found = FALSE;
+
+ size_t match_len;
+
+ view->search_start = want_search_start;
+ /* for avoid infinite search loop we need to increase or decrease start offset of search */
+
+ if (view->search_start != 0)
+ {
+ if (!view->mode_flags.nroff)
+ search_start = view->search_start + (mcview_search_options.backwards ? -2 : 0);
+ else
+ {
+ if (mcview_search_options.backwards)
+ {
+ mcview_nroff_t *nroff;
+
+ nroff = mcview_nroff_seq_new_num (view, view->search_start);
+ if (mcview_nroff_seq_prev (nroff) != -1)
+ search_start =
+ -(mcview__get_nroff_real_len (view, nroff->index - 1, 2) +
+ nroff->char_length + 1);
+ else
+ search_start = -2;
+
+ mcview_nroff_seq_free (&nroff);
+ }
+ else
+ {
+ search_start = mcview__get_nroff_real_len (view, view->search_start + 1, 2);
+ }
+ search_start += view->search_start;
+ }
+ }
+
+ if (mcview_search_options.backwards && search_start < 0)
+ search_start = 0;
+
+ /* Compute the percent steps */
+ mcview_search_update_steps (view);
+
+ view->update_activate = search_start;
+
+ vsm.first = TRUE;
+ vsm.view = view;
+ vsm.offset = search_start;
+
+ status_msg_init (STATUS_MSG (&vsm), _("Search"), 1.0, simple_status_msg_init_cb,
+ mcview_search_status_update_cb, NULL);
+
+ do
+ {
+ off_t growbufsize;
+
+ if (view->growbuf_in_use)
+ growbufsize = mcview_growbuf_filesize (view);
+ else
+ growbufsize = view->search->original.str->len;
+
+ if (mcview_find (&vsm, search_start, mcview_get_filesize (view), &match_len))
+ {
+ mcview_search_show_result (view, match_len);
+ found = TRUE;
+ break;
+ }
+
+ /* Search error is here.
+ * MC_SEARCH_E_NOTFOUND: continue search
+ * others: stop
+ */
+ if (view->search->error != MC_SEARCH_E_NOTFOUND)
+ break;
+
+ search_start = growbufsize - view->search->original.str->len;
+ }
+ while (search_start > 0 && mcview_may_still_grow (view));
+
+ /* After mcview_may_still_grow (view) == FALSE we have remained last chunk. Search there. */
+ if (view->growbuf_in_use && !found && view->search->error == MC_SEARCH_E_NOTFOUND
+ && !mcview_search_options.backwards
+ && mcview_find (&vsm, search_start, mcview_get_filesize (view), &match_len))
+ {
+ mcview_search_show_result (view, match_len);
+ found = TRUE;
+ }
+
+ status_msg_deinit (STATUS_MSG (&vsm));
+
+ if (orig_search_start != 0 && (!found && view->search->error == MC_SEARCH_E_NOTFOUND)
+ && !mcview_search_options.backwards)
+ {
+ view->search_start = orig_search_start;
+ mcview_update (view);
+
+ if (query_dialog
+ (_("Search done"), _("Continue from beginning?"), D_NORMAL, 2, _("&Yes"),
+ _("&No")) != 0)
+ found = TRUE;
+ else
+ {
+ /* continue search from beginning */
+ view->update_activate = 0;
+
+ vsm.first = TRUE;
+ vsm.view = view;
+ vsm.offset = 0;
+
+ status_msg_init (STATUS_MSG (&vsm), _("Search"), 1.0, simple_status_msg_init_cb,
+ mcview_search_status_update_cb, NULL);
+
+ /* search from file begin up to initial search start position */
+ if (mcview_find (&vsm, 0, orig_search_start, &match_len))
+ {
+ mcview_search_show_result (view, match_len);
+ found = TRUE;
+ }
+
+ status_msg_deinit (STATUS_MSG (&vsm));
+ }
+ }
+
+ if (!found)
+ {
+ view->search_start = orig_search_start;
+ mcview_update (view);
+
+ if (view->search->error == MC_SEARCH_E_NOTFOUND)
+ query_dialog (_("Search"), _(STR_E_NOTFOUND), D_NORMAL, 1, _("&Dismiss"));
+ else if (view->search->error_str != NULL)
+ query_dialog (_("Search"), view->search->error_str, D_NORMAL, 1, _("&Dismiss"));
+ }
+ view->dirty++;
+}
+
+/* --------------------------------------------------------------------------------------------- */