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.
 
 
 
 
 
 

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