/* dirnav.c - SDL2/OpenGL Image Viewer
* by Pegasus Epsilon <pegasus@pimpninjas.org>
* (C)2014 Distribute Unmodified
*
* Massive shame we can't do OOP in C -- Waiiiit a second...
*
* You're going to need libffcall.
* https://www.gnu.org/software/libffcall/
*
*/
#define _XOPEN_SOURCE 600
#include <stdlib.h> /* posix_memalign(), malloc() */
#include <string.h> /* strcpy(), strdup() */
#include <libgen.h> /* dirname() */
#include <dirent.h> /* DIR, dirent, opendir(), readdir(),
* telldir(), seekdir(), closedir()
*/
#include <unistd.h> /* sysconf() */
#include <sys/mman.h> /* mprotect() */
#include <callback.h> /* alloc_callback() */
#define new(x) malloc(sizeof(x))
typedef int (*callback) ();
typedef struct dirnav {
/* public methods */
const char *(*first) (void);
const char *(*previous) (void);
const char *(*current) (void);
const char *(*next) (void);
const char *(*seek) (const char *const);
const char *(*last) (void);
int (*free) (void);
char *path;
/* private data */
char *file;
DIR *dir;
struct dirent *entry;
long ptr;
struct dirent *(*sync_read) (void);
}* dirnav;
/* strdup before and after dirname to make it 100% safe */
/* don't forget to free! */
char *strdup_dirname (const char *const path) {
char *tmp, *dir;
dir = strdup(dirname(tmp = strdup(path)));
free(tmp);
return dir;
}
/* dispose of a block of immutable memory */
void const_free (char *str) {
if (!str) return;
mprotect(str, strlen(str) + 1, PROT_READ | PROT_WRITE);
free(str);
}
/* copy a mutable string into immutable memory */
/* ATTEMPTING TO MODIFY IMMUTABLE MEMORY WILL CAUSE A SEGFAULT */
const char *const_strdup (char *src) {
char *dst;
size_t len = strlen(src) + 1;
if (posix_memalign((void **)&dst, (size_t)sysconf(_SC_PAGE_SIZE), len))
return NULL;
strcpy(dst, src);
mprotect(dst, len, PROT_READ);
return dst;
}
const struct dirent *dirnav_sync_read (struct dirnav *dirnav) {
long ptr = telldir(dirnav->dir);
struct dirent *entry = readdir(dirnav->dir);
if (entry) {
dirnav->entry = entry;
dirnav->ptr = ptr;
}
return entry;
}
const char *dirnav_first (struct dirnav *dirnav) {
rewinddir(dirnav->dir);
dirnav->sync_read();
return dirnav->current();
}
const char *dirnav_previous (struct dirnav *dirnav) {
long previous;
const long this = dirnav->ptr;
rewinddir(dirnav->dir);
/* there are no files before the first one */
if (this == telldir(dirnav->dir)) return NULL;
do previous = dirnav->ptr;
while (dirnav->sync_read() && this != dirnav->ptr);
/* there are no files before a file that does not exist */
if (this != dirnav->ptr) return NULL;
seekdir(dirnav->dir, previous);
dirnav->sync_read();
return dirnav->current();
}
/* returns full path to current file in a string constant */
const char *dirnav_current (struct dirnav *dirnav) {
char *fullpath;
fullpath = strcat(strcat(strcpy(malloc(
strlen(dirnav->path) + 1 + strlen(dirnav->entry->d_name) + 1
), dirnav->path), "/"), dirnav->entry->d_name);
const_free(dirnav->file);
dirnav->file = (char *const)const_strdup(fullpath);
free(fullpath);
return dirnav->file;
}
const char *dirnav_next (struct dirnav *dirnav) {
return dirnav->sync_read() ? dirnav->current() : NULL;
}
const char *dirnav_seek (struct dirnav *dirnav, va_alist list) {
const char *tgt;
va_start_ptr(list, const char *const);
tgt = (const char *)va_arg_ptr(list, const char *);
rewinddir(dirnav->dir);
while (dirnav->sync_read() && strcmp(dirnav->entry->d_name, tgt));
va_return_ptr(list, const char *const, tgt);
return dirnav->current();
}
const char *dirnav_last (struct dirnav *dirnav) {
while (dirnav->sync_read()); /* fast forward */
return dirnav->current();
}
int dirnav_free (struct dirnav *dirnav) {
int ret = closedir(dirnav->dir);
free_callback((callback)dirnav->first);
free_callback((callback)dirnav->previous);
free_callback((callback)dirnav->current);
free_callback((callback)dirnav->next);
free_callback((callback)dirnav->seek);
free_callback((callback)dirnav->last);
free_callback((callback)dirnav->free);
free_callback((callback)dirnav->sync_read);
mprotect(dirnav->file, strlen(dirnav->path) + 1, PROT_READ | PROT_WRITE);
free(dirnav->file);
free(dirnav->path);
free(dirnav);
return ret;
}
struct dirnav *new_dirnav (const char *const filename) {
/* allocate a new opaque struct */
struct dirnav *dirnav = new(struct dirnav);
/* cache the path in the public struct */
/* yeah, this shouldn't break the opendir below, yet it does.
* I guess glibc doesn't really give a fuck about const variables.
dirnav->path = const_strdup(tmp = strdup_dirname(filename));
free(tmp);
* whatever, we'll do this instead:
*/
dirnav->path = strdup_dirname(filename);
/* open directory */
dirnav->dir = opendir(dirnav->path);
/* init dirnav->file */
if (posix_memalign((void **)&(dirnav->file), (size_t)sysconf(_SC_PAGE_SIZE), 0))
return NULL;
/* load up methods */
dirnav->first = (const char *(*) (void))alloc_callback(&dirnav_first, dirnav);
dirnav->previous = (const char *(*) (void))alloc_callback(&dirnav_previous, dirnav);
dirnav->current = (const char *(*) (void))alloc_callback(&dirnav_current, dirnav);
dirnav->next = (const char *(*) (void))alloc_callback(&dirnav_next, dirnav);
dirnav->seek = (const char *(*) (const char *const))alloc_callback(&dirnav_seek, dirnav);
dirnav->last = (const char *(*) (void))alloc_callback(&dirnav_last, dirnav);
dirnav->free = (int (*) (void))alloc_callback(&dirnav_free, dirnav);
dirnav->sync_read = (struct dirent *(*) (void))alloc_callback(&dirnav_sync_read, dirnav);
/* kick things off */
dirnav->sync_read();
return (struct dirnav *) dirnav;
}