#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <pwd.h>
#include <unistd.h>
#include "pistachio.h"
#define POOL_SIZE 1024 * 1024
Listing *listings = NULL;
Listing **list_head = NULL;
static Arena arena = {0};
void init_directory_arena() {
make_arena(POOL_SIZE, &arena);
list_head = &listings;
}
Listing *current = NULL;
static int compare_entries(const void *p1, const void *p2) {
Listing *l = current;
int idx1 = *(int*)p1;
int idx2 = *(int*)p2;
int mode1 = l->stats[idx1].st_mode & S_IFDIR;
int mode2 = l->stats[idx2].st_mode & S_IFDIR;
if (mode1 && !mode2)
return -1;
if (!mode1 && mode2)
return 1;
return strcmp(l->table[idx1], l->table[idx2]);
}
void sort_entries(Listing *l, char *path) {
if (arena.idx % sizeof(char*))
allocate(&arena, sizeof(char*) - (arena.idx % sizeof(char*)));
l->index = (int*)allocate(&arena, l->n_entries * sizeof(int));
l->index[0] = 0;
l->table = (char**)allocate(&arena, l->n_entries * sizeof(char*));
l->table[0] = l->first;
int path_len = strlen(path);
strcpy(&path[path_len++], "/");
l->stats = (struct stat*)allocate(&arena, l->n_entries * sizeof(struct stat));
strcpy(&path[path_len], l->table[0]);
stat(path, &l->stats[0]);
for (int i = 1; i < l->n_entries; i++) {
l->index[i] = i;
int sz = strlen(l->table[i-1]) + 1;
l->table[i] = &l->table[i-1][sz];
strcpy(&path[path_len], l->table[i]);
if (lstat(path, &l->stats[i]) != 0)
memset(&l->stats[i], 0, sizeof(struct stat));
}
current = l;
qsort(l->index, l->n_entries, sizeof(int), compare_entries);
}
void get_directory_entries(DIR *d, Listing *l) {
// This codebase depends on directory entries being laid out contiguously in memory,
// so we temporarily disable the ability for this arena to spill over to another pool.
arena.allow_overflow = false;
char *prev = NULL;
struct dirent *ent;
while ((ent = readdir(d))) {
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
continue;
int ent_sz = strlen(ent->d_name) + 1;
char *str = allocate(&arena, ent_sz);
if (!str) {
// If there was enough room for the string
if (arena.idx <= arena.pool_size - ent_sz)
break;
int allocated = prev == NULL ? 0 : (int)(prev - l->first) + strlen(prev) + 1;
int size = allocated + ent_sz;
// If this set of entries takes up more room than a single pool
if (size > arena.pool_size)
break;
// Copy this set of entries over to the next pool
find_next_pool(&arena);
if (l->first && prev) {
char *start = l->first;
l->first = allocate(&arena, size);
memcpy(l->first, start, allocated);
str = &l->first[allocated];
}
else
str = allocate(&arena, ent_sz);
}
strcpy(str, ent->d_name);
l->n_entries++;
if (!l->first)
l->first = str;
prev = str;
}
arena.allow_overflow = true;
}
bool list_directory(char *directory, int len, Listing *info) {
if (len < 0)
len = strlen(directory);
if (listings) {
Listing *l = listings;
while (l) {
if (l->name && !strncmp(directory, l->name, len)) {
memcpy(info, l, sizeof(Listing));
return true;
}
l = l->next;
}
}
DIR *d = NULL;
char path[4096];
if (directory[0] == '~') {
char *home = get_home_directory();
int home_len = strlen(home);
strcpy(path, home);
strncpy(&path[home_len], &directory[1], len);
path[home_len + len] = 0;
d = opendir(path);
}
else {
strncpy(path, directory, len);
path[len] = 0;
}
d = opendir(path);
if (!d) {
memset(info, 0, sizeof(Listing));
return false;
}
Listing *l = (Listing*)allocate(&arena, sizeof(Listing));
*list_head = l;
list_head = &l->next;
memset(l, 0, sizeof(Listing));
l->name = allocate(&arena, len + 1);
memcpy(l->name, directory, len + 1);
get_directory_entries(d, l);
closedir(d);
if (l->n_entries > 0)
sort_entries(l, path);
memcpy(info, l, sizeof(Listing));
return true;
}
char *home_dir = NULL;
char *get_home_directory() {
if (home_dir)
return home_dir;
char *home = getenv("HOME");
if (!home) {
struct passwd *ps = getpwuid(getuid());
if (ps)
home = ps->pw_dir;
}
if (home) {
int len = strlen(home);
home_dir = allocate(&arena, len + 1);
strcpy(home_dir, home);
}
return home_dir;
}
char *get_desugared_path(char *str, int len) {
char *home = NULL;
int home_len = 0;
int offset = 0;
if (str[0] == '~') {
home = get_home_directory();
home_len = strlen(home);
offset = 1;
}
len -= offset;
char *path = allocate(&arena, len + home_len + 1);
if (home) strcpy(path, home);
memcpy(&path[home_len], &str[offset], len);
path[home_len + len] = 0;
remove_backslashes(path, -1);
return path;
}
bool find_program(char *name, char **error_str) {
char *bin_path = getenv("PATH");
char *next = bin_path;
char file[200];
char *p = &bin_path[-1];
do {
p++;
if (*p != ':' && *p != 0)
continue;
char *path = next;
next = p + 1;
int len = p - path;
Listing list;
list_directory(path, len, &list);
bool found = false;
for (int i = 0; i < list.n_entries; i++) {
if (!strcmp(list.table[i], name)) {
found = true;
break;
}
}
if (!found) {
if (error_str) *error_str = "command not found: ";
continue;
}
if (error_str) *error_str = NULL;
sprintf(file, "%.*s/%s", len, path, name);
struct stat s;
if (stat(file, &s) != 0) {
if (error_str) *error_str = "command not found: ";
break;
}
if ((s.st_mode & S_IFMT) == S_IFDIR) {
if (error_str) *error_str = "not a regular file: ";
break;
}
if ((s.st_mode & S_IXUSR) == 0) {
if (error_str) *error_str = "missing execute permission: ";
break;
}
return true;
} while (*p);
return false;
}