#include "::/Adam/Net/Url" #define PKG_EURL (-20001) #define PKG_EMOUNT (-20002) #define PKG_EMANIFEST (-20003) #define PKG_EVERSION (-20004) #define PKG_EOSVERSION (-20005) #define PKG_EUNSUITABLE (-20006) #define PKG_VERSION 11 static U8* PKG_BASE_URL = "http://update.shrine.systems/packages"; static U8* PKG_LOCAL_REPO = "::/Misc/Packages"; static U8* PKG_TMP_DIR = "::/Tmp/PkgTmp"; class CPkgInfo { U8* package_name; I32 pkgmin; I32 release; I32 osmin; I32 osmax; I64 size; U8* version; U8* installdir; U8* iso_c; U8* post_install_doc; }; // TODO: Is there a built-in for this? static U8* StripDir(U8* file_path) { U8* slash = StrLastOcc(file_path, "/"); if (slash) return slash + 1; else return file_path; } U0 PkgInfoInit(CPkgInfo* pinf) { pinf->package_name = 0; pinf->pkgmin = 0x7fffffff; pinf->release = 0; pinf->osmin = 0; pinf->osmax = 0x7fffffff; pinf->size = 0; pinf->version = 0; pinf->installdir = 0; pinf->iso_c = 0; pinf->post_install_doc = 0; } U0 PkgInfoFree(CPkgInfo* pinf) { Free(pinf->package_name); Free(pinf->version); Free(pinf->installdir); Free(pinf->iso_c); Free(pinf->post_install_doc); PkgInfoInit(pinf); } // Returns 0 or error code I64 PkgParseManifest(CPkgInfo* pinf, U8* manifest) { U8* key = manifest; while (*key) { //"?%s", key; U8* end = StrFirstOcc(key, "\n"); if (end) { *end = 0; end++; } else end = key + StrLen(key); U8* value = StrFirstOcc(key, "\t"); if (!value) return PKG_EMANIFEST; *value = 0; value++; //"%s=%s;\n", key, value; if (0) {} else if (!StrCmp(key, "name")) { Free(pinf->package_name); pinf->package_name = StrNew(value); } else if (!StrCmp(key, "pkgmin")) { pinf->pkgmin = Str2I64(value); } else if (!StrCmp(key, "release")) { pinf->release = Str2I64(value); } else if (!StrCmp(key, "osmin")) { pinf->osmin = Str2I64(value); } else if (!StrCmp(key, "osmax")) { pinf->osmax = Str2I64(value); } else if (!StrCmp(key, "size")) { pinf->size = Str2I64(value); } else if (!StrCmp(key, "version")) { Free(pinf->version); pinf->version = StrNew(value); } else if (!StrCmp(key, "installdir")) { Free(pinf->installdir); pinf->installdir = StrNew(value); } else if (!StrCmp(key, "iso.c")) { Free(pinf->iso_c); pinf->iso_c = StrNew(value); } else if (!StrCmp(key, "post-install-doc")) { Free(pinf->post_install_doc); pinf->post_install_doc = StrNew(value); } else { /* unrecognized keys are simply ignored */ } key = end; } return 0; } I64 PkgWriteManifest(CPkgInfo* pinf, U8* path) {// TODO: implement no_warn pinf; FileWrite(path, "", 0); return 0; } // Downloads a package info from the repository. // Returns 0 or error code I64 PkgFetchManifest(CPkgInfo* pinf, U8* package_name) {// Old packages didn't have to specify a name, so we'll keep this for now pinf->package_name = StrNew(package_name); U8* url = MStrPrint("%s/%s", PKG_BASE_URL, package_name); U8* manifest = 0; I64 size = 0; I64 error = UrlGet(url, &manifest, &size); if (error == 0) error = PkgParseManifest(pinf, manifest); Free(manifest); Free(url); return error; } // Get the URL of the package's ISO.C download. // Returns NULL if N/A, otherwise must be Free()d. U8* PkgAllocISOCUrl(CPkgInfo* pinf) { if (!pinf->iso_c) return NULL; // A bit hacky, but will probably always work if (StrFind("//", pinf->iso_c)) return StrNew(pinf->iso_c); else return MStrPrint("%s/%s", PKG_BASE_URL, pinf->iso_c); } // Check if the package metadata makes it viable for installation. // You still need to do PkgCheckCompatibility, dependency resolution, // and check for a suitable installable format. Bool PkgIsInstallable(CPkgInfo* pinf) { return pinf->package_name != NULL && pinf->version != NULL && pinf->installdir != NULL; } // Check if the package is compatible with this OS & Pkg version I64 PkgCheckCompatibility(CPkgInfo* pinf) { if (pinf->pkgmin > PKG_VERSION) { "This package requires a more recent version of Pkg\n"; "Please update.\n"; return PKG_EVERSION; } I64 osver = ToI64(sys_os_version * 100); if (osver < pinf->osmin) { "This package requires a more recent system version.\n"; "Please update. (need %d, have %d)\n" , pinf->osmin, osver; return PKG_EOSVERSION; } if (osver > pinf->osmax) { "This package is not compatible with your system version.\n"; "Last supported version is %d, you have %d.\n" , pinf->osmax, osver; return PKG_EOSVERSION; } return 0; } I64 PkgRegister(CPkgInfo* pinf) {// TODO: this is very preliminary if (pinf->package_name == NULL) return PKG_EUNSUITABLE; U8* path = MStrPrint("%s/%s", PKG_LOCAL_REPO, pinf->package_name); PkgWriteManifest(pinf, path); return 0; } // Install a package, using the provided ISO.C file. // This will also register the package as installed. I64 PkgInstallISOC(CPkgInfo* pinf, U8* iso_c) { if (pinf->package_name == NULL || pinf->installdir == NULL) return PKG_EUNSUITABLE; I64 error = 0; "Installing %s\n" , pinf->package_name; I64 letter = MountFile(iso_c); if (letter) { U8 src_path[8]; StrPrint(src_path, "%c:/", letter); // StrLen check is a temporary hack to not complain about MkDir("::/"); if (StrLen(pinf->installdir) > 3) DirMk(pinf->installdir); CopyTree(src_path, pinf->installdir); // Register package as installed error = PkgRegister(pinf); // Display post-install doc if (pinf->post_install_doc) { Ed(pinf->post_install_doc); } } else error = PKG_EMOUNT; Unmount(letter); ""; return error; } // Verify, download & install a single package // All dependencies must have been installed at this point. I64 PkgDownloadAndInstall(CPkgInfo* pinf) { I64 error = PkgCheckCompatibility(pinf); if (error) { return error; } U8* iso_c_url = PkgAllocISOCUrl(pinf); if (iso_c_url) { U8* iso_data = 0; I64 iso_size = 0; "Downloading %s...\n" , pinf->package_name; error = UrlGetWithProgress(iso_c_url, &iso_data, &iso_size); if (error == 0) { U8* tmppath = "::/Tmp/Package.ISO.C"; FileWrite(tmppath, iso_data, iso_size); error = PkgInstallISOC(pinf, tmppath); } Free(iso_data); Free(iso_c_url); } else { "No suitable download address. Package broken?\n"; error = PKG_EUNSUITABLE; } return error; } // Expected max length: 5 ("1023k") static U8* FormatSize(I64 size) { static U8 buf[16]; if (size > 0x40000000) StrPrint(buf, "%dG", (size + 0x3fffffff) / 0x40000000); else if (size > 0x100000) StrPrint(buf, "%dM", (size + 0xfffff) / 0x100000); else if (size > 0x400) StrPrint(buf, "%dk", (size + 0x3ff) / 0x400); else StrPrint(buf, "%d", size); return buf; } // Install a package using a local manifest file public I64 PkgInstallFromFile(U8* manifest_path) { DirMk(PKG_LOCAL_REPO); CPkgInfo pinf; PkgInfoInit(&pinf); // Parse manifest I64 manifest_size; U8* manifest_file = FileRead(manifest_path, &manifest_size); // This relies on FileRead returning a 0-terminated buffer. // As of v502, this happens for all file systems I64 error = PkgParseManifest(&pinf, manifest_file); if (error == 0) { error = PkgCheckCompatibility(&pinf); if (!error) { if (pinf.iso_c) { PkgInstallISOC(&pinf, pinf.iso_c); } else { "No suitable installable file. Package broken?\n"; error = PKG_EUNSUITABLE; } } else { "PkgCheckCompatibility error: %d\n", error; } } else { "PkgParseManifest error: %d\n", error; } PkgInfoFree(&pinf); return error; } // Install a package from the repository public I64 PkgInstall(U8* package_name) { SocketInit(); DirMk(PKG_LOCAL_REPO); CPkgInfo pinf; PkgInfoInit(&pinf); I64 error = PkgFetchManifest(&pinf, package_name); if (error == 0) { if (PkgIsInstallable(&pinf)) { " Package Ver \n" " Dir Size \n" "============================\n" "+ %-20s %-6s\n", package_name, pinf.version; " %-20s %-6s\n" , pinf.installdir, FormatSize(pinf.size); "\n" "Is this ok? (y/n) "; I64 ok = GetKey(NULL, TRUE); "\n"; // TODO: verify all packages before we start downloading if (ok == 'y') { error = PkgDownloadAndInstall(&pinf); if (error == 0) { "Installed 1 package(s)\n"; } else { "PkgDownloadAndInstall error: %d\n", error; } } } else { "PkgInstall: %s is not installable\n", package_name; error = PKG_EUNSUITABLE; } } else { "PkgFetchManifest error: %d\n", error; } PkgInfoFree(&pinf); return error; } // List packages available in the repository public I64 PkgList() { SocketInit(); U8* url = MStrPrint("%s/packages.list", PKG_BASE_URL); U8* list = 0; I64 size = 0; I64 error = UrlGet(url, &list, &size); if (error == 0) { "%s\n", list; /*U8* entry = list; while (*entry) { U8* end = StrFirstOcc(entry, "\n"); if (end) { *end = 0; end++; } else end = value + StrLen(value); "%s\n", entry; entry = end; }*/ } else { "UrlGet error: %d\n", error; } Free(list); Free(url); return error; } // Build a package from directory contents public I64 PkgMakeFromDir(U8* manifest_path, U8* src_dir) { CPkgInfo pinf; PkgInfoInit(&pinf); // Parse manifest I64 manifest_size; U8* manifest_file = FileRead(manifest_path, &manifest_size); // This relies on FileRead returning a 0-terminated buffer. // As of v502, this happens for all file systems I64 error = PkgParseManifest(&pinf, manifest_file); if (error == 0) { // Build RedSea ISO if (pinf.iso_c) { U8* iso_path = pinf.iso_c; // RedSeaISO doesn't return a proper error code RedSeaISO(iso_path, src_dir); // TODO: update & save manifest /*CDirEntry* de; if (FileFind(iso_path, &de)) { pinf.size = de.size; // Save updated manifest PkgWriteManifest(&pinf, manifest_path); Free(de->full_name); } else { "Something went wrong, can't stat %s.\n", iso_path; error = PKG_EMOUNT; }*/ } else { "No output file defined.\n"; error = PKG_EUNSUITABLE; } } else { "PkgParseManifest error: %d\n", error; } PkgInfoFree(&pinf); return error; } // Build a package using a single file I64 PkgMakeFromFile(U8* manifest_path, U8* file_path) { DelTree(PKG_TMP_DIR); DirMk(PKG_TMP_DIR); U8* tmppath = MStrPrint("%s/%s", PKG_TMP_DIR, StripDir(file_path)); Copy(file_path, tmppath); I64 error = PkgMakeFromDir(manifest_path, PKG_TMP_DIR); Free(tmppath); return error; }