sites

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit 47adf72bc2b16430715e7fd771b6ad276f296562
parent b9bc232439d3003d881d642fd1598925c7520cf0
Author: Elmo Todurov <todurov@gmail.com>
Date:   Wed, 19 May 2010 13:32:21 +0300

Added dmenu_path.c under dmenu patches, also changed dmenu index to point to it.
Diffstat:
Mtools.suckless.org/dmenu/index.md | 1+
Atools.suckless.org/dmenu/patches/dmenu_path.c | 364+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 365 insertions(+), 0 deletions(-)

diff --git a/tools.suckless.org/dmenu/index.md b/tools.suckless.org/dmenu/index.md @@ -22,3 +22,4 @@ Links ----- * [Man page](http://man.suckless.org/tools/1/dmenu) +* Rewrite of dmenu_path in C is under [Patches](http://tools.suckless.org/dmenu/patches). It is supposedly around 2-4 times faster on cache hits and around 10-20 times faster on cache misses. diff --git a/tools.suckless.org/dmenu/patches/dmenu_path.c b/tools.suckless.org/dmenu/patches/dmenu_path.c @@ -0,0 +1,364 @@ +/* + * dmenu_path + * This program dumps all executables in $PATH to stdout. + * It uses the file $HOME/.dmenu_cache as a cache. + * + * This program is released under the X11 license (sometimes known as the MIT + * license), which basically means that you can do whatever you want with it. + * + * Sorry for the hairy code. I didn't know how to make it simpler and still + * as generic. Valgrind claims it's correct and doesn't leak, but I'm sure you + * can find a couple of ways to make it crash. + * + * I'd appreciate, but I don't require, that you mail me any improvements or + * comments on the code. + * + * Elmo Todurov todurov+dmenu@gmail.com + * 2010-05-19 09:55 + */ + +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <malloc.h> +#include <unistd.h> +#include <dirent.h> +#include <assert.h> + +static uid_t uid; +static gid_t gid; +static char* cache_path; + +static int uptodate(char** paths) +{ + struct stat dirstat; + time_t cache_time; + char** dirs; + + if (stat(cache_path, &dirstat)) + { + if (errno != ENOENT) + { + perror("stat"); + } + return 0; + } + cache_time = dirstat.st_mtime; + + dirs = paths; + while (*dirs != NULL) + { + if (stat(*dirs, &dirstat)) + { + if (errno != ENOENT) + perror("stat"); + return 0; + } + + if (cache_time < dirstat.st_mtime) + return 0; + + dirs++; + } + + return 1; +} + +static char* get_cache_path() +{ + const char* home = getenv("HOME"); + char* path = (char*)malloc(strlen(home) + strlen("/.dmenu_cache") + 1); + strcpy(path, home); + strcat(path, "/.dmenu_cache"); + return path; +} + +static char* get_PATH() +{ + const char* path = getenv("PATH"); + char* copy_path; + if (path == NULL) + { + perror("getenv"); + exit(1); + } + + copy_path = strdup(path); + return copy_path; +} + +static void split_PATH(char* PATH, char*** dirs_in) +{ + char** dirs; + const char* dir = strtok(PATH, ":"); + size_t i = 0; + size_t allocated = 10; + dirs = (char**)malloc(sizeof(char*) * allocated); + + while (dir != NULL) + { + dirs[i] = (char*)malloc(strlen(dir) + 1); + strcpy(dirs[i], dir); + dir = strtok(NULL, ":"); + i++; + if (i == allocated) + { + allocated *= 2; + dirs = (char**)realloc(dirs, allocated * sizeof(char**)); + } + } + dirs[i] = NULL; + + *dirs_in = dirs; +} + +static void free_charpp(char** in) +{ + char** ptr = in; + while (*ptr != NULL) + { + free(*ptr); + ptr++; + } + free(in); +} + +static void fprint_charpp(char** in, FILE* out) +{ + char** ptr = in; + while (*ptr != NULL) + { + fputs(*ptr, out); + fputc('\n', out); + ptr++; + } +} + +static size_t count_charpp(char** in) +{ + char** ptr = in; + size_t count = 0; + while (*ptr != NULL) + { + count++; + ptr++; + } + return count; +} + + +static int isexecutable(const char* dir, const char* file) +{ + char* fname; + struct stat st; + int ret; + int success; + gid_t* grouplist; + fname = (char*)malloc(strlen(dir) + strlen(file) + 2); + strcpy(fname, dir); + strcat(fname, "/"); + strcat(fname, file); + + ret = stat(fname, &st); + free(fname); + if (ret != 0) + return 0; + if (!S_ISREG(st.st_mode)) /* this catches regular files and symlinks as well */ + return 0; + if ((st.st_uid == uid && (st.st_mode & S_IXUSR) != 0) + || (st.st_uid != uid && st.st_gid != gid && (st.st_mode & S_IXOTH) != 0)) + { + return 1; + } + + /* check secondary groups */ + if (st.st_mode & S_IXGRP) + { + success = 0; + ret = getgroups(0, 0); + grouplist = (gid_t*)malloc(sizeof(gid_t) * ret); + ret = getgroups(ret, grouplist); + while (ret != 0) + { + ret--; + if (st.st_uid != uid /* for group to match, user must not match. */ + && st.st_gid == grouplist[ret]) + { + success = 1; + break; + } + } + free(grouplist); + return success; + } + + return 0; +} + +static void add(const char* prog, char*** progs) +{ + static unsigned progs_allocated = 0; + static unsigned progs_used = 0; + + if (progs_used == progs_allocated) + { + progs_allocated = progs_allocated == 0 ? 256 : progs_allocated * 2; + *progs = (char**)realloc(*progs, sizeof(char*) * progs_allocated); + } + + if (prog != NULL) + { + (*progs)[progs_used] = (char*)malloc(strlen(prog) + 1); + strcpy((*progs)[progs_used], prog); + progs_used++; + } + else + { + (*progs)[progs_used] = NULL; + } +} + +static void refresh_path(const char* path, char*** progs) +{ + DIR* dirp = opendir(path); + struct dirent* dp; + + if (dirp == NULL) + { + if (errno != ENOENT) + perror("opendir"); + return; + } + + dp = readdir(dirp); + while (dp != NULL) + { + if (isexecutable(path, dp->d_name)) + add(dp->d_name, progs); + dp = readdir(dirp); + } + closedir(dirp); +} + +static int compare(const void* a, const void* b) +{ + return strcmp(*(const char**)a, *(const char**)b); +} + +static void sort(char*** progs) +{ + qsort(*progs, count_charpp(*progs), sizeof(*progs), compare); +} + +static void uniq(char*** progs) +{ + char** progs_new = (char**)malloc(sizeof(char*) * (count_charpp(*progs) + 1)); + char** ptr_1 = *progs; + char** ptr_2 = ptr_1 + 1; + unsigned long i = 0;; + while (*ptr_1 != NULL) + { + while (*ptr_2 != NULL && strcmp(*ptr_1, *ptr_2) == 0) + { + free(*ptr_2); + ptr_2++; + } + progs_new[i] = *ptr_1; + i++; + ptr_1 = ptr_2; + ptr_2++; + } + progs_new[i] = NULL; + + free(*progs); + + *progs = progs_new; +} + +static void refresh(char** paths) +{ + char** progs = NULL; + FILE* out; + while (*paths != NULL) + { + refresh_path(*paths, &progs); + paths++; + } + add(NULL, &progs); + + out = fopen(cache_path, "w"); + if (out == NULL) + { + perror("fopen"); + exit(1); + } + + sort(&progs); + uniq(&progs); + fprint_charpp(progs, out); + fprint_charpp(progs, stdout); + + free_charpp(progs); + fclose(out); +} + +static void cat() +{ + FILE* cache = fopen(cache_path, "r"); + char buf[4096]; + struct stat cachestat; + size_t still_unread; + size_t chunk; + if (cache == NULL) + { + perror("fopen cache"); + exit(1); + } + + if (stat(cache_path, &cachestat)) + { + perror("stat"); + exit(1); + } + still_unread = cachestat.st_size; + + while (still_unread > 0) + { + chunk = fread(buf, 1, 4096, cache); + still_unread -= chunk; + fwrite(buf, 1, chunk, stdout); + } + fclose(cache); +} + +int main(int argc, char *argv[]) +{ + char* PATH = get_PATH(); + char** paths = NULL; + uid = getuid(); + gid = getgid(); + if (uid == 0 || gid == 0) + { + puts("I refuse to run as root!"); + return 1; + } + + cache_path = get_cache_path(); + split_PATH(PATH, &paths); + free(PATH); + sort(&paths); + uniq(&paths); + + if ((argc == 2 && strcmp(argv[1], "--force") == 0) + || !uptodate(paths)) + refresh(paths); + else + cat(); + + free_charpp(paths); + free(cache_path); + + return 0; +}