]> git.ipfire.org Git - pakfire.git/blobdiff - src/libpakfire/util.c
util: Add test for pakfire_string_endswith
[pakfire.git] / src / libpakfire / util.c
index 17b007b9dd9428f8b7e0d576c80a1057972d8340..553c83af65ec79a0a9c061961e9af723c6ab7210 100644 (file)
 #                                                                             #
 #############################################################################*/
 
+#include <ctype.h>
 #include <errno.h>
+#include <fcntl.h>
+#include <ftw.h>
 #include <libgen.h>
 #include <math.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/resource.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <time.h>
 #include <unistd.h>
 
+#include <archive.h>
+#include <archive_entry.h>
+#include <json.h>
+#include <solv/pool.h>
+#include <uuid/uuid.h>
+
 #include <pakfire/constants.h>
 #include <pakfire/logging.h>
-#include <pakfire/private.h>
-#include <pakfire/types.h>
+#include <pakfire/package.h>
+#include <pakfire/util.h>
 
-void pakfire_oom(size_t num, size_t len) {
-       if (num)
-               fprintf(stderr, "Out of memory allocating %zu*%zu bytes!\n", num, len);
-       else
-               fprintf(stderr, "Out of memory allocating %zu bytes!\n", len);
+#define NSEC_PER_SEC 1000000000
 
-       abort();
-       exit(1);
-}
+static Id pakfire_parse_namespace(Pool* pool, const char* s) {
+       const char* p = strchr(s, '(');
+       if (!p)
+               return 0;
+
+       // Store the namespace ID
+       Id namespace = pool_strn2id(pool, s, p - s, 1);
 
-char* pakfire_strdup(const char* s) {
+       // Find the end of the string
+       s = strrchr(p, ')');
        if (!s)
                return 0;
 
-       char* r = strdup(s);
-       if (!r)
-               pakfire_oom(0, strlen(s));
+       Id id = pool_strn2id(pool, p + 1, s - p - 1, 1);
 
-       return r;
+       // Bring it all together
+       return pool_rel2id(pool, namespace, id, REL_NAMESPACE, 1);
 }
 
-PAKFIRE_EXPORT int pakfire_string_startswith(const char* s, const char* prefix) {
-       return !strncmp(s, prefix, strlen(prefix));
-}
+Id pakfire_parse_dep(struct pakfire* pakfire, const char* s) {
+       Id id;
 
-char* pakfire_format_size(double size) {
-       char string[STRING_SIZE];
-       const char* units[] = {" ", "k", "M", "G", "T", NULL};
-       const char** unit = units;
-
-       while (*(unit + 1) && size >= 1024.0) {
-               size /= 1024.0;
-               unit++;
+       if (!s) {
+               errno = EINVAL;
+               return 0;
        }
 
-       snprintf(string, STRING_SIZE, "%.0f%s", round(size), *unit);
+       // Ignore empty strings
+       if (!*s)
+               return 0;
+
+       Pool* pool = pakfire_get_solv_pool(pakfire);
+
+       // Consume any leading space
+       if (isspace(*s))
+               s++;
+
+       const char* p = s;
+
+       // Find the first part (before =, >= or <=)
+       while (*p && !isspace(*p) && *p != '<' && *p != '=' && *p != '>')
+               p++;
+
+       // The length of the first part
+       size_t l = p - s;
+
+       // Add name to pool
+       if (pakfire_string_startswith(s, "pakfire("))
+               id = pakfire_parse_namespace(pool, s);
+       else
+               id = pool_strn2id(pool, s, l, 1);
+
+       // Consume any more space
+       if (isspace(*p))
+               p++;
+
+       if (*p == '<' || *p == '=' || *p == '>') {
+               int flags = 0;
+
+               while (1) {
+                       if (*p == '<')
+                               flags |= REL_LT;
+                       else if (*p == '=')
+                               flags |= REL_EQ;
+                       else if (*p == '>')
+                               flags |= REL_GT;
+                       else
+                               break;
+
+                       p++;
+               }
+
+               // Consume any more space
+               if (isspace(*p))
+                       p++;
+
+               // Add EVR to pool
+               Id evr = pool_str2id(pool, p, 1);
+
+               // Combine everything
+               id = pool_rel2id(pool, id, evr, flags, 1);
+       }
 
-       return pakfire_strdup(string);
+       return id;
 }
 
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wformat-nonliteral"
-static char* pakfire_strftime(const char* format, time_t t) {
-       char string[STRING_SIZE];
-       struct tm* tm = gmtime(&t);
+void pakfire_parse_deps(struct pakfire* pakfire, struct pakfire_package* pkg,
+               void (*func)(struct pakfire_package* pkg, const char* dep), const char* deps) {
+       char* p = strdupa(deps);
+
+       while (*p) {
+               char* e = strchr(p, '\n');
+
+               // Terminate the string
+               if (e)
+                       *e = '\0';
+
+               // Add the dependency
+               func(pkg, p);
 
-       strftime(string, sizeof(string), format, tm);
+               // End loop when we reached the end
+               if (!e)
+                       break;
 
-       return pakfire_strdup(string);
+               // Or continue at the next line
+               p = e + 1;
+       }
 }
-#pragma GCC diagnostic pop
 
-char* pakfire_format_date(time_t t) {
-       return pakfire_strftime("%Y-%m-%d", t);
+int pakfire_string_startswith(const char* s, const char* prefix) {
+       // Validate input
+       if (!s || !prefix) {
+               errno = EINVAL;
+               return 1;
+       }
+
+       return !strncmp(s, prefix, strlen(prefix));
 }
 
-PAKFIRE_EXPORT char* pakfire_path_join(const char* first, const char* second) {
-       char* buffer;
+int pakfire_string_endswith(const char* s, const char* suffix) {
+       // Validate input
+       if (!s || !suffix) {
+               errno = EINVAL;
+               return 1;
+       }
 
-       if (!first)
-               return pakfire_strdup(second);
+       return !strcmp(s + strlen(s) - strlen(suffix), suffix);
+}
 
-       if (!second)
-               return pakfire_strdup(first);
+int pakfire_string_matches(const char* s, const char* pattern) {
+       return !!strstr(s, pattern);
+}
 
-       if (*second == '/')
-               return pakfire_strdup(second);
+char* pakfire_unquote_in_place(char* s) {
+       if (!s || !*s)
+               return s;
 
-       asprintf(&buffer, "%s/%s", first, second);
+       // Is the first character a quote?
+       if (*s != '"')
+               return s;
 
-       return buffer;
-}
+       // Find the end of value
+       size_t l = strlen(s);
+       if (!l)
+               return s;
 
-PAKFIRE_EXPORT const char* pakfire_path_relpath(const char* root, const char* path) {
-       if (pakfire_string_startswith(path, root))
-               return path + strlen(root);
+       // Is the last character a quote?
+       if (s[l - 1] != '"')
+               return s;
 
-       return NULL;
+       // The string seems to be in quotes; remove them
+       s[l - 1] = '\0';
+       s++;
+
+       return s;
 }
 
-PAKFIRE_EXPORT int pakfire_path_isdir(const char* path) {
-       struct stat s;
+int pakfire_string_partition(const char* s, const char* delim, char** s1, char** s2) {
+       char* p = strstr(s, delim);
 
-       if (stat(path, &s) != 0) {
-               // Does not seem to exist
-               return 0;
+       // Delim was not found
+       if (!p) {
+               *s1 = NULL;
+               *s2 = NULL;
+               return 1;
        }
 
-       if (S_ISDIR(s.st_mode))
-               return 1;
+       // Length of string before delim
+       size_t l = p - s;
+
+       char* buffer = malloc(l + 1);
+       if (!buffer)
+               return -ENOMEM;
+
+       // Copy first part
+       *s1 = memcpy(buffer, s, l);
+       buffer[l] = '\0';
+
+       // Copy second part
+       *s2 = strdup(p + strlen(delim));
 
        return 0;
 }
 
-PAKFIRE_EXPORT char* pakfire_basename(const char* path) {
-       char* name = pakfire_strdup(path);
+char* pakfire_string_replace(const char* s, const char* pattern, const char* repl) {
+       // Return NULL on no input or no pattern
+       if (!s || !pattern) {
+               errno = EINVAL;
+               return NULL;
+       }
 
-       char* r = basename(name);
-       if (r)
-               r = pakfire_strdup(r);
+       // Replace with nothing when repl is NULL
+       if (!repl)
+               repl = "";
 
-       free(name);
+       char* result = NULL;
+       const char** cache = NULL;
+       unsigned int count = 0;
 
-       return r;
-}
+       const size_t pattern_length = strlen(pattern);
 
-PAKFIRE_EXPORT char* pakfire_dirname(const char* path) {
-       char* parent = pakfire_strdup(path);
+       // Working pointer
+       const char* p = s;
 
-       char* r = dirname(parent);
-       if (r)
-               r = pakfire_strdup(r);
+       // Find all occurrences of pattern and store their location
+       while (1) {
+               const char* needle = strstr(p, pattern);
+               if (!needle)
+                       break;
 
-       free(parent);
+               // Make space in the cache
+               cache = reallocarray(cache, sizeof(*cache), count + 1);
+               cache[count++] = needle;
 
-       return r;
+               // Move p forward
+               p = needle + pattern_length;
+       }
+
+       // Copy the string if no occurence was found
+       if (count == 0) {
+               result = strdup(s);
+               goto ERROR;
+       }
+
+       // Store the end pointer
+       cache = reallocarray(cache, sizeof(*cache), count + 1);
+       cache[count] = s + strlen(s);
+
+       const size_t repl_length = strlen(repl);
+
+       // Determine the length of the final string
+       const size_t length = strlen(s) + ((repl_length - pattern_length) * count);
+
+       // Allocate enough memory for the result
+       result = malloc(length + 1);
+       if (!result)
+               goto ERROR;
+
+       // Reset p
+       p = s;
+
+       // Working pointer for the result
+       char* r = result;
+
+       // Copy everything up to the first match
+       ssize_t l = cache[0] - s;
+       memcpy(r, p, l);
+       r += l;
+       p += l;
+
+       for (unsigned int i = 0; i < count; i++) {
+               // Put replacement here
+               memcpy(r, repl, repl_length);
+               r += repl_length;
+               p += pattern_length;
+
+               // Determine the length between two matches
+               l = cache[i+1] - (cache[i] + pattern_length);
+
+               memcpy(r, p, l);
+               r += l;
+               p += l;
+       }
+
+       // Terminate the string
+       result[length] = '\0';
+
+ERROR:
+       if (cache)
+               free(cache);
+
+       return result;
 }
 
-PAKFIRE_EXPORT int pakfire_access(Pakfire pakfire, const char* dir, const char* file, int mode) {
-       char* path = pakfire_path_join(dir, file);
+static unsigned int pakfire_chrcnt(const char* s, char delim) {
+       size_t length = strlen(s);
 
-       int r = access(path, mode);
+       unsigned int count = 0;
 
-       if (r) {
-               if (mode & R_OK)
-                       DEBUG(pakfire, "%s is not readable\n", path);
+       for (unsigned int i = 0; i < length; i++)
+               if (s[i] == delim)
+                       count++;
 
-               if (mode & W_OK)
-                       DEBUG(pakfire, "%s is not writable\n", path);
+       return count;
+}
 
-               if (mode & X_OK)
-                       DEBUG(pakfire, "%s is not executable\n", path);
+char** pakfire_split_string(const char* s, char delim) {
+       char** array = NULL;
 
-               if (mode & F_OK)
-                       DEBUG(pakfire, "%s does not exist\n", path);
+       if (!s) {
+               errno = EINVAL;
+               return NULL;
        }
 
-       free(path);
+       // Count how often we need to split
+       unsigned int count = pakfire_chrcnt(s, delim) + 1;
 
-       return r;
+       // Allocate array
+       array = calloc(count + 1, sizeof(*array));
+       if (!array)
+               return NULL;
+
+       // Copy string to stack
+       char* p = strdupa(s);
+       if (!p)
+               return NULL;
+
+       unsigned int i = 0;
+       while (*p) {
+               char* e = strchr(p, delim);
+
+               // Terminate the string
+               if (e)
+                       *e = '\0';
+
+               // Add string to the array
+               array[i++] = strdup(p);
+
+               // End loop when we reached the end
+               if (!e)
+                       break;
+
+               // Or continue at the next line
+               p = e + 1;
+       }
+
+       return array;
 }
 
-int pakfire_mkdir(Pakfire pakfire, const char* path, mode_t mode) {
-       int r = 0;
+char* pakfire_string_join(char** list, const char* delim) {
+       if (!list)
+               return NULL;
 
-       if ((strcmp(path, "/") == 0) || (strcmp(path, ".") == 0))
-               return 0;
+       size_t length = 0;
+       unsigned int elements = 0;
 
-       // If parent does not exists, we try to create it.
-       char* parent = pakfire_dirname(path);
-       r = pakfire_access(pakfire, parent, NULL, F_OK);
-       if (r)
-               r = pakfire_mkdir(pakfire, parent, 0);
+       // Count the number of elements and the total length
+       for (char** item = list; *item; item++) {
+               length += strlen(*item);
+               elements++;
+       }
 
-       free(parent);
+       // Empty list?
+       if (!elements)
+               return NULL;
 
-       // Exit if parent directory could not be created
-       if (r)
-               return r;
+       // Add the delimiters
+       length += strlen(delim) * (elements - 1);
 
-       DEBUG(pakfire, "Creating directory %s\n", path);
+       // Allocate the result string
+       char* string = malloc(length + 1);
+       if (!string)
+               return NULL;
 
-       // Finally, create the directory we want.
-       r = mkdir(path, mode);
-       if (r) {
-               switch (errno) {
-                       // If the directory already exists, this is fine.
-                       case EEXIST:
-                               r = 0;
-                               break;
+       // Pointer to where we are writing
+       char* p = string;
+
+       size_t bytes_left = length;
+       size_t bytes_written;
+
+       for (char** item = list; *item; item++) {
+               bytes_written = snprintf(p, bytes_left, "%s", *item);
+
+               bytes_left -= bytes_written;
+               p += bytes_written;
+
+               // Write the delimiter
+               if (bytes_left) {
+                       bytes_written = snprintf(p, bytes_left, "%s", delim);
+
+                       bytes_left -= bytes_written;
+                       p += bytes_written;
                }
        }
 
-       return r;
+       return string;
 }
 
-char* pakfire_sgets(char* str, int num, char** input) {
-       char* next = *input;
-       int numread = 0;
+int pakfire_format_size(char* dst, size_t length, double value) {
+       const char* units[] = {
+               "%.0f ",
+               "%.0fk",
+               "%.1fM",
+               "%.1fG",
+               "%.1fT",
+               NULL
+       };
+       const char** unit = units;
 
-       while (numread + 1 < num && *next) {
-               int isnewline = (*next == '\n');
+       while (*(unit + 1) && value >= 1024.0) {
+               value /= 1024.0;
+               unit++;
+       }
 
-               *str++ = *next++;
-               numread++;
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+       return snprintf(dst, length, *unit, value);
+#pragma GCC diagnostic pop
+}
 
-               if (isnewline)
-                       break;
+int pakfire_format_speed(char* dst, size_t length, double value) {
+       const char* units[] = {
+               "%4.0fB/s",
+               "%4.0fkB/s",
+               "%4.1fMB/s",
+               "%4.1fGB/s",
+               "%4.1fTB/s",
+               NULL
+       };
+       const char** unit = units;
+
+       while (*(unit + 1) && value >= 1024.0) {
+               value /= 1024.0;
+               unit++;
        }
 
-       // Terminate string
-       *str = '\0';
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+       return snprintf(dst, length, *unit, value);
+#pragma GCC diagnostic pop
+}
 
-       *input = next;
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+static char* pakfire_strftime(const char* format, time_t t) {
+       char string[128];
+       struct tm* tm = gmtime(&t);
 
-       return str;
+       strftime(string, sizeof(string) - 1, format, tm);
+
+       return strdup(string);
 }
+#pragma GCC diagnostic pop
 
-char* pakfire_remove_trailing_newline(char* str) {
-       ssize_t pos = strlen(str) - 1;
+char* pakfire_format_date(time_t t) {
+       return pakfire_strftime("%Y-%m-%d", t);
+}
 
-       if (str[pos] == '\n')
-               str[pos] = '\0';
+int __pakfire_strftime_now(char* dest, size_t length, const char* format) {
+       struct tm tm;
 
-       return str;
+       // Fetch the current time
+       const time_t t = time(NULL);
+       if (t < 0)
+               return 1;
+
+       // Convert to struct tm
+       struct tm* now = gmtime_r(&t, &tm);
+       if (!now)
+               return 1;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+       strftime(dest, length, format, now);
+#pragma GCC diagnostic pop
+
+       return 0;
 }
 
-PAKFIRE_EXPORT const char* pakfire_action_type_string(pakfire_action_type_t type) {
-       switch (type) {
-               case PAKFIRE_ACTION_NOOP:
-                       return "NOOP";
+int __pakfire_path_join(char* dest, size_t length,
+               const char* first, const char* second) {
+       if (!first)
+               return snprintf(dest, length, "%s", second);
 
-               case PAKFIRE_ACTION_VERIFY:
-                       return "VERIFY";
+       if (!second)
+               return snprintf(dest, length, "%s", first);
 
-               case PAKFIRE_ACTION_EXECUTE:
-                       return "EXECUTE";
+       // Remove leading slashes from second argument
+       while (*second == '/')
+               second++;
 
-               case PAKFIRE_ACTION_PRETRANS:
-                       return "PRETRANS";
+       return snprintf(dest, length, "%s/%s", first, second);
+}
 
-               case PAKFIRE_ACTION_POSTTRANS:
-                       return "POSTTRANS";
-       }
+const char* pakfire_path_relpath(const char* root, const char* path) {
+       if (pakfire_string_startswith(path, root))
+               return path + strlen(root);
 
        return NULL;
 }
 
-PAKFIRE_EXPORT int pakfire_read_file_into_buffer(FILE* f, char** buffer, size_t* len) {
+int pakfire_path_exists(const char* path) {
+       return !access(path, F_OK);
+}
+
+time_t pakfire_path_age(const char* path) {
+       struct stat st;
+
+       int r = stat(path, &st);
+       if (r == 0) {
+               // Get current timestamp
+               time_t now = time(NULL);
+
+               // Return the difference since the file has been created and now
+               return now - st.st_ctime;
+       }
+
+       return -1;
+}
+
+char* pakfire_basename(const char* path) {
+       char* name = strdup(path);
+
+       char* r = basename(name);
+       if (r)
+               r = strdup(r);
+
+       free(name);
+
+       return r;
+}
+
+char* pakfire_dirname(const char* path) {
+       char* parent = strdup(path);
+
+       char* r = dirname(parent);
+       if (r)
+               r = strdup(r);
+
+       free(parent);
+
+       return r;
+}
+
+char* pakfire_remove_trailing_newline(char* str) {
+       ssize_t pos = strlen(str) - 1;
+
+       if (str[pos] == '\n')
+               str[pos] = '\0';
+
+       return str;
+}
+
+int pakfire_read_file_into_buffer(FILE* f, char** buffer, size_t* len) {
        if (!f)
                return -EBADF;
 
@@ -300,64 +617,384 @@ PAKFIRE_EXPORT int pakfire_read_file_into_buffer(FILE* f, char** buffer, size_t*
        return 0;
 }
 
-PAKFIRE_EXPORT size_t pakfire_string_to_size(const char* s) {
-       size_t size;
+char* pakfire_generate_uuid() {
+       uuid_t uuid;
+
+       // Generate a new random value
+       uuid_generate_random(uuid);
 
-       int r = sscanf(s, "%zu", &size);
-       if (r == 1)
-               return size;
+       char* ret = malloc(UUID_STR_LEN + 1);
+       if (!ret)
+               return NULL;
+
+       // Convert it to string
+       uuid_unparse_lower(uuid, ret);
+
+       // Terminate string
+       ret[UUID_STR_LEN] = '\0';
+
+       return ret;
+}
+
+int pakfire_tty_is_noninteractive(void) {
+       const int fds[] = {
+               STDIN_FILENO,
+               STDOUT_FILENO,
+               STDERR_FILENO,
+               -1,
+       };
+
+       for (const int* fd = fds; *fd >= 0; fd++) {
+               if (!isatty(*fd))
+                       return 1;
+       }
 
        return 0;
 }
 
-PAKFIRE_EXPORT char** pakfire_split_string(const char* s, char delim) {
-       // Copy string to stack and count spaces
-       char buffer[strlen(s) + 2];
+char* __pakfire_hexlify(const unsigned char* digest, const size_t length) {
+       const char* hexdigits = "0123456789abcdef";
 
-       size_t count = 1;
-       for (unsigned int i = 0; i < strlen(s) + 1; i++) {
-               buffer[i] = s[i];
+       char* s = malloc((length * 2) + 1);
+       if (!s)
+               return NULL;
 
-               if (s[i] == delim) {
-                       buffer[i] = '\0';
-                       count++;
+       for (unsigned int i = 0, j = 0; i < length; i++) {
+               char b = digest[i];
+
+               s[j++] = hexdigits[(b >> 4) & 0xf];
+               s[j++] = hexdigits[(b & 0x0f)];
+       }
+
+       // Terminate result
+       s[length * 2] = '\0';
+
+       return s;
+}
+
+static int parse_nibble(char nibble) {
+       // Handle digits
+       if (nibble >= '0' && nibble <= '9')
+               return nibble - '0';
+
+       // Handle lowercase letters
+       if (nibble >= 'a' && nibble <= 'f')
+               return 10 + nibble - 'a';
+
+       // Handle uppercase letters
+       if (nibble >= 'A' && nibble <= 'F')
+               return 10 + nibble - 'A';
+
+       // Error
+       return -1;
+}
+
+int __pakfire_unhexlify(unsigned char* dst, const size_t l, const char* src) {
+       if (!src) {
+               errno = EINVAL;
+               return 1;
+       }
+
+       // Determine input length
+       const size_t length = strlen(src);
+
+       for (unsigned int i = 0, j = 0; i < length && j < l; i += 2, j++) {
+               int h = parse_nibble(src[i]);
+               int l = parse_nibble(src[i+1]);
+
+               // Check for invalid input
+               if (h < 0 || l < 0) {
+                       errno = EINVAL;
+                       return 1;
                }
+
+               // Store result
+               dst[j] = h << 4 | l;
        }
 
-       // Allocate an array of sufficient size
-       char** ret = malloc(sizeof(*ret) * (count + 1));
+       return 0;
+}
 
-       // Copy strings to heap one by one
-       unsigned int i = 0;
-       char* p = buffer;
-       while (*p) {
-               ret[i++] = pakfire_strdup(p);
+int pakfire_mkparentdir(const char* path, mode_t mode) {
+       int r;
+
+       char* dirname = pakfire_dirname(path);
+       if (!dirname)
+               return 1;
+
+       // We have arrived at the top of the tree
+       if (*dirname == '.' || strcmp(dirname, "/") == 0)
+               return 0;
+
+       // Ensure the parent directory exists
+       r = pakfire_mkparentdir(dirname, mode);
+       if (r)
+               goto END;
+
+       // Create this directory
+       r = mkdir(dirname, mode);
+
+       // Ignore when the directory already exists
+       if (r && errno == EEXIST)
+               r = 0;
+
+END:
+       free(dirname);
+
+       return r;
+}
+
+int pakfire_mkdir(const char* path, mode_t mode) {
+       int r = pakfire_mkparentdir(path, mode);
+       if (r)
+               return r;
+
+       // Finally, create the directory we want
+       return mkdir(path, mode);
+}
+
+FILE* pakfire_mktemp(char* path) {
+       int r = pakfire_mkparentdir(path, 0);
+       if (r)
+               return NULL;
+
+       // Create a temporary result file
+       int fd = mkostemp(path, O_CLOEXEC);
+       if (fd < 0)
+               return NULL;
 
-               // Move pointer to the next string
-               p += strlen(p) + 1;
+       // Re-open as file handle
+       return fdopen(fd, "w+");
+}
+
+char* pakfire_mkdtemp(char* path) {
+       int r = pakfire_mkparentdir(path, 0);
+       if (r)
+               return NULL;
+
+       return mkdtemp(path);
+}
+
+static int _unlink(const char* path, const struct stat* stat, int typeflag, struct FTW* ftwbuf) {
+       return remove(path);
+}
+
+int pakfire_rmtree(const char* path, int flags) {
+       int r = nftw(path, _unlink, 64, flags|FTW_DEPTH|FTW_PHYS);
+
+       // Ignore if path didn't exist
+       if (r < 0 && errno == ENOENT)
+               r = 0;
+
+       return r;
+}
+
+// Digests
+
+size_t pakfire_digest_length(enum pakfire_digests digest) {
+       switch (digest) {
+               case PAKFIRE_DIGEST_SHA512:
+                       return 64;
+
+               case PAKFIRE_DIGEST_SHA256:
+                       return 32;
+
+               case PAKFIRE_DIGEST_SHA1:
+                       return 20;
+
+               case PAKFIRE_DIGEST_NONE:
+                       return 0;
        }
 
-       // Terminate array
-       ret[count] = NULL;
+       return 0;
+}
 
-       return ret;
+// Archive Stuff
+
+int pakfire_archive_copy_data(struct archive* src, struct archive* dst,
+               struct archive_entry* entry) {
+       const void* buffer;
+       size_t size;
+       off_t offset;
+       int r;
+
+       for (;;) {
+               // Read a block of data
+               r = archive_read_data_block(src, &buffer, &size, &offset);
+               if (r == ARCHIVE_EOF)
+                       return ARCHIVE_OK;
+               else if (r)
+                       return r;
+
+               // Write the read block of data
+               r = archive_write_data(dst, buffer, size);
+               if (r < 0)
+                       return r;
+       }
 }
 
-PAKFIRE_EXPORT void pakfire_partition_string(const char* s, const char* delim, char** s1, char** s2) {
-       char* p = strstr(s, delim);
+int pakfire_archive_copy_data_to_buffer(struct pakfire* pakfire, struct archive* a,
+               struct archive_entry* entry, char** data, size_t* data_size) {
+       *data = NULL;
+       *data_size = 0;
 
-       // Delim was not found
-       if (!p) {
-               *s1 = NULL;
-               *s2 = NULL;
-               return;
+       size_t required_size = archive_entry_size(entry);
+       if (!required_size)
+               return 0;
+
+       // Allocate a block of the required size
+       *data = calloc(1, required_size + 1);
+       if (!*data)
+               return ENOMEM;
+
+       ssize_t bytes_read = archive_read_data(a, *data, required_size);
+       if (bytes_read < 0) {
+               ERROR(pakfire, "Could not read from archive: %s\n", archive_error_string(a));
+               free(*data);
+               return 1;
        }
 
-       // Length of string before delim
-       size_t l = p - s;
+       *data_size = bytes_read;
+
+       return 0;
+}
+
+// JSON Stuff
+
+static struct json_object* pakfire_json_parse(struct pakfire* pakfire, FILE* f) {
+       struct json_tokener* tokener = NULL;
+       struct json_object* json = NULL;
+       char* buffer = NULL;
+       size_t length;
+
+       // Read everything into memory
+       int r = pakfire_read_file_into_buffer(f, &buffer, &length);
+       if (r)
+               goto ERROR;
+
+       // Create tokener
+       tokener = json_tokener_new();
+       if (!tokener) {
+               ERROR(pakfire, "Could not allocate JSON tokener: %m\n");
+               goto ERROR;
+       }
+
+       // Parse JSON from path
+       json = json_tokener_parse_ex(tokener, buffer, length);
+       if (!json) {
+               enum json_tokener_error error = json_tokener_get_error(tokener);
 
-       *s1 = malloc(l);
-       snprintf(*s1, l, "%s", s);
+               ERROR(pakfire, "JSON parsing error: %s\n", json_tokener_error_desc(error));
+               goto ERROR;
+       }
+
+       // Log what we have parsed
+       DEBUG(pakfire, "Parsed JSON:\n%s\n",
+               json_object_to_json_string_ext(json,
+                       JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY)
+       );
+
+ERROR:
+       if (buffer)
+               free(buffer);
+
+       if (tokener)
+               json_tokener_free(tokener);
+
+       return json;
+}
+
+struct json_object* pakfire_json_parse_from_file(struct pakfire* pakfire, const char* path) {
+       FILE* f = fopen(path, "r");
+       if (!f)
+               return NULL;
+
+       struct json_object* json = pakfire_json_parse(pakfire, f);
+       fclose(f);
+
+       return json;
+}
+
+// Time Stuff
+
+static void timespec_normalize(struct timespec* t) {
+       while (t->tv_nsec >= NSEC_PER_SEC) {
+               t->tv_sec++;
+               t->tv_nsec -= NSEC_PER_SEC;
+       }
+
+       while (t->tv_nsec <= -NSEC_PER_SEC) {
+               t->tv_sec--;
+               t->tv_nsec += NSEC_PER_SEC;
+       }
+}
+
+struct timespec timespec_add(const struct timespec* t1, const struct timespec* t2) {
+       struct timespec r = {
+               .tv_sec  = t1->tv_sec  + t2->tv_sec,
+               .tv_nsec = t2->tv_nsec + t2->tv_nsec,
+       };
+
+       // Correct any negative values
+       timespec_normalize(&r);
+
+       return r;
+}
+
+struct timespec timespec_from_ms(int milliseconds) {
+       struct timespec t = {
+               .tv_sec  = (milliseconds / 1000),
+               .tv_nsec = (milliseconds % 1000) * 1000000,
+       };
+       
+       return t;
+}
+
+int timespec_lt(struct timespec* t1, struct timespec* t2) {    
+       return (
+               t1->tv_sec < t2->tv_sec ||
+               (t1->tv_sec == t2->tv_sec && t1->tv_nsec < t2->tv_nsec)
+       );
+}
+
+// Resource Limits
+
+int pakfire_rlimit_set(struct pakfire* pakfire, int limit) {
+       struct rlimit rl;
+
+       // Sanity check
+       if (limit < 3) {
+               errno = EINVAL;
+               return 1;
+       }
+
+       // Fetch current configuration
+       if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
+               ERROR(pakfire, "Could not read RLIMIT_NOFILE: %m\n");
+               return 1;
+       }
+
+       // Do not attempt to set higher than maximum
+       if ((long unsigned int)limit > rl.rlim_max)
+               limit = rl.rlim_max;
+
+       rl.rlim_cur = limit;
+
+       // Set the new limit
+       if (setrlimit(RLIMIT_NOFILE, &rl) < 0) {
+               ERROR(pakfire, "Could not set RLIMIT_NOFILE to %lu: %m\n", rl.rlim_cur);
+               return 1;
+       }
+
+       DEBUG(pakfire, "RLIMIT_NOFILE set to %d\n", limit);
+
+       return 0;
+}
 
-       *s2 = pakfire_strdup(p + strlen(delim));
+/*
+       Resets RLIMIT_NOFILE to FD_SETSIZE (e.g. 1024)
+       for compatibility with software that uses select()
+*/
+int pakfire_rlimit_reset_nofile(struct pakfire* pakfire) {
+       return pakfire_rlimit_set(pakfire, FD_SETSIZE);
 }