summaryrefslogtreecommitdiff
path: root/plugins/static/mime.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/static/mime.c')
-rw-r--r--plugins/static/mime.c214
1 files changed, 214 insertions, 0 deletions
diff --git a/plugins/static/mime.c b/plugins/static/mime.c
new file mode 100644
index 0000000..6be5078
--- /dev/null
+++ b/plugins/static/mime.c
@@ -0,0 +1,214 @@
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdatomic.h>
+#include <pthread.h>
+#include <sys/stat.h>
+#include "memory.h"
+#include "util.h"
+#include "mime.h"
+#include "hashtable.h"
+#include "buffer.h"
+
+
+static char* process_read(const char *cmd, char **argv) {
+ const int error_exit_status = 97;
+
+ int outpipe[2];
+ if (pipe(outpipe) < 0) {
+ perror("pipe");
+ exit(1);
+ }
+
+ pid_t pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ exit(1);
+ }
+
+ if (pid == 0) {
+ close(outpipe[0]);
+ dup2(outpipe[1], STDOUT_FILENO);
+ execvp(cmd, argv);
+ perror("execvp");
+ exit(error_exit_status);
+ }
+
+ close(outpipe[1]);
+
+ struct buffer buffer = buffer_make(256);
+ char tempbuf[256];
+
+ while (true) {
+ ssize_t nr = read(outpipe[0], tempbuf, sizeof tempbuf);
+ if (nr < 0) {
+ if (errno == EINTR) continue;
+ perror("read");
+ break;
+ }
+
+ if (nr == 0) break; // eof
+
+ buffer_append_mem(&buffer, tempbuf, nr);
+ }
+
+ close(outpipe[0]);
+
+ while (true) {
+ int status;
+ if (waitpid(pid, &status, 0) < 0) {
+ if (errno == EINTR) continue;
+ perror("waitpid");
+ return NULL;
+ }
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) == error_exit_status) {
+ buffer_free(buffer);
+ return NULL;
+ } else if (WEXITSTATUS(status) == 0) {
+ return buffer.buf;
+ } else {
+ fprintf(stderr, "Command '%s' had unexpected exit code %d!\n", cmd, WEXITSTATUS(status));
+ return NULL;
+ }
+ }
+ }
+}
+
+static struct timespec get_file_mtime(const char *path) {
+ struct stat st;
+ memset(&st, 0, sizeof st);
+ if (stat(path, &st) < 0) {
+ return (struct timespec){.tv_sec = -1, .tv_nsec = -1};
+ }
+
+#if defined(__LINUX__)
+ return st.st_mtim;
+#elif defined(__APPLE__)
+ return st.st_mtimespec;
+#else
+#error Unknown operating system, how does the stat() data structure look?
+#endif
+}
+
+static void strip_string(char *str) {
+ size_t len = strlen(str);
+ size_t idx = 0;
+ while (str[idx] && isspace(str[idx])) idx++;
+ if (idx > 0) memmove(str, str + idx, len - idx + 1);
+ len -= idx;
+ while (len > 0 && isspace(str[len - 1])) str[--len] = '\0';
+}
+
+struct cache_entry {
+ char *mimetype;
+ struct timespec mtime;
+};
+
+HASHTABLE_DEFINE(mht_, struct cache_entry)
+
+struct mime_storage {
+ struct mht_table *mime_ht;
+};
+static struct mime_storage global_storage = {NULL};
+static pthread_mutex_t global_storage_mutex;
+static struct atomic_flag global_storage_inited = ATOMIC_FLAG_INIT;
+
+// Cannot be atomic_flag's because we need to load them without changing them
+static atomic_bool xdg_works = ATOMIC_VAR_INIT(true);
+static atomic_bool file_works = ATOMIC_VAR_INIT(true);
+
+#define WITH_MIME_STORAGE_LOCK(...) \
+ do { \
+ PTHREAD_CHECK(pthread_mutex_lock, &global_storage_mutex); \
+ {__VA_ARGS__}; \
+ PTHREAD_CHECK(pthread_mutex_unlock, &global_storage_mutex); \
+ } while (0)
+
+void mime_init(void) {
+ if (!atomic_flag_test_and_set(&global_storage_inited)) {
+ PTHREAD_CHECK(pthread_mutex_init, &global_storage_mutex, NULL);
+ global_storage.mime_ht = malloc(1, struct mht_table);
+ *global_storage.mime_ht = mht_make();
+ }
+}
+
+static char* mime_detect_xdg(const char *path) {
+ char *pathdup = strdup(path);
+ char *argv[5] = { "xdg-mime", "query", "filetype", pathdup, NULL };
+ char *output = process_read("xdg-mime", argv);
+ free(pathdup);
+ if (output) strip_string(output);
+ return output;
+}
+
+static char* mime_detect_file(const char *path) {
+ char *pathdup = strdup(path);
+ char *argv[6] = { "file", "--brief", "--dereference", "--mime-type", pathdup, NULL };
+ char *output = process_read("file", argv);
+ free(pathdup);
+ if (output) strip_string(output);
+ return output;
+}
+
+static char* mime_detect_perform(const char *path) {
+ // Try 'file' first, because xdg-mime calls that on my system
+ if (atomic_load(&file_works)) {
+ fprintf(stderr, "mime: trying 'file'...\n");
+ char *typ = mime_detect_file(path);
+ if (typ) return typ;
+ atomic_store(&file_works, false);
+ fprintf(stderr, "mime: 'file' doesn't work\n");
+ }
+
+ if (atomic_load(&xdg_works)) {
+ fprintf(stderr, "mime: trying 'xdg'...\n");
+ char *typ = mime_detect_xdg(path);
+ if (typ) return typ;
+ atomic_store(&xdg_works, false);
+ fprintf(stderr, "mime: 'xdg-mime' doesn't work\n");
+ }
+
+ fprintf(stderr, "No working mimetype detection! Please fix " __FILE__ " for this platform!\n");
+
+ return NULL;
+}
+
+const char* mime_detect(const char *path) {
+ struct timespec now_mtime = get_file_mtime(path);
+ if (now_mtime.tv_sec < 0) return NULL;
+
+ char *typ = NULL;
+
+ WITH_MIME_STORAGE_LOCK(
+ struct cache_entry *entry = mht_find(global_storage.mime_ht, path);
+ if (entry) {
+ if (now_mtime.tv_sec > entry->mtime.tv_sec ||
+ (now_mtime.tv_sec == entry->mtime.tv_sec &&
+ now_mtime.tv_nsec > entry->mtime.tv_nsec)) {
+ fprintf(stderr, "Erasing <%s>\n", path);
+ mht_erase(global_storage.mime_ht, path);
+ } else {
+ typ = entry->mimetype;
+ }
+ }
+ );
+
+ if (typ) return typ;
+
+ typ = mime_detect_perform(path);
+ if (typ) {
+ struct cache_entry entry;
+ entry.mimetype = typ;
+ entry.mtime = now_mtime;
+ WITH_MIME_STORAGE_LOCK(
+ fprintf(stderr, "Inserting <%s> -> <%s>\n", path, typ);
+ mht_insert(global_storage.mime_ht, path, entry);
+ );
+ }
+ return typ;
+}