/******************************************************************************* Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014. 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 . initrd.cpp Extracts the initrd into the initial memory filesystem. *******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "initrd.h" namespace Sortix { namespace InitRD { // TODO: The initrd is not being properly verified. // TODO: The initrd is not handled in an endian-neutral manner. struct initrd_context { uint8_t* initrd; size_t initrd_size; size_t amount_extracted; initrd_superblock_t* sb; Ref links; ioctx_t ioctx; int last_percent; }; void initrd_activity(const char* status, const char* format, ...) { Log::PrintF("\r"); Log::PrintF(" [%6.6s] ", status); va_list ap; va_start(ap, format); Log::PrintFV(format, ap); va_end(ap); Log::PrintF("..."); size_t column, row; while ( Log::GetCursor(&column, &row), column != Log::Width() ) Log::PrintF(" "); Log::PrintF("\e[0J"); } void initrd_activity_done() { Log::PrintF("\r\e[0J"); } void initrd_progress(struct initrd_context* ctx) { int percent = 100; if ( ctx->initrd_size && ctx->initrd_size != ctx->amount_extracted ) percent = ((uint64_t) ctx->amount_extracted * 100) / ctx->initrd_size; if ( percent == ctx->last_percent ) return; char status[6 + 1]; snprintf(status, sizeof(status), " %3i%% ", percent); initrd_activity(status, "Extracting ramdisk into initial filesystem"); ctx->last_percent = percent; } static mode_t initrd_mode_to_host_mode(uint32_t mode) { mode_t result = mode & 0777; if ( INITRD_S_ISVTX & mode ) result |= S_ISVTX; if ( INITRD_S_ISSOCK(mode) ) result |= S_IFSOCK; if ( INITRD_S_ISLNK(mode) ) result |= S_IFLNK; if ( INITRD_S_ISREG(mode) ) result |= S_IFREG; if ( INITRD_S_ISBLK(mode) ) result |= S_IFBLK; if ( INITRD_S_ISDIR(mode) ) result |= S_IFDIR; if ( INITRD_S_ISCHR(mode) ) result |= S_IFCHR; if ( INITRD_S_ISFIFO(mode) ) result |= S_IFIFO; return result; } static initrd_inode_t* initrd_get_inode(struct initrd_context* ctx, uint32_t inode) { if ( ctx->sb->inodecount <= inode ) return errno = EINVAL, (initrd_inode_t*) NULL; uint32_t pos = ctx->sb->inodeoffset + ctx->sb->inodesize * inode; return (initrd_inode_t*) (ctx->initrd + pos); } static uint8_t* initrd_inode_get_data(struct initrd_context* ctx, initrd_inode_t* inode, size_t* size) { return *size = inode->size, ctx->initrd + inode->dataoffset; } static uint32_t initrd_directory_open(struct initrd_context* ctx, initrd_inode_t* inode, const char* name) { if ( !INITRD_S_ISDIR(inode->mode) ) return errno = ENOTDIR, 0; uint32_t offset = 0; while ( offset < inode->size ) { uint32_t pos = inode->dataoffset + offset; initrd_dirent* dirent = (initrd_dirent*) (ctx->initrd + pos); if ( dirent->namelen && !strcmp(dirent->name, name) ) return dirent->inode; offset += dirent->reclen; } return errno = ENOENT, 0; } static const char* initrd_directory_get_filename(struct initrd_context* ctx, initrd_inode_t* inode, size_t index) { if ( !INITRD_S_ISDIR(inode->mode) ) return errno = ENOTDIR, (const char*) NULL; uint32_t offset = 0; while ( offset < inode->size ) { uint32_t pos = inode->dataoffset + offset; initrd_dirent* dirent = (initrd_dirent*) (ctx->initrd + pos); if ( index-- == 0 ) return dirent->name; offset += dirent->reclen; } return errno = EINVAL, (const char*) NULL; } static size_t initrd_directory_get_num_files(struct initrd_context* ctx, initrd_inode_t* inode) { if ( !INITRD_S_ISDIR(inode->mode) ) return errno = ENOTDIR, 0; uint32_t offset = 0; size_t numentries = 0; while ( offset < inode->size ) { uint32_t pos = inode->dataoffset + offset; const initrd_dirent* dirent = (const initrd_dirent*) (ctx->initrd + pos); numentries++; offset += dirent->reclen; } return numentries; } static bool ExtractNode(struct initrd_context* ctx, initrd_inode_t* inode, Ref node); static bool ExtractFile(struct initrd_context* ctx, initrd_inode_t* inode, Ref file) { size_t filesize; uint8_t* data = initrd_inode_get_data(ctx, inode, &filesize); if ( !data ) return false; if ( file->truncate(&ctx->ioctx, filesize) != 0 ) return false; size_t sofar = 0; while ( sofar < filesize ) { size_t left = filesize - sofar; size_t chunk = 1024 * 1024; size_t count = left < chunk ? left : chunk; ssize_t numbytes = file->write(&ctx->ioctx, data + sofar, count); if ( numbytes <= 0 ) return false; sofar += numbytes; ctx->amount_extracted += numbytes; initrd_progress(ctx); } return true; } static bool ExtractDir(struct initrd_context* ctx, initrd_inode_t* inode, Ref dir) { size_t numfiles = initrd_directory_get_num_files(ctx, inode); for ( size_t i = 0; i < numfiles; i++ ) { const char* name = initrd_directory_get_filename(ctx, inode, i); if ( !name ) return false; if ( IsDotOrDotDot(name) ) continue; uint32_t childino = initrd_directory_open(ctx, inode, name); if ( !childino ) return false; initrd_inode_t* child = (initrd_inode_t*) initrd_get_inode(ctx, childino); mode_t mode = initrd_mode_to_host_mode(child->mode); if ( INITRD_S_ISDIR(child->mode) ) { if ( dir->mkdir(&ctx->ioctx, name, mode) && errno != EEXIST ) return false; Ref desc = dir->open(&ctx->ioctx, name, O_SEARCH | O_DIRECTORY, 0); if ( !desc ) return false; if ( !ExtractNode(ctx, child, desc) ) return false; } if ( INITRD_S_ISREG(child->mode) ) { assert(child->nlink != 0); char link_path[sizeof(childino) * 3]; snprintf(link_path, sizeof(link_path), "%ju", (uintmax_t) childino); Ref existing(ctx->links->open(&ctx->ioctx, link_path, O_READ, 0)); if ( !existing || dir->link(&ctx->ioctx, name, existing) != 0 ) { Ref desc(dir->open(&ctx->ioctx, name, O_WRITE | O_CREATE, mode)); if ( !desc ) return false; if ( !ExtractNode(ctx, child, desc) ) return false; if ( 2 <= child->nlink ) ctx->links->link(&ctx->ioctx, link_path, desc); } if ( --child->nlink == 0 && INITRD_S_ISREG(child->mode) ) { size_t filesize; const uint8_t* data = initrd_inode_get_data(ctx, child, &filesize); uintptr_t from = (uintptr_t) data; uintptr_t size = filesize; uintptr_t from_aligned = Page::AlignUp(from); uintptr_t from_distance = from_aligned - from; if ( from_distance <= size ) { uintptr_t size_aligned = Page::AlignDown(size - from_distance); for ( size_t i = 0; i < size_aligned; i += Page::Size() ) Page::Put(Memory::Unmap(from_aligned + i), PAGE_USAGE_WASNT_ALLOCATED); Memory::Flush(); } } } if ( INITRD_S_ISLNK(child->mode) ) { size_t filesize; uint8_t* data = initrd_inode_get_data(ctx, child, &filesize); if ( !data ) return false; char* oldname = new char[filesize + 1]; memcpy(oldname, data, filesize); oldname[filesize] = '\0'; int ret = dir->symlink(&ctx->ioctx, oldname, name); delete[] oldname; if ( ret < 0 ) return false; Ref desc = dir->open(&ctx->ioctx, name, O_READ | O_SYMLINK_NOFOLLOW, 0); if ( desc ) ExtractNode(ctx, child, desc); ctx->amount_extracted += child->size; initrd_progress(ctx); } } ctx->amount_extracted += inode->size; initrd_progress(ctx); return true; } static bool ExtractNode(struct initrd_context* ctx, initrd_inode_t* inode, Ref node) { if ( node->chmod(&ctx->ioctx, initrd_mode_to_host_mode(inode->mode)) < 0 ) return false; if ( node->chown(&ctx->ioctx, inode->uid, inode->gid) < 0 ) return false; if ( INITRD_S_ISDIR(inode->mode) ) if ( !ExtractDir(ctx, inode, node) ) return false; if ( INITRD_S_ISREG(inode->mode) ) if ( !ExtractFile(ctx, inode, node) ) return false; struct timespec ctime = timespec_make((time_t) inode->ctime, 0); struct timespec mtime = timespec_make((time_t) inode->mtime, 0); if ( node->utimens(&ctx->ioctx, &mtime, &ctime, &mtime) < 0 ) return false; return true; } bool ExtractFromPhysicalInto(addr_t physaddr, size_t size, Ref desc) { initrd_activity(" ", "Verifying ramdisk checksum"); // Allocate the needed kernel virtual address space. addralloc_t initrd_addr_alloc; if ( !AllocateKernelAddress(&initrd_addr_alloc, size) ) PanicF("Can't allocate 0x%zx bytes of kernel address space for the " "init ramdisk", size ); // Map the physical frames onto our address space. addr_t mapat = initrd_addr_alloc.from; for ( size_t i = 0; i < size; i += Page::Size() ) if ( !Memory::Map(physaddr + i, mapat + i, PROT_KREAD | PROT_KWRITE) ) PanicF("Unable to map the init ramdisk into virtual memory"); Memory::Flush(); struct initrd_context ctx; memset(&ctx, 0, sizeof(ctx)); ctx.initrd = (uint8_t*) mapat; ctx.initrd_size = size; ctx.amount_extracted = 0; ctx.last_percent = -1; SetupKernelIOCtx(&ctx.ioctx); if ( size < sizeof(*ctx.sb) ) PanicF("initrd is too small"); ctx.sb = (initrd_superblock_t*) ctx.initrd; if ( !String::StartsWith(ctx.sb->magic, "sortix-initrd") ) PanicF("Invalid magic value in initrd. This means the ramdisk may have " "been corrupted by the bootloader, or that an incompatible file " "has been passed to the kernel."); if ( strcmp(ctx.sb->magic, "sortix-initrd-1") == 0 ) PanicF("Sortix initrd format version 1 is no longer supported."); if ( strcmp(ctx.sb->magic, "sortix-initrd-2") != 0 ) PanicF("The initrd has a format that isn't supported. Perhaps it is " "too new? Try downgrade or regenerate the initrd."); if ( size < ctx.sb->fssize ) PanicF("The initrd said it is %u bytes, but the kernel was only passed " "%zu bytes by the bootloader, which is not enough.", ctx.sb->fssize, size); uint32_t amount = ctx.sb->fssize - ctx.sb->sumsize; uint8_t* filesum = ctx.initrd + amount; if ( ctx.sb->sumalgorithm != INITRD_ALGO_CRC32 ) { Log::PrintF("Warning: InitRD checksum algorithm not supported\n"); } else { uint32_t crc32 = *((uint32_t*) filesum); uint32_t filecrc32 = CRC32::Hash(ctx.initrd, amount); if ( crc32 != filecrc32 ) { PanicF("InitRD had checksum %X, expected %X: this means the ramdisk " "may have been corrupted by the bootloader.", filecrc32, crc32); } } initrd_activity(" OK ", "Verifying ramdisk checksum"); initrd_progress(&ctx); if ( desc->mkdir(&ctx.ioctx, ".initrd-links", 0777) != 0 ) return false; if ( !(ctx.links = desc->open(&ctx.ioctx, ".initrd-links", O_READ | O_DIRECTORY, 0)) ) return false; if ( !ExtractNode(&ctx, initrd_get_inode(&ctx, ctx.sb->root), desc) ) return false; union { struct dirent dirent; uint8_t dirent_data[sizeof(struct dirent) + sizeof(uintmax_t) * 3]; }; while ( 0 < ctx.links->readdirents(&ctx.ioctx, &dirent, sizeof(dirent_data)) && ((const char*) dirent.d_name)[0] ) { if ( ((const char*) dirent.d_name)[0] == '.' ) continue; ctx.links->unlinkat(&ctx.ioctx, dirent.d_name, AT_REMOVEFILE); ctx.links->lseek(&ctx.ioctx, 0, SEEK_SET); } ctx.links.Reset(); desc->unlinkat(&ctx.ioctx, ".initrd-links", AT_REMOVEDIR); // Unmap the pages and return the physical frames for reallocation. for ( size_t i = 0; i < initrd_addr_alloc.size; i += Page::Size() ) { if ( !Memory::LookUp(mapat + i, NULL, NULL) ) continue; addr_t addr = Memory::Unmap(mapat + i); Page::Put(addr, PAGE_USAGE_WASNT_ALLOCATED); } Memory::Flush(); // Free the used virtual address space. FreeKernelAddress(&initrd_addr_alloc); ctx.amount_extracted = ctx.initrd_size; initrd_progress(&ctx); initrd_activity_done(); return true; } } // namespace InitRD } // namespace Sortix