From ffccd5b2b05243e7976db80f90f453dccfae9886 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 22:22:03 +0200 Subject: Adding upstream version 3:4.8.30. Signed-off-by: Daniel Baumann --- src/Makefile.am | 128 + src/Makefile.in | 1047 +++++++ src/args.c | 845 ++++++ src/args.h | 55 + src/background.c | 657 +++++ src/background.h | 54 + src/clipboard.c | 268 ++ src/clipboard.h | 33 + src/cons.handler.c | 502 ++++ src/consaver/Makefile.am | 7 + src/consaver/Makefile.in | 777 ++++++ src/consaver/cons.saver.c | 289 ++ src/consaver/cons.saver.h | 46 + src/diffviewer/Makefile.am | 8 + src/diffviewer/Makefile.in | 740 +++++ src/diffviewer/internal.h | 153 + src/diffviewer/search.c | 288 ++ src/diffviewer/ydiff.c | 3648 ++++++++++++++++++++++++ src/diffviewer/ydiff.h | 16 + src/editor/Makefile.am | 33 + src/editor/Makefile.in | 801 ++++++ src/editor/bookmark.c | 349 +++ src/editor/edit-impl.h | 278 ++ src/editor/edit.c | 4067 +++++++++++++++++++++++++++ src/editor/edit.h | 84 + src/editor/editbuffer.c | 900 ++++++ src/editor/editbuffer.h | 117 + src/editor/editcmd.c | 2108 ++++++++++++++ src/editor/editcomplete.c | 483 ++++ src/editor/editcomplete.h | 21 + src/editor/editdraw.c | 1122 ++++++++ src/editor/editmacros.c | 437 +++ src/editor/editmacros.h | 24 + src/editor/editmenu.c | 338 +++ src/editor/editoptions.c | 241 ++ src/editor/editsearch.c | 1042 +++++++ src/editor/editsearch.h | 36 + src/editor/editwidget.c | 1550 ++++++++++ src/editor/editwidget.h | 173 ++ src/editor/etags.c | 468 ++++ src/editor/etags.h | 26 + src/editor/format.c | 538 ++++ src/editor/spell.c | 834 ++++++ src/editor/spell.h | 36 + src/editor/syntax.c | 1606 +++++++++++ src/events_init.c | 86 + src/events_init.h | 19 + src/execute.c | 670 +++++ src/execute.h | 51 + src/file_history.c | 246 ++ src/file_history.h | 20 + src/filemanager/Makefile.am | 40 + src/filemanager/Makefile.in | 839 ++++++ src/filemanager/achown.c | 1107 ++++++++ src/filemanager/boxes.c | 1343 +++++++++ src/filemanager/boxes.h | 37 + src/filemanager/cd.c | 310 ++ src/filemanager/cd.h | 23 + src/filemanager/chattr.c | 1358 +++++++++ src/filemanager/chmod.c | 664 +++++ src/filemanager/chown.c | 552 ++++ src/filemanager/cmd.c | 1470 ++++++++++ src/filemanager/cmd.h | 172 ++ src/filemanager/command.c | 255 ++ src/filemanager/command.h | 27 + src/filemanager/dir.c | 839 ++++++ src/filemanager/dir.h | 115 + src/filemanager/ext.c | 1089 ++++++++ src/filemanager/ext.h | 33 + src/filemanager/file.c | 3562 +++++++++++++++++++++++ src/filemanager/file.h | 72 + src/filemanager/filegui.c | 1498 ++++++++++ src/filemanager/filegui.h | 40 + src/filemanager/filemanager.c | 1852 ++++++++++++ src/filemanager/filemanager.h | 53 + src/filemanager/filenot.c | 150 + src/filemanager/filenot.h | 26 + src/filemanager/fileopctx.c | 128 + src/filemanager/fileopctx.h | 198 ++ src/filemanager/find.c | 1968 +++++++++++++ src/filemanager/hotlist.c | 1733 ++++++++++++ src/filemanager/hotlist.h | 33 + src/filemanager/info.c | 377 +++ src/filemanager/info.h | 24 + src/filemanager/ioblksize.h | 86 + src/filemanager/layout.c | 1580 +++++++++++ src/filemanager/layout.h | 98 + src/filemanager/mountlist.c | 1575 +++++++++++ src/filemanager/mountlist.h | 44 + src/filemanager/panel.c | 5428 ++++++++++++++++++++++++++++++++++++ src/filemanager/panel.h | 285 ++ src/filemanager/panelize.c | 573 ++++ src/filemanager/panelize.h | 25 + src/filemanager/tree.c | 1345 +++++++++ src/filemanager/tree.h | 35 + src/filemanager/treestore.c | 941 +++++++ src/filemanager/treestore.h | 63 + src/help.c | 1181 ++++++++ src/help.h | 57 + src/history.h | 61 + src/keymap.c | 985 +++++++ src/keymap.h | 64 + src/learn.c | 424 +++ src/learn.h | 21 + src/main.c | 556 ++++ src/man2hlp/Makefile.am | 1 + src/man2hlp/Makefile.in | 584 ++++ src/man2hlp/man2hlp.in | 987 +++++++ src/selcodepage.c | 178 ++ src/selcodepage.h | 39 + src/setup.c | 1239 ++++++++ src/setup.h | 162 ++ src/subshell/Makefile.am | 9 + src/subshell/Makefile.in | 741 +++++ src/subshell/common.c | 1863 +++++++++++++ src/subshell/internal.h | 29 + src/subshell/proxyfunc.c | 113 + src/subshell/subshell.h | 55 + src/textconf.c | 264 ++ src/textconf.h | 23 + src/usermenu.c | 1176 ++++++++ src/usermenu.h | 29 + src/util.c | 75 + src/util.h | 19 + src/vfs/Makefile.am | 47 + src/vfs/Makefile.in | 875 ++++++ src/vfs/cpio/Makefile.am | 7 + src/vfs/cpio/Makefile.in | 735 +++++ src/vfs/cpio/cpio.c | 905 ++++++ src/vfs/cpio/cpio.h | 18 + src/vfs/extfs/Makefile.am | 12 + src/vfs/extfs/Makefile.in | 856 ++++++ src/vfs/extfs/extfs.c | 1722 ++++++++++++ src/vfs/extfs/extfs.h | 18 + src/vfs/extfs/helpers/Makefile.am | 76 + src/vfs/extfs/helpers/Makefile.in | 812 ++++++ src/vfs/extfs/helpers/README | 200 ++ src/vfs/extfs/helpers/README.extfs | 78 + src/vfs/extfs/helpers/a+.in | 126 + src/vfs/extfs/helpers/apt+.in | 359 +++ src/vfs/extfs/helpers/audio.in | 53 + src/vfs/extfs/helpers/bpp | 50 + src/vfs/extfs/helpers/changesetfs | 109 + src/vfs/extfs/helpers/deb.in | 203 ++ src/vfs/extfs/helpers/deba.in | 107 + src/vfs/extfs/helpers/debd.in | 362 +++ src/vfs/extfs/helpers/dpkg+.in | 337 +++ src/vfs/extfs/helpers/gitfs+ | 39 + src/vfs/extfs/helpers/hp48+.in | 132 + src/vfs/extfs/helpers/iso9660.in | 235 ++ src/vfs/extfs/helpers/lslR.in | 74 + src/vfs/extfs/helpers/mailfs.in | 219 ++ src/vfs/extfs/helpers/patchfs.in | 427 +++ src/vfs/extfs/helpers/patchsetfs | 104 + src/vfs/extfs/helpers/rpm | 349 +++ src/vfs/extfs/helpers/rpms+.in | 66 + src/vfs/extfs/helpers/s3+.in | 490 ++++ src/vfs/extfs/helpers/trpm | 176 ++ src/vfs/extfs/helpers/u7z | 135 + src/vfs/extfs/helpers/uace.in | 67 + src/vfs/extfs/helpers/ualz.in | 68 + src/vfs/extfs/helpers/uar.in | 60 + src/vfs/extfs/helpers/uarc.in | 92 + src/vfs/extfs/helpers/uarj.in | 75 + src/vfs/extfs/helpers/uc1541 | 702 +++++ src/vfs/extfs/helpers/ucab.in | 40 + src/vfs/extfs/helpers/uha.in | 52 + src/vfs/extfs/helpers/ulha.in | 142 + src/vfs/extfs/helpers/ulib.in | 146 + src/vfs/extfs/helpers/unar.in | 59 + src/vfs/extfs/helpers/urar.in | 180 ++ src/vfs/extfs/helpers/uwim.in | 208 ++ src/vfs/extfs/helpers/uzip.in | 483 ++++ src/vfs/extfs/helpers/uzoo.in | 69 + src/vfs/fish/Makefile.am | 13 + src/vfs/fish/Makefile.in | 857 ++++++ src/vfs/fish/fish.c | 1805 ++++++++++++ src/vfs/fish/fish.h | 28 + src/vfs/fish/fishdef.h | 236 ++ src/vfs/fish/helpers/Makefile.am | 10 + src/vfs/fish/helpers/Makefile.in | 642 +++++ src/vfs/fish/helpers/README.fish | 217 ++ src/vfs/fish/helpers/append | 16 + src/vfs/fish/helpers/chmod | 6 + src/vfs/fish/helpers/chown | 6 + src/vfs/fish/helpers/fexists | 3 + src/vfs/fish/helpers/get | 105 + src/vfs/fish/helpers/hardlink | 8 + src/vfs/fish/helpers/info | 44 + src/vfs/fish/helpers/ln | 8 + src/vfs/fish/helpers/ls | 170 ++ src/vfs/fish/helpers/mkdir | 6 + src/vfs/fish/helpers/mv | 6 + src/vfs/fish/helpers/rmdir | 6 + src/vfs/fish/helpers/send | 17 + src/vfs/fish/helpers/unlink | 6 + src/vfs/fish/helpers/utime | 13 + src/vfs/ftpfs/Makefile.am | 8 + src/vfs/ftpfs/Makefile.in | 740 +++++ src/vfs/ftpfs/ftpfs.c | 2784 ++++++++++++++++++ src/vfs/ftpfs/ftpfs.h | 46 + src/vfs/ftpfs/ftpfs_parse_ls.c | 1236 ++++++++ src/vfs/local/Makefile.am | 7 + src/vfs/local/Makefile.in | 735 +++++ src/vfs/local/local.c | 523 ++++ src/vfs/local/local.h | 32 + src/vfs/plugins_init.c | 123 + src/vfs/plugins_init.h | 18 + src/vfs/sfs/Makefile.am | 16 + src/vfs/sfs/Makefile.in | 794 ++++++ src/vfs/sfs/sfs.c | 604 ++++ src/vfs/sfs/sfs.h | 18 + src/vfs/sfs/sfs.ini | 34 + src/vfs/sftpfs/Makefile.am | 12 + src/vfs/sftpfs/Makefile.in | 759 +++++ src/vfs/sftpfs/config_parser.c | 427 +++ src/vfs/sftpfs/connection.c | 970 +++++++ src/vfs/sftpfs/dir.c | 230 ++ src/vfs/sftpfs/file.c | 424 +++ src/vfs/sftpfs/internal.c | 621 +++++ src/vfs/sftpfs/internal.h | 114 + src/vfs/sftpfs/sftpfs.c | 866 ++++++ src/vfs/sftpfs/sftpfs.h | 23 + src/vfs/tar/Makefile.am | 10 + src/vfs/tar/Makefile.in | 750 +++++ src/vfs/tar/tar-internal.c | 482 ++++ src/vfs/tar/tar-internal.h | 351 +++ src/vfs/tar/tar-sparse.c | 777 ++++++ src/vfs/tar/tar-xheader.c | 1051 +++++++ src/vfs/tar/tar.c | 1302 +++++++++ src/vfs/tar/tar.h | 18 + src/vfs/undelfs/Makefile.am | 7 + src/vfs/undelfs/Makefile.in | 735 +++++ src/vfs/undelfs/undelfs.c | 844 ++++++ src/vfs/undelfs/undelfs.h | 18 + src/viewer/Makefile.am | 21 + src/viewer/Makefile.in | 793 ++++++ src/viewer/actions_cmd.c | 790 ++++++ src/viewer/ascii.c | 1051 +++++++ src/viewer/coord_cache.c | 395 +++ src/viewer/datasource.c | 434 +++ src/viewer/dialogs.c | 263 ++ src/viewer/display.c | 404 +++ src/viewer/growbuf.c | 297 ++ src/viewer/hex.c | 484 ++++ src/viewer/internal.h | 472 ++++ src/viewer/lib.c | 437 +++ src/viewer/mcviewer.c | 469 ++++ src/viewer/mcviewer.h | 57 + src/viewer/move.c | 415 +++ src/viewer/nroff.c | 287 ++ src/viewer/search.c | 491 ++++ 252 files changed, 116618 insertions(+) create mode 100644 src/Makefile.am create mode 100644 src/Makefile.in create mode 100644 src/args.c create mode 100644 src/args.h create mode 100644 src/background.c create mode 100644 src/background.h create mode 100644 src/clipboard.c create mode 100644 src/clipboard.h create mode 100644 src/cons.handler.c create mode 100644 src/consaver/Makefile.am create mode 100644 src/consaver/Makefile.in create mode 100644 src/consaver/cons.saver.c create mode 100644 src/consaver/cons.saver.h create mode 100644 src/diffviewer/Makefile.am create mode 100644 src/diffviewer/Makefile.in create mode 100644 src/diffviewer/internal.h create mode 100644 src/diffviewer/search.c create mode 100644 src/diffviewer/ydiff.c create mode 100644 src/diffviewer/ydiff.h create mode 100644 src/editor/Makefile.am create mode 100644 src/editor/Makefile.in create mode 100644 src/editor/bookmark.c create mode 100644 src/editor/edit-impl.h create mode 100644 src/editor/edit.c create mode 100644 src/editor/edit.h create mode 100644 src/editor/editbuffer.c create mode 100644 src/editor/editbuffer.h create mode 100644 src/editor/editcmd.c create mode 100644 src/editor/editcomplete.c create mode 100644 src/editor/editcomplete.h create mode 100644 src/editor/editdraw.c create mode 100644 src/editor/editmacros.c create mode 100644 src/editor/editmacros.h create mode 100644 src/editor/editmenu.c create mode 100644 src/editor/editoptions.c create mode 100644 src/editor/editsearch.c create mode 100644 src/editor/editsearch.h create mode 100644 src/editor/editwidget.c create mode 100644 src/editor/editwidget.h create mode 100644 src/editor/etags.c create mode 100644 src/editor/etags.h create mode 100644 src/editor/format.c create mode 100644 src/editor/spell.c create mode 100644 src/editor/spell.h create mode 100644 src/editor/syntax.c create mode 100644 src/events_init.c create mode 100644 src/events_init.h create mode 100644 src/execute.c create mode 100644 src/execute.h create mode 100644 src/file_history.c create mode 100644 src/file_history.h create mode 100644 src/filemanager/Makefile.am create mode 100644 src/filemanager/Makefile.in create mode 100644 src/filemanager/achown.c create mode 100644 src/filemanager/boxes.c create mode 100644 src/filemanager/boxes.h create mode 100644 src/filemanager/cd.c create mode 100644 src/filemanager/cd.h create mode 100644 src/filemanager/chattr.c create mode 100644 src/filemanager/chmod.c create mode 100644 src/filemanager/chown.c create mode 100644 src/filemanager/cmd.c create mode 100644 src/filemanager/cmd.h create mode 100644 src/filemanager/command.c create mode 100644 src/filemanager/command.h create mode 100644 src/filemanager/dir.c create mode 100644 src/filemanager/dir.h create mode 100644 src/filemanager/ext.c create mode 100644 src/filemanager/ext.h create mode 100644 src/filemanager/file.c create mode 100644 src/filemanager/file.h create mode 100644 src/filemanager/filegui.c create mode 100644 src/filemanager/filegui.h create mode 100644 src/filemanager/filemanager.c create mode 100644 src/filemanager/filemanager.h create mode 100644 src/filemanager/filenot.c create mode 100644 src/filemanager/filenot.h create mode 100644 src/filemanager/fileopctx.c create mode 100644 src/filemanager/fileopctx.h create mode 100644 src/filemanager/find.c create mode 100644 src/filemanager/hotlist.c create mode 100644 src/filemanager/hotlist.h create mode 100644 src/filemanager/info.c create mode 100644 src/filemanager/info.h create mode 100644 src/filemanager/ioblksize.h create mode 100644 src/filemanager/layout.c create mode 100644 src/filemanager/layout.h create mode 100644 src/filemanager/mountlist.c create mode 100644 src/filemanager/mountlist.h create mode 100644 src/filemanager/panel.c create mode 100644 src/filemanager/panel.h create mode 100644 src/filemanager/panelize.c create mode 100644 src/filemanager/panelize.h create mode 100644 src/filemanager/tree.c create mode 100644 src/filemanager/tree.h create mode 100644 src/filemanager/treestore.c create mode 100644 src/filemanager/treestore.h create mode 100644 src/help.c create mode 100644 src/help.h create mode 100644 src/history.h create mode 100644 src/keymap.c create mode 100644 src/keymap.h create mode 100644 src/learn.c create mode 100644 src/learn.h create mode 100644 src/main.c create mode 100644 src/man2hlp/Makefile.am create mode 100644 src/man2hlp/Makefile.in create mode 100644 src/man2hlp/man2hlp.in create mode 100644 src/selcodepage.c create mode 100644 src/selcodepage.h create mode 100644 src/setup.c create mode 100644 src/setup.h create mode 100644 src/subshell/Makefile.am create mode 100644 src/subshell/Makefile.in create mode 100644 src/subshell/common.c create mode 100644 src/subshell/internal.h create mode 100644 src/subshell/proxyfunc.c create mode 100644 src/subshell/subshell.h create mode 100644 src/textconf.c create mode 100644 src/textconf.h create mode 100644 src/usermenu.c create mode 100644 src/usermenu.h create mode 100644 src/util.c create mode 100644 src/util.h create mode 100644 src/vfs/Makefile.am create mode 100644 src/vfs/Makefile.in create mode 100644 src/vfs/cpio/Makefile.am create mode 100644 src/vfs/cpio/Makefile.in create mode 100644 src/vfs/cpio/cpio.c create mode 100644 src/vfs/cpio/cpio.h create mode 100644 src/vfs/extfs/Makefile.am create mode 100644 src/vfs/extfs/Makefile.in create mode 100644 src/vfs/extfs/extfs.c create mode 100644 src/vfs/extfs/extfs.h create mode 100644 src/vfs/extfs/helpers/Makefile.am create mode 100644 src/vfs/extfs/helpers/Makefile.in create mode 100644 src/vfs/extfs/helpers/README create mode 100644 src/vfs/extfs/helpers/README.extfs create mode 100644 src/vfs/extfs/helpers/a+.in create mode 100644 src/vfs/extfs/helpers/apt+.in create mode 100755 src/vfs/extfs/helpers/audio.in create mode 100755 src/vfs/extfs/helpers/bpp create mode 100755 src/vfs/extfs/helpers/changesetfs create mode 100644 src/vfs/extfs/helpers/deb.in create mode 100644 src/vfs/extfs/helpers/deba.in create mode 100644 src/vfs/extfs/helpers/debd.in create mode 100644 src/vfs/extfs/helpers/dpkg+.in create mode 100755 src/vfs/extfs/helpers/gitfs+ create mode 100644 src/vfs/extfs/helpers/hp48+.in create mode 100644 src/vfs/extfs/helpers/iso9660.in create mode 100644 src/vfs/extfs/helpers/lslR.in create mode 100644 src/vfs/extfs/helpers/mailfs.in create mode 100644 src/vfs/extfs/helpers/patchfs.in create mode 100755 src/vfs/extfs/helpers/patchsetfs create mode 100755 src/vfs/extfs/helpers/rpm create mode 100644 src/vfs/extfs/helpers/rpms+.in create mode 100644 src/vfs/extfs/helpers/s3+.in create mode 100755 src/vfs/extfs/helpers/trpm create mode 100755 src/vfs/extfs/helpers/u7z create mode 100644 src/vfs/extfs/helpers/uace.in create mode 100644 src/vfs/extfs/helpers/ualz.in create mode 100644 src/vfs/extfs/helpers/uar.in create mode 100644 src/vfs/extfs/helpers/uarc.in create mode 100644 src/vfs/extfs/helpers/uarj.in create mode 100755 src/vfs/extfs/helpers/uc1541 create mode 100644 src/vfs/extfs/helpers/ucab.in create mode 100644 src/vfs/extfs/helpers/uha.in create mode 100644 src/vfs/extfs/helpers/ulha.in create mode 100644 src/vfs/extfs/helpers/ulib.in create mode 100644 src/vfs/extfs/helpers/unar.in create mode 100644 src/vfs/extfs/helpers/urar.in create mode 100644 src/vfs/extfs/helpers/uwim.in create mode 100644 src/vfs/extfs/helpers/uzip.in create mode 100644 src/vfs/extfs/helpers/uzoo.in create mode 100644 src/vfs/fish/Makefile.am create mode 100644 src/vfs/fish/Makefile.in create mode 100644 src/vfs/fish/fish.c create mode 100644 src/vfs/fish/fish.h create mode 100644 src/vfs/fish/fishdef.h create mode 100644 src/vfs/fish/helpers/Makefile.am create mode 100644 src/vfs/fish/helpers/Makefile.in create mode 100644 src/vfs/fish/helpers/README.fish create mode 100644 src/vfs/fish/helpers/append create mode 100644 src/vfs/fish/helpers/chmod create mode 100644 src/vfs/fish/helpers/chown create mode 100644 src/vfs/fish/helpers/fexists create mode 100644 src/vfs/fish/helpers/get create mode 100644 src/vfs/fish/helpers/hardlink create mode 100644 src/vfs/fish/helpers/info create mode 100644 src/vfs/fish/helpers/ln create mode 100644 src/vfs/fish/helpers/ls create mode 100644 src/vfs/fish/helpers/mkdir create mode 100644 src/vfs/fish/helpers/mv create mode 100644 src/vfs/fish/helpers/rmdir create mode 100644 src/vfs/fish/helpers/send create mode 100644 src/vfs/fish/helpers/unlink create mode 100644 src/vfs/fish/helpers/utime create mode 100644 src/vfs/ftpfs/Makefile.am create mode 100644 src/vfs/ftpfs/Makefile.in create mode 100644 src/vfs/ftpfs/ftpfs.c create mode 100644 src/vfs/ftpfs/ftpfs.h create mode 100644 src/vfs/ftpfs/ftpfs_parse_ls.c create mode 100644 src/vfs/local/Makefile.am create mode 100644 src/vfs/local/Makefile.in create mode 100644 src/vfs/local/local.c create mode 100644 src/vfs/local/local.h create mode 100644 src/vfs/plugins_init.c create mode 100644 src/vfs/plugins_init.h create mode 100644 src/vfs/sfs/Makefile.am create mode 100644 src/vfs/sfs/Makefile.in create mode 100644 src/vfs/sfs/sfs.c create mode 100644 src/vfs/sfs/sfs.h create mode 100644 src/vfs/sfs/sfs.ini create mode 100644 src/vfs/sftpfs/Makefile.am create mode 100644 src/vfs/sftpfs/Makefile.in create mode 100644 src/vfs/sftpfs/config_parser.c create mode 100644 src/vfs/sftpfs/connection.c create mode 100644 src/vfs/sftpfs/dir.c create mode 100644 src/vfs/sftpfs/file.c create mode 100644 src/vfs/sftpfs/internal.c create mode 100644 src/vfs/sftpfs/internal.h create mode 100644 src/vfs/sftpfs/sftpfs.c create mode 100644 src/vfs/sftpfs/sftpfs.h create mode 100644 src/vfs/tar/Makefile.am create mode 100644 src/vfs/tar/Makefile.in create mode 100644 src/vfs/tar/tar-internal.c create mode 100644 src/vfs/tar/tar-internal.h create mode 100644 src/vfs/tar/tar-sparse.c create mode 100644 src/vfs/tar/tar-xheader.c create mode 100644 src/vfs/tar/tar.c create mode 100644 src/vfs/tar/tar.h create mode 100644 src/vfs/undelfs/Makefile.am create mode 100644 src/vfs/undelfs/Makefile.in create mode 100644 src/vfs/undelfs/undelfs.c create mode 100644 src/vfs/undelfs/undelfs.h create mode 100644 src/viewer/Makefile.am create mode 100644 src/viewer/Makefile.in create mode 100644 src/viewer/actions_cmd.c create mode 100644 src/viewer/ascii.c create mode 100644 src/viewer/coord_cache.c create mode 100644 src/viewer/datasource.c create mode 100644 src/viewer/dialogs.c create mode 100644 src/viewer/display.c create mode 100644 src/viewer/growbuf.c create mode 100644 src/viewer/hex.c create mode 100644 src/viewer/internal.h create mode 100644 src/viewer/lib.c create mode 100644 src/viewer/mcviewer.c create mode 100644 src/viewer/mcviewer.h create mode 100644 src/viewer/move.c create mode 100644 src/viewer/nroff.c create mode 100644 src/viewer/search.c (limited to 'src') 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 , 2009. + Andrew Borodin , 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 . + */ + +#include +#include +#include + +#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_("") + }, + +#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_("") + }, +#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_("") + }, + + { + /* 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_(" ...") + }, + + 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_("") + }, + + { + "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_("") + }, + + { + "skin", 'S', ARGS_COLOR_OPTIONS, G_OPTION_ARG_STRING, + &mc_global.tty.skin, + N_("Show mc with specified skin"), + N_("") + }, + + 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 . + */ + +/* }}} */ + +/** \file background.c + * \brief Source: Background support + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include /* 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 /* 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 , 2010. + Andrew Borodin , 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 . + */ + +#include + +#include +#include +#include + +#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 . + */ + +/** \file cons.handler.c + * \brief Source: client %interface for General purpose Linux console save/restore server + */ + +#include + +#include +#include +#include +#include +#include +#ifdef __FreeBSD__ +#include +#ifdef HAVE_SYS_IOCTL_H +#include +#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 . + * Support for screenmaps by Max Khon + */ + +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 . + + 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 . + */ + +/* 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 + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include + +#include +#include +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#include + +#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 , 2010. + Andrew Borodin , 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 . + */ + +#include + +#include + +#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 , 2007 + Slava Zanko , 2010, 2013 + Andrew Borodin , 2010-2022 + Ilia Maslakov , 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 . + */ + + +#include + +#include +#include +#include /* ptrdiff_t */ +#include +#include +#include +#include + +#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 . + */ + +/** \file + * \brief Source: editor book mark handling + * \author Paul Sheer + * \date 1996, 1997 + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 + +#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 2009, 2010, 2011 + Andrew Borodin 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 . + */ + +/** \file + * \brief Source: editor low level data handling and cursor fundamentals + * \author Paul Sheer + * \date 1996, 1997 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* UINTMAX_MAX */ +#include + +#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 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 . + */ + +/** \file + * \brief Source: editor text keep buffer. + * \author Andrew Borodin + * \date 2013 + */ + +#include + +#include /* isdigit() */ +#include +#include +#include + +#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 , 2012-2022 + Ilia Maslakov , 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 . + */ + +/** \file + * \brief Source: editor high level editing commands + * \author Paul Sheer + * \date 1996, 1997 + */ + +/* #define PIPE_BLOCKS_SO_READ_BYTE_BY_BYTE */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 file, + b) save to ; + if 1 (safe save) then a) save to , + b) rename to ; + if 2 (do backups) then a) save to , + b) rename to , + c) rename to . */ + +/* 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 -c "), 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 , 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 . + */ + +#include + +#include /* isspace() */ +#include + +#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 2012-2022 + Slava Zanko , 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 . + */ + +/** \file + * \brief Source: editor text drawing + * \author Paul Sheer + * \date 1996, 1997 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 , + * otherwise print the current character as is (if printable), + * as decimal and as hex. + */ + if (edit->buffer.curs1 >= edit->buffer.size) + strcpy (byte_str, " "); +#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 , + * 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 ("[ ]"); +#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 . + */ + +#include + +#include + +#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 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 . + */ + +/** \file + * \brief Source: editor menu definitions and initialisation + * \author Paul Sheer + * \date 1996, 1997 + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 , 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 . + */ + +/** \file + * \brief Source: editor options dialog box + * \author Paul Sheer + * \date 1996, 1997 + */ + +#include + +#include /* 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 , 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 . + */ + +#include + +#include + +#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 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 . + */ + +/** \file + * \brief Source: editor initialisation and callback handler + * \author Paul Sheer + * \date 1996, 1997 + */ + +#include + +#include +#include +#include +#include +#include +#include + +#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 /* 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 , 2009 + Slava Zanko , 2009 + Andrew Borodin , 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 . + */ + +#include + +#include +#include +#include +#include + +#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 , 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 . + */ + +/** \file + * \brief Source: Dynamic paragraph formatting + * \author Paul Sheer + * \date 1996 + * \author Andrew Borodin + * \date 2013, 2014 + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 , 2012 + Andrew Borodin , 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 . + */ + +#include + +#include +#include +#include +#include + +#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 */ + +/*** 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 , 2005, 2006 + Egmont Koblinger , 2010 + Slava Zanko , 2013 + Andrew Borodin , 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 . + */ + +/** \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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 , 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 . + */ + +#include + +#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 , 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 . + */ + +/** \file execute.c + * \brief Source: execution routines + */ + +#include + +#include +#include +#include +#include + +#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 , 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 . + */ + +#include + +#include /* 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 . + */ + +/** \file achown.c + * \brief Source: Contains functions for advanced chowning + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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, "", 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 , 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 . + */ + +/** \file boxes.c + * \brief Source: Some misc dialog boxes for the program + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#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, ¤t_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 , 2013 + Andrew Borodin , 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 . + */ + +/** \file cd.c + * \brief Source: cd_to() function + */ + +#include + +#include +#include +#include + +#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 M-a " 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 , 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 . + */ + +/** \file chattr.c + * \brief Source: chattr command + */ + +/* TODO: change attributes recursively (ticket #3109) */ + +#include + +#include +#include +#include + +#include + +#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 . + */ + +/** \file chmod.c + * \brief Source: chmod command + */ + +#include + +#include +#include +#include +#include + +#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 . + */ + +/** \file chown.c + * \brief Source: chown command + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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, _(""), 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, _(""), 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 , 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 . + */ + +/** \file cmd.c + * \brief Source: routines invoked by a function key + * + * They normally operate on the current panel. + */ + +#include + +#include +#include +#include + +#include +#include +#ifdef HAVE_MMAP +#include +#endif +#ifdef ENABLE_VFS_NET +#include +#endif +#include +#include +#include +#include + +#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 , 2013 + Andrew Borodin , 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 . + */ + +/** \file command.c + * \brief Source: command line widget + */ + +#include + +#include +#include + +#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 , 2013 + Andrew Borodin , 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 . + */ + +/** \file src/filemanager/dir.c + * \brief Source: directory routines + */ + +#include + +#include +#include +#include +#include + +#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 + +#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 , 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 . + */ + +/** \file ext.c + * \brief Source: extension dependent execution + */ + +#include + +#include +#include +#include +#include +#include +#include + +#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 , 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 . + */ + +/* + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 (×tamp, 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, ×); + mc_utime (dst_vpath, ×); + } + 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, ×); + + 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, ×); + 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, ×); + 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, ×); + } + + 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, ×); + mc_utime (dst_vpath, ×); + } + 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 /* 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 , 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 . + */ + +/* + * 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 + +#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 +#include +#include +#include +#include +#include + +#if USE_STATVFS +#include +#elif defined HAVE_SYS_VFS_H +#include +#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 +#include +#elif defined HAVE_OS_H /* Haiku, also (obsolete) BeOS */ +#include +#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 +#include +#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 + +#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 (×tamp, 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 , 2009-2022 + Slava Zanko , 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 . + */ + +/** \file filemanager.c + * \brief Source: main dialog (file panels) of the Midnight Commander + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* 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 , 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 . + */ + + +/** \file filenot.c + * \brief Source: wrapper for routines to notify the + * tree about the changes made to the directory + * structure. + */ + +#include + +#include +#include + +#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 + Miguel de Icaza + + 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 . + */ + +/** \file fileopctx.c + * \brief Source: file operation contexts + * \date 1998-2007 + * \author Federico Mena + * \author Miguel de Icaza + */ + +#include + +#include + +#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 + * \author Miguel de Icaza + */ + +#ifndef MC__FILEOPCTX_H +#define MC__FILEOPCTX_H + +#include +#include +#include /* 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 , 2013 + Andrew Borodin , 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 . + */ + +/** \file find.c + * \brief Source: Find file command + */ + +#include + +#include +#include +#include +#include +#include + +#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 , 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 . + */ + +/** \file hotlist.c + * \brief Source: directory hotlist + */ + +#include + +#include +#include +#include +#include +#include +#include + +#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 **) ¤t); + + /* 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 , 2013 + Andrew Borodin , 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 . + */ + +/** \file info.c + * \brief Source: panel managing + */ + +#include + +#include +#include +#include +#include /* 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 . */ + +/* 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 , 2011-2022 + Slava Zanko , 2013 + Avi Kelman , 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 . + */ + +/** \file layout.c + * \brief Source: panel layout module + */ + +#include + +#include /* for username in xterm title */ +#include +#include +#include +#include +#include + +#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 (×tamp, 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 . + */ + +/** \file mountlist.c + * \brief Source: list of mounted filesystems + */ + +#include + +#include +#include +#include +#include +#include /* SIZE_MAX */ +#include + +#include + +/* This header needs to be included before sys/mount.h on *BSD */ +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#if defined STAT_STATVFS || defined STAT_STATVFS64 /* POSIX 1003.1-2001 (and later) with XSI */ +#include +#else +/* Don't include backward-compatibility files unless they're needed. + Eventually we'd like to remove all this cruft. */ +#include +#include +#include + +#ifdef MOUNTED_GETFSSTAT /* OSF_1, also (obsolete) Apple Darwin 1.3 */ +#ifdef HAVE_SYS_UCRED_H +#include /* needed on OSF V4.0 for definition of NGROUPS, + NGROUPS is used as an array dimension in ucred.h */ +#include /* needed by powerpc-apple-darwin1.3.7 */ +#endif +#ifdef HAVE_SYS_MOUNT_H +#include +#endif +#ifdef HAVE_SYS_FS_TYPES_H +#include /* 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 +#endif +#ifdef HAVE_SYS_FS_S5PARAM_H /* Fujitsu UXP/V */ +#include +#endif +#ifdef HAVE_SYS_STATFS_H +#include +#endif + +#ifdef MOUNTED_GETMNTENT1 /* glibc, HP-UX, IRIX, Cygwin, Android, + also (obsolete) 4.3BSD, SunOS */ +#include +#include +#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 +#endif + +#ifdef MOUNTED_GETMNTINFO2 /* NetBSD, Minix */ +#include +#endif + +#ifdef MOUNTED_FS_STAT_DEV /* Haiku, also (obsolete) BeOS */ +#include +#include +#endif + +#ifdef MOUNTED_FREAD_FSTYP /* (obsolete) SVR3 */ +#include +#include +#include +#endif + +#ifdef MOUNTED_GETEXTMNTENT /* Solaris >= 8 */ +#include +#endif + +#ifdef MOUNTED_GETMNTENT2 /* Solaris < 8, also (obsolete) SVR4 */ +#include +#endif + +#ifdef MOUNTED_VMOUNT /* AIX */ +#include +#include +#endif + +#ifdef MOUNTED_INTERIX_STATVFS /* Interix */ +#include +#include +#endif + +#ifdef HAVE_SYS_MNTENT_H +/* This is to get MNTOPT_IGNORE on e.g. SVR4. */ +#include +#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 +#include +#endif + +#ifdef HAVE_SYS_STATVFS_H /* SVR4. */ +#include +#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 +#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 +#include +#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 /* 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 , 2013 + Andrew Borodin , 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 . + */ + +/** \file panel.c + * \brief Source: panel managin module + */ + +#include + +#include +#include +#include + +#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 (_(""), 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, ®_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. + */ + +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 = ¤t_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), ¤t_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 /* uintmax_t */ +#include /* 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 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 . + */ + +/** \file panelize.c + * \brief Source: External panelization module + */ + +#include + +#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 = ¤t_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 , 2013 + Andrew Borodin , 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 . + */ + +/** \file tree.c + * \brief Source: directory tree browser + */ + +#include + +#include +#include +#include +#include + +#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 , 2013 + Andrew Borodin , 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 . + */ + +/** \file treestore.c + * \brief Source: tree store + * + * Contains a storage of the file system tree representation. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 . + */ + + +/** \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 + +#include +#include /* MB_LEN_MAX */ +#include +#include +#include + +#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 , 2009, 2010 + Andrew Borodin , 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 . + */ + +#include + +#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= */ + 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 , 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 . + */ + +/** \file learn.c + * \brief Source: learn keys module + */ + +#include + +#include + +#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 . + */ + +/** \file main.c + * \brief Source: this is a main module + */ + +#include + +#include +#include +#include +#include /* for username in xterm title */ +#include +#include +#include +#include +#include +#include +#include /* 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 , 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 . +# +# \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 + + Copyright (C) 2011-2023 + Free Software Foundation, Inc. + + Written by: + Walery Studennikov , 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 . + */ + +/** \file selcodepage.c + * \brief Source: user %interface for charset %selection + */ + +#include + +#include +#include + +#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 . + */ + +/** \file setup.c + * \brief Source: setup loading/saving + */ + +#include + +#include +#include +#include +#include +#include + +#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", ©move_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 + +#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 + Aliaksey Kandratsenka + Andreas Mohr + Andrew Borodin + Andrew V. Samoilov + Chris Owen + Claes Nästén + Egmont Koblinger + Enrico Weigelt, metux IT service + Eric Roberts + Igor Urazov + Ilia Maslakov + Leonard den Ottolander + Miguel de Icaza + Mikhail S. Pobolovets + Norbert Warmuth + Oswald Buddenhagen + Patrick Winnertz + Pavel Machek + Pavel Roskin + Pavel Tsekov + Roland Illig + Sergei Trofimovich + Slava Zanko + Timur Bakeyev + Vit Rosin + + 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 . + */ + +/** \file subshell.c + * \brief Source: concurrent shell support + */ + +#include + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_SELECT_H +#include +#else +#include +#include +#endif +#include +#include +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#include + +#ifdef HAVE_STROPTS_H +#include /* For I_PUSH */ +#endif /* HAVE_STROPTS_H */ + +#ifdef HAVE_OPENPTY +/* includes for openpty() */ +#ifdef HAVE_PTY_H +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif +/* is a prerequisite of on FreeBSD 8.0. */ +#ifdef HAVE_LIBUTIL_H +#include +#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 , 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 . + */ + +#include + +#include /* kill() */ +#include +#include /* 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 . + */ + +/** \file textconf.c + * \brief Source: prints features specific for this build + */ + +#include + +#include +#include +#include + +#if defined (ENABLE_VFS) && defined(ENABLE_VFS_SFTP) +#include +#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 , 2013 + Andrew Borodin , 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 . + */ + +/** \file usermenu.c + * \brief Source: user menu implementation + */ + +#include + +#include +#include +#include +#include +#include + +#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 , 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 . + */ + +#include + +#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 , 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 . + */ + +/** \file + * \brief Source: Virtual File System: GNU Tar file system. + * \author Jan Hudec + * \date 2000 + */ + +#include + +#include +#include +#include + +#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 , 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 . + */ + +/** + * \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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, ¤t_file_name, ¤t_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 or . 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 , 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 ( ) { + 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 +# 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() { + 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( ) { + 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( ) { + 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=<&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 1996 +# +# Applied patch by Dimitri Maziuk 1997 +# (to handle new tar format) +# +# Modified by Fernando Alegre 1997 +# (to handle both new and old tar formats) +# +# Modified by Patrik Rak 1998 +# (add by Michael Bramer Debian-mc-maintainer ) +# (to allow access to package control files) +# +# Modified by Martin Bialasinski 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() + { + @_ = 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() + { + @_ = 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=< +# 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=< +# 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() { + 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() { + 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=< +# 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( ) { + 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=< "$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 , 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 ( - i/o - iopar): +# port: wire +# baud: 9600 +# transfer format: binary (fast transfers) or +# ascii (editable on the pc) +# - start the server on the HP48: - i/o - srvr - serve +# or the shortcut - +# - 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 April 2000 +# +# Copyright (C) 2000, 2003 +# The Free Software Foundation, Inc. +# +# Written by: +# Michael Shigorin , +# Grigory Milev , +# Kachalov Anton , 2003 +# Victor Ananjevsky , 2013 +# slava zanko , 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 . + +#*** 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 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 (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 () { + $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() { + 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() { + 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 , 2002 +# Rewritten by David Sterba , 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++,$_=)) { + $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 + $_ .=; # 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 1996 +# Jakub Jelinek 1996, 2004 +# Tomasz KÅ‚oczko 1997 +# Wojtek Pilorz +# 1997: minor changes +# Michele Marziani +# 1997: minor changes +# Marc Merlin 1998 +# 1998: bug files +# Michal Svec 2000 +# 2000: locale bugfix +# Andrew V. Samoilov +# 2004: Whitespace(s) & single quote(s) in filename workaround +# https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=64007 +# Slava Zanko +# 2009: Totally rewritten. +# Alexander Chumachenko +# 2013: add dependency version output +# Denis Silakov +# 2013: tar payload support. +# Arkadiusz MiÅ›kiewicz +# 2013: improve support for EPOCH +# add support for PREINPROG/POSTINPROG/PREUNPROG/POSTUNPROG +# add support for VERIFYSCRIPTPROG +# add support for TRIGGERSCRIPTS/TRIGGERSCRIPTPROG +# Jiri Tyr +# 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 . + + +# 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 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 . +# +# +# 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 (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" () and type: s3:// (or simply type `cd s3://' in shell line) +# +# +# History: +# +# 2015-07-22 Dmitry Koterov +# - Support for non-ASCII characters in filenames (system encoding detection). +# +# 2015-05-21 Dmitry Koterov +# - 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 +# - Updated instructions. +# - Improved error reporting. +# +# 2009-02-06 Jakob Kemi +# - 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 +# - 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'(.*?)', 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 +# Some Bugfixes/workarounds by Sergiy Niskorodov +# +# 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 . + +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 +# +# + +# 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 +# (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 +# Alex Tkachenko +# Updated by Vitezslav Samel +# +# (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 +# +# + +# 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 +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 +# +# + +# Code for mc_lha_fs_run() suggested by: +# Jan 97 Zdenek Kabelac + +# 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 () + { + 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 () + { + 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 +# +# (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 2000 +# +# Andrew Borodin +# David Haller +# 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 , +# +# 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 . +# + +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 () { + $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 () { + 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 () { + 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 , 2010-2022 + Slava Zanko , 2010, 2013 + Ilia Maslakov , 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 . + */ + +/** + * \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 +#include +#include +#include +#include +#include +#include /* 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= + * while [ $rest -gt 0 ] + * do + * cnt=`expr \( $rest + 255 \) / 256` + * n=`dd bs=256 count=$cnt | tee -a | 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// + +Server's reply is multiline, but always ends with + +### 000 + +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 <...> +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 +### 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 : +may be omitted): + +P . +S +d<3-letters month name> +D [.1234] +E, +: +L + + +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 /file/name +> /file/name; echo '### 001'; ( dd bs=4096 count=; dd bs= 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 /path/and/filename +cat /path/and/filename | ( dd bs=4096 count= > /dev/null; +dd bs= count=1 > /dev/null; +dd bs=4096 count=; +dd bs= 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 /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 , 2010, 2013 + Andrew Borodin , 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 . + */ + +/** + * \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 +#include /* sscanf() */ +#include /* atoi() */ +#include /* POSIX-required by sys/socket.h and netdb.h */ +#include /* struct hostent */ +#include /* AF_INET */ +#include /* struct in_addr */ +#ifdef HAVE_ARPA_INET_H +#include +#endif +#include +#include +#ifdef HAVE_SYS_PARAM_H +#include +#endif +#include +#include +#include +#include /* 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 ', if it fails + * fall back to CWD ; LIST + * 0: always use CWD ; LIST + */ +gboolean ftpfs_use_unix_list_options = TRUE; + +/* First "CWD ", 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 "; use "CWD "/ + * "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 \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; + + /* (||||) */ + 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 , 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 . + */ + +/** \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 + +#include /* isdigit() */ +#include /* sscanf() */ +#include +#include +#include /* mode_t */ +#include +#include +#include + +#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 , with the help of + Mark Baushke 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 aix + 07-13-98 09:06PM hpux + 07-13-98 09:06PM linux + 07-13-98 09:06PM ncr + 07-13-98 09:06PM 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, "") == 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 . + */ + +/** + * \file + * \brief Source: local FS + */ + +#include +#include +#include +#include +#include +#include +#ifdef ENABLE_EXT2FS_ATTR +#include /* 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 , 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 . + */ + +/** \file + * \brief This is a template file (here goes brief description). + * \author Author1 + * \author Author2 + * \date 20xx + * + * Detailed description. + */ + +#include + +#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 , 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 . + */ + +/** + * \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 +#include +#include +#include +#include + +#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 , 2011 + Slava Zanko , 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 . + */ + +#include +#include +#include +#include /* 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 , 2011 + Slava Zanko , 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 . + */ + +#include +#include + +#include /* struct hostent */ +#include /* AF_INET */ +#include /* struct in_addr */ +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#include +#include + +#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 , 2011 + Slava Zanko , 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 . + */ + +#include + +#include +#include + +#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 , 2011 + Slava Zanko , 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 . + */ + +#include + +#include /* ENOENT, EACCES */ + +#include +#include + +#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 , 2011 + Slava Zanko , 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 . + */ + +#include +#include + +#ifdef HAVE_SYS_SELECT_H +#include +#else +#include +#include +#include +#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 +#include + +#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 , 2011 + Slava Zanko , 2011-2013 + Andrew Borodin , 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 . + */ + +#include +#include +#include +#include /* 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 , 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 . + */ + +/** + * \file + * \brief Source: Virtual File System: GNU Tar file system + * \author Andrew Borodin + * \date 2022 + */ + +#include + +#include /* isspace() */ +#include /* uintmax_t */ +#include /* 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 /* (u)intmax_t */ +#include /* CHAR_BIT, INT_MAX, etc */ +#include +#include + +#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 , 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 . + */ + +/** + * \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 + +#include /* isdigit() */ +#include +#include /* 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 + . */ +#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 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 , 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 . + */ + +/** + * \file + * \brief Source: Virtual File System: GNU Tar file system + */ + +#include + +#include /* isdigit() */ +#include +#include +#include + +#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 , 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 . + */ + +/** + * \file + * \brief Source: Virtual File System: GNU Tar file system + * \author Jakub Jelinek + * \author Pavel Machek + * \date 1995, 1998 + */ + +#include + +#include +#include /* memset() */ + +#ifdef hpux +/* major() and minor() macros (among other things) defined here for hpux */ +#include +#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, ¤t_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 (¤t_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, ¤t_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, ¤t_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 (¤t_stat_info.orig_file_name, file_name); + canonicalize_pathname (file_name); + tar_assign_string (¤t_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 (¤t_stat_info.link_name, link_name); + + if (current_stat_info.xhdr.buffer != NULL && !tar_xheader_decode (¤t_stat_info)) + { + status = HEADER_FAILURE; + goto ret; + } + + if (tar_sparse_member_p (arch, ¤t_stat_info)) + { + if (!tar_sparse_fixup_header (arch, ¤t_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 (¤t_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 (¤t_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 . + */ + +/** + * \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 + +#include +#include +#include /* memset() */ +#include +#include +#include + +#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 , 2004, 2005 + Slava Zanko , 2009, 2013 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +/* + 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 + +#include + +#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 = ¤t_panel->dir; + view->dir_idx = ¤t_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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 2009 + Rewritten almost from scratch by: + Egmont Koblinger , 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 . + + ------------------------------------------------------------------------------------------------ + + 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 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. "") 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 + +#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 + * .) + * + * 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, ¶graph_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, ¶graph_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, ¶graph_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, ¶graph_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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +/* + 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 + +#include /* memset() */ +#ifdef MC_ENABLE_DEBUGGING_CODE +#include /* 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 (¤t, 0, sizeof (current)); + mcview_ccache_add_entry (cache, ¤t); + } + + 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 (¤t, 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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009 + Ilia Maslakov , 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 . + */ + +/* + 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 + +#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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +#include + +#include +#include + +#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 *) ¤t_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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +#include +#include /* 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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009, 2014 + Ilia Maslakov , 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 . + */ + +#include +#include + +#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 , 2004, 2005 + Slava Zanko , 2009, 2013 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +#include + +#include +#include /* 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 /* CHAR_BIT */ +#include +#include +#include + +#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 , 2004, 2005 + Slava Zanko , 2009, 2013 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +#include + +#include /* memset() */ +#include + +#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 , 2004, 2005 + Slava Zanko , 2009, 2013 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +#include +#include + +#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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +/* + 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 + +#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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009 + Ilia Maslakov , 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 . + */ + +#include + +#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 , 2004, 2005 + Slava Zanko , 2009 + Andrew Borodin , 2009-2022 + Ilia Maslakov , 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 . + */ + +#include + +#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++; +} + +/* --------------------------------------------------------------------------------------------- */ -- cgit v1.2.3