changeset: 518:1acb3ed872be
tag: tip
user: Elmo Todurov <todurov_AT_gmail.com>
date: Wed May 19 13:32:21 2010 +0300
files: tools.suckless.org/dmenu/index.md tools.suckless.org/dmenu/patches/dmenu_path.c
description:
Added dmenu_path.c under dmenu patches, also changed dmenu index to point to it.
diff -r 74c6e888e541 -r 1acb3ed872be tools.suckless.org/dmenu/index.md
--- a/tools.suckless.org/dmenu/index.md Thu May 13 03:08:59 2010 -0400
+++ b/tools.suckless.org/dmenu/index.md Wed May 19 13:32:21 2010 +0300
_AT_@ -22,3 +22,4 @@
-----
* [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 -r 74c6e888e541 -r 1acb3ed872be tools.suckless.org/dmenu/patches/dmenu_path.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tools.suckless.org/dmenu/patches/dmenu_path.c Wed May 19 13:32:21 2010 +0300
_AT_@ -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_AT_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;
+}
Received on Wed May 19 2010 - 12:33:19 CEST