sortix-mirror/kernel/initrd.cpp

394 lines
12 KiB
C++
Raw Normal View History

/*******************************************************************************
2015-11-04 13:06:17 +00:00
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 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 <http://www.gnu.org/licenses/>.
initrd.cpp
2015-11-04 13:06:17 +00:00
Extracts initrds into the initial memory filesystem.
*******************************************************************************/
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <timespec.h>
2014-04-29 21:52:52 +00:00
#include <sortix/dirent.h>
#include <sortix/fcntl.h>
#include <sortix/initrd.h>
#include <sortix/mman.h>
#include <sortix/stat.h>
#include <sortix/kernel/addralloc.h>
#include <sortix/kernel/descriptor.h>
2013-10-27 00:42:10 +00:00
#include <sortix/kernel/fsfunc.h>
#include <sortix/kernel/ioctx.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/memorymanagement.h>
#include <sortix/kernel/string.h>
2013-01-08 23:41:35 +00:00
#include <sortix/kernel/syscall.h>
2013-10-27 00:42:10 +00:00
#include <sortix/kernel/vnode.h>
2013-01-08 23:41:35 +00:00
#include "initrd.h"
2015-11-04 13:06:17 +00:00
#include "multiboot.h"
namespace Sortix {
2014-05-08 23:32:51 +00:00
// TODO: The initrd is not being properly verified.
// TODO: The initrd is not handled in an endian-neutral manner.
2014-05-08 23:32:51 +00:00
struct initrd_context
{
2014-05-08 23:32:51 +00:00
uint8_t* initrd;
size_t initrd_size;
2015-11-04 13:06:17 +00:00
addr_t initrd_unmap_start;
addr_t initrd_unmap_end;
struct initrd_superblock* sb;
2014-05-08 23:32:51 +00:00
Ref<Descriptor> links;
ioctx_t ioctx;
};
2015-11-04 13:06:17 +00:00
// TODO: GRUB is currently buggy and doesn't ensure that other things are placed
// at the end of a module, i.e. that the module doesn't own all the bugs
// that it spans. It's thus risky to actually recycle the last page if the
// module doesn't use all of it. Remove this compatibility when this has
// been fixed in GRUB and a few years have passed such that most GRUB
// systems have this fixed.
static void UnmapInitrdPage(struct initrd_context* ctx, addr_t vaddr)
{
2015-11-04 13:06:17 +00:00
if ( !Memory::LookUp(vaddr, NULL, NULL) )
return;
2015-11-04 13:06:17 +00:00
addr_t addr = Memory::Unmap(vaddr);
if ( !(ctx->initrd_unmap_start <= addr && addr < ctx->initrd_unmap_end) )
return;
Page::Put(addr, PAGE_USAGE_WASNT_ALLOCATED);
}
2014-05-08 23:32:51 +00:00
static mode_t initrd_mode_to_host_mode(uint32_t mode)
{
2014-05-08 23:32:51 +00:00
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;
}
2015-11-04 13:06:17 +00:00
static struct initrd_inode* initrd_get_inode(struct initrd_context* ctx,
uint32_t inode)
{
2014-05-08 23:32:51 +00:00
if ( ctx->sb->inodecount <= inode )
2015-11-04 13:06:17 +00:00
return errno = EINVAL, (struct initrd_inode*) NULL;
2014-05-08 23:32:51 +00:00
uint32_t pos = ctx->sb->inodeoffset + ctx->sb->inodesize * inode;
2015-11-04 13:06:17 +00:00
return (struct initrd_inode*) (ctx->initrd + pos);
}
2015-11-04 13:06:17 +00:00
static uint8_t* initrd_inode_get_data(struct initrd_context* ctx,
struct initrd_inode* inode,
size_t* size)
{
2014-05-08 23:32:51 +00:00
return *size = inode->size, ctx->initrd + inode->dataoffset;
}
2015-11-04 13:06:17 +00:00
static uint32_t initrd_directory_open(struct initrd_context* ctx,
struct initrd_inode* inode,
const char* name)
{
2014-05-08 23:32:51 +00:00
if ( !INITRD_S_ISDIR(inode->mode) )
return errno = ENOTDIR, 0;
uint32_t offset = 0;
while ( offset < inode->size )
{
uint32_t pos = inode->dataoffset + offset;
2015-11-04 13:06:17 +00:00
struct initrd_dirent* dirent =
(struct initrd_dirent*) (ctx->initrd + pos);
if ( dirent->namelen && !strcmp(dirent->name, name) )
return dirent->inode;
offset += dirent->reclen;
}
2014-05-08 23:32:51 +00:00
return errno = ENOENT, 0;
}
2015-11-04 13:06:17 +00:00
static const char* initrd_directory_get_filename(struct initrd_context* ctx,
struct initrd_inode* inode,
size_t index)
{
2014-05-08 23:32:51 +00:00
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;
2015-11-04 13:06:17 +00:00
struct initrd_dirent* dirent =
(struct initrd_dirent*) (ctx->initrd + pos);
2014-05-08 23:32:51 +00:00
if ( index-- == 0 )
return dirent->name;
offset += dirent->reclen;
}
2014-05-08 23:32:51 +00:00
return errno = EINVAL, (const char*) NULL;
}
2015-11-04 13:06:17 +00:00
static size_t initrd_directory_get_num_files(struct initrd_context* ctx,
struct initrd_inode* inode)
{
2014-05-08 23:32:51 +00:00
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;
2015-11-04 13:06:17 +00:00
const struct initrd_dirent* dirent =
(const struct initrd_dirent*) (ctx->initrd + pos);
numentries++;
offset += dirent->reclen;
}
return numentries;
}
2011-12-05 18:36:15 +00:00
2015-11-04 13:06:17 +00:00
static void ExtractNode(struct initrd_context* ctx,
struct initrd_inode* inode,
Ref<Descriptor> node);
2015-11-04 13:06:17 +00:00
static void ExtractFile(struct initrd_context* ctx,
struct initrd_inode* inode,
Ref<Descriptor> file)
{
2014-05-08 23:32:51 +00:00
size_t filesize;
uint8_t* data = initrd_inode_get_data(ctx, inode, &filesize);
if ( file->truncate(&ctx->ioctx, filesize) != 0 )
2015-11-04 13:06:17 +00:00
PanicF("initrd: truncate: %m");
2014-05-08 23:32:51 +00:00
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);
2014-05-08 23:32:51 +00:00
if ( numbytes <= 0 )
2015-11-04 13:06:17 +00:00
PanicF("initrd: write: %m");
2014-05-08 23:32:51 +00:00
sofar += numbytes;
}
}
2015-11-04 13:06:17 +00:00
static void ExtractDir(struct initrd_context* ctx,
struct initrd_inode* inode,
Ref<Descriptor> dir)
{
2014-05-08 23:32:51 +00:00
size_t numfiles = initrd_directory_get_num_files(ctx, inode);
for ( size_t i = 0; i < numfiles; i++ )
{
2014-05-08 23:32:51 +00:00
const char* name = initrd_directory_get_filename(ctx, inode, i);
if ( !name )
2015-11-04 13:06:17 +00:00
PanicF("initrd_directory_get_filename: %m");
if ( IsDotOrDotDot(name) )
continue;
2014-05-08 23:32:51 +00:00
uint32_t childino = initrd_directory_open(ctx, inode, name);
if ( !childino )
2015-11-04 13:06:17 +00:00
PanicF("initrd_directory_open: %s: %m", name);
struct initrd_inode* child =
(struct initrd_inode*) initrd_get_inode(ctx, childino);
2014-05-08 23:32:51 +00:00
mode_t mode = initrd_mode_to_host_mode(child->mode);
if ( INITRD_S_ISDIR(child->mode) )
{
2014-05-08 23:32:51 +00:00
if ( dir->mkdir(&ctx->ioctx, name, mode) && errno != EEXIST )
2015-11-04 13:06:17 +00:00
PanicF("initrd: mkdir: %s: %m", name);
Ref<Descriptor> desc = dir->open(&ctx->ioctx, name,
O_SEARCH | O_DIRECTORY, 0);
if ( !desc )
2015-11-04 13:06:17 +00:00
PanicF("initrd: %s: %m", name);
ExtractNode(ctx, child, desc);
}
if ( INITRD_S_ISREG(child->mode) )
{
2014-04-29 21:52:52 +00:00
assert(child->nlink != 0);
char link_path[sizeof(childino) * 3];
snprintf(link_path, sizeof(link_path), "%ju", (uintmax_t) childino);
2015-11-04 13:06:17 +00:00
Ref<Descriptor> existing(ctx->links->open(&ctx->ioctx, link_path,
O_READ, 0));
2014-05-08 23:32:51 +00:00
if ( !existing || dir->link(&ctx->ioctx, name, existing) != 0 )
2014-04-29 21:52:52 +00:00
{
2015-11-04 13:06:17 +00:00
Ref<Descriptor> desc(dir->open(&ctx->ioctx, name,
O_WRITE | O_CREATE, mode));
2014-04-29 21:52:52 +00:00
if ( !desc )
2015-11-04 13:06:17 +00:00
PanicF("initrd: %s: %m", name);
ExtractNode(ctx, child, desc);
2014-04-29 21:52:52 +00:00
if ( 2 <= child->nlink )
2014-05-08 23:32:51 +00:00
ctx->links->link(&ctx->ioctx, link_path, desc);
2014-04-29 21:52:52 +00:00
}
if ( --child->nlink == 0 && INITRD_S_ISREG(child->mode) )
{
size_t filesize;
2015-11-04 13:06:17 +00:00
const uint8_t* data =
initrd_inode_get_data(ctx, child, &filesize);
2014-04-29 21:52:52 +00:00
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 )
{
2015-11-04 13:06:17 +00:00
uintptr_t size_aligned =
Page::AlignDown(size - from_distance);
2014-04-29 21:52:52 +00:00
for ( size_t i = 0; i < size_aligned; i += Page::Size() )
2015-11-04 13:06:17 +00:00
UnmapInitrdPage(ctx, from_aligned + i);
2014-04-29 21:52:52 +00:00
Memory::Flush();
}
}
}
if ( INITRD_S_ISLNK(child->mode) )
{
size_t filesize;
uint8_t* data = initrd_inode_get_data(ctx, child, &filesize);
2014-09-22 15:35:54 +00:00
char* oldname = new char[filesize + 1];
2015-11-04 13:06:17 +00:00
if ( !oldname )
PanicF("initrd: malloc: %m");
2014-09-22 15:35:54 +00:00
memcpy(oldname, data, filesize);
oldname[filesize] = '\0';
int ret = dir->symlink(&ctx->ioctx, oldname, name);
delete[] oldname;
if ( ret < 0 )
2015-11-04 13:06:17 +00:00
PanicF("initrd: symlink: %s", name);
Ref<Descriptor> desc = dir->open(&ctx->ioctx, name,
O_READ | O_SYMLINK_NOFOLLOW, 0);
2014-09-22 15:35:54 +00:00
if ( desc )
ExtractNode(ctx, child, desc);
}
}
}
2015-11-04 13:06:17 +00:00
static void ExtractNode(struct initrd_context* ctx,
struct initrd_inode* inode,
Ref<Descriptor> node)
{
2014-05-08 23:32:51 +00:00
if ( node->chmod(&ctx->ioctx, initrd_mode_to_host_mode(inode->mode)) < 0 )
2015-11-04 13:06:17 +00:00
PanicF("initrd: chmod: %m");
2014-05-08 23:32:51 +00:00
if ( node->chown(&ctx->ioctx, inode->uid, inode->gid) < 0 )
2015-11-04 13:06:17 +00:00
PanicF("initrd: chown: %m");
if ( INITRD_S_ISDIR(inode->mode) )
2015-11-04 13:06:17 +00:00
ExtractDir(ctx, inode, node);
if ( INITRD_S_ISREG(inode->mode) )
2015-11-04 13:06:17 +00:00
ExtractFile(ctx, inode, node);
struct timespec ctime = timespec_make((time_t) inode->ctime, 0);
struct timespec mtime = timespec_make((time_t) inode->mtime, 0);
2014-05-08 23:32:51 +00:00
if ( node->utimens(&ctx->ioctx, &mtime, &ctime, &mtime) < 0 )
2015-11-04 13:06:17 +00:00
PanicF("initrd: utimens: %m");
}
2015-11-04 13:06:17 +00:00
static void ExtractInitrd(Ref<Descriptor> desc, struct initrd_context* ctx)
{
2015-11-04 13:06:17 +00:00
ctx->sb = (struct initrd_superblock*) ctx->initrd;
2014-05-08 23:32:51 +00:00
2015-11-04 13:06:17 +00:00
if ( ctx->initrd_size < ctx->sb->fssize )
Panic("Initrd header does not match its size");
2014-05-08 23:32:51 +00:00
2015-11-04 13:06:17 +00:00
if ( desc->mkdir(&ctx->ioctx, ".initrd-links", 0777) != 0 )
PanicF("initrd: .initrd-links: %m");
2015-11-04 13:06:17 +00:00
if ( !(ctx->links = desc->open(&ctx->ioctx, ".initrd-links",
O_READ | O_DIRECTORY, 0)) )
PanicF("initrd: .initrd-links: %m");
2015-11-04 13:06:17 +00:00
ExtractNode(ctx, initrd_get_inode(ctx, ctx->sb->root), desc);
2014-04-29 21:52:52 +00:00
union
{
2015-11-20 01:57:09 +00:00
struct dirent dirent;
uint8_t dirent_data[sizeof(struct dirent) + sizeof(uintmax_t) * 3];
2014-04-29 21:52:52 +00:00
};
2015-11-04 13:06:17 +00:00
while ( 0 < ctx->links->readdirents(&ctx->ioctx, &dirent, sizeof(dirent_data)) &&
2014-04-29 21:52:52 +00:00
((const char*) dirent.d_name)[0] )
{
if ( ((const char*) dirent.d_name)[0] == '.' )
continue;
2015-11-04 13:06:17 +00:00
ctx->links->unlinkat(&ctx->ioctx, dirent.d_name, AT_REMOVEFILE);
ctx->links->lseek(&ctx->ioctx, 0, SEEK_SET);
2014-04-29 21:52:52 +00:00
}
2015-11-04 13:06:17 +00:00
ctx->links.Reset();
desc->unlinkat(&ctx->ioctx, ".initrd-links", AT_REMOVEDIR);
}
2014-05-08 23:32:51 +00:00
2015-11-04 13:06:17 +00:00
static void ExtractModule(struct multiboot_mod_list* module,
Ref<Descriptor> desc,
struct initrd_context* ctx)
{
size_t mod_size = module->mod_end - module->mod_start;
2013-03-12 16:40:33 +00:00
2015-11-04 13:06:17 +00:00
// Allocate the needed kernel virtual address space.
addralloc_t initrd_addr_alloc;
if ( !AllocateKernelAddress(&initrd_addr_alloc, mod_size) )
PanicF("Failed to allocate kernel address space for the initrd");
// Map the physical frames onto our address space.
addr_t physfrom = module->mod_start;
addr_t mapat = initrd_addr_alloc.from;
for ( size_t i = 0; i < mod_size; i += Page::Size() )
2013-03-12 16:40:33 +00:00
{
2015-11-04 13:06:17 +00:00
if ( !Memory::Map(physfrom + i, mapat + i, PROT_KREAD | PROT_KWRITE) )
PanicF("Unable to map the initrd into virtual memory");
}
Memory::Flush();
ctx->initrd = (uint8_t*) initrd_addr_alloc.from;
ctx->initrd_size = mod_size;
ctx->initrd_unmap_start = module->mod_start;
ctx->initrd_unmap_end = Page::AlignDown(module->mod_end);
if ( sizeof(struct initrd_superblock) <= ctx->initrd_size &&
!memcmp(ctx->initrd, "sortix-initrd-2", strlen("sortix-initrd-2")) )
{
ExtractInitrd(desc, ctx);
}
else
{
Panic("Unsupported initrd format");
2013-03-12 16:40:33 +00:00
}
2015-11-04 13:06:17 +00:00
// Unmap the pages and return the physical frames for reallocation.
for ( size_t i = 0; i < mod_size; i += Page::Size() )
UnmapInitrdPage(ctx, mapat + i);
2013-03-12 16:40:33 +00:00
Memory::Flush();
2015-11-04 13:06:17 +00:00
2013-03-12 16:40:33 +00:00
// Free the used virtual address space.
FreeKernelAddress(&initrd_addr_alloc);
2015-11-04 13:06:17 +00:00
}
2014-04-29 21:52:52 +00:00
2015-11-04 13:06:17 +00:00
void ExtractModules(struct multiboot_info* bootinfo, Ref<Descriptor> root)
{
struct multiboot_mod_list* modules =
(struct multiboot_mod_list*) (uintptr_t) bootinfo->mods_addr;
struct initrd_context ctx;
memset(&ctx, 0, sizeof(ctx));
SetupKernelIOCtx(&ctx.ioctx);
for ( uint32_t i = 0; i < bootinfo->mods_count; i++ )
ExtractModule(&modules[i], root, &ctx);
2013-03-12 16:40:33 +00:00
}
} // namespace Sortix