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