Okay, here's the new version.
Changes:
* no root check
* -f instead of --force
* less mallocs, less copying strings around (no noticeable change in
speed, though)
* die()
Elmo Todurov
/*
* 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+dm...@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 void die(const char* msg)
{
perror(msg);
exit(EXIT_FAILURE);
}
static char* get_cache_path()
{
const char* home;
char* path;
home = getenv("HOME");
if (home == NULL)
die("getenv");
path = (char*)malloc(strlen(home) + strlen("/.dmenu_cache") + 1);
if (path == NULL)
die("malloc");
strcpy(path, home);
strcat(path, "/.dmenu_cache");
return path;
}
static char* get_PATH()
{
const char* path = getenv("PATH");
char* copy_path;
if (path == NULL)
die("getenv");
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);
if (dirs == NULL)
die("malloc");
while (dir != NULL)
{
dirs[i] = (char*)malloc(strlen(dir) + 1);
if (dirs[i] == NULL)
die("malloc");
strcpy(dirs[i], dir);
dir = strtok(NULL, ":");
i++;
if (i == allocated)
{
allocated *= 2;
dirs = (char**)realloc(dirs, allocated * sizeof(char**));
if (dirs == NULL)
die("realloc");
}
}
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* fname)
{
struct stat st;
int ret;
int success;
gid_t* grouplist;
ret = stat(fname, &st);
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);
if (grouplist == NULL)
die("malloc");
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 (*progs == NULL)
die("realloc");
}
if (prog != NULL)
{
(*progs)[progs_used] = (char*)malloc(strlen(prog) + 1);
if ((*progs)[progs_used] == NULL)
die("malloc");
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;
char fullpath[PATH_MAX];
char* end;
strcpy(fullpath, path);
end = fullpath + strlen(fullpath);
if (dirp == NULL)
{
if (errno != ENOENT)
perror("opendir");
return;
}
dp = readdir(dirp);
while (dp != NULL)
{
strcat(end, "/");
strcpy(end + 1, dp->d_name);
if (isexecutable(fullpath))
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** ptr_1 = *progs;
char** ptr_2 = ptr_1 + 1;
unsigned long i = 0;;
progs_new = (char**)malloc(sizeof(char*) * (count_charpp(*progs) + 1));
if (progs_new == NULL)
die("malloc");
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)
die("fopen");
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)
die("fopen");
if (stat(cache_path, &cachestat))
die("stat");
still_unread = cachestat.st_size;
while (still_unread > 0)
{
chunk = fread(buf, 1, sizeof(buf), cache);
still_unread -= chunk;
fwrite(buf, 1, chunk, stdout);
}
fclose(cache);
}
int main(int argc, char *argv[])
{
char* PATH;
char** paths = NULL;
PATH = get_PATH();
uid = getuid();
gid = getgid();
cache_path = get_cache_path();
split_PATH(PATH, &paths);
free(PATH);
sort(&paths);
uniq(&paths);
if ((argc == 2 && strcmp(argv[1], "-f") == 0)
|| !uptodate(paths))
refresh(paths);
else
cat();
free_charpp(paths);
free(cache_path);
return 0;
}