diff --git a/include/dynarray.h b/include/dynarray.h new file mode 100644 index 0000000..e552ec4 --- /dev/null +++ b/include/dynarray.h @@ -0,0 +1,19 @@ +#ifndef VIETER_DYNARRAY +#define VIETER_DYNARRAY + +#include +#include + +typedef struct dyn_array DynArray; + +DynArray *dynarray_init(size_t initial_capacity); +void dynarray_add(DynArray *da, const char * s); +void dynarray_free(DynArray *da); + +/** + * Convert a DynArray into an array by freeing all its surrounding components + * and returning the underlying array pointer. + */ +char **dynarray_convert(DynArray *da); + +#endif diff --git a/include/package.h b/include/package.h new file mode 100644 index 0000000..09e91bd --- /dev/null +++ b/include/package.h @@ -0,0 +1,26 @@ +#ifndef VIETER_PACKAGE +#define VIETER_PACKAGE + +#include +#include +#include +#include + +#include "archive.h" +#include "archive_entry.h" + +#include "package_info.h" +#include "dynarray.h" + +typedef struct pkg { + char *path; + PkgInfo *info; + DynArray *files; + int compression; +} Pkg; + +Pkg *package_read_archive(const char *pkg_path); +void package_free(Pkg ** ptp); +char *package_to_description(Pkg *pkg); + +#endif diff --git a/include/package_info.h b/include/package_info.h new file mode 100644 index 0000000..d71386e --- /dev/null +++ b/include/package_info.h @@ -0,0 +1,39 @@ +#ifndef VIETER_PACKAGE_INFO +#define VIETER_PACKAGE_INFO + +#define FREE_STRING(sp) if (sp != NULL) free(sp) + +#include + +#include "dynarray.h" + +typedef struct pkg_info { + char *name; + char *base; + char *version; + char *description; + int64_t size; + int64_t csize; + char *url; + char *arch; + int64_t build_date; + char *packager; + char *pgpsig; + int64_t pgpsigsize; + + DynArray *groups; + DynArray *licenses; + DynArray *replaces; + DynArray *depends; + DynArray *conflicts; + DynArray *provides; + DynArray *optdepends; + DynArray *makedepends; + DynArray *checkdepends; +} PkgInfo; + +PkgInfo *package_info_init(); +int package_info_parse(PkgInfo *pkg_info, char *pkg_info_str); +void package_info_free(PkgInfo *pkg_info); + +#endif diff --git a/src/package/dynarray.c b/src/package/dynarray.c new file mode 100644 index 0000000..480c08f --- /dev/null +++ b/src/package/dynarray.c @@ -0,0 +1,55 @@ +#include "dynarray.h" + +struct dyn_array { + char **array; + size_t size; + size_t capacity; +}; + +DynArray *dynarray_init(size_t initial_capacity) { + DynArray *da = malloc(sizeof(DynArray)); + da->size = 0; + da->capacity = initial_capacity; + + return da; +} + +void dynarray_add(DynArray *da, const char *s) { + // An empty dynarray does not have an allocated internal array yet + if (da->size == 0) { + da->array = malloc(sizeof(char*) * da->capacity); + } + // Double array size if it's full + else if (da->size == da->capacity) { + da->array = realloc(da->array, da->capacity * 2); + da->capacity *= 2; + } + + da->array[da->size] = strdup(s); + da->size++; +} + +void dynarray_free(DynArray *da) { + if (da == NULL) { + return; + } + + if (da->array != NULL) { + for (size_t i = 0; i < da->size; i++) { + free(da->array[i]); + } + + free(da->array); + } + + free(da); +} + +char **dynarray_convert(DynArray *da) { + char **array = da->array; + + da->array = NULL; + dynarray_free(da); + + return array; +} diff --git a/src/package/package.c b/src/package/package.c new file mode 100644 index 0000000..a098abb --- /dev/null +++ b/src/package/package.c @@ -0,0 +1,98 @@ +#include "package.h" + +static char *ignored_names[5] = { + ".BUILDINFO", + ".INSTALL", + ".MTREE", + ".PKGINFO", + ".CHANGELOG" +}; +static int ignored_words_len = sizeof(ignored_names) / sizeof(char *); + +Pkg *package_init() { + return calloc(sizeof(PkgInfo), 1); +} + +Pkg *package_read_archive(const char *pkg_path) { + struct archive *a = archive_read_new(); + struct archive_entry *entry = archive_entry_new(); + + // These three are the most commonly used compression methods + archive_read_support_filter_zstd(a); + archive_read_support_filter_gzip(a); + archive_read_support_filter_xz(a); + + // Contents should always be a tarball + archive_read_support_format_tar(a); + + // TODO where does this 10240 come from? + int r = archive_read_open_filename(a, pkg_path, 10240); + + // Exit early if we weren't able to successfully open the archive for reading + if (r != ARCHIVE_OK) { + return NULL; + } + + int compression_code = archive_filter_code(a, 0); + const char *path_name; + + PkgInfo *pkg_info; + DynArray *files = dynarray_init(16); + + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { + path_name = archive_entry_pathname(entry); + + bool ignore = false; + + for (size_t i = 0; i < ignored_words_len; i++) { + if (strcmp(path_name, ignored_names[i]) == 0) { + ignore = true; + break; + } + } + + if (!ignore) { + dynarray_add(files, path_name); + } + + if (strcmp(path_name, ".PKGINFO") == 0) { + // Read data of file into memory buffer + int size = archive_entry_size(entry); + char *buf = malloc(size); + archive_read_data(a, buf, size); + + // Parse package info string into a struct + pkg_info = package_info_init(); + package_info_parse(pkg_info, buf); + + free(buf); + } else { + archive_read_data_skip(a); + } + } + + // Get size of file + struct stat stats; + + if (stat(pkg_path, &stats) != 0) { + return NULL; + } + + pkg_info->csize = stats.st_size; + + archive_read_free(a); + archive_entry_free(entry); + + // Create final return value + Pkg *pkg = package_init(); + pkg->path = strdup(pkg_path); + pkg->info = pkg_info; + pkg->files = files; + pkg->compression = compression_code; + + return pkg; +} + +char *package_to_description(Pkg *pkg) { + +} diff --git a/src/package/package_info.c b/src/package/package_info.c new file mode 100644 index 0000000..1202485 --- /dev/null +++ b/src/package/package_info.c @@ -0,0 +1,139 @@ +#include + +#include "package_info.h" + +PkgInfo *package_info_init() { + return calloc(1, sizeof(PkgInfo)); +} + +void package_info_free(PkgInfo *pkg_info) { + FREE_STRING(pkg_info->name); + FREE_STRING(pkg_info->base); + FREE_STRING(pkg_info->version); + FREE_STRING(pkg_info->description); + FREE_STRING(pkg_info->url); + FREE_STRING(pkg_info->arch); + FREE_STRING(pkg_info->packager); + FREE_STRING(pkg_info->pgpsig); + + dynarray_free(pkg_info->groups); + dynarray_free(pkg_info->licenses); + dynarray_free(pkg_info->replaces); + dynarray_free(pkg_info->depends); + dynarray_free(pkg_info->conflicts); + dynarray_free(pkg_info->provides); + dynarray_free(pkg_info->optdepends); + dynarray_free(pkg_info->makedepends); + dynarray_free(pkg_info->checkdepends); + + free(pkg_info); +} + +/** + * Advance the pointer until all spaces are skipped. + */ +static inline char *trim_spaces_front(char *ptr) { + while (ptr[0] == ' ') { + ptr++; + } + + return ptr; +} + +/** + * Given a string pointer in the middle of a string, move over all spaces in the + * given direction. The final space is replaced with a NULL-byte. + */ +static inline void trim_spaces_back(char *ptr) { + if (ptr[0] != ' ') { + return; + } + + while (ptr[-1] == ' ') { + ptr--; + } + + ptr[0] = '\0'; +} + +#define PKG_INFO_STRING(key, field) if (strcmp(key_ptr, key) == 0) { pkg_info->field = strdup(value_ptr); goto advance; } +#define PKG_INFO_INT(key, field) if (strcmp(key_ptr, key) == 0) { pkg_info->field = atoi(value_ptr); goto advance; } +#define PKG_INFO_ARRAY(key, field) if (strcmp(key_ptr, key) == 0) { \ + if (pkg_info->field == NULL) { pkg_info->field = dynarray_init(4); } \ + dynarray_add(pkg_info->field, value_ptr); goto advance; \ +} + +int package_info_parse(PkgInfo *pkg_info, char *pkg_info_str) { + char *offset_ptr, *equals_ptr, *key_ptr, *value_ptr; + + bool end = false; + + // Iterate over all lines in file + while (!end) { + // This pointer will always point to the final character of the + // current line, be it the position of a newline or the NULL byte at + // the end of the entire string + offset_ptr = strchr(pkg_info_str, '\n'); + + // We replace the newline with a NULL byte. Now we know the line runs + // until the next NULL byte. + if (offset_ptr != NULL) { + offset_ptr[0] = '\0'; + } else { + // Advance pointer to the NULL byte of the string + offset_ptr = pkg_info_str + 1; + + while (*offset_ptr != '\0') { + offset_ptr++; + } + + end = true; + } + + // Skip comment lines + if (pkg_info_str[0] == '#') { + goto advance; + } + + equals_ptr = strchr(pkg_info_str, '='); + + // If a line doesn't contain an equals sign, the file is invalid + if (equals_ptr == NULL) { + return 1; + } + + // Trim whitespace from key + key_ptr = trim_spaces_front(pkg_info_str); + trim_spaces_back(equals_ptr - 1); + + // Trim spaces from value + value_ptr = trim_spaces_front(equals_ptr + 1); + trim_spaces_back(offset_ptr - 1); + + // Match key + PKG_INFO_STRING("pkgname", name); + PKG_INFO_STRING("pkgbase", base); + PKG_INFO_STRING("pkgver", version); + PKG_INFO_STRING("pkgdesc", description); + PKG_INFO_INT("size", size); + PKG_INFO_STRING("url", url); + PKG_INFO_STRING("arch", arch); + PKG_INFO_INT("builddate", build_date); + PKG_INFO_STRING("packager", packager); + PKG_INFO_STRING("pgpsig", pgpsig); + PKG_INFO_INT("pgpsigsize", pgpsigsize); + PKG_INFO_ARRAY("group", groups); + PKG_INFO_ARRAY("license", licenses); + PKG_INFO_ARRAY("replaces", replaces); + PKG_INFO_ARRAY("depend", depends); + PKG_INFO_ARRAY("optdepend", optdepends); + PKG_INFO_ARRAY("makedepend", makedepends); + PKG_INFO_ARRAY("checkdepend", checkdepends); + +advance: + pkg_info_str = offset_ptr + 1; + continue; + } + + return 0; +}