]> Sergey Matveev's repositories - nnn.git/commitdiff
Move helper APIs to header file
authorArun Prakash Jana <engineerarun@gmail.com>
Fri, 7 Aug 2020 17:40:25 +0000 (23:10 +0530)
committerArun Prakash Jana <engineerarun@gmail.com>
Fri, 7 Aug 2020 17:40:25 +0000 (23:10 +0530)
src/nnn.c
src/nnn.h

index f8c03cf252827a07f9dafd879ea1aafda230409c..a66fa4411c94bbc748408466a926388d8d090c10 100644 (file)
--- a/src/nnn.c
+++ b/src/nnn.c
 #define alloca(size) __builtin_alloca(size)
 #endif
 
-#include "nnn.h"
-#include "dbg.h"
-
-/* Macro definitions */
-#define VERSION "3.3"
-#define GENERAL_INFO "BSD 2-Clause\nhttps://github.com/jarun/nnn"
-#define SESSIONS_VERSION 1
-
-#ifndef S_BLKSIZE
-#define S_BLKSIZE 512 /* S_BLKSIZE is missing on Android NDK (Termux) */
-#endif
-
-/*
- * NAME_MAX and PATH_MAX may not exist, e.g. with dirent.c_name being a
- * flexible array on Illumos. Use somewhat accomodating fallback values.
- */
-#ifndef NAME_MAX
-#define NAME_MAX 255
-#endif
-
-#ifndef PATH_MAX
-#define PATH_MAX 4096
-#endif
-
-#define _ABSSUB(N, M) (((N) <= (M)) ? ((M) - (N)) : ((N) - (M)))
-#define DOUBLECLICK_INTERVAL_NS (400000000)
-#define XDELAY_INTERVAL_MS (350000) /* 350 ms delay */
-#define ELEMENTS(x) (sizeof(x) / sizeof(*(x)))
-#undef MIN
-#define MIN(x, y) ((x) < (y) ? (x) : (y))
-#undef MAX
-#define MAX(x, y) ((x) > (y) ? (x) : (y))
-#define ISODD(x) ((x) & 1)
-#define ISBLANK(x) ((x) == ' ' || (x) == '\t')
-#define TOUPPER(ch) (((ch) >= 'a' && (ch) <= 'z') ? ((ch) - 'a' + 'A') : (ch))
-#define CMD_LEN_MAX (PATH_MAX + ((NAME_MAX + 1) << 1))
-#define READLINE_MAX 256
-#define FILTER '/'
-#define RFILTER '\\'
-#define CASE ':'
-#define MSGWAIT '$'
-#define SELECT ' '
-#define REGEX_MAX 48
-#define ENTRY_INCR 64 /* Number of dir 'entry' structures to allocate per shot */
-#define NAMEBUF_INCR 0x800 /* 64 dir entries at once, avg. 32 chars per filename = 64*32B = 2KB */
-#define DESCRIPTOR_LEN 32
-#define _ALIGNMENT 0x10 /* 16-byte alignment */
-#define _ALIGNMENT_MASK 0xF
-#define TMP_LEN_MAX 64
-#define DOT_FILTER_LEN 7
-#define ASCII_MAX 128
-#define EXEC_ARGS_MAX 8
-#define LIST_FILES_MAX (1 << 16)
-#define SCROLLOFF 3
-
-#ifndef CTX8
-#define CTX_MAX 4
-#else
-#define CTX_MAX 8
-#endif
-
-#define MIN_DISPLAY_COLS ((CTX_MAX * 2) + 2) /* Two chars for [ and ] */
-#define LONG_SIZE sizeof(ulong)
-#define ARCHIVE_CMD_LEN 16
-#define BLK_SHIFT_512 9
-
-/* Detect hardlinks in du */
-#define HASH_BITS (0xFFFFFF)
-#define HASH_OCTETS (HASH_BITS >> 6) /* 2^6 = 64 */
-
-/* Entry flags */
-#define DIR_OR_LINK_TO_DIR 0x01
-#define HARD_LINK 0x02
-#define SYM_ORPHAN 0x04
-#define FILE_MISSING 0x08
-#define FILE_SELECTED 0x10
-
-/* Macros to define process spawn behaviour as flags */
-#define F_NONE    0x00  /* no flag set */
-#define F_MULTI   0x01  /* first arg can be combination of args; to be used with F_NORMAL */
-#define F_NOWAIT  0x02  /* don't wait for child process (e.g. file manager) */
-#define F_NOTRACE 0x04  /* suppress stdout and strerr (no traces) */
-#define F_NORMAL  0x08  /* spawn child process in non-curses regular CLI mode */
-#define F_CONFIRM 0x10  /* run command - show results before exit (must have F_NORMAL) */
-#define F_CHKRTN  0x20  /* wait for user prompt if cmd returns failure status */
-#define F_CLI     (F_NORMAL | F_MULTI)
-#define F_SILENT  (F_CLI | F_NOTRACE)
-
-/* Version compare macros */
-/*
- * states: S_N: normal, S_I: comparing integral part, S_F: comparing
- *         fractional parts, S_Z: idem but with leading Zeroes only
- */
-#define S_N 0x0
-#define S_I 0x3
-#define S_F 0x6
-#define S_Z 0x9
-
-/* result_type: VCMP: return diff; VLEN: compare using len_diff/diff */
-#define VCMP 2
-#define VLEN 3
-
-/* Volume info */
-#define FREE 0
-#define CAPACITY 1
-
-/* TYPE DEFINITIONS */
-typedef unsigned long ulong;
-typedef unsigned int uint;
-typedef unsigned char uchar;
-typedef unsigned short ushort;
-typedef long long ll;
-typedef unsigned long long ull;
-
-/* STRUCTURES */
-
-/* Directory entry */
-typedef struct entry {
-       char *name;
-       time_t t;
-       off_t size;
-       blkcnt_t blocks; /* number of 512B blocks allocated */
-       mode_t mode;
-       ushort nlen; /* Length of file name */
-       uchar flags; /* Flags specific to the file */
-} *pEntry;
-
-/* Key-value pairs from env */
-typedef struct {
-       int key;
-       int off;
-} kv;
-
-typedef struct {
-#ifdef PCRE
-       const pcre *pcrex;
-#else
-       const regex_t *regex;
-#endif
-       const char *str;
-} fltrexp_t;
-
-/*
- * Settings
- * NOTE: update default values if changing order
- */
-typedef struct {
-       uint filtermode : 1;  /* Set to enter filter mode */
-       uint timeorder  : 1;  /* Set to sort by time */
-       uint sizeorder  : 1;  /* Set to sort by file size */
-       uint apparentsz : 1;  /* Set to sort by apparent size (disk usage) */
-       uint blkorder   : 1;  /* Set to sort by blocks used (disk usage) */
-       uint extnorder  : 1;  /* Order by extension */
-       uint showhidden : 1;  /* Set to show hidden files */
-       uint reserved0  : 1;
-       uint showdetail : 1;  /* Clear to show lesser file info */
-       uint ctxactive  : 1;  /* Context active or not */
-       uint reverse    : 1;  /* Reverse sort */
-       uint version    : 1;  /* Version sort */
-       uint reserved1  : 1;
-       /* The following settings are global */
-       uint curctx     : 3;  /* Current context number */
-       uint prefersel  : 1;  /* Prefer selection over current, if exists */
-       uint reserved2  : 1;
-       uint nonavopen  : 1;  /* Open file on right arrow or `l` */
-       uint autoselect : 1;  /* Auto-select dir in type-to-nav mode */
-       uint cursormode : 1;  /* Move hardware cursor with selection */
-       uint useeditor  : 1;  /* Use VISUAL to open text files */
-       uint reserved3  : 3;
-       uint regex      : 1;  /* Use regex filters */
-       uint x11        : 1;  /* Copy to system clipboard and show notis */
-       uint timetype   : 2;  /* Time sort type (0: access, 1: change, 2: modification) */
-       uint cliopener  : 1;  /* All-CLI app opener */
-       uint waitedit   : 1;  /* For ops that can't be detached, used EDITOR */
-       uint rollover   : 1;  /* Roll over at edges */
-} settings;
-
-/* Non-persistent program-internal states */
-typedef struct {
-       uint pluginit   : 1;  /* Plugin framework initialized */
-       uint interrupt  : 1;  /* Program received an interrupt */
-       uint rangesel   : 1;  /* Range selection on */
-       uint move       : 1;  /* Move operation */
-       uint autonext   : 1;  /* Auto-proceed on open */
-       uint fortune    : 1;  /* Show fortune messages in help */
-       uint trash      : 1;  /* Use trash to delete files */
-       uint forcequit  : 1;  /* Do not prompt on quit */
-       uint autofifo   : 1;  /* Auto-create NNN_FIFO */
-       uint initfile   : 1;  /* Positional arg is a file */
-       uint dircolor   : 1;  /* Current status of dir color */
-       uint picker     : 1;  /* Write selection to user-specified file */
-       uint pickraw    : 1;  /* Write selection to sdtout before exit */
-       uint runplugin  : 1;  /* Choose plugin mode */
-       uint runctx     : 2;  /* The context in which plugin is to be run */
-       uint selmode    : 1;  /* Set when selecting files */
-       uint oldcolor   : 1;  /* Show dirs in context colors */
-       uint reserved   : 14;
-} runstate;
-
-/* Contexts or workspaces */
-typedef struct {
-       char c_path[PATH_MAX]; /* Current dir */
-       char c_last[PATH_MAX]; /* Last visited dir */
-       char c_name[NAME_MAX + 1]; /* Current file name */
-       char c_fltr[REGEX_MAX]; /* Current filter */
-       settings c_cfg; /* Current configuration */
-       uint color; /* Color code for directories */
-} context;
-
-typedef struct {
-       size_t ver;
-       size_t pathln[CTX_MAX];
-       size_t lastln[CTX_MAX];
-       size_t nameln[CTX_MAX];
-       size_t fltrln[CTX_MAX];
-} session_header_t;
-
-/* GLOBALS */
-
-/* Configuration, contexts */
-static settings cfg = {
-       0, /* filtermode */
-       0, /* timeorder */
-       0, /* sizeorder */
-       0, /* apparentsz */
-       0, /* blkorder */
-       0, /* extnorder */
-       0, /* showhidden */
-       0, /* reserved0 */
-       0, /* showdetail */
-       1, /* ctxactive */
-       0, /* reverse */
-       0, /* version */
-       0, /* reserved1 */
-       0, /* curctx */
-       0, /* prefersel */
-       0, /* reserved2 */
-       0, /* nonavopen */
-       1, /* autoselect */
-       0, /* cursormode */
-       0, /* useeditor */
-       0, /* reserved3 */
-       0, /* regex */
-       0, /* x11 */
-       2, /* timetype (T_MOD) */
-       0, /* cliopener */
-       0, /* waitedit */
-       1, /* rollover */
-};
-
-static context g_ctx[CTX_MAX] __attribute__ ((aligned));
-
-static int ndents, cur, last, curscroll, last_curscroll, total_dents = ENTRY_INCR, scroll_lines = 1;
-static int nselected;
-#ifndef NOFIFO
-static int fifofd = -1;
-#endif
-static uint idletimeout, selbufpos, lastappendpos, selbuflen;
-static ushort xlines, xcols;
-static ushort idle;
-static uchar maxbm, maxplug;
-static char *bmstr;
-static char *pluginstr;
-static char *opener;
-static char *editor;
-static char *enveditor;
-static char *pager;
-static char *shell;
-static char *home;
-static char *initpath;
-static char *cfgpath;
-static char *selpath;
-static char *listpath;
-static char *listroot;
-static char *plgpath;
-static char *pnamebuf, *pselbuf;
-static char *mark;
-#ifndef NOFIFO
-static char *fifopath;
-#endif
-static ull *ihashbmp;
-static struct entry *pdents;
-static blkcnt_t ent_blocks;
-static blkcnt_t dir_blocks;
-static ulong num_files;
-static kv *bookmark;
-static kv *plug;
-static uchar tmpfplen;
-static uchar blk_shift = BLK_SHIFT_512;
-#ifndef NOMOUSE
-static int middle_click_key;
-#endif
-#ifdef PCRE
-static pcre *archive_pcre;
-#else
-static regex_t archive_re;
-#endif
-
-/* Retain old signal handlers */
-static struct sigaction oldsighup;
-static struct sigaction oldsigtstp;
-
-/* For use in functions which are isolated and don't return the buffer */
-static char g_buf[CMD_LEN_MAX] __attribute__ ((aligned));
-
-/* Buffer to store tmp file path to show selection, file stats and help */
-static char g_tmpfpath[TMP_LEN_MAX] __attribute__ ((aligned));
-
-/* Buffer to store plugins control pipe location */
-static char g_pipepath[TMP_LEN_MAX] __attribute__ ((aligned));
-
-/* Non-persistent runtime states */
-static runstate g_state;
-
-/* Options to identify file mime */
-#if defined(__APPLE__)
-#define FILE_MIME_OPTS "-bIL"
-#elif !defined(__sun) /* no mime option for 'file' */
-#define FILE_MIME_OPTS "-biL"
-#endif
-
-/* Macros for utilities */
-#define UTIL_OPENER 0
-#define UTIL_ATOOL 1
-#define UTIL_BSDTAR 2
-#define UTIL_UNZIP 3
-#define UTIL_TAR 4
-#define UTIL_LOCKER 5
-#define UTIL_LAUNCH 6
-#define UTIL_SH_EXEC 7
-#define UTIL_BASH 8
-#define UTIL_ARCHIVEMOUNT 9
-#define UTIL_SSHFS 10
-#define UTIL_RCLONE 11
-#define UTIL_VI 12
-#define UTIL_LESS 13
-#define UTIL_SH 14
-#define UTIL_FZF 15
-#define UTIL_NTFY 16
-#define UTIL_CBCP 17
-#define UTIL_NMV 18
-
-/* Utilities to open files, run actions */
-static char * const utils[] = {
-#ifdef __APPLE__
-       "/usr/bin/open",
-#elif defined __CYGWIN__
-       "cygstart",
-#elif defined __HAIKU__
-       "open",
-#else
-       "xdg-open",
-#endif
-       "atool",
-       "bsdtar",
-       "unzip",
-       "tar",
-#ifdef __APPLE__
-       "bashlock",
-#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
-       "lock",
-#elif defined __HAIKU__
-       "peaclock",
-#else
-       "vlock",
-#endif
-       "launch",
-       "sh -c",
-       "bash",
-       "archivemount",
-       "sshfs",
-       "rclone",
-       "vi",
-       "less",
-       "sh",
-       "fzf",
-       ".ntfy",
-       ".cbcp",
-       ".nmv",
-};
-
-/* Common strings */
-#define MSG_NO_TRAVERSAL 0
-#define MSG_INVALID_KEY 1
-#define STR_TMPFILE 2
-#define MSG_0_SELECTED 3
-#define MSG_UTIL_MISSING 4
-#define MSG_FAILED 5
-#define MSG_SSN_NAME 6
-#define MSG_CP_MV_AS 7
-#define MSG_CUR_SEL_OPTS 8
-#define MSG_FORCE_RM 9
-#define MSG_LIMIT 10
-#define MSG_NEW_OPTS 11
-#define MSG_CLI_MODE 12
-#define MSG_OVERWRITE 13
-#define MSG_SSN_OPTS 14
-#define MSG_QUIT_ALL 15
-#define MSG_HOSTNAME 16
-#define MSG_ARCHIVE_NAME 17
-#define MSG_OPEN_WITH 18
-#define MSG_REL_PATH 19
-#define MSG_LINK_PREFIX 20
-#define MSG_COPY_NAME 21
-#define MSG_CONTINUE 22
-#define MSG_SEL_MISSING 23
-#define MSG_ACCESS 24
-#define MSG_EMPTY_FILE 25
-#define MSG_UNSUPPORTED 26
-#define MSG_NOT_SET 27
-#define MSG_EXISTS 28
-#define MSG_FEW_COLUMNS 29
-#define MSG_REMOTE_OPTS 30
-#define MSG_RCLONE_DELAY 31
-#define MSG_APP_NAME 32
-#define MSG_ARCHIVE_OPTS 33
-#define MSG_PLUGIN_KEYS 34
-#define MSG_BOOKMARK_KEYS 35
-#define MSG_INVALID_REG 36
-#define MSG_ORDER 37
-#define MSG_LAZY 38
-#define MSG_FIRST 39
-#define MSG_RM_TMP 40
-#define MSG_NOCHNAGE 41
-#define MSG_CANCEL 42
-#define MSG_0_ENTRIES 43
-#ifndef DIR_LIMITED_SELECTION
-#define MSG_DIR_CHANGED 44 /* Must be the last entry */
-#endif
-
-static const char * const messages[] = {
-       "no traversal",
-       "invalid key",
-       "/.nnnXXXXXX",
-       "0 selected",
-       "missing util",
-       "failed!",
-       "session name: ",
-       "'c'p / 'm'v as?",
-       "'c'urrent / 's'el?",
-       "rm -rf %s file%s? [Esc cancels]",
-       "limit exceeded",
-       "'f'ile / 'd'ir / 's'ym / 'h'ard?",
-       "'c'li / 'g'ui?",
-       "overwrite?",
-       "'s'ave / 'l'oad / 'r'estore?",
-       "Quit all contexts?",
-       "remote name ('-' for hovered): ",
-       "archive name: ",
-       "open with: ",
-       "relative path: ",
-       "link prefix [@ for none]: ",
-       "copy name: ",
-       "\n'Enter' to continue",
-       "open failed",
-       "dir inaccessible",
-       "empty: edit/open with",
-       "unknown",
-       "not set",
-       "entry exists",
-       "too few columns!",
-       "'s'shfs / 'r'clone?",
-       "refresh if slow",
-       "app name: ",
-       "'d'efault / e'x'tract / 'l'ist / 'm'ount?",
-       "plugin keys:",
-       "bookmark keys:",
-       "invalid regex",
-       "'a'u / 'd'u / 'e'xtn / 'r'ev / 's'ize / 't'ime / 'v'er / 'c'lear?",
-       "unmount failed! try lazy?",
-       "first file (\')/char?",
-       "remove tmp file?",
-       "unchanged",
-       "cancelled",
-       "0 entries",
-#ifndef DIR_LIMITED_SELECTION
-       "dir changed, range sel off", /* Must be the last entry */
-#endif
-};
-
-/* Supported configuration environment variables */
-#define NNN_OPTS 0
-#define NNN_BMS 1
-#define NNN_PLUG 2
-#define NNN_OPENER 3
-#define NNN_COLORS 4
-#define NNNLVL 5
-#define NNN_PIPE 6
-#define NNN_MCLICK 7
-#define NNN_SEL 8
-#define NNN_ARCHIVE 9 /* strings end here */
-#define NNN_TRASH 10 /* flags begin here */
-
-static const char * const env_cfg[] = {
-       "NNN_OPTS",
-       "NNN_BMS",
-       "NNN_PLUG",
-       "NNN_OPENER",
-       "NNN_COLORS",
-       "NNNLVL",
-       "NNN_PIPE",
-       "NNN_MCLICK",
-       "NNN_SEL",
-       "NNN_ARCHIVE",
-       "NNN_TRASH",
-};
-
-/* Required environment variables */
-#define ENV_SHELL 0
-#define ENV_VISUAL 1
-#define ENV_EDITOR 2
-#define ENV_PAGER 3
-#define ENV_NCUR 4
-
-static const char * const envs[] = {
-       "SHELL",
-       "VISUAL",
-       "EDITOR",
-       "PAGER",
-       "nnn",
-};
-
-/* Time type used */
-#define T_ACCESS 0
-#define T_CHANGE 1
-#define T_MOD 2
-
-#ifdef __linux__
-static char cp[] = "cp   -iRp";
-static char mv[] = "mv   -i";
-#else
-static char cp[] = "cp -iRp";
-static char mv[] = "mv -i";
-#endif
-
-/* Tokens used for path creation */
-#define TOK_SSN 0
-#define TOK_MNT 1
-#define TOK_PLG 2
-
-static const char * const toks[] = {
-       "sessions",
-       "mounts",
-       "plugins", /* must be the last entry */
-};
-
-/* Patterns */
-#define P_CPMVFMT 0
-#define P_CPMVRNM 1
-#define P_ARCHIVE 2
-#define P_REPLACE 3
-
-static const char * const patterns[] = {
-       "sed -i 's|^\\(\\(.*/\\)\\(.*\\)$\\)|#\\1\\n\\3|' %s",
-       "sed 's|^\\([^#/][^/]\\?.*\\)$|%s/\\1|;s|^#\\(/.*\\)$|\\1|' "
-               "%s | tr '\\n' '\\0' | xargs -0 -n2 sh -c '%s \"$0\" \"$@\" < /dev/tty'",
-       "\\.(bz|bz2|gz|tar|taz|tbz|tbz2|tgz|z|zip)$",
-       "sed -i 's|^%s\\(.*\\)$|%s\\1|' %s",
-};
-
-/* Colors */
-#define C_BLK (CTX_MAX + 1) /* Block device: DarkSeaGreen1 */
-#define C_CHR (C_BLK + 1) /* Character device: Yellow1 */
-#define C_DIR (C_CHR + 1) /* Directory: DeepSkyBlue1 */
-#define C_EXE (C_DIR + 1) /* Executable file: Green1 */
-#define C_FIL (C_EXE + 1) /* Regular file: Normal */
-#define C_HRD (C_FIL + 1) /* Hard link: Plum4 */
-#define C_LNK (C_HRD + 1) /* Symbolic link: Cyan1 */
-#define C_MIS (C_LNK + 1) /* Missing file OR file details: Grey62 */
-#define C_ORP (C_MIS + 1) /* Orphaned symlink: DeepPink1 */
-#define C_PIP (C_ORP + 1) /* Named pipe (FIFO): Orange1 */
-#define C_SOC (C_PIP + 1) /* Socket: MediumOrchid1 */
-#define C_UND (C_SOC + 1) /* Unknown OR 0B regular/exe file: Red1 */
-
-static char gcolors[] = "c1e2272e006033f7c6d6abc4";
-static uint fcolors[C_UND + 1] = {0};
-
-/* Event handling */
-#ifdef LINUX_INOTIFY
-#define NUM_EVENT_SLOTS 32 /* Make room for 32 events */
-#define EVENT_SIZE (sizeof(struct inotify_event))
-#define EVENT_BUF_LEN (EVENT_SIZE * NUM_EVENT_SLOTS)
-static int inotify_fd, inotify_wd = -1;
-static uint INOTIFY_MASK = /* IN_ATTRIB | */ IN_CREATE | IN_DELETE | IN_DELETE_SELF
-                          | IN_MODIFY | IN_MOVE_SELF | IN_MOVED_FROM | IN_MOVED_TO;
-#elif defined(BSD_KQUEUE)
-#define NUM_EVENT_SLOTS 1
-#define NUM_EVENT_FDS 1
-static int kq, event_fd = -1;
-static struct kevent events_to_monitor[NUM_EVENT_FDS];
-static uint KQUEUE_FFLAGS = NOTE_DELETE | NOTE_EXTEND | NOTE_LINK
-                           | NOTE_RENAME | NOTE_REVOKE | NOTE_WRITE;
-static struct timespec gtimeout;
-#elif defined(HAIKU_NM)
-static bool haiku_nm_active = FALSE;
-static haiku_nm_h haiku_hnd;
-#endif
-
-/* Function macros */
-#define tolastln() move(xlines - 1, 0)
-#define tocursor() move(cur + 2, 0)
-#define exitcurses() endwin()
-#define printwarn(presel) printwait(strerror(errno), presel)
-#define istopdir(path) ((path)[1] == '\0' && (path)[0] == '/')
-#define copycurname() xstrsncpy(lastname, pdents[cur].name, NAME_MAX + 1)
-#define settimeout() timeout(1000)
-#define cleartimeout() timeout(-1)
-#define errexit() printerr(__LINE__)
-#define setdirwatch() (cfg.filtermode ? (presel = FILTER) : (watch = TRUE))
-#define filterset() (g_ctx[cfg.curctx].c_fltr[1])
-/* We don't care about the return value from strcmp() */
-#define xstrcmp(a, b)  (*(a) != *(b) ? -1 : strcmp((a), (b)))
-/* A faster version of xisdigit */
-#define xisdigit(c) ((unsigned int) (c) - '0' <= 9)
-#define xerror() perror(xitoa(__LINE__))
-
-#ifdef __GNUC__
-#define UNUSED(x) UNUSED_##x __attribute__((__unused__))
-#else
-#define UNUSED(x) UNUSED_##x
-#endif /* __GNUC__ */
-
-/* Forward declarations */
-static void redraw(char *path);
-static int spawn(char *file, char *arg1, char *arg2, uchar flag);
-static int (*nftw_fn)(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf);
-static void move_cursor(int target, int ignore_scrolloff);
-static char *load_input(int fd, const char *path);
-static int set_sort_flags(int r);
-
-/* Functions */
-
-static void sigint_handler(int UNUSED(sig))
-{
-       g_state.interrupt = 1;
-}
-
-static void clean_exit_sighandler(int UNUSED(sig))
-{
-       exitcurses();
-       /* This triggers cleanup() thanks to atexit() */
-       exit(EXIT_SUCCESS);
-}
-
-static char *xitoa(uint val)
-{
-       static char ascbuf[32] = {0};
-       int i = 30;
-       uint rem;
-
-       if (!val)
-               return "0";
-
-       while (val && i) {
-               rem = val / 10;
-               ascbuf[i] = '0' + (val - (rem * 10));
-               val = rem;
-               --i;
-       }
-
-       return &ascbuf[++i];
-}
-
-/* Return the integer value of a char representing HEX */
-static uchar xchartohex(uchar c)
-{
-       if (xisdigit(c))
-               return c - '0';
-
-       if (c >= 'a' && c <= 'f')
-               return c - 'a' + 10;
-
-       if (c >= 'A' && c <= 'F')
-               return c - 'A' + 10;
-
-       return c;
-}
-
-/*
- * Source: https://elixir.bootlin.com/linux/latest/source/arch/alpha/include/asm/bitops.h
- */
-static bool test_set_bit(uint nr)
-{
-       nr &= HASH_BITS;
-
-       ull *m = ((ull *)ihashbmp) + (nr >> 6);
-
-       if (*m & (1 << (nr & 63)))
-               return FALSE;
-
-       *m |= 1 << (nr & 63);
-
-       return TRUE;
-}
-
-#if 0
-static bool test_clear_bit(uint nr)
-{
-       nr &= HASH_BITS;
-
-       ull *m = ((ull *) ihashbmp) + (nr >> 6);
-
-       if (!(*m & (1 << (nr & 63))))
-               return FALSE;
-
-       *m &= ~(1 << (nr & 63));
-       return TRUE;
-}
-#endif
-
-/* Increase the limit on open file descriptors, if possible */
-static rlim_t max_openfds(void)
-{
-       struct rlimit rl;
-       rlim_t limit = getrlimit(RLIMIT_NOFILE, &rl);
-
-       if (!limit) {
-               limit = rl.rlim_cur;
-               rl.rlim_cur = rl.rlim_max;
-
-               /* Return ~75% of max possible */
-               if (setrlimit(RLIMIT_NOFILE, &rl) == 0) {
-                       limit = rl.rlim_max - (rl.rlim_max >> 2);
-                       /*
-                        * 20K is arbitrary. If the limit is set to max possible
-                        * value, the memory usage increases to more than double.
-                        */
-                       if (limit > 20480)
-                               limit = 20480;
-               }
-       } else
-               limit = 32;
-
-       return limit;
-}
-
-/*
- * Wrapper to realloc()
- * Frees current memory if realloc() fails and returns NULL.
- *
- * As per the docs, the *alloc() family is supposed to be memory aligned:
- * Ubuntu: http://manpages.ubuntu.com/manpages/xenial/man3/malloc.3.html
- * macOS: https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man3/malloc.3.html
- */
-static void *xrealloc(void *pcur, size_t len)
-{
-       void *pmem = realloc(pcur, len);
-
-       if (!pmem)
-               free(pcur);
-
-       return pmem;
-}
-
-/*
- * Just a safe strncpy(3)
- * Always null ('\0') terminates if both src and dest are valid pointers.
- * Returns the number of bytes copied including terminating null byte.
- */
-static size_t xstrsncpy(char *restrict dst, const char *restrict src, size_t n)
-{
-       char *end = memccpy(dst, src, '\0', n);
-
-       if (!end) {
-               dst[n - 1] = '\0'; // NOLINT
-               end = dst + n; /* If we return n here, binary size increases due to auto-inlining */
-       }
-
-       return end - dst;
-}
-
-static inline size_t xstrlen(const char *restrict s)
-{
-#if !defined(__GLIBC__)
-       return strlen(s); // NOLINT
-#else
-       return (char *)rawmemchr(s, '\0') - s; // NOLINT
-#endif
-}
-
-static char *xstrdup(const char *restrict s)
-{
-       size_t len = xstrlen(s) + 1;
-       char *ptr = malloc(len);
-
-       if (ptr)
-               xstrsncpy(ptr, s, len);
-       return ptr;
-}
-
-static bool is_suffix(const char *restrict str, const char *restrict suffix)
-{
-       if (!str || !suffix)
-               return FALSE;
-
-       size_t lenstr = xstrlen(str);
-       size_t lensuffix = xstrlen(suffix);
-
-       if (lensuffix > lenstr)
-               return FALSE;
-
-       return (xstrcmp(str + (lenstr - lensuffix), suffix) == 0);
-}
-
-static bool is_prefix(const char *restrict str, const char *restrict prefix, size_t len)
-{
-       return !strncmp(str, prefix, len);
-}
-
-/*
- * The poor man's implementation of memrchr(3).
- * We are only looking for '/' in this program.
- * And we are NOT expecting a '/' at the end.
- * Ideally 0 < n <= xstrlen(s).
- */
-static void *xmemrchr(uchar *restrict s, uchar ch, size_t n)
-{
-#if defined(__GLIBC__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
-       return memrchr(s, ch, n);
-#else
-
-       if (!s || !n)
-               return NULL;
-
-       uchar *ptr = s + n;
-
-       do
-               if (*--ptr == ch)
-                       return ptr;
-       while (s != ptr);
-
-       return NULL;
-#endif
-}
-
-/* A very simplified implementation, changes path */
-static char *xdirname(char *path)
-{
-       char *base = xmemrchr((uchar *)path, '/', xstrlen(path));
-
-       if (base == path)
-               path[1] = '\0';
-       else
-               *base = '\0';
-
-       return path;
-}
-
-static char *xbasename(char *path)
-{
-       char *base = xmemrchr((uchar *)path, '/', xstrlen(path)); // NOLINT
-
-       return base ? base + 1 : path;
-}
-
-static char *xextension(const char *fname, size_t len)
-{
-       return xmemrchr((uchar *)fname, '.', len);
-}
-
-static inline bool getutil(char *util)
-{
-       return spawn("which", util, NULL, F_NORMAL | F_NOTRACE) == 0;
-}
-
-/*
- * Updates out with "dir/name or "/name"
- * Returns the number of bytes copied including the terminating NULL byte
- */
-static size_t mkpath(const char *dir, const char *name, char *out)
-{
-       size_t len;
-
-       /* Handle absolute path */
-       if (name[0] == '/') // NOLINT
-               return xstrsncpy(out, name, PATH_MAX);
-
-       /* Handle root case */
-       if (istopdir(dir))
-               len = 1;
-       else
-               len = xstrsncpy(out, dir, PATH_MAX);
-
-       out[len - 1] = '/'; // NOLINT
-       return (xstrsncpy(out + len, name, PATH_MAX - len) + len);
-}
-
-/* Assumes both the paths passed are directories */
-static char *common_prefix(const char *path, char *prefix)
-{
-       const char *x = path, *y = prefix;
-       char *sep;
-
-       if (!path || !*path || !prefix)
-               return NULL;
-
-       if (!*prefix) {
-               xstrsncpy(prefix, path, PATH_MAX);
-               return prefix;
-       }
-
-       while (*x && *y && (*x == *y))
-               ++x, ++y;
-
-       /* Strings are same */
-       if (!*x && !*y)
-               return prefix;
-
-       /* Path is shorter */
-       if (!*x && *y == '/') {
-               xstrsncpy(prefix, path, y - path);
-               return prefix;
-       }
-
-       /* Prefix is shorter */
-       if (!*y && *x == '/')
-               return prefix;
-
-       /* Shorten prefix */
-       prefix[y - prefix] = '\0';
-
-       sep = xmemrchr((uchar *)prefix, '/', y - prefix);
-       if (sep != prefix)
-               *sep = '\0';
-       else /* Just '/' */
-               prefix[1] = '\0';
-
-       return prefix;
-}
-
-/*
- * The library function realpath() resolves symlinks.
- * If there's a symlink in file list we want to show the symlink not what it's points to.
- */
-static char *abspath(const char *path, const char *cwd)
-{
-       if (!path || !cwd)
-               return NULL;
-
-       size_t dst_size = 0, src_size = xstrlen(path), cwd_size = xstrlen(cwd);
-       size_t len = src_size;
-       const char *src;
-       char *dst;
-       /*
-        * We need to add 2 chars at the end as relative paths may start with:
-        * ./ (find .)
-        * no separator (fd .): this needs an additional char for '/'
-        */
-       char *resolved_path = malloc(src_size + (*path == '/' ? 0 : cwd_size) + 2);
-       if (!resolved_path)
-               return NULL;
-
-       /* Turn relative paths into absolute */
-       if (path[0] != '/')
-               dst_size = xstrsncpy(resolved_path, cwd, cwd_size + 1) - 1;
-       else
-               resolved_path[0] = '\0';
-
-       src = path;
-       dst = resolved_path + dst_size;
-       for (const char *next = NULL; next != path + src_size;) {
-               next = memchr(src, '/', len);
-               if (!next)
-                       next = path + src_size;
-
-               if (next - src == 2 && src[0] == '.' && src[1] == '.') {
-                       if (dst - resolved_path) {
-                               dst = xmemrchr((uchar *)resolved_path, '/', dst - resolved_path);
-                               *dst = '\0';
-                       }
-               } else if (next - src == 1 && src[0] == '.') {
-                       /* NOP */
-               } else if (next - src) {
-                       *(dst++) = '/';
-                       xstrsncpy(dst, src, next - src + 1);
-                       dst += next - src;
-               }
-
-               src = next + 1;
-               len = src_size - (src - path);
-       }
-
-       if (*resolved_path == '\0') {
-               resolved_path[0] = '/';
-               resolved_path[1] = '\0';
-       }
-
-       return resolved_path;
-}
-
-static int create_tmp_file(void)
-{
-       xstrsncpy(g_tmpfpath + tmpfplen - 1, messages[STR_TMPFILE], TMP_LEN_MAX - tmpfplen);
-
-       int fd = mkstemp(g_tmpfpath);
-
-       if (fd == -1) {
-               DPRINTF_S(strerror(errno));
-       }
-
-       return fd;
-}
-
-static void clearinfoln(void)
-{
-       move(xlines - 2, 0);
-       clrtoeol();
-}
-
-#ifdef KEY_RESIZE
-/* Clear the old prompt */
-static void clearoldprompt(void)
-{
-       clearinfoln();
-       tolastln();
-       addch('\n');
-}
-#endif
-
-/* Messages show up at the bottom */
-static inline void printmsg_nc(const char *msg)
-{
-       tolastln();
-       addstr(msg);
-       addch('\n');
-}
-
-static void printmsg(const char *msg)
-{
-       attron(COLOR_PAIR(cfg.curctx + 1));
-       printmsg_nc(msg);
-       attroff(COLOR_PAIR(cfg.curctx + 1));
-}
-
-static void printwait(const char *msg, int *presel)
-{
-       printmsg(msg);
-       if (presel) {
-               *presel = MSGWAIT;
-               if (ndents)
-                       xstrsncpy(g_ctx[cfg.curctx].c_name, pdents[cur].name, NAME_MAX + 1);
-       }
-}
-
-/* Kill curses and display error before exiting */
-static void printerr(int linenum)
-{
-       exitcurses();
-       perror(xitoa(linenum));
-       if (!g_state.picker && selpath)
-               unlink(selpath);
-       free(pselbuf);
-       exit(1);
-}
-
-static inline bool xconfirm(int c)
-{
-       return (c == 'y' || c == 'Y');
-}
-
-static int get_input(const char *prompt)
-{
-       if (prompt)
-               printmsg(prompt);
-       cleartimeout();
-
-       int r = getch();
-
-#ifdef KEY_RESIZE
-       while (r == KEY_RESIZE) {
-               if (prompt) {
-                       clearoldprompt();
-                       xlines = LINES;
-                       printmsg(prompt);
-               }
-
-               r = getch();
-       }
-#endif
-       settimeout();
-       return r;
-}
-
-static int get_cur_or_sel(void)
-{
-       if (selbufpos && ndents) {
-               if (cfg.prefersel)
-                       return 's';
-
-               int choice = get_input(messages[MSG_CUR_SEL_OPTS]);
-
-               return ((choice == 'c' || choice == 's') ? choice : 0);
-       }
-
-       if (selbufpos)
-               return 's';
-
-       if (ndents)
-               return 'c';
-
-       return 0;
-}
-
-static void xdelay(useconds_t delay)
-{
-       refresh();
-       usleep(delay);
-}
-
-static char confirm_force(bool selection)
-{
-       char str[64];
-
-       snprintf(str, 64, messages[MSG_FORCE_RM],
-                (selection ? xitoa(nselected) : "current"), (selection ? "(s)" : ""));
-
-       int r = get_input(str);
-
-       if (r == 27)
-               return '\0'; /* cancel */
-       if (r == 'y' || r == 'Y')
-               return 'f'; /* forceful */
-       return 'i'; /* interactive */
-}
-
-/* Writes buflen char(s) from buf to a file */
-static void writesel(const char *buf, const size_t buflen)
-{
-       if (g_state.pickraw || !selpath)
-               return;
-
-       FILE *fp = fopen(selpath, "w");
-
-       if (fp) {
-               if (fwrite(buf, 1, buflen, fp) != buflen)
-                       printwarn(NULL);
-               fclose(fp);
-       } else
-               printwarn(NULL);
-}
-
-static void appendfpath(const char *path, const size_t len)
-{
-       if ((selbufpos >= selbuflen) || ((len + 3) > (selbuflen - selbufpos))) {
-               selbuflen += PATH_MAX;
-               pselbuf = xrealloc(pselbuf, selbuflen);
-               if (!pselbuf)
-                       errexit();
-       }
-
-       selbufpos += xstrsncpy(pselbuf + selbufpos, path, len);
-}
-
-/* Write selected file paths to fd, linefeed separated */
-static size_t seltofile(int fd, uint *pcount)
-{
-       uint lastpos, count = 0;
-       char *pbuf = pselbuf;
-       size_t pos = 0;
-       ssize_t len, prefixlen = 0, initlen = 0;
-
-       if (pcount)
-               *pcount = 0;
-
-       if (!selbufpos)
-               return 0;
-
-       lastpos = selbufpos - 1;
-
-       if (listpath) {
-               prefixlen = (ssize_t)xstrlen(listroot);
-               initlen = (ssize_t)xstrlen(listpath);
-       }
-
-       while (pos <= lastpos) {
-               DPRINTF_S(pbuf);
-               len = (ssize_t)xstrlen(pbuf);
-
-               if (!listpath || !is_prefix(pbuf, listpath, initlen)) {
-                       if (write(fd, pbuf, len) != len)
-                               return pos;
-               } else {
-                       if (write(fd, listroot, prefixlen) != prefixlen)
-                               return pos;
-                       if (write(fd, pbuf + initlen, len - initlen) != (len - initlen))
-                               return pos;
-               }
-
-               pos += len;
-               if (pos <= lastpos) {
-                       if (write(fd, "\n", 1) != 1)
-                               return pos;
-                       pbuf += len + 1;
-               }
-               ++pos;
-               ++count;
-       }
-
-       if (pcount)
-               *pcount = count;
-
-       return pos;
-}
-
-/* List selection from selection file (another instance) */
-static bool listselfile(void)
-{
-       struct stat sb;
-
-       if (stat(selpath, &sb) == -1)
-               return FALSE;
-
-       /* Nothing selected if file size is 0 */
-       if (!sb.st_size)
-               return FALSE;
-
-       snprintf(g_buf, CMD_LEN_MAX, "tr \'\\0\' \'\\n\' < %s", selpath);
-       spawn(utils[UTIL_SH_EXEC], g_buf, NULL, F_CLI | F_CONFIRM);
-
-       return TRUE;
-}
-
-/* Reset selection indicators */
-static void resetselind(void)
-{
-       for (int r = 0; r < ndents; ++r)
-               if (pdents[r].flags & FILE_SELECTED)
-                       pdents[r].flags &= ~FILE_SELECTED;
-}
-
-static void startselection(void)
-{
-       if (!g_state.selmode) {
-               g_state.selmode = 1;
-               nselected = 0;
-
-               if (selbufpos) {
-                       resetselind();
-                       writesel(NULL, 0);
-                       selbufpos = 0;
-               }
-
-               lastappendpos = 0;
-       }
-}
-
-static void updateselbuf(const char *path, char *newpath)
-{
-       size_t r;
-
-       for (int i = 0; i < ndents; ++i)
-               if (pdents[i].flags & FILE_SELECTED) {
-                       r = mkpath(path, pdents[i].name, newpath);
-                       appendfpath(newpath, r);
-               }
-}
-
-/* Finish selection procedure before an operation */
-static void endselection(void)
-{
-       int fd;
-       ssize_t count;
-       char buf[sizeof(patterns[P_REPLACE]) + PATH_MAX + (TMP_LEN_MAX << 1)];
-
-       if (g_state.selmode)
-               g_state.selmode = 0;
-
-       if (!listpath || !selbufpos)
-               return;
-
-       fd = create_tmp_file();
-       if (fd == -1) {
-               DPRINTF_S("couldn't create tmp file");
-               return;
-       }
-
-       seltofile(fd, NULL);
-       if (close(fd)) {
-               DPRINTF_S(strerror(errno));
-               printwarn(NULL);
-               return;
-       }
-
-       snprintf(buf, sizeof(buf), patterns[P_REPLACE], listpath, listroot, g_tmpfpath);
-       spawn(utils[UTIL_SH_EXEC], buf, NULL, F_CLI);
-
-       fd = open(g_tmpfpath, O_RDONLY);
-       if (fd == -1) {
-               DPRINTF_S(strerror(errno));
-               printwarn(NULL);
-               if (unlink(g_tmpfpath)) {
-                       DPRINTF_S(strerror(errno));
-                       printwarn(NULL);
-               }
-               return;
-       }
-
-       count = read(fd, pselbuf, selbuflen);
-       if (count < 0) {
-               DPRINTF_S(strerror(errno));
-               printwarn(NULL);
-               if (close(fd) || unlink(g_tmpfpath)) {
-                       DPRINTF_S(strerror(errno));
-               }
-               return;
-       }
-
-       if (close(fd) || unlink(g_tmpfpath)) {
-               DPRINTF_S(strerror(errno));
-               printwarn(NULL);
-               return;
-       }
-
-       selbufpos = count;
-       pselbuf[--count] = '\0';
-       for (--count; count > 0; --count)
-               if (pselbuf[count] == '\n' && pselbuf[count+1] == '/')
-                       pselbuf[count] = '\0';
-
-       writesel(pselbuf, selbufpos - 1);
-}
-
-static void clearselection(void)
-{
-       nselected = 0;
-       selbufpos = 0;
-       g_state.selmode = 0;
-       writesel(NULL, 0);
-}
-
-/* Returns: 1 - success, 0 - none selected, -1 - other failure */
-static int editselection(void)
-{
-       int ret = -1;
-       int fd, lines = 0;
-       ssize_t count;
-       struct stat sb;
-       time_t mtime;
-
-       if (!selbufpos)
-               return listselfile();
-
-       fd = create_tmp_file();
-       if (fd == -1) {
-               DPRINTF_S("couldn't create tmp file");
-               return -1;
-       }
-
-       seltofile(fd, NULL);
-       if (close(fd)) {
-               DPRINTF_S(strerror(errno));
-               return -1;
-       }
-
-       /* Save the last modification time */
-       if (stat(g_tmpfpath, &sb)) {
-               DPRINTF_S(strerror(errno));
-               unlink(g_tmpfpath);
-               return -1;
-       }
-       mtime = sb.st_mtime;
-
-       spawn((cfg.waitedit ? enveditor : editor), g_tmpfpath, NULL, F_CLI);
-
-       fd = open(g_tmpfpath, O_RDONLY);
-       if (fd == -1) {
-               DPRINTF_S(strerror(errno));
-               unlink(g_tmpfpath);
-               return -1;
-       }
-
-       fstat(fd, &sb);
-
-       if (mtime == sb.st_mtime) {
-               DPRINTF_S("selection is not modified");
-               unlink(g_tmpfpath);
-               return 1;
-       }
-
-       if (sb.st_size > selbufpos) {
-               DPRINTF_S("edited buffer larger than previous");
-               unlink(g_tmpfpath);
-               goto emptyedit;
-       }
-
-       count = read(fd, pselbuf, selbuflen);
-       if (count < 0) {
-               DPRINTF_S(strerror(errno));
-               printwarn(NULL);
-               if (close(fd) || unlink(g_tmpfpath)) {
-                       DPRINTF_S(strerror(errno));
-                       printwarn(NULL);
-               }
-               goto emptyedit;
-       }
-
-       if (close(fd) || unlink(g_tmpfpath)) {
-               DPRINTF_S(strerror(errno));
-               printwarn(NULL);
-               goto emptyedit;
-       }
-
-       if (!count) {
-               ret = 1;
-               goto emptyedit;
-       }
-
-       resetselind();
-       selbufpos = count;
-       /* The last character should be '\n' */
-       pselbuf[--count] = '\0';
-       for (--count; count > 0; --count) {
-               /* Replace every '\n' that separates two paths */
-               if (pselbuf[count] == '\n' && pselbuf[count + 1] == '/') {
-                       ++lines;
-                       pselbuf[count] = '\0';
-               }
-       }
-
-       /* Add a line for the last file */
-       ++lines;
-
-       if (lines > nselected) {
-               DPRINTF_S("files added to selection");
-               goto emptyedit;
-       }
+#include "dbg.h"
+#include "nnn.h"
 
-       nselected = lines;
-       writesel(pselbuf, selbufpos - 1);
+/* Macro definitions */
+#define VERSION "3.3"
+#define GENERAL_INFO "BSD 2-Clause\nhttps://github.com/jarun/nnn"
+#define SESSIONS_VERSION 1
 
-       return 1;
+/* Forward declarations */
+static void redraw(char *path);
+static void move_cursor(int target, int ignore_scrolloff);
+static char *load_input(int fd, const char *path);
+static int set_sort_flags(int r);
+static int (*nftw_fn)(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf);
 
-emptyedit:
-       resetselind();
-       clearselection();
-       return ret;
-}
+/* FUNCTIONS */
 
