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ů.
 
 
 
 
 
 

851 řá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) {
  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;
  321. int i;
  322. char *ln = *str;
  323. int ret = 0;
  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. if (c == KEY_ENTER || c == '\r') {
  332. ret = 1;
  333. goto out;
  334. }
  335. if (c == KEY_BACKSPACE) {
  336. i--;
  337. if (i > 0) {
  338. ln = xrealloc(ln, (i + 1) * sizeof(*ln));
  339. ln[i] = '\0';
  340. } else {
  341. free(ln);
  342. ln = NULL;
  343. }
  344. goto out;
  345. }
  346. ln = xrealloc(ln, (i + 2) * sizeof(*ln));
  347. ln[i] = c;
  348. i++;
  349. ln[i] = '\0';
  350. out:
  351. curs_set(FALSE);
  352. *str = ln;
  353. return ret;
  354. }
  355. int
  356. canopendir(char *path)
  357. {
  358. DIR *dirp;
  359. dirp = opendir(path);
  360. if (dirp == NULL)
  361. return 0;
  362. closedir(dirp);
  363. return 1;
  364. }
  365. void
  366. printent(struct entry *ent, int active)
  367. {
  368. char *name;
  369. unsigned int maxlen = COLS - strlen(CURSR) - 1;
  370. char cm = 0;
  371. /* Copy name locally */
  372. name = xstrdup(ent->name);
  373. if (S_ISDIR(ent->mode)) {
  374. cm = '/';
  375. maxlen--;
  376. } else if (S_ISLNK(ent->mode)) {
  377. cm = '@';
  378. maxlen--;
  379. } else if (S_ISSOCK(ent->mode)) {
  380. cm = '=';
  381. maxlen--;
  382. } else if (S_ISFIFO(ent->mode)) {
  383. cm = '|';
  384. maxlen--;
  385. } else if (ent->mode & S_IXUSR) {
  386. cm = '*';
  387. maxlen--;
  388. }
  389. /* No text wrapping in entries */
  390. if (strlen(name) > maxlen)
  391. name[maxlen] = '\0';
  392. if (cm == 0)
  393. printw("%s%s\n", active ? CURSR : EMPTY, name);
  394. else
  395. printw("%s%s%c\n", active ? CURSR : EMPTY, name, cm);
  396. free(name);
  397. }
  398. int
  399. dentfill(char *path, struct entry **dents,
  400. int (*filter)(regex_t *, char *), regex_t *re)
  401. {
  402. DIR *dirp;
  403. struct dirent *dp;
  404. struct stat sb;
  405. char *newpath;
  406. int r, n = 0;
  407. dirp = opendir(path);
  408. if (dirp == NULL)
  409. return 0;
  410. while ((dp = readdir(dirp)) != NULL) {
  411. /* Skip self and parent */
  412. if (strcmp(dp->d_name, ".") == 0
  413. || strcmp(dp->d_name, "..") == 0)
  414. continue;
  415. if (filter(re, dp->d_name) == 0)
  416. continue;
  417. *dents = xrealloc(*dents, (n + 1) * sizeof(**dents));
  418. (*dents)[n].name = xstrdup(dp->d_name);
  419. /* Get mode flags */
  420. newpath = makepath(path, dp->d_name);
  421. r = lstat(newpath, &sb);
  422. if (r == -1)
  423. printerr(1, "lstat");
  424. (*dents)[n].mode = sb.st_mode;
  425. n++;
  426. }
  427. /* Should never be null */
  428. r = closedir(dirp);
  429. if (r == -1)
  430. printerr(1, "closedir");
  431. return n;
  432. }
  433. void
  434. dentfree(struct entry *dents, int n)
  435. {
  436. int i;
  437. for (i = 0; i < n; i++)
  438. free(dents[i].name);
  439. free(dents);
  440. }
  441. char *
  442. makepath(char *dir, char *name)
  443. {
  444. char path[PATH_MAX];
  445. /* Handle absolute path */
  446. if (name[0] == '/') {
  447. strlcpy(path, name, sizeof(path));
  448. } else {
  449. /* Handle root case */
  450. if (strcmp(dir, "/") == 0) {
  451. strlcpy(path, "/", sizeof(path));
  452. strlcat(path, name, sizeof(path));
  453. } else {
  454. strlcpy(path, dir, sizeof(path));
  455. strlcat(path, "/", sizeof(path));
  456. strlcat(path, name, sizeof(path));
  457. }
  458. }
  459. return xstrdup(path);
  460. }
  461. /* Return the position of the matching entry or 0 otherwise */
  462. int
  463. dentfind(struct entry *dents, int n, char *cwd, char *path)
  464. {
  465. int i;
  466. char *tmp;
  467. if (path == NULL)
  468. return 0;
  469. for (i = 0; i < n; i++) {
  470. tmp = makepath(cwd, dents[i].name);
  471. DPRINTF_S(path);
  472. DPRINTF_S(tmp);
  473. if (strcmp(tmp, path) == 0) {
  474. free(tmp);
  475. return i;
  476. }
  477. free(tmp);
  478. }
  479. return 0;
  480. }
  481. void
  482. browse(const char *ipath, const char *ifilter)
  483. {
  484. struct entry *dents;
  485. int i, n, cur, r, fd;
  486. int nlines, odd;
  487. char *path = xstrdup(ipath);
  488. char *filter = xstrdup(ifilter);
  489. regex_t filter_re, re;
  490. char *cwd, *newpath, *oldpath = NULL;
  491. struct stat sb;
  492. char *name, *bin, *dir, *tmp;
  493. int nowtyping = 0;
  494. begin:
  495. /* Path and filter should be malloc(3)-ed strings at all times */
  496. n = 0;
  497. dents = NULL;
  498. if (canopendir(path) == 0) {
  499. printwarn();
  500. goto nochange;
  501. }
  502. /* Search filter */
  503. r = setfilter(&filter_re, filter);
  504. if (r != 0)
  505. goto nochange;
  506. n = dentfill(path, &dents, visible, &filter_re);
  507. qsort(dents, n, sizeof(*dents), entrycmp);
  508. /* Find cur from history */
  509. cur = dentfind(dents, n, path, oldpath);
  510. free(oldpath);
  511. oldpath = NULL;
  512. for (;;) {
  513. nlines = MIN(LINES - 4, n);
  514. /* Clean screen */
  515. erase();
  516. /* Strip trailing slashes */
  517. for (i = strlen(path) - 1; i > 0; i--)
  518. if (path[i] == '/')
  519. path[i] = '\0';
  520. else
  521. break;
  522. DPRINTF_D(cur);
  523. DPRINTF_S(path);
  524. /* No text wrapping in cwd line */
  525. cwd = xmalloc(COLS * sizeof(char));
  526. strlcpy(cwd, path, COLS * sizeof(char));
  527. cwd[COLS - strlen(CWD) - 1] = '\0';
  528. printw(CWD "%s\n\n", cwd);
  529. /* Print listing */
  530. odd = ISODD(nlines);
  531. if (cur < nlines / 2) {
  532. for (i = 0; i < nlines; i++)
  533. printent(&dents[i], i == cur);
  534. } else if (cur >= n - nlines / 2) {
  535. for (i = n - nlines; i < n; i++)
  536. printent(&dents[i], i == cur);
  537. } else {
  538. for (i = cur - nlines / 2;
  539. i < cur + nlines / 2 + odd; i++)
  540. printent(&dents[i], i == cur);
  541. }
  542. /* Handle filter-as-you-type mode */
  543. if (nowtyping)
  544. goto moretyping;
  545. nochange:
  546. switch (nextsel()) {
  547. case SEL_QUIT:
  548. free(path);
  549. free(filter);
  550. dentfree(dents, n);
  551. return;
  552. case SEL_BACK:
  553. /* There is no going back */
  554. if (strcmp(path, "/") == 0 ||
  555. strcmp(path, ".") == 0 ||
  556. strchr(path, '/') == NULL)
  557. goto nochange;
  558. if (canopendir(path) == 0) {
  559. printwarn();
  560. goto nochange;
  561. }
  562. dir = xdirname(path);
  563. /* Save history */
  564. oldpath = path;
  565. path = dir;
  566. /* Reset filter */
  567. free(filter);
  568. filter = xstrdup(ifilter);
  569. goto out;
  570. case SEL_GOIN:
  571. /* Cannot descend in empty directories */
  572. if (n == 0)
  573. goto nochange;
  574. name = dents[cur].name;
  575. newpath = makepath(path, name);
  576. DPRINTF_S(newpath);
  577. /* Get path info */
  578. fd = open(newpath, O_RDONLY | O_NONBLOCK);
  579. if (fd == -1) {
  580. printwarn();
  581. free(newpath);
  582. goto nochange;
  583. }
  584. r = fstat(fd, &sb);
  585. if (r == -1) {
  586. printwarn();
  587. close(fd);
  588. free(newpath);
  589. goto nochange;
  590. }
  591. close(fd);
  592. DPRINTF_U(sb.st_mode);
  593. switch (sb.st_mode & S_IFMT) {
  594. case S_IFDIR:
  595. if (canopendir(newpath) == 0) {
  596. printwarn();
  597. free(newpath);
  598. goto nochange;
  599. }
  600. free(path);
  601. path = newpath;
  602. /* Reset filter */
  603. free(filter);
  604. filter = xstrdup(ifilter);
  605. goto out;
  606. case S_IFREG:
  607. bin = openwith(newpath);
  608. if (bin == NULL) {
  609. printmsg("No association");
  610. free(newpath);
  611. goto nochange;
  612. }
  613. exitcurses();
  614. spawn(bin, newpath, NULL);
  615. initcurses();
  616. free(newpath);
  617. continue;
  618. default:
  619. printmsg("Unsupported file");
  620. goto nochange;
  621. }
  622. case SEL_FLTR:
  623. /* Read filter */
  624. printprompt("filter: ");
  625. tmp = readln();
  626. if (tmp == NULL)
  627. tmp = xstrdup(ifilter);
  628. /* Check and report regex errors */
  629. r = setfilter(&re, tmp);
  630. if (r != 0) {
  631. free(tmp);
  632. goto nochange;
  633. }
  634. free(filter);
  635. filter = tmp;
  636. DPRINTF_S(filter);
  637. /* Save current */
  638. if (n > 0)
  639. oldpath = makepath(path, dents[cur].name);
  640. goto out;
  641. case SEL_TYPE:
  642. nowtyping = 1;
  643. tmp = NULL;
  644. moretyping:
  645. printprompt("type: ");
  646. if (tmp != NULL)
  647. printw("%s", tmp);
  648. r = readmore(&tmp);
  649. DPRINTF_D(r);
  650. DPRINTF_S(tmp);
  651. if (r == 1)
  652. nowtyping = 0;
  653. /* Check regex errors */
  654. if (tmp != NULL)
  655. r = setfilter(&re, tmp);
  656. if (r != 0 && nowtyping)
  657. goto moretyping;
  658. /* Copy or reset filter */
  659. free(filter);
  660. if (tmp != NULL)
  661. filter = xstrdup(tmp);
  662. else
  663. filter = xstrdup(ifilter);
  664. /* Save current */
  665. if (n > 0)
  666. oldpath = makepath(path, dents[cur].name);
  667. if (!nowtyping)
  668. free(tmp);
  669. goto out;
  670. case SEL_NEXT:
  671. if (cur < n - 1)
  672. cur++;
  673. break;
  674. case SEL_PREV:
  675. if (cur > 0)
  676. cur--;
  677. break;
  678. case SEL_PGDN:
  679. if (cur < n - 1)
  680. cur += MIN((LINES - 4) / 2, n - 1 - cur);
  681. break;
  682. case SEL_PGUP:
  683. if (cur > 0)
  684. cur -= MIN((LINES - 4) / 2, cur);
  685. break;
  686. case SEL_SH:
  687. exitcurses();
  688. spawn("/bin/sh", NULL, path);
  689. initcurses();
  690. break;
  691. case SEL_CD:
  692. /* Read target dir */
  693. printprompt("chdir: ");
  694. tmp = readln();
  695. if (tmp == NULL) {
  696. clearprompt();
  697. goto nochange;
  698. }
  699. newpath = makepath(path, tmp);
  700. free(tmp);
  701. if (canopendir(newpath) == 0) {
  702. free(newpath);
  703. printwarn();
  704. goto nochange;
  705. }
  706. free(path);
  707. path = newpath;
  708. free(filter);
  709. filter = xstrdup(ifilter); /* Reset filter */
  710. DPRINTF_S(path);
  711. goto out;
  712. }
  713. }
  714. out:
  715. dentfree(dents, n);
  716. goto begin;
  717. }
  718. int
  719. main(int argc, char *argv[])
  720. {
  721. char cwd[PATH_MAX], *ipath;
  722. char *ifilter;
  723. if (getuid() == 0)
  724. ifilter = ".";
  725. else
  726. ifilter = "^[^.]"; /* Hide dotfiles */
  727. if (argv[1] != NULL) {
  728. ipath = argv[1];
  729. } else {
  730. ipath = getcwd(cwd, sizeof(cwd));
  731. if (ipath == NULL)
  732. ipath = "/";
  733. }
  734. /* Test initial path */
  735. if (canopendir(ipath) == 0)
  736. printerr(1, ipath);
  737. /* Set locale before curses setup */
  738. setlocale(LC_ALL, "");
  739. initcurses();
  740. browse(ipath, ifilter);
  741. exitcurses();
  742. return 0;
  743. }