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
This commit is contained in:
KlzXS 2020-02-01 15:54:56 +01:00 committed by GitHub
parent 099a9af289
commit 2390ac7b74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 422 additions and 22 deletions

View file

@ -77,6 +77,7 @@ It runs smoothly on the Pi, [Termux](https://www.youtube.com/watch?v=AbaauM7gUJw
- FreeDesktop compliant trash (needs trash-cli) - FreeDesktop compliant trash (needs trash-cli)
- Cross-dir file/all/range selection - Cross-dir file/all/range selection
- Batch renamer (feature-limited) for selection or dir - Batch renamer (feature-limited) for selection or dir
- Display a list of files from stdin
- Copy (as), move (as), delete, archive, link selection - Copy (as), move (as), delete, archive, link selection
- Dir updates, notification on cp, mv, rm completion - Dir updates, notification on cp, mv, rm completion
- Copy file paths to system clipboard on select - Copy file paths to system clipboard on select

443
src/nnn.c
View file

@ -342,6 +342,8 @@ static char *home;
static char *initpath; static char *initpath;
static char *cfgdir; static char *cfgdir;
static char *g_selpath; static char *g_selpath;
static char *g_listpath;
static char *g_prefixpath;
static char *plugindir; static char *plugindir;
static char *sessiondir; static char *sessiondir;
static char *pnamebuf, *pselbuf; static char *pnamebuf, *pselbuf;
@ -353,6 +355,7 @@ static kv bookmark[BM_MAX];
static kv plug[PLUGIN_MAX]; static kv plug[PLUGIN_MAX];
static uchar g_tmpfplen; static uchar g_tmpfplen;
static uchar blk_shift = BLK_SHIFT_512; static uchar blk_shift = BLK_SHIFT_512;
static const uint _WSHIFT = (LONG_SIZE == 8) ? 3 : 2;
#ifdef PCRE #ifdef PCRE
static pcre *archive_pcre; static pcre *archive_pcre;
#else #else
@ -386,6 +389,7 @@ static char g_pipepath[TMP_LEN_MAX] __attribute__ ((aligned));
#define STATE_RANGESEL 0x4 #define STATE_RANGESEL 0x4
#define STATE_MOVE_OP 0x8 #define STATE_MOVE_OP 0x8
#define STATE_AUTONEXT 0x10 #define STATE_AUTONEXT 0x10
#define STATE_MSG 0x20
static uchar g_states; static uchar g_states;
@ -496,8 +500,9 @@ static char * const utils[] = {
#define MSG_INVALID_REG 36 #define MSG_INVALID_REG 36
#define MSG_ORDER 37 #define MSG_ORDER 37
#define MSG_LAZY 38 #define MSG_LAZY 38
#define MSG_IGNORED 39
#ifndef DIR_LIMITED_SELECTION #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 #endif
static const char * const messages[] = { static const char * const messages[] = {
@ -540,6 +545,7 @@ static const char * const messages[] = {
"invalid regex", "invalid regex",
"toggle 'a'u / 'd'u / 'e'xtn / 'r'everse / 's'ize / 't'ime / 'v'ersion?", "toggle 'a'u / 'd'u / 'e'xtn / 'r'everse / 's'ize / 't'ime / 'v'ersion?",
"unmount failed! try lazy?", "unmount failed! try lazy?",
"ignoring invalid paths...",
#ifndef DIR_LIMITED_SELECTION #ifndef DIR_LIMITED_SELECTION
"dir changed, range sel off", /* Must be the last entry */ "dir changed, range sel off", /* Must be the last entry */
#endif #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' | " 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"; "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 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 */ /* Event handling */
#ifdef LINUX_INOTIFY #ifdef LINUX_INOTIFY
@ -859,7 +866,6 @@ static size_t xstrlcpy(char *dest, const char *src, size_t n)
ulong *s, *d; ulong *s, *d;
size_t len = strlen(src) + 1, blocks; size_t len = strlen(src) + 1, blocks;
const uint _WSHIFT = (LONG_SIZE == 8) ? 3 : 2;
if (n > len) if (n > len)
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 * 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 && if ((n >= LONG_SIZE) && (((ulong)src & _ALIGNMENT_MASK) == 0 &&
((ulong)dest & _ALIGNMENT_MASK) == 0)) { ((ulong)dest & _ALIGNMENT_MASK) == 0)) {
@ -940,6 +946,80 @@ static void *xmemrchr(uchar *s, uchar ch, size_t n)
return NULL; 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) static char *xbasename(char *path)
{ {
char *base = xmemrchr((uchar *)path, '/', strlen(path)); // NOLINT char *base = xmemrchr((uchar *)path, '/', strlen(path)); // NOLINT
@ -947,6 +1027,55 @@ static char *xbasename(char *path)
return base ? base + 1 : 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) static int create_tmp_file(void)
{ {
xstrlcpy(g_tmpfpath + g_tmpfplen - 1, messages[STR_TMPFILE], TMP_LEN_MAX - g_tmpfplen); 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 */ /* Finish selection procedure before an operation */
static void endselection(void) static void endselection(void)
{ {
int fd;
ssize_t count;
char buf[sizeof(replaceprefixcmd) + PATH_MAX + (TMP_LEN_MAX << 1)];
if (cfg.selmode) if (cfg.selmode)
cfg.selmode = 0; 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) static void clearselection(void)
@ -1117,7 +1304,10 @@ static int editselection(void)
} }
seltofile(fd, NULL); seltofile(fd, NULL);
close(fd); if (close(fd)) {
DPRINTF_S(strerror(errno));
return -1;
}
/* Save the last modification time */ /* Save the last modification time */
if (stat(g_tmpfpath, &sb)) { if (stat(g_tmpfpath, &sb)) {
@ -1151,16 +1341,24 @@ static int editselection(void)
} }
count = read(fd, pselbuf, selbuflen); count = read(fd, pselbuf, selbuflen);
close(fd); if (count < 0) {
unlink(g_tmpfpath); DPRINTF_S(strerror(errno));
printwarn(NULL);
if (!count) { if (close(fd) || unlink(g_tmpfpath)) {
ret = 1; DPRINTF_S(strerror(errno));
printwarn(NULL);
}
goto emptyedit; goto emptyedit;
} }
if (count < 0) { if (close(fd) || unlink(g_tmpfpath)) {
DPRINTF_S("error reading tmp file"); DPRINTF_S(strerror(errno));
printwarn(NULL);
goto emptyedit;
}
if (!count) {
ret = 1;
goto emptyedit; goto emptyedit;
} }
@ -4791,6 +4989,13 @@ nochange:
return _FAILURE; 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); sel = nextsel(presel);
if (presel) if (presel)
presel = 0; presel = 0;
@ -5239,7 +5444,9 @@ nochange:
case SEL_STATS: // fallthrough case SEL_STATS: // fallthrough
case SEL_CHMODX: case SEL_CHMODX:
if (ndents) { 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 if (lstat(newpath, &sb) == -1
|| (sel == SEL_STATS && !show_stats(newpath, &sb)) || (sel == SEL_STATS && !show_stats(newpath, &sb))
|| (sel == SEL_CHMODX && !xchmod(newpath, sb.st_mode))) { || (sel == SEL_CHMODX && !xchmod(newpath, sb.st_mode))) {
@ -5421,7 +5628,8 @@ nochange:
} }
if (r == 'c') { 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); xrm(newpath);
if (cur && access(newpath, F_OK) == -1) { 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) static void check_key_collision(void)
{ {
int key; int key;
@ -6034,6 +6411,7 @@ static void cleanup(void)
free(initpath); free(initpath);
free(bmstr); free(bmstr);
free(pluginstr); free(pluginstr);
free(g_prefixpath);
unlink(g_pipepath); 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 #ifdef DBGMODE
enabledbg(); enabledbg();
DPRINTF_S(VERSION); DPRINTF_S(VERSION);
#endif #endif
/* Prefix for temporary files */
if (!set_tmp_path())
return _FAILURE;
atexit(cleanup); 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"); home = getenv("HOME");
if (!home) { if (!home) {
fprintf(stderr, "set HOME\n"); fprintf(stderr, "set HOME\n");
@ -6183,7 +6580,9 @@ int main(int argc, char *argv[])
return _FAILURE; 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 */ if (!arg[1]) /* Bookmarks keys are single char */
initpath = get_kv_val(bookmark, NULL, *arg, BM_MAX, TRUE); 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])) if (xgetenv_set(env_cfg[NNN_TRASH]))
cfg.trash = 1; cfg.trash = 1;
/* Prefix for temporary files */
if (!set_tmp_path())
return _FAILURE;
/* Ignore/handle certain signals */ /* Ignore/handle certain signals */
struct sigaction act = {.sa_handler = sigint_handler}; struct sigaction act = {.sa_handler = sigint_handler};
@ -6327,6 +6722,10 @@ int main(int argc, char *argv[])
opt = browse(initpath, session); opt = browse(initpath, session);
mousemask(mask, NULL); mousemask(mask, NULL);
if (g_listpath)
spawn("rm -rf", initpath, NULL, NULL, F_SILENT);
exitcurses(); exitcurses();
#ifndef NORL #ifndef NORL