summaryrefslogtreecommitdiff
path: root/src/paths.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/paths.c')
-rw-r--r--src/paths.c1030
1 files changed, 1030 insertions, 0 deletions
diff --git a/src/paths.c b/src/paths.c
new file mode 100644
index 0000000..6a253ca
--- /dev/null
+++ b/src/paths.c
@@ -0,0 +1,1030 @@
+#include "common.h"
+
+#include "paths.h"
+#include "ref.h"
+#include "thread.h"
+#include "safe_fifo.h"
+#include "strutil.h"
+
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define SEARCH_CACHE_SIZE (2)
+
+typedef struct search_t
+{
+ const char* dir;
+ const char** dirs;
+ char* match;
+ const char* match_name;
+ bool plain;
+ paths_find_callback_t find_callback;
+ void* userdata;
+} search_t;
+
+typedef struct searchargs_t
+{
+ bool quit;
+ safe_fifo_t* queue;
+ thread_lock_t* lock;
+ search_t* cache[SEARCH_CACHE_SIZE];
+} searchargs_t;
+
+struct paths_t
+{
+ REF_DECLARE();
+ char* user_data;
+ char* user_config;
+ char* user_cache;
+ char* user_runtime;
+ char** search_data;
+ char** search_config;
+
+ searchargs_t* search;
+#ifdef DEBUG
+ thread_t* searcher;
+#endif
+};
+
+static void free_list(char** list);
+static void free_search_data(search_t* search);
+static bool paths_init(paths_t* paths);
+static char* build_path(paths_t* paths, paths_source_t source, ...);
+static bool create_dir(paths_t* paths, paths_source_t source,
+ const char* addon, size_t addon_len);
+static void* searcher(void* _args);
+static search_t* get_search(searchargs_t* args);
+static void put_search(searchargs_t* args, search_t* search);
+
+paths_t* paths_new(void)
+{
+ paths_t* paths = calloc(1, sizeof(paths_t));
+ if (!paths)
+ {
+ return NULL;
+ }
+ if (!REF_INIT(paths))
+ {
+ free(paths);
+ return NULL;
+ }
+ for (;;)
+ {
+ paths->search = calloc(1, sizeof(searchargs_t));
+ if (!paths->search)
+ {
+ break;
+ }
+ paths->search->lock = thread_lock_new();
+ if (!paths->search->lock)
+ {
+ break;
+ }
+ paths->search->queue = safe_fifo_new();
+ if (!paths->search->queue)
+ {
+ break;
+ }
+ safe_fifo_ref(paths->search->queue);
+#ifdef DEBUG
+ paths->searcher = thread_joinable_new(searcher, paths->search);
+ if (!paths->searcher)
+ {
+ safe_fifo_unref(paths->search->queue);
+ break;
+ }
+#else
+ if (!thread_new(searcher, paths->search))
+ {
+ safe_fifo_unref(paths->search->queue);
+ break;
+ }
+#endif
+ if (!paths_init(paths))
+ {
+ break;
+ }
+ return paths;
+ }
+ paths_unref(paths);
+ return NULL;
+}
+
+void paths_ref(paths_t* paths)
+{
+ assert(paths);
+ REF_INC(paths);
+}
+
+void paths_unref(paths_t* paths)
+{
+ if (paths && REF_DEC(paths))
+ {
+ if (paths->search)
+ {
+ if (paths->search->queue)
+ {
+ search_t* search = get_search(paths->search);
+ paths->search->quit = true;
+ if (search)
+ {
+ memset(search, 0, sizeof(search_t));
+ safe_fifo_push(paths->search->queue, search);
+ }
+ safe_fifo_unref(paths->search->queue);
+ }
+#ifdef DEBUG
+ thread_join(paths->searcher);
+#endif
+ }
+ free(paths->user_data);
+ free(paths->user_config);
+ free(paths->user_cache);
+ free(paths->user_runtime);
+ free_list(paths->search_data);
+ free_list(paths->search_config);
+ REF_FREE(paths);
+ free(paths);
+ }
+}
+
+const char* paths_user_dir(paths_t* paths, paths_source_t source)
+{
+ assert(paths);
+ switch (source)
+ {
+ case PATHS_DATA:
+ return paths->user_data;
+ case PATHS_CONFIG:
+ return paths->user_config;
+ case PATHS_CACHE:
+ return paths->user_cache;
+ case PATHS_RUNTIME:
+ return paths->user_runtime;
+ }
+ assert(false);
+ return NULL;
+}
+
+const char** paths_search_dirs(paths_t* paths, paths_source_t source)
+{
+ assert(paths);
+ switch (source)
+ {
+ case PATHS_DATA:
+ return (const char**)paths->search_data;
+ case PATHS_CONFIG:
+ return (const char**)paths->search_config;
+ case PATHS_CACHE:
+ case PATHS_RUNTIME:
+ return NULL;
+ }
+ assert(false);
+ return NULL;
+}
+
+struct paths_file_t
+{
+ paths_source_t source;
+ char* path, * target;
+ int fd;
+};
+
+paths_file_t* paths_write(paths_t* paths, paths_source_t source,
+ const char* filename, unsigned int flags, ...)
+{
+ paths_file_t* file;
+ int f;
+ const char* subpath;
+ assert(filename);
+ if (source == PATHS_RUNTIME && !paths->user_runtime)
+ {
+ errno = ENOENT;
+ return NULL;
+ }
+ if (filename[0] == '/')
+ {
+ filename++;
+ }
+ subpath = strrchr(filename, '/');
+ if (subpath)
+ {
+ if (!create_dir(paths, source, filename, subpath - filename))
+ {
+ return NULL;
+ }
+ }
+ else
+ {
+ if (!create_dir(paths, source, NULL, 0))
+ {
+ return NULL;
+ }
+ }
+ file = calloc(1, sizeof(paths_file_t));
+ if (!file)
+ {
+ return NULL;
+ }
+ file->source = source;
+ if (source == PATHS_CONFIG &&
+ (((flags & PATHS_CREATE) && (flags & PATHS_TRUNCATE)) ||
+ ((flags & PATHS_CREATE) && (flags & PATHS_EXCLUSIVE))))
+ {
+ file->target = build_path(paths, source, filename, NULL);
+ if (!file->target)
+ {
+ free(file);
+ return NULL;
+ }
+ file->path = build_path(paths, source, ".#", filename, NULL);
+ }
+ else
+ {
+ file->path = build_path(paths, source, filename, NULL);
+ }
+ if (!file->path)
+ {
+ free(file->target);
+ free(file);
+ return NULL;
+ }
+ f = O_WRONLY;
+ if ((flags & PATHS_APPEND))
+ {
+ f |= O_APPEND;
+ }
+ if ((flags & PATHS_CREATE))
+ {
+ f |= O_CREAT;
+ if ((flags & PATHS_EXCLUSIVE))
+ {
+ f |= O_EXCL;
+ }
+ }
+ if ((flags & PATHS_TRUNCATE))
+ {
+ f |= O_TRUNC;
+ }
+#ifdef O_EXLOCK
+ f |= O_EXLOCK;
+#endif
+ if ((f & O_CREAT))
+ {
+ va_list args;
+ va_start(args, flags);
+ file->fd = open(file->path, f, va_arg(args, int));
+ va_end(args);
+ }
+ else
+ {
+ file->fd = open(file->path, f);
+ }
+ if (file->fd < 0)
+ {
+ free(file->path);
+ free(file->target);
+ free(file);
+ return NULL;
+ }
+#ifndef O_EXLOCK
+ for (;;)
+ {
+ if (flock(file->fd, LOCK_EX) == 0)
+ {
+ break;
+ }
+ if (errno != EINTR)
+ {
+ if ((flags & O_CREAT))
+ {
+ unlink(file->path);
+ }
+ close(file->fd);
+ free(file->path);
+ free(file->target);
+ free(file);
+ return NULL;
+ }
+ }
+#endif
+ return file;
+}
+
+ssize_t paths_file_write(paths_file_t* file,
+ const void* data, size_t size)
+{
+ size_t pos = 0;
+ assert(file);
+ assert(size == 0 || data);
+ while (pos < size)
+ {
+ ssize_t ret = write(file->fd, data + pos, size - pos);
+ if (ret < 0)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK ||
+ errno == EINTR)
+ {
+ /* Non fatal errors */
+ if (pos > 0)
+ {
+ return pos;
+ }
+ }
+ return -1;
+ }
+ if (ret == 0)
+ {
+ return pos;
+ }
+ pos += ret;
+ }
+ return size;
+}
+
+int paths_file_close(paths_file_t* file)
+{
+ assert(file);
+ if (close(file->fd))
+ {
+ return -1;
+ }
+ file->fd = -1;
+ if (file->target)
+ {
+ size_t len = strlen(file->target);
+ char* tmp = malloc(len + 2);
+ int ret;
+ if (!tmp)
+ {
+ return -1;
+ }
+ memcpy(tmp, file->target, len);
+ memcpy(tmp + len, "~", 2);
+ ret = rename(file->target, tmp);
+ free(tmp);
+ if (ret && errno == ENOENT)
+ {
+ int olderrno = errno;
+ /* Check if rename failed because target doesn't exist */
+ if (access(file->target, F_OK) == 0 ||
+ errno != ENOENT)
+ {
+ /* Unable to create backup */
+ errno = olderrno;
+ return -1;
+ }
+ }
+ if (rename(file->path, file->target))
+ {
+ return -1;
+ }
+ free(file->target);
+ }
+ free(file->path);
+ free(file);
+ return 0;
+}
+
+void paths_file_abort(paths_file_t* file)
+{
+ if (file)
+ {
+ if (file->fd >= 0)
+ {
+ close(file->fd);
+ }
+ unlink(file->path);
+ free(file->path);
+ free(file->target);
+ free(file);
+ }
+}
+
+bool create_dir(paths_t* paths, paths_source_t source, const char* addon,
+ size_t addon_len)
+{
+ const char* dir = paths_user_dir(paths, source);
+ char* tmp = NULL;
+ assert(dir);
+ assert(addon || !addon_len);
+ if (addon_len)
+ {
+ size_t dlen = strlen(dir);
+ tmp = malloc(dlen + 1 + addon_len + 1);
+ if (!tmp)
+ {
+ return false;
+ }
+ memcpy(tmp, dir, dlen);
+ tmp[dlen++] = '/';
+ memcpy(tmp + dlen, addon, addon_len);
+ tmp[dlen + addon_len] = '\0';
+ dir = tmp;
+ }
+ if (mkdir(dir, 0700))
+ {
+ free(tmp);
+ return errno == EEXIST;
+ }
+ free(tmp);
+ return true;
+}
+
+void free_list(char** list)
+{
+ if (list)
+ {
+ char** i;
+ for (i = list; *i; i++)
+ {
+ free(*i);
+ }
+ free(list);
+ }
+}
+
+typedef struct homedir_t
+{
+ char* path;
+ size_t len;
+} homedir_t;
+
+static char* get_userdir(const char* envvar, homedir_t* homedir,
+ const char* addon);
+static char** get_searchdirs(const char* envvar, const char* default_list);
+
+static bool valid_runtime(const char* path);
+
+bool paths_init(paths_t* paths)
+{
+ homedir_t homedir = { NULL, 0 };
+ paths->user_data = get_userdir("XDG_DATA_HOME", &homedir, "/.local/share");
+ paths->user_config = get_userdir("XDG_CONFIG_HOME", &homedir, "/.config");
+ paths->user_cache = get_userdir("XDG_CACHE_HOME", &homedir, "/.cache");
+ paths->user_runtime = get_userdir("XDG_RUNTIME_HOME", NULL, NULL);
+ free(homedir.path);
+ if (!paths->user_data || !paths->user_config || !paths->user_cache)
+ {
+ return false;
+ }
+ if (paths->user_runtime)
+ {
+ if (!valid_runtime(paths->user_runtime))
+ {
+ free(paths->user_runtime);
+ paths->user_runtime = NULL;
+ }
+ }
+ paths->search_data = get_searchdirs("XDG_DATA_DIRS",
+ "/usr/local/share:/usr/share");
+ if (!paths->search_data)
+ {
+ return false;
+ }
+ paths->search_config = get_searchdirs("XDG_CONFIG_DIRS", "/etc/xdg");
+ if (!paths->search_config)
+ {
+ return false;
+ }
+ return true;
+}
+
+static void cleanup_dir(char* path, size_t len)
+{
+ while (len > 1 && path[len - 1] == '/')
+ {
+ path[--len] = '\0';
+ }
+}
+
+static char* get_homedir(void)
+{
+ const char* data = getenv("HOME");
+ size_t len;
+ char* ret;
+ if (data && *data)
+ {
+ len = strlen(data);
+ ret = malloc(len + 1);
+ if (ret)
+ {
+ memcpy(ret, data, len + 1);
+ cleanup_dir(ret, len);
+ }
+ return ret;
+ }
+ for (;;)
+ {
+ long size = sysconf(_SC_GETPW_R_SIZE_MAX);
+ char* buf;
+ struct passwd entry, *result = NULL;
+ assert(size != -1);
+ if (size == -1)
+ {
+ break;
+ }
+ buf = malloc(size);
+ if (!buf)
+ {
+ break;
+ }
+ if (getpwuid_r(getuid(), &entry, buf, size, &result) != 0)
+ {
+ /* Error getting entry */
+ free(buf);
+ break;
+ }
+ if (!result)
+ {
+ /* No entry found */
+ free(buf);
+ break;
+ }
+ len = strlen(entry.pw_dir);
+ if (len == 0)
+ {
+ free(buf);
+ break;
+ }
+ ret = malloc(len + 1);
+ if (!ret)
+ {
+ free(buf);
+ return NULL;
+ }
+ memcpy(ret, entry.pw_dir, len + 1);
+ free(buf);
+ cleanup_dir(ret, len);
+ return ret;
+ }
+ /* When all else fails */
+ return strdup("/");
+}
+
+char* get_userdir(const char* envvar, homedir_t* homedir, const char* addon)
+{
+ const char* data = getenv(envvar);
+ size_t len;
+ char* ret;
+ assert(!addon || addon[0] == '/');
+ if (data && *data)
+ {
+ len = strlen(data);
+ ret = malloc(len + 1);
+ if (ret)
+ {
+ memcpy(ret, data, len + 1);
+ cleanup_dir(ret, len);
+ }
+ return ret;
+ }
+ if (!homedir && !addon)
+ {
+ return NULL;
+ }
+ assert(homedir && addon);
+ if (!homedir->path)
+ {
+ homedir->path = get_homedir();
+ if (!homedir->path)
+ {
+ return NULL;
+ }
+ homedir->len = strlen(homedir->path);
+ }
+ len = homedir->len + strlen(addon);
+ ret = malloc(len + 1);
+ if (ret)
+ {
+ memcpy(ret, homedir->path, homedir->len);
+ if (homedir->len > 1)
+ {
+ assert(homedir->path[homedir->len - 1] != '/');
+ memcpy(ret + homedir->len, addon, (len - homedir->len) + 1);
+ }
+ else
+ {
+ assert(homedir->path[0] == '/');
+ len--;
+ memcpy(ret + homedir->len, addon + 1, len - homedir->len);
+ ret[len] = '\0';
+ }
+ }
+ return ret;
+}
+
+static void cleanup_dirs(char** dirs)
+{
+ char** d;
+ for (d = dirs; *d; d++)
+ {
+ cleanup_dir(*d, strlen(*d));
+ }
+}
+
+static char** splitlist(const char* str)
+{
+ size_t count = 0, size = 2;
+ char** ret = calloc(size + 1, sizeof(char*));
+ const char* last = str, * pos;
+ if (!ret)
+ {
+ return NULL;
+ }
+ for (;;)
+ {
+ size_t len;
+ pos = strchr(last, ':');
+ if (!pos)
+ {
+ pos = last + strlen(last);
+ }
+
+ if (count == size)
+ {
+ size_t ns = size * 2;
+ char** tmp = realloc(ret, (ns + 1) * sizeof(char*));
+ if (!tmp)
+ {
+ free_list(ret);
+ return NULL;
+ }
+ memset(tmp + size, 0, (ns + 1 - size) * sizeof(char*));
+ ret = tmp;
+ size = ns;
+ }
+ len = pos - last;
+ ret[count] = strdup_len(last, len);
+ if (!ret[count])
+ {
+ free_list(ret);
+ return NULL;
+ }
+ count++;
+
+ if (*pos)
+ {
+ last = pos + 1;
+ }
+ else
+ {
+ break;
+ }
+ }
+ assert(!ret[count]);
+ return ret;
+}
+
+char** get_searchdirs(const char* envvar, const char* default_list)
+{
+ const char* data = getenv(envvar);
+ char** ret;
+ bool cleanup;
+ if (data && *data)
+ {
+ ret = splitlist(data);
+ cleanup = true;
+ }
+ else
+ {
+ ret = splitlist(default_list);
+ cleanup = false;
+ }
+ if (!ret)
+ {
+ return NULL;
+ }
+ if (cleanup)
+ {
+ cleanup_dirs(ret);
+ }
+ return ret;
+}
+
+bool valid_runtime(const char* path)
+{
+ struct stat buf;
+ if (stat(path, &buf) != 0)
+ {
+ return false;
+ }
+ if (buf.st_uid != getuid())
+ {
+ return false;
+ }
+ if ((buf.st_mode & 0777) != 0700)
+ {
+ return false;
+ }
+ return true;
+}
+
+char* build_path(paths_t* paths, paths_source_t source, ...)
+{
+ va_list args;
+ bool first = true;
+ dynstr_t* ret;
+ const char* base = paths_user_dir(paths, source), * part;
+ assert(base);
+ ret = dynstr_new_str(base);
+ if (!ret)
+ {
+ return NULL;
+ }
+ va_start(args, source);
+ while ((part = va_arg(args, const char*)))
+ {
+ if (first)
+ {
+ dynstr_appendc(ret, '/');
+ first = false;
+ }
+ dynstr_append(ret, part);
+ }
+ return dynstr_done(ret);
+}
+
+static bool need_glob(char* match)
+{
+ const char* magic_chars = "*?[]{}";
+ char* s;
+ bool need_unescape = false;
+ for (s = match; *s; s++)
+ {
+ if (*s == '\\')
+ {
+ s++;
+ need_unescape = true;
+ }
+ else if (strchr(magic_chars, *s))
+ {
+ return true;
+ }
+ }
+ if (need_unescape)
+ {
+ unescape(match);
+ }
+ return false;
+}
+
+void paths_find(paths_t* paths, paths_source_t source,
+ const char* match, paths_find_callback_t find_callback,
+ void* userdata)
+{
+ search_t* search;
+ assert(paths && match && find_callback);
+ search = get_search(paths->search);
+ search->dir = paths_user_dir(paths, source);
+ search->dirs = paths_search_dirs(paths, source);
+ search->match = strdup(match);
+ search->find_callback = find_callback;
+ search->userdata = userdata;
+ if (!search->match)
+ {
+ find_callback(userdata, NULL);
+ put_search(paths->search, search);
+ return;
+ }
+ if (*search->match == '/')
+ {
+ size_t len = strlen(search->match);
+ memmove(search->match, search->match + 1, len);
+ }
+ if (!*search->match)
+ {
+ find_callback(userdata, NULL);
+ put_search(paths->search, search);
+ return;
+ }
+ search->plain = !need_glob(search->match);
+ if (search->plain)
+ {
+ search->match_name = strrchr(search->match, '/');
+ if (search->match_name)
+ {
+ search->match_name++;
+ }
+ else
+ {
+ search->match_name = search->match;
+ }
+ }
+ safe_fifo_push(paths->search->queue, search);
+}
+
+search_t* get_search(searchargs_t* args)
+{
+ size_t i;
+ thread_lock_lock(args->lock);
+ for (i = 0; i < SEARCH_CACHE_SIZE; i++)
+ {
+ if (args->cache[i])
+ {
+ search_t* ret = args->cache[i];
+ args->cache[i] = NULL;
+ thread_lock_unlock(args->lock);
+ return ret;
+ }
+ }
+ thread_lock_unlock(args->lock);
+ return malloc(sizeof(search_t));
+}
+
+void put_search(searchargs_t* args, search_t* search)
+{
+ size_t i;
+ thread_lock_lock(args->lock);
+ for (i = 0; i < SEARCH_CACHE_SIZE; i++)
+ {
+ if (!args->cache[i])
+ {
+ args->cache[i] = search;
+ free_search_data(search);
+ thread_lock_unlock(args->lock);
+ return;
+ }
+ }
+ thread_lock_unlock(args->lock);
+ free_search_data(search);
+ free(search);
+}
+
+void free_search_data(search_t* search)
+{
+ free(search->match);
+}
+
+static bool search_dir(search_t* search, const char* dir)
+{
+ bool canceled = false;
+ if (search->plain)
+ {
+ DIR* dh;
+ struct dirent* d;
+ char* path = NULL;
+ size_t dirlen = strlen(dir);
+ size_t matchlen = strlen(search->match);
+ if (search->match == search->match_name)
+ {
+ path = malloc(dirlen + 1 + matchlen + 1);
+ if (!path)
+ {
+ return false;
+ }
+ memcpy(path, dir, dirlen + 1);
+ }
+ else
+ {
+ size_t subdirlen = search->match_name - 1 - search->match;
+ matchlen = strlen(search->match_name);
+ path = malloc(dirlen + 1 + subdirlen + 1 + matchlen + 1);
+ assert(search->match_name > search->match);
+ assert(search->match_name[-1] == '/');
+ if (!path)
+ {
+ return false;
+ }
+ memcpy(path, dir, dirlen);
+ path[dirlen++] = '/';
+ memcpy(path + dirlen, search->match, subdirlen);
+ dirlen += subdirlen;
+ path[dirlen] = '\0';
+ }
+ dh = opendir(path);
+ if (!dh)
+ {
+ free(path);
+ return false;
+ }
+ path[dirlen] = '/';
+ while ((d = readdir(dh)))
+ {
+ if (strcmp(d->d_name, search->match_name) == 0)
+ {
+ memcpy(path + dirlen + 1, search->match_name, matchlen + 1);
+ if (!search->find_callback(search->userdata, path))
+ {
+ canceled = true;
+ break;
+ }
+ }
+ }
+ closedir(dh);
+ free(path);
+ }
+ else
+ {
+ glob_t data;
+ dynstr_t* str;
+ char* pattern;
+ str = dynstr_new_str(dir);
+ if (!str)
+ {
+ return false;
+ }
+ if (!dynstr_escape(str, "*?[]{}") || !dynstr_appendc(str, '/') ||
+ !dynstr_append(str, search->match))
+ {
+ dynstr_free(str);
+ return false;
+ }
+ pattern = dynstr_done(str);
+ memset(&data, 0, sizeof(glob_t));
+ if (glob(pattern, GLOB_NOSORT, NULL, &data) == 0)
+ {
+ size_t i;
+ for (i = 0; i < data.gl_pathc; i++)
+ {
+ if (!search->find_callback(search->userdata, data.gl_pathv[i]))
+ {
+ canceled = true;
+ break;
+ }
+ }
+ globfree(&data);
+ }
+ free(pattern);
+ }
+ return canceled;
+}
+
+void* searcher(void* _args)
+{
+ searchargs_t* args = _args;
+ size_t i;
+
+ while (!args->quit)
+ {
+ search_t* search = safe_fifo_pop(args->queue);
+ bool canceled = false;
+ if (!search->match)
+ {
+ free(search);
+ assert(args->quit);
+ break;
+ }
+ do
+ {
+ if (search->dir)
+ {
+ if (args->quit ||
+ (canceled = search_dir(search, search->dir)))
+ {
+ break;
+ }
+ }
+ if (search->dirs)
+ {
+ const char** dir;
+ for (dir = search->dirs; *dir; dir++)
+ {
+ if (args->quit ||
+ (canceled = search_dir(search, *dir)))
+ {
+ break;
+ }
+ }
+ }
+ } while (false);
+ if (!canceled)
+ {
+ search->find_callback(search->userdata, NULL);
+ }
+ put_search(args, search);
+ }
+
+ /* Free all items in the queue */
+ for (;;)
+ {
+ search_t* search = safe_fifo_trypop(args->queue);
+ if (!search)
+ {
+ break;
+ }
+ put_search(args, search);
+ }
+
+ safe_fifo_unref(args->queue);
+ thread_lock_free(args->lock);
+ for (i = 0; i < SEARCH_CACHE_SIZE; i++)
+ {
+ if (args->cache[i])
+ {
+ free_search_data(args->cache[i]);
+ free(args->cache[i]);
+ }
+ }
+ free(args);
+ return NULL;
+}