My build of nnn with minor changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1485 lines
31 KiB

  1. /* See LICENSE file for copyright and license details. */
  2. #include <sys/stat.h>
  3. #include <sys/types.h>
  4. #include <sys/wait.h>
  5. #include <curses.h>
  6. #include <dirent.h>
  7. #include <errno.h>
  8. #include <fcntl.h>
  9. #include <limits.h>
  10. #include <locale.h>
  11. #include <regex.h>
  12. #include <signal.h>
  13. #include <stdarg.h>
  14. #include <stdio.h>
  15. #include <stdlib.h>
  16. #include <string.h>
  17. #include <unistd.h>
  18. #include <time.h>
  19. #include <pwd.h>
  20. #include <grp.h>
  21. #ifdef DEBUG
  22. static int
  23. xprintf(int fd, const char *fmt, ...)
  24. {
  25. char buf[BUFSIZ];
  26. int r;
  27. va_list ap;
  28. va_start(ap, fmt);
  29. r = vsnprintf(buf, sizeof(buf), fmt, ap);
  30. if (r > 0)
  31. r = write(fd, buf, r);
  32. va_end(ap);
  33. return r;
  34. }
  35. #define DEBUG_FD 8
  36. #define DPRINTF_D(x) xprintf(DEBUG_FD, #x "=%d\n", x)
  37. #define DPRINTF_U(x) xprintf(DEBUG_FD, #x "=%u\n", x)
  38. #define DPRINTF_S(x) xprintf(DEBUG_FD, #x "=%s\n", x)
  39. #define DPRINTF_P(x) xprintf(DEBUG_FD, #x "=0x%p\n", x)
  40. #else
  41. #define DPRINTF_D(x)
  42. #define DPRINTF_U(x)
  43. #define DPRINTF_S(x)
  44. #define DPRINTF_P(x)
  45. #endif /* DEBUG */
  46. #define LEN(x) (sizeof(x) / sizeof(*(x)))
  47. #undef MIN
  48. #define MIN(x, y) ((x) < (y) ? (x) : (y))
  49. #define ISODD(x) ((x) & 1)
  50. #define CONTROL(c) ((c) ^ 0x40)
  51. #define TOUPPER(ch) \
  52. (((ch) >= 'a' && (ch) <= 'z') ? ((ch) - 'a' + 'A') : (ch))
  53. #define MAX_CMD_LEN (PATH_MAX << 1)
  54. #define CURSYM(flag) (flag ? CURSR : EMPTY)
  55. struct assoc {
  56. char *regex; /* Regex to match on filename */
  57. char *bin; /* Program */
  58. };
  59. /* Supported actions */
  60. enum action {
  61. SEL_QUIT = 1,
  62. SEL_BACK,
  63. SEL_GOIN,
  64. SEL_FLTR,
  65. SEL_NEXT,
  66. SEL_PREV,
  67. SEL_PGDN,
  68. SEL_PGUP,
  69. SEL_HOME,
  70. SEL_END,
  71. SEL_CD,
  72. SEL_CDHOME,
  73. SEL_LAST,
  74. SEL_TOGGLEDOT,
  75. SEL_DETAIL,
  76. SEL_STATS,
  77. SEL_FSIZE,
  78. SEL_MTIME,
  79. SEL_REDRAW,
  80. SEL_COPY,
  81. SEL_HELP,
  82. SEL_RUN,
  83. SEL_RUNARG,
  84. };
  85. struct key {
  86. int sym; /* Key pressed */
  87. enum action act; /* Action */
  88. char *run; /* Program to run */
  89. char *env; /* Environment variable to run */
  90. };
  91. #include "config.h"
  92. typedef struct entry {
  93. char name[PATH_MAX];
  94. mode_t mode;
  95. time_t t;
  96. off_t size;
  97. } *pEntry;
  98. typedef unsigned long ulong;
  99. /* Global context */
  100. static struct entry *dents;
  101. static int ndents, cur;
  102. static int idle;
  103. static char *opener;
  104. static char *fallback_opener;
  105. static char *copier;
  106. static const char* size_units[] = {"B", "K", "M", "G", "T", "P", "E", "Z", "Y"};
  107. /*
  108. * Layout:
  109. * .---------
  110. * | cwd: /mnt/path
  111. * |
  112. * | file0
  113. * | file1
  114. * | > file2
  115. * | file3
  116. * | file4
  117. * ...
  118. * | filen
  119. * |
  120. * | Permission denied
  121. * '------
  122. */
  123. static void printmsg(char *);
  124. static void printwarn(void);
  125. static void printerr(int, char *);
  126. static void *
  127. xrealloc(void *p, size_t size)
  128. {
  129. p = realloc(p, size);
  130. if (p == NULL)
  131. printerr(1, "realloc");
  132. return p;
  133. }
  134. static size_t
  135. xstrlcpy(char *dest, const char *src, size_t n)
  136. {
  137. size_t i;
  138. for (i = 0; i < n && *src; i++)
  139. *dest++ = *src++;
  140. if (n) {
  141. *dest = '\0';
  142. #ifdef CHECK_XSTRLCPY_RET
  143. /* Compiling this out as we are not checking
  144. the return value anywhere (controlled case).
  145. Just returning the number of bytes copied. */
  146. while(*src++)
  147. i++;
  148. #endif
  149. }
  150. return i;
  151. }
  152. /*
  153. * The poor man's implementation of memrchr(3).
  154. * We are only looking for '/' in this program.
  155. */
  156. static void *
  157. xmemrchr(const void *s, int c, size_t n)
  158. {
  159. unsigned char *p;
  160. unsigned char ch = (unsigned char)c;
  161. if (!s || !n)
  162. return NULL;
  163. p = (unsigned char *)s + n - 1;
  164. while(n--)
  165. if ((*p--) == ch)
  166. return ++p;
  167. return NULL;
  168. }
  169. #if 0
  170. /* Some implementations of dirname(3) may modify `path' and some
  171. * return a pointer inside `path'. */
  172. static char *
  173. xdirname(const char *path)
  174. {
  175. static char out[PATH_MAX];
  176. char tmp[PATH_MAX], *p;
  177. xstrlcpy(tmp, path, sizeof(tmp));
  178. p = dirname(tmp);
  179. if (p == NULL)
  180. printerr(1, "dirname");
  181. xstrlcpy(out, p, sizeof(out));
  182. return out;
  183. }
  184. #endif
  185. /*
  186. * The following dirname(3) implementation does not
  187. * change the input. We use a copy of the original.
  188. *
  189. * Modified from the glibc (GNU LGPL) version.
  190. */
  191. static char *
  192. xdirname(const char *path)
  193. {
  194. static char name[PATH_MAX];
  195. char *last_slash;
  196. xstrlcpy(name, path, PATH_MAX);
  197. /* Find last '/'. */
  198. last_slash = strrchr(name, '/');
  199. if (last_slash != NULL && last_slash != name && last_slash[1] == '\0') {
  200. /* Determine whether all remaining characters are slashes. */
  201. char *runp;
  202. for (runp = last_slash; runp != name; --runp)
  203. if (runp[-1] != '/')
  204. break;
  205. /* The '/' is the last character, we have to look further. */
  206. if (runp != name)
  207. last_slash = xmemrchr(name, '/', runp - name);
  208. }
  209. if (last_slash != NULL) {
  210. /* Determine whether all remaining characters are slashes. */
  211. char *runp;
  212. for (runp = last_slash; runp != name; --runp)
  213. if (runp[-1] != '/')
  214. break;
  215. /* Terminate the name. */
  216. if (runp == name) {
  217. /* The last slash is the first character in the string.
  218. We have to return "/". As a special case we have to
  219. return "//" if there are exactly two slashes at the
  220. beginning of the string. See XBD 4.10 Path Name
  221. Resolution for more information. */
  222. if (last_slash == name + 1)
  223. ++last_slash;
  224. else
  225. last_slash = name + 1;
  226. } else
  227. last_slash = runp;
  228. last_slash[0] = '\0';
  229. } else {
  230. /* This assignment is ill-designed but the XPG specs require to
  231. return a string containing "." in any case no directory part
  232. is found and so a static and constant string is required. */
  233. name[0] = '.';
  234. name[1] = '\0';
  235. }
  236. return name;
  237. }
  238. static void
  239. spawn(char *file, char *arg, char *dir, int notify)
  240. {
  241. pid_t pid;
  242. int status;
  243. pid = fork();
  244. if (pid == 0) {
  245. if (dir != NULL)
  246. status = chdir(dir);
  247. if (notify)
  248. fprintf(stdout, "\n +-++-++-+\n | n n n |\n +-++-++-+\n\n");
  249. execlp(file, file, arg, NULL);
  250. _exit(1);
  251. } else {
  252. /* Ignore interruptions */
  253. while (waitpid(pid, &status, 0) == -1)
  254. DPRINTF_D(status);
  255. DPRINTF_D(pid);
  256. }
  257. }
  258. static char *
  259. xgetenv(char *name, char *fallback)
  260. {
  261. char *value;
  262. if (name == NULL)
  263. return fallback;
  264. value = getenv(name);
  265. return value && value[0] ? value : fallback;
  266. }
  267. int xisdigit(const char c) {
  268. if (c >= '0' && c <= '9') \
  269. return 1; \
  270. return 0;
  271. }
  272. /*
  273. * We assume none of the strings are NULL.
  274. *
  275. * Let's have the logic to sort numeric names in numeric order.
  276. * E.g., the order '1, 10, 2' doesn't make sense to human eyes.
  277. *
  278. * If the absolute numeric values are same, we fallback to alphasort.
  279. */
  280. static int
  281. xstricmp(const char *s1, const char *s2)
  282. {
  283. static char *c1, *c2;
  284. static long long num1, num2;
  285. num1 = strtoll(s1, &c1, 10);
  286. num2 = strtoll(s2, &c2, 10);
  287. if (*c1 == '\0' && *c2 == '\0') {
  288. if (num1 != num2) {
  289. if (num1 > num2)
  290. return 1;
  291. else
  292. return -1;
  293. }
  294. } else if (*c1 == '\0' && *c2 != '\0')
  295. return -1;
  296. else if (*c1 != '\0' && *c2 == '\0')
  297. return 1;
  298. while (*s2 && *s1 && TOUPPER(*s1) == TOUPPER(*s2))
  299. s1++, s2++;
  300. /* In case of alphabetically same names, make sure
  301. lower case one comes before upper case one */
  302. if (!*s1 && !*s2)
  303. return 1;
  304. return (int) (TOUPPER(*s1) - TOUPPER(*s2));
  305. }
  306. static char *
  307. openwith(char *file)
  308. {
  309. regex_t regex;
  310. char *bin = NULL;
  311. unsigned int i;
  312. for (i = 0; i < LEN(assocs); i++) {
  313. if (regcomp(&regex, assocs[i].regex,
  314. REG_NOSUB | REG_EXTENDED | REG_ICASE) != 0)
  315. continue;
  316. if (regexec(&regex, file, 0, NULL, 0) == 0) {
  317. bin = assocs[i].bin;
  318. break;
  319. }
  320. }
  321. DPRINTF_S(bin);
  322. return bin;
  323. }
  324. static int
  325. setfilter(regex_t *regex, char *filter)
  326. {
  327. char errbuf[LINE_MAX];
  328. size_t len;
  329. int r;
  330. r = regcomp(regex, filter, REG_NOSUB | REG_EXTENDED | REG_ICASE);
  331. if (r != 0) {
  332. len = COLS;
  333. if (len > sizeof(errbuf))
  334. len = sizeof(errbuf);
  335. regerror(r, regex, errbuf, len);
  336. printmsg(errbuf);
  337. }
  338. return r;
  339. }
  340. static void
  341. initfilter(int dot, char **ifilter)
  342. {
  343. *ifilter = dot ? "." : "^[^.]";
  344. }
  345. static int
  346. visible(regex_t *regex, char *file)
  347. {
  348. return regexec(regex, file, 0, NULL, 0) == 0;
  349. }
  350. static int
  351. entrycmp(const void *va, const void *vb)
  352. {
  353. static pEntry pa, pb;
  354. pa = (pEntry)va;
  355. pb = (pEntry)vb;
  356. /* Sort directories first */
  357. if (S_ISDIR(pb->mode) && !S_ISDIR(pa->mode))
  358. return 1;
  359. else if (S_ISDIR(pa->mode) && !S_ISDIR(pb->mode))
  360. return -1;
  361. /* Do the actual sorting */
  362. if (mtimeorder)
  363. return pb->t - pa->t;
  364. if (sizeorder)
  365. if (pb->size != pa->size)
  366. return pb->size - pa->size;
  367. return xstricmp(pa->name, pb->name);
  368. }
  369. static void
  370. initcurses(void)
  371. {
  372. if (initscr() == NULL) {
  373. char *term = getenv("TERM");
  374. if (term != NULL)
  375. fprintf(stderr, "error opening terminal: %s\n", term);
  376. else
  377. fprintf(stderr, "failed to initialize curses\n");
  378. exit(1);
  379. }
  380. cbreak();
  381. noecho();
  382. nonl();
  383. intrflush(stdscr, FALSE);
  384. keypad(stdscr, TRUE);
  385. curs_set(FALSE); /* Hide cursor */
  386. timeout(1000); /* One second */
  387. }
  388. static void
  389. exitcurses(void)
  390. {
  391. endwin(); /* Restore terminal */
  392. }
  393. /* Messages show up at the bottom */
  394. static void
  395. printmsg(char *msg)
  396. {
  397. move(LINES - 1, 0);
  398. printw("%s\n", msg);
  399. }
  400. /* Display warning as a message */
  401. static void
  402. printwarn(void)
  403. {
  404. printmsg(strerror(errno));
  405. }
  406. /* Kill curses and display error before exiting */
  407. static void
  408. printerr(int ret, char *prefix)
  409. {
  410. exitcurses();
  411. fprintf(stderr, "%s: %s\n", prefix, strerror(errno));
  412. exit(ret);
  413. }
  414. /* Clear the last line */
  415. static void
  416. clearprompt(void)
  417. {
  418. printmsg("");
  419. }
  420. /* Print prompt on the last line */
  421. static void
  422. printprompt(char *str)
  423. {
  424. clearprompt();
  425. printw(str);
  426. }
  427. /* Returns SEL_* if key is bound and 0 otherwise.
  428. * Also modifies the run and env pointers (used on SEL_{RUN,RUNARG}) */
  429. static int
  430. nextsel(char **run, char **env)
  431. {
  432. int c;
  433. unsigned int i;
  434. c = getch();
  435. if (c == -1)
  436. idle++;
  437. else
  438. idle = 0;
  439. for (i = 0; i < LEN(bindings); i++)
  440. if (c == bindings[i].sym) {
  441. *run = bindings[i].run;
  442. *env = bindings[i].env;
  443. return bindings[i].act;
  444. }
  445. return 0;
  446. }
  447. static char *
  448. readln(void)
  449. {
  450. static char ln[LINE_MAX];
  451. timeout(-1);
  452. echo();
  453. curs_set(TRUE);
  454. memset(ln, 0, sizeof(ln));
  455. wgetnstr(stdscr, ln, sizeof(ln) - 1);
  456. noecho();
  457. curs_set(FALSE);
  458. timeout(1000);
  459. return ln[0] ? ln : NULL;
  460. }
  461. static int
  462. canopendir(char *path)
  463. {
  464. DIR *dirp;
  465. dirp = opendir(path);
  466. if (dirp == NULL)
  467. return 0;
  468. closedir(dirp);
  469. return 1;
  470. }
  471. /*
  472. * Returns "dir/name or "/name"
  473. */
  474. static char *
  475. mkpath(char *dir, char *name, char *out, size_t n)
  476. {
  477. /* Handle absolute path */
  478. if (name[0] == '/')
  479. xstrlcpy(out, name, n);
  480. else {
  481. /* Handle root case */
  482. if (strcmp(dir, "/") == 0)
  483. snprintf(out, n, "/%s", name);
  484. else
  485. snprintf(out, n, "%s/%s", dir, name);
  486. }
  487. return out;
  488. }
  489. static void
  490. printent(struct entry *ent, int active)
  491. {
  492. if (S_ISDIR(ent->mode))
  493. printw("%s%s/\n", CURSYM(active), ent->name);
  494. else if (S_ISLNK(ent->mode))
  495. printw("%s%s@\n", CURSYM(active), ent->name);
  496. else if (S_ISSOCK(ent->mode))
  497. printw("%s%s=\n", CURSYM(active), ent->name);
  498. else if (S_ISFIFO(ent->mode))
  499. printw("%s%s|\n", CURSYM(active), ent->name);
  500. else if (ent->mode & S_IXUSR)
  501. printw("%s%s*\n", CURSYM(active), ent->name);
  502. else
  503. printw("%s%s\n", CURSYM(active), ent->name);
  504. }
  505. static void (*printptr)(struct entry *ent, int active) = &printent;
  506. static char*
  507. coolsize(off_t size)
  508. {
  509. static char size_buf[12]; /* Buffer to hold human readable size */
  510. int i = 0;
  511. long double fsize = (double)size;
  512. while (fsize > 1024) {
  513. fsize /= 1024;
  514. i++;
  515. }
  516. snprintf(size_buf, 12, "%.*Lf%s", i, fsize, size_units[i]);
  517. return size_buf;
  518. }
  519. static void
  520. printent_long(struct entry *ent, int active)
  521. {
  522. static char buf[18];
  523. strftime(buf, 18, "%b %d %H:%M %Y", localtime(&ent->t));
  524. if (active)
  525. attron(A_REVERSE);
  526. if (S_ISDIR(ent->mode))
  527. printw("%s%-17.17s / %s/\n",
  528. CURSYM(active), buf, ent->name);
  529. else if (S_ISLNK(ent->mode))
  530. printw("%s%-17.17s @ %s@\n",
  531. CURSYM(active), buf, ent->name);
  532. else if (S_ISSOCK(ent->mode))
  533. printw("%s%-17.17s = %s=\n",
  534. CURSYM(active), buf, ent->name);
  535. else if (S_ISFIFO(ent->mode))
  536. printw("%s%-17.17s | %s|\n",
  537. CURSYM(active), buf, ent->name);
  538. else if (S_ISBLK(ent->mode))
  539. printw("%s%-17.17s b %s\n",
  540. CURSYM(active), buf, ent->name);
  541. else if (S_ISCHR(ent->mode))
  542. printw("%s%-17.17s c %s\n",
  543. CURSYM(active), buf, ent->name);
  544. else if (ent->mode & S_IXUSR)
  545. printw("%s%-17.17s %8.8s* %s*\n", CURSYM(active),
  546. buf, coolsize(ent->size), ent->name);
  547. else
  548. printw("%s%-17.17s %8.8s %s\n", CURSYM(active),
  549. buf, coolsize(ent->size), ent->name);
  550. if (active)
  551. attroff(A_REVERSE);
  552. }
  553. static char
  554. get_fileind(mode_t mode, char *desc)
  555. {
  556. char c;
  557. if (S_ISREG(mode)) {
  558. c = '-';
  559. sprintf(desc, "%s", "regular file");
  560. if (mode & S_IXUSR)
  561. strcat(desc, ", executable");
  562. } else if (S_ISDIR(mode)) {
  563. c = 'd';
  564. sprintf(desc, "%s", "directory");
  565. } else if (S_ISBLK(mode)) {
  566. c = 'b';
  567. sprintf(desc, "%s", "block special device");
  568. } else if (S_ISCHR(mode)) {
  569. c = 'c';
  570. sprintf(desc, "%s", "character special device");
  571. #ifdef S_ISFIFO
  572. } else if (S_ISFIFO(mode)) {
  573. c = 'p';
  574. sprintf(desc, "%s", "FIFO");
  575. #endif /* S_ISFIFO */
  576. #ifdef S_ISLNK
  577. } else if (S_ISLNK(mode)) {
  578. c = 'l';
  579. sprintf(desc, "%s", "symbolic link");
  580. #endif /* S_ISLNK */
  581. #ifdef S_ISSOCK
  582. } else if (S_ISSOCK(mode)) {
  583. c = 's';
  584. sprintf(desc, "%s", "socket");
  585. #endif /* S_ISSOCK */
  586. #ifdef S_ISDOOR
  587. /* Solaris 2.6, etc. */
  588. } else if (S_ISDOOR(mode)) {
  589. c = 'D';
  590. desc[0] = '\0';
  591. #endif /* S_ISDOOR */
  592. } else {
  593. /* Unknown type -- possibly a regular file? */
  594. c = '?';
  595. desc[0] = '\0';
  596. }
  597. return(c);
  598. }
  599. /* Convert a mode field into "ls -l" type perms field. */
  600. static char *
  601. get_lsperms(mode_t mode, char *desc)
  602. {
  603. static const char *rwx[] = {"---", "--x", "-w-", "-wx",
  604. "r--", "r-x", "rw-", "rwx"};
  605. static char bits[11];
  606. bits[0] = get_fileind(mode, desc);
  607. strcpy(&bits[1], rwx[(mode >> 6) & 7]);
  608. strcpy(&bits[4], rwx[(mode >> 3) & 7]);
  609. strcpy(&bits[7], rwx[(mode & 7)]);
  610. if (mode & S_ISUID)
  611. bits[3] = (mode & S_IXUSR) ? 's' : 'S';
  612. if (mode & S_ISGID)
  613. bits[6] = (mode & S_IXGRP) ? 's' : 'l';
  614. if (mode & S_ISVTX)
  615. bits[9] = (mode & S_IXOTH) ? 't' : 'T';
  616. bits[10] = '\0';
  617. return(bits);
  618. }
  619. char *
  620. get_output(char *buf, size_t bytes)
  621. {
  622. char *ret;
  623. FILE *pf = popen(buf, "r");
  624. if (pf) {
  625. ret = fgets(buf, bytes, pf);
  626. pclose(pf);
  627. return ret;
  628. }
  629. return NULL;
  630. }
  631. /*
  632. * Follows the stat(1) output closely
  633. */
  634. void
  635. show_stats(char* fpath, char* fname, struct stat *sb)
  636. {
  637. char buf[PATH_MAX + 48];
  638. char *perms = get_lsperms(sb->st_mode, buf);
  639. char *p, *begin = buf;
  640. clear();
  641. /* Show file name or 'symlink' -> 'target' */
  642. if (perms[0] == 'l') {
  643. char symtgt[PATH_MAX];
  644. ssize_t len = readlink(fpath, symtgt, PATH_MAX);
  645. if (len != -1) {
  646. symtgt[len] = '\0';
  647. printw("\n\n File: '%s' -> '%s'", fname, symtgt);
  648. }
  649. } else
  650. printw("\n File: '%s'", fname);
  651. /* Show size, blocks, file type */
  652. printw("\n Size: %-15llu Blocks: %-10llu IO Block: %-6llu %s",
  653. sb->st_size, sb->st_blocks, sb->st_blksize, buf);
  654. /* Show containing device, inode, hardlink count */
  655. sprintf(buf, "%lxh/%lud", (ulong)sb->st_dev, (ulong)sb->st_dev);
  656. printw("\n Device: %-15s Inode: %-11lu Links: %-9lu",
  657. buf, sb->st_ino, sb->st_nlink);
  658. /* Show major, minor number for block or char device */
  659. if (perms[0] == 'b' || perms[0] == 'c')
  660. printw(" Device type: %lx,%lx",
  661. major(sb->st_rdev), minor(sb->st_rdev));
  662. /* Show permissions, owner, group */
  663. printw("\n Access: 0%d%d%d/%s Uid: (%lu/%s) Gid: (%lu/%s)",
  664. (sb->st_mode >> 6) & 7, (sb->st_mode >> 3) & 7, sb->st_mode & 7,
  665. perms,
  666. sb->st_uid, (getpwuid(sb->st_uid))->pw_name,
  667. sb->st_gid, (getgrgid(sb->st_gid))->gr_name);
  668. /* Show last access time */
  669. strftime(buf, 40, "%a %d-%b-%Y %T %z,%Z", localtime(&sb->st_atime));
  670. printw("\n\n Access: %s", buf);
  671. /* Show last modification time */
  672. strftime(buf, 40, "%a %d-%b-%Y %T %z,%Z", localtime(&sb->st_mtime));
  673. printw("\n Modify: %s", buf);
  674. /* Show last status change time */
  675. strftime(buf, 40, "%a %d-%b-%Y %T %z,%Z", localtime(&sb->st_ctime));
  676. printw("\n Change: %s", buf);
  677. if (S_ISREG(sb->st_mode)) {
  678. /* Show file(1) output */
  679. sprintf(buf, "file -b \"%s\" 2>&1", fpath);
  680. p = get_output(buf, PATH_MAX + 48);
  681. if (p) {
  682. printw("\n\n ");
  683. while (*p) {
  684. if (*p == ',') {
  685. *p = '\0';
  686. printw(" %s\n", begin);
  687. begin = p + 1;
  688. }
  689. p++;
  690. }
  691. printw(" %s", begin);
  692. }
  693. #ifdef SUPPORT_CHKSUM
  694. /* Calculating checksums can take VERY long */
  695. /* Show md5 */
  696. sprintf(buf, "openssl md5 \"%s\" 2>&1", fpath);
  697. p = get_output(buf, PATH_MAX + 48);
  698. if (p) {
  699. p = xmemrchr(buf, ' ', strlen(buf));
  700. if (!p)
  701. p = buf;
  702. else
  703. p++;
  704. printw("\n md5: %s", p);
  705. }
  706. /* Show sha256 */
  707. sprintf(buf, "openssl sha256 \"%s\" 2>&1", fpath);
  708. p = get_output(buf, PATH_MAX + 48);
  709. if (p) {
  710. p = xmemrchr(buf, ' ', strlen(buf));
  711. if (!p)
  712. p = buf;
  713. else
  714. p++;
  715. printw(" sha256: %s", p);
  716. }
  717. #endif
  718. }
  719. /* Show exit keys */
  720. printw("\n\n << (q/Esc)");
  721. for (*buf = getch(); *buf != 'q' && *buf != 27; *buf = getch())
  722. if (*buf == 'q' || *buf == 27)
  723. return;
  724. }
  725. void
  726. show_help(void)
  727. {
  728. char c;
  729. clear();
  730. printw("\n\
  731. << Key >> << Function >>\n\n\
  732. [Up], k, ^P Previous entry\n\
  733. [Down], j, ^N Next entry\n\
  734. [PgUp], ^U Scroll half page up\n\
  735. [PgDn], ^D Scroll half page down\n\
  736. [Home], g, ^, ^A Jump to first entry\n\
  737. [End], G, $, ^E Jump to last entry\n\
  738. [Right], [Enter], l, ^M Open file or enter dir\n\
  739. [Left], [Backspace], h, ^H Go to parent dir\n\
  740. ~ Jump to HOME dir\n\
  741. - Jump to last visited dir\n\
  742. /, & Filter dir contents\n\
  743. c Show change dir prompt\n\
  744. d Toggle detail view\n\
  745. D Show details of selected file\n\
  746. . Toggle hide .dot files\n\
  747. s Toggle sort by file size\n\
  748. t Toggle sort by modified time\n\
  749. ! Spawn SHELL in PWD (fallback sh)\n\
  750. z Run top\n\
  751. e Edit entry in EDITOR (fallback vi)\n\
  752. p Open entry in PAGER (fallback less)\n\
  753. ^K Invoke file name copier\n\
  754. ^L Force a redraw\n\
  755. ? Show help\n\
  756. q Quit\n");
  757. /* Show exit keys */
  758. printw("\n\n << (q/Esc)");
  759. for (c = getch(); c != 'q' && c != 27; c = getch())
  760. if (c == 'q' || c == 27)
  761. return;
  762. }
  763. static int
  764. dentfill(char *path, struct entry **dents,
  765. int (*filter)(regex_t *, char *), regex_t *re)
  766. {
  767. char newpath[PATH_MAX];
  768. DIR *dirp;
  769. struct dirent *dp;
  770. struct stat sb;
  771. int r, n = 0;
  772. dirp = opendir(path);
  773. if (dirp == NULL)
  774. return 0;
  775. while ((dp = readdir(dirp)) != NULL) {
  776. /* Skip self and parent */
  777. if (strcmp(dp->d_name, ".") == 0 ||
  778. strcmp(dp->d_name, "..") == 0)
  779. continue;
  780. if (filter(re, dp->d_name) == 0)
  781. continue;
  782. *dents = xrealloc(*dents, (n + 1) * sizeof(**dents));
  783. xstrlcpy((*dents)[n].name, dp->d_name, sizeof((*dents)[n].name));
  784. /* Get mode flags */
  785. mkpath(path, dp->d_name, newpath, sizeof(newpath));
  786. r = lstat(newpath, &sb);
  787. if (r == -1)
  788. printerr(1, "lstat");
  789. (*dents)[n].mode = sb.st_mode;
  790. (*dents)[n].t = sb.st_mtime;
  791. (*dents)[n].size = sb.st_size;
  792. n++;
  793. }
  794. /* Should never be null */
  795. r = closedir(dirp);
  796. if (r == -1)
  797. printerr(1, "closedir");
  798. return n;
  799. }
  800. static void
  801. dentfree(struct entry *dents)
  802. {
  803. free(dents);
  804. }
  805. /* Return the position of the matching entry or 0 otherwise */
  806. static int
  807. dentfind(struct entry *dents, int n, char *path)
  808. {
  809. if (!path)
  810. return 0;
  811. int i;
  812. char *p = xmemrchr(path, '/', strlen(path));
  813. if (!p)
  814. p = path;
  815. else
  816. /* We are assuming an entry with actual
  817. name ending in '/' will not appear */
  818. p++;
  819. DPRINTF_S(p);
  820. for (i = 0; i < n; i++)
  821. if (strcmp(p, dents[i].name) == 0)
  822. return i;
  823. return 0;
  824. }
  825. static int
  826. populate(char *path, char *oldpath, char *fltr)
  827. {
  828. regex_t re;
  829. int r;
  830. /* Can fail when permissions change while browsing */
  831. if (canopendir(path) == 0)
  832. return -1;
  833. /* Search filter */
  834. r = setfilter(&re, fltr);
  835. if (r != 0)
  836. return -1;
  837. dentfree(dents);
  838. ndents = 0;
  839. dents = NULL;
  840. ndents = dentfill(path, &dents, visible, &re);
  841. qsort(dents, ndents, sizeof(*dents), entrycmp);
  842. /* Find cur from history */
  843. cur = dentfind(dents, ndents, oldpath);
  844. return 0;
  845. }
  846. static void
  847. redraw(char *path)
  848. {
  849. static char cwd[PATH_MAX];
  850. static int nlines, odd;
  851. static int i;
  852. nlines = MIN(LINES - 4, ndents);
  853. /* Clean screen */
  854. erase();
  855. /* Strip trailing slashes */
  856. for (i = strlen(path) - 1; i > 0; i--)
  857. if (path[i] == '/')
  858. path[i] = '\0';
  859. else
  860. break;
  861. DPRINTF_D(cur);
  862. DPRINTF_S(path);
  863. /* No text wrapping in cwd line */
  864. if (!realpath(path, cwd)) {
  865. printmsg("Cannot resolve path");
  866. return;
  867. }
  868. printw(CWD "%s\n\n", cwd);
  869. /* Print listing */
  870. odd = ISODD(nlines);
  871. if (cur < (nlines >> 1)) {
  872. for (i = 0; i < nlines; i++)
  873. printptr(&dents[i], i == cur);
  874. } else if (cur >= ndents - (nlines >> 1)) {
  875. for (i = ndents - nlines; i < ndents; i++)
  876. printptr(&dents[i], i == cur);
  877. } else {
  878. nlines >>= 1;
  879. for (i = cur - nlines; i < cur + nlines + odd; i++)
  880. printptr(&dents[i], i == cur);
  881. }
  882. if (showdetail) {
  883. if (ndents) {
  884. static char ind[2] = "\0\0";
  885. static char sort[9];
  886. if (mtimeorder)
  887. sprintf(sort, "by time ");
  888. else if (sizeorder)
  889. sprintf(sort, "by size ");
  890. else
  891. sort[0] = '\0';
  892. if (S_ISDIR(dents[cur].mode))
  893. ind[0] = '/';
  894. else if (S_ISLNK(dents[cur].mode))
  895. ind[0] = '@';
  896. else if (S_ISSOCK(dents[cur].mode))
  897. ind[0] = '=';
  898. else if (S_ISFIFO(dents[cur].mode))
  899. ind[0] = '|';
  900. else if (dents[cur].mode & S_IXUSR)
  901. ind[0] = '*';
  902. else
  903. ind[0] = '\0';
  904. sprintf(cwd, "total %d %s[%s%s]", ndents, sort,
  905. dents[cur].name, ind);
  906. printmsg(cwd);
  907. } else
  908. printmsg("0 items");
  909. }
  910. }
  911. static void
  912. browse(char *ipath, char *ifilter)
  913. {
  914. static char path[PATH_MAX], oldpath[PATH_MAX], newpath[PATH_MAX];
  915. static char lastdir[PATH_MAX];
  916. static char fltr[LINE_MAX];
  917. char *bin, *dir, *tmp, *run, *env;
  918. struct stat sb;
  919. regex_t re;
  920. int r, fd;
  921. enum action sel = SEL_RUNARG + 1;
  922. xstrlcpy(path, ipath, sizeof(path));
  923. xstrlcpy(lastdir, ipath, sizeof(lastdir));
  924. xstrlcpy(fltr, ifilter, sizeof(fltr));
  925. oldpath[0] = '\0';
  926. newpath[0] = '\0';
  927. begin:
  928. if (sel == SEL_GOIN && S_ISDIR(sb.st_mode))
  929. r = populate(path, NULL, fltr);
  930. else
  931. r = populate(path, oldpath, fltr);
  932. if (r == -1) {
  933. printwarn();
  934. goto nochange;
  935. }
  936. for (;;) {
  937. redraw(path);
  938. nochange:
  939. sel = nextsel(&run, &env);
  940. switch (sel) {
  941. case SEL_QUIT:
  942. dentfree(dents);
  943. return;
  944. case SEL_BACK:
  945. /* There is no going back */
  946. if (strcmp(path, "/") == 0 ||
  947. strcmp(path, ".") == 0 ||
  948. strchr(path, '/') == NULL) {
  949. printmsg("You are at /");
  950. goto nochange;
  951. }
  952. dir = xdirname(path);
  953. if (canopendir(dir) == 0) {
  954. printwarn();
  955. goto nochange;
  956. }
  957. /* Save history */
  958. xstrlcpy(oldpath, path, sizeof(oldpath));
  959. /* Save last working directory */
  960. xstrlcpy(lastdir, path, sizeof(lastdir));
  961. xstrlcpy(path, dir, sizeof(path));
  962. /* Reset filter */
  963. xstrlcpy(fltr, ifilter, sizeof(fltr));
  964. goto begin;
  965. case SEL_GOIN:
  966. /* Cannot descend in empty directories */
  967. if (ndents == 0)
  968. goto nochange;
  969. mkpath(path, dents[cur].name, newpath, sizeof(newpath));
  970. DPRINTF_S(newpath);
  971. /* Get path info */
  972. fd = open(newpath, O_RDONLY | O_NONBLOCK);
  973. if (fd == -1) {
  974. printwarn();
  975. goto nochange;
  976. }
  977. r = fstat(fd, &sb);
  978. if (r == -1) {
  979. printwarn();
  980. close(fd);
  981. goto nochange;
  982. }
  983. close(fd);
  984. DPRINTF_U(sb.st_mode);
  985. switch (sb.st_mode & S_IFMT) {
  986. case S_IFDIR:
  987. if (canopendir(newpath) == 0) {
  988. printwarn();
  989. goto nochange;
  990. }
  991. /* Save last working directory */
  992. xstrlcpy(lastdir, path, sizeof(lastdir));
  993. xstrlcpy(path, newpath, sizeof(path));
  994. /* Reset filter */
  995. xstrlcpy(fltr, ifilter, sizeof(fltr));
  996. goto begin;
  997. case S_IFREG:
  998. {
  999. static char cmd[MAX_CMD_LEN];
  1000. static char *runvi = "vi";
  1001. static int status;
  1002. /* If default mime opener is set, use it */
  1003. if (opener) {
  1004. snprintf(cmd, MAX_CMD_LEN,
  1005. "%s \"%s\" > /dev/null 2>&1",
  1006. opener, newpath);
  1007. status = system(cmd);
  1008. continue;
  1009. }
  1010. /* Try custom applications */
  1011. bin = openwith(newpath);
  1012. /* If custom app doesn't exist try fallback */
  1013. snprintf(cmd, MAX_CMD_LEN, "which \"%s\"", bin);
  1014. if (get_output(cmd, MAX_CMD_LEN) == NULL)
  1015. bin = NULL;
  1016. if (bin == NULL) {
  1017. /* If a custom handler application is
  1018. not set, open plain text files with
  1019. vi, then try fallback_opener */
  1020. snprintf(cmd, MAX_CMD_LEN,
  1021. "file \"%s\"", newpath);
  1022. if (get_output(cmd, MAX_CMD_LEN) == NULL)
  1023. goto nochange;
  1024. if (strstr(cmd, "ASCII text") != NULL)
  1025. bin = runvi;
  1026. else if (fallback_opener) {
  1027. snprintf(cmd, MAX_CMD_LEN,
  1028. "%s \"%s\" > \
  1029. /dev/null 2>&1",
  1030. fallback_opener,
  1031. newpath);
  1032. status = system(cmd);
  1033. continue;
  1034. } else {
  1035. status++; /* Dummy operation */
  1036. printmsg("No association");
  1037. goto nochange;
  1038. }
  1039. }
  1040. exitcurses();
  1041. spawn(bin, newpath, NULL, 0);
  1042. initcurses();
  1043. continue;
  1044. }
  1045. default:
  1046. printmsg("Unsupported file");
  1047. goto nochange;
  1048. }
  1049. case SEL_FLTR:
  1050. /* Read filter */
  1051. printprompt("filter: ");
  1052. tmp = readln();
  1053. if (tmp == NULL)
  1054. tmp = ifilter;
  1055. /* Check and report regex errors */
  1056. r = setfilter(&re, tmp);
  1057. if (r != 0)
  1058. goto nochange;
  1059. xstrlcpy(fltr, tmp, sizeof(fltr));
  1060. DPRINTF_S(fltr);
  1061. /* Save current */
  1062. if (ndents > 0)
  1063. mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
  1064. goto begin;
  1065. case SEL_NEXT:
  1066. if (cur < ndents - 1)
  1067. cur++;
  1068. else if (ndents)
  1069. /* Roll over, set cursor to first entry */
  1070. cur = 0;
  1071. break;
  1072. case SEL_PREV:
  1073. if (cur > 0)
  1074. cur--;
  1075. else if (ndents)
  1076. /* Roll over, set cursor to last entry */
  1077. cur = ndents - 1;
  1078. break;
  1079. case SEL_PGDN:
  1080. if (cur < ndents - 1)
  1081. cur += MIN((LINES - 4) / 2, ndents - 1 - cur);
  1082. break;
  1083. case SEL_PGUP:
  1084. if (cur > 0)
  1085. cur -= MIN((LINES - 4) / 2, cur);
  1086. break;
  1087. case SEL_HOME:
  1088. cur = 0;
  1089. break;
  1090. case SEL_END:
  1091. cur = ndents - 1;
  1092. break;
  1093. case SEL_CD:
  1094. /* Read target dir */
  1095. printprompt("chdir: ");
  1096. tmp = readln();
  1097. if (tmp == NULL) {
  1098. clearprompt();
  1099. goto nochange;
  1100. }
  1101. if (tmp[0] == '~') {
  1102. char *home = getenv("HOME");
  1103. if (home)
  1104. snprintf(newpath, PATH_MAX,
  1105. "%s%s", home, tmp + 1);
  1106. else
  1107. mkpath(path, tmp, newpath, sizeof(newpath));
  1108. } else if (tmp[0] == '-' && tmp[1] == '\0')
  1109. xstrlcpy(newpath, lastdir, sizeof(newpath));
  1110. else
  1111. mkpath(path, tmp, newpath, sizeof(newpath));
  1112. if (canopendir(newpath) == 0) {
  1113. printwarn();
  1114. goto nochange;
  1115. }
  1116. /* Save last working directory */
  1117. xstrlcpy(lastdir, path, sizeof(lastdir));
  1118. xstrlcpy(path, newpath, sizeof(path));
  1119. /* Reset filter */
  1120. xstrlcpy(fltr, ifilter, sizeof(fltr));
  1121. DPRINTF_S(path);
  1122. goto begin;
  1123. case SEL_CDHOME:
  1124. tmp = getenv("HOME");
  1125. if (tmp == NULL) {
  1126. clearprompt();
  1127. goto nochange;
  1128. }
  1129. if (canopendir(tmp) == 0) {
  1130. printwarn();
  1131. goto nochange;
  1132. }
  1133. /* Save last working directory */
  1134. xstrlcpy(lastdir, path, sizeof(lastdir));
  1135. xstrlcpy(path, tmp, sizeof(path));
  1136. /* Reset filter */
  1137. xstrlcpy(fltr, ifilter, sizeof(fltr));
  1138. DPRINTF_S(path);
  1139. goto begin;
  1140. case SEL_LAST:
  1141. xstrlcpy(newpath, lastdir, sizeof(newpath));
  1142. xstrlcpy(lastdir, path, sizeof(lastdir));
  1143. xstrlcpy(path, newpath, sizeof(path));
  1144. DPRINTF_S(path);
  1145. goto begin;
  1146. case SEL_TOGGLEDOT:
  1147. showhidden ^= 1;
  1148. initfilter(showhidden, &ifilter);
  1149. xstrlcpy(fltr, ifilter, sizeof(fltr));
  1150. goto begin;
  1151. case SEL_DETAIL:
  1152. showdetail = !showdetail;
  1153. showdetail ? (printptr = &printent_long)
  1154. : (printptr = &printent);
  1155. /* Save current */
  1156. if (ndents > 0)
  1157. mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
  1158. goto begin;
  1159. case SEL_STATS:
  1160. {
  1161. struct stat sb;
  1162. if (ndents > 0)
  1163. mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
  1164. r = lstat(oldpath, &sb);
  1165. if (r == -1)
  1166. printerr(1, "lstat");
  1167. else
  1168. show_stats(oldpath, dents[cur].name, &sb);
  1169. goto begin;
  1170. }
  1171. case SEL_FSIZE:
  1172. sizeorder = !sizeorder;
  1173. mtimeorder = 0;
  1174. /* Save current */
  1175. if (ndents > 0)
  1176. mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
  1177. goto begin;
  1178. case SEL_MTIME:
  1179. mtimeorder = !mtimeorder;
  1180. sizeorder = 0;
  1181. /* Save current */
  1182. if (ndents > 0)
  1183. mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
  1184. goto begin;
  1185. case SEL_REDRAW:
  1186. /* Save current */
  1187. if (ndents > 0)
  1188. mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
  1189. goto begin;
  1190. case SEL_COPY:
  1191. if (copier && ndents) {
  1192. char abspath[PATH_MAX];
  1193. if (strcmp(path, "/") == 0)
  1194. snprintf(abspath, PATH_MAX, "/%s",
  1195. dents[cur].name);
  1196. else
  1197. snprintf(abspath, PATH_MAX, "%s/%s",
  1198. path, dents[cur].name);
  1199. spawn(copier, abspath, NULL, 0);
  1200. printmsg(abspath);
  1201. } else if (!copier)
  1202. printmsg("NNN_COPIER is not set");
  1203. goto nochange;
  1204. case SEL_HELP:
  1205. show_help();
  1206. /* Save current */
  1207. if (ndents > 0)
  1208. mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
  1209. goto begin;
  1210. case SEL_RUN:
  1211. run = xgetenv(env, run);
  1212. exitcurses();
  1213. spawn(run, NULL, path, 1);
  1214. initcurses();
  1215. /* Re-populate as directory content may have changed */
  1216. goto begin;
  1217. case SEL_RUNARG:
  1218. run = xgetenv(env, run);
  1219. exitcurses();
  1220. spawn(run, dents[cur].name, path, 0);
  1221. initcurses();
  1222. break;
  1223. }
  1224. /* Screensaver */
  1225. if (idletimeout != 0 && idle == idletimeout) {
  1226. idle = 0;
  1227. exitcurses();
  1228. spawn(idlecmd, NULL, NULL, 0);
  1229. initcurses();
  1230. }
  1231. }
  1232. }
  1233. static void
  1234. usage(void)
  1235. {
  1236. fprintf(stderr, "usage: nnn [-d] [dir]\n");
  1237. exit(1);
  1238. }
  1239. int
  1240. main(int argc, char *argv[])
  1241. {
  1242. char cwd[PATH_MAX], *ipath;
  1243. char *ifilter;
  1244. int opt = 0;
  1245. /* Confirm we are in a terminal */
  1246. if (!isatty(0) || !isatty(1)) {
  1247. fprintf(stderr, "stdin or stdout is not a tty\n");
  1248. exit(1);
  1249. }
  1250. if (argc > 3)
  1251. usage();
  1252. while ((opt = getopt(argc, argv, "d")) != -1) {
  1253. switch (opt) {
  1254. case 'd':
  1255. /* Open in detail mode, if set */
  1256. showdetail = 1;
  1257. printptr = &printent_long;
  1258. break;
  1259. default:
  1260. usage();
  1261. }
  1262. }
  1263. if (argc == optind) {
  1264. /* Start in the current directory */
  1265. ipath = getcwd(cwd, sizeof(cwd));
  1266. if (ipath == NULL)
  1267. ipath = "/";
  1268. } else {
  1269. ipath = realpath(argv[optind], cwd);
  1270. if (!ipath) {
  1271. fprintf(stderr, "%s: no such dir\n", argv[optind]);
  1272. exit(1);
  1273. }
  1274. }
  1275. if (getuid() == 0)
  1276. showhidden = 1;
  1277. initfilter(showhidden, &ifilter);
  1278. /* Get the default desktop mime opener, if set */
  1279. opener = getenv("NNN_OPENER");
  1280. /* Get the fallback desktop mime opener, if set */
  1281. fallback_opener = getenv("NNN_FALLBACK_OPENER");
  1282. /* Get the default copier, if set */
  1283. copier = getenv("NNN_COPIER");
  1284. signal(SIGINT, SIG_IGN);
  1285. /* Test initial path */
  1286. if (canopendir(ipath) == 0) {
  1287. fprintf(stderr, "%s: %s\n", ipath, strerror(errno));
  1288. exit(1);
  1289. }
  1290. /* Set locale before curses setup */
  1291. setlocale(LC_ALL, "");
  1292. initcurses();
  1293. browse(ipath, ifilter);
  1294. exitcurses();
  1295. exit(0);
  1296. }