Thumbnail

rani/cscroll.git

Clone URL: https://git.buni.party/rani/cscroll.git

Viewing file on branch master

1#include <sys/resource.h>
2#include <sys/stat.h>
3#include <sys/wait.h>
4#include <stdbool.h>
5#include <strings.h>
6#include <dirent.h>
7#include <stdlib.h>
8#include <string.h>
9#include <unistd.h>
10#include <stdio.h>
11#include <regex.h>
12#include <errno.h>
13#include <fcntl.h>
14#include <pwd.h>
15#include <grp.h>
16#include <ftw.h>
17
18
19#include "dir.h"
20#include "config.h"
21#include "cvector.h"
22#include "hashmap.h"
23
24// S_ISVTX requires _XOPEN_SOURCE >= 500
25// I guess sticky bit isn't POSIX
26#ifndef S_ISVTX
27#define S_ISVTX 0
28#endif
29
30#ifndef PATH_MAX
31#define PATH_MAX 4096
32#endif
33
34#ifdef UNUSED
35#error UNUSED is already defined
36#undef UNUSED
37#endif
38#ifndef UNUSED
39# if __GNUC__ || defined(__clang__)
40# define UNUSED __attribute__((unused))
41# elif __STDC_VERSION__ >= 202311L
42# define UNUSED [[maybe_unused]]
43# else
44# define UNUSED
45# endif
46#endif /* UNUSED */
47
48#define CWDSZ (PATH_MAX + 1)
49#define LINKNAMESZ PATH_MAX
50
51static char cwd_buf[CWDSZ];
52static char * cwd = NULL;
53// marks is a map of sets
54// keys are directories and values are sets of marked files in that directory
55static hashmap * marks = NULL;
56static size_t n_marks = 0;
57
58static void dir_entry(int dirfd, const char * name, dirent_t * dirent, const hashmap * dirmarks);
59static void dir_fill_nodots(dir_t * dir);
60static char de_crepr(enum de_type de_type);
61static void free_dirent(void * p) {
62 dirent_t * de = (dirent_t*)p;
63 free(de->name);
64 free(de->uname);
65 free(de->gname);
66 free(de->linkname);
67}
68static size_t ilen(size_t i, int base) {
69 if (i == 0) return 1;
70 size_t r = 0;
71 while (i != 0) {
72 r++;
73 i /= base;
74 }
75 return r;
76}
77static void marks_destroyer(void * p) {
78 hashmap_destroy((hashmap*)p);
79}
80
81void dir_init(void) {
82 // read cwd
83 getcwd(cwd_buf, CWDSZ);
84 cwd = cwd_buf;
85 char * p = strrchr(cwd, '/');
86 if (*(p + 1) == 0) *p = 0;
87
88 marks = hashmap_new(marks_destroyer);
89}
90
91void dir_deinit(void) {
92 hashmap_destroy(marks);
93}
94
95int dir_list(const char * path, dir_t * dir) {
96 dir->len = 0;
97 dir->entries = NULL;
98 cvector_init(dir->entries, 1, free_dirent);
99 dir->nodots_len = 0;
100 dir->nodots = NULL;
101 dir->longest_uname = 0;
102 dir->longest_gname = 0;
103 dir->longest_size = 0;
104 dir->longest_size_small = 0;
105 dir->longest_size_unit = 0;
106
107 struct dirent * de;
108 DIR * dp = opendir(path);
109 if (!dp) {
110 return -errno;
111 }
112
113 hashmap * dirmarks = hashmap_get(marks, path);
114
115 while ((de = readdir(dp))) {
116 if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
117 continue;
118 }
119
120 dirent_t dirent;
121 dir_entry(dirfd(dp), de->d_name, &dirent, dirmarks);
122 cvector_push_back(dir->entries, dirent);
123 dir->len++;
124
125 size_t uname_len = 0;
126 size_t gname_len = 0;
127 size_t size_len = ilen(dirent.size, 10);
128 size_t size_small_len = ilen(dirent.size_small, 10);
129 size_t size_unit_len = (dirent.size_unit == SIZE_B) ? 1 : 2;
130 if (dirent.uname) uname_len = strlen(dirent.uname);
131 if (dirent.gname) gname_len = strlen(dirent.gname);
132 if (uname_len > dir->longest_uname) dir->longest_uname = uname_len;
133 if (gname_len > dir->longest_gname) dir->longest_gname = gname_len;
134 if (size_len > dir->longest_size) dir->longest_size = size_len;
135 if (size_small_len > dir->longest_size_small)
136 dir->longest_size_small = size_small_len;
137 if (size_unit_len > dir->longest_size_unit)
138 dir->longest_size_unit = size_unit_len;
139 }
140
141 closedir(dp);
142 dir_fill_nodots(dir);
143 dir_sort(dir);
144 return 0;
145}
146
147static void dir_fill_nodots(dir_t * dir) {
148 cvector_clear(dir->nodots);
149 dir->nodots_len = 0;
150
151 for (size_t i = 0; i < dir->len; i++) {
152 dirent_t * dirent = &dir->entries[i];
153 if (*dirent->name != '.') {
154 cvector_push_back(dir->nodots, dirent);
155 dir->nodots_len++;
156 }
157 }
158}
159
160void dir_entry(int dirfd, const char * name, dirent_t * dirent, const hashmap * dirmarks) {
161 memset(dirent, 0, sizeof(dirent_t));
162 dirent->name = strdup(name);
163 dirent->type = DE_UNKNOWN;
164
165 struct stat statbuf;
166 if (fstatat(dirfd, name, &statbuf, AT_SYMLINK_NOFOLLOW) < 0) {
167 // in the case where stat fails, the file is marked unknown
168 return;
169 }
170
171 if (dirmarks && hashmap_get(dirmarks, name)) dirent->marked = true;
172
173 // convert the ugly st_mode to a nice de_type
174 switch (statbuf.st_mode & S_IFMT) {
175 case S_IFSOCK: dirent->type = DE_SOCKET; break;
176 case S_IFLNK: dirent->type = DE_LINK; break;
177 case S_IFREG: dirent->type = DE_FILE; break;
178 case S_IFBLK: dirent->type = DE_BLOCK; break;
179 case S_IFDIR: dirent->type = DE_DIR; break;
180 case S_IFCHR: dirent->type = DE_CHAR; break;
181 case S_IFIFO: dirent->type = DE_FIFO; break;
182 default: dirent->type = DE_UNKNOWN; break;
183 }
184
185 if (S_ISLNK(statbuf.st_mode)) {
186 size_t linknamesz;
187 if (statbuf.st_size == 0) linknamesz = LINKNAMESZ;
188 else linknamesz = statbuf.st_size;
189 char * linkname = malloc(linknamesz + 1);
190 ssize_t len = readlinkat(dirfd, name, linkname, linknamesz);
191 if (len < 0) {
192 free(linkname);
193 dirent->linkname = NULL;
194 } else {
195 linkname[len] = 0;
196 dirent->linkname = linkname;
197 }
198
199 struct stat lstatbuf;
200 if (fstatat(dirfd, name, &lstatbuf, 0) < 0) {
201 dirent->linktype = DE_UNKNOWN;
202 } else switch (lstatbuf.st_mode & S_IFMT) {
203 case S_IFSOCK: dirent->linktype = DE_SOCKET; break;
204 case S_IFLNK: dirent->linktype = DE_LINK; break;
205 case S_IFREG: dirent->linktype = DE_FILE; break;
206 case S_IFBLK: dirent->linktype = DE_BLOCK; break;
207 case S_IFDIR: dirent->linktype = DE_DIR; break;
208 case S_IFCHR: dirent->linktype = DE_CHAR; break;
209 case S_IFIFO: dirent->linktype = DE_FIFO; break;
210 default: dirent->linktype = DE_UNKNOWN; break;
211 }
212 }
213
214 size_t size = statbuf.st_size;
215 enum size_unit size_unit = SIZE_B;
216 for (;size >= 1024;) {
217 size /= 1024;
218 size_unit++;
219 if (size_unit == SIZE_EB) break;
220 }
221
222 dirent->size = statbuf.st_size;
223 dirent->size_small = size;
224 dirent->size_unit = size_unit;
225
226 dirent->mode = statbuf.st_mode & 07777;
227 dirent->mtime = statbuf.st_mtime;
228
229 struct passwd * pw = getpwuid(statbuf.st_uid);
230 if (pw) dirent->uname = strdup(pw->pw_name);
231
232 struct group * gr = getgrgid(statbuf.st_gid);
233 if (gr) dirent->gname = strdup(gr->gr_name);
234}
235
236void dir_free(dir_t * dir) {
237 cvector_free(dir->entries);
238 dir->len = 0;
239 cvector_free(dir->nodots);
240 dir->nodots_len = 0;
241}
242
243char de_crepr(enum de_type de_type) {
244 switch (de_type) {
245 case DE_SOCKET: return '=';
246 case DE_LINK: return '@';
247 case DE_FILE: return 0;
248 case DE_BLOCK: return '#';
249 case DE_DIR: return '/';
250 case DE_CHAR: return '#';
251 case DE_FIFO: return '|';
252 default: return '?';
253 }
254}
255
256char dirent_crepr(const dirent_t * de) {
257 char c = de_crepr(de->type);
258 if (!c && dirent_isexec(de)) return '*';
259 return c;
260}
261
262char dirent_creprl(const dirent_t * de) {
263 return de_crepr(de->linktype);
264}
265
266char dirent_longcrepr(const dirent_t * de) {
267 switch (de->type) {
268 case DE_SOCKET: return '=';
269 case DE_LINK: return 'l';
270 case DE_BLOCK: return 'b';
271 case DE_DIR: return 'd';
272 case DE_CHAR: return 'c';
273 case DE_FIFO: return '|';
274 default: return '.';
275 }
276}
277
278bool dirent_isexec(const dirent_t * de) {
279 // this is because the condition can be truthy but is not actually a bool
280 return (de->mode & (M_USRX | M_GRPX | M_OTHX)) ? true : false;
281}
282
283const char * dirent_prettymode(const dirent_t * de) {
284 // 1 repr char, 9 mode bits, 1 null byte
285 static char s[11];
286 char * p = s;
287
288 if (de->type == DE_UNKNOWN) {
289 strcpy(s, "??????????");
290 return s;
291 }
292
293 *p++ = dirent_longcrepr(de);
294 *p++ = (de->mode & M_USRR) ? 'r' : '-';
295 *p++ = (de->mode & M_USRW) ? 'w' : '-';
296 *p++ = (de->mode & M_USRX) ?
297 (de->mode & M_SUID) ? 's' : 'x'
298 : (de->mode & M_SUID) ? 'S' : '-';
299 *p++ = (de->mode & M_GRPR) ? 'r' : '-';
300 *p++ = (de->mode & M_GRPW) ? 'w' : '-';
301 *p++ = (de->mode & M_GRPX) ?
302 (de->mode & M_SGID) ? 's' : 'x'
303 : (de->mode & M_SGID) ? 'S' : '-';
304 *p++ = (de->mode & M_OTHR) ? 'r' : '-';
305 *p++ = (de->mode & M_OTHW) ? 'w' : '-';
306 *p++ = (de->mode & M_OTHX) ?
307 (de->mode & M_STICKY) ? 't' : 'x'
308 : (de->mode & M_STICKY) ? 'T' : '-';
309
310 *p = 0;
311
312 return s;
313}
314
315const char * dir_get_cwd(void) {
316 return cwd;
317}
318
319int dir_cd_back(const char * wd) {
320 if (!*wd) return -EPERM;
321 if (!strcmp(wd, "/")) return 0;
322
323 char * newwd = strdup(wd);
324 char * p = strrchr(newwd, '/');
325 *p = 0;
326 if (*newwd == 0) {
327 *newwd = '/';
328 *(newwd + 1) = 0;
329 }
330 int ret = chdir(newwd);
331 int terrno = errno;
332 if (ret >= 0) strcpy(cwd, newwd);
333 free(newwd);
334
335 if (ret < 0) return -terrno;
336 return 0;
337}
338
339int dir_cd(const char * wd, const char * next) {
340 size_t wdlen = strlen(wd);
341 size_t nextlen = strlen(next);
342
343 // extra byte for '/' character
344 char * newwd = malloc(wdlen + nextlen + 1 + 1);
345 if (strcmp(wd, "/") != 0) {
346 strcpy(newwd, wd);
347 newwd[wdlen] = '/';
348 strcpy(newwd + wdlen + 1, next);
349 } else {
350 newwd[0] = '/';
351 strcpy(newwd + 1, next);
352 }
353
354 int ret = chdir(newwd);
355 int terrno = errno;
356 if (ret >= 0) strcpy(cwd, newwd);
357 free(newwd);
358
359 if (ret < 0) return -terrno;
360 return 0;
361}
362
363const char * dir_basename(const char * path) {
364 const char * p = strrchr(path, '/');
365 if (!p || *(p + 1) == 0) return NULL;
366 return p + 1;
367}
368
369int dir_search_name(const dir_t * dir, const char * name, size_t * idx) {
370 size_t dirlen = dir_len(dir);
371
372 for (size_t i = 0; i < dirlen; i++) {
373 dirent_t * de = dir_get_entry(dir, i);
374 if (!strcmp(de->name, name)) {
375 *idx = i;
376 return 0;
377 }
378 }
379
380 return -1;
381}
382
383int dir_search_regex(const dir_t * dir, const char * regexstr, size_t * idx) {
384 size_t dirlen = dir_len(dir);
385
386 regex_t regex;
387 int ret = regcomp(&regex, regexstr, REG_EXTENDED);
388 if (!!ret) return -REGSEARCH_BAD_REGEX;
389
390 for (size_t i = 0; i < dirlen; i++) {
391 dirent_t * de = dir_get_entry(dir, i);
392 ret = regexec(&regex, de->name, 0, NULL, 0);
393 if (!ret) {
394 *idx = i;
395 regfree(&regex);
396 return 0;
397 }
398 }
399
400 regfree(&regex);
401 return -REGSEARCH_NOT_FOUND;
402}
403
404size_t dir_len(const dir_t * dir) {
405 if (config.dots) return dir->len;
406 else return dir->nodots_len;
407}
408
409dirent_t * dir_get_entry(const dir_t * dir, size_t cursor) {
410 if (dir_len(dir) == 0) return NULL;
411
412 if (config.dots) return &dir->entries[cursor];
413 else return dir->nodots[cursor];
414}
415
416static int dir_sort_cmp(const void * inpa, const void * inpb) {
417 const dirent_t * a = (const dirent_t*)inpa;
418 const dirent_t * b = (const dirent_t*)inpb;
419
420 if (config.dir_sort_dirs != DIR_SORT_DIRS_UNSORTED
421 && ((a->type == DE_DIR) != (b->type == DE_DIR))) {
422 if (config.dir_sort_dirs == DIR_SORT_DIRS_FIRST)
423 return (a->type == DE_DIR) ? -1 : 1;
424 if (config.dir_sort_dirs == DIR_SORT_DIRS_LAST)
425 return (b->type == DE_DIR) ? -1 : 1;
426 }
427
428 if (config.dir_sort == DIR_SORT_DECREASING) {
429 const dirent_t * tmp = a;
430 a = b;
431 b = tmp;
432 }
433
434 switch (config.dir_sortby) {
435 case DIR_SORTBY_NAME:
436 return strcasecmp(a->name, b->name);
437 case DIR_SORTBY_TIME:
438 return (a->mtime < b->mtime) ? -1 : 1;
439 case DIR_SORTBY_SIZE:
440 return (a->size < b->size) ? -1 : 1;
441 default: return 0;
442 }
443}
444
445
446void dir_sort(dir_t * dir) {
447 if (config.dir_sort == DIR_SORT_UNSORTED) return;
448 if (dir->entries && dir->len > 0)
449 qsort(dir->entries, dir->len, sizeof(dir->entries[0]), dir_sort_cmp);
450 dir_fill_nodots(dir);
451}
452
453const char * dirent_size_unit(const dirent_t * de) {
454 static const char * units[] = {
455 [SIZE_B] = "B", [SIZE_KB] = "kB", [SIZE_MB] = "MB",
456 [SIZE_GB] = "GB", [SIZE_TB] = "TB", [SIZE_EB] = "EB",
457 };
458 static const char * nounit = "";
459
460 const char * ret = units[de->size_unit];
461 if (!ret) return nounit;
462 return ret;
463}
464
465static int dir_internal_ftw_(
466 int dfd,
467 int (*cb)(
468 int dirfd,
469 const char * path,
470 const struct stat * sb,
471 void * arg
472 ),
473 void * arg,
474 bool postorder,
475
476 dev_t rootdev
477) {
478 int retval = 0;
479
480 DIR * dp = fdopendir(dfd);
481 if (!dp) return 0;
482 dfd = dirfd(dp);
483
484 struct dirent * de;
485 while ((de = readdir(dp)) != NULL) {
486 int ret;
487
488 char * name = de->d_name;
489 if (!strcmp(name, ".") || !strcmp(name, "..")) continue;
490
491 struct stat statbuf;
492 ret = fstatat(dfd, name, &statbuf, AT_SYMLINK_NOFOLLOW);
493 struct stat * statbufp;
494 if (ret < 0) statbufp = NULL;
495 else statbufp = &statbuf;
496
497 if (statbuf.st_dev != rootdev) continue;
498
499 // do an inorder traversal
500 if (!postorder) {
501 ret = cb(dfd, name, statbufp, arg);
502 // if cb returns a negative value, stop the walk immediately
503 if (ret < 0) {
504 retval = ret;
505 goto done;
506 }
507 }
508
509 if (S_ISDIR(statbuf.st_mode)) {
510 int nextfd = openat(dfd, name, O_RDONLY | O_DIRECTORY);
511 ret = dir_internal_ftw_(nextfd, cb, arg, postorder, rootdev);
512 if (ret < 0) {
513 retval = ret;
514 goto done;
515 }
516 }
517
518 // do a postorder traversal
519 if (postorder) {
520 ret = cb(dfd, name, statbufp, arg);
521 if (ret < 0) {
522 retval = ret;
523 goto done;
524 }
525 }
526 }
527
528done:
529 closedir(dp);
530 return retval;
531}
532
533static int dir_internal_ftw(
534 const char * path,
535 int (*cb)(
536 int dirfd,
537 const char * path,
538 const struct stat * sb,
539 void * arg
540 ),
541 void * arg,
542 bool postorder
543) {
544 DIR * d = opendir(path);
545 if (!d) return -1;
546
547 struct stat statbuf;
548 // I don't think it can error here
549 if (lstat(path, &statbuf) < 0) {
550 closedir(d);
551 return -1;
552 }
553
554 dev_t rootdev = statbuf.st_dev;
555
556 int ret = dir_internal_ftw_(dup(dirfd(d)), cb, arg, postorder, rootdev);
557 closedir(d);
558 return ret;
559}
560
561static int dirent_subfiles_ftw_cb(
562 UNUSED int dirfd, UNUSED const char * path,
563 UNUSED const struct stat * sb, void * arg
564) {
565 (*(size_t*)arg)++;
566 return 0;
567}
568
569static int dirent_delete_ftw_cb(
570 int dirfd, const char * path, const struct stat * sb,
571 UNUSED void * arg
572) {
573 if (!sb) return 0;
574
575 int flags = 0;
576 if (S_ISDIR(sb->st_mode)) flags = AT_REMOVEDIR;
577
578 unlinkat(dirfd, path, flags);
579 return 0;
580}
581
582size_t dirent_subfiles(const dirent_t * de) {
583 if (de->type != DE_DIR) return 0;
584
585 size_t count = 0;
586 dir_internal_ftw(de->name, dirent_subfiles_ftw_cb, &count, false);
587 return count;
588}
589
590int dirent_delete(const dirent_t * de) {
591 if (de->type == DE_DIR) {
592 int ret = dir_internal_ftw(de->name, dirent_delete_ftw_cb, NULL, true);
593 if (ret >= 0) {
594 if (rmdir(de->name) < 0) return -errno;
595 return 0;
596 }
597 return ret;
598 } else {
599 if (unlink(de->name) < 0) return -errno;
600 return 0;
601 }
602}
603
604int dirent_open(const dirent_t * de) {
605 pid_t pid = fork();
606 if (pid < 0) return -errno;
607
608 if (!pid) {
609 // detach everything from this terminal basically
610 int fd = open("/dev/null", O_RDWR);
611 if (fd < 0) {
612 close(STDOUT_FILENO);
613 close(STDIN_FILENO);
614 } else {
615 dup2(fd, STDOUT_FILENO);
616 dup2(fd, STDIN_FILENO);
617 }
618 execvp(config.opener, (char*[]){config.opener, de->name, NULL});
619 if (fd >= 0) close(fd);
620 exit(0);
621 }
622
623 waitpid(pid, NULL, 0);
624 return 0;
625}
626
627static void dir_normalize_path(char * path) {
628 if (!path || !*path) return;
629
630 char * base = path + 1;
631 // remove duplicate separators
632 for (char * p = base; *p; p++) {
633 // a + a'b = a + b
634 if (*p != '/' || *(base - 1) != '/')
635 *base++ = *p;
636 }
637 *base = 0;
638
639 // remove trailing separators
640 while (base > path + 1 && *(base - 1) == '/') {
641 *(base - 1) = 0;
642 base--;
643 }
644}
645
646const char * dir_get_home(void) {
647 static char home_buf[PATH_MAX+1];
648
649 uid_t uid = getuid();
650 struct passwd * pw = getpwuid(uid);
651 if (!pw) {
652 const char * envhome = getenv("HOME");
653 if (!envhome) return NULL;
654 strncpy(home_buf, envhome, PATH_MAX);
655 home_buf[PATH_MAX] = 0;
656
657 if (!home_buf[0]) return NULL; // idk but HOME should be absolute
658 dir_normalize_path(home_buf);
659 return home_buf;
660 }
661 return pw->pw_dir;
662}
663
664size_t dir_get_total_marked(void) {
665 return n_marks;
666}
667
668static size_t dir_get_total_marked_(void) {
669 size_t n_marks = 0;
670
671 hashmap_walk_state marksws = {0};
672 while (hashmap_walk(marks, &marksws)) {
673 hashmap * dirmarks = marksws.val;
674 hashmap_walk_state dirmarksws = {0};
675 while (hashmap_walk(dirmarks, &dirmarksws))
676 n_marks++;
677 }
678
679 return n_marks;
680}
681
682int dirent_togglemark(dirent_t * de) {
683 if (de->type == DE_UNKNOWN) return -TOGGLEMARK_DIRENT_UNKNOWN;
684
685 if (de->marked) {
686 hashmap * dirmarks = hashmap_get(marks, cwd);
687 de->marked = false;
688 hashmap_remove(dirmarks, de->name);
689 if (dirmarks->len == 0) hashmap_remove(marks, cwd);
690 n_marks--;
691 } else {
692 // do not allow marking if an ancestor is already marked
693 bool bad = false;
694 char * ancestor = strdup(cwd);
695 do {
696 char * sep = strrchr(ancestor, '/');
697 *sep = 0;
698 char * base = sep + 1;
699
700 hashmap * ancestor_marks;
701 if (sep == ancestor) ancestor_marks = hashmap_get(marks, "/");
702 else ancestor_marks = hashmap_get(marks, ancestor);
703
704 if (!ancestor_marks) continue;
705 if (hashmap_get(ancestor_marks, base)) {
706 bad = true;
707 break;
708 }
709 } while (*ancestor);
710 free(ancestor);
711 if (bad) return -TOGGLEMARK_PARENT_MARKED;
712
713 de->marked = true;
714 hashmap * dirmarks = hashmap_get(marks, cwd);
715 if (!dirmarks) {
716 dirmarks = hashmap_new(NULL);
717 hashmap_insert(marks, cwd, dirmarks);
718 }
719 hashmap_insert(dirmarks, de->name, (void*)true);
720
721 // unmark all children if marking a directory
722 if (de->type == DE_DIR) {
723 const char * fmt;
724 if (!strcmp(cwd, "/")) fmt = "%s%s";
725 else fmt = "%s/%s";
726
727 size_t dirpathlen = snprintf(NULL, 0, fmt, cwd, de->name);
728 // extra byte for trailing slash
729 char * dirpath = malloc(dirpathlen + 1 + 1);
730 sprintf(dirpath, fmt, cwd, de->name);
731
732 // check any direct children
733 if (hashmap_get(marks, dirpath)) hashmap_remove(marks, dirpath);
734
735 // check any subchildren
736 strcat(dirpath, "/");
737 dirpathlen++;
738 bool deleted;
739 do {
740 deleted = false;
741
742 // this is a horrible solution
743 hashmap_walk_state ws = {0};
744 while (hashmap_walk(marks, &ws)) {
745 if (!strncmp(ws.key, dirpath, dirpathlen)) {
746 hashmap_remove(marks, ws.key);
747 deleted = true;
748 break;
749 }
750 }
751 } while (deleted);
752
753 free(dirpath);
754 }
755
756 n_marks = dir_get_total_marked_();
757 }
758
759 return 0;
760}
761
762int dir_paste_marks(const char * cwd, size_t * total_pastes) {
763 *total_pastes = 0;
764
765 DIR * pastedir = opendir(cwd);
766 if (!pastedir) return -errno;
767 int pastefd = dirfd(pastedir);
768
769 int retval = 0;
770
771 hashmap_walk_state marksws = {0};
772 while (hashmap_walk(marks, &marksws)) {
773 const char * dirpath = marksws.key;
774 hashmap * dirmarks = marksws.val;
775
776 DIR * sourcedir = opendir(dirpath);
777 if (!sourcedir) continue;
778 int sourcefd = dirfd(sourcedir);
779
780 hashmap_walk_state dirmarksws = {0};
781 while (hashmap_walk(dirmarks, &dirmarksws)) {
782 const char * fname = dirmarksws.key;
783 // make sure the new file name doesn't already exist
784 struct stat statbuf;
785 int ret = fstatat(pastefd, fname, &statbuf, AT_SYMLINK_NOFOLLOW);
786 if (ret >= 0) continue;
787 ret = renameat(sourcefd, fname, pastefd, fname);
788 if (ret < 0) continue;
789 (*total_pastes)++;
790 }
791
792 closedir(sourcedir);
793 }
794
795 closedir(pastedir);
796 hashmap_destroy(marks);
797 marks = hashmap_new(marks_destroyer);
798 n_marks = 0;
799 return retval;
800}
801
802size_t dir_marked_subfiles(void) {
803 size_t total = 0;
804
805 hashmap_walk_state marksws = {0};
806 while (hashmap_walk(marks, &marksws)) {
807 const char * dirpath = marksws.key;
808 hashmap * dirmarks = marksws.val;
809
810 DIR * basedir = opendir(dirpath);
811 if (!basedir) continue;
812 int basedirfd = dirfd(basedir);
813
814 hashmap_walk_state dirmarksws = {0};
815 while (hashmap_walk(dirmarks, &dirmarksws)) {
816 const char * fname = dirmarksws.key;
817
818 struct stat statbuf;
819 int ret = fstatat(basedirfd, fname, &statbuf, AT_SYMLINK_NOFOLLOW);
820 if (ret < 0) continue;
821 if (!S_ISDIR(statbuf.st_mode)) continue;
822
823 char * subpath = malloc(strlen(dirpath) + 1 + strlen(fname) + 1);
824 sprintf(subpath, "%s/%s", dirpath, fname);
825
826 size_t subfiles = 0;
827 dir_internal_ftw(subpath, dirent_subfiles_ftw_cb, &subfiles, false);
828 free(subpath);
829 total += subfiles;
830 }
831
832 closedir(basedir);
833 }
834
835 return total;
836}
837
838int dir_marked_delete(void) {
839 int retval = 0;
840
841 hashmap_walk_state marksws = {0};
842 while (hashmap_walk(marks, &marksws)) {
843 const char * dirpath = marksws.key;
844 hashmap * dirmarks = marksws.val;
845
846 hashmap_walk_state dirmarksws = {0};
847 while (hashmap_walk(dirmarks, &dirmarksws)) {
848 const char * fname = dirmarksws.key;
849
850 char * subpath = malloc(strlen(dirpath) + 1 + strlen(fname) + 1);
851 sprintf(subpath, "%s/%s", dirpath, fname);
852
853 // subpath is absolute so dirfd isn't used
854 dirent_t de;
855 dir_entry(-1, subpath, &de, NULL);
856 free(subpath);
857
858 int ret = dirent_delete(&de);
859 if (ret < 0) retval = ret;
860 free_dirent(&de);
861 }
862 }
863
864 hashmap_destroy(marks);
865 marks = hashmap_new(marks_destroyer);
866 n_marks = 0;
867
868 return retval;
869}
870