fe
terminal file explorer and picker
git clone https://9o.is/git/fe.git
entries.c
(14983B)
1 #include <sys/stat.h>
2 #include <sys/types.h>
3
4 #include <dirent.h>
5 #include <errno.h>
6 #include <fcntl.h>
7 #include <libgen.h>
8 #include <limits.h>
9 #include <locale.h>
10 #include <regex.h>
11 #include <signal.h>
12 #include <stdarg.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <strings.h>
17 #include <unistd.h>
18 #include "entries.h"
19 #include "config.h"
20
21 static int sort_dir;
22 static int sort_icase;
23 static int sort_mtime;
24 static int show_hidden;
25
26 static void *xrealloc(void *p, size_t size) {
27 p = realloc(p, size);
28 if (p == NULL)
29 exit(1);
30 return p;
31 }
32
33 static char *xdirname(const char *path) {
34 static char out[PATH_MAX];
35 char tmp[PATH_MAX], *p;
36
37 strlcpy(tmp, path, sizeof(tmp));
38 p = dirname(tmp);
39 if (p == NULL)
40 exit(1);
41 strlcpy(out, p, sizeof(out));
42 return out;
43 }
44
45 static int dircmp(mode_t a, mode_t b) {
46 if (S_ISDIR(a) && S_ISDIR(b))
47 return 0;
48 if (!S_ISDIR(a) && !S_ISDIR(b))
49 return 0;
50 if (S_ISDIR(a))
51 return -1;
52 else
53 return 1;
54 }
55
56 static int entrycmp(const void *va, const void *vb) {
57 const struct entry *a = va, *b = vb;
58
59 if (sort_dir && dircmp(a->mode, b->mode) != 0)
60 return dircmp(a->mode, b->mode);
61 if (sort_mtime)
62 return b->t - a->t;
63 if (sort_icase)
64 return strcasecmp(a->name, b->name);
65 return strcmp(a->name, b->name);
66 }
67
68 static int canopendir(char *path) {
69 DIR *dirp;
70
71 dirp = opendir(path);
72 if (dirp == NULL) {
73 printf("Error opening directory '%s': %s\n", path, strerror(errno));
74 return 0;
75 }
76 closedir(dirp);
77 return 1;
78 }
79
80 static char *mkpath(char *dir, char *name, char *out, size_t n) {
81 if (name[0] == '/') {
82 strlcpy(out, name, n);
83 } else {
84 if (strcmp(dir, "/") == 0) {
85 strlcpy(out, "/", n);
86 strlcat(out, name, n);
87 } else {
88 strlcpy(out, dir, n);
89 strlcat(out, "/", n);
90 strlcat(out, name, n);
91 }
92 }
93 return out;
94 }
95
96 static int dentfill(char *path, struct entry **dents) {
97 char newpath[PATH_MAX];
98 DIR *dirp;
99 struct dirent *dp;
100 struct stat sb;
101 int r, n = 0;
102
103 dirp = opendir(path);
104 if (dirp == NULL)
105 return 0;
106
107 while ((dp = readdir(dirp)) != NULL) {
108 if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
109 continue;
110 if (!show_hidden && dp->d_name[0] == '.')
111 continue;
112 *dents = xrealloc(*dents, (n + 1) * sizeof(**dents));
113 strlcpy((*dents)[n].name, dp->d_name, sizeof((*dents)[n].name));
114 mkpath(path, dp->d_name, newpath, sizeof(newpath));
115 r = lstat(newpath, &sb);
116 if (r == -1)
117 exit(1);
118
119 (*dents)[n].mode = sb.st_mode;
120 (*dents)[n].t = sb.st_mtime;
121
122 if (S_ISDIR(sb.st_mode)) {
123 (*dents)[n].color = COLOR_DIRECTORY;
124 (*dents)[n].cm = '/';
125 } else if (S_ISSOCK(sb.st_mode)) {
126 (*dents)[n].color = COLOR_SOCK;
127 (*dents)[n].cm = '=';
128 } else if (S_ISFIFO(sb.st_mode)) {
129 (*dents)[n].color = COLOR_FIFO;
130 (*dents)[n].cm = '|';
131 } else if (S_ISLNK(sb.st_mode)) {
132 (*dents)[n].color = COLOR_LINK;
133 (*dents)[n].cm = '@';
134 } else if (sb.st_mode & S_IXUSR) {
135 (*dents)[n].color = COLOR_EXEC;
136 (*dents)[n].cm = '*';
137 } else {
138 (*dents)[n].color = COLOR_REGULAR;
139 (*dents)[n].cm = '\0';
140 }
141
142 n++;
143 }
144
145 r = closedir(dirp);
146 if (r == -1)
147 exit(1);
148 return n;
149 }
150
151 static int dentfind(struct entry *dents, int n, char *cwd, const char *path) {
152 char tmp[PATH_MAX];
153 int i;
154
155 if (path == NULL)
156 return 0;
157 for (i = 0; i < n; i++) {
158 mkpath(cwd, dents[i].name, tmp, sizeof(tmp));
159 if (strcmp(tmp, path) == 0)
160 return i;
161 }
162 return 0;
163 }
164
165 static int filetype(const char *path) {
166 int fd = open(path, O_RDONLY | O_NONBLOCK);
167 if (fd == -1) return 0;
168
169 struct stat sb;
170 int r = fstat(fd, &sb);
171
172 close(fd);
173 if (r == -1) return 0;
174
175 return sb.st_mode & S_IFMT;
176 }
177
178 static int set_directory(entries_t *entries, char *path) {
179 if (canopendir(path) == 0)
180 return -1;
181
182 char oldpath[PATH_MAX];
183
184 free(entries->dents);
185 entries->dents = NULL;
186
187 strlcpy(oldpath, entries->path, sizeof(oldpath));
188 strlcpy(entries->path, path, sizeof(entries->path));
189 entries->size = dentfill(entries->path, &entries->dents);
190
191 if (entries->size > 0) {
192 qsort(entries->dents, entries->size, sizeof(*entries->dents), entrycmp);
193 entries->selection = dentfind(entries->dents, entries->size, entries->path, oldpath);
194 }
195
196 return 0;
197 }
198
199 static void set_path(entries_t *entries, char *path) {
200 if (filetype(path) == S_IFDIR) {
201 set_directory(entries, path);
202 } else {
203 char *dir = xdirname(path);
204
205 if (filetype(dir) == 0) {
206 perror("Invalid argument");
207 exit(1);
208 }
209
210 strlcpy(entries->path, path, sizeof(entries->path));
211 set_directory(entries, dir);
212 }
213 }
214
215 static void truncate_at_newline(const char *buffer) {
216 char *newline_pos = strchr(buffer, '\n');
217 if (newline_pos != NULL) {
218 *newline_pos = '\0';
219 }
220 }
221
222 void entries_init(entries_t *entries, const options_t *options) {
223 strcpy(entries->path, "");
224 entries->dents = NULL;
225 entries->size = 0;
226 entries->selection = 0;
227
228 sort_dir = options->sort_dir;
229 sort_mtime = options->sort_mtime;
230 sort_icase = options->sort_icase;
231 show_hidden = options->show_hidden;
232 }
233
234 void entries_init_path(entries_t *entries, const char *path) {
235 char p[PATH_MAX];
236 if (path != NULL) {
237 strlcpy(p, path, sizeof(p));
238 } else if (getcwd(p, sizeof(p)) == NULL) {
239 perror("Failed to get current working directory");
240 exit(1);
241 }
242
243 set_path(entries, p);
244 }
245
246 void entries_init_stdinpath(entries_t *entries) {
247 int c;
248 char p[PATH_MAX];
249 size_t i = 0;
250
251 while ((c = fgetc(stdin)) != EOF && i < PATH_MAX - 1) {
252 if (c == '\n') {
253 break;
254 }
255 p[i++] = c;
256 }
257
258 p[i] = '\0';
259 set_path(entries, p);
260 }
261
262 void entries_destroy(entries_t *entries) {
263 free(entries->dents);
264
265 entries->dents = NULL;
266 entries->size = 0;
267 entries->selection = 0;
268 }
269
270 void entries_parent(entries_t *entries) {
271 if (strcmp(entries->path, "/") == 0 ||
272 strcmp(entries->path, ".") == 0 ||
273 strchr(entries->path, '/') == NULL) {
274 return;
275 }
276 char *newpath = xdirname(entries->path);
277 set_directory(entries, newpath);
278 }
279
280 void entries_setpath(entries_t *entries, char *path) {
281 if (!path || !path[0]) return;
282 truncate_at_newline(path);
283 set_path(entries, path);
284 }
285
286 int entries_select(entries_t *entries) {
287 char newpath[PATH_MAX];
288
289 if (entries->size == 0)
290 return 0;
291
292 mkpath(entries->path, entries->dents[entries->selection].name, newpath, sizeof(newpath));
293
294 switch (filetype(newpath)) {
295 case S_IFREG: return 1;
296 case S_IFDIR: return set_directory(entries, newpath);
297 }
298
299 return 0;
300 }
301
302 void entries_position(entries_t *entries, size_t position) {
303 if (entries->size > 0 && position < entries->size)
304 entries->selection = position;
305 }
306
307 void entries_prev(entries_t *entries) {
308 if (entries->size > 0)
309 entries->selection = (entries->selection + entries->size - 1) % entries->size;
310 }
311
312 void entries_next(entries_t *entries) {
313 if (entries->size > 0)
314 entries->selection = (entries->selection + 1) % entries->size;
315 }
316
317 void entries_togglehidden() {
318 show_hidden = show_hidden == 0 ? 1 : 0;
319 }
320
321 struct entry *entries_item(entries_t *entries, size_t n) {
322 if (n < entries->size) {
323 return &entries->dents[n];
324 } else {
325 return NULL;
326 }
327 }
328
329 struct entry *entries_selected(entries_t *entries) {
330 return entries_item(entries, entries->selection);
331 }
332
333 int entries_remove(entries_t *entries) {
334 char rmpath[PATH_MAX];
335 char stack[PATH_MAX][1024];
336 int stack_top = 0;
337
338 mkpath(entries->path, entries->dents[entries->selection].name, rmpath, sizeof(rmpath));
339
340 if (filetype(rmpath) != S_IFDIR) {
341 return unlink(rmpath);
342 }
343
344 strlcpy(stack[stack_top], rmpath, PATH_MAX);
345 stack_top++;
346
347 while (stack_top > 0) {
348 stack_top--;
349 char current_path[PATH_MAX];
350 strlcpy(current_path, stack[stack_top], PATH_MAX);
351
352 DIR *dirp = opendir(current_path);
353 if (dirp == NULL) {
354 perror("opendir");
355 continue;
356 }
357
358 int is_empty = 1;
359 struct dirent *dp;
360
361 while ((dp = readdir(dirp)) != NULL) {
362 if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
363 continue;
364
365 char child_path[PATH_MAX];
366 mkpath(current_path, dp->d_name, child_path, sizeof(child_path));
367
368 if (filetype(child_path) == S_IFDIR) {
369 if (stack_top < 1024) {
370 if (is_empty) {
371 strlcpy(stack[stack_top], current_path, PATH_MAX);
372 stack_top++;
373 }
374 strlcpy(stack[stack_top], child_path, PATH_MAX);
375 stack_top++;
376 }
377 is_empty = 0;
378 } else {
379 if (unlink(child_path) == -1) {
380 perror("unlink");
381 }
382 }
383 }
384
385 closedir(dirp);
386
387 if (is_empty) {
388 if (rmdir(current_path) == -1) {
389 perror("rmdir");
390 }
391 }
392 }
393
394 return 0;
395 }
396
397 int entries_create_file(entries_t *entries, const char *filename) {
398 char fullpath[PATH_MAX];
399 int fd;
400
401 mkpath(entries->path, (char *)filename, fullpath, sizeof(fullpath));
402
403 fd = open(fullpath, O_CREAT | O_EXCL | O_WRONLY, 0644);
404 if (fd == -1) {
405 return -1;
406 }
407
408 close(fd);
409 return 0;
410 }
411
412 int entries_create_dir(entries_t *entries, const char *dirname) {
413 char fullpath[PATH_MAX];
414
415 mkpath(entries->path, (char *)dirname, fullpath, sizeof(fullpath));
416
417 if (mkdir(fullpath, 0755) == -1) {
418 return -1;
419 }
420
421 return 0;
422 }
423
424 int entries_find_file(entries_t *entries, const char *filename) {
425 for (size_t i = 0; i < entries->size; i++) {
426 if (strcmp(entries->dents[i].name, filename) == 0) {
427 return i;
428 }
429 }
430 return -1;
431 }
432
433 static int copy_internal(const char *src, const char *dst) {
434 int src_fd, dst_fd;
435 char buffer[4096];
436 ssize_t bytes_read;
437
438 src_fd = open(src, O_RDONLY);
439 if (src_fd == -1) {
440 return -1;
441 }
442
443 dst_fd = open(dst, O_CREAT | O_WRONLY | O_TRUNC, 0644);
444 if (dst_fd == -1) {
445 close(src_fd);
446 return -1;
447 }
448
449 while ((bytes_read = read(src_fd, buffer, sizeof(buffer))) > 0) {
450 if (write(dst_fd, buffer, bytes_read) != bytes_read) {
451 close(src_fd);
452 close(dst_fd);
453 return -1;
454 }
455 }
456
457 close(src_fd);
458 close(dst_fd);
459 return 0;
460 }
461
462 int entries_copy_file(const char *src, const char *dst) {
463 if (filetype(src) != S_IFDIR) {
464 return copy_internal(src, dst);
465 }
466
467 struct {
468 char src[PATH_MAX];
469 char dst[PATH_MAX];
470 } stack[128];
471 int stack_top = 0;
472
473 if (mkdir(dst, 0755) == -1 && errno != EEXIST) {
474 perror("mkdir");
475 return -1;
476 }
477
478 strlcpy(stack[stack_top].src, src, PATH_MAX);
479 strlcpy(stack[stack_top].dst, dst, PATH_MAX);
480 stack_top++;
481
482 while (stack_top > 0) {
483 stack_top--;
484 char curr_src[PATH_MAX];
485 char curr_dst[PATH_MAX];
486 strlcpy(curr_src, stack[stack_top].src, PATH_MAX);
487 strlcpy(curr_dst, stack[stack_top].dst, PATH_MAX);
488
489 DIR *dirp = opendir(curr_src);
490 if (dirp == NULL) {
491 perror("opendir");
492 continue;
493 }
494
495 struct dirent *dp;
496 while ((dp = readdir(dirp)) != NULL) {
497 if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
498 continue;
499
500 char next_src[PATH_MAX];
501 char next_dst[PATH_MAX];
502 mkpath(curr_src, dp->d_name, next_src, sizeof(next_src));
503 mkpath(curr_dst, dp->d_name, next_dst, sizeof(next_dst));
504
505 if (filetype(next_src) == S_IFDIR) {
506 if (mkdir(next_dst, 0755) == -1 && errno != EEXIST) {
507 perror("mkdir");
508 } else if (stack_top < 128) {
509 strlcpy(stack[stack_top].src, next_src, PATH_MAX);
510 strlcpy(stack[stack_top].dst, next_dst, PATH_MAX);
511 stack_top++;
512 }
513 } else {
514 if (copy_internal(next_src, next_dst) == -1) {
515 perror("copy_internal");
516 }
517 }
518 }
519 closedir(dirp);
520 }
521
522 return 0;
523 }
524
525 void entries_reload(entries_t *entries) {
526 char *name = entries->dents[entries->selection].name;
527 set_directory(entries, entries->path);
528 int index = entries_find_file(entries, name);
529 entries_position(entries, (size_t)index);
530 }
531
532 int entries_move_file(const char *src, const char *dst) {
533 if (filetype(src) != S_IFDIR) {
534 return rename(src, dst);
535 }
536
537 if (rename(src, dst) == 0) {
538 return 0;
539 }
540
541 if (errno != EXDEV) {
542 return -1;
543 }
544
545 if (entries_copy_file(src, dst) == 0) {
546 char stack[PATH_MAX][1024];
547 int stack_top = 0;
548 strlcpy(stack[stack_top], src, PATH_MAX);
549 stack_top++;
550
551 while (stack_top > 0) {
552 stack_top--;
553 char current_path[PATH_MAX];
554 strlcpy(current_path, stack[stack_top], PATH_MAX);
555
556 DIR *dirp = opendir(current_path);
557 if (dirp == NULL) {
558 continue;
559 }
560
561 int is_empty = 1;
562 struct dirent *dp;
563
564 while ((dp = readdir(dirp)) != NULL) {
565 if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
566 continue;
567
568 char child_path[PATH_MAX];
569 mkpath(current_path, dp->d_name, child_path, sizeof(child_path));
570
571 if (filetype(child_path) == S_IFDIR) {
572 if (stack_top < 1024) {
573 strlcpy(stack[stack_top], child_path, PATH_MAX);
574 stack_top++;
575 }
576 is_empty = 0;
577 } else {
578 if (unlink(child_path) == -1) {
579 perror("unlink");
580 }
581 }
582 }
583
584 closedir(dirp);
585
586 if (is_empty) {
587 if (rmdir(current_path) == -1) {
588 perror("rmdir");
589 }
590 }
591 }
592
593 return 0;
594 }
595
596 return -1;
597 }