My build of nnn with minor changes
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 
 
 

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