My build of nnn with minor changes
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 
 

863 строки
14 KiB

  1. #include <sys/stat.h>
  2. #include <sys/types.h>
  3. #include <sys/wait.h>
  4. #include <errno.h>
  5. #include <fcntl.h>
  6. #include <dirent.h>
  7. #include <curses.h>
  8. #include <libgen.h>
  9. #include <limits.h>
  10. #include <locale.h>
  11. #include <regex.h>
  12. #include <stdarg.h>
  13. #include <stdlib.h>
  14. #include <stdio.h>
  15. #include <signal.h>
  16. #include <string.h>
  17. #include <unistd.h>
  18. #include "util.h"
  19. #ifdef DEBUG
  20. #define DEBUG_FD 8
  21. #define DPRINTF_D(x) dprintf(DEBUG_FD, #x "=%d\n", x)
  22. #define DPRINTF_U(x) dprintf(DEBUG_FD, #x "=%u\n", x)
  23. #define DPRINTF_S(x) dprintf(DEBUG_FD, #x "=%s\n", x)
  24. #define DPRINTF_P(x) dprintf(DEBUG_FD, #x "=0x%p\n", x)
  25. #else
  26. #define DPRINTF_D(x)
  27. #define DPRINTF_U(x)
  28. #define DPRINTF_S(x)
  29. #define DPRINTF_P(x)
  30. #endif /* DEBUG */
  31. #define LEN(x) (sizeof(x) / sizeof(*(x)))
  32. #undef MIN
  33. #define MIN(x, y) ((x) < (y) ? (x) : (y))
  34. #define ISODD(x) ((x) & 1)
  35. #define CONTROL(c) ((c) ^ 0x40)
  36. struct assoc {
  37. char *regex; /* Regex to match on filename */
  38. char *bin; /* Program */
  39. };
  40. /* Supported actions */
  41. enum action {
  42. SEL_QUIT = 1,
  43. SEL_BACK,
  44. SEL_GOIN,
  45. SEL_FLTR,
  46. SEL_TYPE,
  47. SEL_NEXT,
  48. SEL_PREV,
  49. SEL_PGDN,
  50. SEL_PGUP,
  51. SEL_SH,
  52. SEL_CD,
  53. SEL_MTIME,
  54. };
  55. struct key {
  56. int sym; /* Key pressed */
  57. enum action act; /* Action */
  58. };
  59. #include "config.h"
  60. struct entry {
  61. char *name;
  62. mode_t mode;
  63. time_t t;
  64. };
  65. /*
  66. * Layout:
  67. * .---------
  68. * | cwd: /mnt/path
  69. * |
  70. * | file0
  71. * | file1
  72. * | > file2
  73. * | file3
  74. * | file4
  75. * ...
  76. * | filen
  77. * |
  78. * | Permission denied
  79. * '------
  80. */
  81. void printmsg(char *msg);
  82. void printwarn(void);
  83. void printerr(int ret, char *prefix);
  84. char *makepath(char *dir, char *name);
  85. #undef dprintf
  86. int
  87. dprintf(int fd, const char *fmt, ...)
  88. {
  89. char buf[BUFSIZ];
  90. int r;
  91. va_list ap;
  92. va_start(ap, fmt);
  93. r = vsnprintf(buf, sizeof(buf), fmt, ap);
  94. if (r > 0)
  95. write(fd, buf, r);
  96. va_end(ap);
  97. return r;
  98. }
  99. void *
  100. xmalloc(size_t size)
  101. {
  102. void *p;
  103. p = malloc(size);
  104. if (p == NULL)
  105. printerr(1, "malloc");
  106. return p;
  107. }
  108. void *
  109. xrealloc(void *p, size_t size)
  110. {
  111. p = realloc(p, size);
  112. if (p == NULL)
  113. printerr(1, "realloc");
  114. return p;
  115. }
  116. char *
  117. xstrdup(const char *s)
  118. {
  119. char *p;
  120. p = strdup(s);
  121. if (p == NULL)
  122. printerr(1, "strdup");
  123. return p;
  124. }
  125. char *
  126. xdirname(const char *path)
  127. {
  128. char *p, *tmp;
  129. /* Some implementations of dirname(3) may modify `path' and some
  130. * return a pointer inside `path' and we cannot free(3) the
  131. * original string if we lose track of it. */
  132. tmp = xstrdup(path);
  133. p = dirname(tmp);
  134. if (p == NULL) {
  135. free(tmp);
  136. printerr(1, "dirname");
  137. }
  138. /* Make sure this is a malloc(3)-ed string */
  139. p = xstrdup(p);
  140. free(tmp);
  141. return p;
  142. }
  143. void
  144. spawn(const char *file, const char *arg, const char *dir)
  145. {
  146. pid_t pid;
  147. int status;
  148. pid = fork();
  149. if (pid == 0) {
  150. if (dir != NULL)
  151. chdir(dir);
  152. execlp(file, file, arg, NULL);
  153. _exit(1);
  154. } else {
  155. /* Ignore interruptions */
  156. while (waitpid(pid, &status, 0) == -1)
  157. DPRINTF_D(status);
  158. DPRINTF_D(pid);
  159. }
  160. }
  161. char *
  162. openwith(char *file)
  163. {
  164. regex_t regex;
  165. char *bin = NULL;
  166. int i;
  167. for (i = 0; i < LEN(assocs); i++) {
  168. if (regcomp(&regex, assocs[i].regex,
  169. REG_NOSUB | REG_EXTENDED) != 0)
  170. continue;
  171. if (regexec(&regex, file, 0, NULL, 0) == 0) {
  172. bin = assocs[i].bin;
  173. break;
  174. }
  175. }
  176. DPRINTF_S(bin);
  177. return bin;
  178. }
  179. int
  180. setfilter(regex_t *regex, char *filter)
  181. {
  182. char *errbuf;
  183. int r;
  184. r = regcomp(regex, filter, REG_NOSUB | REG_EXTENDED);
  185. if (r != 0) {
  186. errbuf = xmalloc(COLS * sizeof(char));
  187. regerror(r, regex, errbuf, COLS * sizeof(char));
  188. printmsg(errbuf);
  189. free(errbuf);
  190. }
  191. return r;
  192. }
  193. int
  194. visible(regex_t *regex, char *file)
  195. {
  196. return regexec(regex, file, 0, NULL, 0) == 0;
  197. }
  198. int
  199. entrycmp(const void *va, const void *vb)
  200. {
  201. const struct entry *a, *b;
  202. a = (struct entry *)va;
  203. b = (struct entry *)vb;
  204. if (mtimeorder)
  205. return b->t - a->t;
  206. return strcmp(a->name, b->name);
  207. }
  208. void
  209. initcurses(void)
  210. {
  211. initscr();
  212. cbreak();
  213. noecho();
  214. nonl();
  215. intrflush(stdscr, FALSE);
  216. keypad(stdscr, TRUE);
  217. curs_set(FALSE); /* Hide cursor */
  218. }
  219. void
  220. exitcurses(void)
  221. {
  222. endwin(); /* Restore terminal */
  223. }
  224. /* Messages show up at the bottom */
  225. void
  226. printmsg(char *msg)
  227. {
  228. move(LINES - 1, 0);
  229. printw("%s\n", msg);
  230. }
  231. /* Display warning as a message */
  232. void
  233. printwarn(void)
  234. {
  235. printmsg(strerror(errno));
  236. }
  237. /* Kill curses and display error before exiting */
  238. void
  239. printerr(int ret, char *prefix)
  240. {
  241. exitcurses();
  242. fprintf(stderr, "%s: %s\n", prefix, strerror(errno));
  243. exit(ret);
  244. }
  245. /* Clear the last line */
  246. void
  247. clearprompt(void)
  248. {
  249. printmsg("");
  250. }
  251. /* Print prompt on the last line */
  252. void
  253. printprompt(char *str)
  254. {
  255. clearprompt();
  256. printw(str);
  257. }
  258. /* Returns SEL_* if key is bound and 0 otherwise */
  259. int
  260. nextsel(void)
  261. {
  262. int c, i;
  263. c = getch();
  264. for (i = 0; i < LEN(bindings); i++)
  265. if (c == bindings[i].sym)
  266. return bindings[i].act;
  267. return 0;
  268. }
  269. char *
  270. readln(void)
  271. {
  272. int c;
  273. int i = 0;
  274. char *ln = NULL;
  275. int y, x, x0;
  276. echo();
  277. curs_set(TRUE);
  278. /* Starting point */
  279. getyx(stdscr, y, x);
  280. x0 = x;
  281. while ((c = getch()) != ERR) {
  282. if (c == KEY_ENTER || c == '\r')
  283. break;
  284. if (c == KEY_BACKSPACE || c == CONTROL('H')) {
  285. getyx(stdscr, y, x);
  286. if (x >= x0) {
  287. i--;
  288. if (i > 0) {
  289. ln = xrealloc(ln, i * sizeof(*ln));
  290. } else {
  291. free(ln);
  292. ln = NULL;
  293. }
  294. move(y, x);
  295. printw("%c", ' ');
  296. move(y, x);
  297. } else {
  298. move(y, x0);
  299. }
  300. continue;
  301. }
  302. ln = xrealloc(ln, (i + 1) * sizeof(*ln));
  303. ln[i] = c;
  304. i++;
  305. }
  306. if (ln != NULL) {
  307. ln = xrealloc(ln, (i + 1) * sizeof(*ln));
  308. ln[i] = '\0';
  309. }
  310. curs_set(FALSE);
  311. noecho();
  312. return ln;
  313. }
  314. /*
  315. * Read one key and modify the provided string accordingly.
  316. * Returns 0 when more input is expected and 1 on completion.
  317. */
  318. int
  319. readmore(char **str)
  320. {
  321. int c, ret = 0;
  322. size_t i;
  323. char *ln = *str;
  324. if (ln != NULL)
  325. i = strlen(ln);
  326. else
  327. i = 0;
  328. DPRINTF_D(i);
  329. curs_set(TRUE);
  330. c = getch();
  331. switch (c) {
  332. case KEY_ENTER:
  333. case '\r':
  334. ret = 1;
  335. break;
  336. case KEY_BACKSPACE:
  337. case CONTROL('H'):
  338. i--;
  339. if (i > 0) {
  340. ln = xrealloc(ln, (i + 1) * sizeof(*ln));
  341. ln[i] = '\0';
  342. } else {
  343. free(ln);
  344. ln = NULL;
  345. }
  346. break;
  347. default:
  348. i++;
  349. ln = xrealloc(ln, (i + 1) * sizeof(*ln));
  350. ln[i - 1] = c;
  351. ln[i] = '\0';
  352. }
  353. curs_set(FALSE);
  354. *str = ln;
  355. return ret;
  356. }
  357. int
  358. canopendir(char *path)
  359. {
  360. DIR *dirp;
  361. dirp = opendir(path);
  362. if (dirp == NULL)
  363. return 0;
  364. closedir(dirp);
  365. return 1;
  366. }
  367. void
  368. printent(struct entry *ent, int active)
  369. {
  370. char *name;
  371. unsigned int maxlen = COLS - strlen(CURSR) - 1;
  372. char cm = 0;
  373. /* Copy name locally */
  374. name = xstrdup(ent->name);
  375. if (S_ISDIR(ent->mode)) {
  376. cm = '/';
  377. maxlen--;
  378. } else if (S_ISLNK(ent->mode)) {
  379. cm = '@';
  380. maxlen--;
  381. } else if (S_ISSOCK(ent->mode)) {
  382. cm = '=';
  383. maxlen--;
  384. } else if (S_ISFIFO(ent->mode)) {
  385. cm = '|';
  386. maxlen--;
  387. } else if (ent->mode & S_IXUSR) {
  388. cm = '*';
  389. maxlen--;
  390. }
  391. /* No text wrapping in entries */
  392. if (strlen(name) > maxlen)
  393. name[maxlen] = '\0';
  394. if (cm == 0)
  395. printw("%s%s\n", active ? CURSR : EMPTY, name);
  396. else
  397. printw("%s%s%c\n", active ? CURSR : EMPTY, name, cm);
  398. free(name);
  399. }
  400. int
  401. dentfill(char *path, struct entry **dents,
  402. int (*filter)(regex_t *, char *), regex_t *re)
  403. {
  404. DIR *dirp;
  405. struct dirent *dp;
  406. struct stat sb;
  407. char *newpath;
  408. int r, n = 0;
  409. dirp = opendir(path);
  410. if (dirp == NULL)
  411. return 0;
  412. while ((dp = readdir(dirp)) != NULL) {
  413. /* Skip self and parent */
  414. if (strcmp(dp->d_name, ".") == 0
  415. || strcmp(dp->d_name, "..") == 0)
  416. continue;
  417. if (filter(re, dp->d_name) == 0)
  418. continue;
  419. *dents = xrealloc(*dents, (n + 1) * sizeof(**dents));
  420. (*dents)[n].name = xstrdup(dp->d_name);
  421. /* Get mode flags */
  422. newpath = makepath(path, dp->d_name);
  423. r = lstat(newpath, &sb);
  424. if (r == -1)
  425. printerr(1, "lstat");
  426. (*dents)[n].mode = sb.st_mode;
  427. (*dents)[n].t = sb.st_mtime;
  428. n++;
  429. }
  430. /* Should never be null */
  431. r = closedir(dirp);
  432. if (r == -1)
  433. printerr(1, "closedir");
  434. return n;
  435. }
  436. void
  437. dentfree(struct entry *dents, int n)
  438. {
  439. int i;
  440. for (i = 0; i < n; i++)
  441. free(dents[i].name);
  442. free(dents);
  443. }
  444. char *
  445. makepath(char *dir, char *name)
  446. {
  447. char path[PATH_MAX];
  448. /* Handle absolute path */
  449. if (name[0] == '/') {
  450. strlcpy(path, name, sizeof(path));
  451. } else {
  452. /* Handle root case */
  453. if (strcmp(dir, "/") == 0) {
  454. strlcpy(path, "/", sizeof(path));
  455. strlcat(path, name, sizeof(path));
  456. } else {
  457. strlcpy(path, dir, sizeof(path));
  458. strlcat(path, "/", sizeof(path));
  459. strlcat(path, name, sizeof(path));
  460. }
  461. }
  462. return xstrdup(path);
  463. }
  464. /* Return the position of the matching entry or 0 otherwise */
  465. int
  466. dentfind(struct entry *dents, int n, char *cwd, char *path)
  467. {
  468. int i;
  469. char *tmp;
  470. if (path == NULL)
  471. return 0;
  472. for (i = 0; i < n; i++) {
  473. tmp = makepath(cwd, dents[i].name);
  474. DPRINTF_S(path);
  475. DPRINTF_S(tmp);
  476. if (strcmp(tmp, path) == 0) {
  477. free(tmp);
  478. return i;
  479. }
  480. free(tmp);
  481. }
  482. return 0;
  483. }
  484. void
  485. browse(const char *ipath, const char *ifilter)
  486. {
  487. struct entry *dents;
  488. int i, n, cur, r, fd;
  489. int nlines, odd;
  490. char *path = xstrdup(ipath);
  491. char *filter = xstrdup(ifilter);
  492. regex_t filter_re, re;
  493. char *cwd, *newpath, *oldpath = NULL;
  494. struct stat sb;
  495. char *name, *bin, *dir, *tmp;
  496. int nowtyping = 0;
  497. begin:
  498. /* Path and filter should be malloc(3)-ed strings at all times */
  499. n = 0;
  500. dents = NULL;
  501. if (canopendir(path) == 0) {
  502. printwarn();
  503. goto nochange;
  504. }
  505. /* Search filter */
  506. r = setfilter(&filter_re, filter);
  507. if (r != 0)
  508. goto nochange;
  509. n = dentfill(path, &dents, visible, &filter_re);
  510. qsort(dents, n, sizeof(*dents), entrycmp);
  511. /* Find cur from history */
  512. cur = dentfind(dents, n, path, oldpath);
  513. free(oldpath);
  514. oldpath = NULL;
  515. for (;;) {
  516. nlines = MIN(LINES - 4, n);
  517. /* Clean screen */
  518. erase();
  519. /* Strip trailing slashes */
  520. for (i = strlen(path) - 1; i > 0; i--)
  521. if (path[i] == '/')
  522. path[i] = '\0';
  523. else
  524. break;
  525. DPRINTF_D(cur);
  526. DPRINTF_S(path);
  527. /* No text wrapping in cwd line */
  528. cwd = xmalloc(COLS * sizeof(char));
  529. strlcpy(cwd, path, COLS * sizeof(char));
  530. cwd[COLS - strlen(CWD) - 1] = '\0';
  531. printw(CWD "%s\n\n", cwd);
  532. /* Print listing */
  533. odd = ISODD(nlines);
  534. if (cur < nlines / 2) {
  535. for (i = 0; i < nlines; i++)
  536. printent(&dents[i], i == cur);
  537. } else if (cur >= n - nlines / 2) {
  538. for (i = n - nlines; i < n; i++)
  539. printent(&dents[i], i == cur);
  540. } else {
  541. for (i = cur - nlines / 2;
  542. i < cur + nlines / 2 + odd; i++)
  543. printent(&dents[i], i == cur);
  544. }
  545. /* Handle filter-as-you-type mode */
  546. if (nowtyping)
  547. goto moretyping;
  548. nochange:
  549. switch (nextsel()) {
  550. case SEL_QUIT:
  551. free(path);
  552. free(filter);
  553. dentfree(dents, n);
  554. return;
  555. case SEL_BACK:
  556. /* There is no going back */
  557. if (strcmp(path, "/") == 0 ||
  558. strcmp(path, ".") == 0 ||
  559. strchr(path, '/') == NULL)
  560. goto nochange;
  561. if (canopendir(path) == 0) {
  562. printwarn();
  563. goto nochange;
  564. }
  565. dir = xdirname(path);
  566. /* Save history */
  567. oldpath = path;
  568. path = dir;
  569. /* Reset filter */
  570. free(filter);
  571. filter = xstrdup(ifilter);
  572. goto out;
  573. case SEL_GOIN:
  574. /* Cannot descend in empty directories */
  575. if (n == 0)
  576. goto nochange;
  577. name = dents[cur].name;
  578. newpath = makepath(path, name);
  579. DPRINTF_S(newpath);
  580. /* Get path info */
  581. fd = open(newpath, O_RDONLY | O_NONBLOCK);
  582. if (fd == -1) {
  583. printwarn();
  584. free(newpath);
  585. goto nochange;
  586. }
  587. r = fstat(fd, &sb);
  588. if (r == -1) {
  589. printwarn();
  590. close(fd);
  591. free(newpath);
  592. goto nochange;
  593. }
  594. close(fd);
  595. DPRINTF_U(sb.st_mode);
  596. switch (sb.st_mode & S_IFMT) {
  597. case S_IFDIR:
  598. if (canopendir(newpath) == 0) {
  599. printwarn();
  600. free(newpath);
  601. goto nochange;
  602. }
  603. free(path);
  604. path = newpath;
  605. /* Reset filter */
  606. free(filter);
  607. filter = xstrdup(ifilter);
  608. goto out;
  609. case S_IFREG:
  610. bin = openwith(newpath);
  611. if (bin == NULL) {
  612. printmsg("No association");
  613. free(newpath);
  614. goto nochange;
  615. }
  616. exitcurses();
  617. spawn(bin, newpath, NULL);
  618. initcurses();
  619. free(newpath);
  620. continue;
  621. default:
  622. printmsg("Unsupported file");
  623. goto nochange;
  624. }
  625. case SEL_FLTR:
  626. /* Read filter */
  627. printprompt("filter: ");
  628. tmp = readln();
  629. if (tmp == NULL)
  630. tmp = xstrdup(ifilter);
  631. /* Check and report regex errors */
  632. r = setfilter(&re, tmp);
  633. if (r != 0) {
  634. free(tmp);
  635. goto nochange;
  636. }
  637. free(filter);
  638. filter = tmp;
  639. DPRINTF_S(filter);
  640. /* Save current */
  641. if (n > 0)
  642. oldpath = makepath(path, dents[cur].name);
  643. goto out;
  644. case SEL_TYPE:
  645. nowtyping = 1;
  646. tmp = NULL;
  647. moretyping:
  648. printprompt("type: ");
  649. if (tmp != NULL)
  650. printw("%s", tmp);
  651. r = readmore(&tmp);
  652. DPRINTF_D(r);
  653. DPRINTF_S(tmp);
  654. if (r == 1)
  655. nowtyping = 0;
  656. /* Check regex errors */
  657. if (tmp != NULL) {
  658. r = setfilter(&re, tmp);
  659. if (r != 0)
  660. if (nowtyping) {
  661. goto moretyping;
  662. } else {
  663. free(tmp);
  664. goto nochange;
  665. }
  666. }
  667. /* Copy or reset filter */
  668. free(filter);
  669. if (tmp != NULL)
  670. filter = xstrdup(tmp);
  671. else
  672. filter = xstrdup(ifilter);
  673. /* Save current */
  674. if (n > 0)
  675. oldpath = makepath(path, dents[cur].name);
  676. if (!nowtyping)
  677. free(tmp);
  678. goto out;
  679. case SEL_NEXT:
  680. if (cur < n - 1)
  681. cur++;
  682. break;
  683. case SEL_PREV:
  684. if (cur > 0)
  685. cur--;
  686. break;
  687. case SEL_PGDN:
  688. if (cur < n - 1)
  689. cur += MIN((LINES - 4) / 2, n - 1 - cur);
  690. break;
  691. case SEL_PGUP:
  692. if (cur > 0)
  693. cur -= MIN((LINES - 4) / 2, cur);
  694. break;
  695. case SEL_SH:
  696. exitcurses();
  697. spawn("/bin/sh", NULL, path);
  698. initcurses();
  699. break;
  700. case SEL_CD:
  701. /* Read target dir */
  702. printprompt("chdir: ");
  703. tmp = readln();
  704. if (tmp == NULL) {
  705. clearprompt();
  706. goto nochange;
  707. }
  708. newpath = makepath(path, tmp);
  709. free(tmp);
  710. if (canopendir(newpath) == 0) {
  711. free(newpath);
  712. printwarn();
  713. goto nochange;
  714. }
  715. free(path);
  716. path = newpath;
  717. free(filter);
  718. filter = xstrdup(ifilter); /* Reset filter */
  719. DPRINTF_S(path);
  720. goto out;
  721. case SEL_MTIME:
  722. mtimeorder = !mtimeorder;
  723. goto out;
  724. }
  725. }
  726. out:
  727. dentfree(dents, n);
  728. goto begin;
  729. }
  730. int
  731. main(int argc, char *argv[])
  732. {
  733. char cwd[PATH_MAX], *ipath;
  734. char *ifilter;
  735. if (getuid() == 0)
  736. ifilter = ".";
  737. else
  738. ifilter = "^[^.]"; /* Hide dotfiles */
  739. if (argv[1] != NULL) {
  740. ipath = argv[1];
  741. } else {
  742. ipath = getcwd(cwd, sizeof(cwd));
  743. if (ipath == NULL)
  744. ipath = "/";
  745. }
  746. /* Test initial path */
  747. if (canopendir(ipath) == 0)
  748. printerr(1, ipath);
  749. /* Set locale before curses setup */
  750. setlocale(LC_ALL, "");
  751. initcurses();
  752. browse(ipath, ifilter);
  753. exitcurses();
  754. return 0;
  755. }