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 }