]> git.neil.brown.name Git - edlib.git/blob - doc-dir.c
749d1c34a261ee874d1937181f661bb37972b68c
[edlib.git] / doc-dir.c
1 /*
2  * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * directory listing as a document.
6  *
7  * The 'text' of the document is a single '\n' char per director entry:
8  *
9  * Each char has a set of attributes which give details
10  * name   file name
11  * size
12  * mtime
13  * atime
14  * ctime
15  * owner
16  * group
17  * modes
18  * nlinks
19  * type:
20  *  .  current directory
21  *  : parent directory
22  *  d  other directory
23  *  f  regular file
24  *  l  link
25  *  c  char-special
26  *  b  block-special
27  *  p  named-pipe
28  *  s  socket
29  *
30  */
31
32 #define _GNU_SOURCE /*  for asprintf */
33 #include <unistd.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <locale.h>
37 #include <sys/stat.h>
38 #include <sys/fcntl.h>
39 #include <dirent.h>
40 #include <stdio.h>
41 #include <pwd.h>
42 #include <grp.h>
43 #include <time.h>
44
45 #define PRIVATE_DOC_REF
46 struct doc_ref {
47         struct dir_ent  *d;
48         unsigned int    ignore;
49 };
50 #define DOC_SHARESREF
51 #define DOC_DATA_TYPE struct directory
52 #define DOC_NEXT(d,m,r,b) dir_next(d,r,b)
53 #define DOC_PREV(d,m,r,b) dir_prev(d,r,b)
54 #include "core.h"
55
56 struct dir_ent {
57         char                    *name safe;
58         wchar_t                 ch;
59         struct list_head        lst;
60         struct attrset          *attrs;
61         struct stat             st;
62         char                    nbuf[30];
63 };
64
65 struct directory {
66         struct doc              doc;
67         struct list_head        ents;
68
69         struct stat             stat;
70         char                    *fname;
71 };
72 #include "core-pane.h"
73
74 static void get_stat(struct directory *dr safe, struct dir_ent *de safe);
75
76 static char *key(struct list_head *le, const void *data)
77 {
78         if (le == NULL)
79                 return NULL;
80         return list_entry(le, struct dir_ent, lst)->name;
81 }
82
83 static bool add_ent(struct list_head *lst safe, struct dirent *de safe)
84 {
85         struct dir_ent *dre;
86
87         if (de->d_ino == 0)
88                 return False;
89
90         dre = malloc(sizeof(*dre));
91         dre->name = strdup(de->d_name);
92         dre->attrs = NULL;
93         dre->st.st_mode = 0;
94         if (strcmp(de->d_name, ".") == 0)
95                 dre->ch = '.';
96         else if (strcmp(de->d_name, "..") == 0)
97                 dre->ch = ':';
98         else switch (de->d_type) {
99                 case DT_BLK: dre->ch = 'b'; break;
100                 case DT_CHR: dre->ch = 'c'; break;
101                 case DT_DIR: dre->ch = 'd'; break;
102                 case DT_FIFO:dre->ch = 'p'; break;
103                 case DT_LNK: dre->ch = 'l'; break;
104                 case DT_REG: dre->ch = 'f'; break;
105                 case DT_SOCK:dre->ch = 's'; break;
106                 default:
107                 case DT_UNKNOWN:dre->ch = '?'; break;
108                 }
109         list_add(&dre->lst, lst);
110         return True;
111 }
112
113 static void load_dir(struct list_head *lst safe, int fd)
114 {
115         DIR *dir;
116         struct dirent *res;
117
118         dir = fdopendir(dup(fd));
119         if (!dir)
120                 return;
121         while ((res = readdir(dir)) != NULL)
122                 add_ent(lst, res);
123         sort_list(lst, key, NULL);
124         closedir(dir);
125 }
126
127 static struct map *dir_map;
128 DEF_LOOKUP_CMD(dir_handle, dir_map);
129
130 DEF_CMD(dir_new)
131 {
132         struct directory *dr;
133         struct pane *p;
134
135         p = doc_register(ci->home, &dir_handle.c);
136         if (!p)
137                 return Efail;
138         dr = p->doc_data;
139         INIT_LIST_HEAD(&dr->ents);
140         dr->fname = NULL;
141
142         return comm_call(ci->comm2, "callback:doc", p);
143 }
144
145 DEF_CMD(dir_new2)
146 {
147         if (ci->num2 != S_IFDIR)
148                 return Efallthrough;
149         return dir_new_func(ci);
150 }
151
152 DEF_CMD(dir_load_file)
153 {
154         int fd = ci->num2;
155         const char *name = ci->str;
156         struct directory *dr = ci->home->doc_data;
157         struct list_head new;
158         struct dir_ent *de1, *de2;
159         struct mark *prev, *m;
160         int doclose = 0;
161
162         prev = NULL;
163         m = mark_new(ci->home);
164         if (!m)
165                 return Efail;
166         if (fd < 0) {
167                 if (!dr->fname)
168                         return Efail;
169                 fd = open(dr->fname, O_RDONLY|O_DIRECTORY);
170                 if (fd < 0)
171                         return Efail;
172                 doclose = 1;
173         }
174
175         INIT_LIST_HEAD(&new);
176         load_dir(&new, fd);
177         de2 = list_first_entry_or_null(&new, struct dir_ent, lst);
178         while (m->ref.d || de2) {
179                 de1 = m->ref.d;
180                 if (de1 &&
181                     (de2 == NULL || strcmp(de1->name, de2->name) < 0)) {
182                         /* de1 doesn't exist in new: need to delete it. */
183                         struct mark *m2;
184                         struct dir_ent *de = de1;
185                         if (de1 == list_last_entry(&dr->ents,
186                                                    struct dir_ent, lst))
187                                 de1 = NULL;
188                         else
189                                 de1 = list_next_entry(de1, lst);
190                         for (m2 = m; m2 && m2->ref.d == de;
191                              m2 = mark_next(m2))
192                                 m2->ref.d = de1;
193                         attr_free(&de->attrs);
194                         free(de->name);
195                         list_del(&de->lst);
196                         free(de);
197                         if (!prev) {
198                                 prev = mark_dup(m);
199                                 doc_prev(ci->home, prev);
200                         }
201                 } else if (de2 &&
202                            (de1 == NULL || strcmp(de2->name, de1->name) < 0)) {
203                         /* de2 doesn't already exist, so add it before de1 */
204                         list_del(&de2->lst);
205                         if (de1)
206                                 list_add_tail(&de2->lst, &de1->lst);
207                         else
208                                 list_add_tail(&de2->lst, &dr->ents);
209                         if (!prev) {
210                                 prev = mark_dup(m);
211                                 doc_prev(ci->home, prev);
212                         }
213                 } else if (de1 && de2) {
214                         /* de1 and de2 are the same.  Just step over de1 and
215                          * delete de2
216                          */
217                         bool changed = False;
218                         if (de1->st.st_mode) {
219                                 /* Need to check if stat info has changed */
220                                 get_stat(dr, de1);
221                                 if (de1->st.st_mode != de2->st.st_mode ||
222                                     de1->st.st_size != de2->st.st_size ||
223                                     de1->st.st_mtime != de2->st.st_mtime ||
224                                     de1->st.st_ctime != de2->st.st_ctime) {
225                                         changed = True;
226                                         de1->st = de2->st;
227                                 }
228                         }
229                         if (changed) {
230                                 if (!prev)
231                                         prev = mark_dup(m);
232                         } else if (prev) {
233                                 pane_notify("doc:replaced", ci->home,
234                                             0, prev, NULL,
235                                             0, m);
236                                 mark_free(prev);
237                                 prev = NULL;
238                         }
239                         doc_next(ci->home, m);
240                         mark_step_sharesref(m,0);
241
242                         list_del(&de2->lst);
243                         attr_free(&de2->attrs);
244                         free(de2->name);
245                         free(de2);
246                 }
247                 de2 = list_first_entry_or_null(&new, struct dir_ent, lst);
248         }
249         if (prev) {
250                 pane_notify("doc:replaced", ci->home, 0, prev, NULL,
251                             0, m);
252                 mark_free(prev);
253         }
254
255         if (name) {
256                 const char *dname;
257                 int l = strlen(name);
258
259                 fstat(fd, &dr->stat);
260                 dr->fname = malloc(l+2);
261                 strcpy(dr->fname, name);
262                 if (l > 1 && dr->fname[l-1] == '/')
263                         dr->fname[l-1] = '\0';
264                 dname = strrchr(dr->fname, '/');
265                 if (dname && dname[1])
266                         dname += 1;
267                 else
268                         dname = name;
269                 call("doc:set-name", ci->home, 0, NULL, dname);
270                 if (l > 1)
271                         strcat(dr->fname, "/");
272         }
273         if (doclose)
274                 close(fd);
275         return 1;
276 }
277
278 DEF_CMD(dir_revisited)
279 {
280         struct directory *dr = ci->home->doc_data;
281         struct stat st;
282
283         if (ci->num <= 0)
284                 /* Being buried, not visited */
285                 return Efallthrough;
286
287         if (stat(dr->fname, &st) == 0 &&
288             (st.st_ino != dr->stat.st_ino ||
289              st.st_dev != dr->stat.st_dev ||
290              st.st_mtime != dr->stat.st_mtime ||
291              st.st_mtim.tv_nsec != dr->stat.st_mtim.tv_nsec)) {
292                 char *msg = NULL;
293                 call("doc:load-file", ci->home, 2, NULL, NULL, -1);
294                 asprintf(&msg, "Directory %s reloaded", dr->fname);
295                 call("Message", ci->focus, 0, NULL, msg);
296                 free(msg);
297         }
298         return Efallthrough;
299 }
300
301 DEF_CMD(dir_same_file)
302 {
303         int fd = ci->num2;
304         struct stat stb;
305         struct directory *dr = ci->home->doc_data;
306
307         if (!dr->fname)
308                 return 0;
309         if (fstat(fd, &stb) != 0)
310                 return 0;
311         if (! (dr->stat.st_ino == stb.st_ino &&
312                dr->stat.st_dev == stb.st_dev))
313                 return 0;
314         /* Let's reload it now */
315         home_call(ci->home, "doc:load-file", ci->focus, 0, NULL, NULL, fd);
316         return 1;
317 }
318
319 static inline wint_t dir_next(struct pane *p safe, struct doc_ref *r safe, bool bytes)
320 {
321         struct directory *dr = p->doc_data;
322         struct dir_ent *d = r->d;
323
324         if (d == NULL)
325                 return WEOF;
326         else {
327                 if (d == list_last_entry(&dr->ents,
328                                          struct dir_ent, lst))
329                         d = NULL;
330                 else
331                         d = list_next_entry(d, lst);
332         }
333         r->d = d;
334         return '\n';
335 }
336
337 static inline wint_t dir_prev(struct pane *p safe, struct doc_ref *r safe, bool bytes)
338 {
339         struct directory *dr = p->doc_data;
340         struct dir_ent *d = r->d;
341
342         if (d == list_first_entry(&dr->ents, struct dir_ent, lst))
343                 d = NULL;
344         else if (d == NULL)
345                 d = list_last_entry(&dr->ents, struct dir_ent, lst);
346         else
347                 d = list_prev_entry(d, lst);
348         if (!d)
349                 return WEOF;
350
351         r->d = d;
352         return '\n';
353 }
354
355 DEF_CMD(dir_char)
356 {
357         return do_char_byte(ci);
358 }
359
360 DEF_CMD(dir_set_ref)
361 {
362         struct directory *dr = ci->home->doc_data;
363         struct mark *m = ci->mark;
364
365         if (!m)
366                 return Enoarg;
367
368         mark_to_end(ci->home, m, ci->num != 1);
369         if (list_empty(&dr->ents) || ci->num != 1)
370                 m->ref.d = NULL;
371         else
372                 m->ref.d = list_first_entry(&dr->ents, struct dir_ent, lst);
373         m->ref.ignore = 0;
374         return 1;
375 }
376
377 static void get_stat(struct directory *dr safe, struct dir_ent *de safe)
378 {
379         int dfd;
380         struct stat st;
381         if (de->st.st_mode)
382                 return;
383         dfd = open(dr->fname, O_RDONLY);
384         if (!dfd)
385                 return;
386         if (fstatat(dfd, de->name, &de->st, AT_SYMLINK_NOFOLLOW) != 0) {
387                 de->st.st_mode = 0xffff;
388                 de->ch = '?';
389         } else if ((de->st.st_mode & S_IFMT) == S_IFLNK &&
390                    fstatat(dfd, de->name, &st, 0) == 0 &&
391                    (st.st_mode & S_IFMT) == S_IFDIR)
392                 de->ch = 'L';
393         close(dfd);
394 }
395
396 static char *fmt_num(struct dir_ent *de safe, long num)
397 {
398         sprintf(de->nbuf, "%ld", num);
399         return de->nbuf;
400 }
401
402 static char *save_str(struct dir_ent *de safe, char *str safe)
403 {
404         strncpy(de->nbuf, str, sizeof(de->nbuf));
405         de->nbuf[sizeof(de->nbuf)-1] = 0;
406         return de->nbuf;
407 }
408
409 static char *fmt_date(struct dir_ent *de safe, time_t t, struct pane *p safe)
410 {
411         struct tm tm;
412         time_t now = time(NULL);
413
414         if (edlib_testing(p)) {
415                 t = 1581382278;
416                 now = t;
417         }
418         localtime_r(&t, &tm);
419         if (t > now || t < now - 10*30*24*3600)
420                 strftime(de->nbuf, sizeof(de->nbuf),
421                          "%b %d  %Y", &tm);
422         else
423                 strftime(de->nbuf, sizeof(de->nbuf),
424                          "%b %d %H:%M", &tm);
425         return de->nbuf;
426 }
427
428 static char *fmt_size(struct dir_ent *de safe, loff_t size)
429 {
430         if (size < 1024)
431                 snprintf(de->nbuf, sizeof(de->nbuf),
432                         "%ld", size);
433         else if (size < 1024*10)
434                 snprintf(de->nbuf, sizeof(de->nbuf),
435                         "%ld.%02ldK", size/1024, (size%1023)*100 / 1024);
436         else if (size < 1024*1024)
437                 snprintf(de->nbuf, sizeof(de->nbuf),
438                         "%ldK", size/1024);
439         else if (size < 1024L*1024*10)
440                 snprintf(de->nbuf, sizeof(de->nbuf),
441                         "%ld.%02ldM", size/1024/1024, ((size/1024)%1023)*100 / 1024);
442         else if (size < 1024L*1024*1024)
443                 snprintf(de->nbuf, sizeof(de->nbuf),
444                         "%ldM", size/1024/1024);
445         else if (size < 1024L*1024*1024*10)
446                 snprintf(de->nbuf, sizeof(de->nbuf),
447                         "%ld.%02ldG", size/1024/1024/1024, ((size/1024/1024)%1023)*100 / 1024);
448         else
449                 snprintf(de->nbuf, sizeof(de->nbuf),
450                         "%ldG", size/1024/1024/1024);
451         return de->nbuf;
452 }
453
454 static char *pwname(int uid)
455 {
456         static int last_uid = -1;
457         static char *last_name = NULL;
458         struct passwd *pw;
459
460         if (uid != last_uid) {
461                 free(last_name);
462                 pw = getpwuid(uid);
463                 if (pw && pw->pw_name)
464                         last_name = strdup(pw->pw_name);
465                 else
466                         last_name = NULL;
467                 last_uid = uid;
468         }
469         return last_name;
470 }
471
472 static char *grname(int gid)
473 {
474         static int last_gid = -1;
475         static char *last_name = NULL;
476         struct group *gr;
477
478         if (gid != last_gid) {
479                 free(last_name);
480                 gr = getgrgid(gid);
481                 if (gr && gr->gr_name)
482                         last_name = strdup(gr->gr_name);
483                 else
484                         last_name = NULL;
485                 last_gid = gid;
486         }
487         return last_name;
488 }
489
490 static const char *_dir_get_attr(struct pane *home safe, struct mark *m safe,
491                                   const char *attr safe)
492
493 {
494         struct directory *dr = home->doc_data;
495         struct dir_ent *de;
496
497         de = m->ref.d;
498         if (!de)
499                 return NULL;
500         if (strcmp(attr, "name") == 0) {
501                 return de->name;
502         } else if (strcmp(attr, "type") == 0) {
503                 de->nbuf[0] = de->ch;
504                 de->nbuf[1] = 0;
505                 return de->nbuf;
506         } else if (strcmp(attr, "size") == 0) {
507                 get_stat(dr, de);
508                 return fmt_num(de, de->st.st_size);
509         } else if (strcmp(attr, "hsize") == 0) {
510                 get_stat(dr, de);
511                 if (strchr(".:d", de->ch) &&
512                     edlib_testing(home))
513                         /* Size might not be reliable for testing */
514                         return "DIR";
515                 return fmt_size(de, de->st.st_size);
516         } else if (strcmp(attr, "mtime") == 0) {
517                 get_stat(dr, de);
518                 return fmt_num(de, de->st.st_mtime);
519         } else if (strcmp(attr, "mdate") == 0) {
520                 get_stat(dr, de);
521                 return fmt_date(de, de->st.st_mtime, home);
522         } else if (strcmp(attr, "atime") == 0) {
523                 get_stat(dr, de);
524                 return fmt_num(de, de->st.st_atime);
525         } else if (strcmp(attr, "adate") == 0) {
526                 get_stat(dr, de);
527                 return fmt_date(de, de->st.st_atime, home);
528         } else if (strcmp(attr, "ctime") == 0) {
529                 get_stat(dr, de);
530                 return fmt_num(de, de->st.st_ctime);
531         } else if (strcmp(attr, "cdate") == 0) {
532                 get_stat(dr, de);
533                 return fmt_date(de, de->st.st_ctime, home);
534         } else if (strcmp(attr, "uid") == 0) {
535                 get_stat(dr, de);
536                 return fmt_num(de, de->st.st_uid);
537         } else if (strcmp(attr, "gid") == 0) {
538                 get_stat(dr, de);
539                 return fmt_num(de, de->st.st_gid);
540         } else if (strcmp(attr, "user") == 0) {
541                 char *n;
542                 get_stat(dr, de);
543                 if (edlib_testing(home))
544                         return "User";
545                 n = pwname(de->st.st_uid);
546                 if (n)
547                         return save_str(de, n);
548                 else
549                         return fmt_num(de, de->st.st_uid);
550         } else if (strcmp(attr, "group") == 0) {
551                 char *n;
552                 get_stat(dr, de);
553                 if (edlib_testing(home))
554                         return "Group";
555                 n = grname(de->st.st_gid);
556                 if (n)
557                         return save_str(de, n);
558                 else
559                         return fmt_num(de, de->st.st_gid);
560         } else if (strcmp(attr, "mode") == 0) {
561                 get_stat(dr, de);
562                 return fmt_num(de, de->st.st_mode & 0777);
563         } else if (strcmp(attr, "perms") == 0) {
564                 char *c;
565                 int mode;
566                 int i;
567                 get_stat(dr, de);
568                 c = de->nbuf;
569                 mode = de->st.st_mode;
570                 switch (mode & S_IFMT) {
571                 case S_IFREG: *c ++ = '-'; break;
572                 case S_IFDIR: *c ++ = 'd'; break;
573                 case S_IFBLK: *c ++ = 'b'; break;
574                 case S_IFCHR: *c ++ = 'c'; break;
575                 case S_IFSOCK:*c ++ = 's'; break;
576                 case S_IFLNK: *c ++ = 'l'; break;
577                 default:      *c ++ = '?'; break;
578                 }
579                 if (edlib_testing(home) && de->ch == ':')
580                         /* ".." might not be under control of the test */
581                         mode = 0777;
582                 for (i = 0; i < 3; i++) {
583                         *c ++ = (mode & 0400) ? 'r':'-';
584                         *c ++ = (mode & 0200) ? 'w':'-';
585                         *c ++ = (mode & 0100) ? 'x':'-';
586                         mode = mode << 3;
587                 }
588                 *c = 0;
589                 return de->nbuf;
590         } else if (strcmp(attr, "suffix") == 0) {
591                 if (de->ch == 'l')
592                         get_stat(dr, de);
593                 if (strchr(".:dL", de->ch))
594                         return "/";
595                 return "";
596         } else if (strcmp(attr, "arrow") == 0) {
597                 if (strchr("lL", de->ch))
598                         return " -> ";
599                 else
600                         return "";
601         } else if (strcmp(attr, "target") == 0) {
602                 int dfd;
603                 char buf[PATH_MAX];
604                 int len;
605
606                 if (strchr("lL", de->ch) == NULL)
607                         return "";
608                 dfd = open(dr->fname, O_RDONLY);
609                 if (dfd < 0)
610                         return "";
611                 len = readlinkat(dfd, de->name, buf, sizeof(buf));
612                 close(dfd);
613                 if (len <= 0 || len >= (int)sizeof(buf))
614                         return "";
615                 buf[len] = 0;
616                 return strsave(home, buf);
617         } else
618                 return attr_find(de->attrs, attr);
619 }
620
621 DEF_CMD(dir_doc_get_attr)
622 {
623         struct mark *m = ci->mark;
624         const char *attr = ci->str;
625         const char *val;
626
627         if (!m || !attr)
628                 return Enoarg;
629         val = _dir_get_attr(ci->home, m, attr);
630
631         if (!val)
632                 return Efallthrough;
633         comm_call(ci->comm2, "callback:get_attr", ci->focus, 0, m, val,
634                   0, NULL, attr);
635         return 1;
636 }
637
638 DEF_CMD(dir_doc_set_attr)
639 {
640         struct mark *m = ci->mark;
641         const char *attr = ci->str;
642         const char *val = ci->str2;
643
644         if (!m || !attr)
645                 return Enoarg;
646         if (!m->ref.d)
647                 return Einval;
648         attr_set_str(&m->ref.d->attrs, attr, val);
649         pane_notify("doc:replaced-attr", ci->home, 1, ci->mark);
650         return 1;
651 }
652
653 DEF_CMD(dir_get_attr)
654 {
655         struct directory *dr = ci->home->doc_data;
656         const char *attr = ci->str;
657         const char *val;
658
659         if (!attr)
660                 return Enoarg;
661
662         if ((val = attr_find(ci->home->attrs, attr)) != NULL)
663                 ;
664         else if (strcmp(attr, "heading") == 0)
665                 val = "File Name";
666         else if (strcmp(attr, "render-default") == 0)
667                 val = "format";
668         else if (strcmp(attr, "render-simple") == 0)
669                 val = "format";
670         else if (strcmp(attr, "view-default") == 0)
671                 val = "dirview";
672         else if (strcmp(attr, "doc-type") == 0)
673                 val = "dir";
674         else if (strcmp(attr, "line-format") == 0)
675                 val = "%name";
676         else if (strcmp(attr, "filename") == 0)
677                 val = dr->fname;
678         else
679                 return Efallthrough;
680         comm_call(ci->comm2, "callback:get_attr", ci->focus, 0, NULL, val);
681         return 1;
682 }
683
684 DEF_CMD(dir_val_marks)
685 {
686         struct directory *dr = ci->home->doc_data;
687         struct dir_ent *de;
688         int found;
689
690         if (!ci->mark || !ci->mark2)
691                 return Enoarg;
692
693         if (ci->mark->ref.d == ci->mark2->ref.d) {
694                 if (ci->mark->ref.ignore < ci->mark2->ref.ignore)
695                         return 1;
696                 LOG("dir_val_marks: same buf, bad offset: %u, %u",
697                     ci->mark->ref.ignore, ci->mark2->ref.ignore);
698                 return Efalse;
699         }
700         if (ci->mark->ref.d == NULL) {
701                 LOG("dir_val_marks: mark.d is NULL");
702                 return Efalse;
703         }
704         found = 0;
705         list_for_each_entry(de, &dr->ents, lst) {
706                 if (ci->mark->ref.d == de)
707                         found = 1;
708                 if (ci->mark2->ref.d == de) {
709                         if (found == 1)
710                                 return 1;
711                         LOG("dir_val_marks: mark2.d found before mark1");
712                         return Efalse;
713                 }
714         }
715         if (ci->mark2->ref.d == NULL) {
716                 if (found == 1)
717                         return 1;
718                 LOG("dir_val_marks: mark2.d (NULL) found before mark1");
719                 return Efalse;
720         }
721         if (found == 0)
722                 LOG("dir_val_marks: Neither mark found in de list");
723         if (found == 1)
724                 LOG("dir_val_marks: mark2 not found in de list");
725         return Efalse;
726 }
727
728 DEF_CMD(dir_destroy)
729 {
730         struct directory *dr = ci->home->doc_data;
731
732         while (!list_empty(&dr->ents)) {
733                 struct dir_ent *de = list_entry(dr->ents.next,
734                                                 struct dir_ent, lst);
735
736                 attr_free(&de->attrs);
737                 free(de->name);
738                 list_del(&de->lst);
739                 free(de);
740         }
741         return Efallthrough;
742 }
743
744 DEF_CMD(dir_shares_ref)
745 {
746         return 1;
747 }
748
749 DEF_CMD(dir_debug_mark)
750 {
751         char *ret = NULL;
752         struct mark *m = ci->mark;
753         struct dir_ent *de;
754
755         if (!m || m->owner != ci->home || !ci->comm2)
756                 return Enoarg;
757         de = m->ref.d;
758         if (!mark_valid(m))
759                 ret = strdup("M:FREED");
760         else if (!de)
761                 ret = strdup("M:EOF");
762         else
763                 asprintf(&ret, "M:%s(#%x)", de->name, m->ref.ignore);
764         comm_call(ci->comm2, "cb", ci->focus, 0, NULL, ret);
765         free(ret);
766         return 1;
767 }
768
769 static struct map *dirview_map;
770 DEF_LOOKUP_CMD(dirview_handle, dirview_map);
771
772 static int dir_open(struct pane *focus safe,
773                     struct mark *m, bool other, bool follow)
774 {
775         /* close this pane, open the given file. */
776
777         struct pane *par, *p;
778         int fd;
779         char *dirname, *basename, *type;
780         char *fname = NULL;
781
782         if (!m)
783                 m = call_ret(mark, "doc:point", focus);
784         if (!m)
785                 return Enoarg;
786         dirname = pane_attr_get(focus, "filename");
787         basename = pane_mark_attr(focus, m, "name");
788         type = pane_mark_attr(focus, m, "type");
789         if (!dirname || !basename || !type)
790                 return Efail;
791
792         asprintf(&fname, "%s/%s", dirname, basename);
793         if (!fname)
794                 return Efail;
795
796         if (follow && (type[0] == 'l' || type[0] == 'L')) {
797                 /* Fname is a symlink.  Read it and open
798                  * that directly.  Only follow this step once.
799                  */
800                 char path[PATH_MAX];
801                 int ret;
802
803                 ret = readlink(fname, path, sizeof(path));
804                 if (ret > 0 && ret < (int)sizeof(path)) {
805                         path[ret] = 0;
806                         if (fname[0] == '/')
807                                 asprintf(&fname, "%s", path);
808                         else
809                                 asprintf(&fname, "%s/%s", dirname, path);
810                         if (!fname)
811                                 return Efail;
812                 }
813         }
814         fd = open(fname, O_RDONLY);
815         if (fd >= 0) {
816                 p = call_ret(pane, "doc:open", focus, fd, NULL, fname);
817                 close(fd);
818         } else
819                 p = call_ret(pane, "doc:from-text", focus, 0, NULL, fname,
820                              0, NULL, "File not found\n");
821         free(fname);
822         if (!p)
823                 return Efail;
824         if (other) {
825                 par = home_call_ret(pane, focus, "DocPane", p);
826                 if (par) {
827                         pane_take_focus(par);
828                         return 1;
829                 }
830                 par = call_ret(pane, "OtherPane", focus);
831         } else
832                 par = call_ret(pane, "ThisPane", focus);
833         if (par) {
834                 p = home_call_ret(pane, p, "doc:attach-view", par);
835                 pane_take_focus(p);
836         }
837         return 1;
838 }
839
840 static int dir_open_alt(struct pane *focus safe,
841                         struct mark *m, char cmd)
842 {
843         /* close this pane, open the given file. */
844         struct pane *p;
845         int fd;
846         char *dirname, *basename;
847         char *fname = NULL;
848         char buf[100];
849
850         if (!m)
851                 return Enoarg;
852         dirname = pane_attr_get(focus, "filename");
853         basename = pane_mark_attr(focus, m, "name");
854         if (!dirname || !basename)
855                 return Efail;
856
857         asprintf(&fname, "%s/%s", dirname, basename);
858         if (!fname)
859                 return Efail;
860         fd = open(fname, O_RDONLY);
861
862         if (fd >= 0) {
863                 struct pane *new = call_ret(pane, "doc:open", focus,
864                                             fd, NULL, fname);
865                 close(fd);
866                 if (!new)
867                         return Efail;
868                 snprintf(buf, sizeof(buf), "cmd-%c", cmd);
869                 p = call_ret(pane, "ThisPane", focus);
870                 if (!p)
871                         return Efail;
872
873                 p = home_call_ret(pane, new, "doc:attach-view", p,
874                                   1, NULL, buf);
875         } else {
876                 struct pane *doc = call_ret(pane, "doc:from-text", focus,
877                                             0, NULL, fname,
878                                             0, NULL, "File not found\n");
879                 if (!doc)
880                         return Efail;
881                 p = call_ret(pane, "ThisPane", focus);
882                 if (!p)
883                         return Efail;
884                 p = home_call_ret(pane, doc, "doc:attach-view", p, 1);
885         }
886         free(fname);
887         pane_take_focus(p);
888         return 1;
889 }
890
891 DEF_CMD(dir_do_open)
892 {
893         return dir_open(ci->focus, ci->mark, False, ci->num == 1);
894 }
895
896 DEF_CMD(dir_do_open_other)
897 {
898         return dir_open(ci->focus, ci->mark, True, ci->num == 1);
899 }
900
901 DEF_CMD(dir_do_reload)
902 {
903         return call("doc:load-file", ci->focus, 0, NULL, NULL, -1);
904 }
905
906 DEF_CMD(dir_do_mark_del)
907 {
908         call("doc:set-attr", ci->focus, 0, ci->mark, "dir-cmd",
909              0, NULL, "D");
910         call("doc:EOL", ci->focus, 1, ci->mark, NULL, 1);
911         return 1;
912 }
913
914 DEF_CMD(dir_do_mark)
915 {
916         call("doc:set-attr", ci->focus, 0, ci->mark, "dir-cmd",
917              0, NULL, "*");
918         call("doc:EOL", ci->focus, 1, ci->mark, NULL, 1);
919         return 1;
920 }
921
922 DEF_CMD(dir_un_mark)
923 {
924         call("doc:set-attr", ci->focus, 0, ci->mark, "dir-cmd",
925              0, NULL, NULL);
926         call("doc:EOL", ci->focus, 1, ci->mark, NULL, 1);
927         return 1;
928 }
929
930 DEF_CMD(dir_un_mark_back)
931 {
932         call("doc:EOL", ci->focus, -2, ci->mark);
933         call("doc:set-attr", ci->focus, 0, ci->mark, "dir-cmd",
934              0, NULL, NULL);
935         return 1;
936 }
937
938 DEF_CMD(dir_do_quit)
939 {
940         return call("doc:destroy", ci->home);
941 }
942
943 DEF_CMD(dir_do_special)
944 {
945         const char *c = ksuffix(ci, "doc:cmd-");
946
947         return dir_open_alt(ci->focus, ci->mark, c[0]);
948 }
949
950 static void add_name(struct buf *b safe, char *name safe)
951 {
952         if (strchr(name, '\'') == NULL) {
953                 buf_append(b, ' ');
954                 buf_append(b, '\'');
955                 buf_concat(b, name);
956                 buf_append(b, '\'');
957         } else {
958                 buf_append(b, ' ');
959                 buf_append(b, '"');
960                 while (*name) {
961                         if (strchr("\"$`\\", *name))
962                                 buf_append(b, '\\');
963                         buf_append_byte(b, *name);
964                         name += 1;
965                 }
966                 buf_append(b, '"');
967         }
968 }
969
970 static char *collect_names(struct pane *p safe, char *type,
971                            struct mark *mark)
972 {
973         struct mark *m = mark_new(p);
974         struct buf b;
975
976         if (!m)
977                 return NULL;
978         buf_init(&b);
979         while (type && doc_following(p, m) != WEOF) {
980                 char *t, *name;
981                 t = pane_mark_attr(p, m, "dir-cmd");
982                 if (!t  || strcmp(t, type) != 0) {
983                         doc_next(p, m);
984                         continue;
985                 }
986                 name = pane_mark_attr(p, m, "name");
987                 call("doc:set-attr", p, 0, m, "dir-cmd");
988                 doc_next(p, m);
989                 if (!name)
990                         continue;
991                 add_name(&b, name);
992         }
993         mark_free(m);
994         if (!b.len && mark) {
995                 char *name = pane_mark_attr(p, mark, "name");
996                 if (name)
997                         add_name(&b, name);
998         }
999         return buf_final(&b);
1000 }
1001
1002 DEF_CMD(dir_expunge)
1003 {
1004         char *names, *cmd;
1005         struct pane *p;
1006
1007         names = collect_names(ci->focus, "D", NULL);
1008
1009         if (!names || !names[0]) {
1010                 free(names);
1011                 call("Message:modal", ci->focus, 0, NULL,
1012                      "No files marked for deletion");
1013                 return Efail;
1014         }
1015         cmd = strconcat(ci->focus, "rm -f ", names);
1016         p = call_ret(pane, "attach-shell-prompt", ci->focus, 0, NULL, cmd);
1017         if (p) {
1018                 // put cursor after the "-f"
1019                 call("doc:file", p, -1);
1020                 call("doc:char", p, 5);
1021                 pane_add_notify(ci->home, p, "Notify:Close");
1022         }
1023         free(names);
1024         return 1;
1025 }
1026
1027 DEF_CMD(dir_chmodown)
1028 {
1029         char *names, *cmd;
1030         struct pane *p;
1031         char *which;
1032
1033         if (strcmp(ci->key, "K:A-o") == 0)
1034                 which = "chown";
1035         else
1036                 which = "chmod";
1037
1038         names = collect_names(ci->focus, "*", ci->mark);
1039         if (!names || !*names) {
1040                 free(names);
1041                 call("Message:modal", ci->focus, 0, NULL,
1042                      strconcat(ci->focus, "No file for ", which));
1043                 return Efail;
1044         }
1045         cmd = strconcat(ci->focus, which, " ", names);
1046         p = call_ret(pane, "attach-shell-prompt", ci->focus, 0, NULL, cmd);
1047         if (p) {
1048                 call("doc:file", p, -1);
1049                 call("doc:char", p, strlen(which) + 1);
1050                 pane_add_notify(ci->home, p, "Notify:Close");
1051         }
1052         free(names);
1053         return 1;
1054 }
1055
1056 DEF_CMD(dir_rename)
1057 {
1058         char *names, *cmd;
1059         struct pane *p;
1060         char *dirname = pane_attr_get(ci->focus, "dirname");
1061         int prefix;
1062
1063         if (!dirname)
1064                 dirname = ".";
1065
1066         names = collect_names(ci->focus, "*", NULL);
1067         if (names && *names) {
1068                 cmd = strconcat(ci->focus, "mv --target-directory ",
1069                                 dirname, names);
1070                 prefix = strlen(cmd) - strlen(names);
1071         } else {
1072                 free(names);
1073                 names = collect_names(ci->focus, NULL, ci->mark);
1074                 if (!names || !*names) {
1075                         free(names);
1076                         call("Message:modal", ci->focus, 0, NULL,
1077                              "No file for rename");
1078                         return Efail;
1079                 }
1080                 cmd = strconcat(ci->focus, "mv", names, " ", dirname);
1081                 prefix = strlen(cmd);
1082         }
1083         p = call_ret(pane, "attach-shell-prompt", ci->focus, 0, NULL, cmd);
1084         if (p) {
1085                 call("doc:file", p, -1);
1086                 call("doc:char", p, prefix);
1087                 pane_add_notify(ci->home, p, "Notify:Close");
1088         }
1089         free(names);
1090         return 1;
1091 }
1092
1093 DEF_CMD(dirview_attach)
1094 {
1095         struct pane *p;
1096
1097         p = call_ret(pane, "attach-viewer", ci->focus);
1098         if (!p)
1099                 p = ci->focus;
1100         p = pane_register(p, 0, &dirview_handle.c);
1101         if (!p)
1102                 return Efail;
1103         attr_set_str(&p->attrs, "line-format",
1104                      "<fg:green-40>%flag</> <fg:red>%perms</> %mdate:13 %user:10 %group:10%hsize:-6  <fg:blue>%name%suffix</>%arrow<fg:green-30>%target</>");
1105         attr_set_str(&p->attrs, "heading",
1106                      "<bold,fg:blue,underline>  Perms      Mtime         Owner      Group      Size   File Name</>");
1107
1108         comm_call(ci->comm2, "cb", p);
1109         return 1;
1110 }
1111
1112 DEF_CMD(dirview_clone)
1113 {
1114         struct pane *p;
1115
1116         p = pane_register(ci->focus, 0, &dirview_handle.c);
1117         if (!p)
1118                 return Efail;
1119
1120         pane_clone_children(ci->home, p);
1121         return 1;
1122 }
1123
1124 DEF_CMD(dirview_doc_get_attr)
1125 {
1126         struct mark *m = ci->mark;
1127         const char *attr = ci->str;
1128         const char *val;
1129
1130         if (!m || !attr)
1131                 return Enoarg;
1132         if (strcmp(attr, "flag") != 0)
1133                 return Efallthrough;
1134         val = pane_mark_attr(ci->home->parent, m, "dir-cmd");
1135         if (!val)
1136                 val = " ";
1137         comm_call(ci->comm2, "cb", ci->focus, 0, m, val, 0, NULL, attr);
1138         return 1;
1139 }
1140
1141 DEF_CMD(dirview_close_notify)
1142 {
1143         /* shell window closed, maybe something was changed - check */
1144         if (ci->key[0] == 'c') {
1145                 /* the callback */
1146                 call("doc:notify:doc:revisit", ci->focus, 1);
1147                 return 1;
1148         }
1149         call_comm("event:timer", ci->home, &dirview_close_notify, 500);
1150         return 1;
1151 }
1152
1153 void edlib_init(struct pane *ed safe)
1154 {
1155         call_comm("global-set-command", ed, &dir_new, 0, NULL, "attach-doc-dir");
1156         call_comm("global-set-command", ed, &dir_new2, 0, NULL, "open-doc-dir");
1157
1158         dir_map = key_alloc();
1159         key_add_chain(dir_map, doc_default_cmd);
1160
1161         key_add(dir_map, "doc:load-file", &dir_load_file);
1162         key_add(dir_map, "doc:same-file", &dir_same_file);
1163         key_add(dir_map, "doc:set-ref", &dir_set_ref);
1164         key_add(dir_map, "doc:get-attr", &dir_doc_get_attr);
1165         key_add(dir_map, "doc:set-attr", &dir_doc_set_attr);
1166         key_add(dir_map, "doc:char", &dir_char);
1167         key_add(dir_map, "doc:notify:doc:revisit", &dir_revisited);
1168         key_add(dir_map, "doc:debug:mark", &dir_debug_mark);
1169
1170         key_add(dir_map, "doc:shares-ref", &dir_shares_ref);
1171
1172         key_add(dir_map, "get-attr", &dir_get_attr);
1173         key_add(dir_map, "Close", &dir_destroy);
1174         if(0)key_add(dir_map, "debug:validate-marks", &dir_val_marks);
1175
1176         call_comm("global-set-command", ed, &dirview_attach, 0, NULL,
1177                   "attach-dirview");
1178
1179         dirview_map = key_alloc();
1180         key_add(dirview_map, "doc:cmd-f", &dir_do_open);
1181         key_add(dirview_map, "doc:cmd-o", &dir_do_open_other);
1182         key_add(dirview_map, "doc:cmd-\n", &dir_do_open);
1183         key_add(dirview_map, "doc:cmd:Enter", &dir_do_open);
1184         key_add(dirview_map, "doc:cmd-g", &dir_do_reload);
1185         key_add(dirview_map, "doc:cmd-q", &dir_do_quit);
1186         key_add(dirview_map, "doc:cmd-d", &dir_do_mark_del);
1187         key_add(dirview_map, "doc:cmd-m", &dir_do_mark);
1188         key_add(dirview_map, "doc:cmd-u", &dir_un_mark);
1189         key_add(dirview_map, "doc:cmd-x", &dir_expunge);
1190         key_add(dirview_map, "doc:cmd-r", &dir_rename);
1191         key_add(dirview_map, "K:A-o", &dir_chmodown);
1192         key_add(dirview_map, "K:A-m", &dir_chmodown);
1193         key_add(dirview_map, "K:Del", &dir_un_mark_back);
1194         key_add_range(dirview_map, "doc:cmd-A", "doc:cmd-Z", &dir_do_special);
1195         key_add(dirview_map, "doc:get-attr", &dirview_doc_get_attr);
1196         key_add(dirview_map, "Clone", &dirview_clone);
1197         key_add(dirview_map, "Notify:Close", &dirview_close_notify);
1198 }