// Simple package manager. // Only needs stdint.h, stdlib.h, stdio.h, string.h & mkdir() // May be extended with optional extras (checking timestamps etc.) later. // This is NEW CODE written by Zak Yani Star Fenton, public domain (UNLICENSE license) #ifndef PKG_MKDIR_SIMPLE #define PKG_MKDIR_LINUX #endif // For reliable integer sizing #include // For malloc, free #include // For FILE, fopen, fread, fwrite, fclose, printf, fprintf, stdout, stderr #include // For strcmp, strdup #include // For mkdir #ifdef PKG_MKDIR_LINUX #include #else #ifdef PKG_MKDIR_SIMPLE int mkdir(const char* dirname); #else #error Either (-D) PKG_MKDIR_LINUX or PKG_MKDIR_SIMPLE must be defined #endif #endif // The compressed buffer needs to be a bit over twice size the regular // buffer to account for worst case scenario. #define PKG_BUFFERSIZE 4096 #define PKG_COMPRBUFSIZE 8224 #define PKG_JOBTYPE_BUILD 1 #define PKG_JOBTYPE_INSTALL 2 #define PKG_JOBTYPE_REMOVE 3 #define PKG_JOBTYPE_SUM 4 #define PKG_JOBTYPE_COMPRESS 5 #define PKG_JOBTYPE_DECOMPRESS 6 #define PKG_FLAGS_DIRECTORY 0x100 #define PKG_FLAGS_EXECUTABLE 0x200 #define PKG_FLAGS_PROVIDES 0x300 #define PKG_FLAGS_REQUIRES 0x400 #define PKG_FLAGS_SUGGESTS 0x500 #define PKG_FLAGS_CONFLICTS 0x600 typedef struct pkg_job pkg_job_t; typedef struct pkg_project pkg_project_t; typedef struct pkg_sum pkg_sum_t; typedef struct pkg_archive pkg_archive_t; typedef struct pkg_version pkg_version_t; typedef struct pkg_versionmeta pkg_versionmeta_t; typedef long long pkg_size_t; typedef unsigned long long pkg_usize_t; struct pkg_job { char** names; char* target; char* installroot; char* dbroot; int usedb; int nnames; int jobtype; int verbose; }; struct pkg_project { char* projdir; char* projfile; }; struct pkg_sum { char* name; pkg_sum_t* next; pkg_usize_t offset; pkg_usize_t cmprsize; // Compressed/in-package size pkg_usize_t extrsize; // Extracted/real-file size pkg_usize_t checksum64; unsigned int flags; // This field is set to zero normally unsigned int checksum32; unsigned int nameoffset; unsigned int namelength; unsigned int namechecksum; }; struct pkg_archive { int version; int nheaders; pkg_sum_t* headerlist; }; struct pkg_versionmeta { const char* value; pkg_versionmeta_t* next; }; struct pkg_version { int major; int minor; int patch; pkg_versionmeta_t* prerelease; pkg_versionmeta_t* meta; }; // Buffer used for most reads/writes, could be dynamically allocated later. char pkg_linebuffer[PKG_BUFFERSIZE]; char pkg_comprbuffer[PKG_COMPRBUFSIZE]; int pkg_mkdir(const char* path) { #ifdef PKG_MKDIR_LINUX return mkdir(path, 0775); #endif #ifdef PKG_MKDIR_SIMPLE return mkdir(path); #endif } int pkg_remove(const char* path, int isdir) { return unlink(path); } int pkg_versionmeta_alldigits(pkg_versionmeta_t* m) { const char* s = m->value; while (*s) { if (*s < '0' || *s > '9') { return 0; } s++; } return 1; } int pkg_versionmeta_compare(pkg_versionmeta_t* left, pkg_versionmeta_t* right) { int ldigits = pkg_versionmeta_alldigits(left); int rdigits = pkg_versionmeta_alldigits(right); if (ldigits && !rdigits) { return -1; } else if (rdigits && !ldigits) { return 1; } else if (!rdigits && !ldigits) { return strcmp(left->value, right->value); } else { const char* l = left->value; const char* r = right->value; while (*l == '0') { l++; } while (*r == '0') { r++; } size_t llen = strlen(l); size_t rlen = strlen(r); if (llen < rlen) { return -1; } else if (llen > rlen) { return 1; } else { return strcmp(l, r); // ASCII comparison will do for numbers too if same number of digits } } } // Compares versions, returns 0 if equal, -1 if left < right or 1 if // left > right. Treats NULLs as lower-than any real version. int pkg_version_compare(pkg_version_t* left, pkg_version_t* right) { // A NULL is just treated as less-than any real version. if (left == NULL && right == NULL) { return 0; } else if (left == NULL) { return -1; } else if (right == NULL) { return 1; } if (left->major < right->major) { return -1; } else if (left->major > right->major) { return 1; } if (left->minor < right->minor) { return -1; } else if (left->minor > right->minor) { return 1; } if (left->patch < right->patch) { return -1; } else if (left->patch > right->patch) { return 1; } if (left->prerelease != NULL && right->prerelease == NULL) { return -1; } else if (left->prerelease == NULL && right->prerelease != NULL) { return 1; } else if (left->prerelease == NULL && right->prerelease == NULL) { return 0; } pkg_versionmeta_t* lpre = left->prerelease; pkg_versionmeta_t* rpre = right->prerelease; while (lpre != NULL && rpre != NULL) { lpre = lpre->next; rpre = rpre->next; } if (rpre != NULL) { return -1; } else if (lpre != NULL) { return 1; } else { return 0; } } int pkg_version_parseint(int* resultvar, const char* string) { int i = 0; int result = 0; while (string[i] >= '0' && string[i] <= '9') { result *= 10; result += (string[i] - '0'); i++; } *resultvar = result; return i; } int pkg_version_isalnum(char foo) { if (foo >= '0' && foo <= '9') { return 1; } else if (foo >= 'a' && foo <= 'z') { return 1; } else if (foo >= 'A' && foo <= 'Z') { return 1; } else if (foo == '-') { // Hyphens are treated as alnum in version meta return 1; } else { return 0; } } int pkg_versionmeta_parse1(pkg_versionmeta_t** resultvar, const char* string) { int i = 0; while (pkg_version_isalnum(string[i])) { i++; } if (i > 0) { pkg_versionmeta_t* newmeta = malloc(sizeof(pkg_versionmeta_t)); if (newmeta == NULL) { return -1; } newmeta->value = strndup(string, i); newmeta->next = NULL; *resultvar = newmeta; } return i; } int pkg_versionmeta_parse(pkg_versionmeta_t** resultvar, const char* string) { int i = 0; pkg_versionmeta_t* firstmeta = NULL; pkg_versionmeta_t* oldmeta = NULL; do { pkg_versionmeta_t* newmeta; int tmp = pkg_versionmeta_parse1(&newmeta, string+i); if (tmp < 1) { return -1; } i += tmp; if (oldmeta == NULL) { firstmeta = newmeta; } else { oldmeta->next = newmeta; } oldmeta = newmeta; } while (string[i++] == '.'); *resultvar = firstmeta; return i; } // Parses a version string, returns a positive integer indicating the // number of chars parsed on success and <1 if no version string was // detected. int pkg_version_parse(pkg_version_t* version, const char* string) { int i = 0; while (string[i] == ' ') { i++; } int tmp = pkg_version_parseint(&version->major, string+i); if (tmp < 1) { return -1; } i += tmp; if (string[i] != '.') { return -1; } i++; tmp = pkg_version_parseint(&version->minor, string+i); if (tmp < 1) { return -1; } i += tmp; if (string[i] != '.') { return -1; } i++; tmp = pkg_version_parseint(&version->patch, string); if (tmp < 1) { return -1; } i += tmp; if (string[i] == '-') { i++; tmp = pkg_versionmeta_parse(&version->prerelease, string+i); if (tmp < 1) { return -1; } i += tmp; } else { version->prerelease = NULL; } if (string[i] == '+') { i++; tmp = pkg_versionmeta_parse(&version->meta, string+i); if (tmp < 1) { return -1; } i += tmp; } else { version->meta = NULL; } return i; } size_t pkg_firstlongestmatch(unsigned char* data, size_t dlength, unsigned char* key, size_t klength, size_t* indexvar) { if (klength < 1) { *indexvar = -1; return 0; } size_t mlength = 0; size_t i = 0; while (i < dlength) { size_t j = 0; while (j < klength && i + j < dlength && data[i + j] == key[j]) { j++; } if (j > mlength) { mlength = j; *indexvar = i; if (mlength == klength) { return mlength; } } i += j ? j : 1; } return mlength; } #define PKG_COMPRESS_OUTBYTE(b) if (outi < olength) {output[outi] = (unsigned char) (b);} outi++ #define PKG_COMPRESS_FLUSHUNCOMPRESSED() if (unflushed) { \ PKG_COMPRESS_OUTBYTE((unsigned char) unflushed); \ for (size_t flushi = 0; flushi < unflushed; flushi++) { \ PKG_COMPRESS_OUTBYTE(input[(i - unflushed) + flushi]); \ } \ unflushed = 0; \ } size_t pkg_compressv1(unsigned char* input, size_t ilength, unsigned char* output, size_t olength) { if (ilength > 64*1024) { return -1; } size_t i = 0; size_t outi = 0; size_t unflushed = 0; while (i < ilength) { size_t mindex = -1; size_t mlength = pkg_firstlongestmatch(input, i, input + i, ilength - i > 32 ? 32 : ilength - i, &mindex); /*if (mlength > 2 && mindex < 256) { PKG_COMPRESS_FLUSHUNCOMPRESSED(); PKG_COMPRESS_OUTBYTE(0 - (mlength + 32)); PKG_COMPRESS_OUTBYTE(mindex); i += mlength; } else*/ if (mlength > 3) { PKG_COMPRESS_FLUSHUNCOMPRESSED(); PKG_COMPRESS_OUTBYTE(0 - mlength); PKG_COMPRESS_OUTBYTE(mindex); PKG_COMPRESS_OUTBYTE(mindex >> 8); i += mlength; } else { // TODO: This will need to be changed to write multiple uncompressed bytes! if (unflushed >= 100) { PKG_COMPRESS_FLUSHUNCOMPRESSED(); } //PKG_COMPRESS_OUTBYTE(1); //PKG_COMPRESS_OUTBYTE(input[i]); /*if (unflushed == 0 && input[i] >= 'a' && input[i] <= 'z') { PKG_COMPRESS_OUTBYTE(0 - (64 + (input[i] - 'a'))); } else if (unflushed == 0 && input[i] >= 'A' && input[i] <= 'Z') { PKG_COMPRESS_OUTBYTE(0 - (64 + 26 + (input[i] - 'A'))); } else if (unflushed == 0 && input[i] == 0) { PKG_COMPRESS_OUTBYTE(0 - (64 + 26*2 + 0)); } else if (unflushed == 0 && input[i] == 1) { PKG_COMPRESS_OUTBYTE(0 - (64 + 26*2 + 1)); } else if (unflushed == 0 && input[i] == 2) { PKG_COMPRESS_OUTBYTE(0 - (64 + 26*2 + 2)); } else if (unflushed == 0 && input[i] == 0xFF) { PKG_COMPRESS_OUTBYTE(0 - (64 + 26*2 + 3)); } else if (unflushed == 0 && input[i] == ' ') { PKG_COMPRESS_OUTBYTE(0 - (64 + 26*2 + 4)); } else if (unflushed == 0 && input[i] == '.') { PKG_COMPRESS_OUTBYTE(0 - (64 + 26*2 + 5)); } else if (unflushed == 0 && input[i] == ',') { PKG_COMPRESS_OUTBYTE(0 - (64 + 26*2 + 6)); } else if (unflushed == 0 && input[i] == '(') { PKG_COMPRESS_OUTBYTE(0 - (64 + 26*2 + 7)); } else if (unflushed == 0 && input[i] == ')') { PKG_COMPRESS_OUTBYTE(0 - (64 + 26*2 + 8)); } else if (unflushed == 0 && input[i] == '\n') { PKG_COMPRESS_OUTBYTE(0 - (64 + 26*2 + 9)); } else if (unflushed == 0 && input[i] == '/') { PKG_COMPRESS_OUTBYTE(0 - (64 + 26*2 + 10)); } else if (unflushed == 0 && input[i] == ';') { PKG_COMPRESS_OUTBYTE(0 - (64 + 26*2 + 11)); } else {*/ unflushed++; /*}*/ i++; } } PKG_COMPRESS_FLUSHUNCOMPRESSED(); return outi; } size_t pkg_decompressv1(unsigned char* input, size_t ilength, unsigned char* output, size_t olength) { if (ilength > 129*1024) { return -1; } size_t i = 0; size_t outi = 0; while (i < ilength) { char b = (char) input[i]; if (b < 0) { if (i+2 < ilength) { size_t len = 0 - b; size_t idx = (((size_t)input[i+1]) & 0xFF) | ((((size_t)input[i+2]) & 0xFF) << 8); i += 3; if (idx+len > outi) { return -1; } memcpy(output + outi, output + idx, len); outi += len; } else { return -1; } } else { if (i + b >= ilength) { return -1; } memcpy(output + outi, input + i + 1, b); i += 1 + b; outi += b; } } return outi; } char* pkg_ezstrcat(const char* a, const char* b) { int la = strlen(a); int lb = strlen(b); char* output = malloc(la + lb + 1); if (output == NULL) { fprintf(stderr, "MEMORY ERROR: Failed to allocate memory for string concatenation!\n"); return NULL; } int i; for (i = 0; i < la; i++) { output[i] = a[i]; } for (; i < la + lb; i++) { output[i] = b[i - la]; } output[i] = 0; return output; } uint32_t pkg_cstringhash32(const char* cstring) { uint32_t result = 0x811C9DC5U; uint32_t chud = 0x01000193U; while (*cstring) { result ^= ((uint32_t) *cstring++) & 0xFFU; result *= chud; } return result; } int pkg_sum_init(pkg_sum_t* sum, char* filename, int dupfilename) { if (sum == NULL) { return -1; } sum->cmprsize = 0; sum->extrsize = 0; sum->checksum64 = 0x811C9DC5BADC0DE5ULL; sum->checksum32 = 0x811C9DC5U; sum->next = NULL; if (dupfilename) { sum->name = strdup(filename); if (sum->name == NULL) { return -1; } } else { sum->name = filename; } return 0; } int pkg_sum_append(pkg_sum_t* sum, void* data, pkg_usize_t size) { unsigned char* bytes = data; if (sum == NULL) { return -1; } if (size == 0) { return 0; } if (data == NULL) { return -1; } pkg_usize_t chud64 = 0x0100019301000193ULL; unsigned int chud32 = 0x01000193U; for (pkg_usize_t i = 0; i < size; i++) { sum->checksum64 ^= ((pkg_usize_t) bytes[i]) & 0xFFULL; sum->checksum64 *= chud64; sum->checksum32 ^= ((unsigned int) bytes[i]) & 0xFFU; sum->checksum32 *= chud32; } sum->extrsize += size; return 0; } pkg_sum_t* pkg_archive_addheader(pkg_archive_t* archive, const char* name) { if (archive == NULL) { return NULL; } pkg_sum_t* newheader = malloc(sizeof(pkg_sum_t)); if (newheader == NULL) { return NULL; } if (pkg_sum_init(newheader, name, 1) != 0) { free(newheader); return NULL; } pkg_sum_t* lastheader = archive->headerlist; while (lastheader != NULL && lastheader->next != NULL) { lastheader = lastheader->next; } if (lastheader == NULL) { archive->headerlist = newheader; } else { lastheader->next = newheader; } return newheader; } int pkg_archive_init(pkg_archive_t* archive) { if (archive == NULL) { return -1; } archive->version = 0; archive->headerlist = NULL; return 0; } size_t pkg_writefzeros(size_t nzeros, FILE* f) { char zero = 0; size_t nwritten = 0; while (nwritten < nzeros && fwrite(&zero, 1, 1, f) == 1) { nwritten++; } return nwritten; } int pkg_archive_writef32(pkg_archive_t* archive, unsigned int value, FILE* output, pkg_sum_t* sum) { unsigned char bytes[4]; bytes[0] = (unsigned char) value; bytes[1] = (unsigned char) (value >> 8); bytes[2] = (unsigned char) (value >> 16); bytes[3] = (unsigned char) (value >> 24); int nwritten = (int) fwrite(bytes, 1, 4, output); if (sum != NULL) { pkg_sum_append(sum, bytes, nwritten); } return nwritten; } int pkg_archive_writef64(pkg_archive_t* archive, pkg_usize_t value, FILE* output, pkg_sum_t* sum) { int nwritten = pkg_archive_writef32(archive, (unsigned int) value, output, sum); if (nwritten != 4) { return nwritten; } nwritten += pkg_archive_writef32(archive, (unsigned int) (value>>32), output, sum); return nwritten; } int pkg_archive_readf32(pkg_archive_t* archive, unsigned int* valuevar, FILE* input, pkg_sum_t* sum) { unsigned char bytes[4]; int nread = (int) fread(bytes, 1, 4, input); if (nread != 4) { fprintf(stderr, "WARNING: fread of 4 bytes read %d bytes!\n", nread); } *valuevar = bytes[0] | ((unsigned int) bytes[1])<<8 | ((unsigned int) bytes[2])<<16 | ((unsigned int) bytes[3])<<24; if (sum != NULL) { pkg_sum_append(sum, bytes, nread); } return nread; } int pkg_archive_readf64(pkg_archive_t* archive, pkg_usize_t* valuevar, FILE* input, pkg_sum_t* sum) { unsigned int valuelow, valuehigh; int nread = pkg_archive_readf32(archive, &valuelow, input, sum); if (nread != 4) { return nread; } nread += pkg_archive_readf32(archive, &valuehigh, input, sum); *valuevar = ((pkg_usize_t) valuelow) | (((pkg_usize_t) valuehigh) << 32); return nread; } int pkg_archive_readheaders(pkg_archive_t* archive, FILE* input) { char buffer[9]; buffer[8] = 0; if (archive == NULL) { fprintf(stderr, "INTERNAL ERROR: Invalid archive structure!\n"); return -1; } pkg_usize_t nread = 0; if (fread(buffer, 1, 8, input) != 8) { fprintf(stderr, "WRITE ERROR: Reading from archive file failed!\n"); return -1; } nread += 8; if (strcmp(buffer, "DEMOPKG0")) { fprintf(stderr, "BAD PACKAGE DATA: Magic number doesn't match!\n"); return -1; } if (fread(buffer, 1, 8, input) != 8) { fprintf(stderr, "WRITE ERROR: Reading from archive file failed!\n"); return -1; } nread += 8; fprintf(stderr, "Got reserved string '%s'\n", buffer); unsigned int nfiles, headersize; nread += pkg_archive_readf32(archive, &nfiles, input, NULL); // Number of header entries nread += pkg_archive_readf32(archive, &headersize, input, NULL); // Size of per-entry header if (headersize != 48) { fprintf(stderr, "BAD PACKAGE DATA: Expected a header size of 48 bytes per encoded file but got %d\n", headersize); return -1; } fprintf(stderr, "Loading %d file headers...\n", nfiles); pkg_sum_t* filehdr; for (unsigned int i = 0; i < nfiles; i++) { filehdr = pkg_archive_addheader(archive, ""); if (filehdr == NULL) { fprintf(stderr, "INTERNAL ERROR: Failed to add new header to archive struct!\n"); return -1; } pkg_sum_t filehdrsum; if (pkg_sum_init(&filehdrsum, "", 0) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_init failed!\n"); return -1; } size_t nread2 = 0; unsigned int hdrsum; nread2 += pkg_archive_readf64(archive, &filehdr->offset, input, &filehdrsum); nread2 += pkg_archive_readf64(archive, &filehdr->cmprsize, input, &filehdrsum); nread2 += pkg_archive_readf64(archive, &filehdr->extrsize, input, &filehdrsum); nread2 += pkg_archive_readf32(archive, &filehdr->flags, input, &filehdrsum); nread2 += pkg_archive_readf32(archive, &filehdr->checksum32, input, &filehdrsum); nread2 += pkg_archive_readf32(archive, &filehdr->nameoffset, input, &filehdrsum); nread2 += pkg_archive_readf32(archive, &filehdr->namelength, input, &filehdrsum); nread2 += pkg_archive_readf32(archive, &filehdr->namechecksum, input, &filehdrsum); nread2 += pkg_archive_readf32(archive, &hdrsum, input, NULL); if (hdrsum != filehdrsum.checksum32) { fprintf(stderr, "VERIFICATION ERROR: Header checksum for file #%d in archive doesn't match. Recorded checksum 0x%X calculated checksum 0x%X!\n", i, filehdrsum.checksum32, hdrsum); return -1; } //fprintf(stderr, "For file header #%d: recorded checksum 0x%X calculated checksum 0x%X!\n", i, filehdrsum.checksum32, hdrsum); if (nread2 != 48) { fprintf(stderr, "READ ERROR: Expected to read 48 bytes of file header info but read %d instead!\n", (int) nread2); return -1; } nread += nread2; } filehdr = archive->headerlist; size_t nreadstringdata; int filenum = 0; while (filehdr != NULL) { size_t n = fread(pkg_linebuffer, 1, filehdr->namelength, input); if (n != filehdr->namelength) { fprintf(stderr, "READ ERROR: Failed while reading filename strings!\n"); return -1; } filehdr->name = strndup(pkg_linebuffer, filehdr->namelength); unsigned int namesum = pkg_cstringhash32(filehdr->name); if (namesum != filehdr->namechecksum) { fprintf(stderr, "VERIFICATION ERROR: Filename checksum for file #%d in archive doesn't match. Recorded checksum 0x%X calculated checksum 0x%X!\n", filenum, filehdr->namechecksum, namesum); return -1; } //fprintf(stderr, "For file header #%d in archive: Recorded checksum 0x%X calculated checksum 0x%X!\n", filenum, filehdr->namechecksum, namesum); fprintf(stderr, "Loaded header for file #%d in archive: extracted size %lu, compressed size %lu, data checksum 0x%X, name '%s'\n", filenum, (long) filehdr->extrsize, filehdr->cmprsize, filehdr->checksum32, filehdr->name); filehdr = filehdr->next; filenum++; } return 0; } int pkg_archive_writeheaders(pkg_archive_t* archive, FILE* output, int blankrun) { if (archive == NULL) { fprintf(stderr, "INTERNAL ERROR: Invalid archive structure!\n"); return -1; } pkg_usize_t nwritten = 0; if (fwrite((blankrun ? "UNFINSHD" : "DEMOPKG0"), 1, 8, output) != 8) { fprintf(stderr, "WRITE ERROR: Writing to archive file failed!\n"); return -1; } nwritten += 8; if (fwrite((blankrun ? "PARTWRIT" : "RESERVED"), 1, 8, output) != 8) { fprintf(stderr, "WRITE ERROR: Writing to archive file failed!\n"); return -1; } nwritten += 8; unsigned int nfiles = 0; pkg_sum_t* filehdr = archive->headerlist; while (filehdr != NULL) { filehdr = filehdr->next; nfiles++; } nwritten += pkg_archive_writef32(archive, nfiles, output, NULL); // Number of header entries nwritten += pkg_archive_writef32(archive, 48, output, NULL); // Size of per-entry header filehdr = archive->headerlist; unsigned int nameoffset = 0; while (filehdr != NULL) { unsigned int namelen = (unsigned int) strlen(filehdr->name); pkg_sum_t filehdrsum; if (pkg_sum_init(&filehdrsum, "", 0) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_init failed!\n"); return -1; } size_t nwritten2 = 0; nwritten2 += pkg_archive_writef64(archive, filehdr->offset, output, &filehdrsum); nwritten2 += pkg_archive_writef64(archive, filehdr->cmprsize, output, &filehdrsum); nwritten2 += pkg_archive_writef64(archive, filehdr->extrsize, output, &filehdrsum); nwritten2 += pkg_archive_writef32(archive, filehdr->flags, output, &filehdrsum); nwritten2 += pkg_archive_writef32(archive, filehdr->checksum32, output, &filehdrsum); nwritten2 += pkg_archive_writef32(archive, nameoffset, output, &filehdrsum); nwritten2 += pkg_archive_writef32(archive, namelen, output, &filehdrsum); nwritten2 += pkg_archive_writef32(archive, pkg_cstringhash32(filehdr->name), output, &filehdrsum); nwritten2 += pkg_archive_writef32(archive, filehdrsum.checksum32, output, NULL); if (nwritten2 != 48) { fprintf(stderr, "WRITE ERROR: Expected to write 48 bytes of file header info but wrote %d instead!\n", (int) nwritten2); return -1; } nwritten += nwritten2; nameoffset += namelen; filehdr = filehdr->next; } filehdr = archive->headerlist; while (filehdr != NULL) { size_t nwritten2 = 0; nwritten2 = fwrite(filehdr->name, 1, strlen(filehdr->name), output); if (nwritten2 != strlen(filehdr->name)) { fprintf(stderr, "WRITE ERROR: Failed while writing filename strings!\n"); return -1; } nwritten += nwritten2; filehdr = filehdr->next; } fprintf(stderr, "Written %ld bytes of header\n", (long) nwritten); return 0; } int pkg_archive_extractcontents(pkg_archive_t* archive, FILE* input, const char* installroot) { if (archive == NULL || archive->headerlist == NULL) { fprintf(stderr, "INTERNAL ERROR: Invalid or uninitialised archive structure!\n"); return -1; } pkg_usize_t nwritten = 0; pkg_sum_t* filehdr = archive->headerlist; while (filehdr != NULL) { char* outputname = pkg_ezstrcat(installroot, filehdr->name); // Note, this assumes one or the other includes a path separator if (filehdr->flags & PKG_FLAGS_DIRECTORY) { fprintf(stderr, "Creating directory '%s'...\n", outputname); int mkdirresult = pkg_mkdir(outputname); fprintf(stderr, "mkdir of '%s' returned %d\n", outputname, mkdirresult); } else { fprintf(stderr, "Creating file '%s'...\n", outputname); FILE* f = fopen(outputname, "w"); if (f == 0) { fprintf(stderr, "BAD FILE: Failed to open '%s' for writing!\n", outputname); return -1; } size_t nreadnow, nwrittennow; size_t remaining = filehdr->extrsize; pkg_sum_t datasum; if (pkg_sum_init(&datasum, "", 0) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_init failed!\n"); return -1; } do { size_t part = PKG_BUFFERSIZE > remaining ? remaining : PKG_BUFFERSIZE; nreadnow = fread(pkg_linebuffer, 1, part, input); if (nreadnow > 0) { nwrittennow = fwrite(pkg_linebuffer, 1, nreadnow, f); if (pkg_sum_append(&datasum, pkg_linebuffer, nreadnow) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_append failed!\n"); free(outputname); fclose(f); return -1; } if (nwrittennow != nreadnow) { fprintf(stderr, "WRITE ERROR: Error while extracting contents of '%s' from the archive!\n", outputname); free(outputname); fclose(f); return -1; } nwritten += nwrittennow; remaining -= nwrittennow; } } while(remaining > 0); fclose(f); if (datasum.checksum32 != filehdr->checksum32) { fprintf(stderr, "VERIFICATION ERROR: File data checksums for '%s' don't match. Recorded checksum 0x%X, calculated checksum 0x%X!\n", filehdr->name, filehdr->checksum32, datasum.checksum32); return -1; } } filehdr = filehdr->next; } return 0; } int pkg_archive_writecontents(pkg_archive_t* archive, FILE* output, const char* inputdir) { if (archive == NULL || archive->headerlist == NULL) { fprintf(stderr, "INTERNAL ERROR: Invalid or uninitialised archive structure!\n"); return -1; } pkg_usize_t nwritten = 0; pkg_sum_t* filehdr = archive->headerlist; while (filehdr != NULL) { if (filehdr->flags & PKG_FLAGS_DIRECTORY) { fprintf(stderr, "Skipping writing of file contents for '%s' (is directory)\n", filehdr->name); } else { char* inputname = pkg_ezstrcat(inputdir, filehdr->name); // Note, this assumes one or the other includes a path separator FILE* f = fopen(inputname, "rb"); if (f == 0) { fprintf(stderr, "BAD FILE: Failed to open '%s' for reading!\n", inputname); return -1; } size_t nreadnow, nwrittennow; do { nreadnow = fread(pkg_linebuffer, 1, PKG_BUFFERSIZE, f); if (nreadnow > 0) { nwrittennow = fwrite(pkg_linebuffer, 1, nreadnow, output); if (pkg_sum_append(filehdr, pkg_linebuffer, nreadnow) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_append failed!\n"); free(inputname); fclose(f); return -1; } if (nwrittennow != nreadnow) { fprintf(stderr, "WRITE ERROR: Error while writing contents of '%s' into the archive!\n", inputname); free(inputname); fclose(f); return -1; } nwritten += nwrittennow; } } while(nreadnow > 0); fclose(f); } filehdr = filehdr->next; } return 0; } char* pkg_linein(FILE* f, pkg_size_t* sizevar) { char b; char* buffer = pkg_linebuffer; pkg_size_t i = 0; do { if (i == PKG_BUFFERSIZE - 1) { fprintf(stderr, "BAD FILE: Text line overflows buffer size!\n"); if (sizevar) { *sizevar += i; } buffer[i] = 0; return NULL; } if (fread(&b, 1, 1, f) != 1) { if (sizevar) { *sizevar += i; } buffer[i] = 0; if (i == 0) { return NULL; } return buffer; } if (b == 0 || b == '\n') { if (sizevar) { *sizevar += i + 1; } buffer[i] = 0; return buffer; } buffer[i] = b; i++; } while (1); } int pkg_archive_uninstallcontents(pkg_archive_t* archive, FILE* input, const char* installroot) { if (archive == NULL || archive->headerlist == NULL) { fprintf(stderr, "INTERNAL ERROR: Invalid or uninitialised archive structure!\n"); return -1; } pkg_usize_t nwritten = 0; pkg_sum_t* filehdr = archive->headerlist; while (filehdr != NULL) { char* outputname = pkg_ezstrcat(installroot, filehdr->name); // Note, this assumes one or the other includes a path separator if (filehdr->flags & PKG_FLAGS_DIRECTORY) { // Skip } else { fprintf(stderr, "Removing packaged file '%s'...\n", outputname); pkg_remove(outputname, 0); } filehdr = filehdr->next; } filehdr = archive->headerlist; while (filehdr != NULL) { char* outputname = pkg_ezstrcat(installroot, filehdr->name); // Note, this assumes one or the other includes a path separator if (filehdr->flags & PKG_FLAGS_DIRECTORY) { fprintf(stderr, "Removing packaged directory '%s'...\n", outputname); pkg_remove(outputname, 1); } else { // Skip (already deleted) } filehdr = filehdr->next; } return 0; } const char* pkg_build_linematch(const char* line, const char* cmd) { while (*cmd) { if (*line == 0) { return NULL; } if (*line != *cmd) { return NULL; } *line++; *cmd++; } return line; } int pkg_job_build(pkg_job_t* job, char* name) { int n = strlen(name); if (n < 5 || name[n-4] != '.' || name[n-3] != 'p' || name[n-2] != 'k' || name[n-1] != 'b') { fprintf(stderr, "BAD FILENAME: Package build file must end in .pkb\n"); return -1; } int sepi = 0; for (int i = 0; name[i] != 0; i++) { if (name[i] == '/' || name[i] == '\\') { sepi = i+1; } } char* cd = "./"; char* srcd; if (sepi == 0) { srcd = cd; } else { srcd = strndup(name, sepi); } char* distsubname = "dist"; // The directory packageable files should be kept char* distd = pkg_ezstrcat(srcd, "dist"); char* outname = strdup(name); outname[strlen(outname)-1] = 'g'; // "*.pkb"-> "*.pkg" char* shortname = strndup(name + sepi, n - (sepi + 4)); // Trim directory path and filename extension printf("TODO build '%s' source dirname '%s' dist dirname '%s' output name '%s' shortname '%s'...\n", name, srcd, distd, outname, shortname); FILE* buildf = fopen(name, "rb"); if (buildf == NULL) { fprintf(stderr, "BAD FILE: Failed to open package build file '%s' for reading!\n", name); return -1; } char* line; pkg_archive_t archive; if (pkg_archive_init(&archive) != 0) { fprintf(stderr, "INTERNAL ERROR: Failed to initialise archive structure!\n"); fclose(buildf); return -1; } while ((line = pkg_linein(buildf, NULL)) != NULL) { fprintf(stderr, "Processing line '%s'...\n", line); const char* arg; if ((arg = pkg_build_linematch(line, "provides ")) != NULL) { pkg_sum_t* mkdirhdr = pkg_archive_addheader(&archive, arg); if (mkdirhdr == NULL) { fprintf(stderr, "INTERNAL ERROR: Failed to add archive header for '%s'!\n", line); fclose(buildf); return -1; } mkdirhdr->flags |= PKG_FLAGS_PROVIDES; } else if ((arg = pkg_build_linematch(line, "requires ")) != NULL) { pkg_sum_t* mkdirhdr = pkg_archive_addheader(&archive, arg); if (mkdirhdr == NULL) { fprintf(stderr, "INTERNAL ERROR: Failed to add archive header for '%s'!\n", line); fclose(buildf); return -1; } mkdirhdr->flags |= PKG_FLAGS_REQUIRES; } else if ((arg = pkg_build_linematch(line, "suggests ")) != NULL) { pkg_sum_t* mkdirhdr = pkg_archive_addheader(&archive, arg); if (mkdirhdr == NULL) { fprintf(stderr, "INTERNAL ERROR: Failed to add archive header for '%s'!\n", line); fclose(buildf); return -1; } mkdirhdr->flags |= PKG_FLAGS_SUGGESTS; } else if ((arg = pkg_build_linematch(line, "conflicts ")) != NULL) { pkg_sum_t* mkdirhdr = pkg_archive_addheader(&archive, arg); if (mkdirhdr == NULL) { fprintf(stderr, "INTERNAL ERROR: Failed to add archive header for '%s'!\n", line); fclose(buildf); return -1; } mkdirhdr->flags |= PKG_FLAGS_CONFLICTS; } else if ((arg = pkg_build_linematch(line, "directory ")) != NULL) { pkg_sum_t* mkdirhdr = pkg_archive_addheader(&archive, arg); if (mkdirhdr == NULL) { fprintf(stderr, "INTERNAL ERROR: Failed to add archive header for '%s'!\n", line); fclose(buildf); return -1; } mkdirhdr->flags |= PKG_FLAGS_DIRECTORY; } else if ((arg = pkg_build_linematch(line, "executable ")) != NULL) { pkg_sum_t* mkdirhdr = pkg_archive_addheader(&archive, arg); if (mkdirhdr == NULL) { fprintf(stderr, "INTERNAL ERROR: Failed to add archive header for '%s'!\n", line); fclose(buildf); return -1; } mkdirhdr->flags |= PKG_FLAGS_EXECUTABLE; } else if (pkg_archive_addheader(&archive, line) == NULL) { fprintf(stderr, "INTERNAL ERROR: Failed to add archive header for '%s'!\n", line); fclose(buildf); return -1; } } fclose(buildf); FILE* outputf = fopen(outname, "w"); if (outputf == NULL) { fprintf(stderr, "WRITE ERROR: Failed to open package build file '%s' for writing!\n", outname); return -1; } // The first, "blank" run only writes a placeholder of the headers if (pkg_archive_writeheaders(&archive, outputf, 1) != 0) { fprintf(stderr, "ERROR WRITING HEADERS: First run of pkg_archive_writeheaders failed!\n"); return -1; } // The files themselves are written after the blank headers, but checksums & // sizes for the headers are calculated during writing. if (pkg_archive_writecontents(&archive, outputf, distd) != 0) { fprintf(stderr, "ERROR WRITING CONTENTS: The pkg_archive_writecontents function failed!\n"); return -1; } // The output file is then closed and reopened, or on compatible systems fseek // or something like that can be used to return to the start of the file. fclose(outputf); outputf = fopen(outname, "w"); if (outputf == NULL) { fprintf(stderr, "WRITE ERROR: Failed to open package build file '%s' for writing!\n", outname); return -1; } // Now at the start of the file again, we can write the final headers. if (pkg_archive_writeheaders(&archive, outputf, 0) != 0) { fprintf(stderr, "ERROR WRITING HEADERS: Second run of pkg_archive_writeheaders failed!\n"); return -1; } // And it seems we have to write the contents properly this time. if (pkg_archive_writecontents(&archive, outputf, distd) != 0) { fprintf(stderr, "ERROR WRITING CONTENTS: The pkg_archive_writecontents function failed!\n"); return -1; } // Then upon this final close the file should be finished (subject to // verification when installing later). fclose(outputf); if (srcd != cd) { free(srcd); } free(distd); free(outname); free(shortname); return 0; } char* pkg_simplefname(char* name) { char* laststart = name; while (*name) { if ((*name == '/' || *name == '\\') && *(name+1) != 0) { laststart = name+1; } name++; } laststart = strdup(laststart); if (laststart == NULL) { return NULL; } for (int i = 0; laststart[i] != 0; i++) { if (laststart[i] == '.' || laststart[i] == '/' || laststart[i] == '\\') { laststart[i] = 0; return laststart; } } return laststart; } int pkg_job_install(pkg_job_t* job, char* name) { int n = strlen(name); if (n < 5 || name[n-4] != '.' || name[n-3] != 'p' || name[n-2] != 'k' || name[n-1] != 'g') { fprintf(stderr, "BAD FILENAME: Installable package file must end in .pkg\n"); return -1; } pkg_archive_t archive; if (pkg_archive_init(&archive) != 0) { fprintf(stderr, "INTERNAL ERROR: Failed to initialise archive structure!\n"); return -1; } FILE* inputf = fopen(name, "rb"); if (inputf == NULL) { fprintf(stderr, "READ ERROR: Failed to open package file '%s' for reading!\n", name); return -1; } if (pkg_archive_readheaders(&archive, inputf) != 0) { fprintf(stderr, "FILE ERROR: Failed to read package headers!\n"); return -1; } if (pkg_archive_extractcontents(&archive, inputf, job->installroot) != 0) { fprintf(stderr, "FILE ERROR: Failed to extract package contents!\n"); return -1; } fclose(inputf); if (job->usedb) { inputf = fopen(name, "rb"); if (inputf == NULL) { fprintf(stderr, "READ ERROR: Failed to open package file '%s' for reading!\n", name); return -1; } char* simplename = pkg_simplefname(name); char* dbfile = pkg_ezstrcat(job->dbroot, simplename); dbfile = pkg_ezstrcat(dbfile, ".pkh"); fprintf(stderr, "Creating installation record '%s'...\n", dbfile); FILE* outputf = fopen(dbfile, "w"); if (outputf == NULL) { pkg_mkdir(job->dbroot); outputf = fopen(dbfile, "w"); if (outputf == NULL) { fprintf(stderr, "READ ERROR: Failed to open db file '%s' for writing!\n", dbfile); return -1; } } size_t nread; do { nread = fread(pkg_linebuffer, 1, PKG_BUFFERSIZE, inputf); if (nread > 0) { size_t nwritten = fwrite(pkg_linebuffer, 1, nread, outputf); if (nwritten != nread) { fprintf(stderr, "WRITE ERROR: Failed while writing db file '%s'!\n", dbfile); fclose(inputf); fclose(outputf); return -1; } } } while (nread > 0); fclose(inputf); fclose(outputf); dbfile = pkg_ezstrcat(job->dbroot, "pkglist.txt"); outputf = fopen(dbfile, "a"); if (outputf == NULL) { fprintf(stderr, "Creating database index file '%s'...\n", dbfile); outputf = fopen(dbfile, "w"); if (outputf == NULL) { fprintf(stderr, "READ ERROR: Failed to open db file '%s' for writing!\n", dbfile); return -1; } } fprintf(outputf, "%s\n", simplename); fclose(outputf); } return 0; } int pkg_job_remove(pkg_job_t* job, char* name) { int n = strlen(name); FILE* inputf = NULL; char* dbfile = NULL; if (n < 5 || name[n-4] != '.' || name[n-3] != 'p' || name[n-2] != 'k' || (name[n-1] != 'g' && name[n-1] != 'h')) { if (job->usedb) { char* simplename = pkg_simplefname(name); dbfile = pkg_ezstrcat(job->dbroot, simplename); dbfile = pkg_ezstrcat(dbfile, ".pkh"); inputf = fopen(dbfile, "rb"); if (inputf == NULL) { fprintf(stderr, "READ ERROR: Failed to open package file '%s' for reading!\n", dbfile); return -1; } } else { fprintf(stderr, "BAD FILENAME: Installable package file must end in .pkg or .pkh if db is explicitly disabled\n"); return -1; } } else { inputf = fopen(name, "rb"); if (inputf == NULL) { fprintf(stderr, "READ ERROR: Failed to open package file '%s' for reading!\n", name); return -1; } } pkg_archive_t archive; if (pkg_archive_init(&archive) != 0) { fprintf(stderr, "INTERNAL ERROR: Failed to initialise archive structure!\n"); return -1; } if (pkg_archive_readheaders(&archive, inputf) != 0) { fprintf(stderr, "FILE ERROR: Failed to read package headers!\n"); return -1; } if (pkg_archive_uninstallcontents(&archive, inputf, job->installroot) != 0) { fprintf(stderr, "FILE ERROR: Failed to uninstall package contents!\n"); return -1; } fclose(inputf); // Clean up the .pkh file after uninstalling the packaged files if (dbfile) { fprintf(stderr, "Removing installation record '%s'...\n", dbfile); pkg_remove(dbfile, 0); } return 0; } int pkg_job_sum(pkg_job_t* job, char* name) { pkg_sum_t sum; //fprintf(stderr, "Checking the sum of '%s'...\n", name); if (pkg_sum_init(&sum, name, 0) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_init failed for '%s'!\n", name); return -1; } FILE* inf = fopen(name, "rb"); if (inf == NULL) { fprintf(stderr, "BAD FILE: Failed to open file '%s' for reading!\n", name); return -1; } size_t nread = 0; do { nread = fread(pkg_linebuffer, 1, PKG_BUFFERSIZE, inf); if (pkg_sum_append(&sum, pkg_linebuffer, nread) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_append failed for '%s'!\n", name); fclose(inf); return -1; } } while (nread == PKG_BUFFERSIZE); fclose(inf); printf("%s checksum64=0x%llX checksum32=0x%X size=%llu [namesum=0x%X]\n", sum.name, sum.checksum64, sum.checksum32, sum.extrsize, pkg_cstringhash32(sum.name)); return 0; } int pkg_compress_f2f(FILE* inf, FILE* outf, size_t size, pkg_sum_t* sum) { size_t nread = 0; do { nread = fread(pkg_linebuffer, 1, PKG_BUFFERSIZE, inf); if (sum != NULL && pkg_sum_append(sum, pkg_linebuffer, nread) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_append failed during compression!\n"); return -1; } if (nread > 0) { printf("."); fflush(stdout); pkg_sum_t blocksum; if (pkg_sum_init(&blocksum, "", 0) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_init failed!\n"); return -1; } size_t ncompressed = pkg_compressv1(pkg_linebuffer, nread, pkg_comprbuffer, PKG_COMPRBUFSIZE); if (ncompressed < 1 || ncompressed > PKG_COMPRBUFSIZE || ncompressed >= 64*1024) { fprintf(stderr, "INTERNAL ERROR: pkg_compressv1 failed!\n"); return -1; } if (pkg_sum_append(&blocksum, pkg_comprbuffer, ncompressed) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_append failed during compression!\n"); return -1; } unsigned char hdr[4]; hdr[0] = 0xCF; hdr[1] = 0x11; hdr[2] = (unsigned char) ncompressed; hdr[3] = (unsigned char) (ncompressed >> 8); if (fwrite(hdr, 1, 4, outf) != 4) { fprintf(stderr, "WRITE ERROR: Failed while writing compressed output!\n"); return -1; } if (fwrite(pkg_comprbuffer, 1, ncompressed, outf) != ncompressed) { fprintf(stderr, "WRITE ERROR: Failed while writing compressed output!\n"); return -1; } if (pkg_archive_writef32(NULL, blocksum.checksum32, outf, NULL) != 4) { fprintf(stderr, "WRITE ERROR: Failed while writing compressed output!\n"); return -1; } } } while (nread == PKG_BUFFERSIZE); return 0; } int pkg_decompress_f2f(FILE* inf, FILE* outf, size_t size, pkg_sum_t* sum) { size_t nread = 0; size_t totalread = 0; do { if (size != -1 && nread + 8 > size) { fprintf(stderr, "INTERNAL ERROR: Buffer has trailing junk not long enough to be a compressed chunk!\n"); return -1; } unsigned char hdr[4]; nread = fread(hdr, 1, 4, inf); if (nread == 0 && size == -1) { // Finished reading input stream until the end, return as normal. return 0; } if (nread != 4) { fprintf(stderr, "READ ERROR: Failed at block header while decompressing file stream!\n"); return -1; } if (hdr[0] != 0xCF || hdr[1] != 0x11) { fprintf(stderr, "VERIFICATION ERROR: Magic number at start of compressed data block doesn't match for compressed data!\n"); return -1; } size_t ncompressed = (((size_t) hdr[2]) & 0xFF) | ((((size_t) hdr[3]) & 0xFF) << 8); fprintf(stderr, "Reading %d bytes (compressed)...\n", ncompressed); if (size != -1 && nread + 4 + ncompressed > size) { fprintf(stderr, "INTERNAL ERROR: Buffer size does not fit compressed chunk size!\n"); return -1; } if (sum != NULL && pkg_sum_append(sum, pkg_linebuffer, nread) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_append failed during compression!\n"); return -1; } if (nread > 0) { printf("."); fflush(stdout); pkg_sum_t blocksum; if (pkg_sum_init(&blocksum, "", 0) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_init failed!\n"); return -1; } nread += fread(pkg_comprbuffer, 1, ncompressed, inf); if (nread != 4 + ncompressed) { fprintf(stderr, "READ ERROR: Failed mid-block while decompressing file stream!\n"); return -1; } size_t ndecompressed = pkg_decompressv1(pkg_comprbuffer, ncompressed, pkg_linebuffer, PKG_BUFFERSIZE); if (ndecompressed < 1 || ndecompressed > PKG_BUFFERSIZE || ndecompressed >= 64*1024) { fprintf(stderr, "INTERNAL ERROR: pkg_decompressv1 failed!\n"); return -1; } if (pkg_sum_append(&blocksum, pkg_linebuffer, ndecompressed) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_append failed during compression!\n"); return -1; } unsigned int comprsum; if (pkg_archive_readf32(NULL, &comprsum, inf, NULL) != 4) { fprintf(stderr, "READ ERROR: Failed while reading compressed input!\n"); return -1; } size_t nwritten = fwrite(pkg_linebuffer, 1, ndecompressed, outf); if (nwritten != ndecompressed) { fprintf(stderr, "WRITE ERROR: Failed mid-block while decompressing file stream! Tried to write %d bytes but only wrote %d!\n", (int) ndecompressed, (int) nwritten); return -1; } nread += 4; totalread += nread; } } while (nread > 0 && (size == -1 || totalread < size)); return 0; } int pkg_job_compress(pkg_job_t* job, char* name) { pkg_sum_t sum; char* outname = pkg_ezstrcat(name, ".cv1"); if (pkg_sum_init(&sum, outname, 0) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_init failed for '%s'!\n", name); free(outname); return -1; } FILE* inf = fopen(name, "rb"); if (inf == NULL) { fprintf(stderr, "BAD FILE: Failed to open file '%s' for reading!\n", name); free(outname); return -1; } FILE* outf = fopen(outname, "w"); if (outf == NULL) { fprintf(stderr, "WRITE ERROR: Failed to open file '%s' for writing!\n", outname); free(outname); fclose(inf); return -1; } int result = pkg_compress_f2f(inf, outf, -1, &sum); fclose(inf); fclose(outf); if (result < 0) { fprintf(stderr, "COMPRESSION FAILED: pkg_compress_f2f returned non-zero result!\n"); return -1; } printf("%s checksum64=0x%llX checksum32=0x%X size=%llu [namesum=0x%X]\n", sum.name, sum.checksum64, sum.checksum32, sum.extrsize, pkg_cstringhash32(sum.name)); return 0; } int pkg_job_decompress(pkg_job_t* job, char* name) { pkg_sum_t sum; int namelen = (int) strlen(name); if (namelen < 4 || name[namelen-4] != '.' || name[namelen-3] != 'c' || name[namelen-2] != 'v' || name[namelen-1] != '1') { fprintf(stderr, "BAD FILE: expected a file with extension .cv1 but got '%s'!\n", name); } char* outname = strdup(name); outname[namelen-4] = 0; // Remove .cv1 extension if (pkg_sum_init(&sum, outname, 0) != 0) { fprintf(stderr, "INTERNAL ERROR: pkg_sum_init failed for '%s'!\n", name); free(outname); return -1; } FILE* inf = fopen(name, "rb"); if (inf == NULL) { fprintf(stderr, "BAD FILE: Failed to open file '%s' for reading!\n", name); free(outname); return -1; } FILE* outf = fopen(outname, "w"); if (outf == NULL) { fprintf(stderr, "WRITE ERROR: Failed to open file '%s' for writing!\n", outname); free(outname); fclose(inf); return -1; } int result = pkg_decompress_f2f(inf, outf, -1, &sum); fclose(inf); fclose(outf); if (result < 0) { fprintf(stderr, "DECOMPRESSION FAILED: pkg_decompress_f2f returned non-zero result!\n"); return -1; } printf("%s checksum64=0x%llX checksum32=0x%X size=%llu [namesum=0x%X]\n", sum.name, sum.checksum64, sum.checksum32, sum.extrsize, pkg_cstringhash32(sum.name)); return 0; } int pkg_job_subrun(pkg_job_t* job, char* name) { switch (job->jobtype) { case PKG_JOBTYPE_BUILD: return pkg_job_build(job, name); case PKG_JOBTYPE_INSTALL: return pkg_job_install(job, name); case PKG_JOBTYPE_REMOVE: return pkg_job_remove(job, name); case PKG_JOBTYPE_SUM: return pkg_job_sum(job, name); case PKG_JOBTYPE_COMPRESS: return pkg_job_compress(job, name); case PKG_JOBTYPE_DECOMPRESS: return pkg_job_decompress(job, name); default: return -1; } } const char* pkg_job_typename(pkg_job_t* job) { switch (job->jobtype) { case PKG_JOBTYPE_BUILD: return "build"; case PKG_JOBTYPE_INSTALL: return "install"; case PKG_JOBTYPE_REMOVE: return "remove"; case PKG_JOBTYPE_SUM: return "sum"; case PKG_JOBTYPE_COMPRESS: return "compress"; case PKG_JOBTYPE_DECOMPRESS: return "decompress"; default: return -1; } } int pkg_job_run(pkg_job_t* job) { int i; printf("Running job type '%s' with %d entries\n", pkg_job_typename(job), job->nnames); for (i = 0; i < job->nnames; i++) { int subresult = pkg_job_subrun(job, job->names[i]); if (subresult != 0) { fprintf(stderr, "JOB FAILED!\n"); return subresult; } } fprintf(stderr, "JOB DONE.\n"); return 0; } void formatinfo(FILE* out) { fprintf(out, ".pkb (package build file) format:\n"); fprintf(out, " The build command runs on package build files (.pkb), creating associated .pkg package files.\n"); fprintf(out, " A `.pkb` file must exist in the root of a package source directory or a `pkgs` subdirectory.\n"); fprintf(out, " One `.pkb` file exists per package built from a source directory.\n"); fprintf(out, " This file lists packageable files, normally placed in a 'dist' subdirectory.\n"); fprintf(out, " A single `.pkb` file contains one such line for every installed file.\n"); fprintf(out, " Some lines are versioning or entries for directories, plain files can just be listed.\n\n"); fprintf(out, ".pkg (package file) format:\n"); fprintf(out, " Package files use a custom format (not zip or tar) making management seamless.\n"); fprintf(out, " Full headers & checksums are placed at the start of the package file.\n"); fprintf(out, " Data for each installable file follows and can be either raw or compressed (unfinished/testing).\n\n"); fprintf(out, ".cv1 (compression version 1) format:\n"); fprintf(out, " This is a simple stream-of-blocks style compression format with per-block integrity checking.\n"); fprintf(out, " These files are used to compress individual files, but the format itself may be used elsewhere.\n\n"); fprintf(out, ".pkh (package header) format:\n"); fprintf(out, " This is the same as the `.pkg` format except only the headers are used to track installed files.\n"); fprintf(out, " These files are kept automatically by the package manager for it's internal database.\n\n"); fprintf(out, "pkglist.txt format:\n"); fprintf(out, " This is just a list of possibly-valid packages which might be installed on the system.\n"); fprintf(out, " A package is considered installed if it's listed here AND has a valid .pkh file.\n"); fprintf(out, " These files are kept automatically by the package manager for it's internal database.\n\n"); } int usage(int argc, char** argv, int argi, char* error) { FILE* out = error ? stderr : stdout; char* prgname = argv[0]; fprintf(out, "USAGE:\n\n"); fprintf(out, " %s build [...]\n ^ to build .pkg files from .pkb files in a source/build directory\n\n", prgname); fprintf(out, " %s [options] install [...]\n ^ to install one or more .pkg files\n\n", prgname); fprintf(out, " %s [options] remove [...]\n ^ to un-install one or more packages\n\n", prgname); fprintf(out, " %s sum [...]\n ^ to print checksum and size info of regular files\n\n", prgname); fprintf(out, " %s compress [...]\n ^ writes compressed version of file with .cv1 extension added\n\n", prgname); fprintf(out, " %s decompress [...]\n ^ writes decompressed version of file with .cv1 extension removed\n\n", prgname); fprintf(out, " %s help\n ^ displays this usage information\n\n", prgname); fprintf(out, "OPTIONS:\n\n"); fprintf(out, " --root \n ^ installs/manages packages inside a directory (uses it as a root directory)\n\n"); fprintf(out, " --dbroot \n ^ keeps database of installed packages in this directory, defaults to /.pkgs/\n\n"); fprintf(out, " --nodb\n ^ doesn't use a database of installed packages\n\n"); formatinfo(out); fprintf(out, "DEMO INSTRUCTIONS:\n\n"); fprintf(out, " 1. compile slabpkg.c\n 2. place the executable in a subdirectory named `dist`\n 3. create a file `slabpkg.pkb` with a line `/slabpkg` (executable path within `dist`)\n 4. run `./dist/slabpkg build slabpkg.pkb`\n 5. work out how to install it (try --root ... install)\n\n"); if (error) { fprintf(out, "ERROR:\n %s\n", error); return -1; } else { return 0; } } int main(int argc, char** argv) { pkg_job_t job; int argi = 1; printf("SecureLang Package Manager 0.0.2\nThis currently doesn't include proper versioning or dependencies.\n"); if (argc < 2) { return usage(argc, argv, argi, "Expected command (build, install, ...)"); } job.installroot = NULL; job.dbroot = NULL; job.usedb = 1; job.verbose = 0; job.target = "/"; int foundoption; do { foundoption = 0; if (!strcmp(argv[argi], "--root")) { argi++; if (argi >= argc) { return usage(argc, argv, argi, "Expected a directory name following the --root option."); } job.installroot = strdup(argv[argi]); argi++; } else if (!strcmp(argv[argi], "--dbroot")) { argi++; if (argi >= argc) { return usage(argc, argv, argi, "Expected a directory name following the --dbroot option."); } job.installroot = strdup(argv[argi]); argi++; } else if (!strcmp(argv[argi], "--nodb")) { argi++; job.usedb = 0; } } while (foundoption); if (job.installroot == NULL) { job.installroot = strdup("/"); } if (job.usedb && job.dbroot == NULL) { job.dbroot = pkg_ezstrcat(job.installroot, ".pkgs/"); } while(job.installroot[strlen(job.installroot) - 1] == '/' || job.installroot[strlen(job.installroot) - 1] == '\\') { job.installroot[strlen(job.installroot) - 1] = 0; } if (!strcmp(argv[argi], "build")) { argi++; if (argi >= argc) { return usage(argc, argv, argi, "Expected one or more package build files (.pkb) for build command.\n"); } job.names = argv + argi; job.nnames = argc - argi; job.jobtype = PKG_JOBTYPE_BUILD; return pkg_job_run(&job); } else if (!strcmp(argv[argi], "install")) { argi++; if (argi >= argc) { return usage(argc, argv, argi, "Expected one or more package files (.pkg) for install command.\n"); } job.names = argv + argi; job.nnames = argc - argi; job.jobtype = PKG_JOBTYPE_INSTALL; return pkg_job_run(&job); } else if (!strcmp(argv[argi], "remove")) { argi++; if (argi >= argc) { return usage(argc, argv, argi, "Expected one or more package names for remove command.\n"); } job.names = argv + argi; job.nnames = argc - argi; job.jobtype = PKG_JOBTYPE_REMOVE; return pkg_job_run(&job); } else if (!strcmp(argv[argi], "sum")) { argi++; if (argi >= argc) { return usage(argc, argv, argi, "Expected one or more files for sum command.\n"); } job.names = argv + argi; job.nnames = argc - argi; job.jobtype = PKG_JOBTYPE_SUM; return pkg_job_run(&job); } else if (!strcmp(argv[argi], "compress")) { argi++; if (argi >= argc) { return usage(argc, argv, argi, "Expected one or more files for compress command.\n"); } job.names = argv + argi; job.nnames = argc - argi; job.jobtype = PKG_JOBTYPE_COMPRESS; return pkg_job_run(&job); } else if (!strcmp(argv[argi], "decompress")) { argi++; if (argi >= argc) { return usage(argc, argv, argi, "Expected one or more compressed files (.cv1) for decompress command.\n"); } job.names = argv + argi; job.nnames = argc - argi; job.jobtype = PKG_JOBTYPE_DECOMPRESS; return pkg_job_run(&job); } else if (!strcmp(argv[argi], "help")) { return usage(argc, argv, argi, NULL); } else { return usage(argc, argv, argi, "Unrecognised command"); } }