diff --git a/kernel/include/sortix/tar.h b/kernel/include/sortix/tar.h new file mode 100644 index 00000000..4b879cef --- /dev/null +++ b/kernel/include/sortix/tar.h @@ -0,0 +1,49 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2015. + + This file is part of Sortix. + + Sortix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Sortix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . + + sortix/tar.h + tar archieve format. + +*******************************************************************************/ + +#ifndef INCLUDE_SORTIX_TAR_H +#define INCLUDE_SORTIX_TAR_H + +struct tar +{ + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char chksum[8]; + char typeflag; + char linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char devmajor[8]; + char devminor[8]; + char prefix[155]; + char pad[12]; +}; + +#endif diff --git a/kernel/initrd.cpp b/kernel/initrd.cpp index b99b7b83..5901094b 100644 --- a/kernel/initrd.cpp +++ b/kernel/initrd.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include @@ -333,6 +335,338 @@ static void ExtractInitrd(Ref desc, struct initrd_context* ctx) desc->unlinkat(&ctx->ioctx, ".initrd-links", AT_REMOVEDIR); } +struct TAR +{ + unsigned char* tar_file; + size_t tar_file_size; + size_t next_offset; + size_t offset; + size_t data_offset; + char* name; + char* linkname; + unsigned char* data; + size_t size; + mode_t mode; + char typeflag; +}; + +static void OpenTar(TAR* TAR, unsigned char* tar_file, size_t tar_file_size) +{ + memset(TAR, 0, sizeof(*TAR)); + TAR->tar_file = tar_file; + TAR->tar_file_size = tar_file_size; +} + +static void CloseTar(TAR* TAR) +{ + free(TAR->name); + free(TAR->linkname); + memset(TAR, 0, sizeof(*TAR)); +} + +static bool ReadTar(TAR* TAR) +{ + free(TAR->name); + free(TAR->linkname); + TAR->name = NULL; + TAR->linkname = NULL; + while ( true ) + { + if ( TAR->tar_file_size - TAR->next_offset < sizeof(struct TAR) ) + return false; + TAR->offset = TAR->next_offset; + struct tar* tar = (struct tar*) (TAR->tar_file + TAR->offset); + if ( tar->size[sizeof(tar->size) - 1] != '\0' ) + return false; + size_t size = strtoul(tar->size, NULL, 8); + size_t dist = sizeof(struct tar) + -(-size & ~((size_t) 512 - 1)); + if ( TAR->tar_file_size - TAR->offset < dist ) + return false; + TAR->next_offset = TAR->offset + dist; + TAR->data_offset = TAR->offset + 512; + TAR->data = TAR->tar_file + TAR->data_offset; + TAR->size = size; + if ( tar->mode[sizeof(tar->mode) - 1] != '\0' ) + return false; + TAR->mode = strtoul(tar->mode, NULL, 8) & 07777; + TAR->typeflag = tar->typeflag; + // TODO: Things like modified time and other meta data! + if ( tar->typeflag == 'L' ) + { + free(TAR->name); + if ( !(TAR->name = (char*) malloc(size + 1)) ) + Panic("initrd tar malloc failure"); + memcpy(TAR->name, TAR->data, size); + TAR->name[size] = '\0'; + continue; + } + else if ( tar->typeflag == 'g' ) + { + // TODO: Implement pax extensions. + continue; + } + else if ( tar->typeflag == 'x' ) + { + // TODO: Implement pax extensions. + continue; + } + if ( !tar->name[0] ) + continue; + if ( !TAR->name ) + { + if ( tar->prefix[0] ) + { + size_t prefix_len = strnlen(tar->prefix, sizeof(tar->prefix)); + size_t name_len = strnlen(tar->name, sizeof(tar->name)); + size_t name_size = prefix_len + 1 + name_len + 1; + if ( !(TAR->name = (char*) malloc(name_size)) ) + Panic("initrd tar malloc failure"); + memcpy(TAR->name, tar->prefix, prefix_len); + TAR->name[prefix_len] = '/'; + memcpy(TAR->name + prefix_len + 1, tar->name, name_len); + TAR->name[prefix_len + 1 + name_len] = '\0'; + } + else + { + TAR->name = (char*) strndup(tar->name, sizeof(tar->name)); + if ( !TAR->name ) + Panic("initrd tar malloc failure"); + } + } + if ( !TAR->linkname ) + { + TAR->linkname = (char*) strndup(tar->linkname, sizeof(tar->linkname)); + if ( !TAR->linkname ) + Panic("initrd tar malloc failure"); + } + return true; + } +} + +static bool SearchTar(struct initrd_context* ctx, TAR* TAR, const char* path) +{ + OpenTar(TAR, ctx->initrd, ctx->initrd_size); + while ( ReadTar(TAR) ) + { + if ( !strcmp(TAR->name, path) ) + return true; + } + CloseTar(TAR); + return false; +} + +static void ExtractTarObject(Ref desc, + struct initrd_context* ctx, + TAR* TAR) +{ + if ( TAR->typeflag == '0' ) + { + int oflags = O_WRITE | O_CREATE | O_TRUNC; + Ref file(desc->open(&ctx->ioctx, TAR->name, oflags, TAR->mode)); + if ( !file ) + PanicF("%s: %m", TAR->name); + if ( file->truncate(&ctx->ioctx, TAR->size) != 0 ) + PanicF("truncate: %s: %m", TAR->name); + size_t sofar = 0; + while ( sofar < TAR->size ) + { + size_t left = TAR->size - sofar; + size_t chunk = 1024 * 1024; + size_t count = left < chunk ? left : chunk; + ssize_t numbytes = file->write(&ctx->ioctx, TAR->data + sofar, count); + if ( numbytes <= 0 ) + PanicF("write: %s: %m", TAR->name); + sofar += numbytes; + } + } + else if ( TAR->typeflag == '1' ) + { + Ref dest(desc->open(&ctx->ioctx, TAR->linkname, O_READ, 0)); + if ( !dest ) + PanicF("%s: %m", TAR->linkname); + if ( desc->link(&ctx->ioctx, TAR->name, dest) != 0 ) + PanicF("link: %s -> %s: %m", TAR->linkname, TAR->name); + } + else if ( TAR->typeflag == '2' ) + { + if ( desc->symlink(&ctx->ioctx, TAR->linkname, TAR->name) != 0 ) + PanicF("symlink: %s: %m", TAR->name); + } + else if ( TAR->typeflag == '5' ) + { + if ( desc->mkdir(&ctx->ioctx, TAR->name, TAR->mode) && errno != EEXIST ) + PanicF("mkdir: %s: %m", TAR->name); + } + else + { + Log::PrintF("kernel: initrd: %s: Unsupported tar filetype '%c'\n", + TAR->name, TAR->typeflag); + } +} + +static void ExtractTar(Ref desc, struct initrd_context* ctx) +{ + TAR TAR; + OpenTar(&TAR, ctx->initrd, ctx->initrd_size); + while ( ReadTar(&TAR) ) + ExtractTarObject(desc, ctx, &TAR); + CloseTar(&TAR); +} + +static bool TarIsTix(struct initrd_context* ctx) +{ + TAR TAR; + bool result = SearchTar(ctx, &TAR, "tix/tixinfo"); + CloseTar(&TAR); + return result; +} + +static char* tixinfo_lookup(const char* info, + size_t info_size, + const char* what) +{ + size_t what_length = strlen(what); + while ( info_size ) + { + size_t line_length = 0; + while ( line_length < info_size && info[line_length] != '\n' ) + line_length++; + if ( what_length <= line_length && + !strncmp(info, what, what_length) && + info[what_length] == '=' ) + { + char* result = strndup(info + what_length + 1, + line_length - (what_length + 1)); + if ( !result ) + Panic("initrd tar malloc failure"); + return result; + } + info += line_length; + info_size -= line_length; + if ( info_size && info[0] == '\n' ) + { + info++; + info_size--; + } + } + return NULL; +} + +static void DescriptorWriteLine(Ref desc, + ioctx_t* ioctx, + const char* str) +{ + size_t len = strlen(str); + while ( str[0] ) + { + ssize_t done = desc->write(ioctx, (unsigned char*) str, len); + if ( done <= 0 ) + PanicF("initrd tix metadata write: %m"); + str += done; + len -= done; + } + if ( desc->write(ioctx, (unsigned char*) "\n", 1) != 1 ) + PanicF("initrd tix metadata write: %m"); +} + +static int manifest_sort(const void* a_ptr, const void* b_ptr) +{ + const char* a = *(const char* const*) a_ptr; + const char* b = *(const char* const*) b_ptr; + return strcmp(a, b); +} + +static void ExtractTix(Ref desc, struct initrd_context* ctx) +{ + TAR TAR; + if ( !SearchTar(ctx, &TAR, "tix/tixinfo") ) + Panic("initrd was not tix"); + char* pkg_name = + tixinfo_lookup((const char*) TAR.data, TAR.size, "pkg.name"); + if ( !pkg_name ) + Panic("initrd tixinfo lacked pkg.name"); + if ( desc->mkdir(&ctx->ioctx, "/tix", 0755) < 0 && errno != EEXIST ) + PanicF("/tix: %m"); + if ( desc->mkdir(&ctx->ioctx, "/tix/tixinfo", 0755) < 0 && errno != EEXIST ) + PanicF("/tix/tixinfo: %m"); + if ( desc->mkdir(&ctx->ioctx, "/tix/manifest", 0755) < 0 && errno != EEXIST ) + PanicF("/tix/manifest: %m"); + char* tixinfo_path; + if ( asprintf(&tixinfo_path, "/tix/tixinfo/%s", pkg_name) < 0 ) + Panic("initrd tar malloc failure"); + char* TAR_oldname = TAR.name; + TAR.name = tixinfo_path; + ExtractTarObject(desc, ctx, &TAR); + TAR.name = TAR_oldname; + free(tixinfo_path); + CloseTar(&TAR); + Ref installed_list = + desc->open(&ctx->ioctx, "/tix/installed.list", + O_CREATE | O_WRITE | O_APPEND, 0644); + if ( !installed_list ) + PanicF("/tix/installed.list: %m"); + DescriptorWriteLine(installed_list, &ctx->ioctx, pkg_name); + installed_list.Reset(); + size_t manifest_list_size = 0; + OpenTar(&TAR, ctx->initrd, ctx->initrd_size); + while ( ReadTar(&TAR) ) + { + if ( !strncmp(TAR.name, "data", 4) && TAR.name[4] == '/' ) + manifest_list_size++; + } + CloseTar(&TAR); + char** manifest_list = new char*[manifest_list_size]; + if ( !manifest_list ) + Panic("initrd tar malloc failure"); + OpenTar(&TAR, ctx->initrd, ctx->initrd_size); + size_t manifest_list_count = 0; + while ( ReadTar(&TAR) ) + { + if ( strncmp(TAR.name, "data", 4) != 0 || TAR.name[4] != '/' ) + continue; + if ( !(manifest_list[manifest_list_count++] = strdup(TAR.name + 4)) ) + Panic("initrd tar malloc failure"); + } + CloseTar(&TAR); + qsort(manifest_list, manifest_list_count, sizeof(char*), manifest_sort); + char* manifest_path; + if ( asprintf(&manifest_path, "/tix/manifest/%s", pkg_name) < 0 ) + Panic("initrd tar malloc failure"); + Ref manifest = + desc->open(&ctx->ioctx, manifest_path, O_WRITE | O_CREATE | O_TRUNC, 0644); + if ( !manifest ) + PanicF("%s: %m", manifest_path); + free(manifest_path); + for ( size_t i = 0; i < manifest_list_count; i++ ) + DescriptorWriteLine(manifest, &ctx->ioctx, manifest_list[i]); + manifest.Reset(); + for ( size_t i = 0; i < manifest_list_count; i++ ) + free(manifest_list[i]); + delete[] manifest_list; + OpenTar(&TAR, ctx->initrd, ctx->initrd_size); + const char* subdir = "data/"; + size_t subdir_length = strlen(subdir); + while ( ReadTar(&TAR) ) + { + bool name_data = !strncmp(TAR.name, subdir, subdir_length) && + TAR.name[subdir_length]; + bool linkname_data = !strncmp(TAR.linkname, subdir, subdir_length) && + TAR.linkname[subdir_length]; + if ( name_data ) + { + TAR.name += subdir_length; + if ( linkname_data ) + TAR.linkname += subdir_length; + ExtractTarObject(desc, ctx, &TAR); + TAR.name -= subdir_length; + if ( linkname_data ) + TAR.linkname -= subdir_length; + } + } + CloseTar(&TAR); + free(pkg_name); +} + static void ExtractModule(struct multiboot_mod_list* module, Ref desc, struct initrd_context* ctx) @@ -364,6 +698,14 @@ static void ExtractModule(struct multiboot_mod_list* module, { ExtractInitrd(desc, ctx); } + else if ( sizeof(struct tar) <= ctx->initrd_size && + !memcmp(ctx->initrd + offsetof(struct tar, magic), "ustar", 5) ) + { + if ( TarIsTix(ctx) ) + ExtractTix(desc, ctx); + else + ExtractTar(desc, ctx); + } else { Panic("Unsupported initrd format");