Clean up initrd extraction code.

This commit is contained in:
Jonas 'Sortie' Termansen 2014-05-09 01:32:51 +02:00
parent a334ede4c4
commit ae73391a10
1 changed files with 148 additions and 220 deletions

View File

@ -18,7 +18,7 @@
Sortix. If not, see <http://www.gnu.org/licenses/>.
initrd.cpp
Provides low-level access to a Sortix init ramdisk.
Extracts the initrd into the initial memory filesystem.
*******************************************************************************/
@ -53,239 +53,137 @@
namespace Sortix {
namespace InitRD {
addralloc_t initrd_addr_alloc;
uint8_t* initrd = NULL;
size_t initrdsize;
const initrd_superblock_t* sb;
// TODO: The initrd is not being properly verified.
// TODO: The initrd is not handled in an endian-neutral manner.
__attribute__((unused))
static uint32_t HostModeToInitRD(mode_t mode)
struct initrd_context
{
uint32_t result = mode & 0777; // Lower 9 bits per POSIX and tradition.
if ( S_ISVTX & mode ) { result |= INITRD_S_ISVTX; }
if ( S_ISSOCK(mode) ) { result |= INITRD_S_IFSOCK; }
if ( S_ISLNK(mode) ) { result |= INITRD_S_IFLNK; }
if ( S_ISREG(mode) ) { result |= INITRD_S_IFREG; }
if ( S_ISBLK(mode) ) { result |= INITRD_S_IFBLK; }
if ( S_ISDIR(mode) ) { result |= INITRD_S_IFDIR; }
if ( S_ISCHR(mode) ) { result |= INITRD_S_IFCHR; }
if ( S_ISFIFO(mode) ) { result |= INITRD_S_IFIFO; }
uint8_t* initrd;
size_t initrd_size;
initrd_superblock_t* sb;
Ref<Descriptor> 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;
}
__attribute__((unused))
static mode_t InitRDModeToHost(uint32_t mode)
static initrd_inode_t* initrd_get_inode(struct initrd_context* ctx, uint32_t inode)
{
mode_t result = mode & 0777; // Lower 9 bits per POSIX and tradition.
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;
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);
}
uint32_t Root()
static uint8_t* initrd_inode_get_data(struct initrd_context* ctx, initrd_inode_t* inode, size_t* size)
{
return sb->root;
return *size = inode->size, ctx->initrd + inode->dataoffset;
}
static const initrd_inode_t* GetInode(uint32_t inode)
static uint32_t initrd_directory_open(struct initrd_context* ctx, initrd_inode_t* inode, const char* name)
{
if ( sb->inodecount <= inode ) { errno = EINVAL; return NULL; }
uint32_t pos = sb->inodeoffset + sb->inodesize * inode;
return (const initrd_inode_t*) (initrd + pos);
}
bool Stat(uint32_t ino, struct stat* st)
{
const initrd_inode_t* inode = GetInode(ino);
if ( !inode ) { return false; }
st->st_ino = ino;
st->st_mode = HostModeToInitRD(inode->mode);
st->st_nlink = inode->nlink;
st->st_uid = inode->uid;
st->st_gid = inode->gid;
st->st_size = inode->size;
st->st_atim = timespec_make(inode->mtime, 0);
st->st_ctim = timespec_make(inode->ctime, 0);
st->st_mtim = timespec_make(inode->mtime, 0);
st->st_blksize = 1;
st->st_blocks = inode->size;
return true;
}
uint8_t* Open(uint32_t ino, size_t* size)
{
const initrd_inode_t* inode = GetInode(ino);
if ( !inode ) { return NULL; }
*size = inode->size;
return initrd + inode->dataoffset;
}
uint32_t Traverse(uint32_t ino, const char* name)
{
const initrd_inode_t* inode = GetInode(ino);
if ( !inode ) { return 0; }
if ( !INITRD_S_ISDIR(inode->mode) ) { errno = ENOTDIR; return 0; }
if ( !INITRD_S_ISDIR(inode->mode) )
return errno = ENOTDIR, 0;
uint32_t offset = 0;
while ( offset < inode->size )
{
uint32_t pos = inode->dataoffset + offset;
const initrd_dirent* dirent = (const initrd_dirent*) (initrd + pos);
initrd_dirent* dirent = (initrd_dirent*) (ctx->initrd + pos);
if ( dirent->namelen && !strcmp(dirent->name, name) )
{
return dirent->inode;
}
offset += dirent->reclen;
}
errno = ENOENT;
return 0;
return errno = ENOENT, 0;
}
const char* GetFilename(uint32_t dir, size_t index)
static const char* initrd_directory_get_filename(struct initrd_context* ctx, initrd_inode_t* inode, size_t index)
{
const initrd_inode_t* inode = GetInode(dir);
if ( !inode ) { return 0; }
if ( !INITRD_S_ISDIR(inode->mode) ) { errno = ENOTDIR; return 0; }
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;
const initrd_dirent* dirent = (const initrd_dirent*) (initrd + pos);
if ( index-- == 0 ) { return dirent->name; }
initrd_dirent* dirent = (initrd_dirent*) (ctx->initrd + pos);
if ( index-- == 0 )
return dirent->name;
offset += dirent->reclen;
}
errno = EINVAL;
return NULL;
return errno = EINVAL, (const char*) NULL;
}
size_t GetNumFiles(uint32_t dir)
static size_t initrd_directory_get_num_files(struct initrd_context* ctx, initrd_inode_t* inode)
{
const initrd_inode_t* inode = GetInode(dir);
if ( !inode ) { return 0; }
if ( !INITRD_S_ISDIR(inode->mode) ) { errno = ENOTDIR; return 0; }
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*) (initrd + pos);
const initrd_dirent* dirent = (const initrd_dirent*) (ctx->initrd + pos);
numentries++;
offset += dirent->reclen;
}
return numentries;
}
void CheckSum()
static bool ExtractNode(struct initrd_context* ctx, initrd_inode_t* inode, Ref<Descriptor> node);
static bool ExtractFile(struct initrd_context* ctx, initrd_inode_t* inode, Ref<Descriptor> file)
{
uint32_t amount = sb->fssize - sb->sumsize;
uint8_t* filesum = initrd + amount;
if ( sb->sumalgorithm != INITRD_ALGO_CRC32 )
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 )
{
Log::PrintF("Warning: InitRD checksum algorithm not supported\n");
return;
}
uint32_t crc32 = *((uint32_t*) filesum);
uint32_t filecrc32 = CRC32::Hash(initrd, amount);
uint32_t doublecheck = CRC32::Hash(initrd, amount);
if ( filecrc32 != doublecheck )
{
// Yes, this has happened. This seems like the goto place for such bugs
// to trigger, so I added a more accurate warning.
Panic("Calculating InitRD checksum two times gave different results: "
"this likely means the kernel have a corruption bug, possibly "
"caused by building libc and kernel with different settings or "
"a bug in the scheduler/interrupt handler or who knows. "
"It is also possible that the laws of logic has changed.");
}
if ( crc32 != filecrc32 )
{
PanicF("InitRD had checksum %X, expected %X: this means the ramdisk "
"may have been corrupted by the bootloader.", filecrc32, crc32);
ssize_t numbytes = file->write(&ctx->ioctx, data + sofar, filesize - sofar);
if ( numbytes <= 0 )
return false;
sofar += numbytes;
}
return true;
}
void Init(addr_t phys, size_t size)
static bool ExtractDir(struct initrd_context* ctx, initrd_inode_t* inode, Ref<Descriptor> dir)
{
assert(!initrd);
// Allocate the needed kernel virtual address space.
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(phys + i, mapat + i, PROT_KREAD) )
Panic("Unable to map the init ramdisk into virtual memory");
Memory::Flush();
initrd = (uint8_t*) mapat;
initrdsize = size;
if ( size < sizeof(*sb) ) { PanicF("initrd is too small"); }
sb = (const initrd_superblock_t*) initrd;
if ( !String::StartsWith(sb->magic, "sortix-initrd") )
{
Panic("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(sb->magic, "sortix-initrd-1") == 0 )
{
Panic("Sortix initrd format version 1 is no longer supported.");
}
if ( strcmp(sb->magic, "sortix-initrd-2") != 0 )
{
Panic("The initrd has a format that isn't supported. Perhaps it is "
"too new? Try downgrade or regenerate the initrd.");
}
if ( size < 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.", sb->fssize,
size);
}
CheckSum();
}
static bool ExtractDir(ioctx_t* ctx, uint32_t ino, Ref<Descriptor> node, Ref<Descriptor> links);
static bool ExtractFile(ioctx_t* ctx, uint32_t ino, Ref<Descriptor> file);
static bool ExtractNode(ioctx_t* ctx, uint32_t ino, Ref<Descriptor> node, Ref<Descriptor> links);
static bool ExtractDir(ioctx_t* ctx, uint32_t ino, Ref<Descriptor> dir, Ref<Descriptor> links)
{
size_t numfiles = GetNumFiles(ino);
size_t numfiles = initrd_directory_get_num_files(ctx, inode);
for ( size_t i = 0; i < numfiles; i++ )
{
const char* name = GetFilename(ino, i);
const char* name = initrd_directory_get_filename(ctx, inode, i);
if ( !name )
return false;
if ( IsDotOrDotDot(name) )
continue;
uint32_t childino = Traverse(ino, name);
uint32_t childino = initrd_directory_open(ctx, inode, name);
if ( !childino )
return false;
initrd_inode_t* child = (initrd_inode_t*) GetInode(childino);
mode_t mode = InitRDModeToHost(child->mode);
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, name, mode) && errno != EEXIST )
if ( dir->mkdir(&ctx->ioctx, name, mode) && errno != EEXIST )
return false;
Ref<Descriptor> desc = dir->open(ctx, name, O_SEARCH | O_DIRECTORY, 0);
Ref<Descriptor> desc = dir->open(&ctx->ioctx, name, O_SEARCH | O_DIRECTORY, 0);
if ( !desc )
return false;
if ( !ExtractNode(ctx, childino, desc, links) )
if ( !ExtractNode(ctx, child, desc) )
return false;
}
if ( INITRD_S_ISREG(child->mode) )
@ -293,21 +191,21 @@ static bool ExtractDir(ioctx_t* ctx, uint32_t ino, Ref<Descriptor> dir, Ref<Desc
assert(child->nlink != 0);
char link_path[sizeof(childino) * 3];
snprintf(link_path, sizeof(link_path), "%ju", (uintmax_t) childino);
Ref<Descriptor> existing(links->open(ctx, link_path, O_READ, 0));
if ( !existing || dir->link(ctx, name, existing) != 0 )
Ref<Descriptor> existing(ctx->links->open(&ctx->ioctx, link_path, O_READ, 0));
if ( !existing || dir->link(&ctx->ioctx, name, existing) != 0 )
{
Ref<Descriptor> desc(dir->open(ctx, name, O_WRITE | O_CREATE, mode));
Ref<Descriptor> desc(dir->open(&ctx->ioctx, name, O_WRITE | O_CREATE, mode));
if ( !desc )
return false;
if ( !ExtractNode(ctx, childino, desc, links) )
if ( !ExtractNode(ctx, child, desc) )
return false;
if ( 2 <= child->nlink )
links->link(ctx, link_path, desc);
ctx->links->link(&ctx->ioctx, link_path, desc);
}
if ( --child->nlink == 0 && INITRD_S_ISREG(child->mode) )
{
size_t filesize;
const uint8_t* data = Open(childino, &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);
@ -325,60 +223,91 @@ static bool ExtractDir(ioctx_t* ctx, uint32_t ino, Ref<Descriptor> dir, Ref<Desc
return true;
}
static bool ExtractFile(ioctx_t* ctx, uint32_t ino, Ref<Descriptor> file)
static bool ExtractNode(struct initrd_context* ctx, initrd_inode_t* inode, Ref<Descriptor> node)
{
size_t filesize;
const uint8_t* data = Open(ino, &filesize);
if ( !data )
if ( node->chmod(&ctx->ioctx, initrd_mode_to_host_mode(inode->mode)) < 0 )
return false;
if ( file->truncate(ctx, filesize) != 0 )
return false;
size_t sofar = 0;
while ( sofar < filesize )
{
ssize_t numbytes = file->write(ctx, data + sofar, filesize - sofar);
if ( numbytes <= 0 )
return false;
sofar += numbytes;
}
return true;
}
static bool ExtractNode(ioctx_t* ctx, uint32_t ino, Ref<Descriptor> node, Ref<Descriptor> links)
{
const initrd_inode_t* inode = GetInode(ino);
if ( !inode )
return false;
if ( node->chmod(ctx, InitRDModeToHost(inode->mode)) < 0 )
return false;
if ( node->chown(ctx, inode->uid, inode->gid) < 0 )
if ( node->chown(&ctx->ioctx, inode->uid, inode->gid) < 0 )
return false;
if ( INITRD_S_ISDIR(inode->mode) )
if ( !ExtractDir(ctx, ino, node, links) )
if ( !ExtractDir(ctx, inode, node) )
return false;
if ( INITRD_S_ISREG(inode->mode) )
if ( !ExtractFile(ctx, ino, node) )
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, &mtime, &ctime, &mtime) < 0 )
if ( node->utimens(&ctx->ioctx, &mtime, &ctime, &mtime) < 0 )
return false;
return true;
}
bool ExtractFromPhysicalInto(addr_t physaddr, size_t size, Ref<Descriptor> desc)
{
Init(physaddr, size);
// 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 );
ioctx_t ctx; SetupKernelIOCtx(&ctx);
if ( desc->mkdir(&ctx, ".initrd-links", 0777) != 0 )
// 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;
Ref<Descriptor> links(desc->open(&ctx, ".initrd-links", O_READ | O_DIRECTORY, 0));
if ( !links )
if ( !(ctx.links = desc->open(&ctx.ioctx, ".initrd-links", O_READ | O_DIRECTORY, 0)) )
return false;
if ( !ExtractNode(&ctx, sb->root, desc, links) )
if ( !ExtractNode(&ctx, initrd_get_inode(&ctx, ctx.sb->root), desc) )
return false;
union
@ -387,20 +316,21 @@ bool ExtractFromPhysicalInto(addr_t physaddr, size_t size, Ref<Descriptor> desc)
uint8_t dirent_data[sizeof(struct kernel_dirent) + sizeof(uintmax_t) * 3];
};
while ( 0 < links->readdirents(&ctx, &dirent, sizeof(dirent_data), 1) &&
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;
links->unlink(&ctx, dirent.d_name);
links->lseek(&ctx, 0, SEEK_SET);
ctx.links->unlink(&ctx.ioctx, dirent.d_name);
ctx.links->lseek(&ctx.ioctx, 0, SEEK_SET);
}
desc->rmdir(&ctx, ".initrd-links");
ctx.links.Reset();
desc->rmdir(&ctx.ioctx, ".initrd-links");
// Unmap the pages and return the physical frames for reallocation.
addr_t mapat = initrd_addr_alloc.from;
for ( size_t i = 0; i < initrdsize; i += Page::Size() )
for ( size_t i = 0; i < initrd_addr_alloc.size; i += Page::Size() )
{
if ( !Memory::LookUp(mapat + i, NULL, NULL) )
continue;
@ -409,8 +339,6 @@ bool ExtractFromPhysicalInto(addr_t physaddr, size_t size, Ref<Descriptor> desc)
}
Memory::Flush();
initrdsize = 0;
// Free the used virtual address space.
FreeKernelAddress(&initrd_addr_alloc);