aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Arun Prakash Jana <engineerarun@gmail.com>2020-08-07 23:16:17 +0530
committerGravatar Arun Prakash Jana <engineerarun@gmail.com>2020-08-07 23:16:17 +0530
commitc566afd81948f6a208bef1973efac064a3d17a38 (patch)
treed1e7c8b46cc8ac1771b34012d282524485ee395c
parent1fecdb2393c31283267c01dd4f48d9ff6df17474 (diff)
downloadnnn-c566afd81948f6a208bef1973efac064a3d17a38.tar.gz
Revert "Move helper APIs to header file"
-rw-r--r--src/nnn.c1613
-rw-r--r--src/nnn.h1613
2 files changed, 1610 insertions, 1616 deletions
diff --git a/src/nnn.c b/src/nnn.c
index a66fa44..f8c03cf 100644
--- a/src/nnn.c
+++ b/src/nnn.c
@@ -109,28 +109,1461 @@
#define alloca(size) __builtin_alloca(size)
#endif
-#include "dbg.h"
#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);
-static int (*nftw_fn)(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf);
-/* FUNCTIONS */
+/* 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;
+ }
+
+ 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;
+}
+
static void export_file_list(void)
{
if (!ndents)
@@ -287,6 +1720,180 @@ 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);
diff --git a/src/nnn.h b/src/nnn.h
index a6e67ec..57c8649 100644
--- a/src/nnn.h
+++ b/src/nnn.h
@@ -32,114 +32,7 @@
#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 {
@@ -381,1509 +274,3 @@ 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;
-}