From cc3cacc1f76c404290ab1ce815ad500c753aeef4 Mon Sep 17 00:00:00 2001 From: GreekStapler Date: Wed, 25 Jan 2023 13:00:33 +0100 Subject: [PATCH 1/2] refactor: Add original .c package files by Jef. --- include/dynarray.h | 19 +++++ include/package.h | 26 +++++++ include/package_info.h | 39 +++++++++++ src/package/dynarray.c | 55 +++++++++++++++ src/package/package.c | 98 ++++++++++++++++++++++++++ src/package/package_info.c | 139 +++++++++++++++++++++++++++++++++++++ 6 files changed, 376 insertions(+) create mode 100644 include/dynarray.h create mode 100644 include/package.h create mode 100644 include/package_info.h create mode 100644 src/package/dynarray.c create mode 100644 src/package/package.c create mode 100644 src/package/package_info.c 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; +} From d252c55fb01eabdd8dc6760f1b3bf8ca421d0965 Mon Sep 17 00:00:00 2001 From: GreekStapler Date: Wed, 25 Jan 2023 16:22:22 +0100 Subject: [PATCH 2/2] refactor: Modified .PKGINFO parser not to rely on gotos and continues. Parser is now optimistic and assumes .PKGINFO file is valid. After my changes to the macros, they still feel a bit hacky, but I'm content with them. I also changed the parser to assume the .PKGINFO files are always valid because they are automatically generated. The parser also assumes the same fields will always appear in the same fixed order. I made this change after checking how makepkg generated this file. --- include/package_info.h | 2 +- src/package/package_info.c | 152 +++++++++++-------------------------- 2 files changed, 47 insertions(+), 107 deletions(-) diff --git a/include/package_info.h b/include/package_info.h index d71386e..b1388a2 100644 --- a/include/package_info.h +++ b/include/package_info.h @@ -33,7 +33,7 @@ typedef struct pkg_info { } PkgInfo; PkgInfo *package_info_init(); -int package_info_parse(PkgInfo *pkg_info, char *pkg_info_str); +void package_info_parse(PkgInfo *pkg_info, char *pkg_info_str); void package_info_free(PkgInfo *pkg_info); #endif diff --git a/src/package/package_info.c b/src/package/package_info.c index 1202485..5e1986d 100644 --- a/src/package/package_info.c +++ b/src/package/package_info.c @@ -2,6 +2,31 @@ #include "package_info.h" +#define PKG_INFO_STRING(key_ptr, field) if ((value_ptr = strstr(value_ptr, key_ptr)) != NULL) { \ + value_ptr += strlen(key_ptr);\ + tail_ptr = strchr(value_ptr, '\n');\ + tail_ptr[0] = '\0'; \ + pkg_info->field = strdup(value_ptr); \ + tail_ptr[0] = '\n'; \ +} value_ptr = tail_ptr; + +#define PKG_INFO_INT(key_ptr, field) value_ptr = strstr(value_ptr, key_ptr) + strlen(key_ptr);\ + tail_ptr = strchr(value_ptr, '\n');\ + tail_ptr[0] = '\0'; \ + pkg_info->field = atoi(value_ptr); \ + tail_ptr[0] = '\n'; \ + value_ptr = tail_ptr; + +#define PKG_INFO_ARRAY(key_ptr, field) while((value_ptr = strstr(value_ptr, key_ptr)) != NULL){ \ + value_ptr = value_ptr + strlen(key_ptr);\ + tail_ptr = strchr(value_ptr, '\n'); \ + tail_ptr[0] = '\0'; \ + if(pkg_info->field == NULL) { pkg_info->field = dynarray_init(4); } \ + dynarray_add(pkg_info->field, value_ptr); \ + tail_ptr[0] = '\n'; \ + value_ptr = tail_ptr;\ +} value_ptr = tail_ptr; + PkgInfo *package_info_init() { return calloc(1, sizeof(PkgInfo)); } @@ -29,111 +54,26 @@ void package_info_free(PkgInfo *pkg_info) { free(pkg_info); } -/** - * Advance the pointer until all spaces are skipped. - */ -static inline char *trim_spaces_front(char *ptr) { - while (ptr[0] == ' ') { - ptr++; - } +void package_info_parse(PkgInfo *pkg_info, char *pkg_info_str) { + char *value_ptr = pkg_info_str, *tail_ptr; + + PKG_INFO_STRING("\npkgname = ", name); + PKG_INFO_STRING("\npkgbase = ", base); + PKG_INFO_STRING("\npkgver = ", version); + PKG_INFO_STRING("\npkgdesc = ", description); + PKG_INFO_STRING("\nurl = ", url); + PKG_INFO_INT("\nbuilddate = ", build_date); + PKG_INFO_STRING("\npackager = ", packager); + PKG_INFO_INT("\nsize = ", size); + PKG_INFO_STRING("\narch = ", arch); + PKG_INFO_ARRAY("\nlicense = ", licenses); + PKG_INFO_ARRAY("\nreplaces = ", replaces); + PKG_INFO_ARRAY("\ngroup = ", groups); + PKG_INFO_ARRAY("\nconflict = ", conflicts); + PKG_INFO_ARRAY("\nprovides = ", provides); + PKG_INFO_ARRAY("\ndepend = ", depends); + PKG_INFO_ARRAY("\noptdepend = ", optdepends); + PKG_INFO_ARRAY("\nmakedepend = ", makedepends); + PKG_INFO_ARRAY("\ncheckdepend = ", checkdepends); - 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; }