#include #include #include #include #include #include #include #include #include #include #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; }