aboutsummaryrefslogtreecommitdiffstats
path: root/src/nnn.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nnn.c')
-rw-r--r--src/nnn.c1613
1 files changed, 1610 insertions, 3 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);