/******************************************************************************* 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 "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; initrd_superblock_t* sb; Ref links; ioctx_t ioctx; }; 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 ) { ssize_t numbytes = file->write(&ctx->ioctx, data + sofar, filesize - sofar); if ( numbytes <= 0 ) return false; sofar += numbytes; } 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)); Memory::Flush(); } } } } 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) { // 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) ) 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; 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); } } 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 kernel_dirent dirent; uint8_t dirent_data[sizeof(struct kernel_dirent) + sizeof(uintmax_t) * 3]; }; while ( 0 < ctx.links->readdirents(&ctx.ioctx, &dirent, sizeof(dirent_data), 1) && ((const char*) dirent.d_name)[0] ) { if ( ((const char*) dirent.d_name)[0] == '.' ) continue; ctx.links->unlink(&ctx.ioctx, dirent.d_name); ctx.links->lseek(&ctx.ioctx, 0, SEEK_SET); } ctx.links.Reset(); desc->rmdir(&ctx.ioctx, ".initrd-links"); // 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); } Memory::Flush(); // Free the used virtual address space. FreeKernelAddress(&initrd_addr_alloc); return true; } } // namespace InitRD } // namespace Sortix