#include <config.h>
#include <sys/stat.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sudoers.h>
#include <redblack.h>
static struct rbtree *canon_cache;
struct cache_item {
unsigned int refcnt;
char *pathname;
char resolved[];
};
static int
compare(const void *v1, const void *v2)
{
const struct cache_item *ci1 = (const struct cache_item *)v1;
const struct cache_item *ci2 = (const struct cache_item *)v2;
return strcmp(ci1->pathname, ci2->pathname);
}
#define resolved_to_item(_r) ((struct cache_item *)((_r) - offsetof(struct cache_item, resolved)))
static void
canon_path_free_item(void *v)
{
struct cache_item *item = v;
debug_decl(canon_path_free_item, SUDOERS_DEBUG_UTIL);
if (--item->refcnt == 0)
free(item);
debug_return;
}
void
canon_path_free(char *resolved)
{
debug_decl(canon_path_free, SUDOERS_DEBUG_UTIL);
if (resolved != NULL)
canon_path_free_item(resolved_to_item(resolved));
debug_return;
}
void
canon_path_free_cache(void)
{
debug_decl(canon_path_free_cache, SUDOERS_DEBUG_UTIL);
if (canon_cache != NULL) {
rbdestroy(canon_cache, canon_path_free_item);
canon_cache = NULL;
}
debug_return;
}
char *
canon_path(const char *inpath)
{
size_t item_size, inlen, reslen = 0;
char *resolved, resbuf[PATH_MAX];
struct cache_item key, *item;
struct rbnode *node = NULL;
debug_decl(canon_path, SUDOERS_DEBUG_UTIL);
if (canon_cache == NULL) {
canon_cache = rbcreate(compare);
if (canon_cache == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
debug_return_str(NULL);
}
} else {
key.pathname = (char *)inpath;
if ((node = rbfind(canon_cache, &key)) != NULL) {
item = node->data;
goto done;
}
}
if (*inpath == '\0')
resolved = (char *)"/";
else
resolved = realpath(inpath, resbuf);
inlen = strlen(inpath);
item_size = sizeof(*item) + inlen + 2;
if (resolved != NULL) {
reslen = strlen(resolved);
item_size += reslen;
}
item = malloc(item_size);
if (item == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
debug_return_str(NULL);
}
if (resolved != NULL)
memcpy(item->resolved, resolved, reslen);
item->resolved[reslen] = '\0';
item->pathname = item->resolved + reslen + 1;
memcpy(item->pathname, inpath, inlen);
item->pathname[inlen] = '\0';
item->refcnt = 1;
switch (rbinsert(canon_cache, item, NULL)) {
case 1:
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"path \"%s\" already exists in the cache", inpath);
item->refcnt = 0;
break;
case -1:
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"can't cache path \"%s\"", inpath);
item->refcnt = 0;
break;
}
done:
if (item->refcnt != 0) {
sudo_debug_printf(SUDO_DEBUG_DEBUG,
"%s: path %s -> %s (%s)", __func__, inpath,
item->resolved[0] ? item->resolved : "NULL",
node ? "cache hit" : "cached");
}
if (item->resolved[0] == '\0') {
if (item->refcnt == 0)
free(item);
debug_return_str(NULL);
}
item->refcnt++;
debug_return_str(item->resolved);
}