-static bool selsafe(void)
+static inline bool getutil(char *util)
 {
-       /* Fail if selection file path not generated */
-       if (!selpath) {
-               printmsg(messages[MSG_SEL_MISSING]);
-               return FALSE;
-       }
-
-       /* Fail if selection file path isn't accessible */
-       if (access(selpath, R_OK | W_OK) == -1) {
-               errno == ENOENT ? printmsg(messages[MSG_0_SELECTED]) : printwarn(NULL);
-               return FALSE;
-       }
-
-       return TRUE;
+       return spawn("which", util, NULL, F_NORMAL | F_NOTRACE) == 0;
 }
 
 static void export_file_list(void)
@@ -1720,180 +287,6 @@ static bool initcurses(void *oldmask)
        return TRUE;
 }
 
-/* No NULL check here as spawn() guards against it */
-static int parseargs(char *line, char **argv)
-{
-       int count = 0;
-
-       argv[count++] = line;
-
-       while (*line) { // NOLINT
-               if (ISBLANK(*line)) {
-                       *line++ = '\0';
-
-                       if (!*line) // NOLINT
-                               return count;
-
-                       argv[count++] = line;
-                       if (count == EXEC_ARGS_MAX)
-                               return -1;
-               }
-
-               ++line;
-       }
-
-       return count;
-}
-
-static pid_t xfork(uchar flag)
-{
-       int status;
-       pid_t p = fork();
-       struct sigaction dfl_act = {.sa_handler = SIG_DFL};
-
-       if (p > 0) {
-               /* the parent ignores the interrupt, quit and hangup signals */
-               sigaction(SIGHUP, &(struct sigaction){.sa_handler = SIG_IGN}, &oldsighup);
-               sigaction(SIGTSTP, &dfl_act, &oldsigtstp);
-       } else if (p == 0) {
-               /* We create a grandchild to detach */
-               if (flag & F_NOWAIT) {
-                       p = fork();
-
-                       if (p > 0)
-                               _exit(EXIT_SUCCESS);
-                       else if (p == 0) {
-                               sigaction(SIGHUP, &dfl_act, NULL);
-                               sigaction(SIGINT, &dfl_act, NULL);
-                               sigaction(SIGQUIT, &dfl_act, NULL);
-                               sigaction(SIGTSTP, &dfl_act, NULL);
-
-                               setsid();
-                               return p;
-                       }
-
-                       perror("fork");
-                       _exit(EXIT_FAILURE);
-               }
-
-               /* so they can be used to stop the child */
-               sigaction(SIGHUP, &dfl_act, NULL);
-               sigaction(SIGINT, &dfl_act, NULL);
-               sigaction(SIGQUIT, &dfl_act, NULL);
-               sigaction(SIGTSTP, &dfl_act, NULL);
-       }
-
-       /* This is the parent waiting for the child to create grandchild*/
-       if (flag & F_NOWAIT)
-               waitpid(p, &status, 0);
-
-       if (p == -1)
-               perror("fork");
-       return p;
-}
-
-static int join(pid_t p, uchar flag)
-{
-       int status = 0xFFFF;
-
-       if (!(flag & F_NOWAIT)) {
-               /* wait for the child to exit */
-               do {
-               } while (waitpid(p, &status, 0) == -1);
-
-               if (WIFEXITED(status)) {
-                       status = WEXITSTATUS(status);
-                       DPRINTF_D(status);
-               }
-       }
-
-       /* restore parent's signal handling */
-       sigaction(SIGHUP, &oldsighup, NULL);
-       sigaction(SIGTSTP, &oldsigtstp, NULL);
-
-       return status;
-}
-
-/*
- * Spawns a child process. Behaviour can be controlled using flag.
- * Limited to 2 arguments to a program, flag works on bit set.
- */
-static int spawn(char *file, char *arg1, char *arg2, uchar flag)
-{
-       pid_t pid;
-       int status = 0, retstatus = 0xFFFF;
-       char *argv[EXEC_ARGS_MAX] = {0};
-       char *cmd = NULL;
-
-       if (!file || !*file)
-               return retstatus;
-
-       /* Swap args if the first arg is NULL and second isn't */
-       if (!arg1 && arg2) {
-               arg1 = arg2;
-               arg2 = NULL;
-       }
-
-       if (flag & F_MULTI) {
-               size_t len = xstrlen(file) + 1;
-
-               cmd = (char *)malloc(len);
-               if (!cmd) {
-                       DPRINTF_S("malloc()!");
-                       return retstatus;
-               }
-
-               xstrsncpy(cmd, file, len);
-               status = parseargs(cmd, argv);
-               if (status == -1 || status > (EXEC_ARGS_MAX - 3)) { /* arg1, arg2 and last NULL */
-                       free(cmd);
-                       DPRINTF_S("NULL or too many args");
-                       return retstatus;
-               }
-       } else
-               argv[status++] = file;
-
-       argv[status] = arg1;
-       argv[++status] = arg2;
-
-       if (flag & F_NORMAL)
-               exitcurses();
-
-       pid = xfork(flag);
-       if (pid == 0) {
-               /* Suppress stdout and stderr */
-               if (flag & F_NOTRACE) {
-                       int fd = open("/dev/null", O_WRONLY, 0200);
-
-                       dup2(fd, 1);
-                       dup2(fd, 2);
-                       close(fd);
-               }
-
-               execvp(*argv, argv);
-               _exit(EXIT_SUCCESS);
-       } else {
-               retstatus = join(pid, flag);
-
-               DPRINTF_D(pid);
-
-               if ((flag & F_CONFIRM) || ((flag & F_CHKRTN) && retstatus)) {
-                       printf("%s", messages[MSG_CONTINUE]);
-#ifndef NORL
-                       fflush(stdout);
-#endif
-                       while (getchar() != '\n');
-               }
-
-               if (flag & F_NORMAL)
-                       refresh();
-
-               free(cmd);
-       }
-
-       return retstatus;
-}
-
 static void prompt_run(char *cmd, const char *current)
 {
        setenv(envs[ENV_NCUR], current, 1);
index 57c8649f4a5f18a5e944abaa8632b3fc84c8127b..a6e67ec0a2e61a043463b1e26be789b83ec58cf7 100644 (file)
--- a/src/nnn.h
+++ b/src/nnn.h
 
 #include <curses.h>
 
+#ifndef S_BLKSIZE
+#define S_BLKSIZE 512 /* S_BLKSIZE is missing on Android NDK (Termux) */
+#endif
+
+/*
+ * NAME_MAX and PATH_MAX may not exist, e.g. with dirent.c_name being a
+ * flexible array on Illumos. Use somewhat accomodating fallback values.
+ */
+#ifndef NAME_MAX
+#define NAME_MAX 255
+#endif
+
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
 #define CONTROL(c) ((c) & 0x1f)
+#define _ABSSUB(N, M) (((N) <= (M)) ? ((M) - (N)) : ((N) - (M)))
+#define DOUBLECLICK_INTERVAL_NS (400000000)
+#define XDELAY_INTERVAL_MS (350000) /* 350 ms delay */
+#define ELEMENTS(x) (sizeof(x) / sizeof(*(x)))
+#undef MIN
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#undef MAX
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+#define ISODD(x) ((x) & 1)
+#define ISBLANK(x) ((x) == ' ' || (x) == '\t')
+#define TOUPPER(ch) (((ch) >= 'a' && (ch) <= 'z') ? ((ch) - 'a' + 'A') : (ch))
+#define CMD_LEN_MAX (PATH_MAX + ((NAME_MAX + 1) << 1))
+#define READLINE_MAX 256
+#define FILTER '/'
+#define RFILTER '\\'
+#define CASE ':'
+#define MSGWAIT '$'
+#define SELECT ' '
+#define REGEX_MAX 48
+#define ENTRY_INCR 64 /* Number of dir 'entry' structures to allocate per shot */
+#define NAMEBUF_INCR 0x800 /* 64 dir entries at once, avg. 32 chars per filename = 64*32B = 2KB */
+#define DESCRIPTOR_LEN 32
+#define _ALIGNMENT 0x10 /* 16-byte alignment */
+#define _ALIGNMENT_MASK 0xF
+#define TMP_LEN_MAX 64
+#define DOT_FILTER_LEN 7
+#define ASCII_MAX 128
+#define EXEC_ARGS_MAX 8
+#define LIST_FILES_MAX (1 << 16)
+#define SCROLLOFF 3
+
+#ifndef CTX8
+#define CTX_MAX 4
+#else
+#define CTX_MAX 8
+#endif
+
+#define MIN_DISPLAY_COLS ((CTX_MAX * 2) + 2) /* Two chars for [ and ] */
+#define LONG_SIZE sizeof(ulong)
+#define ARCHIVE_CMD_LEN 16
+#define BLK_SHIFT_512 9
+
+/* Detect hardlinks in du */
+#define HASH_BITS (0xFFFFFF)
+#define HASH_OCTETS (HASH_BITS >> 6) /* 2^6 = 64 */
+
+/* Entry flags */
+#define DIR_OR_LINK_TO_DIR 0x01
+#define HARD_LINK 0x02
+#define SYM_ORPHAN 0x04
+#define FILE_MISSING 0x08
+#define FILE_SELECTED 0x10
+
+/* Macros to define process spawn behaviour as flags */
+#define F_NONE    0x00  /* no flag set */
+#define F_MULTI   0x01  /* first arg can be combination of args; to be used with F_NORMAL */
+#define F_NOWAIT  0x02  /* don't wait for child process (e.g. file manager) */
+#define F_NOTRACE 0x04  /* suppress stdout and strerr (no traces) */
+#define F_NORMAL  0x08  /* spawn child process in non-curses regular CLI mode */
+#define F_CONFIRM 0x10  /* run command - show results before exit (must have F_NORMAL) */
+#define F_CHKRTN  0x20  /* wait for user prompt if cmd returns failure status */
+#define F_CLI     (F_NORMAL | F_MULTI)
+#define F_SILENT  (F_CLI | F_NOTRACE)
+
+/* Version compare macros */
+/*
+ * states: S_N: normal, S_I: comparing integral part, S_F: comparing
+ *         fractional parts, S_Z: idem but with leading Zeroes only
+ */
+#define S_N 0x0
+#define S_I 0x3
+#define S_F 0x6
+#define S_Z 0x9
+
+/* result_type: VCMP: return diff; VLEN: compare using len_diff/diff */
+#define VCMP 2
+#define VLEN 3
+
+/* Volume info */
+#define FREE 0
+#define CAPACITY 1
+
+/* TYPE DEFINITIONS */
+typedef unsigned long ulong;
+typedef unsigned int uint;
+typedef unsigned char uchar;
+typedef unsigned short ushort;
+typedef long long ll;
+typedef unsigned long long ull;
+
+/* STRUCTURES */
 
 /* Supported actions */
 enum action {
@@ -274,3 +381,1509 @@ static struct key bindings[] = {
        { KEY_MOUSE,      SEL_CLICK },
 #endif
 };
+
+/* Directory entry */
+typedef struct entry {
+       char *name;
+       time_t t;
+       off_t size;
+       blkcnt_t blocks; /* number of 512B blocks allocated */
+       mode_t mode;
+       ushort nlen; /* Length of file name */
+       uchar flags; /* Flags specific to the file */
+} *pEntry;
+
+/* Key-value pairs from env */
+typedef struct {
+       int key;
+       int off;
+} kv;
+
+typedef struct {
+#ifdef PCRE
+       const pcre *pcrex;
+#else
+       const regex_t *regex;
+#endif
+       const char *str;
+} fltrexp_t;
+
+/*
+ * Settings
+ * NOTE: update default values if changing order
+ */
+typedef struct {
+       uint filtermode : 1;  /* Set to enter filter mode */
+       uint timeorder  : 1;  /* Set to sort by time */
+       uint sizeorder  : 1;  /* Set to sort by file size */
+       uint apparentsz : 1;  /* Set to sort by apparent size (disk usage) */
+       uint blkorder   : 1;  /* Set to sort by blocks used (disk usage) */
+       uint extnorder  : 1;  /* Order by extension */
+       uint showhidden : 1;  /* Set to show hidden files */
+       uint reserved0  : 1;
+       uint showdetail : 1;  /* Clear to show lesser file info */
+       uint ctxactive  : 1;  /* Context active or not */
+       uint reverse    : 1;  /* Reverse sort */
+       uint version    : 1;  /* Version sort */
+       uint reserved1  : 1;
+       /* The following settings are global */
+       uint curctx     : 3;  /* Current context number */
+       uint prefersel  : 1;  /* Prefer selection over current, if exists */
+       uint reserved2  : 1;
+       uint nonavopen  : 1;  /* Open file on right arrow or `l` */
+       uint autoselect : 1;  /* Auto-select dir in type-to-nav mode */
+       uint cursormode : 1;  /* Move hardware cursor with selection */
+       uint useeditor  : 1;  /* Use VISUAL to open text files */
+       uint reserved3  : 3;
+       uint regex      : 1;  /* Use regex filters */
+       uint x11        : 1;  /* Copy to system clipboard and show notis */
+       uint timetype   : 2;  /* Time sort type (0: access, 1: change, 2: modification) */
+       uint cliopener  : 1;  /* All-CLI app opener */
+       uint waitedit   : 1;  /* For ops that can't be detached, used EDITOR */
+       uint rollover   : 1;  /* Roll over at edges */
+} settings;
+
+/* Non-persistent program-internal states */
+typedef struct {
+       uint pluginit   : 1;  /* Plugin framework initialized */
+       uint interrupt  : 1;  /* Program received an interrupt */
+       uint rangesel   : 1;  /* Range selection on */
+       uint move       : 1;  /* Move operation */
+       uint autonext   : 1;  /* Auto-proceed on open */
+       uint fortune    : 1;  /* Show fortune messages in help */
+       uint trash      : 1;  /* Use trash to delete files */
+       uint forcequit  : 1;  /* Do not prompt on quit */
+       uint autofifo   : 1;  /* Auto-create NNN_FIFO */
+       uint initfile   : 1;  /* Positional arg is a file */
+       uint dircolor   : 1;  /* Current status of dir color */
+       uint picker     : 1;  /* Write selection to user-specified file */
+       uint pickraw    : 1;  /* Write selection to sdtout before exit */
+       uint runplugin  : 1;  /* Choose plugin mode */
+       uint runctx     : 2;  /* The context in which plugin is to be run */
+       uint selmode    : 1;  /* Set when selecting files */
+       uint oldcolor   : 1;  /* Show dirs in context colors */
+       uint reserved   : 14;
+} runstate;
+
+/* Contexts or workspaces */
+typedef struct {
+       char c_path[PATH_MAX]; /* Current dir */
+       char c_last[PATH_MAX]; /* Last visited dir */
+       char c_name[NAME_MAX + 1]; /* Current file name */
+       char c_fltr[REGEX_MAX]; /* Current filter */
+       settings c_cfg; /* Current configuration */
+       uint color; /* Color code for directories */
+} context;
+
+typedef struct {
+       size_t ver;
+       size_t pathln[CTX_MAX];
+       size_t lastln[CTX_MAX];
+       size_t nameln[CTX_MAX];
+       size_t fltrln[CTX_MAX];
+} session_header_t;
+
+/* GLOBALS */
+
+/* Configuration, contexts */
+static settings cfg = {
+       0, /* filtermode */
+       0, /* timeorder */
+       0, /* sizeorder */
+       0, /* apparentsz */
+       0, /* blkorder */
+       0, /* extnorder */
+       0, /* showhidden */
+       0, /* reserved0 */
+       0, /* showdetail */
+       1, /* ctxactive */
+       0, /* reverse */
+       0, /* version */
+       0, /* reserved1 */
+       0, /* curctx */
+       0, /* prefersel */
+       0, /* reserved2 */
+       0, /* nonavopen */
+       1, /* autoselect */
+       0, /* cursormode */
+       0, /* useeditor */
+       0, /* reserved3 */
+       0, /* regex */
+       0, /* x11 */
+       2, /* timetype (T_MOD) */
+       0, /* cliopener */
+       0, /* waitedit */
+       1, /* rollover */
+};
+
+static context g_ctx[CTX_MAX] __attribute__ ((aligned));
+
+static int ndents, cur, last, curscroll, last_curscroll, total_dents = ENTRY_INCR, scroll_lines = 1;
+static int nselected;
+#ifndef NOFIFO
+static int fifofd = -1;
+#endif
+static uint idletimeout, selbufpos, lastappendpos, selbuflen;
+static ushort xlines, xcols;
+static ushort idle;
+static uchar maxbm, maxplug;
+static char *bmstr;
+static char *pluginstr;
+static char *opener;
+static char *editor;
+static char *enveditor;
+static char *pager;
+static char *shell;
+static char *home;
+static char *initpath;
+static char *cfgpath;
+static char *selpath;
+static char *listpath;
+static char *listroot;
+static char *plgpath;
+static char *pnamebuf, *pselbuf;
+static char *mark;
+#ifndef NOFIFO
+static char *fifopath;
+#endif
+static ull *ihashbmp;
+static struct entry *pdents;
+static blkcnt_t ent_blocks;
+static blkcnt_t dir_blocks;
+static ulong num_files;
+static kv *bookmark;
+static kv *plug;
+static uchar tmpfplen;
+static uchar blk_shift = BLK_SHIFT_512;
+#ifndef NOMOUSE
+static int middle_click_key;
+#endif
+#ifdef PCRE
+static pcre *archive_pcre;
+#else
+static regex_t archive_re;
+#endif
+
+/* Retain old signal handlers */
+static struct sigaction oldsighup;
+static struct sigaction oldsigtstp;
+
+/* For use in functions which are isolated and don't return the buffer */
+static char g_buf[CMD_LEN_MAX] __attribute__ ((aligned));
+
+/* Buffer to store tmp file path to show selection, file stats and help */
+static char g_tmpfpath[TMP_LEN_MAX] __attribute__ ((aligned));
+
+/* Buffer to store plugins control pipe location */
+static char g_pipepath[TMP_LEN_MAX] __attribute__ ((aligned));
+
+/* Non-persistent runtime states */
+static runstate g_state;
+
+/* Options to identify file mime */
+#if defined(__APPLE__)
+#define FILE_MIME_OPTS "-bIL"
+#elif !defined(__sun) /* no mime option for 'file' */
+#define FILE_MIME_OPTS "-biL"
+#endif
+
+/* Macros for utilities */
+#define UTIL_OPENER 0
+#define UTIL_ATOOL 1
+#define UTIL_BSDTAR 2
+#define UTIL_UNZIP 3
+#define UTIL_TAR 4
+#define UTIL_LOCKER 5
+#define UTIL_LAUNCH 6
+#define UTIL_SH_EXEC 7
+#define UTIL_BASH 8
+#define UTIL_ARCHIVEMOUNT 9
+#define UTIL_SSHFS 10
+#define UTIL_RCLONE 11
+#define UTIL_VI 12
+#define UTIL_LESS 13
+#define UTIL_SH 14
+#define UTIL_FZF 15
+#define UTIL_NTFY 16
+#define UTIL_CBCP 17
+#define UTIL_NMV 18
+
+/* Utilities to open files, run actions */
+static char * const utils[] = {
+#ifdef __APPLE__
+       "/usr/bin/open",
+#elif defined __CYGWIN__
+       "cygstart",
+#elif defined __HAIKU__
+       "open",
+#else
+       "xdg-open",
+#endif
+       "atool",
+       "bsdtar",
+       "unzip",
+       "tar",
+#ifdef __APPLE__
+       "bashlock",
+#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
+       "lock",
+#elif defined __HAIKU__
+       "peaclock",
+#else
+       "vlock",
+#endif
+       "launch",
+       "sh -c",
+       "bash",
+       "archivemount",
+       "sshfs",
+       "rclone",
+       "vi",
+       "less",
+       "sh",
+       "fzf",
+       ".ntfy",
+       ".cbcp",
+       ".nmv",
+};
+
+/* Common strings */
+#define MSG_NO_TRAVERSAL 0
+#define MSG_INVALID_KEY 1
+#define STR_TMPFILE 2
+#define MSG_0_SELECTED 3
+#define MSG_UTIL_MISSING 4
+#define MSG_FAILED 5
+#define MSG_SSN_NAME 6
+#define MSG_CP_MV_AS 7
+#define MSG_CUR_SEL_OPTS 8
+#define MSG_FORCE_RM 9
+#define MSG_LIMIT 10
+#define MSG_NEW_OPTS 11
+#define MSG_CLI_MODE 12
+#define MSG_OVERWRITE 13
+#define MSG_SSN_OPTS 14
+#define MSG_QUIT_ALL 15
+#define MSG_HOSTNAME 16
+#define MSG_ARCHIVE_NAME 17
+#define MSG_OPEN_WITH 18
+#define MSG_REL_PATH 19
+#define MSG_LINK_PREFIX 20
+#define MSG_COPY_NAME 21
+#define MSG_CONTINUE 22
+#define MSG_SEL_MISSING 23
+#define MSG_ACCESS 24
+#define MSG_EMPTY_FILE 25
+#define MSG_UNSUPPORTED 26
+#define MSG_NOT_SET 27
+#define MSG_EXISTS 28
+#define MSG_FEW_COLUMNS 29
+#define MSG_REMOTE_OPTS 30
+#define MSG_RCLONE_DELAY 31
+#define MSG_APP_NAME 32
+#define MSG_ARCHIVE_OPTS 33
+#define MSG_PLUGIN_KEYS 34
+#define MSG_BOOKMARK_KEYS 35
+#define MSG_INVALID_REG 36
+#define MSG_ORDER 37
+#define MSG_LAZY 38
+#define MSG_FIRST 39
+#define MSG_RM_TMP 40
+#define MSG_NOCHNAGE 41
+#define MSG_CANCEL 42
+#define MSG_0_ENTRIES 43
+#ifndef DIR_LIMITED_SELECTION
+#define MSG_DIR_CHANGED 44 /* Must be the last entry */
+#endif
+
+static const char * const messages[] = {
+       "no traversal",
+       "invalid key",
+       "/.nnnXXXXXX",
+       "0 selected",
+       "missing util",
+       "failed!",
+       "session name: ",
+       "'c'p / 'm'v as?",
+       "'c'urrent / 's'el?",
+       "rm -rf %s file%s? [Esc cancels]",
+       "limit exceeded",
+       "'f'ile / 'd'ir / 's'ym / 'h'ard?",
+       "'c'li / 'g'ui?",
+       "overwrite?",
+       "'s'ave / 'l'oad / 'r'estore?",
+       "Quit all contexts?",
+       "remote name ('-' for hovered): ",
+       "archive name: ",
+       "open with: ",
+       "relative path: ",
+       "link prefix [@ for none]: ",
+       "copy name: ",
+       "\n'Enter' to continue",
+       "open failed",
+       "dir inaccessible",
+       "empty: edit/open with",
+       "unknown",
+       "not set",
+       "entry exists",
+       "too few columns!",
+       "'s'shfs / 'r'clone?",
+       "refresh if slow",
+       "app name: ",
+       "'d'efault / e'x'tract / 'l'ist / 'm'ount?",
+       "plugin keys:",
+       "bookmark keys:",
+       "invalid regex",
+       "'a'u / 'd'u / 'e'xtn / 'r'ev / 's'ize / 't'ime / 'v'er / 'c'lear?",
+       "unmount failed! try lazy?",
+       "first file (\')/char?",
+       "remove tmp file?",
+       "unchanged",
+       "cancelled",
+       "0 entries",
+#ifndef DIR_LIMITED_SELECTION
+       "dir changed, range sel off", /* Must be the last entry */
+#endif
+};
+
+/* Supported configuration environment variables */
+#define NNN_OPTS 0
+#define NNN_BMS 1
+#define NNN_PLUG 2
+#define NNN_OPENER 3
+#define NNN_COLORS 4
+#define NNNLVL 5
+#define NNN_PIPE 6
+#define NNN_MCLICK 7
+#define NNN_SEL 8
+#define NNN_ARCHIVE 9 /* strings end here */
+#define NNN_TRASH 10 /* flags begin here */
+
+static const char * const env_cfg[] = {
+       "NNN_OPTS",
+       "NNN_BMS",
+       "NNN_PLUG",
+       "NNN_OPENER",
+       "NNN_COLORS",
+       "NNNLVL",
+       "NNN_PIPE",
+       "NNN_MCLICK",
+       "NNN_SEL",
+       "NNN_ARCHIVE",
+       "NNN_TRASH",
+};
+
+/* Required environment variables */
+#define ENV_SHELL 0
+#define ENV_VISUAL 1
+#define ENV_EDITOR 2
+#define ENV_PAGER 3
+#define ENV_NCUR 4
+
+static const char * const envs[] = {
+       "SHELL",
+       "VISUAL",
+       "EDITOR",
+       "PAGER",
+       "nnn",
+};
+
+/* Time type used */
+#define T_ACCESS 0
+#define T_CHANGE 1
+#define T_MOD 2
+
+#ifdef __linux__
+static char cp[] = "cp   -iRp";
+static char mv[] = "mv   -i";
+#else
+static char cp[] = "cp -iRp";
+static char mv[] = "mv -i";
+#endif
+
+/* Tokens used for path creation */
+#define TOK_SSN 0
+#define TOK_MNT 1
+#define TOK_PLG 2
+
+static const char * const toks[] = {
+       "sessions",
+       "mounts",
+       "plugins", /* must be the last entry */
+};
+
+/* Patterns */
+#define P_CPMVFMT 0
+#define P_CPMVRNM 1
+#define P_ARCHIVE 2
+#define P_REPLACE 3
+
+static const char * const patterns[] = {
+       "sed -i 's|^\\(\\(.*/\\)\\(.*\\)$\\)|#\\1\\n\\3|' %s",
+       "sed 's|^\\([^#/][^/]\\?.*\\)$|%s/\\1|;s|^#\\(/.*\\)$|\\1|' "
+               "%s | tr '\\n' '\\0' | xargs -0 -n2 sh -c '%s \"$0\" \"$@\" < /dev/tty'",
+       "\\.(bz|bz2|gz|tar|taz|tbz|tbz2|tgz|z|zip)$",
+       "sed -i 's|^%s\\(.*\\)$|%s\\1|' %s",
+};
+
+/* Colors */
+#define C_BLK (CTX_MAX + 1) /* Block device: DarkSeaGreen1 */
+#define C_CHR (C_BLK + 1) /* Character device: Yellow1 */
+#define C_DIR (C_CHR + 1) /* Directory: DeepSkyBlue1 */
+#define C_EXE (C_DIR + 1) /* Executable file: Green1 */
+#define C_FIL (C_EXE + 1) /* Regular file: Normal */
+#define C_HRD (C_FIL + 1) /* Hard link: Plum4 */
+#define C_LNK (C_HRD + 1) /* Symbolic link: Cyan1 */
+#define C_MIS (C_LNK + 1) /* Missing file OR file details: Grey62 */
+#define C_ORP (C_MIS + 1) /* Orphaned symlink: DeepPink1 */
+#define C_PIP (C_ORP + 1) /* Named pipe (FIFO): Orange1 */
+#define C_SOC (C_PIP + 1) /* Socket: MediumOrchid1 */
+#define C_UND (C_SOC + 1) /* Unknown OR 0B regular/exe file: Red1 */
+
+static char gcolors[] = "c1e2272e006033f7c6d6abc4";
+static uint fcolors[C_UND + 1] = {0};
+
+/* Event handling */
+#ifdef LINUX_INOTIFY
+#define NUM_EVENT_SLOTS 32 /* Make room for 32 events */
+#define EVENT_SIZE (sizeof(struct inotify_event))
+#define EVENT_BUF_LEN (EVENT_SIZE * NUM_EVENT_SLOTS)
+static int inotify_fd, inotify_wd = -1;
+static uint INOTIFY_MASK = /* IN_ATTRIB | */ IN_CREATE | IN_DELETE | IN_DELETE_SELF
+                          | IN_MODIFY | IN_MOVE_SELF | IN_MOVED_FROM | IN_MOVED_TO;
+#elif defined(BSD_KQUEUE)
+#define NUM_EVENT_SLOTS 1
+#define NUM_EVENT_FDS 1
+static int kq, event_fd = -1;
+static struct kevent events_to_monitor[NUM_EVENT_FDS];
+static uint KQUEUE_FFLAGS = NOTE_DELETE | NOTE_EXTEND | NOTE_LINK
+                           | NOTE_RENAME | NOTE_REVOKE | NOTE_WRITE;
+static struct timespec gtimeout;
+#elif defined(HAIKU_NM)
+static bool haiku_nm_active = FALSE;
+static haiku_nm_h haiku_hnd;
+#endif
+
+/* Function macros */
+#define tolastln() move(xlines - 1, 0)
+#define tocursor() move(cur + 2, 0)
+#define exitcurses() endwin()
+#define printwarn(presel) printwait(strerror(errno), presel)
+#define istopdir(path) ((path)[1] == '\0' && (path)[0] == '/')
+#define copycurname() xstrsncpy(lastname, pdents[cur].name, NAME_MAX + 1)
+#define settimeout() timeout(1000)
+#define cleartimeout() timeout(-1)
+#define errexit() printerr(__LINE__)
+#define setdirwatch() (cfg.filtermode ? (presel = FILTER) : (watch = TRUE))
+#define filterset() (g_ctx[cfg.curctx].c_fltr[1])
+/* We don't care about the return value from strcmp() */
+#define xstrcmp(a, b)  (*(a) != *(b) ? -1 : strcmp((a), (b)))
+/* A faster version of xisdigit */
+#define xisdigit(c) ((unsigned int) (c) - '0' <= 9)
+#define xerror() perror(xitoa(__LINE__))
+
+#ifdef __GNUC__
+#define UNUSED(x) UNUSED_##x __attribute__((__unused__))
+#else
+#define UNUSED(x) UNUSED_##x
+#endif /* __GNUC__ */
+
+/* HELPER FUNCTIONS */
+
+static void sigint_handler(int UNUSED(sig))
+{
+       g_state.interrupt = 1;
+}
+
+static void clean_exit_sighandler(int UNUSED(sig))
+{
+       exitcurses();
+       /* This triggers cleanup() thanks to atexit() */
+       exit(EXIT_SUCCESS);
+}
+
+static char *xitoa(uint val)
+{
+       static char ascbuf[32] = {0};
+       int i = 30;
+       uint rem;
+
+       if (!val)
+               return "0";
+
+       while (val && i) {
+               rem = val / 10;
+               ascbuf[i] = '0' + (val - (rem * 10));
+               val = rem;
+               --i;
+       }
+
+       return &ascbuf[++i];
+}
+
+/* Return the integer value of a char representing HEX */
+static uchar xchartohex(uchar c)
+{
+       if (xisdigit(c))
+               return c - '0';
+
+       if (c >= 'a' && c <= 'f')
+               return c - 'a' + 10;
+
+       if (c >= 'A' && c <= 'F')
+               return c - 'A' + 10;
+
+       return c;
+}
+
+/*
+ * Source: https://elixir.bootlin.com/linux/latest/source/arch/alpha/include/asm/bitops.h
+ */
+static bool test_set_bit(uint nr)
+{
+       nr &= HASH_BITS;
+
+       ull *m = ((ull *)ihashbmp) + (nr >> 6);
+
+       if (*m & (1 << (nr & 63)))
+               return FALSE;
+
+       *m |= 1 << (nr & 63);
+
+       return TRUE;
+}
+
+#if 0
+static bool test_clear_bit(uint nr)
+{
+       nr &= HASH_BITS;
+
+       ull *m = ((ull *) ihashbmp) + (nr >> 6);
+
+       if (!(*m & (1 << (nr & 63))))
+               return FALSE;
+
+       *m &= ~(1 << (nr & 63));
+       return TRUE;
+}
+#endif
+
+/* Increase the limit on open file descriptors, if possible */
+static rlim_t max_openfds(void)
+{
+       struct rlimit rl;
+       rlim_t limit = getrlimit(RLIMIT_NOFILE, &rl);
+
+       if (!limit) {
+               limit = rl.rlim_cur;
+               rl.rlim_cur = rl.rlim_max;
+
+               /* Return ~75% of max possible */
+               if (setrlimit(RLIMIT_NOFILE, &rl) == 0) {
+                       limit = rl.rlim_max - (rl.rlim_max >> 2);
+                       /*
+                        * 20K is arbitrary. If the limit is set to max possible
+                        * value, the memory usage increases to more than double.
+                        */
+                       if (limit > 20480)
+                               limit = 20480;
+               }
+       } else
+               limit = 32;
+
+       return limit;
+}
+
+/*
+ * Wrapper to realloc()
+ * Frees current memory if realloc() fails and returns NULL.
+ *
+ * As per the docs, the *alloc() family is supposed to be memory aligned:
+ * Ubuntu: http://manpages.ubuntu.com/manpages/xenial/man3/malloc.3.html
+ * macOS: https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man3/malloc.3.html
+ */
+static void *xrealloc(void *pcur, size_t len)
+{
+       void *pmem = realloc(pcur, len);
+
+       if (!pmem)
+               free(pcur);
+
+       return pmem;
+}
+
+/*
+ * Just a safe strncpy(3)
+ * Always null ('\0') terminates if both src and dest are valid pointers.
+ * Returns the number of bytes copied including terminating null byte.
+ */
+static size_t xstrsncpy(char *restrict dst, const char *restrict src, size_t n)
+{
+       char *end = memccpy(dst, src, '\0', n);
+
+       if (!end) {
+               dst[n - 1] = '\0'; // NOLINT
+               end = dst + n; /* If we return n here, binary size increases due to auto-inlining */
+       }
+
+       return end - dst;
+}
+
+static inline size_t xstrlen(const char *restrict s)
+{
+#if !defined(__GLIBC__)
+       return strlen(s); // NOLINT
+#else
+       return (char *)rawmemchr(s, '\0') - s; // NOLINT
+#endif
+}
+
+static char *xstrdup(const char *restrict s)
+{
+       size_t len = xstrlen(s) + 1;
+       char *ptr = malloc(len);
+
+       if (ptr)
+               xstrsncpy(ptr, s, len);
+       return ptr;
+}
+
+static bool is_suffix(const char *restrict str, const char *restrict suffix)
+{
+       if (!str || !suffix)
+               return FALSE;
+
+       size_t lenstr = xstrlen(str);
+       size_t lensuffix = xstrlen(suffix);
+
+       if (lensuffix > lenstr)
+               return FALSE;
+
+       return (xstrcmp(str + (lenstr - lensuffix), suffix) == 0);
+}
+
+static bool is_prefix(const char *restrict str, const char *restrict prefix, size_t len)
+{
+       return !strncmp(str, prefix, len);
+}
+
+/*
+ * The poor man's implementation of memrchr(3).
+ * We are only looking for '/' in this program.
+ * And we are NOT expecting a '/' at the end.
+ * Ideally 0 < n <= xstrlen(s).
+ */
+static void *xmemrchr(uchar *restrict s, uchar ch, size_t n)
+{
+#if defined(__GLIBC__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
+       return memrchr(s, ch, n);
+#else
+
+       if (!s || !n)
+               return NULL;
+
+       uchar *ptr = s + n;
+
+       do
+               if (*--ptr == ch)
+                       return ptr;
+       while (s != ptr);
+
+       return NULL;
+#endif
+}
+
+/* A very simplified implementation, changes path */
+static char *xdirname(char *path)
+{
+       char *base = xmemrchr((uchar *)path, '/', xstrlen(path));
+
+       if (base == path)
+               path[1] = '\0';
+       else
+               *base = '\0';
+
+       return path;
+}
+
+static char *xbasename(char *path)
+{
+       char *base = xmemrchr((uchar *)path, '/', xstrlen(path)); // NOLINT
+
+       return base ? base + 1 : path;
+}
+
+static char *xextension(const char *fname, size_t len)
+{
+       return xmemrchr((uchar *)fname, '.', len);
+}
+
+/*
+ * Updates out with "dir/name or "/name"
+ * Returns the number of bytes copied including the terminating NULL byte
+ */
+static size_t mkpath(const char *dir, const char *name, char *out)
+{
+       size_t len;
+
+       /* Handle absolute path */
+       if (name[0] == '/') // NOLINT
+               return xstrsncpy(out, name, PATH_MAX);
+
+       /* Handle root case */
+       if (istopdir(dir))
+               len = 1;
+       else
+               len = xstrsncpy(out, dir, PATH_MAX);
+
+       out[len - 1] = '/'; // NOLINT
+       return (xstrsncpy(out + len, name, PATH_MAX - len) + len);
+}
+
+/* Assumes both the paths passed are directories */
+static char *common_prefix(const char *path, char *prefix)
+{
+       const char *x = path, *y = prefix;
+       char *sep;
+
+       if (!path || !*path || !prefix)
+               return NULL;
+
+       if (!*prefix) {
+               xstrsncpy(prefix, path, PATH_MAX);
+               return prefix;
+       }
+
+       while (*x && *y && (*x == *y))
+               ++x, ++y;
+
+       /* Strings are same */
+       if (!*x && !*y)
+               return prefix;
+
+       /* Path is shorter */
+       if (!*x && *y == '/') {
+               xstrsncpy(prefix, path, y - path);
+               return prefix;
+       }
+
+       /* Prefix is shorter */
+       if (!*y && *x == '/')
+               return prefix;
+
+       /* Shorten prefix */
+       prefix[y - prefix] = '\0';
+
+       sep = xmemrchr((uchar *)prefix, '/', y - prefix);
+       if (sep != prefix)
+               *sep = '\0';
+       else /* Just '/' */
+               prefix[1] = '\0';
+
+       return prefix;
+}
+
+/*
+ * The library function realpath() resolves symlinks.
+ * If there's a symlink in file list we want to show the symlink not what it's points to.
+ */
+static char *abspath(const char *path, const char *cwd)
+{
+       if (!path || !cwd)
+               return NULL;
+
+       size_t dst_size = 0, src_size = xstrlen(path), cwd_size = xstrlen(cwd);
+       size_t len = src_size;
+       const char *src;
+       char *dst;
+       /*
+        * We need to add 2 chars at the end as relative paths may start with:
+        * ./ (find .)
+        * no separator (fd .): this needs an additional char for '/'
+        */
+       char *resolved_path = malloc(src_size + (*path == '/' ? 0 : cwd_size) + 2);
+       if (!resolved_path)
+               return NULL;
+
+       /* Turn relative paths into absolute */
+       if (path[0] != '/')
+               dst_size = xstrsncpy(resolved_path, cwd, cwd_size + 1) - 1;
+       else
+               resolved_path[0] = '\0';
+
+       src = path;
+       dst = resolved_path + dst_size;
+       for (const char *next = NULL; next != path + src_size;) {
+               next = memchr(src, '/', len);
+               if (!next)
+                       next = path + src_size;
+
+               if (next - src == 2 && src[0] == '.' && src[1] == '.') {
+                       if (dst - resolved_path) {
+                               dst = xmemrchr((uchar *)resolved_path, '/', dst - resolved_path);
+                               *dst = '\0';
+                       }
+               } else if (next - src == 1 && src[0] == '.') {
+                       /* NOP */
+               } else if (next - src) {
+                       *(dst++) = '/';
+                       xstrsncpy(dst, src, next - src + 1);
+                       dst += next - src;
+               }
+
+               src = next + 1;
+               len = src_size - (src - path);
+       }
+
+       if (*resolved_path == '\0') {
+               resolved_path[0] = '/';
+               resolved_path[1] = '\0';
+       }
+
+       return resolved_path;
+}
+
+static int create_tmp_file(void)
+{
+       xstrsncpy(g_tmpfpath + tmpfplen - 1, messages[STR_TMPFILE], TMP_LEN_MAX - tmpfplen);
+
+       int fd = mkstemp(g_tmpfpath);
+
+       if (fd == -1) {
+               DPRINTF_S(strerror(errno));
+       }
+
+       return fd;
+}
+
+/* PRINT I/O FUNCTIONS */
+
+static void clearinfoln(void)
+{
+       move(xlines - 2, 0);
+       clrtoeol();
+}
+
+#ifdef KEY_RESIZE
+/* Clear the old prompt */
+static void clearoldprompt(void)
+{
+       clearinfoln();
+       tolastln();
+       addch('\n');
+}
+#endif
+
+/* Messages show up at the bottom */
+static inline void printmsg_nc(const char *msg)
+{
+       tolastln();
+       addstr(msg);
+       addch('\n');
+}
+
+static void printmsg(const char *msg)
+{
+       attron(COLOR_PAIR(cfg.curctx + 1));
+       printmsg_nc(msg);
+       attroff(COLOR_PAIR(cfg.curctx + 1));
+}
+
+static void printwait(const char *msg, int *presel)
+{
+       printmsg(msg);
+       if (presel) {
+               *presel = MSGWAIT;
+               if (ndents)
+                       xstrsncpy(g_ctx[cfg.curctx].c_name, pdents[cur].name, NAME_MAX + 1);
+       }
+}
+
+/* Kill curses and display error before exiting */
+static void printerr(int linenum)
+{
+       exitcurses();
+       perror(xitoa(linenum));
+       if (!g_state.picker && selpath)
+               unlink(selpath);
+       free(pselbuf);
+       exit(1);
+}
+
+static inline bool xconfirm(int c)
+{
+       return (c == 'y' || c == 'Y');
+}
+
+static int get_input(const char *prompt)
+{
+       if (prompt)
+               printmsg(prompt);
+       cleartimeout();
+
+       int r = getch();
+
+#ifdef KEY_RESIZE
+       while (r == KEY_RESIZE) {
+               if (prompt) {
+                       clearoldprompt();
+                       xlines = LINES;
+                       printmsg(prompt);
+               }
+
+               r = getch();
+       }
+#endif
+       settimeout();
+       return r;
+}
+
+static int get_cur_or_sel(void)
+{
+       if (selbufpos && ndents) {
+               if (cfg.prefersel)
+                       return 's';
+
+               int choice = get_input(messages[MSG_CUR_SEL_OPTS]);
+
+               return ((choice == 'c' || choice == 's') ? choice : 0);
+       }
+
+       if (selbufpos)
+               return 's';
+
+       if (ndents)
+               return 'c';
+
+       return 0;
+}
+
+static void xdelay(useconds_t delay)
+{
+       refresh();
+       usleep(delay);
+}
+
+static char confirm_force(bool selection)
+{
+       char str[64];
+
+       snprintf(str, 64, messages[MSG_FORCE_RM],
+                (selection ? xitoa(nselected) : "current"), (selection ? "(s)" : ""));
+
+       int r = get_input(str);
+
+       if (r == 27)
+               return '\0'; /* cancel */
+       if (r == 'y' || r == 'Y')
+               return 'f'; /* forceful */
+       return 'i'; /* interactive */
+}
+
+/* FORK FUNCTIONS */
+
+/* No NULL check here as spawn() guards against it */
+static int parseargs(char *line, char **argv)
+{
+       int count = 0;
+
+       argv[count++] = line;
+
+       while (*line) { // NOLINT
+               if (ISBLANK(*line)) {
+                       *line++ = '\0';
+
+                       if (!*line) // NOLINT
+                               return count;
+
+                       argv[count++] = line;
+                       if (count == EXEC_ARGS_MAX)
+                               return -1;
+               }
+
+               ++line;
+       }
+
+       return count;
+}
+
+static pid_t xfork(uchar flag)
+{
+       int status;
+       pid_t p = fork();
+       struct sigaction dfl_act = {.sa_handler = SIG_DFL};
+
+       if (p > 0) {
+               /* the parent ignores the interrupt, quit and hangup signals */
+               sigaction(SIGHUP, &(struct sigaction){.sa_handler = SIG_IGN}, &oldsighup);
+               sigaction(SIGTSTP, &dfl_act, &oldsigtstp);
+       } else if (p == 0) {
+               /* We create a grandchild to detach */
+               if (flag & F_NOWAIT) {
+                       p = fork();
+
+                       if (p > 0)
+                               _exit(EXIT_SUCCESS);
+                       else if (p == 0) {
+                               sigaction(SIGHUP, &dfl_act, NULL);
+                               sigaction(SIGINT, &dfl_act, NULL);
+                               sigaction(SIGQUIT, &dfl_act, NULL);
+                               sigaction(SIGTSTP, &dfl_act, NULL);
+
+                               setsid();
+                               return p;
+                       }
+
+                       perror("fork");
+                       _exit(EXIT_FAILURE);
+               }
+
+               /* so they can be used to stop the child */
+               sigaction(SIGHUP, &dfl_act, NULL);
+               sigaction(SIGINT, &dfl_act, NULL);
+               sigaction(SIGQUIT, &dfl_act, NULL);
+               sigaction(SIGTSTP, &dfl_act, NULL);
+       }
+
+       /* This is the parent waiting for the child to create grandchild*/
+       if (flag & F_NOWAIT)
+               waitpid(p, &status, 0);
+
+       if (p == -1)
+               perror("fork");
+       return p;
+}
+
+static int join(pid_t p, uchar flag)
+{
+       int status = 0xFFFF;
+
+       if (!(flag & F_NOWAIT)) {
+               /* wait for the child to exit */
+               do {
+               } while (waitpid(p, &status, 0) == -1);
+
+               if (WIFEXITED(status)) {
+                       status = WEXITSTATUS(status);
+                       DPRINTF_D(status);
+               }
+       }
+
+       /* restore parent's signal handling */
+       sigaction(SIGHUP, &oldsighup, NULL);
+       sigaction(SIGTSTP, &oldsigtstp, NULL);
+
+       return status;
+}
+
+/*
+ * Spawns a child process. Behaviour can be controlled using flag.
+ * Limited to 2 arguments to a program, flag works on bit set.
+ */
+static int spawn(char *file, char *arg1, char *arg2, uchar flag)
+{
+       pid_t pid;
+       int status = 0, retstatus = 0xFFFF;
+       char *argv[EXEC_ARGS_MAX] = {0};
+       char *cmd = NULL;
+
+       if (!file || !*file)
+               return retstatus;
+
+       /* Swap args if the first arg is NULL and second isn't */
+       if (!arg1 && arg2) {
+               arg1 = arg2;
+               arg2 = NULL;
+       }
+
+       if (flag & F_MULTI) {
+               size_t len = xstrlen(file) + 1;
+
+               cmd = (char *)malloc(len);
+               if (!cmd) {
+                       DPRINTF_S("malloc()!");
+                       return retstatus;
+               }
+
+               xstrsncpy(cmd, file, len);
+               status = parseargs(cmd, argv);
+               if (status == -1 || status > (EXEC_ARGS_MAX - 3)) { /* arg1, arg2 and last NULL */
+                       free(cmd);
+                       DPRINTF_S("NULL or too many args");
+                       return retstatus;
+               }
+       } else
+               argv[status++] = file;
+
+       argv[status] = arg1;
+       argv[++status] = arg2;
+
+       if (flag & F_NORMAL)
+               exitcurses();
+
+       pid = xfork(flag);
+       if (pid == 0) {
+               /* Suppress stdout and stderr */
+               if (flag & F_NOTRACE) {
+                       int fd = open("/dev/null", O_WRONLY, 0200);
+
+                       dup2(fd, 1);
+                       dup2(fd, 2);
+                       close(fd);
+               }
+
+               execvp(*argv, argv);
+               _exit(EXIT_SUCCESS);
+       } else {
+               retstatus = join(pid, flag);
+
+               DPRINTF_D(pid);
+
+               if ((flag & F_CONFIRM) || ((flag & F_CHKRTN) && retstatus)) {
+                       printf("%s", messages[MSG_CONTINUE]);
+#ifndef NORL
+                       fflush(stdout);
+#endif
+                       while (getchar() != '\n');
+               }
+
+               if (flag & F_NORMAL)
+                       refresh();
+
+               free(cmd);
+       }
+
+       return retstatus;
+}
+
+/* SELECTION HANDLER FUNCTIONS */
+
+/* Writes buflen char(s) from buf to a file */
+static void writesel(const char *buf, const size_t buflen)
+{
+       if (g_state.pickraw || !selpath)
+               return;
+
+       FILE *fp = fopen(selpath, "w");
+
+       if (fp) {
+               if (fwrite(buf, 1, buflen, fp) != buflen)
+                       printwarn(NULL);
+               fclose(fp);
+       } else
+               printwarn(NULL);
+}
+
+static void appendfpath(const char *path, const size_t len)
+{
+       if ((selbufpos >= selbuflen) || ((len + 3) > (selbuflen - selbufpos))) {
+               selbuflen += PATH_MAX;
+               pselbuf = xrealloc(pselbuf, selbuflen);
+               if (!pselbuf)
+                       errexit();
+       }
+
+       selbufpos += xstrsncpy(pselbuf + selbufpos, path, len);
+}
+
+/* Write selected file paths to fd, linefeed separated */
+static size_t seltofile(int fd, uint *pcount)
+{
+       uint lastpos, count = 0;
+       char *pbuf = pselbuf;
+       size_t pos = 0;
+       ssize_t len, prefixlen = 0, initlen = 0;
+
+       if (pcount)
+               *pcount = 0;
+
+       if (!selbufpos)
+               return 0;
+
+       lastpos = selbufpos - 1;
+
+       if (listpath) {
+               prefixlen = (ssize_t)xstrlen(listroot);
+               initlen = (ssize_t)xstrlen(listpath);
+       }
+
+       while (pos <= lastpos) {
+               DPRINTF_S(pbuf);
+               len = (ssize_t)xstrlen(pbuf);
+
+               if (!listpath || !is_prefix(pbuf, listpath, initlen)) {
+                       if (write(fd, pbuf, len) != len)
+                               return pos;
+               } else {
+                       if (write(fd, listroot, prefixlen) != prefixlen)
+                               return pos;
+                       if (write(fd, pbuf + initlen, len - initlen) != (len - initlen))
+                               return pos;
+               }
+
+               pos += len;
+               if (pos <= lastpos) {
+                       if (write(fd, "\n", 1) != 1)
+                               return pos;
+                       pbuf += len + 1;
+               }
+               ++pos;
+               ++count;
+       }
+
+       if (pcount)
+               *pcount = count;
+
+       return pos;
+}
+
+/* List selection from selection file (another instance) */
+static bool listselfile(void)
+{
+       struct stat sb;
+
+       if (stat(selpath, &sb) == -1)
+               return FALSE;
+
+       /* Nothing selected if file size is 0 */
+       if (!sb.st_size)
+               return FALSE;
+
+       snprintf(g_buf, CMD_LEN_MAX, "tr \'\\0\' \'\\n\' < %s", selpath);
+       spawn(utils[UTIL_SH_EXEC], g_buf, NULL, F_CLI | F_CONFIRM);
+
+       return TRUE;
+}
+
+/* Reset selection indicators */
+static void resetselind(void)
+{
+       for (int r = 0; r < ndents; ++r)
+               if (pdents[r].flags & FILE_SELECTED)
+                       pdents[r].flags &= ~FILE_SELECTED;
+}
+
+static void startselection(void)
+{
+       if (!g_state.selmode) {
+               g_state.selmode = 1;
+               nselected = 0;
+
+               if (selbufpos) {
+                       resetselind();
+                       writesel(NULL, 0);
+                       selbufpos = 0;
+               }
+
+               lastappendpos = 0;
+       }
+}
+
+static void updateselbuf(const char *path, char *newpath)
+{
+       size_t r;
+
+       for (int i = 0; i < ndents; ++i)
+               if (pdents[i].flags & FILE_SELECTED) {
+                       r = mkpath(path, pdents[i].name, newpath);
+                       appendfpath(newpath, r);
+               }
+}
+
+/* Finish selection procedure before an operation */
+static void endselection(void)
+{
+       int fd;
+       ssize_t count;
+       char buf[sizeof(patterns[P_REPLACE]) + PATH_MAX + (TMP_LEN_MAX << 1)];
+
+       if (g_state.selmode)
+               g_state.selmode = 0;
+
+       if (!listpath || !selbufpos)
+               return;
+
+       fd = create_tmp_file();
+       if (fd == -1) {
+               DPRINTF_S("couldn't create tmp file");
+               return;
+       }
+
+       seltofile(fd, NULL);
+       if (close(fd)) {
+               DPRINTF_S(strerror(errno));
+               printwarn(NULL);
+               return;
+       }
+
+       snprintf(buf, sizeof(buf), patterns[P_REPLACE], listpath, listroot, g_tmpfpath);
+       spawn(utils[UTIL_SH_EXEC], buf, NULL, F_CLI);
+
+       fd = open(g_tmpfpath, O_RDONLY);
+       if (fd == -1) {
+               DPRINTF_S(strerror(errno));
+               printwarn(NULL);
+               if (unlink(g_tmpfpath)) {
+                       DPRINTF_S(strerror(errno));
+                       printwarn(NULL);
+               }
+               return;
+       }
+
+       count = read(fd, pselbuf, selbuflen);
+       if (count < 0) {
+               DPRINTF_S(strerror(errno));
+               printwarn(NULL);
+               if (close(fd) || unlink(g_tmpfpath)) {
+                       DPRINTF_S(strerror(errno));
+               }
+               return;
+       }
+
+       if (close(fd) || unlink(g_tmpfpath)) {
+               DPRINTF_S(strerror(errno));
+               printwarn(NULL);
+               return;
+       }
+
+       selbufpos = count;
+       pselbuf[--count] = '\0';
+       for (--count; count > 0; --count)
+               if (pselbuf[count] == '\n' && pselbuf[count+1] == '/')
+                       pselbuf[count] = '\0';
+
+       writesel(pselbuf, selbufpos - 1);
+}
+
+static void clearselection(void)
+{
+       nselected = 0;
+       selbufpos = 0;
+       g_state.selmode = 0;
+       writesel(NULL, 0);
+}
+
+/* Returns: 1 - success, 0 - none selected, -1 - other failure */
+static int editselection(void)
+{
+       int ret = -1;
+       int fd, lines = 0;
+       ssize_t count;
+       struct stat sb;
+       time_t mtime;
+
+       if (!selbufpos)
+               return listselfile();
+
+       fd = create_tmp_file();
+       if (fd == -1) {
+               DPRINTF_S("couldn't create tmp file");
+               return -1;
+       }
+
+       seltofile(fd, NULL);
+       if (close(fd)) {
+               DPRINTF_S(strerror(errno));
+               return -1;
+       }
+
+       /* Save the last modification time */
+       if (stat(g_tmpfpath, &sb)) {
+               DPRINTF_S(strerror(errno));
+               unlink(g_tmpfpath);
+               return -1;
+       }
+       mtime = sb.st_mtime;
+
+       spawn((cfg.waitedit ? enveditor : editor), g_tmpfpath, NULL, F_CLI);
+
+       fd = open(g_tmpfpath, O_RDONLY);
+       if (fd == -1) {
+               DPRINTF_S(strerror(errno));
+               unlink(g_tmpfpath);
+               return -1;
+       }
+
+       fstat(fd, &sb);
+
+       if (mtime == sb.st_mtime) {
+               DPRINTF_S("selection is not modified");
+               unlink(g_tmpfpath);
+               return 1;
+       }
+
+       if (sb.st_size > selbufpos) {
+               DPRINTF_S("edited buffer larger than previous");
+               unlink(g_tmpfpath);
+               goto emptyedit;
+       }
+
+       count = read(fd, pselbuf, selbuflen);
+       if (count < 0) {
+               DPRINTF_S(strerror(errno));
+               printwarn(NULL);
+               if (close(fd) || unlink(g_tmpfpath)) {
+                       DPRINTF_S(strerror(errno));
+                       printwarn(NULL);
+               }
+               goto emptyedit;
+       }
+
+       if (close(fd) || unlink(g_tmpfpath)) {
+               DPRINTF_S(strerror(errno));
+               printwarn(NULL);
+               goto emptyedit;
+       }
+
+       if (!count) {
+               ret = 1;
+               goto emptyedit;
+       }
+
+       resetselind();
+       selbufpos = count;
+       /* The last character should be '\n' */
+       pselbuf[--count] = '\0';
+       for (--count; count > 0; --count) {
+               /* Replace every '\n' that separates two paths */
+               if (pselbuf[count] == '\n' && pselbuf[count + 1] == '/') {
+                       ++lines;
+                       pselbuf[count] = '\0';
+               }
+       }
+
+       /* Add a line for the last file */
+       ++lines;
+
+       if (lines > nselected) {
+               DPRINTF_S("files added to selection");
+               goto emptyedit;
+       }
+
+       nselected = lines;
+       writesel(pselbuf, selbufpos - 1);
+
+       return 1;
+
+emptyedit:
+       resetselind();
+       clearselection();
+       return ret;
+}
+
+static bool selsafe(void)
+{
+       /* Fail if selection file path not generated */
+       if (!selpath) {
+               printmsg(messages[MSG_SEL_MISSING]);
+               return FALSE;
+       }
+
+       /* Fail if selection file path isn't accessible */
+       if (access(selpath, R_OK | W_OK) == -1) {
+               errno == ENOENT ? printmsg(messages[MSG_0_SELECTED]) : printwarn(NULL);
+               return FALSE;
+       }
+
+       return TRUE;
+}