aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar KlzXS <azszwymmvqdi@yahoo.com>2020-02-01 15:54:56 +0100
committerGravatar GitHub <noreply@github.com>2020-02-01 20:24:56 +0530
commit2390ac7b745904af4fb6810fd51f1816bd53df81 (patch)
tree31e24f539a7bc6b52e9a37fc8997cb581db82bc3
parent099a9af289e97a56124a47dee6b1c8e4855a0093 (diff)
downloadnnn-2390ac7b745904af4fb6810fd51f1816bd53df81.tar.gz
List of files as input (#443)
* Start implementation on show list of files as input * Remove tmp dir on exit, set global flag * Bug fixes and improvements * Follow symlinks when in list paths mode * Fix bugs * Add flag check * Add message for invalid paths and style fixes * Change the message * Addressing review comments * Handle errno being set * Fix CI break * Decreased startup memory usage and removed loops * Fix CI break
-rw-r--r--README.md1
-rw-r--r--src/nnn.c441
2 files changed, 421 insertions, 21 deletions
diff --git a/README.md b/README.md
index e4794fb..85cc0c0 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,7 @@ It runs smoothly on the Pi, [Termux](https://www.youtube.com/watch?v=AbaauM7gUJw
- FreeDesktop compliant trash (needs trash-cli)
- Cross-dir file/all/range selection
- Batch renamer (feature-limited) for selection or dir
+ - Display a list of files from stdin
- Copy (as), move (as), delete, archive, link selection
- Dir updates, notification on cp, mv, rm completion
- Copy file paths to system clipboard on select
diff --git a/src/nnn.c b/src/nnn.c
index 4b27a2e..a162848 100644
--- a/src/nnn.c
+++ b/src/nnn.c
@@ -342,6 +342,8 @@ static char *home;
static char *initpath;
static char *cfgdir;
static char *g_selpath;
+static char *g_listpath;
+static char *g_prefixpath;
static char *plugindir;
static char *sessiondir;
static char *pnamebuf, *pselbuf;
@@ -353,6 +355,7 @@ static kv bookmark[BM_MAX];
static kv plug[PLUGIN_MAX];
static uchar g_tmpfplen;
static uchar blk_shift = BLK_SHIFT_512;
+static const uint _WSHIFT = (LONG_SIZE == 8) ? 3 : 2;
#ifdef PCRE
static pcre *archive_pcre;
#else
@@ -386,6 +389,7 @@ static char g_pipepath[TMP_LEN_MAX] __attribute__ ((aligned));
#define STATE_RANGESEL 0x4
#define STATE_MOVE_OP 0x8
#define STATE_AUTONEXT 0x10
+#define STATE_MSG 0x20
static uchar g_states;
@@ -496,8 +500,9 @@ static char * const utils[] = {
#define MSG_INVALID_REG 36
#define MSG_ORDER 37
#define MSG_LAZY 38
+#define MSG_IGNORED 39
#ifndef DIR_LIMITED_SELECTION
-#define MSG_DIR_CHANGED 39 /* Must be the last entry */
+#define MSG_DIR_CHANGED 40 /* Must be the last entry */
#endif
static const char * const messages[] = {
@@ -540,6 +545,7 @@ static const char * const messages[] = {
"invalid regex",
"toggle 'a'u / 'd'u / 'e'xtn / 'r'everse / 's'ize / 't'ime / 'v'ersion?",
"unmount failed! try lazy?",
+ "ignoring invalid paths...",
#ifndef DIR_LIMITED_SELECTION
"dir changed, range sel off", /* Must be the last entry */
#endif
@@ -596,6 +602,7 @@ static const char cpmvrenamecmd[] = "sed 's|^\\([^#][^/]\\?.*\\)$|%s/\\1|;s|^#\\
static const char batchrenamecmd[] = "paste -d'\n' %s %s | sed 'N; /^\\(.*\\)\\n\\1$/!p;d' | "
"tr '\n' '\\0' | xargs -0 -n2 mv 2>/dev/null";
static const char archive_regex[] = "\\.(bz|bz2|gz|tar|taz|tbz|tbz2|tgz|z|zip)$";
+static const char replaceprefixcmd[] = "sed -i 's|^%s\\(.*\\)$|%s\\1|' %s";
/* Event handling */
#ifdef LINUX_INOTIFY
@@ -859,7 +866,6 @@ static size_t xstrlcpy(char *dest, const char *src, size_t n)
ulong *s, *d;
size_t len = strlen(src) + 1, blocks;
- const uint _WSHIFT = (LONG_SIZE == 8) ? 3 : 2;
if (n > len)
n = len;
@@ -869,7 +875,7 @@ static size_t xstrlcpy(char *dest, const char *src, size_t n)
/*
* To enable -O3 ensure src and dest are 16-byte aligned
- * More info: http://www.felixcloutier.com/x86/MOVDQA.html
+ * More info: https://www.felixcloutier.com/x86/MOVDQA:VMOVDQA32:VMOVDQA64
*/
if ((n >= LONG_SIZE) && (((ulong)src & _ALIGNMENT_MASK) == 0 &&
((ulong)dest & _ALIGNMENT_MASK) == 0)) {
@@ -940,6 +946,80 @@ static void *xmemrchr(uchar *s, uchar ch, size_t n)
return NULL;
}
+static char *common_prefix(const char *s, char *prefix)
+{
+ if (!s || !prefix)
+ return NULL;
+
+ /* Only accept non-empty strings */
+ if (*s == '\0' || *prefix == '\0')
+ return NULL;
+
+ ulong *x, *y;
+ size_t i = 0, j = 0, blocks = 0;
+ size_t len_s = strlen(s), len_prefix = strlen(prefix);
+ size_t len = MIN(len_s, len_prefix);
+ char *tmp;
+
+ /*
+ * To enable -O3 ensure s and prefix are 16-byte aligned
+ * More info: https://www.felixcloutier.com/x86/MOVDQA:VMOVDQA32:VMOVDQA64
+ */
+ if ((len >= LONG_SIZE) && (((ulong)s & _ALIGNMENT_MASK) == 0
+ && ((ulong)prefix & _ALIGNMENT_MASK) == 0)) {
+ x = (ulong *)s;
+ y = (ulong *)prefix;
+ blocks = len >> _WSHIFT;
+ len &= LONG_SIZE - 1;
+
+ while (i < blocks && !(*x ^ *y))
+ ++x, ++y, ++i;
+
+ /* This should always return */
+ if (i < blocks) {
+ i *= LONG_SIZE;
+ for (; j < LONG_SIZE; ++j)
+ if (s[i + j] != prefix[i + j]) {
+ tmp = xmemrchr((uchar *)prefix, '/', i + j);
+ if (!tmp)
+ return NULL;
+
+ *(tmp != prefix ? tmp : tmp + 1) = '\0';
+
+ return prefix;
+ }
+ }
+
+ if (!len)
+ return prefix;
+ }
+
+ i = blocks * LONG_SIZE;
+ while (j < len && s[i + j] == prefix[i + j])
+ ++j;
+
+ if (j < len) {
+ tmp = xmemrchr((uchar *)prefix, '/', i + j);
+ if (!tmp)
+ return NULL;
+
+ *(tmp != prefix ? tmp : tmp + 1) = '\0';
+
+ return prefix;
+ }
+
+ /* complete match but lenghts might differ */
+ if (len_s < len_prefix || (len_s > len_prefix && s[len_prefix] != '/')) {
+ tmp = xmemrchr((uchar *)prefix, '/', len);
+ if (!tmp)
+ return NULL;
+
+ *(tmp != prefix ? tmp : tmp + 1) = '\0';
+ }
+
+ return prefix;
+}
+
static char *xbasename(char *path)
{
char *base = xmemrchr((uchar *)path, '/', strlen(path)); // NOLINT
@@ -947,6 +1027,55 @@ static char *xbasename(char *path)
return base ? base + 1 : path;
}
+static char *xrealpath(const char *path, const char *cwd)
+{
+ if (!path || !cwd)
+ return NULL;
+
+ size_t dst_size = 0, src_size = strlen(path), cwd_size = strlen(cwd);
+ const char *src, *next;
+ char *dst;
+ char *resolved_path = malloc(src_size + (*path == '/' ? 0 : cwd_size) + 1);
+ if (!resolved_path)
+ return NULL;
+
+ /* Turn relative paths into absolute */
+ if (path[0] != '/')
+ dst_size = xstrlcpy(resolved_path, cwd, cwd_size + 1) - 1;
+ else
+ resolved_path[0] = '\0';
+
+ src = path;
+ dst = resolved_path + dst_size;
+ for (next = NULL; next != path + src_size;) {
+ next = strchr(src, '/');
+ 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++) = '/';
+ xstrlcpy(dst, src, next - src + 1);
+ dst += next - src;
+ }
+
+ src = next + 1;
+ }
+
+ if (*resolved_path == '\0') {
+ resolved_path[0] = '/';
+ resolved_path[1] = '\0';
+ }
+
+ return resolved_path;
+}
+
static int create_tmp_file(void)
{
xstrlcpy(g_tmpfpath + g_tmpfplen - 1, messages[STR_TMPFILE], TMP_LEN_MAX - g_tmpfplen);
@@ -1086,8 +1215,66 @@ static void updateselbuf(const char *path, char *newpath)
/* Finish selection procedure before an operation */
static void endselection(void)
{
+ int fd;
+ ssize_t count;
+ char buf[sizeof(replaceprefixcmd) + PATH_MAX + (TMP_LEN_MAX << 1)];
+
if (cfg.selmode)
cfg.selmode = 0;
+
+ if (!g_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), replaceprefixcmd, g_listpath, g_prefixpath, g_tmpfpath);
+ spawn(utils[UTIL_SH_EXEC], buf, NULL, 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)
@@ -1117,7 +1304,10 @@ static int editselection(void)
}
seltofile(fd, NULL);
- close(fd);
+ if (close(fd)) {
+ DPRINTF_S(strerror(errno));
+ return -1;
+ }
/* Save the last modification time */
if (stat(g_tmpfpath, &sb)) {
@@ -1151,16 +1341,24 @@ static int editselection(void)
}
count = read(fd, pselbuf, selbuflen);
- close(fd);
- unlink(g_tmpfpath);
+ if (count < 0) {
+ DPRINTF_S(strerror(errno));
+ printwarn(NULL);
+ if (close(fd) || unlink(g_tmpfpath)) {
+ DPRINTF_S(strerror(errno));
+ printwarn(NULL);
+ }
+ goto emptyedit;
+ }
- if (!count) {
- ret = 1;
+ if (close(fd) || unlink(g_tmpfpath)) {
+ DPRINTF_S(strerror(errno));
+ printwarn(NULL);
goto emptyedit;
}
- if (count < 0) {
- DPRINTF_S("error reading tmp file");
+ if (!count) {
+ ret = 1;
goto emptyedit;
}
@@ -4791,6 +4989,13 @@ nochange:
return _FAILURE;
}
+ /* Display a one-time message */
+ if (g_states & STATE_MSG) {
+ g_states &= ~STATE_MSG;
+ printwait(messages[MSG_IGNORED], &presel);
+ goto nochange;
+ }
+
sel = nextsel(presel);
if (presel)
presel = 0;
@@ -5239,7 +5444,9 @@ nochange:
case SEL_STATS: // fallthrough
case SEL_CHMODX:
if (ndents) {
- mkpath(path, dents[cur].name, newpath);
+ tmp = (g_listpath && xstrcmp(path, g_listpath) == 0) ? g_prefixpath : path;
+ mkpath(tmp, dents[cur].name, newpath);
+
if (lstat(newpath, &sb) == -1
|| (sel == SEL_STATS && !show_stats(newpath, &sb))
|| (sel == SEL_CHMODX && !xchmod(newpath, sb.st_mode))) {
@@ -5421,7 +5628,8 @@ nochange:
}
if (r == 'c') {
- mkpath(path, dents[cur].name, newpath);
+ tmp = (g_listpath && xstrcmp(path, g_listpath) == 0) ? g_prefixpath : path;
+ mkpath(tmp, dents[cur].name, newpath);
xrm(newpath);
if (cur && access(newpath, F_OK) == -1) {
@@ -5859,6 +6067,175 @@ nochange:
}
}
+static char *make_tmp_tree(char **paths, ssize_t entries, const char *prefix)
+{
+ /* tmpdir holds the full path */
+ /* tmp holds the path without the tmp dir prefix */
+ int err, ignore = 0;
+ struct stat sb;
+ char *slash, *tmp;
+ ssize_t i, len = strlen(prefix);
+ char *tmpdir = malloc(sizeof(char) * (PATH_MAX + TMP_LEN_MAX));
+
+ if (!tmpdir) {
+ DPRINTF_S(strerror(errno));
+ return NULL;
+ }
+
+ tmp = tmpdir + g_tmpfplen - 1;
+ xstrlcpy(tmpdir, g_tmpfpath, g_tmpfplen);
+ xstrlcpy(tmp, "/nnnXXXXXX", 11);
+
+ if (!mkdtemp(tmpdir)) {
+ free(tmpdir);
+
+ DPRINTF_S(strerror(errno));
+ return NULL;
+ }
+
+ g_listpath = tmpdir;
+
+ for (i = 0; i < entries; ++i) {
+ err = stat(paths[i], &sb);
+ if (err && errno == ENOENT) {
+ ignore = 1;
+ continue;
+ }
+
+ xstrlcpy(tmp + 10, paths[i] + len, strlen(paths[i]) + 1);
+
+ slash = xmemrchr((uchar *)tmp, '/', strlen(paths[i]) + 11);
+ *slash = '\0';
+
+ xmktree(tmpdir, TRUE);
+
+ *slash = '/';
+ if (symlink(paths[i], tmpdir)) {
+ DPRINTF_S(paths[i]);
+ DPRINTF_S(strerror(errno));
+ }
+ }
+
+ if (ignore)
+ g_states |= STATE_MSG;
+
+ tmp[10] = '\0';
+ return tmpdir;
+}
+
+static char *load_input()
+{
+ /* 512 KiB chunk size */
+ ssize_t i, chunk_count = 1, chunk = 512 * 1024, entries = 0;
+ char *input = malloc(sizeof(char) * chunk), *tmpdir;
+ char cwd[PATH_MAX], *next, *prev, *tmp;
+ size_t offsets[1 << 16];
+ char *paths[1 << 16];
+ ssize_t input_read, total_read = 0, off = 0;
+
+ if (!input) {
+ DPRINTF_S(strerror(errno));
+ return NULL;
+ }
+
+ while (chunk_count < 512) {
+ input_read = read(STDIN_FILENO, input, chunk);
+ if (input_read < 0) {
+ DPRINTF_S(strerror(errno));
+ goto malloc_1;
+ }
+
+ total_read += input_read;
+ ++chunk_count;
+
+ while (off < total_read) {
+ next = memchr(input + off, '\0', total_read - off) + 1;
+ if (next == (void *)1)
+ break;
+
+ if (next - input == off + 1) {
+ off = next - input;
+ continue;
+ }
+
+ if (entries == (1 << 16))
+ goto malloc_1;
+
+ offsets[entries++] = off;
+ off = next - input;
+ }
+
+ if (input_read < chunk)
+ break;
+
+ if (chunk_count == 512 || !(input = xrealloc(input, (chunk_count + 1) * chunk)))
+ goto malloc_1;
+ }
+
+ if (off != total_read) {
+ if (entries == (1 << 16))
+ goto malloc_1;
+
+ offsets[entries++] = off;
+ }
+
+ if (!entries)
+ goto malloc_1;
+
+ input[total_read] = '\0';
+
+ for (i = 0; i < entries; ++i)
+ paths[i] = input + offsets[i];
+
+ /* prev used as tmp variable */
+ prev = getcwd(cwd, PATH_MAX);
+ if (!prev)
+ goto malloc_1;
+
+ for (i = 0; i < entries; ++i) {
+ if (!(paths[i] = xrealpath(paths[i], cwd))) {
+ for (--i; i >= 0; --i)
+ free(paths[i]);
+ goto malloc_1;
+ }
+ }
+
+ g_prefixpath = malloc(sizeof(char) * PATH_MAX);
+ if (!g_prefixpath)
+ goto malloc_2;
+
+ xstrlcpy(g_prefixpath, paths[0], strlen(paths[0]) + 1);
+ for (i = 1; i < entries; ++i) {
+ if (!common_prefix(paths[i], g_prefixpath))
+ goto malloc_2;
+ }
+
+ if (entries == 1) {
+ tmp = xmemrchr((uchar *)g_prefixpath, '/', strlen(g_prefixpath));
+ if (!tmp)
+ return NULL;
+
+ *(tmp != g_prefixpath ? tmp : tmp + 1) = '\0';
+ }
+
+ tmpdir = make_tmp_tree(paths, entries, g_prefixpath);
+
+ if (tmpdir) {
+ for (i = entries - 1; i >= 0; --i)
+ free(paths[i]);
+ free(input);
+
+ return tmpdir;
+ }
+
+malloc_2:
+ for (i = entries - 1; i >= 0; --i)
+ free(paths[i]);
+malloc_1:
+ free(input);
+ return NULL;
+}
+
static void check_key_collision(void)
{
int key;
@@ -6034,6 +6411,7 @@ static void cleanup(void)
free(initpath);
free(bmstr);
free(pluginstr);
+ free(g_prefixpath);
unlink(g_pipepath);
@@ -6146,17 +6524,36 @@ int main(int argc, char *argv[])
}
}
- /* Confirm we are in a terminal */
- if (!cfg.picker && !(isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)))
- exit(1);
-
#ifdef DBGMODE
enabledbg();
DPRINTF_S(VERSION);
#endif
+ /* Prefix for temporary files */
+ if (!set_tmp_path())
+ return _FAILURE;
+
atexit(cleanup);
+ if (!cfg.picker) {
+ /* Confirm we are in a terminal */
+ if (!isatty(STDOUT_FILENO))
+ exit(1);
+
+ /* Now we are in path list mode */
+ if (!isatty(STDIN_FILENO)) {
+
+ /* This is the same as g_listpath */
+ initpath = load_input();
+ if (!initpath)
+ exit(1);
+
+ /* We return to tty */
+ dup2(STDOUT_FILENO, STDIN_FILENO);
+ }
+
+ }
+
home = getenv("HOME");
if (!home) {
fprintf(stderr, "set HOME\n");
@@ -6183,7 +6580,9 @@ int main(int argc, char *argv[])
return _FAILURE;
}
- if (arg) { /* Open a bookmark directly */
+ if (initpath) {
+ /* NOP */
+ } else if (arg) { /* Open a bookmark directly */
if (!arg[1]) /* Bookmarks keys are single char */
initpath = get_kv_val(bookmark, NULL, *arg, BM_MAX, TRUE);
@@ -6285,10 +6684,6 @@ int main(int argc, char *argv[])
if (xgetenv_set(env_cfg[NNN_TRASH]))
cfg.trash = 1;
- /* Prefix for temporary files */
- if (!set_tmp_path())
- return _FAILURE;
-
/* Ignore/handle certain signals */
struct sigaction act = {.sa_handler = sigint_handler};
@@ -6327,6 +6722,10 @@ int main(int argc, char *argv[])
opt = browse(initpath, session);
mousemask(mask, NULL);
+
+ if (g_listpath)
+ spawn("rm -rf", initpath, NULL, NULL, F_SILENT);
+
exitcurses();
#ifndef NORL