My build of nnn with minor changes
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 
 

753 lignes
12 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 <stdlib.h>
  13. #include <stdio.h>
  14. #include <signal.h>
  15. #include <string.h>
  16. #include <unistd.h>
  17. #include "queue.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. #define MIN(x, y) ((x) < (y) ? (x) : (y))
  33. #define ISODD(x) ((x) & 1)
  34. #define CONTROL(c) ((c) ^ 0x40)
  35. struct assoc {
  36. char *regex; /* Regex to match on filename */
  37. char *bin; /* Program */
  38. };
  39. #include "config.h"
  40. struct entry {
  41. char *name;
  42. mode_t mode;
  43. };
  44. struct history {
  45. int pos;
  46. SLIST_ENTRY(history) entry;
  47. };
  48. SLIST_HEAD(histhead, history) histhead = SLIST_HEAD_INITIALIZER(histhead);
  49. /*
  50. * Layout:
  51. * .---------
  52. * | cwd: /mnt/path
  53. * |
  54. * | file0
  55. * | file1
  56. * | > file2
  57. * | file3
  58. * | file4
  59. * ...
  60. * | filen
  61. * |
  62. * | Permission denied
  63. * '------
  64. */
  65. void printmsg(char *msg);
  66. void printwarn(void);
  67. void printerr(int ret, char *prefix);
  68. void *
  69. xmalloc(size_t size)
  70. {
  71. void *p;
  72. p = malloc(size);
  73. if (p == NULL)
  74. printerr(1, "malloc");
  75. return p;
  76. }
  77. void *
  78. xrealloc(void *p, size_t size)
  79. {
  80. p = realloc(p, size);
  81. if (p == NULL)
  82. printerr(1, "realloc");
  83. return p;
  84. }
  85. char *
  86. xstrdup(const char *s)
  87. {
  88. char *p;
  89. p = strdup(s);
  90. if (p == NULL)
  91. printerr(1, "strdup");
  92. return p;
  93. }
  94. char *
  95. xrealpath(const char *path)
  96. {
  97. char *p;
  98. p = realpath(path, NULL);
  99. if (p == NULL)
  100. printerr(1, "realpath");
  101. return p;
  102. }
  103. char *
  104. xdirname(const char *path)
  105. {
  106. char *p, *tmp;
  107. /* Some implementations of dirname(3) may modify `path' and some
  108. * return a pointer inside `path` and we cannot free(3) the
  109. * original string if we lose track of it. */
  110. tmp = xstrdup(path);
  111. p = dirname(tmp);
  112. if (p == NULL) {
  113. free(tmp);
  114. printerr(1, "dirname");
  115. }
  116. /* Make sure this is a malloc(3)-ed string */
  117. p = xstrdup(p);
  118. free(tmp);
  119. return p;
  120. }
  121. void
  122. spawn(const char *file, const char *arg)
  123. {
  124. pid_t pid;
  125. int status;
  126. pid = fork();
  127. if (pid == 0) {
  128. execlp(file, file, arg, NULL);
  129. _exit(1);
  130. } else {
  131. /* Ignore interruptions */
  132. while (waitpid(pid, &status, 0) == -1)
  133. DPRINTF_D(status);
  134. DPRINTF_D(pid);
  135. }
  136. }
  137. char *
  138. openwith(char *file)
  139. {
  140. regex_t regex;
  141. char *bin = NULL;
  142. int i;
  143. for (i = 0; i < LEN(assocs); i++) {
  144. if (regcomp(&regex, assocs[i].regex,
  145. REG_NOSUB | REG_EXTENDED) != 0)
  146. continue;
  147. if (regexec(&regex, file, 0, NULL, 0) != REG_NOMATCH) {
  148. bin = assocs[i].bin;
  149. break;
  150. }
  151. }
  152. DPRINTF_S(bin);
  153. return bin;
  154. }
  155. int
  156. setfilter(regex_t *regex, char *filter)
  157. {
  158. char *errbuf;
  159. int r;
  160. r = regcomp(regex, filter, REG_NOSUB | REG_EXTENDED);
  161. if (r != 0) {
  162. errbuf = xmalloc(COLS * sizeof(char));
  163. regerror(r, regex, errbuf, COLS * sizeof(char));
  164. printmsg(errbuf);
  165. free(errbuf);
  166. }
  167. return r;
  168. }
  169. int
  170. visible(regex_t *regex, char *file)
  171. {
  172. if (regexec(regex, file, 0, NULL, 0) != REG_NOMATCH)
  173. return 1;
  174. return 0;
  175. }
  176. int
  177. entrycmp(const void *va, const void *vb)
  178. {
  179. const struct entry *a, *b;
  180. a = (struct entry *)va;
  181. b = (struct entry *)vb;
  182. return strcmp(a->name, b->name);
  183. }
  184. void
  185. initcurses(void)
  186. {
  187. initscr();
  188. cbreak();
  189. noecho();
  190. nonl();
  191. intrflush(stdscr, FALSE);
  192. keypad(stdscr, TRUE);
  193. curs_set(FALSE); /* Hide cursor */
  194. }
  195. void
  196. exitcurses(void)
  197. {
  198. endwin(); /* Restore terminal */
  199. }
  200. /* Messages show up at the bottom */
  201. void
  202. printmsg(char *msg)
  203. {
  204. move(LINES - 1, 0);
  205. printw("%s\n", msg);
  206. }
  207. /* Display warning as a message */
  208. void
  209. printwarn(void)
  210. {
  211. printmsg(strerror(errno));
  212. }
  213. /* Kill curses and display error before exiting */
  214. void
  215. printerr(int ret, char *prefix)
  216. {
  217. exitcurses();
  218. fprintf(stderr, "%s: %s\n", prefix, strerror(errno));
  219. exit(ret);
  220. }
  221. /* Clear the last line */
  222. void
  223. clearprompt(void)
  224. {
  225. printmsg("");
  226. }
  227. /* Print prompt on the last line */
  228. void
  229. printprompt(char *str)
  230. {
  231. clearprompt();
  232. printw(str);
  233. }
  234. /*
  235. * Returns 0 normally
  236. * On movement it updates *cur
  237. * Returns SEL_{QUIT,BACK,GOIN,FLTR,SH,CD} otherwise
  238. */
  239. enum {
  240. SEL_QUIT = 1,
  241. SEL_BACK,
  242. SEL_GOIN,
  243. SEL_FLTR,
  244. SEL_SH,
  245. SEL_CD,
  246. };
  247. int
  248. nextsel(int *cur, int max)
  249. {
  250. int c;
  251. c = getch();
  252. switch (c) {
  253. case 'q':
  254. return SEL_QUIT;
  255. /* Back */
  256. case KEY_BACKSPACE:
  257. case KEY_LEFT:
  258. case 'h':
  259. return SEL_BACK;
  260. /* Inside */
  261. case KEY_ENTER:
  262. case '\r':
  263. case KEY_RIGHT:
  264. case 'l':
  265. return SEL_GOIN;
  266. /* Filter */
  267. case '/':
  268. case '&':
  269. return SEL_FLTR;
  270. /* Next */
  271. case 'j':
  272. case KEY_DOWN:
  273. case CONTROL('N'):
  274. if (*cur < max - 1)
  275. (*cur)++;
  276. break;
  277. /* Previous */
  278. case 'k':
  279. case KEY_UP:
  280. case CONTROL('P'):
  281. if (*cur > 0)
  282. (*cur)--;
  283. break;
  284. /* Page down */
  285. case KEY_NPAGE:
  286. case CONTROL('D'):
  287. if (*cur < max -1)
  288. (*cur) += MIN((LINES - 4) / 2, max - 1 - *cur);
  289. break;
  290. /* Page up */
  291. case KEY_PPAGE:
  292. case CONTROL('U'):
  293. if (*cur > 0)
  294. (*cur) -= MIN((LINES - 4) / 2, *cur);
  295. break;
  296. case '!':
  297. return SEL_SH;
  298. case 'c':
  299. return SEL_CD;
  300. }
  301. return 0;
  302. }
  303. char *
  304. readln(void)
  305. {
  306. int c;
  307. int i = 0;
  308. char *ln = NULL;
  309. int y, x, x0;
  310. echo();
  311. curs_set(TRUE);
  312. /* Starting point */
  313. getyx(stdscr, y, x);
  314. x0 = x;
  315. while (c = getch()) {
  316. if (c == KEY_ENTER || c == '\r')
  317. break;
  318. if (c == KEY_BACKSPACE) {
  319. getyx(stdscr, y, x);
  320. if (x >= x0) {
  321. if (i > 0) {
  322. ln = xrealloc(ln, (i - 1) * sizeof(*ln));
  323. i--;
  324. } else {
  325. free(ln);
  326. ln = NULL;
  327. }
  328. move(y, x);
  329. printw("%c", ' ');
  330. move(y, x);
  331. } else {
  332. move(y, x0);
  333. }
  334. continue;
  335. }
  336. ln = xrealloc(ln, (i + 1) * sizeof(*ln));
  337. ln[i] = c;
  338. i++;
  339. }
  340. if (ln != NULL) {
  341. ln = xrealloc(ln, (i + 1) * sizeof(*ln));
  342. ln[i] = '\0';
  343. }
  344. curs_set(FALSE);
  345. noecho();
  346. return ln;
  347. }
  348. int
  349. testopendir(char *path)
  350. {
  351. DIR *dirp;
  352. dirp = opendir(path);
  353. if (dirp == NULL) {
  354. return 0;
  355. } else {
  356. closedir(dirp);
  357. return 1;
  358. }
  359. }
  360. void
  361. printent(struct entry *ent, int active)
  362. {
  363. char *name;
  364. unsigned int maxlen = COLS - strlen(CURSR) - 1;
  365. char cm = 0;
  366. /* Copy name locally */
  367. name = xstrdup(ent->name);
  368. if (S_ISDIR(ent->mode)) {
  369. cm = '/';
  370. maxlen--;
  371. } else if (S_ISLNK(ent->mode)) {
  372. cm = '@';
  373. maxlen--;
  374. } else if (ent->mode & S_IXUSR) {
  375. cm = '*';
  376. maxlen--;
  377. }
  378. /* No text wrapping in entries */
  379. if (strlen(name) > maxlen)
  380. name[maxlen] = '\0';
  381. if (cm == 0)
  382. printw("%s%s\n", active ? CURSR : EMPTY, name);
  383. else
  384. printw("%s%s%c\n", active ? CURSR : EMPTY, name, cm);
  385. free(name);
  386. }
  387. int
  388. dentfill(DIR *dirp, struct entry **dents,
  389. int (*filter)(regex_t *, char *), regex_t *re)
  390. {
  391. struct dirent *dp;
  392. struct stat sb;
  393. int n = 0;
  394. int r;
  395. while ((dp = readdir(dirp)) != NULL) {
  396. /* Skip self and parent */
  397. if (strcmp(dp->d_name, ".") == 0
  398. || strcmp(dp->d_name, "..") == 0)
  399. continue;
  400. if (filter(re, dp->d_name) == 0)
  401. continue;
  402. *dents = xrealloc(*dents, (n + 1) * sizeof(**dents));
  403. (*dents)[n].name = xstrdup(dp->d_name);
  404. /* Get mode flags */
  405. r = fstatat(dirfd(dirp), dp->d_name, &sb,
  406. AT_SYMLINK_NOFOLLOW);
  407. if (r == -1)
  408. printerr(1, "stat");
  409. (*dents)[n].mode = sb.st_mode;
  410. n++;
  411. }
  412. return n;
  413. }
  414. void
  415. dentfree(struct entry *dents, int n)
  416. {
  417. int i;
  418. for (i = 0; i < n; i++)
  419. free(dents[i].name);
  420. free(dents);
  421. }
  422. void
  423. browse(const char *ipath, const char *ifilter)
  424. {
  425. DIR *dirp;
  426. struct entry *dents;
  427. int i, n, cur;
  428. int r, ret;
  429. char *path = xrealpath(ipath);
  430. char *filter = xstrdup(ifilter);
  431. regex_t filter_re;
  432. char *cwd;
  433. struct stat sb;
  434. cur = 0;
  435. begin:
  436. dirp = opendir(path);
  437. if (dirp == NULL) {
  438. printwarn();
  439. goto nochange;
  440. } else {
  441. if (chdir(path) == -1)
  442. printwarn();
  443. }
  444. /* Search filter */
  445. r = setfilter(&filter_re, filter);
  446. if (r != 0)
  447. goto nochange;
  448. dents = NULL;
  449. n = dentfill(dirp, &dents, visible, &filter_re);
  450. /* Make sure cur is in range */
  451. cur = MIN(cur, n - 1);
  452. qsort(dents, n, sizeof(*dents), entrycmp);
  453. for (;;) {
  454. int nlines;
  455. int odd;
  456. char *pathnew;
  457. char *name;
  458. char *bin;
  459. char *dir;
  460. char *tmp;
  461. regex_t re;
  462. struct history *hist;
  463. redraw:
  464. nlines = MIN(LINES - 4, n);
  465. /* Clean screen */
  466. erase();
  467. DPRINTF_D(cur);
  468. DPRINTF_S(path);
  469. /* No text wrapping in cwd line */
  470. cwd = xmalloc(COLS * sizeof(char));
  471. strlcpy(cwd, path, COLS * sizeof(char));
  472. cwd[COLS - strlen(CWD) - 1] = '\0';
  473. printw(CWD "%s\n\n", cwd);
  474. /* Print listing */
  475. odd = ISODD(nlines);
  476. if (cur < nlines / 2) {
  477. for (i = 0; i < nlines; i++)
  478. printent(&dents[i], i == cur);
  479. } else if (cur >= n - nlines / 2) {
  480. for (i = n - nlines; i < n; i++)
  481. printent(&dents[i], i == cur);
  482. } else {
  483. for (i = cur - nlines / 2;
  484. i < cur + nlines / 2 + odd; i++)
  485. printent(&dents[i], i == cur);
  486. }
  487. nochange:
  488. ret = nextsel(&cur, n);
  489. switch (ret) {
  490. case SEL_QUIT:
  491. free(path);
  492. free(filter);
  493. /* Forget history */
  494. while (!SLIST_EMPTY(&histhead)) {
  495. hist = SLIST_FIRST(&histhead);
  496. SLIST_REMOVE_HEAD(&histhead, entry);
  497. free(hist);
  498. }
  499. return;
  500. case SEL_BACK:
  501. /* There is no going back */
  502. if (strcmp(path, "/") == 0) {
  503. goto nochange;
  504. } else {
  505. dir = xdirname(path);
  506. free(path);
  507. path = dir;
  508. free(filter);
  509. filter = xstrdup(ifilter); /* Reset filter */
  510. /* Recall history */
  511. hist = SLIST_FIRST(&histhead);
  512. if (hist != NULL) {
  513. cur = hist->pos;
  514. DPRINTF_D(hist->pos);
  515. SLIST_REMOVE_HEAD(&histhead, entry);
  516. free(hist);
  517. } else {
  518. cur = 0;
  519. }
  520. goto out;
  521. }
  522. case SEL_GOIN:
  523. /* Cannot descend in empty directories */
  524. if (n == 0)
  525. goto nochange;
  526. name = dents[cur].name;
  527. /* Handle root case */
  528. if (strcmp(path, "/") == 0)
  529. asprintf(&pathnew, "/%s", name);
  530. else
  531. asprintf(&pathnew, "%s/%s", path, name);
  532. DPRINTF_S(name);
  533. DPRINTF_S(pathnew);
  534. /* Get path info */
  535. r = stat(pathnew, &sb);
  536. if (r == -1) {
  537. printwarn();
  538. free(pathnew);
  539. goto nochange;
  540. }
  541. DPRINTF_U(sb.st_mode);
  542. /* Directory */
  543. if (S_ISDIR(sb.st_mode)) {
  544. free(path);
  545. path = pathnew;
  546. free(filter);
  547. filter = xstrdup(ifilter); /* Reset filter */
  548. /* Save history */
  549. hist = xmalloc(sizeof(struct history));
  550. hist->pos = cur;
  551. SLIST_INSERT_HEAD(&histhead, hist, entry);
  552. cur = 0;
  553. goto out;
  554. }
  555. /* Regular file */
  556. if (S_ISREG(sb.st_mode)) {
  557. /* Open with */
  558. bin = openwith(name);
  559. if (bin == NULL) {
  560. printmsg("No association");
  561. free(pathnew);
  562. goto nochange;
  563. }
  564. exitcurses();
  565. spawn(bin, pathnew);
  566. initcurses();
  567. free(pathnew);
  568. goto redraw;
  569. }
  570. /* All the rest */
  571. printmsg("Unsupported file");
  572. free(pathnew);
  573. goto nochange;
  574. case SEL_FLTR:
  575. /* Read filter */
  576. printprompt("filter: ");
  577. tmp = readln();
  578. if (tmp == NULL) {
  579. clearprompt();
  580. goto nochange;
  581. }
  582. r = setfilter(&re, tmp);
  583. if (r != 0) {
  584. free(tmp);
  585. goto nochange;
  586. }
  587. free(filter);
  588. filter = tmp;
  589. filter_re = re;
  590. DPRINTF_S(filter);
  591. cur = 0;
  592. goto out;
  593. case SEL_SH:
  594. exitcurses();
  595. spawn("/bin/sh", NULL);
  596. initcurses();
  597. break;
  598. case SEL_CD:
  599. /* Read target dir */
  600. printprompt("chdir: ");
  601. tmp = readln();
  602. if (tmp == NULL) {
  603. clearprompt();
  604. goto nochange;
  605. }
  606. if (testopendir(tmp) == 0) {
  607. printwarn();
  608. goto nochange;
  609. } else {
  610. free(path);
  611. path = xrealpath(tmp);
  612. free(tmp);
  613. free(filter);
  614. filter = xstrdup(ifilter); /* Reset filter */
  615. /* Forget history */
  616. while (!SLIST_EMPTY(&histhead)) {
  617. hist = SLIST_FIRST(&histhead);
  618. SLIST_REMOVE_HEAD(&histhead, entry);
  619. free(hist);
  620. }
  621. DPRINTF_S(path);
  622. cur = 0;
  623. goto out;
  624. }
  625. }
  626. }
  627. out:
  628. dentfree(dents, n);
  629. /* Should never be null */
  630. r = closedir(dirp);
  631. if (r == -1)
  632. printerr(1, "closedir");
  633. goto begin;
  634. }
  635. int
  636. main(int argc, char *argv[])
  637. {
  638. char cwd[PATH_MAX], *ipath;
  639. char *ifilter;
  640. if (getuid() == 0)
  641. ifilter = ".*";
  642. else
  643. ifilter = "^[^.].*"; /* Hide dotfiles */
  644. if (argv[1] != NULL) {
  645. ipath = argv[1];
  646. } else {
  647. ipath = getcwd(cwd, sizeof(cwd));
  648. if (ipath == NULL)
  649. ipath = "/";
  650. }
  651. /* Test initial path */
  652. if (!testopendir(ipath))
  653. printerr(1, ipath);
  654. /* Set locale before curses setup */
  655. setlocale(LC_ALL, "");
  656. initcurses();
  657. browse(ipath, ifilter);
  658. exitcurses();
  659. return 0;
  660. }