libvieter/src/package/vieter_package.c

264 lines
8.5 KiB
C

#include <archive.h>
#include <archive_entry.h>
#include <stdbool.h>
#include <stdlib.h>
#include "sha256.h"
#include "vieter_package_internal.h"
#define ADD_STRING(section, field) \
if (pkg_info->field != 0) { \
size_to_be_written = \
snprintf(aux, small_buff_size, section, pkg_info->field); \
if (size_to_be_written > small_buff_size) { \
aux = realloc(aux, size_to_be_written + 1); \
small_buff_size = size_to_be_written + 1; \
snprintf(aux, small_buff_size, section, pkg_info->field); \
} \
if (buff_size < strlen(description) + small_buff_size + 1) { \
description = realloc(description, buff_size * 2); \
buff_size *= 2; \
} \
strcat(description, aux); \
}
#define ADD_ARRAY(section, field) \
i = 0; \
if (pkg_info->field != NULL) { \
ADD_STRING(section, field->array[i]); \
i++; \
while (pkg_info->field->array[i] != NULL) { \
ADD_STRING("\n%s", field->array[i]); \
i++; \
} \
}
static char *ignored_names[5] = {".BUILDINFO", ".INSTALL", ".MTREE", ".PKGINFO",
".CHANGELOG"};
static size_t ignored_words_len = sizeof(ignored_names) / sizeof(char *);
vieter_package *vieter_package_init() {
return calloc(sizeof(vieter_package_info), 1);
}
vieter_package_error vieter_package_read_archive(vieter_package *pkg,
const char *pkg_path) {
struct archive *a = archive_read_new();
struct archive_entry *entry;
// 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 vieter_package_unarchive_error;
}
int compression_code = archive_filter_code(a, 0);
const char *path_name;
vieter_package_info *pkg_info = NULL;
vieter_dynarray *files = vieter_dynarray_init(16);
vieter_dynarray_add(files, "%FILES%");
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) {
vieter_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 + 1);
archive_read_data(a, buf, size);
buf[size] = '\0';
// Parse package vieter_package_info string into a struct
pkg_info = vieter_package_info_init();
vieter_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) {
// errno is set if stat() fails; the calling function should check
// the value of errno in case vieter_package_stat_error is returned
return vieter_package_stat_error;
}
pkg_info->csize = stats.st_size;
archive_read_free(a);
// Create final return value
pkg->path = strdup(pkg_path);
pkg->info = pkg_info;
pkg->files = files;
pkg->compression = compression_code;
return vieter_package_ok;
}
void vieter_package_sha256sum(vieter_package *pkg, char *res) {
FILE *f = fopen(pkg->path, "r");
// Try to read 100KiB at a time
unsigned char *in = malloc(102400);
// Actual number of bytes read
size_t read_size;
SHA256_CTX *ctx = malloc(sizeof(SHA256_CTX));
sha256_init(ctx);
while ((read_size = fread(in, 1, 102400, f)) != 0) {
sha256_update(ctx, in, read_size);
}
unsigned char hash[SHA256_BLOCK_SIZE];
sha256_final(ctx, hash);
fclose(f);
free(in);
free(ctx);
// We need to convert the bytes in the hash to get a string representation of
// its hex values i.e. turn 1001 1111 into the string "9f" Each byte of the
// hash is going to turn into two bytes in the final string so we are going to
// convert each half byte into a char
unsigned int half_byte = 0;
int j = 0;
// We advance 2 bytes in the string for every one byte of the hash
for (int i = 0; i < SHA256_BLOCK_SIZE; i++) {
// We transform the first half byte into the second character to keep
// each byte from becoming reversed in the final string
half_byte = hash[i] & 0b1111;
if (half_byte < 10) {
res[j + 1] = half_byte + 48;
} else {
res[j + 1] = half_byte + 87;
}
hash[i] = hash[i] >> 4;
half_byte = hash[i] & 0b1111;
if (half_byte < 10) {
res[j] = half_byte + 48;
} else {
res[j] = half_byte + 87;
}
j += 2;
}
res[j] = '\0';
}
char *vieter_package_to_description(vieter_package *pkg) {
vieter_package_info *pkg_info = pkg->info;
size_t buff_size = 1024;
int small_buff_size = 128;
int size_to_be_written;
char *aux = malloc(sizeof(char) * small_buff_size);
char *description = malloc(sizeof(char) * buff_size);
// Helper variable for ADD_ARRAY macro
int i;
// special case for FILENAME
char *ext = NULL;
switch (pkg->compression) {
case 0:
ext = ".tar";
break;
case 1:
ext = ".tar.gz";
break;
case 6:
ext = ".tar.xz";
break;
case 14:
ext = ".tar.zst";
break;
}
size_to_be_written =
snprintf(aux, small_buff_size, "%%FILENAME%%\n%s-%s-%s.pkg%s",
pkg_info->name, pkg_info->version, pkg_info->arch, ext);
// We neither want to let an arbritrarily long input to overflow the buffer
// nor to truncate perfectly valid inputs
if (size_to_be_written > small_buff_size) {
aux = realloc(aux, size_to_be_written + 1);
small_buff_size = size_to_be_written + 1;
snprintf(aux, small_buff_size, "%%FILENAME%%\n%s-%s-%s.pkg.tar.zst",
pkg_info->name, pkg_info->version, pkg_info->arch);
}
strcpy(description, aux);
ADD_STRING("\n\n%%NAME%%\n%s", name);
ADD_STRING("\n\n%%BASE%%\n%s", base);
ADD_STRING("\n\n%%VERSION%%\n%s", version);
ADD_STRING("\n\n%%DESC%%\n%s", description);
ADD_ARRAY("\n\n%%GROUPS%%\n%s", groups);
ADD_STRING("\n\n%%CSIZE%%\n%ld", csize);
ADD_STRING("\n\n%%ISIZE%%\n%ld", size);
char checksum[SHA256_BLOCK_SIZE * 2 + 1];
vieter_package_sha256sum(pkg, checksum);
snprintf(aux, small_buff_size, "\n\n%%SHA256SUM%%\n%s", checksum);
if (buff_size < strlen(description) + small_buff_size + 1) {
description = realloc(description, buff_size * 2);
buff_size *= 2;
}
strcat(description, aux);
ADD_STRING("\n\n%%URL%%\n%s", url);
ADD_ARRAY("\n\n%%LICENSE%%\n%s", licenses);
ADD_STRING("\n\n%%ARCH%%\n%s", arch);
ADD_STRING("\n\n%%BUILDDATE%%\n%ld", build_date);
ADD_STRING("\n\n%%PACKAGER%%\n%s", packager);
ADD_ARRAY("\n\n%%REPLACES%%\n%s", replaces);
ADD_ARRAY("\n\n%%CONFLICTS%%\n%s", conflicts);
ADD_ARRAY("\n\n%%PROVIDES%%\n%s", provides);
ADD_ARRAY("\n\n%%DEPENDS%%\n%s", depends);
ADD_ARRAY("\n\n%%OPTDEPENDS%%\n%s", optdepends);
ADD_ARRAY("\n\n%%MAKEDEPENDS%%\n%s", makedepends);
ADD_ARRAY("\n\n%%CHECKDEPENDS%%\n%s", checkdepends);
strcat(description, "\n\n");
free(aux);
return description;
}
void vieter_package_free(vieter_package **ptp) {
FREE_STRING((*ptp)->path);
vieter_package_info_free((*ptp)->info);
vieter_dynarray_free((*ptp)->files);
free(*ptp);
*ptp = NULL;
}