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

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