From e66b47a0ca6265d1d8ffdb7c73ae087a0e949f7d Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 10:56:35 +0100 Subject: [PATCH 01/27] core/Fix indexer error handling --- src/index.c | 31 ++++++++++++++++++++++++++++++- src/repository.c | 11 +++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/index.c b/src/index.c index 030b22d..c173c96 100644 --- a/src/index.c +++ b/src/index.c @@ -58,6 +58,28 @@ bool indexDependency(KPM* kpm, char* artifact_repository, char* artifact_id, cha bool indexArtifact(KPM* kpm, char* repositoryId, char* packageId, cJSON* artifact, KPMIO* kpmIO) { + if (strlen(cJSON_GetStringValue(cJSON_GetObjectItem(artifact, "url"))) == 0) + { + kpmIO->log(KPM_VERBOSITY_ERROR, "Artifact has an empty URL field"); + return false; + } + + if (cJSON_GetArraySize(cJSON_GetObjectItem(artifact, "version")) != 3) + { + kpmIO->log(KPM_VERBOSITY_ERROR, "Artifact version field is invalid"); + return false; + } + + if ( + cJSON_GetNumberValue(cJSON_GetArrayItem(cJSON_GetObjectItem(artifact, "version"), 0)) < 0 || + cJSON_GetNumberValue(cJSON_GetArrayItem(cJSON_GetObjectItem(artifact, "version"), 1)) < 0 || + cJSON_GetNumberValue(cJSON_GetArrayItem(cJSON_GetObjectItem(artifact, "version"), 2)) < 0 + ) + { + kpmIO->log(KPM_VERBOSITY_ERROR, "Artifact version field contains invalid entries"); + return false; + } + kpmIO->log(KPM_VERBOSITY_DEBUG, " indexing artifact %s (%.0f.%.0f.%.0f)", packageId, cJSON_GetNumberValue(cJSON_GetArrayItem(cJSON_GetObjectItem(artifact, "version"), 0)), cJSON_GetNumberValue(cJSON_GetArrayItem(cJSON_GetObjectItem(artifact, "version"), 1)), cJSON_GetNumberValue(cJSON_GetArrayItem(cJSON_GetObjectItem(artifact, "version"), 2))); const char* zSQL = "INSERT INTO artifacts (url, repository, id, version_major, version_minor, version_patch) VALUES (?, ?, ?, ?, ?, ?);"; sqlite3_stmt* statement; @@ -178,6 +200,7 @@ KPMResult KPM_UpdateIndex(KPM *kpm, KPMIO* kpmIO) return result; } + bool all_ok = true; SimpleGETRequest request; for (size_t i=0; i < repositoryCount; i++) { @@ -259,7 +282,10 @@ KPMResult KPM_UpdateIndex(KPM *kpm, KPMIO* kpmIO) } if (!ok) + { + all_ok = false; sqlite3_exec(kpm->db, "ROLLBACK", NULL, NULL, NULL); + } else sqlite3_exec(kpm->db, "COMMIT", NULL, NULL, NULL); @@ -269,5 +295,8 @@ KPMResult KPM_UpdateIndex(KPM *kpm, KPMIO* kpmIO) KPM_FreeRepositoryList(repositoryCount, repositories); - return KPM_OK; + if (all_ok) + return KPM_OK; + else + return KPM_GENERIC_ERROR; } diff --git a/src/repository.c b/src/repository.c index b7e8839..8e549db 100644 --- a/src/repository.c +++ b/src/repository.c @@ -134,16 +134,23 @@ KPMResult KPM_AddRepository(KPM *kpm, const char *url, Repository* repository, K return KPM_INVALID_RESPONSE_CONTENT; } - kpm_io->log(KPM_VERBOSITY_DEBUG, "ID: %s", cJSON_GetStringValue(cJSON_GetObjectItem(json, "id"))); + char* repo_id = cJSON_GetStringValue(cJSON_GetObjectItem(json, "id")); + kpm_io->log(KPM_VERBOSITY_DEBUG, "ID: %s", repo_id); kpm_io->log(KPM_VERBOSITY_DEBUG, "URL: %s", url); kpm_io->log(KPM_VERBOSITY_DEBUG, "NAME: %s", cJSON_GetStringValue(cJSON_GetObjectItem(json, "name"))); kpm_io->log(KPM_VERBOSITY_DEBUG, "DESC: %s", cJSON_GetStringValue(cJSON_GetObjectItem(json, "description"))); + if (strlen(repo_id) == 0 || repo_id[0] == ' ' || repo_id[strlen(repo_id)-1] == ' ') + { + kpm_io->log(KPM_VERBOSITY_ERROR, "Repository must have a valid id"); + return KPM_PARSE_ERROR; + } + sqlite3_exec(kpm->db, "BEGIN", NULL, NULL, NULL); const char* zSQL = "INSERT INTO repositories (id, url, name, description) VALUES (?, ?, ?, ?);"; sqlite3_stmt* statement; sqlite3_prepare_v2(kpm->db, zSQL, -1, &statement, NULL); - sqlite3_bind_text(statement, 1, cJSON_GetStringValue(cJSON_GetObjectItem(json, "id")), -1, SQLITE_STATIC); + sqlite3_bind_text(statement, 1, repo_id, -1, SQLITE_STATIC); sqlite3_bind_text(statement, 2, url, -1, SQLITE_STATIC); sqlite3_bind_text(statement, 3, cJSON_GetStringValue(cJSON_GetObjectItem(json, "name")), -1, SQLITE_STATIC); sqlite3_bind_text(statement, 4, cJSON_GetStringValue(cJSON_GetObjectItem(json, "description")), -1, SQLITE_STATIC); From 5619540d274b605ed45dcce7ee3752c56c7ff774 Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:24:48 +0100 Subject: [PATCH 02/27] core/Fix package hook path issues --- src/install.c | 14 +++++++++----- src/uninstall.c | 10 ++++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/install.c b/src/install.c index c6a26f8..80e1c54 100644 --- a/src/install.c +++ b/src/install.c @@ -3,6 +3,7 @@ #include "cjson/cJSON.h" #include "kpm/kpm.h" #include +#include #include #include #include @@ -410,6 +411,7 @@ KPMResult Internal_DownloadGraphItems(KPM* kpm, DependencyGraph* graph, size_t d free(path); if (fd == -1) { + kpmIO->log(KPM_VERBOSITY_ERROR, "Could not open file for writing: %s", strerror(errno)); return KPM_FILE_SYSTEM_ERROR; // @TODO } @@ -552,18 +554,20 @@ bool Internal_InstallItem(KPM* kpm, char* repository, char* path, bool installed // If so, run it if (access(installScriptPath, R_OK) == 0) { + free(installScriptPath); kpmIO->log(KPM_VERBOSITY_DEBUG, "Running install script for [%s]", id); // Run install script int result = -1; char* installCommand; if (upgrading) - installCommand = asprintf_hd("KPM_VERSION_MAJOR=%i KPM_VERSION_MINOR=%i KPM_VERSION_PATCH=%i KPM_PLATFORM=\"%s\" sh %s upgrade 2>&1", KPM_VERSION_MAJOR, KPM_VERSION_MINOR, KPM_VERSION_PATCH, KPM_PLATFORM, installScriptPath); + installCommand = asprintf_hd("KPM_VERSION_MAJOR=%i KPM_VERSION_MINOR=%i KPM_VERSION_PATCH=%i KPM_PLATFORM=\"%s\" sh install.sh upgrade 2>&1", KPM_VERSION_MAJOR, KPM_VERSION_MINOR, KPM_VERSION_PATCH, KPM_PLATFORM); else - installCommand = asprintf_hd("KPM_VERSION_MAJOR=%i KPM_VERSION_MINOR=%i KPM_VERSION_PATCH=%i KPM_PLATFORM=\"%s\" sh %s 2>&1", KPM_VERSION_MAJOR, KPM_VERSION_MINOR, KPM_VERSION_PATCH, KPM_PLATFORM, installScriptPath); + installCommand = asprintf_hd("KPM_VERSION_MAJOR=%i KPM_VERSION_MINOR=%i KPM_VERSION_PATCH=%i KPM_PLATFORM=\"%s\" sh install.sh 2>&1", KPM_VERSION_MAJOR, KPM_VERSION_MINOR, KPM_VERSION_PATCH, KPM_PLATFORM); + char* old_path = getcwd(NULL, 0); chdir(outPath); - free(installScriptPath); kpmIO->log(KPM_VERBOSITY_INFO, "Running install hooks for %s", id); + kpmIO->log(KPM_VERBOSITY_DEBUG, "Running: %s", installCommand); FILE* stream = popen(installCommand, "r"); free(installCommand); if (stream != NULL) @@ -580,13 +584,13 @@ bool Internal_InstallItem(KPM* kpm, char* repository, char* path, bool installed kpmIO->log(KPM_VERBOSITY_ERROR, "Could not run script - POPEN FAILURE"); } - chdir("/"); + chdir(old_path); + free(old_path); if (result != 0) { // The install hook failed kpmIO->log(KPM_VERBOSITY_ERROR, "Could not execute install hook for [%s]", id); Internal_RunUninstallHook(outPath, id, false, kpmIO); - chdir("/"); rmdir_r(outPath); free(outPath); free(manifest); diff --git a/src/uninstall.c b/src/uninstall.c index 393ea34..1ff6fbf 100644 --- a/src/uninstall.c +++ b/src/uninstall.c @@ -12,18 +12,19 @@ KPMResult Internal_RunUninstallHook(const char* outPath, const char* packageId, if (access(uninstallScriptPath, R_OK) == 0) { + free(uninstallScriptPath); kpm_io->log(KPM_VERBOSITY_DEBUG, "Running uninstall script for [%s]", packageId); // Run uninstall script int result = -1; char* uninstallCommand; if (upgrading) - uninstallCommand = asprintf_hd("KPM_VERSION_MAJOR=%i KPM_VERSION_MINOR=%i KPM_VERSION_PATCH=%i KPM_PLATFORM=\"%s\" sh %s upgrade 2>&1", KPM_VERSION_MAJOR, KPM_VERSION_MINOR, KPM_VERSION_PATCH, KPM_PLATFORM, uninstallScriptPath); + uninstallCommand = asprintf_hd("KPM_VERSION_MAJOR=%i KPM_VERSION_MINOR=%i KPM_VERSION_PATCH=%i KPM_PLATFORM=\"%s\" sh uninstall.sh upgrade 2>&1", KPM_VERSION_MAJOR, KPM_VERSION_MINOR, KPM_VERSION_PATCH, KPM_PLATFORM); else - uninstallCommand = asprintf_hd("KPM_VERSION_MAJOR=%i KPM_VERSION_MINOR=%i KPM_VERSION_PATCH=%i KPM_PLATFORM=\"%s\" sh %s 2>&1", KPM_VERSION_MAJOR, KPM_VERSION_MINOR, KPM_VERSION_PATCH, KPM_PLATFORM, uninstallScriptPath); + uninstallCommand = asprintf_hd("KPM_VERSION_MAJOR=%i KPM_VERSION_MINOR=%i KPM_VERSION_PATCH=%i KPM_PLATFORM=\"%s\" sh uninstall.sh 2>&1", KPM_VERSION_MAJOR, KPM_VERSION_MINOR, KPM_VERSION_PATCH, KPM_PLATFORM); + char* old_path = getcwd(NULL, 0); chdir(outPath); - free(uninstallScriptPath); kpm_io->log(KPM_VERBOSITY_INFO, "Running uninstall hooks for %s", packageId); FILE* stream = popen(uninstallCommand, "r"); free(uninstallCommand); @@ -41,7 +42,8 @@ KPMResult Internal_RunUninstallHook(const char* outPath, const char* packageId, kpm_io->log(KPM_VERBOSITY_ERROR, "Could not run script - POPEN FAILURE"); } - chdir("/"); + chdir(old_path); + free(old_path); if (result != 0) { // The uninstall hook failed From 4f8dd72c3adbc89aebaab218350251519e130c40 Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:31:04 +0100 Subject: [PATCH 03/27] core/Fix uninstall hook issues --- src/uninstall.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/uninstall.c b/src/uninstall.c index 1ff6fbf..0784d83 100644 --- a/src/uninstall.c +++ b/src/uninstall.c @@ -51,6 +51,8 @@ KPMResult Internal_RunUninstallHook(const char* outPath, const char* packageId, return KPM_GENERIC_ERROR; } } + + return KPM_OK; } KPMResult Internal_UninstallPackage(KPM* kpm, const char* packageId, bool upgrading, KPMIO* kpm_io) @@ -126,6 +128,7 @@ KPMResult KPM_UninstallPackages(KPM* kpm, size_t packageCount, const char* packa int result = KPM_OK; for (int i=0; i < packageCount; i++) { + kpmIO->log(KPM_VERBOSITY_DEBUG, "Uninstalling %s", packageIds[i]); if ((result = Internal_UninstallPackage(kpm, packageIds[i], false, kpmIO)) != KPM_OK) break; } From 6be0bdaa7b4c16709d2decd2f9b2c1e526523e3e Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:40:02 +0100 Subject: [PATCH 04/27] core/Implement more package install error checks --- src/index.c | 2 +- src/install.c | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/index.c b/src/index.c index c173c96..17947c2 100644 --- a/src/index.c +++ b/src/index.c @@ -64,7 +64,7 @@ bool indexArtifact(KPM* kpm, char* repositoryId, char* packageId, cJSON* artifac return false; } - if (cJSON_GetArraySize(cJSON_GetObjectItem(artifact, "version")) != 3) + if (!cJSON_IsArray(cJSON_GetObjectItem(artifact, "version")) || cJSON_GetArraySize(cJSON_GetObjectItem(artifact, "version")) != 3) { kpmIO->log(KPM_VERBOSITY_ERROR, "Artifact version field is invalid"); return false; diff --git a/src/install.c b/src/install.c index 80e1c54..d1e902b 100644 --- a/src/install.c +++ b/src/install.c @@ -539,7 +539,43 @@ bool Internal_InstallItem(KPM* kpm, char* repository, char* path, bool installed } char* id = cJSON_GetStringValue(cJSON_GetObjectItem(json, "id")); - kpmIO->log(KPM_VERBOSITY_DEBUG, "Installing item with id: %s", id); + if (strlen(id) == 0) + { + kpmIO->log(KPM_VERBOSITY_ERROR, "Package has an empty id"); + free(manifest); + cJSON_Delete(json); + return false; + } + + if (strlen(cJSON_GetStringValue(cJSON_GetObjectItem(json, "name"))) == 0) + { + kpmIO->log(KPM_VERBOSITY_ERROR, "Package has an empty name"); + free(manifest); + cJSON_Delete(json); + return false; + } + + if (!cJSON_IsArray(cJSON_GetObjectItem(json, "version")) || cJSON_GetArraySize(cJSON_GetObjectItem(json, "version")) != 3) + { + kpmIO->log(KPM_VERBOSITY_ERROR, "Package version field is invalid"); + free(manifest); + cJSON_Delete(json); + return false; + } + + if ( + cJSON_GetNumberValue(cJSON_GetArrayItem(cJSON_GetObjectItem(json, "version"), 0)) < 0 || + cJSON_GetNumberValue(cJSON_GetArrayItem(cJSON_GetObjectItem(json, "version"), 1)) < 0 || + cJSON_GetNumberValue(cJSON_GetArrayItem(cJSON_GetObjectItem(json, "version"), 2)) < 0 + ) + { + kpmIO->log(KPM_VERBOSITY_ERROR, "Package version field contains invalid entries"); + free(manifest); + cJSON_Delete(json); + return false; + } + + kpmIO->log(KPM_VERBOSITY_DEBUG, "Installing item with id: '%s'", id); char* outPath = asprintf_hd("%s/%s/", kpm->pkgPath, id); rmdir_r(outPath); // Just in case it exists From 0db2c6d6141f0d9046ba1d74b7a72587a3b9e620 Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:40:45 +0100 Subject: [PATCH 05/27] Implement new test suite --- cli/main.c | 2 + tests/common/kpm_test_common.c | 1 + tests/common/kpm_test_io.h | 1 + tests/kpm_init_test.c | 4 + tests/kpm_local_repo_test.c | 58 +++++++++++++ ...kpm_repo_test.c => kpm_online_repo_test.c} | 2 + tests/kpm_package_test.c | 74 +++++++++++++++++ tests/meson.build | 20 ++++- tests/resources/invalid_package_1/install.sh | 3 + .../resources/invalid_package_1/manifest.json | 14 ++++ tests/resources/invalid_package_2/install.sh | 3 + .../resources/invalid_package_2/manifest.json | 10 +++ .../invalid_repository_1/manifest.json | 1 + .../invalid_repository_2/manifest.json | 42 ++++++++++ .../invalid_repository_3/manifest.json | 38 +++++++++ tests/resources/test_repository/manifest.json | 76 ++++++++++++++++++ .../packages/invalid_package_1.kpkg | Bin 0 -> 312 bytes .../packages/invalid_package_2.kpkg | Bin 0 -> 335 bytes .../validpackage1_1.0.0_kindleany.kpkg | Bin 0 -> 435 bytes .../validpackage2_1.0.0_kindleany.kpkg | Bin 0 -> 444 bytes tests/resources/valid_package_1/install.sh | 3 + tests/resources/valid_package_1/manifest.json | 14 ++++ tests/resources/valid_package_2/install.sh | 3 + tests/resources/valid_package_2/manifest.json | 14 ++++ 24 files changed, 381 insertions(+), 2 deletions(-) create mode 100644 tests/kpm_local_repo_test.c rename tests/{kpm_repo_test.c => kpm_online_repo_test.c} (96%) create mode 100644 tests/kpm_package_test.c create mode 100644 tests/resources/invalid_package_1/install.sh create mode 100644 tests/resources/invalid_package_1/manifest.json create mode 100644 tests/resources/invalid_package_2/install.sh create mode 100644 tests/resources/invalid_package_2/manifest.json create mode 100644 tests/resources/invalid_repository_1/manifest.json create mode 100644 tests/resources/invalid_repository_2/manifest.json create mode 100644 tests/resources/invalid_repository_3/manifest.json create mode 100644 tests/resources/test_repository/manifest.json create mode 100644 tests/resources/test_repository/packages/invalid_package_1.kpkg create mode 100644 tests/resources/test_repository/packages/invalid_package_2.kpkg create mode 100644 tests/resources/test_repository/packages/validpackage1/artifacts/validpackage1_1.0.0_kindleany.kpkg create mode 100644 tests/resources/test_repository/packages/validpackage2/artifacts/validpackage2_1.0.0_kindleany.kpkg create mode 100644 tests/resources/valid_package_1/install.sh create mode 100644 tests/resources/valid_package_1/manifest.json create mode 100644 tests/resources/valid_package_2/install.sh create mode 100644 tests/resources/valid_package_2/manifest.json diff --git a/cli/main.c b/cli/main.c index b844728..ad284b0 100644 --- a/cli/main.c +++ b/cli/main.c @@ -405,5 +405,7 @@ launch:\n\ cleanup: io_cleanup(); KPM_Cleanup(&kpm); + free(kpm.dbPath); + free(kpm.pkgPath); return error; } \ No newline at end of file diff --git a/tests/common/kpm_test_common.c b/tests/common/kpm_test_common.c index fb65d09..61bb655 100644 --- a/tests/common/kpm_test_common.c +++ b/tests/common/kpm_test_common.c @@ -1,4 +1,5 @@ #include "kpm.h" +#include KPM initialise_kpm() { diff --git a/tests/common/kpm_test_io.h b/tests/common/kpm_test_io.h index 991fc4e..f41e7ea 100644 --- a/tests/common/kpm_test_io.h +++ b/tests/common/kpm_test_io.h @@ -1,3 +1,4 @@ #pragma once +#include "kpm.h" extern KPMIO kpm_io; \ No newline at end of file diff --git a/tests/kpm_init_test.c b/tests/kpm_init_test.c index b1dabcb..7b5e510 100644 --- a/tests/kpm_init_test.c +++ b/tests/kpm_init_test.c @@ -1,10 +1,14 @@ #include "kpm.h" #include "common/kpm_test_common.h" #include +#include +#include +#include #include int main(int argc, char* argv[]) { + remove("./repo_test.db"); KPM kpm = initialise_kpm(); size_t repositoryCount; assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); diff --git a/tests/kpm_local_repo_test.c b/tests/kpm_local_repo_test.c new file mode 100644 index 0000000..3ce5514 --- /dev/null +++ b/tests/kpm_local_repo_test.c @@ -0,0 +1,58 @@ +#include "kpm.h" +#include "common/kpm_test_common.h" +#include "common/kpm_test_io.h" +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + char* actual_path = dirname(realpath(argv[0], NULL)); + fprintf(stderr, "%s\n", actual_path); + + char* valid_repo_url; + asprintf(&valid_repo_url, "file://%s/resources/test_repository/manifest.json", actual_path); // We don't need that much portability for our tests + char* invalid_repository_1_url; + asprintf(&invalid_repository_1_url, "file://%s/resources/invalid_repository_1/manifest.json", actual_path); // We don't need that much portability for our tests + char* invalid_repository_2_url; + asprintf(&invalid_repository_2_url, "file://%s/resources/invalid_repository_2/manifest.json", actual_path); // We don't need that much portability for our tests + char* invalid_repository_3_url; + asprintf(&invalid_repository_3_url, "file://%s/resources/invalid_repository_3/manifest.json", actual_path); // We don't need that much portability for our tests + + KPM kpm = initialise_kpm(); + + KPM_RemoveRepository(&kpm, "kindlemodding"); + size_t repositoryCount; + assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert(repositoryCount == 0); + + Repository repository; + assert(KPM_AddRepository(&kpm, invalid_repository_1_url, &repository, &kpm_io) != KPM_OK); + assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert(repositoryCount == 0); + + /*assert(KPM_AddRepository(&kpm, invalid_repository_2_url, &repository, &kpm_io) != KPM_OK); + assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert(repositoryCount == 1); */ // We expected cjson to fail and it doesn't + + assert(KPM_AddRepository(&kpm, invalid_repository_3_url, &repository, &kpm_io) == KPM_OK); + assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert(repositoryCount == 1); + assert(KPM_UpdateIndex(&kpm, &kpm_io) != KPM_OK); // Should fail due to artifact invalid version + assert(KPM_RemoveRepository(&kpm, repository.id) == KPM_OK); + KPM_FreeRepository(&repository); + + assert(KPM_AddRepository(&kpm, valid_repo_url, &repository, &kpm_io) == KPM_OK); + assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert(repositoryCount == 1); + assert(KPM_RemoveRepository(&kpm, repository.id) == KPM_OK); + assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert(repositoryCount == 0); + assert(strcmp(repository.id, "testrepo") == 0); + KPM_FreeRepository(&repository); + + KPM_Cleanup(&kpm); +} \ No newline at end of file diff --git a/tests/kpm_repo_test.c b/tests/kpm_online_repo_test.c similarity index 96% rename from tests/kpm_repo_test.c rename to tests/kpm_online_repo_test.c index 7177717..917e314 100644 --- a/tests/kpm_repo_test.c +++ b/tests/kpm_online_repo_test.c @@ -32,5 +32,7 @@ int main(int argc, char* argv[]) assert(KPM_AddRepository(&kpm, "https://google.com", NULL, &kpm_io) != KPM_OK); assert(KPM_RemoveRepository(&kpm, "thisrepodoesnotexist") == KPM_OK); // @TODO: Should we fail? + assert(KPM_UpdateIndex(&kpm, &kpm_io) == KPM_OK); + KPM_Cleanup(&kpm); } \ No newline at end of file diff --git a/tests/kpm_package_test.c b/tests/kpm_package_test.c new file mode 100644 index 0000000..9e44655 --- /dev/null +++ b/tests/kpm_package_test.c @@ -0,0 +1,74 @@ +#include "kpm.h" +#include "common/kpm_test_common.h" +#include "common/kpm_test_io.h" +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + char* actual_path = dirname(realpath(argv[0], NULL)); + fprintf(stderr, "%s\n", actual_path); + + char* valid_repo_url; + asprintf(&valid_repo_url, "file://%s/resources/test_repository/manifest.json", actual_path); // We don't need that much portability for our tests + + KPM kpm = initialise_kpm(); + + KPM_RemoveRepository(&kpm, "kindlemodding"); + size_t repositoryCount; + assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert(repositoryCount == 0); + + Repository repository; + assert(KPM_AddRepository(&kpm, valid_repo_url, &repository, &kpm_io) == KPM_OK); + assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert(repositoryCount == 1); + + + assert(KPM_UpdateIndex(&kpm, &kpm_io) == KPM_OK); + InstallTarget target = { + .id = "validpackage1", + .repository = NULL, + .version = NULL + }; + assert(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) == KPM_OK); + target.id = "validpackage2"; + assert(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) == KPM_OK); + + char* package_ids[] = { + "validpackage1", + "validpackage2" + }; + assert(KPM_UninstallPackages(&kpm, 2, (const char**) package_ids, &kpm_io) == KPM_OK); + + target.id = "invalidpackage1"; + assert(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) != KPM_OK); + target.id = "invalidpackage2"; + assert(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) != KPM_OK); + + InstallTarget targets[] = { + { + .id = "invalidpackage1", + .repository = NULL, + .version = NULL + }, + { + .id = "invalidpackage2", + .repository = NULL, + .version = NULL + } + }; + assert(KPM_InstallPackages(&kpm, 2, targets, &kpm_io) != KPM_OK); + + assert(KPM_RemoveRepository(&kpm, repository.id) == KPM_OK); + assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert(repositoryCount == 0); + assert(strcmp(repository.id, "testrepo") == 0); + KPM_FreeRepository(&repository); + + KPM_Cleanup(&kpm); +} \ No newline at end of file diff --git a/tests/meson.build b/tests/meson.build index 4c2cdf1..723b039 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,3 +1,7 @@ +run_command( + 'bash', '-c', 'cp -ar $MESON_SOURCE_ROOT/tests/resources $MESON_BUILD_ROOT/tests/' +) + all_test_files = files( 'common/kpm_test_io.c', 'common/kpm_test_common.c' @@ -15,7 +19,19 @@ test( ) test( - 'KPM Repo Test', - executable('kpm_REPO_test', files('kpm_repo_test.c') + all_test_files, dependencies: [kpm_dep], include_directories: all_test_includes), + 'KPM Online Repo Test', + executable('kpm_online_repo_test', files('kpm_online_repo_test.c') + all_test_files, dependencies: [kpm_dep], include_directories: all_test_includes), + is_parallel: false +) + +test( + 'KPM Local Repo Test', + executable('kpm_local_repo_test', files('kpm_local_repo_test.c') + all_test_files, dependencies: [kpm_dep], include_directories: all_test_includes), + is_parallel: false +) + +test( + 'KPM Package Test', + executable('kpm_package_test', files('kpm_package_test.c') + all_test_files, dependencies: [kpm_dep], include_directories: all_test_includes), is_parallel: false ) \ No newline at end of file diff --git a/tests/resources/invalid_package_1/install.sh b/tests/resources/invalid_package_1/install.sh new file mode 100644 index 0000000..926d55d --- /dev/null +++ b/tests/resources/invalid_package_1/install.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +echo hi \ No newline at end of file diff --git a/tests/resources/invalid_package_1/manifest.json b/tests/resources/invalid_package_1/manifest.json new file mode 100644 index 0000000..f359606 --- /dev/null +++ b/tests/resources/invalid_package_1/manifest.json @@ -0,0 +1,14 @@ +{ + "manifest_version": 2, + "id": "", + "name": "", + "author": "", + "description": "", + "version": [ + 1, + 0, + 0 + ], + "dependencies": [], + "supported_platforms": null +} \ No newline at end of file diff --git a/tests/resources/invalid_package_2/install.sh b/tests/resources/invalid_package_2/install.sh new file mode 100644 index 0000000..926d55d --- /dev/null +++ b/tests/resources/invalid_package_2/install.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +echo hi \ No newline at end of file diff --git a/tests/resources/invalid_package_2/manifest.json b/tests/resources/invalid_package_2/manifest.json new file mode 100644 index 0000000..2ccba83 --- /dev/null +++ b/tests/resources/invalid_package_2/manifest.json @@ -0,0 +1,10 @@ +{ + "manifest_version": 2, + "id": "invalidvalidpackage2", + "name": "Invalid Valid Package 2", + "author": "Hackerdude", + "description": "", + "version": [], + "dependencies": [], + "supported_platforms": null +} \ No newline at end of file diff --git a/tests/resources/invalid_repository_1/manifest.json b/tests/resources/invalid_repository_1/manifest.json new file mode 100644 index 0000000..5c1219e --- /dev/null +++ b/tests/resources/invalid_repository_1/manifest.json @@ -0,0 +1 @@ +{"manifest_version": 2, "id": "", "name": "", "description": "", "packages": {}} \ No newline at end of file diff --git a/tests/resources/invalid_repository_2/manifest.json b/tests/resources/invalid_repository_2/manifest.json new file mode 100644 index 0000000..fed5463 --- /dev/null +++ b/tests/resources/invalid_repository_2/manifest.json @@ -0,0 +1,42 @@ +{ + "manifest_version": 2, + "id": "invalidrepo2", + "name": "Invalid Repo", + "description": "Repo only used for tests", + "packages": { + "validpackage1": { + "name": "Valid Package 1", + "author": "Hackerdude", + "description": "", + "artifacts": [ + { + "url": "packages/validpackage1/artifacts/validpackage1_1.0.0_kindleany.kpkg", + "version": [ + 1, + 0, + 0 + ], + "dependencies": [], + "supported_platforms": null + } + ] + }, + "validpackage1": { + "name": "Valid Package 1", + "author": "Hackerdude", + "description": "", + "artifacts": [ + { + "url": "packages/validpackage1/artifacts/validpackage1_1.0.0_kindleany.kpkg", + "version": [ + 1, + 0, + 0 + ], + "dependencies": [], + "supported_platforms": null + } + ] + } + } +} \ No newline at end of file diff --git a/tests/resources/invalid_repository_3/manifest.json b/tests/resources/invalid_repository_3/manifest.json new file mode 100644 index 0000000..320b780 --- /dev/null +++ b/tests/resources/invalid_repository_3/manifest.json @@ -0,0 +1,38 @@ +{ + "manifest_version": 2, + "id": "invalidrepo3", + "name": "Invalid Repo", + "description": "Repo only used for tests", + "packages": { + "validpackage1": { + "name": "Valid Package 1", + "author": "Hackerdude", + "description": "", + "artifacts": [ + { + "url": "packages/validpackage1/artifacts/validpackage1_1.0.0_kindleany.kpkg", + "version": [ + 1, + 0, + 0 + ], + "dependencies": [], + "supported_platforms": null + } + ] + }, + "validpackage2": { + "name": "Valid Package 2", + "author": "Hackerdude", + "description": "", + "artifacts": [ + { + "url": "", + "version": [], + "dependencies": [], + "supported_platforms": null + } + ] + } + } +} \ No newline at end of file diff --git a/tests/resources/test_repository/manifest.json b/tests/resources/test_repository/manifest.json new file mode 100644 index 0000000..748ff02 --- /dev/null +++ b/tests/resources/test_repository/manifest.json @@ -0,0 +1,76 @@ +{ + "manifest_version": 2, + "id": "testrepo", + "name": "Test Repo", + "description": "Repo only used for tests", + "packages": { + "validpackage1": { + "name": "Valid Package 1", + "author": "Hackerdude", + "description": "", + "artifacts": [ + { + "url": "packages/validpackage1/artifacts/validpackage1_1.0.0_kindleany.kpkg", + "version": [ + 1, + 0, + 0 + ], + "dependencies": [], + "supported_platforms": null + } + ] + }, + "validpackage2": { + "name": "Valid Package 2", + "author": "Hackerdude", + "description": "", + "artifacts": [ + { + "url": "packages/validpackage2/artifacts/validpackage2_1.0.0_kindleany.kpkg", + "version": [ + 1, + 0, + 0 + ], + "dependencies": [], + "supported_platforms": null + } + ] + }, + "invalidpackage1": { + "name": "Invalid Package 1", + "author": "Hackerdude", + "description": "", + "artifacts": [ + { + "url": "packages/invalid_package_1.kpkg", + "version": [ + 1, + 0, + 0 + ], + "dependencies": [], + "supported_platforms": null + } + ] + }, + "invalidpackage2": { + "name": "Invalid Package 2", + "author": "Hackerdude", + "description": "", + "artifacts": [ + { + "url": "packages/invalid_package_2.kpkg", + "version": [ + 1, + 0, + 0 + ], + "dependencies": [], + "supported_platforms": null + } + ] + } + } +} \ No newline at end of file diff --git a/tests/resources/test_repository/packages/invalid_package_1.kpkg b/tests/resources/test_repository/packages/invalid_package_1.kpkg new file mode 100644 index 0000000000000000000000000000000000000000..3247f62dfe415285ce6c7f6195454453c6de3f43 GIT binary patch literal 312 zcmV-80muFyiwFP!000001MSqyPJ}QN24L1aMaAs|N~Ms@s~9t>v=ge(k`~+;-`&Ds zNG6(z3!JF`x1g8P(uUKLX3^$oP;ow$dBy2c=5zld$~n8^w5Tc;;yGh9j|ks>Yf&`% zK{!I9M%2&B$x*7$$6bp5Qfbx+qmRlD>65o+lQpIUmqqbq{#jYA`R8TGe=!{cLO0pX znSY-D*O-vx;|LsHl=Iq}WKY;`3TPRsNix@s=+xR0zaxyAMr5 z^4(m}r3lZ5Wpb~KRHoL-PlHdre(Zbe1|<)DD~6+W-4rxq+s1F#{%n6nnrRbmK-;Fi z*=mhT_n$I$_Wv(J9QXex+}>o5+GM_oV^uenH2T^*0ssI200000000000001h?|KKR K*Rz8FC;$L=sG6Pt literal 0 HcmV?d00001 diff --git a/tests/resources/test_repository/packages/invalid_package_2.kpkg b/tests/resources/test_repository/packages/invalid_package_2.kpkg new file mode 100644 index 0000000000000000000000000000000000000000..06750ac094f4c31d9a136b7532e296f1e42f5d3d GIT binary patch literal 335 zcmV-V0kHlbiwFP!000001MSq!PQ)M(2XM}P3anStQnqXppFnS(ycn~=!dkSY4YYeP zzPtU>O*Wd1hq~GL{~drZlQ3aQqnIp?^)exbImrZ@=VaWUkwoy7U}=`EGQtwhlY~&Q zd})MqzLkzrQpo&OJJl)uecrM7nTp~{TC>%@jb40h7dc~S@H$O@?Ei|3egCte|7n)2 z31y2^bKsx%|1F?2{QLwqJMFxwZFo)j)etbMUxvo+WNFkmH2pQmXU)SgZe^v1jN2)L z-jCzmltbqv*|kOOhP0cnJDeg)``nqPosJBr&3z3Ik8@U|tdfRk2~(<#h9gzPO4A{g=LBHyW0ZWE^3oS7pL!cpk3eV z8c1KBANaxTf^FxV<-B!%1Ri}hVjM?T^1huR7N1<866=6wOf8iHhHl zG)pkf2+4e!W$9IvxwgIF{8dbfHCLVgtZHdXK9VWwBpPa*q%odGvak6tj2#lwFvc;# zNuujNN^s<$lhov{{tLEx;cedM{Np@Z{7k*N2zk3pu-x!giCTKsh|Wwv z&xdJq!)4CpN^oV8$5qugP2F}pUp6J{)^%H%pzO=ieY3qhaD3?h(EktpKaH}~Pce=3 z4Ep~+Af&$kW$CNJmU%o=)L->~OzG7Bqck1rUthlj`u{0hUIdRq2C8sfzA9=|2-^+- d000000000000000008ity#r5UwfX=k000ZZ)$afR literal 0 HcmV?d00001 diff --git a/tests/resources/test_repository/packages/validpackage2/artifacts/validpackage2_1.0.0_kindleany.kpkg b/tests/resources/test_repository/packages/validpackage2/artifacts/validpackage2_1.0.0_kindleany.kpkg new file mode 100644 index 0000000000000000000000000000000000000000..fcf09b94b8ae0d5b528c4260c498e763a0f9f37f GIT binary patch literal 444 zcmV;t0Ym=%H+ooF000E$*0e?f03iVu0001VFXf})C;tI^T>uvg+ysD7GA8aFeH-)g z%QNik_&4zC)_Xa@yc<>@b#9&+*YTC}t9^+))Nz)s;*jiq^x%qc>XUG-LV!NN9)JjbIVs<=dQO)li?IYaMIc zC>|(u;Xe8W49$G6c~-%y%Dk9z716U$McAJk$$5Y?=oD&UY$ieGNvsCXvSZKpp6%Kz zlUw{#(`|J?pA-D+{PRDn0u;ENJuz*LW*RP~IXs?8Z6e1Z8;R}HDSW}aHn(wh#Cj?rX`i*Jh`@D5dQn6)F!WFmqY2P)Fx7e+~N78cCgsJpwGn5ky m0001RhhW#iUeO%@0ha@SPyhgZ5OW2w#Ao{g000001X)_=QPz_H literal 0 HcmV?d00001 diff --git a/tests/resources/valid_package_1/install.sh b/tests/resources/valid_package_1/install.sh new file mode 100644 index 0000000..926d55d --- /dev/null +++ b/tests/resources/valid_package_1/install.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +echo hi \ No newline at end of file diff --git a/tests/resources/valid_package_1/manifest.json b/tests/resources/valid_package_1/manifest.json new file mode 100644 index 0000000..b055304 --- /dev/null +++ b/tests/resources/valid_package_1/manifest.json @@ -0,0 +1,14 @@ +{ + "manifest_version": 2, + "id": "validpackage1", + "name": "Valid Package 1", + "author": "Hackerdude", + "description": "", + "version": [ + 1, + 0, + 0 + ], + "dependencies": [], + "supported_platforms": null +} \ No newline at end of file diff --git a/tests/resources/valid_package_2/install.sh b/tests/resources/valid_package_2/install.sh new file mode 100644 index 0000000..926d55d --- /dev/null +++ b/tests/resources/valid_package_2/install.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +echo hi \ No newline at end of file diff --git a/tests/resources/valid_package_2/manifest.json b/tests/resources/valid_package_2/manifest.json new file mode 100644 index 0000000..e4f4239 --- /dev/null +++ b/tests/resources/valid_package_2/manifest.json @@ -0,0 +1,14 @@ +{ + "manifest_version": 1, + "id": "validpackage2", + "name": "Valid Package 2", + "author": "Hackerdude", + "description": "", + "version": [ + 1, + 0, + 0 + ], + "dependencies": [], + "supported_platforms": null +} \ No newline at end of file From 1a261878133da3d7ad8566fcf32106251a822c48 Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:41:22 +0100 Subject: [PATCH 06/27] cli/Fix cleanup issue --- cli/main.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/cli/main.c b/cli/main.c index ad284b0..b844728 100644 --- a/cli/main.c +++ b/cli/main.c @@ -405,7 +405,5 @@ launch:\n\ cleanup: io_cleanup(); KPM_Cleanup(&kpm); - free(kpm.dbPath); - free(kpm.pkgPath); return error; } \ No newline at end of file From b4aca6d5454a0eda571d1251e3bfb6aa7e91c06f Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:43:49 +0100 Subject: [PATCH 07/27] CI/Implement tests as separate action --- .github/workflows/tests.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..f2ab5da --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,31 @@ +name: Tests + +on: + push: + pull_request: + workflow_dispatch: + workflow_call: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + - name: Install Meson + shell: bash + run: | + sudo apt update + sudo apt install python3 python3-pip python3-setuptools python3-wheel ninja-build + pip3 install meson + - name: Build + shell: bash + run: | + meson setup --reconfigure --buildtype=release builddir + meson test -C builddir + - uses: actions/upload-artifact@v4 + with: + name: testlog.txt + path: builddir/meson-logs/testlog.txt \ No newline at end of file From 0c913ed8c0f0be74757531e264e0bef64103e877 Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:55:12 +0100 Subject: [PATCH 08/27] CI/Try to fix issues when compiling gnu23 --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f2ab5da..b32df69 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,8 @@ jobs: shell: bash run: | sudo apt update - sudo apt install python3 python3-pip python3-setuptools python3-wheel ninja-build + sudo apt upgrade + sudo apt install python3 python3-pip python3-setuptools python3-wheel ninja-build gcc pip3 install meson - name: Build shell: bash From cdf78ed0a1d9a8e320cf5346bdd215f76e24485a Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:59:36 +0100 Subject: [PATCH 09/27] CI/Try to fix issues when compiling gnu23 --- .github/workflows/tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b32df69..90db023 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,8 +18,7 @@ jobs: shell: bash run: | sudo apt update - sudo apt upgrade - sudo apt install python3 python3-pip python3-setuptools python3-wheel ninja-build gcc + sudo apt install gcc-15 g++-15 python3 python3-pip python3-setuptools python3-wheel ninja-build pip3 install meson - name: Build shell: bash From 2103c3557a3e0a073d8cf02d61e4918b2f3c9a83 Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 12:01:58 +0100 Subject: [PATCH 10/27] CI/Use newer Ubuntu --- .github/workflows/main.yml | 2 +- .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 469dd3f..4e6b704 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v6 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 90db023..38bfa14 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v6 From 7a8b6ecb68e18a819ea447d908fa48a1d14b766c Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 12:06:42 +0100 Subject: [PATCH 11/27] CI/Fix more test issues --- .github/workflows/tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 38bfa14..c14b01a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,8 +24,10 @@ jobs: shell: bash run: | meson setup --reconfigure --buildtype=release builddir - meson test -C builddir + cd builddir + meson test - uses: actions/upload-artifact@v4 + if: always() with: name: testlog.txt path: builddir/meson-logs/testlog.txt \ No newline at end of file From 6fb72bad2a20fc92f5718c6cd56d08f861bef015 Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 12:09:53 +0100 Subject: [PATCH 12/27] tests/Improve test portability --- tests/common/kpm_test_common.c | 35 ++++++++++++++++++++++++++++++++++ tests/common/kpm_test_common.h | 7 ++++++- tests/kpm_local_repo_test.c | 12 ++++-------- tests/kpm_package_test.c | 3 +-- 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/tests/common/kpm_test_common.c b/tests/common/kpm_test_common.c index 61bb655..0f032d7 100644 --- a/tests/common/kpm_test_common.c +++ b/tests/common/kpm_test_common.c @@ -1,4 +1,5 @@ #include "kpm.h" +#include #include KPM initialise_kpm() @@ -11,4 +12,38 @@ KPM initialise_kpm() KPM_Initialise(&kpm); return kpm; +} + +/** + * @brief vasprintf implementation + * + * @param format + * @param args + * @return char* + */ +char* vasprintf_hd(const char* format, va_list args) +{ + va_list args2; + va_copy (args2, args); + int size = vsnprintf(NULL, 0, format, args) + 1; + char* str = malloc(size); + vsnprintf(str, size, format, args2); + va_end(args2); + return str; +} + +/** + * @brief asprintf implementation + * + * @param format + * @param ... + * @return char* + */ +char* asprintf_hd(const char* format, ...) +{ + va_list args; + va_start(args, format); + char* result = vasprintf_hd(format, args); + va_end(args); + return result; } \ No newline at end of file diff --git a/tests/common/kpm_test_common.h b/tests/common/kpm_test_common.h index 31a7e2d..adbea26 100644 --- a/tests/common/kpm_test_common.h +++ b/tests/common/kpm_test_common.h @@ -1,4 +1,9 @@ #pragma once #include "kpm.h" +#include +#include -KPM initialise_kpm(); \ No newline at end of file +KPM initialise_kpm(); + +char* vasprintf_hd(const char* format, va_list args); +char* asprintf_hd(const char* format, ...); \ No newline at end of file diff --git a/tests/kpm_local_repo_test.c b/tests/kpm_local_repo_test.c index 3ce5514..f809bb4 100644 --- a/tests/kpm_local_repo_test.c +++ b/tests/kpm_local_repo_test.c @@ -13,14 +13,10 @@ int main(int argc, char* argv[]) char* actual_path = dirname(realpath(argv[0], NULL)); fprintf(stderr, "%s\n", actual_path); - char* valid_repo_url; - asprintf(&valid_repo_url, "file://%s/resources/test_repository/manifest.json", actual_path); // We don't need that much portability for our tests - char* invalid_repository_1_url; - asprintf(&invalid_repository_1_url, "file://%s/resources/invalid_repository_1/manifest.json", actual_path); // We don't need that much portability for our tests - char* invalid_repository_2_url; - asprintf(&invalid_repository_2_url, "file://%s/resources/invalid_repository_2/manifest.json", actual_path); // We don't need that much portability for our tests - char* invalid_repository_3_url; - asprintf(&invalid_repository_3_url, "file://%s/resources/invalid_repository_3/manifest.json", actual_path); // We don't need that much portability for our tests + char* valid_repo_url = asprintf_hd("file://%s/resources/test_repository/manifest.json", actual_path); + char* invalid_repository_1_url = asprintf_hd("file://%s/resources/invalid_repository_1/manifest.json", actual_path); + //char* invalid_repository_2_url = asprintf_hd("file://%s/resources/invalid_repository_2/manifest.json", actual_path); + char* invalid_repository_3_url = asprintf_hd("file://%s/resources/invalid_repository_3/manifest.json", actual_path); KPM kpm = initialise_kpm(); diff --git a/tests/kpm_package_test.c b/tests/kpm_package_test.c index 9e44655..4770d67 100644 --- a/tests/kpm_package_test.c +++ b/tests/kpm_package_test.c @@ -13,8 +13,7 @@ int main(int argc, char* argv[]) char* actual_path = dirname(realpath(argv[0], NULL)); fprintf(stderr, "%s\n", actual_path); - char* valid_repo_url; - asprintf(&valid_repo_url, "file://%s/resources/test_repository/manifest.json", actual_path); // We don't need that much portability for our tests + char* valid_repo_url = asprintf_hd("file://%s/resources/test_repository/manifest.json", actual_path); // We don't need that much portability for our tests KPM kpm = initialise_kpm(); From 98026575f9b0812a3eaa69e3cc3f31d464b5e26e Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 12:36:19 +0100 Subject: [PATCH 13/27] Update fbink --- subprojects/fbink.wrap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/fbink.wrap b/subprojects/fbink.wrap index 9f93deb..84c3647 100644 --- a/subprojects/fbink.wrap +++ b/subprojects/fbink.wrap @@ -1,6 +1,6 @@ [wrap-git] url = https://github.com/KindleModding/FBInk.git -revision = 87b7ade3314d1d4ae77a4a2b24c130c4d1d5fe04 +revision = 76557104a55ea163769464e59668e26f6af299ef depth = 1 clone-recursive = true patch_directory = fbink/overlay From 8af58bec28ce2b1579d0d7c283587f80a38d9735 Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:45:22 +0100 Subject: [PATCH 14/27] Improve test logging --- tests/kpm_local_repo_test.c | 8 ++++++++ tests/kpm_online_repo_test.c | 7 +++++++ tests/kpm_package_test.c | 14 ++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/tests/kpm_local_repo_test.c b/tests/kpm_local_repo_test.c index f809bb4..725990f 100644 --- a/tests/kpm_local_repo_test.c +++ b/tests/kpm_local_repo_test.c @@ -20,11 +20,13 @@ int main(int argc, char* argv[]) KPM kpm = initialise_kpm(); + kpm_io.log(KPM_VERBOSITY_INFO, "Removing default repository"); KPM_RemoveRepository(&kpm, "kindlemodding"); size_t repositoryCount; assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); assert(repositoryCount == 0); + kpm_io.log(KPM_VERBOSITY_INFO, "Testing invalid repository 1 add"); Repository repository; assert(KPM_AddRepository(&kpm, invalid_repository_1_url, &repository, &kpm_io) != KPM_OK); assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); @@ -34,17 +36,23 @@ int main(int argc, char* argv[]) assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); assert(repositoryCount == 1); */ // We expected cjson to fail and it doesn't + kpm_io.log(KPM_VERBOSITY_INFO, "Testing invalid repository 3 add"); assert(KPM_AddRepository(&kpm, invalid_repository_3_url, &repository, &kpm_io) == KPM_OK); assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); assert(repositoryCount == 1); + kpm_io.log(KPM_VERBOSITY_INFO, "Testing UpdateIndex failure"); assert(KPM_UpdateIndex(&kpm, &kpm_io) != KPM_OK); // Should fail due to artifact invalid version assert(KPM_RemoveRepository(&kpm, repository.id) == KPM_OK); KPM_FreeRepository(&repository); + kpm_io.log(KPM_VERBOSITY_INFO, "Testing adding valid local repository"); assert(KPM_AddRepository(&kpm, valid_repo_url, &repository, &kpm_io) == KPM_OK); + kpm_io.log(KPM_VERBOSITY_INFO, "Listing repositories"); assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); assert(repositoryCount == 1); + kpm_io.log(KPM_VERBOSITY_INFO, "Removing repository"); assert(KPM_RemoveRepository(&kpm, repository.id) == KPM_OK); + kpm_io.log(KPM_VERBOSITY_INFO, "Listing repository"); assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); assert(repositoryCount == 0); assert(strcmp(repository.id, "testrepo") == 0); diff --git a/tests/kpm_online_repo_test.c b/tests/kpm_online_repo_test.c index 917e314..50a5afe 100644 --- a/tests/kpm_online_repo_test.c +++ b/tests/kpm_online_repo_test.c @@ -10,17 +10,20 @@ int main(int argc, char* argv[]) KPM kpm = initialise_kpm(); Repository* repositories; size_t repositoryCount; + kpm_io.log(KPM_VERBOSITY_INFO, "Checking repo count"); assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); assert(repositoryCount == 1); assert(KPM_ListRepositories(&kpm, &repositoryCount, &repositories) == KPM_OK); assert(repositoryCount == 1); + kpm_io.log(KPM_VERBOSITY_INFO, "Checking repo list info"); assert(strcmp(repositories[0].id, "kindlemodding") == 0); assert(strcmp(repositories[0].name, "Official KMC Repo") == 0); assert(strcmp(repositories[0].description, "The official KMC repo") == 0); assert(strcmp(repositories[0].url, "https://repo.kindlemodding.org/manifest.v2.json") == 0); Repository repository; + kpm_io.log(KPM_VERBOSITY_INFO, "Testing GetRepository"); KPM_GetRepository(&kpm, "kindlemodding", &repository); assert(strcmp(repository.id, "kindlemodding") == 0); assert(strcmp(repository.name, "Official KMC Repo") == 0); @@ -29,9 +32,13 @@ int main(int argc, char* argv[]) KPM_FreeRepository(&repository); KPM_FreeRepositoryList(repositoryCount, repositories); + kpm_io.log(KPM_VERBOSITY_INFO, "Testing AddRepository"); assert(KPM_AddRepository(&kpm, "https://google.com", NULL, &kpm_io) != KPM_OK); + + kpm_io.log(KPM_VERBOSITY_INFO, "Testing RemoveRepository"); assert(KPM_RemoveRepository(&kpm, "thisrepodoesnotexist") == KPM_OK); // @TODO: Should we fail? + kpm_io.log(KPM_VERBOSITY_INFO, "Testing UpdateIndex"); assert(KPM_UpdateIndex(&kpm, &kpm_io) == KPM_OK); KPM_Cleanup(&kpm); diff --git a/tests/kpm_package_test.c b/tests/kpm_package_test.c index 4770d67..75f8200 100644 --- a/tests/kpm_package_test.c +++ b/tests/kpm_package_test.c @@ -17,35 +17,45 @@ int main(int argc, char* argv[]) KPM kpm = initialise_kpm(); + kpm_io.log(KPM_VERBOSITY_INFO, "Removing default repository"); KPM_RemoveRepository(&kpm, "kindlemodding"); size_t repositoryCount; assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); assert(repositoryCount == 0); Repository repository; + kpm_io.log(KPM_VERBOSITY_INFO, "Adding valid repository"); assert(KPM_AddRepository(&kpm, valid_repo_url, &repository, &kpm_io) == KPM_OK); assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); assert(repositoryCount == 1); + kpm_io.log(KPM_VERBOSITY_INFO, "Updating index"); assert(KPM_UpdateIndex(&kpm, &kpm_io) == KPM_OK); InstallTarget target = { .id = "validpackage1", .repository = NULL, .version = NULL }; + kpm_io.log(KPM_VERBOSITY_INFO, "Installing validpackage1"); assert(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) == KPM_OK); + + kpm_io.log(KPM_VERBOSITY_INFO, "Installing validpackage2"); target.id = "validpackage2"; assert(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) == KPM_OK); + kpm_io.log(KPM_VERBOSITY_INFO, "Uninstalling both packages"); char* package_ids[] = { "validpackage1", "validpackage2" }; assert(KPM_UninstallPackages(&kpm, 2, (const char**) package_ids, &kpm_io) == KPM_OK); + kpm_io.log(KPM_VERBOSITY_INFO, "Installing invalidpackage1"); target.id = "invalidpackage1"; assert(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) != KPM_OK); + + kpm_io.log(KPM_VERBOSITY_INFO, "Installing invalidpackage2"); target.id = "invalidpackage2"; assert(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) != KPM_OK); @@ -61,9 +71,13 @@ int main(int argc, char* argv[]) .version = NULL } }; + kpm_io.log(KPM_VERBOSITY_INFO, "Installing both invalidpackage1 and invalidpackage2"); assert(KPM_InstallPackages(&kpm, 2, targets, &kpm_io) != KPM_OK); + kpm_io.log(KPM_VERBOSITY_INFO, "Removing repository"); assert(KPM_RemoveRepository(&kpm, repository.id) == KPM_OK); + + kpm_io.log(KPM_VERBOSITY_INFO, "Listing repositories"); assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); assert(repositoryCount == 0); assert(strcmp(repository.id, "testrepo") == 0); From 75c9356511a80d4c21708cbf87e2ade1db70744a Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:51:48 +0100 Subject: [PATCH 15/27] CI/Upload builddir in test CI --- .github/workflows/tests.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c14b01a..a296586 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,4 +30,9 @@ jobs: if: always() with: name: testlog.txt - path: builddir/meson-logs/testlog.txt \ No newline at end of file + path: builddir/meson-logs/testlog.txt + - uses: actions/upload-artifact@v4 + if: always() + with: + name: builddir + path: builddir \ No newline at end of file From 6a21562a85214e6025762ab6ab3e839171209c54 Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 16:38:17 +0100 Subject: [PATCH 16/27] CI/Implement assert_hd so that tests can run in non-debug builds --- tests/common/kpm_test_common.h | 11 +++++++- tests/kpm_init_test.c | 8 +++--- tests/kpm_local_repo_test.c | 47 +++++++++++++++++++--------------- tests/kpm_online_repo_test.c | 32 ++++++++++++----------- tests/kpm_package_test.c | 34 ++++++++++++------------ 5 files changed, 77 insertions(+), 55 deletions(-) diff --git a/tests/common/kpm_test_common.h b/tests/common/kpm_test_common.h index adbea26..c569875 100644 --- a/tests/common/kpm_test_common.h +++ b/tests/common/kpm_test_common.h @@ -6,4 +6,13 @@ KPM initialise_kpm(); char* vasprintf_hd(const char* format, va_list args); -char* asprintf_hd(const char* format, ...); \ No newline at end of file +char* asprintf_hd(const char* format, ...); + +void assert_hd(bool assertion) +{ + if (!assertion) + { + fprintf(stderr, "Assertion failed."); + exit(1); + } +} \ No newline at end of file diff --git a/tests/kpm_init_test.c b/tests/kpm_init_test.c index 7b5e510..271d06c 100644 --- a/tests/kpm_init_test.c +++ b/tests/kpm_init_test.c @@ -11,9 +11,11 @@ int main(int argc, char* argv[]) remove("./repo_test.db"); KPM kpm = initialise_kpm(); size_t repositoryCount; - assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); - assert(repositoryCount == 1); + assert_hd(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert_hd(repositoryCount == 1); KPM_Cleanup(&kpm); - assert(access("./repo_test.db", R_OK) == 0); + assert_hd(access("./repo_test.db", R_OK) == 0); + + return 0; } \ No newline at end of file diff --git a/tests/kpm_local_repo_test.c b/tests/kpm_local_repo_test.c index 725990f..0ca82f9 100644 --- a/tests/kpm_local_repo_test.c +++ b/tests/kpm_local_repo_test.c @@ -23,40 +23,47 @@ int main(int argc, char* argv[]) kpm_io.log(KPM_VERBOSITY_INFO, "Removing default repository"); KPM_RemoveRepository(&kpm, "kindlemodding"); size_t repositoryCount; - assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); - assert(repositoryCount == 0); + assert_hd(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert_hd(repositoryCount == 0); kpm_io.log(KPM_VERBOSITY_INFO, "Testing invalid repository 1 add"); Repository repository; - assert(KPM_AddRepository(&kpm, invalid_repository_1_url, &repository, &kpm_io) != KPM_OK); - assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); - assert(repositoryCount == 0); + assert_hd(KPM_AddRepository(&kpm, invalid_repository_1_url, &repository, &kpm_io) != KPM_OK); + assert_hd(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert_hd(repositoryCount == 0); - /*assert(KPM_AddRepository(&kpm, invalid_repository_2_url, &repository, &kpm_io) != KPM_OK); - assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); - assert(repositoryCount == 1); */ // We expected cjson to fail and it doesn't + /*assert_hd(KPM_AddRepository(&kpm, invalid_repository_2_url, &repository, &kpm_io) != KPM_OK); + assert_hd(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert_hd(repositoryCount == 1); */ // We expected cjson to fail and it doesn't kpm_io.log(KPM_VERBOSITY_INFO, "Testing invalid repository 3 add"); - assert(KPM_AddRepository(&kpm, invalid_repository_3_url, &repository, &kpm_io) == KPM_OK); - assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); - assert(repositoryCount == 1); + assert_hd(KPM_AddRepository(&kpm, invalid_repository_3_url, &repository, &kpm_io) == KPM_OK); + assert_hd(strcmp(repository.id, "invalidrepo3") == 0); + assert_hd(strcmp(repository.name, "Invalid Repo") == 0); + assert_hd(strcmp(repository.description, "Repo only used for tests") == 0); + assert_hd(strcmp(repository.url, invalid_repository_3_url) == 0); + + assert_hd(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert_hd(repositoryCount == 1); kpm_io.log(KPM_VERBOSITY_INFO, "Testing UpdateIndex failure"); - assert(KPM_UpdateIndex(&kpm, &kpm_io) != KPM_OK); // Should fail due to artifact invalid version - assert(KPM_RemoveRepository(&kpm, repository.id) == KPM_OK); + assert_hd(KPM_UpdateIndex(&kpm, &kpm_io) != KPM_OK); // Should fail due to artifact invalid version + assert_hd(KPM_RemoveRepository(&kpm, repository.id) == KPM_OK); KPM_FreeRepository(&repository); kpm_io.log(KPM_VERBOSITY_INFO, "Testing adding valid local repository"); - assert(KPM_AddRepository(&kpm, valid_repo_url, &repository, &kpm_io) == KPM_OK); + assert_hd(KPM_AddRepository(&kpm, valid_repo_url, &repository, &kpm_io) == KPM_OK); kpm_io.log(KPM_VERBOSITY_INFO, "Listing repositories"); - assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); - assert(repositoryCount == 1); + assert_hd(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert_hd(repositoryCount == 1); kpm_io.log(KPM_VERBOSITY_INFO, "Removing repository"); - assert(KPM_RemoveRepository(&kpm, repository.id) == KPM_OK); + assert_hd(KPM_RemoveRepository(&kpm, repository.id) == KPM_OK); kpm_io.log(KPM_VERBOSITY_INFO, "Listing repository"); - assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); - assert(repositoryCount == 0); - assert(strcmp(repository.id, "testrepo") == 0); + assert_hd(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert_hd(repositoryCount == 0); + assert_hd(strcmp(repository.id, "testrepo") == 0); KPM_FreeRepository(&repository); KPM_Cleanup(&kpm); + + return 0; } \ No newline at end of file diff --git a/tests/kpm_online_repo_test.c b/tests/kpm_online_repo_test.c index 50a5afe..c641194 100644 --- a/tests/kpm_online_repo_test.c +++ b/tests/kpm_online_repo_test.c @@ -11,35 +11,37 @@ int main(int argc, char* argv[]) Repository* repositories; size_t repositoryCount; kpm_io.log(KPM_VERBOSITY_INFO, "Checking repo count"); - assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); - assert(repositoryCount == 1); - assert(KPM_ListRepositories(&kpm, &repositoryCount, &repositories) == KPM_OK); - assert(repositoryCount == 1); + assert_hd(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert_hd(repositoryCount == 1); + assert_hd(KPM_ListRepositories(&kpm, &repositoryCount, &repositories) == KPM_OK); + assert_hd(repositoryCount == 1); kpm_io.log(KPM_VERBOSITY_INFO, "Checking repo list info"); - assert(strcmp(repositories[0].id, "kindlemodding") == 0); - assert(strcmp(repositories[0].name, "Official KMC Repo") == 0); - assert(strcmp(repositories[0].description, "The official KMC repo") == 0); - assert(strcmp(repositories[0].url, "https://repo.kindlemodding.org/manifest.v2.json") == 0); + assert_hd(strcmp(repositories[0].id, "kindlemodding") == 0); + assert_hd(strcmp(repositories[0].name, "Official KMC Repo") == 0); + assert_hd(strcmp(repositories[0].description, "The official KMC repo") == 0); + assert_hd(strcmp(repositories[0].url, "https://repo.kindlemodding.org/manifest.v2.json") == 0); Repository repository; kpm_io.log(KPM_VERBOSITY_INFO, "Testing GetRepository"); KPM_GetRepository(&kpm, "kindlemodding", &repository); - assert(strcmp(repository.id, "kindlemodding") == 0); - assert(strcmp(repository.name, "Official KMC Repo") == 0); - assert(strcmp(repository.description, "The official KMC repo") == 0); - assert(strcmp(repository.url, "https://repo.kindlemodding.org/manifest.v2.json") == 0); + assert_hd(strcmp(repository.id, "kindlemodding") == 0); + assert_hd(strcmp(repository.name, "Official KMC Repo") == 0); + assert_hd(strcmp(repository.description, "The official KMC repo") == 0); + assert_hd(strcmp(repository.url, "https://repo.kindlemodding.org/manifest.v2.json") == 0); KPM_FreeRepository(&repository); KPM_FreeRepositoryList(repositoryCount, repositories); kpm_io.log(KPM_VERBOSITY_INFO, "Testing AddRepository"); - assert(KPM_AddRepository(&kpm, "https://google.com", NULL, &kpm_io) != KPM_OK); + assert_hd(KPM_AddRepository(&kpm, "https://google.com", NULL, &kpm_io) != KPM_OK); kpm_io.log(KPM_VERBOSITY_INFO, "Testing RemoveRepository"); - assert(KPM_RemoveRepository(&kpm, "thisrepodoesnotexist") == KPM_OK); // @TODO: Should we fail? + assert_hd(KPM_RemoveRepository(&kpm, "thisrepodoesnotexist") == KPM_OK); // @TODO: Should we fail? kpm_io.log(KPM_VERBOSITY_INFO, "Testing UpdateIndex"); - assert(KPM_UpdateIndex(&kpm, &kpm_io) == KPM_OK); + assert_hd(KPM_UpdateIndex(&kpm, &kpm_io) == KPM_OK); KPM_Cleanup(&kpm); + + return 0; } \ No newline at end of file diff --git a/tests/kpm_package_test.c b/tests/kpm_package_test.c index 75f8200..de20667 100644 --- a/tests/kpm_package_test.c +++ b/tests/kpm_package_test.c @@ -20,44 +20,44 @@ int main(int argc, char* argv[]) kpm_io.log(KPM_VERBOSITY_INFO, "Removing default repository"); KPM_RemoveRepository(&kpm, "kindlemodding"); size_t repositoryCount; - assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); - assert(repositoryCount == 0); + assert_hd(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert_hd(repositoryCount == 0); Repository repository; kpm_io.log(KPM_VERBOSITY_INFO, "Adding valid repository"); - assert(KPM_AddRepository(&kpm, valid_repo_url, &repository, &kpm_io) == KPM_OK); - assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); - assert(repositoryCount == 1); + assert_hd(KPM_AddRepository(&kpm, valid_repo_url, &repository, &kpm_io) == KPM_OK); + assert_hd(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert_hd(repositoryCount == 1); kpm_io.log(KPM_VERBOSITY_INFO, "Updating index"); - assert(KPM_UpdateIndex(&kpm, &kpm_io) == KPM_OK); + assert_hd(KPM_UpdateIndex(&kpm, &kpm_io) == KPM_OK); InstallTarget target = { .id = "validpackage1", .repository = NULL, .version = NULL }; kpm_io.log(KPM_VERBOSITY_INFO, "Installing validpackage1"); - assert(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) == KPM_OK); + assert_hd(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) == KPM_OK); kpm_io.log(KPM_VERBOSITY_INFO, "Installing validpackage2"); target.id = "validpackage2"; - assert(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) == KPM_OK); + assert_hd(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) == KPM_OK); kpm_io.log(KPM_VERBOSITY_INFO, "Uninstalling both packages"); char* package_ids[] = { "validpackage1", "validpackage2" }; - assert(KPM_UninstallPackages(&kpm, 2, (const char**) package_ids, &kpm_io) == KPM_OK); + assert_hd(KPM_UninstallPackages(&kpm, 2, (const char**) package_ids, &kpm_io) == KPM_OK); kpm_io.log(KPM_VERBOSITY_INFO, "Installing invalidpackage1"); target.id = "invalidpackage1"; - assert(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) != KPM_OK); + assert_hd(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) != KPM_OK); kpm_io.log(KPM_VERBOSITY_INFO, "Installing invalidpackage2"); target.id = "invalidpackage2"; - assert(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) != KPM_OK); + assert_hd(KPM_InstallPackages(&kpm, 1, &target, &kpm_io) != KPM_OK); InstallTarget targets[] = { { @@ -72,16 +72,18 @@ int main(int argc, char* argv[]) } }; kpm_io.log(KPM_VERBOSITY_INFO, "Installing both invalidpackage1 and invalidpackage2"); - assert(KPM_InstallPackages(&kpm, 2, targets, &kpm_io) != KPM_OK); + assert_hd(KPM_InstallPackages(&kpm, 2, targets, &kpm_io) != KPM_OK); kpm_io.log(KPM_VERBOSITY_INFO, "Removing repository"); - assert(KPM_RemoveRepository(&kpm, repository.id) == KPM_OK); + assert_hd(KPM_RemoveRepository(&kpm, repository.id) == KPM_OK); kpm_io.log(KPM_VERBOSITY_INFO, "Listing repositories"); - assert(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); - assert(repositoryCount == 0); - assert(strcmp(repository.id, "testrepo") == 0); + assert_hd(KPM_ListRepositories(&kpm, &repositoryCount, NULL) == KPM_OK); + assert_hd(repositoryCount == 0); + assert_hd(strcmp(repository.id, "testrepo") == 0); KPM_FreeRepository(&repository); KPM_Cleanup(&kpm); + + return 0; } \ No newline at end of file From bff39c6a325919513735eecea2c352f0f99ca3da Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Thu, 6 Aug 2026 16:50:22 +0100 Subject: [PATCH 17/27] CI/Update tests workflow --- .github/workflows/tests.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a296586..c14b01a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,9 +30,4 @@ jobs: if: always() with: name: testlog.txt - path: builddir/meson-logs/testlog.txt - - uses: actions/upload-artifact@v4 - if: always() - with: - name: builddir - path: builddir \ No newline at end of file + path: builddir/meson-logs/testlog.txt \ No newline at end of file From 193fb7003c508517f6bea86e4bb8674defdbda15 Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:51:40 +0100 Subject: [PATCH 18/27] core/Implement file hashing when downloading --- src/install.c | 23 ++++++++++++++++++++++- src/meson.build | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/install.c b/src/install.c index d1e902b..b2eb602 100644 --- a/src/install.c +++ b/src/install.c @@ -1,3 +1,6 @@ +#include "openssl/evp.h" +#include "openssl/types.h" + #include "archive.h" #include "archive_entry.h" #include "cjson/cJSON.h" @@ -291,6 +294,7 @@ struct DownloadData long int total_size; long int downloaded; int reported_progress; + EVP_MD_CTX* evp_ctx; int fd; }; @@ -310,6 +314,7 @@ size_t Internal_DownloadWriteCallback(char* ptr, size_t size, size_t nmemb, void } downloadData->downloaded += size * nmemb; + EVP_DigestUpdate(downloadData->evp_ctx, ptr, size*nmemb); // Update digest return write(downloadData->fd, ptr, size * nmemb); } @@ -417,6 +422,7 @@ KPMResult Internal_DownloadGraphItems(KPM* kpm, DependencyGraph* graph, size_t d CURL* curl = curl_easy_init(); kpmIO->log(KPM_VERBOSITY_DEBUG, "Downloading url %s", target_url); + struct DownloadData download_data = { .kpm_io = kpmIO, .total_size = 0, @@ -424,6 +430,10 @@ KPMResult Internal_DownloadGraphItems(KPM* kpm, DependencyGraph* graph, size_t d .reported_progress = 0, .fd = fd }; + + download_data.evp_ctx = EVP_MD_CTX_new(); + EVP_MD_CTX_init(download_data.evp_ctx); + EVP_DigestInit_ex(download_data.evp_ctx, EVP_sha256(), NULL); curl_easy_setopt(curl, CURLOPT_URL, target_url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Internal_DownloadWriteCallback); @@ -456,6 +466,17 @@ KPMResult Internal_DownloadGraphItems(KPM* kpm, DependencyGraph* graph, size_t d } } + // Check the digest + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned int md_len; + EVP_DigestFinal_ex(download_data.evp_ctx, md, &md_len); + EVP_MD_CTX_destroy(download_data.evp_ctx); + kpmIO->log(KPM_VERBOSITY_DEBUG, "Calculated hash for file:"); + for (int i = 0; i < md_len; i++) + { + kpmIO->log(KPM_VERBOSITY_DEBUG, "%x", md[i]); + } + free(target_url); } @@ -1084,7 +1105,7 @@ KPMResult KPM_InstallPackages(KPM* kpm, size_t targetCount, InstallTarget* targe KPMResult result = Internal_DownloadGraphItems(kpm, &graph, deduplicatedPackageCount, deduplicatedPackages, kpm_io); if (result != KPM_OK) { - kpm_io->log(KPM_VERBOSITY_ERROR, "Error: Could not download dependency graph (%i)", result); + kpm_io->log(KPM_VERBOSITY_ERROR, "Error: Could not download dependency graph items (%i)", result); free(deduplicatedPackages); FreeDependencyGraph(&graph); free(upgrade); diff --git a/src/meson.build b/src/meson.build index e93849a..17f6251 100644 --- a/src/meson.build +++ b/src/meson.build @@ -16,7 +16,7 @@ sources = files( ) include_dir = include_directories('./include') -kpm_lib = library('kpm', sources, include_directories: include_dir, dependencies: [curl_dep, sqlite3_dep, libcjson_dep, libarchive_dep, lab126utils_dep], build_rpath: kmc_lib_dir, install_rpath: kmc_lib_dir) +kpm_lib = library('kpm', sources, include_directories: include_dir, dependencies: [curl_dep, sqlite3_dep, libcjson_dep, libarchive_dep, lab126utils_dep, libcrypto_dep], build_rpath: kmc_lib_dir, install_rpath: kmc_lib_dir) kpm_dep = declare_dependency(link_with : kpm_lib, include_directories : './include/kpm') meson.override_dependency('kpm', kpm_dep) \ No newline at end of file From 1212a93761932922038f0c91d6e79db623a06355 Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:52:00 +0100 Subject: [PATCH 19/27] core/Bump manifest and KPM version --- kpm-helper.py | 8 +++++--- meson.build | 5 +++-- src/include/kpm/kpm.h | 7 +++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/kpm-helper.py b/kpm-helper.py index db4d45b..a69d567 100644 --- a/kpm-helper.py +++ b/kpm-helper.py @@ -13,7 +13,7 @@ logging.basicConfig(format="[%(levelname)s] %(message)s", level=logging.INFO) logger = logging.getLogger(__name__) -KPM_MANIFEST_VERSION = 2 +KPM_MANIFEST_VERSION = 3 valid_supported_platforms = [ "kindle", "kindle5", @@ -103,9 +103,9 @@ def __init__( # @TODO if manifest_version != KPM_MANIFEST_VERSION: - if manifest_version["manifest_version"] == 1: + if manifest_version["manifest_version"] < KPM_MANIFEST_VERSION: logger.warning( - "Manifest v1 is deprecated. Please upgrade to manifest v2." + f"Manifest v{manifest_version['manifest_version']} is deprecated. Please upgrade to manifest v{KPM_MANIFEST_VERSION}." ) else: logger.error( @@ -248,6 +248,8 @@ def pack(self, output_path: str, compression: int = 5): file = tarfile.open(packageFilename, "w:") elif self.manifest_version >= 2: file = tarfile.open(packageFilename, "w:gz", compresslevel=compression) + elif self.manifest_version >= 3: + file = tarfile.open(packageFilename, "w:zst", level=compression) else: file = tarfile.open( packageFilename, "w:xz", preset=compression diff --git a/meson.build b/meson.build index a5219bd..2063b05 100644 --- a/meson.build +++ b/meson.build @@ -23,7 +23,7 @@ subproject('libarchive', default_options: [ 'libb2=disabled', 'iconv=disabled', 'lz4=disabled', - 'zstd=disabled', + 'zstd=enabled', 'lzma=enabled', 'lzo2=disabled', 'cng=disabled', @@ -35,7 +35,7 @@ subproject('libarchive', default_options: [ ]) subproject('google-brotli', default_options: ['default_library=static']) # Needed for cURL brotli support -subproject('openssl', default_options: ['default_library=static', 'build_cli=false']) # Needed for cURL HTTPS support +openssl = subproject('openssl', default_options: ['default_library=static', 'build_cli=false']) # Needed for cURL HTTPS support subproject('nghttp2', default_options: ['default_library=static']) # Needed for cURL HTTP/2 support curl = subproject('curl', default_options: [ 'tool=disabled', @@ -85,6 +85,7 @@ sqlite3_dep = dependency('sqlite3', static: true, default_options: [ curl_dep = curl.get_variable('curl_dep') # Get static libcurl libcjson_dep = dependency('libcjson', static: true) libarchive_dep = dependency('libarchive', static: true) +libcrypto_dep = dependency('libcrypto', static: true) subdir('./stubs') subdir('./src') diff --git a/src/include/kpm/kpm.h b/src/include/kpm/kpm.h index 50a0627..7e41e51 100644 --- a/src/include/kpm/kpm.h +++ b/src/include/kpm/kpm.h @@ -12,16 +12,15 @@ */ #include -#include #include #include "semver.h" -#define KPM_MANIFEST_VERSION 2 +#define KPM_MANIFEST_VERSION 3 #define KPM_VERSION_MAJOR 0 -#define KPM_VERSION_MINOR 2 -#define KPM_VERSION_PATCH 3 +#define KPM_VERSION_MINOR 3 +#define KPM_VERSION_PATCH 0 typedef enum { From 2e04dc5195465605517f24d4876a85376add410a Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:50:39 +0100 Subject: [PATCH 20/27] core/Fix zstd compilation issues --- .gitignore | 1 + meson.build | 4 +- subprojects/fbink.wrap | 2 +- subprojects/libarchive.wrap | 5 +- .../fbink/{overlay => }/meson.build | 0 .../fbink/{overlay => }/meson.options | 0 .../packagefiles/libarchive/LICENSE.build | 19 + .../packagefiles/libarchive/cat/meson.build | 6 + .../packagefiles/libarchive/cpio/meson.build | 12 + .../libarchive/libarchive/meson.build | 180 ++++ .../libarchive/libarchive_fe/meson.build | 11 + .../packagefiles/libarchive/meson.build | 812 ++++++++++++++++++ .../packagefiles/libarchive/meson_options.txt | 92 ++ .../packagefiles/libarchive/tar/meson.build | 20 + subprojects/packagefiles/sqlite3/meson.build | 153 ++++ .../packagefiles/sqlite3/meson_options.txt | 50 ++ subprojects/zlib.wrap | 14 - subprojects/zstd.wrap | 13 + 18 files changed, 1373 insertions(+), 21 deletions(-) rename subprojects/packagefiles/fbink/{overlay => }/meson.build (100%) rename subprojects/packagefiles/fbink/{overlay => }/meson.options (100%) create mode 100644 subprojects/packagefiles/libarchive/LICENSE.build create mode 100644 subprojects/packagefiles/libarchive/cat/meson.build create mode 100644 subprojects/packagefiles/libarchive/cpio/meson.build create mode 100644 subprojects/packagefiles/libarchive/libarchive/meson.build create mode 100644 subprojects/packagefiles/libarchive/libarchive_fe/meson.build create mode 100644 subprojects/packagefiles/libarchive/meson.build create mode 100644 subprojects/packagefiles/libarchive/meson_options.txt create mode 100644 subprojects/packagefiles/libarchive/tar/meson.build create mode 100644 subprojects/packagefiles/sqlite3/meson.build create mode 100644 subprojects/packagefiles/sqlite3/meson_options.txt delete mode 100644 subprojects/zlib.wrap create mode 100644 subprojects/zstd.wrap diff --git a/.gitignore b/.gitignore index 47761f4..67dc384 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ builddir/ docs/ subprojects/* !subprojects/*.wrap +!subprojects/packagefiles build/ \ No newline at end of file diff --git a/meson.build b/meson.build index 2063b05..e247cd3 100644 --- a/meson.build +++ b/meson.build @@ -11,14 +11,14 @@ add_global_arguments('-DKPM_PKG_PATH="' + get_option('pkg_path') + '"', language kmc_lib_dir = '/var/local/kmc/lib' +subproject('zstd', default_options: ['default_library=static']) subproject('liblzma', default_options: ['default_library=static']) -subproject('zlib', default_options: ['default_library=static', 'tests=disabled']) subproject('sqlite3', default_options: ['default_library=static']) # PW2 sqlite3 is unusable it straight up doesn't compile I need to fix this subproject('cjson', default_options: ['tests=false', 'default_library=static']) subproject('libarchive', default_options: [ 'default_library=static', 'openssl=disabled', - 'zlib=enabled', + 'zlib=disabled', 'bz2lib=disabled', 'libb2=disabled', 'iconv=disabled', diff --git a/subprojects/fbink.wrap b/subprojects/fbink.wrap index 84c3647..9368e93 100644 --- a/subprojects/fbink.wrap +++ b/subprojects/fbink.wrap @@ -3,7 +3,7 @@ url = https://github.com/KindleModding/FBInk.git revision = 76557104a55ea163769464e59668e26f6af299ef depth = 1 clone-recursive = true -patch_directory = fbink/overlay +patch_directory = fbink [provide] dependency_names = fbink, fbink_input \ No newline at end of file diff --git a/subprojects/libarchive.wrap b/subprojects/libarchive.wrap index b31143c..6a1e126 100644 --- a/subprojects/libarchive.wrap +++ b/subprojects/libarchive.wrap @@ -3,11 +3,8 @@ directory = libarchive-3.8.1 source_url = https://github.com/libarchive/libarchive/releases/download/v3.8.1/libarchive-3.8.1.tar.xz source_filename = libarchive-3.8.1.tar.xz source_hash = 19f917d42d530f98815ac824d90c7eaf648e9d9a50e4f309c812457ffa5496b5 -patch_filename = libarchive_3.8.1-2_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/libarchive_3.8.1-2/get_patch -patch_hash = 26d21f165200741655ac54913e407e849d09fc73e1c68dbcbfe72eaa2d5df4d1 source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/libarchive_3.8.1-2/libarchive-3.8.1.tar.xz -wrapdb_version = 3.8.1-2 +patch_directory = libarchive [provide] libarchive = libarchive_dep diff --git a/subprojects/packagefiles/fbink/overlay/meson.build b/subprojects/packagefiles/fbink/meson.build similarity index 100% rename from subprojects/packagefiles/fbink/overlay/meson.build rename to subprojects/packagefiles/fbink/meson.build diff --git a/subprojects/packagefiles/fbink/overlay/meson.options b/subprojects/packagefiles/fbink/meson.options similarity index 100% rename from subprojects/packagefiles/fbink/overlay/meson.options rename to subprojects/packagefiles/fbink/meson.options diff --git a/subprojects/packagefiles/libarchive/LICENSE.build b/subprojects/packagefiles/libarchive/LICENSE.build new file mode 100644 index 0000000..b59833d --- /dev/null +++ b/subprojects/packagefiles/libarchive/LICENSE.build @@ -0,0 +1,19 @@ +Copyright (c) 2021 The Meson development team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/subprojects/packagefiles/libarchive/cat/meson.build b/subprojects/packagefiles/libarchive/cat/meson.build new file mode 100644 index 0000000..a712e47 --- /dev/null +++ b/subprojects/packagefiles/libarchive/cat/meson.build @@ -0,0 +1,6 @@ +bsdcat = executable( + 'bsdcat', + ['bsdcat.c', 'cmdline.c'], + dependencies: libarchive_fe_dep, + install: true, +) diff --git a/subprojects/packagefiles/libarchive/cpio/meson.build b/subprojects/packagefiles/libarchive/cpio/meson.build new file mode 100644 index 0000000..5e6d77a --- /dev/null +++ b/subprojects/packagefiles/libarchive/cpio/meson.build @@ -0,0 +1,12 @@ +src = ['cmdline.c', 'cpio.c'] + +if host_machine.system() == 'windows' + src += 'cpio_windows.c' +endif + +bsdcpio = executable( + 'bsdcpio', + src, + dependencies: libarchive_fe_dep, + install: true, +) diff --git a/subprojects/packagefiles/libarchive/libarchive/meson.build b/subprojects/packagefiles/libarchive/libarchive/meson.build new file mode 100644 index 0000000..00ead8d --- /dev/null +++ b/subprojects/packagefiles/libarchive/libarchive/meson.build @@ -0,0 +1,180 @@ +sources = files( + 'archive_acl.c', + 'archive_check_magic.c', + 'archive_cmdline.c', + 'archive_cryptor.c', + 'archive_digest.c', + 'archive_entry.c', + 'archive_entry_copy_stat.c', + 'archive_entry_link_resolver.c', + 'archive_entry_sparse.c', + 'archive_entry_stat.c', + 'archive_entry_strmode.c', + 'archive_entry_xattr.c', + 'archive_hmac.c', + 'archive_match.c', + 'archive_options.c', + 'archive_pack_dev.c', + 'archive_parse_date.c', + 'archive_pathmatch.c', + 'archive_ppmd7.c', + 'archive_ppmd8.c', + 'archive_random.c', + 'archive_rb.c', + 'archive_read.c', + 'archive_read_add_passphrase.c', + 'archive_read_append_filter.c', + 'archive_read_data_into_fd.c', + 'archive_read_disk_entry_from_file.c', + 'archive_read_disk_posix.c', + 'archive_read_disk_set_standard_lookup.c', + 'archive_read_extract.c', + 'archive_read_extract2.c', + 'archive_read_open_fd.c', + 'archive_read_open_file.c', + 'archive_read_open_filename.c', + 'archive_read_open_memory.c', + 'archive_read_set_format.c', + 'archive_read_set_options.c', + 'archive_read_support_filter_all.c', + 'archive_read_support_filter_by_code.c', + 'archive_read_support_filter_bzip2.c', + 'archive_read_support_filter_compress.c', + 'archive_read_support_filter_grzip.c', + 'archive_read_support_filter_gzip.c', + 'archive_read_support_filter_lrzip.c', + 'archive_read_support_filter_lz4.c', + 'archive_read_support_filter_lzop.c', + 'archive_read_support_filter_none.c', + 'archive_read_support_filter_program.c', + 'archive_read_support_filter_rpm.c', + 'archive_read_support_filter_uu.c', + 'archive_read_support_filter_xz.c', + 'archive_read_support_filter_zstd.c', + 'archive_read_support_format_7zip.c', + 'archive_read_support_format_all.c', + 'archive_read_support_format_ar.c', + 'archive_read_support_format_by_code.c', + 'archive_read_support_format_cab.c', + 'archive_read_support_format_cpio.c', + 'archive_read_support_format_empty.c', + 'archive_read_support_format_iso9660.c', + 'archive_read_support_format_lha.c', + 'archive_read_support_format_mtree.c', + 'archive_read_support_format_rar.c', + 'archive_read_support_format_rar5.c', + 'archive_read_support_format_raw.c', + 'archive_read_support_format_tar.c', + 'archive_read_support_format_warc.c', + 'archive_read_support_format_xar.c', + 'archive_read_support_format_zip.c', + 'archive_string.c', + 'archive_string_sprintf.c', + 'archive_time.c', + 'archive_util.c', + 'archive_version_details.c', + 'archive_virtual.c', + 'archive_write.c', + 'archive_write_add_filter.c', + 'archive_write_add_filter_b64encode.c', + 'archive_write_add_filter_by_name.c', + 'archive_write_add_filter_bzip2.c', + 'archive_write_add_filter_compress.c', + 'archive_write_add_filter_grzip.c', + 'archive_write_add_filter_gzip.c', + 'archive_write_add_filter_lrzip.c', + 'archive_write_add_filter_lz4.c', + 'archive_write_add_filter_lzop.c', + 'archive_write_add_filter_none.c', + 'archive_write_add_filter_program.c', + 'archive_write_add_filter_uuencode.c', + 'archive_write_add_filter_xz.c', + 'archive_write_add_filter_zstd.c', + 'archive_write_disk_posix.c', + 'archive_write_disk_set_standard_lookup.c', + 'archive_write_open_fd.c', + 'archive_write_open_file.c', + 'archive_write_open_filename.c', + 'archive_write_open_memory.c', + 'archive_write_set_format.c', + 'archive_write_set_format_7zip.c', + 'archive_write_set_format_ar.c', + 'archive_write_set_format_by_name.c', + 'archive_write_set_format_cpio.c', + 'archive_write_set_format_cpio_binary.c', + 'archive_write_set_format_cpio_newc.c', + 'archive_write_set_format_cpio_odc.c', + 'archive_write_set_format_filter_by_ext.c', + 'archive_write_set_format_gnutar.c', + 'archive_write_set_format_iso9660.c', + 'archive_write_set_format_mtree.c', + 'archive_write_set_format_pax.c', + 'archive_write_set_format_raw.c', + 'archive_write_set_format_shar.c', + 'archive_write_set_format_ustar.c', + 'archive_write_set_format_v7tar.c', + 'archive_write_set_format_warc.c', + 'archive_write_set_format_xar.c', + 'archive_write_set_format_zip.c', + 'archive_write_set_options.c', + 'archive_write_set_passphrase.c', + 'filter_fork_posix.c', + 'xxhash.c', +) + +host = host_machine.system() +if host in ['darwin', 'freebsd', 'linux', 'sunos'] + sources += files('archive_disk_acl_@0@.c'.format(host)) +elif host == 'cygwin' + sources += files('archive_disk_acl_linux.c') +endif + +if host == 'windows' + sources += files( + 'archive_entry_copy_bhfi.c', + 'archive_read_disk_windows.c', + 'archive_windows.c', + 'archive_write_disk_windows.c', + 'filter_fork_windows.c', + ) +endif + +if not b2_dep.found() + sources += files('archive_blake2s_ref.c', 'archive_blake2sp_ref.c') +endif + +libinc = [] +if host == 'android' + libinc += '../contrib/android/include' +endif + +configure_file( + input: '../build/cmake/config.h.in', + output: 'config.h', + configuration: cdata, + format: 'cmake@', +) + +ver_parts = meson.project_version().split('.') +ver_darwin_compat = (ver_parts[1].to_int() + 14).to_string() +libarchive = library( + 'libarchive', + sources, + include_directories: libinc, + name_prefix: '', + # match upstream's versioning + version: '.'.join(['13', ver_parts[1], ver_parts[2]]), + darwin_versions: [ + ver_darwin_compat, + '.'.join([ver_darwin_compat, ver_parts[2]]), + ], + gnu_symbol_visibility: 'hidden', + dependencies: deps, + install: true, +) + +depinc = include_directories('.') +libarchive_dep = declare_dependency( + include_directories: depinc, + link_with: libarchive, +) diff --git a/subprojects/packagefiles/libarchive/libarchive_fe/meson.build b/subprojects/packagefiles/libarchive/libarchive_fe/meson.build new file mode 100644 index 0000000..f875c61 --- /dev/null +++ b/subprojects/packagefiles/libarchive/libarchive_fe/meson.build @@ -0,0 +1,11 @@ +libarchive_fe = static_library( + 'libarchive_fe', + ['err.c', 'line_reader.c', 'passphrase.c'], + dependencies: libarchive_dep, +) + +libarchive_fe_dep = declare_dependency( + dependencies: libarchive_dep, + include_directories: include_directories('.'), + link_with: libarchive_fe, +) diff --git a/subprojects/packagefiles/libarchive/meson.build b/subprojects/packagefiles/libarchive/meson.build new file mode 100644 index 0000000..082283e --- /dev/null +++ b/subprojects/packagefiles/libarchive/meson.build @@ -0,0 +1,812 @@ +project( + 'libarchive', + 'c', + version: '3.8.1', + meson_version: '>=0.54.1', +) + +cdata = configuration_data() +cdata.set('VERSION', meson.project_version()) +cdata.set('BSDCAT_VERSION_STRING', meson.project_version()) +cdata.set('BSDCPIO_VERSION_STRING', meson.project_version()) +cdata.set('BSDTAR_VERSION_STRING', meson.project_version()) +cdata.set('LIBARCHIVE_VERSION_STRING', meson.project_version()) +cdata.set('SAFE_TO_DEFINE_EXTENSIONS', 1) + +cc = meson.get_compiler('c') +add_project_arguments( + cc.get_supported_arguments('-Wformat', '-Wformat-security'), + language: 'c', +) +add_project_arguments( + '-DHAVE_CONFIG_H', + language: 'c', +) +if host_machine.system() == 'windows' + if get_option('default_library') == 'static' + add_project_arguments( + '-DLIBARCHIVE_STATIC', + language: 'c', + ) + endif + if cc.sizeof('void*') == 4 + add_project_arguments( + '-D__MINGW_USE_VC2005_COMPAT', + language: 'c', + ) + endif + add_project_arguments( + '-D_CRT_SECURE_NO_WARNINGS', + language: 'c', + ) + cdata.set( + '_WIN32_WINNT', + '0x0A00', + description: 'Windows 10 APIs', + ) + cdata.set( + 'NTDDI_VERSION', + '0x0A000000', + description: 'Windows 10 APIs', + ) + cdata.set( + 'WINVER', + '0x0A00', + description: 'Windows 10 APIs', + ) +else + add_project_arguments( + '-D__LIBARCHIVE_ENABLE_VISIBILITY', + language: 'c', + ) +endif + +if cc.sizeof('long') == 4 + cdata.set('_FILE_OFFSET_BITS', 64) +endif + +sizes = [ + '__int64', + 'int16_t', + 'int32_t', + 'int64_t', + 'intmax_t', + 'uint8_t', + 'uint16_t', + 'uint32_t', + 'uint64_t', + 'uintmax_t', + 'unsigned __int64', +] +foreach s : sizes + cdata.set( + 'HAVE_@0@'.format(s.underscorify().to_upper()), + cc.has_type( + s, + prefix: '#include ', + ), + ) +endforeach + +sizes = [ + 'short', + 'int', + 'long', + 'long long', + 'unsigned', + 'unsigned long', + 'unsigned long long', + 'unsigned short', +] + +foreach s : sizes + upper = s.underscorify().to_upper() + cdata.set( + 'SIZEOF_@0@_CODE'.format(upper), + '#define @0@ @1@'.format('SIZEOF_@0@'.format(upper), cc.sizeof(s)), + ) +endforeach + +cdata.set10( + 'HAVE_WCHAR_T', + cc.has_type( + 'wchar_t', + prefix: '#include ', + ), +) +cdata.set( + 'SIZEOF_WCHAR_T', + cc.sizeof( + 'wchar_t', + prefix: '#include ', + ), +) + +minmax = { + 'INT32_MAX': 'stdint.h', + 'INT32_MIN': 'stdint.h', + 'INT64_MAX': 'stdint.h', + 'INT64_MIN': 'stdint.h', + 'INTMAX_MAX': 'stdint.h', + 'INTMAX_MIN': 'stdint.h', + 'SIZE_MAX': 'stdint.h', + 'SSIZE_MAX': 'limits.h', + 'UINT32_MAX': 'stdint.h', + 'UINT64_MAX': 'stdint.h', + 'UINTMAX_MAX': 'stdint.h', +} + +foreach m, h : minmax + if cc.has_header_symbol(h, m) + cdata.set('HAVE_DECL_@0@'.format(m), 1) + endif +endforeach + +mingw_funcs = {} + +if cc.has_header_symbol('langinfo.h', 'D_MD_ORDER') + cdata.set('HAVE_D_MD_ORDER', 1) +endif + +if cc.has_header_symbol('linux/fs.h', 'FS_IOC_GETFLAGS') + cdata.set('HAVE_WORKING_FS_IOC_GETFLAGS', 1) +endif + +if cc.has_header_symbol('errno.h', 'EFTYPE') + cdata.set('HAVE_EFTYPE', 1) +endif + +if cc.has_header_symbol('errno.h', 'EILSEQ') + cdata.set('HAVE_EILSEQ', 1) +endif + +if cc.has_header_symbol('ext2fs/ext2_fs.h', 'EXT2_IOC_GETFLAGS') + cdata.set('HAVE_WORKING_EXT2_IOC_GETFLAGS', 1) +endif + +if cc.has_header_symbol('unistd.h', 'sysconf') + cdata.set('HAVE_SYSCONF', 1) +endif + +foreach w : ['_mkgmtime', 'ctime_s', 'gmtime_s', 'localtime_s'] + if cc.has_header_symbol('time.h', w) + cdata.set('HAVE_@0@'.format(w.to_upper()), 1) + endif +endforeach + +mem = [ + 'st_birthtime', + 'st_birthtimespec.tv_nsec', + 'st_mtimespec.tv_nsec', + 'st_mtim.tv_nsec', + 'st_mtime_n', + 'st_umtime', + 'st_mtime_usec', + 'st_blksize', + 'st_flags', +] +foreach m : mem + if cc.has_member( + 'struct stat', + m, + prefix: '#include ', + ) + cdata.set('HAVE_STRUCT_STAT_@0@'.format(m.underscorify().to_upper()), 1) + endif +endforeach + +if cc.has_member( + 'struct tm', + 'tm_gmtoff', + prefix: '#include ', +) + cdata.set('HAVE_STRUCT_TM_TM_GMTOFF', 1) +endif + +foreach a : ['acl_t', 'acl_entry_t', 'acl_permset_t', 'acl_tag_t'] + if cc.has_type( + a, + prefix: '#include ', + ) + cdata.set('HAVE_@0@'.format(a.to_upper()), 1) + endif +endforeach + +foreach h : ['dirent.h', 'sys/ndir.h', 'sys/dir.h', 'ndir.h'] + if cc.has_header_symbol(h, 'DIR') + cdata.set('HAVE_@0@'.format(h.underscorify().to_upper()), 1) + break + endif +endforeach + +foreach h : ['sys/types.h', 'sys/time.h', 'time.h'] + if cc.has_member( + 'struct tm', + 'tm_sec', + prefix: '#include <@0@>'.format(h), + ) + cdata.set('TIME_WITH_SYS_TIME', 1) + break + endif +endforeach + +if cc.has_header_symbol('sys/sysmacros.h', 'major') + cdata.set('MAJOR_IN_SYSMACROS', 1) +endif + +if cc.has_header_symbol('crtdbg.h', '_CrtSetReportMode') + cdata.set('HAVE__CrtSetReportMode', 1) +endif + +types = { + 'dev_t': 'unsigned int', + 'id_t': 'short', + 'gid_t': 'short', + 'mode_t': 'unsigned short', + 'off_t': 'int64_t', + 'pid_t': 'int', + 'size_t': 'unsigned long', + 'ssize_t': 'ptrdiff_t', + 'uid_t': 'short', +} + +foreach t, v : types + if not cc.has_type( + t, + prefix: '#include ', + args: '-D_GNU_SOURCE', + ) + cdata.set(t, v) + endif +endforeach + +if host_machine.system() in ['cygwin', 'linux'] + cdata.set('ARCHIVE_XATTR_LINUX', 1) +endif + +deps = [] +if meson.version().version_compare('>= 0.62') + dl_dep = dependency( + 'dl', + required: false, + ) +else + dl_dep = cc.find_library( + 'dl', + required: false, + ) +endif + +if dl_dep.found() + cdata.set('HAVE_DLFCN_H', 1) +endif + +attr_dep = dependency( + 'libattr', + disabler: true, + required: get_option('xattr'), +) +if attr_dep.found() + cdata.set('HAVE_LIBATTR', 1) + if attr_dep.version() != 'unknown' + cdata.set('LIBATTR_PKGCONFIG_VERSION', attr_dep.version()) + endif + deps += attr_dep +endif + +# compression dependencies +zlib_dep = dependency( + 'zlib', + disabler: true, + required: get_option('zlib'), +) +if zlib_dep.found() + cdata.set('HAVE_LIBZ', 1) + cdata.set('HAVE_ZLIB_H', 1) + deps += zlib_dep +endif + +bz2_dep = dependency( + 'bzip2', + disabler: true, + required: get_option('bz2lib'), +) +if bz2_dep.found() + cdata.set('HAVE_LIBBZ2', 1) + deps += bz2_dep +endif + +b2_dep = dependency( + 'libb2', + disabler: true, + required: get_option('libb2'), +) +if b2_dep.found() + cdata.set('HAVE_LIBB2', 1) + if b2_dep.version() != 'unknown' + cdata.set('LIBB2_PKGCONFIG_VERSION', b2_dep.version()) + endif + deps += b2_dep +endif + +lz4_dep = dependency( + 'liblz4', + disabler: true, + required: get_option('lz4'), +) +if lz4_dep.found() + cdata.set('HAVE_LIBLZ4', 1) + deps += lz4_dep +endif + +zstd_dep = dependency( + 'libzstd', + disabler: true, + required: get_option('zstd'), +) +if zstd_dep.found() + cdata.set('HAVE_LIBZSTD', 1) + cdata.set('HAVE_ZSTD_H', 1) + deps += zstd_dep + foreach sym : ['ZSTD_compressStream', 'ZSTD_minCLevel'] + cdata.set('HAVE_' + sym, 1) + endforeach +endif + +lzma_dep = dependency( + 'liblzma', + disabler: true, + required: get_option('lzma'), +) +if lzma_dep.found() + cdata.set('HAVE_LIBLZMA', 1) + deps += lzma_dep + if lzma_dep.version().version_compare('>=5.2.0') + cdata.set('HAVE_LZMA_STREAM_ENCODER_MT', 1) + endif +endif + +lzo2_dep = dependency( + 'lzo2', + required: get_option('lzo2'), +) +if lzo2_dep.found() + cdata.set('HAVE_LIBLZO2', 1) + deps += lzo2_dep +endif + +bcrypt_dep = cc.find_library( + 'bcrypt', + required: get_option('cng'), +) +if bcrypt_dep.found() and cc.has_header( + 'bcrypt.h', + required: get_option('cng'), +) + cdata.set('HAVE_BCRYPT_H', 1) + deps += bcrypt_dep +endif + +crypto_dep = dependency( + 'libcrypto', + required: get_option('openssl'), +) +if crypto_dep.found() + cdata.set('HAVE_LIBCRYPTO', 1) + deps += crypto_dep + foreach f : ['md5', 'rmd160', 'sha1', 'sha256', 'sha384', 'sha512'] + if not cc.has_header_symbol( + 'openssl/ssl.h', + 'OPENSSL_NO_@0@'.format(f.to_upper()), + ) + cdata.set('ARCHIVE_CRYPTO_@0@_OPENSSL'.format(f.to_upper()), 1) + endif + endforeach + if cc.has_header_symbol('openssl/evp.h', 'PKCS5_PBKDF2_HMAC_SHA1') + cdata.set('HAVE_PKCS5_PBKDF2_HMAC_SHA1', 1) + endif +endif + +# just dependencies +xml2_dep = dependency( + 'libxml-2.0', + required: get_option('xml2'), +) +if xml2_dep.found() + cdata.set('HAVE_LIBXML2', 1) + deps += xml2_dep +else + expat_dep = dependency( + 'expat', + required: get_option('expat'), + ) + if expat_dep.found() + cdata.set('HAVE_LIBEXPAT', 1) + deps += expat_dep + endif +endif + +if meson.version().version_compare('>= 0.60') + iconv_dep = dependency( + 'iconv', + disabler: true, + required: get_option('iconv'), + ) +elif get_option('iconv').enabled() + error('Feature iconv cannot be enabled') +else + iconv_dep = dependency( + '', + required: false, + ) +endif +if iconv_dep.found() + cdata.set('HAVE_ICONV', 1) + cdata.set10('HAVE_LIBICONV', iconv_dep.type_name() in ['system', 'internal']) + cdata.set( + 'ICONV_CONST', + cc.compiles( + ''' + #include + size_t iconv(iconv_t, const char**, size_t*, char**, size_t*); + int main(){} + ''', + dependencies: iconv_dep, + ) ? 'const' : '', + ) + deps += iconv_dep +else + cdata.set('ICONV_CONST', '') +endif + +rx_support = false +rx_dep = dependency( + '', + required: false, +) +if not get_option('regex').disabled() + foreach rx_support : get_option('regex_provider') + if (rx_support == 'libc' +and cc.has_function( + 'regcomp', + prefix: '#include ', + ) +) + rx_dep = declare_dependency() + cdata.set('HAVE_REGEX_H', 1) + break + elif rx_support == 'libregex' + regex_dep = cc.find_library( + 'regex', + disabler: true, + required: false, + ) + if cc.has_function( + 'regcomp', + dependencies: regex_dep, + prefix: '#include ', + ) + rx_dep = regex_dep + cdata.set('HAVE_REGEX_H', 1) + break + endif + elif rx_support == 'libpcre2posix' + pcre2_dep = dependency( + 'libpcre2-posix', + disabler: true, + required: false, + ) + if pcre2_dep.found() + cdata.set('HAVE_PCRE2POSIX_H', 1) + rx_dep = pcre2_dep + break + endif + endif + endforeach + if not rx_dep.found() + if get_option('regex').enabled() + error('Feature regex cannot be enabled') + endif + rx_support = false + endif +endif +deps += rx_dep + +libacl_dep = dependency( + 'libacl', + disabler: true, + required: get_option('acl'), +) +if libacl_dep.found() + cdata.set('HAVE_LIBACL', 1) + cdata.set('ARCHIVE_ACL_LIBACL', 1) + if libacl_dep.version() != 'unknown' + cdata.set('LIBACL_PKGCONFIG_VERSION', libacl_dep.version()) + endif + deps += libacl_dep +endif + +headers = { + 'acl/libacl.h': libacl_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'attr/xattr.h': attr_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'blake2.h': b2_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'bzlib.h': bz2_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'copyfile.h': [], + 'ctype.h': [], + 'direct.h': [], + 'errno.h': [], + 'ext2fs/ext2_fs.h': [], + 'fcntl.h': [], + 'grp.h': [], + 'iconv.h': iconv_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'inttypes.h': [], + 'io.h': [], + 'langinfo.h': [], + 'libxml/xmlreader.h': xml2_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'libxml/xmlversion.h': xml2_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'libxml/xmlwriter.h': xml2_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'limits.h': [], + 'linux/ext2_fs.h': [], + 'linux/fiemap.h': [], + 'linux/fs.h': [], + 'linux/magic.h': [], + 'linux/types.h': [], + 'localcharset.h': [], + 'locale.h': [], + 'lz4.h': lz4_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'lz4hc.h': lz4_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'lzma.h': lzma_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'membership.h': [], + 'memory.h': [], + 'openssl/opensslv.h': crypto_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'paths.h': [], + 'poll.h': [], + 'process.h': [], + 'pthread.h': [], + 'pwd.h': [], + 'readpassphrase.h': [], + 'signal.h': [], + 'spawn.h': [], + 'stdarg.h': [], + 'stdint.h': [], + 'stdlib.h': [], + 'string.h': [], + 'strings.h': [], + 'sys/acl.h': [], + 'sys/cdefs.h': [], + 'sys/ea.h': [], + 'sys/extattr.h': [], + 'sys/ioctl.h': [], + 'sys/mkdev.h': [], + 'sys/mount.h': [], + 'sys/param.h': [], + 'sys/poll.h': [], + 'sys/richacl.h': [], + 'sys/select.h': [], + 'sys/stat.h': [], + 'sys/statfs.h': [], + 'sys/statvfs.h': [], + 'sys/sysmacros.h': [], + 'sys/time.h': [], + 'sys/types.h': [], + 'sys/utime.h': [], + 'sys/utsname.h': [], + 'sys/vfs.h': [], + 'sys/wait.h': [], + 'sys/xattr.h': [], + 'time.h': [], + 'unistd.h': [], + 'utime.h': [], + 'wchar.h': [], + 'wctype.h': [], + 'wincrypt.h': [], + 'windows.h': [], + 'winioctl.h': [], +} + +foreach h, d : headers + if cc.has_header( + h, + dependencies: d, + ) + cdata.set('HAVE_@0@'.format(h.underscorify().to_upper()), 1) + endif +endforeach + +functions = { + 'acl_create_entry': libacl_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'acl_get_perm': libacl_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'acl_init': libacl_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'acl_set_fd': libacl_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'acl_set_file': libacl_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'arc4random_buf': [], + 'chflags': [], + 'chown': [], + 'chroot': [], + 'ctime_r': [], + 'cygwin_conv_path': [], + 'dirfd': [], + 'fchdir': [], + 'fchflags': [], + 'fchmod': [], + 'fchown': [], + 'fcntl': [], + 'fdopendir': [], + 'fgetxattr': [], + 'flistxattr': [], + 'fork': [], + 'fseeko': [], + 'fsetxattr': [], + 'fstat': [], + 'fstatat': [], + 'fstatfs': [], + 'fstatvfs': [], + 'ftruncate': [], + 'futimens': [], + 'futimes': [], + 'futimesat': [], + 'geteuid': [], + 'getgrgid_r': [], + 'getgrnam_r': [], + 'getpwnam_r': [], + 'getpwuid_r': [], + 'getpid': [], + 'getvfsbyname': [], + 'getxattr': [], + 'gmtime_r': [], + 'lchflags': [], + 'lchmod': [], + 'lchown': [], + 'lgetxattr': [], + 'link': [], + 'linkat': [], + 'listxattr': [], + 'llistxattr': [], + 'locale_charset': iconv_dep.partial_dependency( + compile_args: true, + includes: true, + ), + 'localtime_r': [], + 'lsetxattr': [], + 'lstat': [], + 'lutimes': [], + 'mbrtowc': [], + 'memmove': [], + 'mkdir': [], + 'mkfifo': [], + 'mknod': [], + 'mkstemp': [], + 'nl_langinfo': [], + 'openat': [], + 'pipe': [], + 'poll': [], + 'posix_spawnp': [], + 'readdir_r': [], + 'readlink': [], + 'readlinkat': [], + 'readpassphrase': [], + 'select': [], + 'setenv': [], + 'setlocale': [], + 'setxattr': [], + 'sigaction': [], + 'statfs': [], + 'statvfs': [], + 'strchr': [], + 'strdup': [], + 'strerror': [], + 'strerror_r': [], + 'strftime': [], + 'strncpy_s': [], + 'strnlen': [], + 'strrchr': [], + 'symlink': [], + 'tcgetattr': [], + 'tcsetattr': [], + 'timegm': [], + 'tzset': [], + 'unlinkat': [], + 'unsetenv': [], + 'utime': [], + 'utimes': [], + 'utimensat': [], + 'vprintf': [], + 'vfork': [], + 'wcrtomb': [], + 'wcscmp': [], + 'wcscpy': [], + 'wcslen': [], + 'wctomb': [], + 'wmemcmp': [], + 'wmemcpy': [], + 'wmemmove': [], + '_fseeki64': [], + '_get_timezone': [], +} + +foreach f, d : functions + if cc.has_function( + f, + dependencies: d, + ) + cdata.set('HAVE_@0@'.format(f.underscorify().to_upper()), 1) + endif +endforeach + +if cdata.has('HAVE_STRERROR_R') + cdata.set('HAVE_DECL_STRERROR_R', 1) +endif + +incdirs = include_directories('.') + +subdir('libarchive') + +if get_option('progs').length() > 0 + + subdir('libarchive_fe') + + foreach _prog : ['cat', 'cpio', 'tar'] + if 'all' in get_option('progs') or 'bsd' + _prog in get_option('progs') + subdir(_prog) + endif + endforeach + +endif + +summary( + { + 'regex support': rx_support, + }, + bool_yn: true, +) diff --git a/subprojects/packagefiles/libarchive/meson_options.txt b/subprojects/packagefiles/libarchive/meson_options.txt new file mode 100644 index 0000000..303d956 --- /dev/null +++ b/subprojects/packagefiles/libarchive/meson_options.txt @@ -0,0 +1,92 @@ +option( + 'progs', + type: 'array', + choices: ['all', 'bsdtar', 'bsdcat', 'bsdcpio'], + value: ['all'], + description: 'Which programs should be built', +) + +# enable deps +option( + 'zlib', + type: 'feature', + description: 'Build support for gzip', +) +option( + 'bz2lib', + type: 'feature', + description: 'Build support for bzip2', +) +option( + 'libb2', + type: 'feature', + description: 'Build support for BLAKE2', +) +option( + 'iconv', + type: 'feature', + description: 'Build with iconv support', +) +option( + 'lz4', + type: 'feature', + description: 'Build support for lz4', +) +option( + 'zstd', + type: 'feature', + description: 'Build support for zstd', +) +option( + 'lzma', + type: 'feature', + description: 'Build support for xz', +) +option( + 'lzo2', + type: 'feature', + description: 'Build support for lzo', +) +option( + 'cng', + type: 'feature', + description: 'Build support for Windows Crypto Next Generation', +) +option( + 'openssl', + type: 'feature', + description: 'Build support for mtree and xar hashes through openssl', +) +option( + 'xml2', + type: 'feature', + description: 'Build support for xar hashes through libxml2', +) +option( + 'expat', + type: 'feature', + description: 'Build support for xar hashes through expat', +) +option( + 'regex', + type: 'feature', + description: 'Enable POSIX regex support', +) +option( + 'regex_provider', + type: 'array', + choices: ['libc', 'libregex', 'libpcre2posix'], + description: 'Regex implementation to check for in preferred order', +) + +# enable features +option( + 'xattr', + type: 'feature', + description: 'Extended Attributes support', +) +option( + 'acl', + type: 'feature', + description: 'ACL support', +) diff --git a/subprojects/packagefiles/libarchive/tar/meson.build b/subprojects/packagefiles/libarchive/tar/meson.build new file mode 100644 index 0000000..a2fc125 --- /dev/null +++ b/subprojects/packagefiles/libarchive/tar/meson.build @@ -0,0 +1,20 @@ +src = [ + 'bsdtar.c', + 'cmdline.c', + 'creation_set.c', + 'read.c', + 'subst.c', + 'util.c', + 'write.c', +] + +if host_machine.system() == 'windows' + src += 'bsdtar_windows.c' +endif + +bsdtar = executable( + 'bsdtar', + src, + dependencies: [libarchive_fe_dep, rx_dep], + install: true, +) diff --git a/subprojects/packagefiles/sqlite3/meson.build b/subprojects/packagefiles/sqlite3/meson.build new file mode 100644 index 0000000..45ebbe9 --- /dev/null +++ b/subprojects/packagefiles/sqlite3/meson.build @@ -0,0 +1,153 @@ +project( + 'sqlite3', + 'c', + license: 'blessing', + version: '3.50.4', + meson_version: '>=0.48.0', +) + +cc = meson.get_compiler('c') +if meson.version().version_compare('>= 0.62.0') + libdl = dependency( + 'dl', + required: false, + ) +else + libdl = cc.find_library( + 'dl', + required: false, + ) +endif +libm = cc.find_library( + 'm', + required: false, +) + +sources = files('sqlite3.c') + +sqlite_args = [ + '-DSQLITE_ENABLE_COLUMN_METADATA', + '-DSQLITE_ENABLE_DBSTAT_VTAB', + '-DSQLITE_ENABLE_UNLOCK_NOTIFY', + '-DSQLITE_SECURE_DELETE', +] + +if get_option('strict') + sqlite_args += [ + '-DSQLITE_LIKE_DOESNT_MATCH_BLOBS', + '-DSQLITE_STRICT_SUBTYPE=1', + ] +endif + +extensions = [ + [ + 'fts34', + true, + [ + '-DSQLITE_ENABLE_FTS3', + '-DSQLITE_ENABLE_FTS3_PARENTHESIS', + '-DSQLITE_ENABLE_FTS3_TOKENIZER', + ], + ], + ['fts5', true, '-DSQLITE_ENABLE_FTS5'], + ['geopoly', false, '-DSQLITE_ENABLE_GEOPOLY'], + ['rbu', false, '-DSQLITE_ENABLE_RBU'], + ['rtree', false, '-DSQLITE_ENABLE_RTREE'], + [ + 'session', + false, + ['-DSQLITE_ENABLE_SESSION', '-DSQLITE_ENABLE_PREUPDATE_HOOK'], + ], +] +extension_default = get_option('all-extensions') +foreach extension : extensions + option = get_option(extension[0]) + if option.auto() + if extension_default.auto() + enabled = extension[1] + else + enabled = extension_default.enabled() + endif + else + enabled = option.enabled() + endif + if enabled + sqlite_args += extension[2] + endif +endforeach + +if host_machine.system() == 'windows' + if get_option('default_library') != 'static' + sqlite_args += ['-DSQLITE_API=__declspec(dllexport)'] + endif + libthreads = dependency( + '', + required: false, + ) +else + sqlite_args += ['-DSQLITE_API=__attribute__((visibility("default")))'] + libthreads = dependency('threads') +endif + +foreach h : ['inttypes.h', 'malloc.h', 'stdint.h'] + if cc.has_header(h) + sqlite_args += '-DHAVE_' + h.to_upper().underscorify() + endif +endforeach + +foreach signed : ['', 'u'] + foreach width : ['8', '16', '32'] + typ = signed + 'int' + width + '_t' + if cc.has_type( + typ, + prefix: '#include ', + ) + sqlite_args += '-DHAVE_' + typ.to_upper() + endif + endforeach +endforeach + +foreach f : ['malloc_usable_size', 'posix_fallocate', 'strchrnul', 'strerror_r'] + if cc.has_function(f) + sqlite_args += '-DHAVE_' + f.to_upper() + endif +endforeach +if cc.has_function( + 'isnan', + dependencies: libm, +) + sqlite_args += '-DSQLITE_HAVE_ISNAN' +endif + +libsqlite3 = library( + 'sqlite3', + sources, + c_args: sqlite_args, + dependencies: [libdl, libm, libthreads], + install: true, + gnu_symbol_visibility: 'hidden', + # ABI compatibility with autotools/libtool + version: '0.8.6', +) + +sqlite_args = [] +if host_machine.system() == 'windows' and get_option('default_library') != 'static' + sqlite_args += '-DSQLITE_API=__declspec(dllimport)' +endif + +depinc = include_directories('.') +sqlite3_dep = declare_dependency( + compile_args: sqlite_args, + include_directories: depinc, + link_with: libsqlite3, +) + +install_headers('sqlite3.h') + +pkgconfig = import('pkgconfig') +pkgconfig.generate( + libsqlite3, + description: 'An embedded SQL database engine', + version: meson.project_version(), + extra_cflags: sqlite_args, +) diff --git a/subprojects/packagefiles/sqlite3/meson_options.txt b/subprojects/packagefiles/sqlite3/meson_options.txt new file mode 100644 index 0000000..5e21125 --- /dev/null +++ b/subprojects/packagefiles/sqlite3/meson_options.txt @@ -0,0 +1,50 @@ +option( + 'strict', + description: 'Enable extra checks', + type: 'boolean', + value: true, +) + +# extensions +option( + 'all-extensions', + description: 'Enable or disable all known extensions by default', + type: 'feature', + value: 'auto', +) +option( + 'fts34', + description: 'Enable FTS3 and FTS4', + type: 'feature', + value: 'auto', +) +option( + 'fts5', + description: 'Enable FTS5', + type: 'feature', + value: 'auto', +) +option( + 'geopoly', + description: 'Enable Geopoly extension', + type: 'feature', + value: 'auto', +) +option( + 'rbu', + description: 'Enable Resumable Bulk Update extension', + type: 'feature', + value: 'auto', +) +option( + 'rtree', + description: 'Enable R*Tree index extension', + type: 'feature', + value: 'auto', +) +option( + 'session', + description: 'Enable session extension', + type: 'feature', + value: 'auto', +) diff --git a/subprojects/zlib.wrap b/subprojects/zlib.wrap deleted file mode 100644 index 0626401..0000000 --- a/subprojects/zlib.wrap +++ /dev/null @@ -1,14 +0,0 @@ -[wrap-file] -directory = zlib-1.3.2 -source_url = https://zlib.net/zlib-1.3.2.tar.xz -source_fallback_url = https://wrapdb.mesonbuild.com/v2/zlib_1.3.2-1/get_source/zlib-1.3.2.tar.xz -source_filename = zlib-1.3.2.tar.xz -source_hash = d7a0654783a4da529d1bb793b7ad9c3318020af77667bcae35f95d0e42a792f3 -patch_filename = zlib_1.3.2-1_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/zlib_1.3.2-1/get_patch -patch_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/zlib_1.3.2-1/zlib_1.3.2-1_patch.zip -patch_hash = 5ae7a2e92f823df118cfb8c1b23d94e3117864392b3446581d669049b2fba6dd -wrapdb_version = 1.3.2-1 - -[provide] -dependency_names = zlib diff --git a/subprojects/zstd.wrap b/subprojects/zstd.wrap new file mode 100644 index 0000000..fc6e4fc --- /dev/null +++ b/subprojects/zstd.wrap @@ -0,0 +1,13 @@ +[wrap-file] +directory = zstd-1.5.7 +source_url = https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz +source_filename = zstd-1.5.7.tar.gz +source_hash = eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 +patch_filename = zstd_1.5.7-3_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/zstd_1.5.7-3/get_patch +patch_hash = 75ccb7b5bd26ae38b9d2db40ea4915504137835c15857ea833e70af1fe6f8686 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/zstd_1.5.7-3/zstd-1.5.7.tar.gz +wrapdb_version = 1.5.7-3 + +[provide] +libzstd = libzstd_dep From ffa767fffadd731bd59f2bca8c83231f4fc0ab2d Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:50:52 +0100 Subject: [PATCH 21/27] core/Fix hash debug logging issues --- src/install.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/install.c b/src/install.c index b2eb602..c2395fd 100644 --- a/src/install.c +++ b/src/install.c @@ -471,11 +471,15 @@ KPMResult Internal_DownloadGraphItems(KPM* kpm, DependencyGraph* graph, size_t d unsigned int md_len; EVP_DigestFinal_ex(download_data.evp_ctx, md, &md_len); EVP_MD_CTX_destroy(download_data.evp_ctx); - kpmIO->log(KPM_VERBOSITY_DEBUG, "Calculated hash for file:"); - for (int i = 0; i < md_len; i++) + + char* hash_str = malloc(md_len*2 + 1); + hash_str[0] = 0; + for (int md_i = 0; md_i < md_len; md_i++) { - kpmIO->log(KPM_VERBOSITY_DEBUG, "%x", md[i]); + sprintf(hash_str, "%s%x", hash_str, md[md_i]); } + kpmIO->log(KPM_VERBOSITY_DEBUG, "Calculated hash for file: %s", hash_str); + free(hash_str); free(target_url); } From 2fa41274264eb5fdfc350312406c0ccbe2fcaeda Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Sun, 6 Sep 2026 06:43:24 +0100 Subject: [PATCH 22/27] Cleanup and document SimpleGET API --- src/include/simpleGET.h | 34 +++++++++++++++++++++++++++------- src/index.c | 26 +++++++++++++------------- src/repository.c | 21 ++++++++++----------- src/simpleGET.c | 13 +++++++++++-- 4 files changed, 61 insertions(+), 33 deletions(-) diff --git a/src/include/simpleGET.h b/src/include/simpleGET.h index 77bb832..cde74eb 100644 --- a/src/include/simpleGET.h +++ b/src/include/simpleGET.h @@ -4,15 +4,35 @@ struct SimpleGETRequest { - CURL* curl; - long response_code; - char* buffer; - size_t size; + CURL* curl; /**< The internal CURL object backing this request */ + long response_code; /**< The received response code */ + unsigned char* buffer; /**< The buffer of data from this request */ + size_t size; /**< The current size of the buffer */ + size_t max_size; /**< The maximum size of the response before the request is terminated */ }; typedef struct SimpleGETRequest SimpleGETRequest; size_t SimpleGET_Callback(char* ptr, size_t size, size_t nmemb, void* userdata); -void SimpleGET_Initialise(SimpleGETRequest* request, const char* url); + +/** + * @brief Create a SimpleGETRequest + * + * @param url The URL to request + * @return SimpleGETRequest* A pointer to the created SimpleGETRequest object + */ +SimpleGETRequest* SimpleGET_Initialise(const char* url); + +/** + * @brief Frees a SimpleGETRequest object + * + * @param request The SimpleGetRequest object to free + */ void SimpleGET_Cleanup(SimpleGETRequest* request); -CURLcode SimpleGET_Perform(SimpleGETRequest* request); -long SimpleGET_GetResponseCode(SimpleGETRequest* request); \ No newline at end of file + +/** + * @brief Perform the request + * + * @param request The SimpleGETRequest to perform + * @return CURLcode The internal CURL result of the request, given by curl_easy_perform + */ +CURLcode SimpleGET_Perform(SimpleGETRequest* request); \ No newline at end of file diff --git a/src/index.c b/src/index.c index 17947c2..c98e63b 100644 --- a/src/index.c +++ b/src/index.c @@ -201,33 +201,33 @@ KPMResult KPM_UpdateIndex(KPM *kpm, KPMIO* kpmIO) } bool all_ok = true; - SimpleGETRequest request; + SimpleGETRequest* request; for (size_t i=0; i < repositoryCount; i++) { kpmIO->log(KPM_VERBOSITY_INFO, "Downloading index [%s]", repositories[i].url); - SimpleGET_Initialise(&request, repositories[i].url); - CURLcode curl_code = SimpleGET_Perform(&request); + request = SimpleGET_Initialise(repositories[i].url); + CURLcode curl_code = SimpleGET_Perform(request); if (curl_code != CURLE_OK) { kpmIO->log(KPM_VERBOSITY_ERROR, "Could not fetch url [%s] - CURL Error: %i - %s", repositories[i].url, curl_code, curl_easy_strerror(curl_code)); - SimpleGET_Cleanup(&request); + SimpleGET_Cleanup(request); continue; } - kpmIO->log(KPM_VERBOSITY_DEBUG, "Got response code: %i", request.response_code); - if (request.response_code >= 400 && strncmp(repositories[i].url, "file://", strlen("file://")) != 0) + kpmIO->log(KPM_VERBOSITY_DEBUG, "Got response code: %i", request->response_code); + if (request->response_code >= 400 && strncmp(repositories[i].url, "file://", strlen("file://")) != 0) { kpmIO->log(KPM_VERBOSITY_ERROR, "Could not fetch url [%s]", repositories[i].url); - SimpleGET_Cleanup(&request); + SimpleGET_Cleanup(request); continue; } - kpmIO->log(KPM_VERBOSITY_DEBUG, "Got manifest: %s", request.buffer); - cJSON* json = cJSON_Parse(request.buffer); + kpmIO->log(KPM_VERBOSITY_DEBUG, "Got manifest: %s", request->buffer); + cJSON* json = cJSON_Parse((char*) request->buffer); if (json == NULL) { kpmIO->log(KPM_VERBOSITY_ERROR, "Could not parse manifest"); - SimpleGET_Cleanup(&request); + SimpleGET_Cleanup(request); cJSON_Delete(json); continue; // Move onto next repo } @@ -236,7 +236,7 @@ KPMResult KPM_UpdateIndex(KPM *kpm, KPMIO* kpmIO) { kpmIO->log(KPM_VERBOSITY_ERROR, "Invalid manifest version, got %.0f, expected %i", cJSON_GetNumberValue(cJSON_GetObjectItem(json, "manifest_version")), KPM_MANIFEST_VERSION); kpmIO->log(KPM_VERBOSITY_ERROR, "You may need to update KPM, run: kpm install kpm or kpm upgrade"); - SimpleGET_Cleanup(&request); + SimpleGET_Cleanup(request); cJSON_Delete(json); continue; } @@ -261,7 +261,7 @@ KPMResult KPM_UpdateIndex(KPM *kpm, KPMIO* kpmIO) KPM_FreeRepositoryList(repositoryCount, repositories); kpmIO->log(KPM_VERBOSITY_ERROR, "Could not clear packages for %s (%i: %s - %s)", repositories[i].id, status, sqlite3_errstr(status), sqlite3_errmsg(kpm->db)); cJSON_Delete(json); - SimpleGET_Cleanup(&request); + SimpleGET_Cleanup(request); sqlite3_exec(kpm->db, "ROLLBACK", NULL, NULL, NULL); continue; // Move onto next repo } @@ -290,7 +290,7 @@ KPMResult KPM_UpdateIndex(KPM *kpm, KPMIO* kpmIO) sqlite3_exec(kpm->db, "COMMIT", NULL, NULL, NULL); cJSON_Delete(json); - SimpleGET_Cleanup(&request); + SimpleGET_Cleanup(request); } KPM_FreeRepositoryList(repositoryCount, repositories); diff --git a/src/repository.c b/src/repository.c index 8e549db..590a2dc 100644 --- a/src/repository.c +++ b/src/repository.c @@ -104,23 +104,22 @@ KPMResult KPM_GetRepository(KPM *kpm, const char *repositoryId, Repository* repo KPMResult KPM_AddRepository(KPM *kpm, const char *url, Repository* repository, KPMIO* kpm_io) { - SimpleGETRequest request; - SimpleGET_Initialise(&request, url); - if (SimpleGET_Perform(&request) != CURLE_OK) + SimpleGETRequest* request = SimpleGET_Initialise(url); + if (SimpleGET_Perform(request) != CURLE_OK) { - SimpleGET_Cleanup(&request); + SimpleGET_Cleanup(request); return KPM_CURL_ERROR; } - if (request.response_code >= 400) + if (request->response_code >= 400) { - SimpleGET_Cleanup(&request); + SimpleGET_Cleanup(request); return KPM_INVALID_RESPONSE_CODE; } - kpm_io->log(KPM_VERBOSITY_DEBUG, "Got JSON:\n%s", request.buffer); - cJSON* json = cJSON_Parse(request.buffer); + kpm_io->log(KPM_VERBOSITY_DEBUG, "Got JSON:\n%s", request->buffer); + cJSON* json = cJSON_Parse((char*) request->buffer); if ( !cJSON_IsString(cJSON_GetObjectItem(json, "id")) || @@ -130,7 +129,7 @@ KPMResult KPM_AddRepository(KPM *kpm, const char *url, Repository* repository, K ) { cJSON_Delete(json); - SimpleGET_Cleanup(&request); + SimpleGET_Cleanup(request); return KPM_INVALID_RESPONSE_CONTENT; } @@ -161,7 +160,7 @@ KPMResult KPM_AddRepository(KPM *kpm, const char *url, Repository* repository, K kpm_io->log(KPM_VERBOSITY_ERROR, "SQLite error: %i", e); sqlite3_finalize(statement); cJSON_Delete(json); - SimpleGET_Cleanup(&request); + SimpleGET_Cleanup(request); sqlite3_exec(kpm->db, "ROLLBACK", NULL, NULL, NULL); return KPM_SQLITE_ERROR; } @@ -175,7 +174,7 @@ KPMResult KPM_AddRepository(KPM *kpm, const char *url, Repository* repository, K repository->url = strdup(url); } cJSON_Delete(json); - SimpleGET_Cleanup(&request); + SimpleGET_Cleanup(request); sqlite3_exec(kpm->db, "COMMIT", NULL, NULL, NULL); return KPM_OK; diff --git a/src/simpleGET.c b/src/simpleGET.c index febe883..aacb9de 100644 --- a/src/simpleGET.c +++ b/src/simpleGET.c @@ -1,15 +1,18 @@ #include "simpleGET.h" #include #include +#include #include #include -void SimpleGET_Initialise(SimpleGETRequest* request, const char* url) +SimpleGETRequest* SimpleGET_Initialise(const char* url) { + SimpleGETRequest* request = malloc(sizeof(SimpleGETRequest)); request->buffer = NULL; request->size = 0; request->response_code = 0; request->curl = curl_easy_init(); + request->max_size = SIZE_MAX; curl_easy_setopt(request->curl, CURLOPT_URL, url); curl_easy_setopt(request->curl, CURLOPT_WRITEFUNCTION, SimpleGET_Callback); @@ -19,12 +22,15 @@ void SimpleGET_Initialise(SimpleGETRequest* request, const char* url) curl_easy_setopt(request->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); curl_easy_setopt(request->curl, CURLOPT_FTP_SKIP_PASV_IP, 1L); curl_easy_setopt(request->curl, CURLOPT_TCP_KEEPALIVE, 1L); + + return request; } void SimpleGET_Cleanup(SimpleGETRequest *request) { free(request->buffer); curl_easy_cleanup(request->curl); + free(request); } CURLcode SimpleGET_Perform(SimpleGETRequest *request) @@ -39,7 +45,7 @@ size_t SimpleGET_Callback(char* ptr, size_t size, size_t nmemb, void* userdata) size_t realsize = size * nmemb; SimpleGETRequest* current = (SimpleGETRequest*) userdata; - char* newBuffer = (char*) realloc(current->buffer, current->size + realsize + 1); + unsigned char* newBuffer = (unsigned char*) realloc(current->buffer, current->size + realsize + 1); if (!newBuffer) { return 0; // OOM @@ -50,5 +56,8 @@ size_t SimpleGET_Callback(char* ptr, size_t size, size_t nmemb, void* userdata) current->size += realsize; current->buffer[current->size] = 0; + if (current->size > current->max_size) + return CURL_WRITEFUNC_ERROR; + return realsize; } \ No newline at end of file From aabafa22a8d7bc213c36f7fce52b5b67da59e7c1 Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Sun, 6 Sep 2026 08:31:29 +0100 Subject: [PATCH 23/27] Implement KPKG 3 file loader --- src/include/internal_utils.h | 3 +- src/include/kpm/kpkg.h | 101 +++++++++++++++++++ src/internal_utils.c | 28 ++++++ src/kpkg.c | 186 +++++++++++++++++++++++++++++++++++ src/meson.build | 3 +- 5 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 src/include/kpm/kpkg.h create mode 100644 src/kpkg.c diff --git a/src/include/internal_utils.h b/src/include/internal_utils.h index b038a64..75f3363 100644 --- a/src/include/internal_utils.h +++ b/src/include/internal_utils.h @@ -7,4 +7,5 @@ void mkdir_r(char* path, __mode_t mode); void rmdir_r(char* path); char* vasprintf_hd(const char* format, va_list args); -char* asprintf_hd(const char* format, ...) __attribute__((format(printf, 1, 2))); \ No newline at end of file +char* asprintf_hd(const char* format, ...) __attribute__((format(printf, 1, 2))); +char* get_realpath(const char* location); \ No newline at end of file diff --git a/src/include/kpm/kpkg.h b/src/include/kpm/kpkg.h new file mode 100644 index 0000000..5941f91 --- /dev/null +++ b/src/include/kpm/kpkg.h @@ -0,0 +1,101 @@ +#pragma once + +#include "kpm/semver.h" +#include + +#define KPKG_MAGIC "KPKG" +#define KPKG_MAGIC_LENGTH 4 +#define KPKG_VERSION 3 + +typedef enum { + KPKG_OK, +} KPKG_Result; + +typedef enum { + KINDLEANY = 0, + KINDLE = 1 << 0, + KINDLE5 = 1 << 1, + KINDLEPW2 = 1 << 2, + KINDLEHF = 1 << 3 +} KPKG_SupportedPlatforms; + +struct KPKG_Header +{ + char magic[KPKG_MAGIC_LENGTH]; // "KPKG" + uint8_t version; // Format version + SemVer package_version; // Version of the package + uint8_t supported_platforms; // Bitfield of KPKG_SupportedPlatforms + uint32_t dependency_count; + uint32_t file_count; + uint64_t strings_size; // String section size +}; +typedef struct KPKG_Header KPKG_Header; + +struct KPKG_DependencyEntry +{ + uint64_t id; // Pointer in string section + SemVer min_version; + SemVer max_version; +}; +typedef struct KPKG_DependencyEntry KPKG_DependencyEntry; + +struct KPKG_FileEntry +{ + uint64_t path; // Pointer in string section + uint8_t digest[16]; + uint8_t file_mode; + uint64_t offset; // Pointer in file data section +}; +typedef struct KPKG_FileEntry KPKG_FileEntry; + +struct KPKG_File +{ + char* location; // The URL/path to the file + void* internal_file; // The memory mapped file or NULL + int fd; // If file is local and memory mapped, fd will be present + KPKG_Header* header; // Local storage of header (always present) + KPKG_DependencyEntry* dependencies; + KPKG_FileEntry* files; + char* strings; +}; +typedef struct KPKG_File KPKG_File; + +/** + * @brief Load a KPKG_File from the specified location + * + * @param location The URL or path to the KPKG file + * @return KPKG_File* A pointer to the KPKG_File file object if valid, otherwise NULL + */ +KPKG_File* KPKG_LoadFile(const char* location); + +/** + * @brief Validate a KPKG_File object such that none of the pointers are OOB, note: this function assumes that the size of the loaded strings array matches what is specified in the header. + * + * @param kpkg_file The file object to validate + * @return true + * @return false + */ +bool KPKG_ValidateFile(KPKG_File* kpkg_file); + +/** + * @brief Get a KPKG's metadata from a given URL and header + * + * @param url + * @param header + * @param dependencies + * @param files + * @param strings + * @return true + * @return false + */ +bool KPKG_RequestMetadata(const char* url, KPKG_Header* header, KPKG_DependencyEntry** dependencies, KPKG_FileEntry** files, char** strings); + +/** + * @brief Extract a KPKG file entry to a location on the disk + * + * @param location + * @param file + * @param out + * @return KPKG_Result + */ +KPKG_Result KPKG_ExtractFile(const char* location, KPKG_File* file, char* out); \ No newline at end of file diff --git a/src/internal_utils.c b/src/internal_utils.c index dc3188a..2b35882 100644 --- a/src/internal_utils.c +++ b/src/internal_utils.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -89,4 +90,31 @@ char* asprintf_hd(const char* format, ...) char* result = vasprintf_hd(format, args); va_end(args); return result; +} + +/** + * @brief Returns null if the location specified is a URL, otherwise returns the realpath of the specified path + * + * @param location + * @return char* + */ +char* get_realpath(const char* location) +{ + char* path = NULL; + if (strncmp(location, "file://", strlen("file://")) == 0) + { + path = malloc(strlen(location) - strlen("file://") + 1); + memcpy(path, location+strlen("file://"), strlen(location) - strlen("file://")); + path[strlen(location) - strlen("file://")] = 0; + } else + { + // @TODO: More robust path checking + } + + if (path == NULL) + return NULL; + + char* abspath = realpath(path, NULL); + free(path); + return abspath; } \ No newline at end of file diff --git a/src/kpkg.c b/src/kpkg.c new file mode 100644 index 0000000..dc37cb3 --- /dev/null +++ b/src/kpkg.c @@ -0,0 +1,186 @@ +#include "kpm/kpkg.h" +#include "curl/curl.h" +#include "simpleGET.h" +#include "internal_utils.h" +#include +#include +#include +#include +#include + +#include "zstd.h" + +KPKG_File* KPKG_LoadFile(const char* location) +{ + char* path = get_realpath(location); + if (path) + { + int fd = open(path, O_RDONLY); + if (!fd) + return NULL; + + struct stat buf; + fstat(fd, &buf); + + KPKG_File* file = malloc(sizeof(KPKG_File)); + file->location = path; + file->internal_file = mmap(NULL, buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + file->fd = fd; + file->header = file->internal_file; + file->dependencies = file->internal_file + sizeof(KPKG_Header); + file->files = file->internal_file + sizeof(KPKG_Header) + + (sizeof(KPKG_DependencyEntry) * file->header->dependency_count); + file->strings = file->internal_file + sizeof(KPKG_Header) + + (sizeof(KPKG_DependencyEntry) * file->header->dependency_count) + + (sizeof(KPKG_FileEntry) * file->header->file_count); + + // Validate dependency, file and string sections exist + if ( + sizeof(KPKG_Header) + + (sizeof(KPKG_DependencyEntry) * file->header->dependency_count) + + (sizeof(KPKG_FileEntry) * file->header->file_count) + + file->header->strings_size < buf.st_size + ) + { + close(fd); + free(path); + free(file); + return NULL; + } + + return file; + } + else + { + // This is a URL + + // Fetch the first half of the header + SimpleGETRequest* request = SimpleGET_Initialise(location); + request->max_size = sizeof(KPKG_Header); + + struct curl_slist* headers = NULL; + headers = curl_slist_append(headers, asprintf_hd("Range: bytes=0-%zi", sizeof(KPKG_Header)+1)); + curl_easy_setopt(request->curl, CURLOPT_HTTPHEADER, headers); + + SimpleGET_Perform(request); + curl_slist_free_all(headers); + if (request->response_code != 206) // @TODO: Handle stupid webservers that can't handle range requests, honestly we should be running our own CURL callback here + { + SimpleGET_Cleanup(request); + return NULL; + } + + KPKG_Header* header = (KPKG_Header*) request->buffer; // Steal the request buffer instead of doing memcpy shennanigans + request->buffer = NULL; + SimpleGET_Cleanup(request); + if ( + strncmp(header->magic, KPKG_MAGIC, KPKG_MAGIC_LENGTH) != 0 || + header->version != KPKG_VERSION // @TODO: We can even validate the platform here, and we SHOULD! + ) + { + free(header); + return NULL; + } + + + // Fetch the second half of the header + request = SimpleGET_Initialise(location); + request->max_size = header->dependency_count * sizeof(KPKG_DependencyEntry) + + header->file_count * sizeof(KPKG_FileEntry) + + header->strings_size; + + headers = NULL; + headers = curl_slist_append(headers, asprintf_hd("Range: bytes=%zi-%zi", + sizeof(KPKG_Header) + 1, + header->dependency_count * sizeof(KPKG_DependencyEntry) + + header->file_count * sizeof(KPKG_FileEntry) + + header->strings_size + 1 + )); + curl_easy_setopt(request->curl, CURLOPT_HTTPHEADER, headers); + + SimpleGET_Perform(request); + curl_slist_free_all(headers); + if ( + request->response_code != 206 || + request->size != header->dependency_count * sizeof(KPKG_DependencyEntry) + + header->file_count * sizeof(KPKG_FileEntry) + + header->strings_size + ) + { + free(header); + SimpleGET_Cleanup(request); + return NULL; + } + + + // Construct file from response + KPKG_File* file = malloc(sizeof(KPKG_File)); + file->location = strdup(location); + file->internal_file = NULL; + file->fd = -1; + file->header = header; + file->dependencies = NULL; + file->files = NULL; + file->strings = NULL; + + file->dependencies = malloc(header->dependency_count * sizeof(KPKG_DependencyEntry)); + memcpy( + file->dependencies, + request->buffer, + header->dependency_count * sizeof(KPKG_DependencyEntry) + ); + + file->files = malloc(header->file_count * sizeof(KPKG_FileEntry)); + memcpy( + file->files, + request->buffer + + header->dependency_count * sizeof(KPKG_DependencyEntry), + header->file_count * sizeof(KPKG_FileEntry) + ); + + file->strings = malloc(header->strings_size); + memcpy( + file->strings, + request->buffer + + header->dependency_count * sizeof(KPKG_DependencyEntry) + + header->file_count * sizeof(KPKG_FileEntry), + header->strings_size + ); + + request->buffer = NULL; + SimpleGET_Cleanup(request); + + return file; + } +} + +bool KPKG_ValidateFile(KPKG_File* kpkg_file) +{ + if (kpkg_file->strings[kpkg_file->header->strings_size - 1] != 0) + return false; // String section not null-terminated + + for (int i=0; i < kpkg_file->header->dependency_count; i++) + if (kpkg_file->dependencies[i].id >= kpkg_file->header->strings_size) + return false; // Dependency id OOB + + for (int i=0; i < kpkg_file->header->file_count; i++) + if (kpkg_file->files[i].path >= kpkg_file->header->strings_size) + return false; // File path OOB + + if (kpkg_file->fd) + { + struct stat buf; + fstat(kpkg_file->fd, &buf); + + __off_t file_section_size = buf.st_size - (sizeof(KPKG_Header) + + kpkg_file->header->dependency_count * sizeof(KPKG_DependencyEntry) + + kpkg_file->header->file_count * sizeof(KPKG_FileEntry) + + kpkg_file->header->strings_size); + + for (int i=0; i < kpkg_file->header->file_count; i++) + if (kpkg_file->files[i].offset >= file_section_size) + return false; // File content OOB + } + + return true; +} \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 17f6251..c36f690 100644 --- a/src/meson.build +++ b/src/meson.build @@ -12,7 +12,8 @@ sources = files( 'repository.c', 'simpleGET.c', 'uninstall.c', - 'utils.c' + 'utils.c', + 'kpkg.c' ) include_dir = include_directories('./include') From 08820ee2eb32afd6f35603abc1211f3f79263f4a Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Sun, 6 Sep 2026 15:28:57 +0100 Subject: [PATCH 24/27] Add more kpkg 3 helper functions --- src/include/kpm/kpkg.h | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/src/include/kpm/kpkg.h b/src/include/kpm/kpkg.h index 5941f91..07496ae 100644 --- a/src/include/kpm/kpkg.h +++ b/src/include/kpm/kpkg.h @@ -1,6 +1,7 @@ #pragma once #include "kpm/semver.h" +#include #include #define KPKG_MAGIC "KPKG" @@ -52,7 +53,7 @@ struct KPKG_File { char* location; // The URL/path to the file void* internal_file; // The memory mapped file or NULL - int fd; // If file is local and memory mapped, fd will be present + size_t map_size; // If file is local and memory mapped, this will indicate the length KPKG_Header* header; // Local storage of header (always present) KPKG_DependencyEntry* dependencies; KPKG_FileEntry* files; @@ -68,8 +69,10 @@ typedef struct KPKG_File KPKG_File; */ KPKG_File* KPKG_LoadFile(const char* location); +void KPKG_CloseFile(KPKG_File* file); + /** - * @brief Validate a KPKG_File object such that none of the pointers are OOB, note: this function assumes that the size of the loaded strings array matches what is specified in the header. + * @brief Validate a KPKG_File object such that none of the pointers are OOB, note: this function assumes that the entry and string arrays are not malformed. * * @param kpkg_file The file object to validate * @return true @@ -77,25 +80,6 @@ KPKG_File* KPKG_LoadFile(const char* location); */ bool KPKG_ValidateFile(KPKG_File* kpkg_file); -/** - * @brief Get a KPKG's metadata from a given URL and header - * - * @param url - * @param header - * @param dependencies - * @param files - * @param strings - * @return true - * @return false - */ -bool KPKG_RequestMetadata(const char* url, KPKG_Header* header, KPKG_DependencyEntry** dependencies, KPKG_FileEntry** files, char** strings); - -/** - * @brief Extract a KPKG file entry to a location on the disk - * - * @param location - * @param file - * @param out - * @return KPKG_Result - */ -KPKG_Result KPKG_ExtractFile(const char* location, KPKG_File* file, char* out); \ No newline at end of file +char* KPKG_GetString(KPKG_File* kpkg_file, uint64_t offset); +KPKG_DependencyEntry* KPKG_GetDependencies(KPKG_File* kpkg_file, size_t* count); +KPKG_FileEntry* KPKG_GetFileEntries(KPKG_File* kpkg_file, size_t* count); From b8f9f1900db6297d42190573c4577d7f0234d764 Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Mon, 7 Sep 2026 07:38:15 +0100 Subject: [PATCH 25/27] core/Implement more KPKG functions + fix KPKG 3 format bugs --- src/include/kpm/kpkg.h | 4 +-- src/include/kpm/semver.h | 7 +++-- src/kpkg.c | 60 ++++++++++++++++++++++++++++++++++------ 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/src/include/kpm/kpkg.h b/src/include/kpm/kpkg.h index 07496ae..a0fd14f 100644 --- a/src/include/kpm/kpkg.h +++ b/src/include/kpm/kpkg.h @@ -43,8 +43,8 @@ typedef struct KPKG_DependencyEntry KPKG_DependencyEntry; struct KPKG_FileEntry { uint64_t path; // Pointer in string section - uint8_t digest[16]; - uint8_t file_mode; + uint8_t checksum[16]; + uint32_t file_mode; uint64_t offset; // Pointer in file data section }; typedef struct KPKG_FileEntry KPKG_FileEntry; diff --git a/src/include/kpm/semver.h b/src/include/kpm/semver.h index 9e543a1..dd8a404 100644 --- a/src/include/kpm/semver.h +++ b/src/include/kpm/semver.h @@ -1,15 +1,16 @@ #pragma once #include +#include #define VERSION_MAX UINT_MAX #define SEMVER_MAX { .major = UINT_MAX, .minor = UINT_MAX, .patch = UINT_MAX } struct SemVer { - unsigned int major; - unsigned int minor; - unsigned int patch; + uint32_t major; + uint32_t minor; + uint32_t patch; }; typedef struct SemVer SemVer; diff --git a/src/kpkg.c b/src/kpkg.c index dc37cb3..4bd3aeb 100644 --- a/src/kpkg.c +++ b/src/kpkg.c @@ -17,15 +17,26 @@ KPKG_File* KPKG_LoadFile(const char* location) { int fd = open(path, O_RDONLY); if (!fd) + { + free(path); return NULL; + } struct stat buf; fstat(fd, &buf); KPKG_File* file = malloc(sizeof(KPKG_File)); file->location = path; - file->internal_file = mmap(NULL, buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - file->fd = fd; + file->map_size = buf.st_size; + file->internal_file = mmap(NULL, file->map_size, PROT_READ, MAP_SHARED, fd, 0); + close(fd); + if (!file->internal_file) + { + free(file); + free(path); + return NULL; + } + file->header = file->internal_file; file->dependencies = file->internal_file + sizeof(KPKG_Header); file->files = file->internal_file + sizeof(KPKG_Header) + @@ -117,7 +128,7 @@ KPKG_File* KPKG_LoadFile(const char* location) KPKG_File* file = malloc(sizeof(KPKG_File)); file->location = strdup(location); file->internal_file = NULL; - file->fd = -1; + file->map_size = 0; file->header = header; file->dependencies = NULL; file->files = NULL; @@ -154,6 +165,23 @@ KPKG_File* KPKG_LoadFile(const char* location) } } +void KPKG_CloseFile(KPKG_File* file) +{ + free(file->location); + if (file->internal_file) + { + munmap(file->internal_file, file->map_size); + } + else + { + free(file->header); + free(file->dependencies); + free(file->files); + free(file->strings); + } + free(file); +} + bool KPKG_ValidateFile(KPKG_File* kpkg_file) { if (kpkg_file->strings[kpkg_file->header->strings_size - 1] != 0) @@ -167,12 +195,9 @@ bool KPKG_ValidateFile(KPKG_File* kpkg_file) if (kpkg_file->files[i].path >= kpkg_file->header->strings_size) return false; // File path OOB - if (kpkg_file->fd) + if (kpkg_file->internal_file) { - struct stat buf; - fstat(kpkg_file->fd, &buf); - - __off_t file_section_size = buf.st_size - (sizeof(KPKG_Header) + + __off_t file_section_size = kpkg_file->map_size - (sizeof(KPKG_Header) + kpkg_file->header->dependency_count * sizeof(KPKG_DependencyEntry) + kpkg_file->header->file_count * sizeof(KPKG_FileEntry) + kpkg_file->header->strings_size); @@ -183,4 +208,21 @@ bool KPKG_ValidateFile(KPKG_File* kpkg_file) } return true; -} \ No newline at end of file +} + +char* KPKG_GetString(KPKG_File* kpkg_file, uint64_t offset) +{ + return kpkg_file->strings + offset; +} + +KPKG_DependencyEntry* KPKG_GetDependencies(KPKG_File* kpkg_file, size_t* count) +{ + *count = kpkg_file->header->dependency_count; + return kpkg_file->dependencies; +} + +KPKG_FileEntry* KPKG_GetFileEntries(KPKG_File* kpkg_file, size_t* count) +{ + *count = kpkg_file->header->file_count; + return kpkg_file->files; +} From 215736c5a2f5839d0aff0d6dfbd4965681228ee4 Mon Sep 17 00:00:00 2001 From: ThatHackerDudeFromCyberspace <69104218+ThatHackerDudeFromCyberspace@users.noreply.github.com> Date: Mon, 7 Sep 2026 08:18:11 +0100 Subject: [PATCH 26/27] Fix compilation errors on older GCC --- cli/meson.build | 12 +++++++++++- meson.build | 15 ++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/cli/meson.build b/cli/meson.build index 58db98f..330ca6c 100644 --- a/cli/meson.build +++ b/cli/meson.build @@ -10,6 +10,16 @@ if KPM_PLATFORM == '' KPM_PLATFORM = host_machine.cpu_family() endif -subproject('fbink', default_options: ['bitmap=enabled', 'button-scan=disabled', 'fonts=disabled', 'image=disabled', 'inputlib=disabled', 'opentype=disabled', 'fbdepth=disabled', 'target=' + meson.get_external_property('target', 'Linux')]) +subproject('fbink', default_options: [ + 'bitmap=enabled', + 'button-scan=disabled', + 'fonts=disabled', + 'image=disabled', + 'inputlib=disabled', + 'opentype=disabled', + 'fbink=disabled', + 'fbdepth=disabled', + 'target=' + meson.get_external_property('target', 'Linux') +]) fbink_dep = dependency('fbink') executable('kpm', sources, include_directories: include_dir, dependencies: [kpm_dep, fbink_dep], build_rpath: kmc_lib_dir, install_rpath: kmc_lib_dir) \ No newline at end of file diff --git a/meson.build b/meson.build index e247cd3..0754a1b 100644 --- a/meson.build +++ b/meson.build @@ -11,7 +11,20 @@ add_global_arguments('-DKPM_PKG_PATH="' + get_option('pkg_path') + '"', language kmc_lib_dir = '/var/local/kmc/lib' -subproject('zstd', default_options: ['default_library=static']) +subproject('zstd', default_options: [ + 'default_library=static', + 'legacy_level=7', + 'debug_level=0', + 'static_runtime=true', + 'bin_programs=false', + 'bin_tests=false', + 'bin_contrib=false', + 'zlib=enabled', + 'lzma=disabled', + 'lz4=disabled', + 'c_args=-lrt', + 'cpp_args=-lrt' +]) subproject('liblzma', default_options: ['default_library=static']) subproject('sqlite3', default_options: ['default_library=static']) # PW2 sqlite3 is unusable it straight up doesn't compile I need to fix this subproject('cjson', default_options: ['tests=false', 'default_library=static']) From 53f211c1c9cba22a26803a7165b6bb55e22eb87f Mon Sep 17 00:00:00 2001 From: Lucas Zampieri Date: Sat, 17 Oct 2026 05:49:05 -0300 Subject: [PATCH 27/27] cli: expand GitHub owner/repo shorthand in add-repo The Kindle searchbar runs ; commands through scriptExecutor, which only lets [A-Za-z0-9/_ -] through in arguments, so a URL can never reach add-repo from there. Expand owner/repo[/path] into a raw GitHub URL on the default branch, with path defaulting to manifest and .json appended. Fixes #5 Signed-off-by: Lucas Zampieri --- cli/main.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/cli/main.c b/cli/main.c index b844728..b911563 100644 --- a/cli/main.c +++ b/cli/main.c @@ -110,14 +110,25 @@ int main(int argc, char* argv[]) Repository repository; for (int i = command_index+1; i < argc; i++) { - if ((error = KPM_AddRepository(&kpm, argv[i], &repository, &kpm_io)) != KPM_OK) - kpm_io.log(KPM_VERBOSITY_ERROR, "Could not add repository '%s' (%i: %s)", argv[i], error, KPM_ErrorToString(error)); + char* url; + char* slash = strchr(argv[i], '/'); + if (strpbrk(argv[i], ".:") == NULL && slash != NULL) + { + char* path = strchr(slash+1, '/'); + int repo_len = path != NULL ? path - argv[i] : strlen(argv[i]); + url = asprintf_hd("https://raw.githubusercontent.com/%.*s/HEAD%s.json", repo_len, argv[i], path != NULL ? path : "/manifest"); + } + else + url = strdup(argv[i]); + + if ((error = KPM_AddRepository(&kpm, url, &repository, &kpm_io)) != KPM_OK) + kpm_io.log(KPM_VERBOSITY_ERROR, "Could not add repository '%s' (%i: %s)", url, error, KPM_ErrorToString(error)); else { kpm_io.log(KPM_VERBOSITY_INFO, "Added repository '%s'", repository.id); KPM_FreeRepository(&repository); } - + free(url); } if (error == KPM_OK) @@ -380,7 +391,7 @@ kpm_io.log(KPM_VERBOSITY_INFO, "usage: kpm [--help, -h] [--fbink] [-y] {version version:\n\ Returns the current version of the KPM library and CLI\n\ add-repo:\n\ - Add a repository from one or more URLs\n\ + Add a repository from one or more URLs, or GitHub owner/repo[/path] shorthands (path defaults to manifest, .json is appended)\n\ remove-repo:\n\ Remove one or more repositories given one or more IDs\n\ list-repo:\n\