My build of nnn with minor changes
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 
 
 

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