The boot loader now has tarfs support needed for booting from CD-ROM.

It expects the zipped TAR at offset 192 kB on the boot image.
This work was mostly done by Ingo during BeGeistert.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@14343 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler 2005-10-11 00:17:59 +00:00
parent b67b9d8c87
commit 85a53ea280
6 changed files with 686 additions and 1 deletions

View File

@ -18,6 +18,7 @@ UsePrivateHeaders [ FDirName storage ] ;
BOOT_SUPPORT_FILE_SYSTEM_BFS
BOOT_SUPPORT_FILE_SYSTEM_AMIGA_FFS
BOOT_SUPPORT_FILE_SYSTEM_TARFS
;
defines = [ FDefines $(defines) ] ;

View File

@ -1,5 +1,6 @@
SubDir OBOS_TOP src system boot loader file_systems ;
SubInclude OBOS_TOP src system boot loader file_systems amiga_ffs ;
SubInclude OBOS_TOP src system boot loader file_systems bfs ;
SubInclude OBOS_TOP src system boot loader file_systems hfs_plus ;
SubInclude OBOS_TOP src system boot loader file_systems amiga_ffs ;
SubInclude OBOS_TOP src system boot loader file_systems tarfs ;

View File

@ -0,0 +1,19 @@
SubDir OBOS_TOP src system boot loader file_systems tarfs ;
UseHeaders [ FDirName $(OBOS_TOP) headers libs zlib ] ;
UsePrivateHeaders [ FDirName kernel boot platform $(OBOS_BOOT_PLATFORM) ] ;
UsePrivateHeaders [ FDirName kernel disk_device_manager ] ;
UsePrivateHeaders shared storage ;
SubDirHdrs $(OBOS_TOP) headers libs zlib ;
SubDirC++Flags -fno-rtti ;
#SubDirCcFlags -DGUNZIP=1 ;
SEARCH_SOURCE += [ FDirName $(OBOS_TOP) src libs zlib ] ;
KernelStaticLibrary boot_tarfs :
tarfs.cpp
inflate.c
: -fno-pic
;

View File

@ -0,0 +1,616 @@
/*
* Copyright 2005, Ingo Weinhold, bonefish@cs.tu-berlin.de. All rights reserved.
* Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
*
* Distributed under the terms of the MIT License.
*/
#include "tarfs.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <AutoDeleter.h>
#include <OS.h>
#include <zlib.h>
#include <boot/partitions.h>
#include <boot/platform.h>
#include <util/kernel_cpp.h>
#include <util/DoublyLinkedList.h>
static const uint32 kCompressedArchiveOffset = 192 * 1024; // at 192 kB
static const size_t kTarRegionSize = 4 * 1024 * 1024; // 4 MB
namespace TarFS {
struct RegionDelete {
inline void operator()(void *memory)
{
platform_free_region(memory, kTarRegionSize);
}
};
struct RegionDeleter : BPrivate::AutoDeleter<void, RegionDelete> {
RegionDeleter() : BPrivate::AutoDeleter<void, RegionDelete>() {}
RegionDeleter(void *memory) : BPrivate::AutoDeleter<void, RegionDelete>(memory) {}
};
class Directory;
class Entry : public DoublyLinkedListLinkImpl<Entry> {
public:
Entry(const char *name) : fName(name) {
printf("created Entry %p: %s\n", this, fName);
}
virtual ~Entry() {}
const char *Name() const { return fName; }
virtual ::Node *ToNode() = 0;
virtual TarFS::Directory *ToTarDirectory() { return NULL; }
protected:
const char *fName;
};
typedef DoublyLinkedList<TarFS::Entry> EntryList;
typedef EntryList::Iterator EntryIterator;
class Node : public ::Node, public Entry {
public:
Node(tar_header *header, const char *name);
virtual ~Node();
virtual ssize_t ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize);
virtual ssize_t WriteAt(void *cookie, off_t pos, const void *buffer, size_t bufferSize);
virtual status_t GetName(char *nameBuffer, size_t bufferSize) const;
virtual ::Node *ToNode() { return this; }
private:
tar_header *fHeader;
off_t fSize;
};
class Directory : public ::Directory, public Entry {
public:
Directory(const char *name);
virtual ~Directory();
virtual status_t Open(void **_cookie, int mode);
virtual status_t Close(void *cookie);
virtual status_t GetName(char *nameBuffer, size_t bufferSize) const;
virtual TarFS::Entry *LookupEntry(const char *name);
virtual Node *Lookup(const char *name, bool traverseLinks);
virtual status_t GetNextEntry(void *cookie, char *nameBuffer, size_t bufferSize);
virtual status_t GetNextNode(void *cookie, Node **_node);
virtual status_t Rewind(void *cookie);
virtual bool IsEmpty();
virtual ::Node *ToNode() { return this; };
virtual TarFS::Directory *ToTarDirectory() { return this; }
status_t AddDirectory(char *dirName, TarFS::Directory **_dir = NULL);
status_t AddFile(tar_header *header);
private:
typedef ::Directory _inherited;
EntryList fEntries;
};
class Volume : public TarFS::Directory {
public:
Volume();
~Volume();
status_t Init(boot::Partition *partition);
TarFS::Directory *Root() { return this; }
};
} // namespace TarFS
//using namespace TarFS;
// #pragma mark -
bool
skip_gzip_header(z_stream *stream)
{
uint8 *buffer = (uint8 *)stream->next_in;
// check magic and skip method
if (buffer[0] != 0x1f || buffer[1] != 0x8b)
return false;
uint32 offset = 3;
// we need the flags field to determine the length of the header
int flags = buffer[offset++];
offset += 6;
if ((flags & 0x04) != 0) {
// skip extra field
offset += buffer[offset++] | (buffer[offset++] << 8);
if (offset >= stream->avail_in)
return false;
}
if ((flags & 0x08) != 0) {
// skip original name
while (buffer[offset++])
;
}
if ((flags & 0x10) != 0) {
// skip comment
while (buffer[offset++])
;
}
if ((flags & 0x02) != 0) {
// skip CRC
offset += 2;
}
if (offset >= stream->avail_in)
return false;
stream->next_in += offset;
stream->avail_in -= offset;
return true;
}
// #pragma mark -
TarFS::Node::Node(tar_header *header, const char *name)
:
TarFS::Entry(name),
fHeader(header)
{
fSize = strtol(header->size, NULL, 8);
}
TarFS::Node::~Node()
{
}
ssize_t
TarFS::Node::ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize)
{
if (pos < 0 || !buffer)
return B_BAD_VALUE;
if (pos >= fSize || bufferSize == 0)
return 0;
size_t toRead = fSize - pos;
if (toRead > bufferSize)
toRead = bufferSize;
memcpy(buffer, (char*)fHeader + 512 + pos, toRead);
return toRead;
}
ssize_t
TarFS::Node::WriteAt(void *cookie, off_t pos, const void *buffer, size_t bufferSize)
{
return B_NOT_ALLOWED;
}
status_t
TarFS::Node::GetName(char *nameBuffer, size_t bufferSize) const
{
return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize ? B_BUFFER_OVERFLOW : B_OK;
}
// #pragma mark -
TarFS::Directory::Directory(const char *name)
: TarFS::Entry(name)
{
}
TarFS::Directory::~Directory()
{
while (TarFS::Entry *entry = fEntries.Head()) {
fEntries.Remove(entry);
delete entry;
}
}
status_t
TarFS::Directory::Open(void **_cookie, int mode)
{
_inherited::Open(_cookie, mode);
EntryIterator *iterator = new EntryIterator(fEntries.GetIterator());
if (iterator == NULL)
return B_NO_MEMORY;
*_cookie = iterator;
return B_OK;
}
status_t
TarFS::Directory::Close(void *cookie)
{
_inherited::Close(cookie);
delete (EntryIterator *)cookie;
return B_OK;
}
status_t
TarFS::Directory::GetName(char *nameBuffer, size_t bufferSize) const
{
return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize ? B_BUFFER_OVERFLOW : B_OK;
}
TarFS::Entry *
TarFS::Directory::LookupEntry(const char *name)
{
EntryIterator iterator(fEntries.GetIterator());
while (iterator.HasNext()) {
TarFS::Entry *entry = iterator.Next();
if (strcmp(name, entry->Name()) == 0)
return entry;
}
return NULL;
}
::Node *
TarFS::Directory::Lookup(const char *name, bool /*traverseLinks*/)
{
if (TarFS::Entry *entry = LookupEntry(name)) {
entry->ToNode()->Acquire();
// our entries are not supposed to be deleted after use
return entry->ToNode();
}
return NULL;
}
status_t
TarFS::Directory::GetNextEntry(void *_cookie, char *name, size_t size)
{
EntryIterator *iterator = (EntryIterator *)_cookie;
TarFS::Entry *entry = iterator->Next();
if (entry != NULL) {
strlcpy(name, entry->Name(), size);
return B_OK;
}
return B_ENTRY_NOT_FOUND;
}
status_t
TarFS::Directory::GetNextNode(void *_cookie, Node **_node)
{
EntryIterator *iterator = (EntryIterator *)_cookie;
TarFS::Entry *entry = iterator->Next();
if (entry != NULL) {
*_node = entry->ToNode();
return B_OK;
}
return B_ENTRY_NOT_FOUND;
}
status_t
TarFS::Directory::Rewind(void *_cookie)
{
EntryIterator *iterator = (EntryIterator *)_cookie;
*iterator = fEntries.GetIterator();
return B_OK;
}
status_t
TarFS::Directory::AddDirectory(char *dirName, TarFS::Directory **_dir)
{
char *subDir = strchr(dirName, '/');
if (subDir) {
// skip slashes
while (*subDir == '/') {
*subDir = '\0';
subDir++;
}
if (*subDir == '\0') {
// a trailing slash
subDir = NULL;
}
}
// check, whether the directory does already exist
Entry *entry = LookupEntry(dirName);
TarFS::Directory *dir = (entry ? entry->ToTarDirectory() : NULL);
if (entry) {
if (!dir)
return B_ERROR;
} else {
// doesn't exist yet -- create it
dir = new TarFS::Directory(dirName);
if (!dir)
return B_NO_MEMORY;
fEntries.Add(dir);
}
// recursively create the subdirectories
if (subDir) {
status_t error = dir->AddDirectory(subDir, &dir);
if (error != B_OK)
return error;
}
if (_dir)
*_dir = dir;
return B_OK;
}
status_t
TarFS::Directory::AddFile(tar_header *header)
{
char *leaf = strrchr(header->name, '/');
char *dirName = NULL;
if (leaf) {
dirName = header->name;
*leaf = '\0';
leaf++;
} else
leaf = header->name;
// create the parent directory
TarFS::Directory *dir = this;
if (dirName) {
status_t error = AddDirectory(dirName, &dir);
if (error != B_OK)
return error;
}
// create the node
TarFS::Node *file = new TarFS::Node(header, leaf);
if (!file)
return B_NO_MEMORY;
dir->fEntries.Add(file);
return B_OK;
}
bool
TarFS::Directory::IsEmpty()
{
return fEntries.IsEmpty();
}
// #pragma mark -
TarFS::Volume::Volume()
: TarFS::Directory("CD/Floppy Boot Disk")
{
}
TarFS::Volume::~Volume()
{
}
status_t
TarFS::Volume::Init(boot::Partition *partition)
{
void *cookie;
status_t error = partition->Open(&cookie, O_RDONLY);
if (error != B_OK)
return error;
struct PartitionCloser {
boot::Partition *partition;
void *cookie;
PartitionCloser(boot::Partition *partition, void *cookie)
: partition(partition),
cookie(cookie)
{
}
~PartitionCloser()
{
partition->Close(cookie);
}
} _(partition, cookie);
RegionDeleter regionDeleter;
char *out = NULL;
char in[512];
z_stream zStream = {
(Bytef*)in, // next in
sizeof(in), // avail in
0, // total in
NULL, // next out
0, // avail out
0, // total out
0, // msg
0, // state
Z_NULL, // zalloc
Z_NULL, // zfree
Z_NULL, // opaque
0, // data type
0, // adler
0, // reserved
};
int status;
uint32 offset = kCompressedArchiveOffset;
do {
if (partition->ReadAt(cookie, offset, in, sizeof(in)) != sizeof(in)) {
status = Z_STREAM_ERROR;
break;
}
zStream.avail_in = sizeof(in);
zStream.next_in = (Bytef *)in;
if (offset == kCompressedArchiveOffset) {
// check and skip gzip header
if (!skip_gzip_header(&zStream))
return B_BAD_DATA;
if (platform_allocate_region((void **)&out, kTarRegionSize,
B_READ_AREA | B_WRITE_AREA) != B_OK)
return B_NO_MEMORY;
regionDeleter.SetTo(out);
zStream.avail_out = kTarRegionSize;
zStream.next_out = (Bytef *)out;
status = inflateInit2(&zStream, -15);
if (status != Z_OK)
return B_ERROR;
}
status = inflate(&zStream, Z_SYNC_FLUSH);
offset += sizeof(in);
if (zStream.avail_in != 0 && status != Z_STREAM_END)
printf("buhuuu");
} while (status == Z_OK);
inflateEnd(&zStream);
if (status != Z_STREAM_END)
return B_BAD_DATA;
status = B_OK;
// parse the tar file
char *block = out;
int blockCount = zStream.total_out / BLOCK_SIZE;
int blockIndex = 0;
while (blockIndex < blockCount) {
// check header
tar_header *header = (tar_header*)(block + blockIndex * BLOCK_SIZE);
//dump_header(*header);
if (header->magic[0] == '\0')
break;
if (strcmp(header->magic, kTarHeaderMagic) != 0) {
if (strcmp(header->magic, kOldTarHeaderMagic) != 0) {
fprintf(stderr, "Bad tar header magic in block %d.\n",
blockIndex);
status = B_BAD_DATA;
break;
}
}
off_t size = strtol(header->size, NULL, 8);
// TODO: this is old-style GNU tar which probably won't work with newer ones...
switch (header->type) {
case TAR_FILE:
case TAR_FILE2:
status = AddFile(header);
break;
case TAR_DIRECTORY:
status = AddDirectory(header->name, NULL);
break;
case TAR_LONG_NAME:
// this is a long file name
// TODO: read long name
default:
// unsupported type
status = B_ERROR;
break;
}
if (status != B_OK)
return status;
// next block
blockIndex += (size + 2 * BLOCK_SIZE - 1) / BLOCK_SIZE;
}
if (status != B_OK)
return status;
regionDeleter.Detach();
return B_OK;
}
// #pragma mark -
static status_t
tarfs_get_file_system(boot::Partition *partition, ::Directory **_root)
{
// TODO: Who owns the Volume object created here?
TarFS::Volume *volume = new TarFS::Volume;
if (volume == NULL)
return B_NO_MEMORY;
if (volume->Init(partition) < B_OK) {
delete volume;
return B_ERROR;
}
*_root = volume->Root();
return B_OK;
}
file_system_module_info gTarFileSystemModule = {
"file_systems/tarfs/v1",
kPartitionTypeTarFS,
tarfs_get_file_system
};

View File

@ -0,0 +1,45 @@
/*
* Copyright 2005, Ingo Weinhold, bonefish@cs.tu-berlin.de. All rights reserved.
* Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
*
* Distributed under the terms of the MIT License.
*/
#ifndef TAR_FS_H
#define TAR_FS_H
enum {
BLOCK_SIZE = 512,
};
struct tar_header {
char name[100];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char modification_time[12];
char check_sum[8];
char type;
char linkname[100];
char magic[6];
char version[2];
char user_name[32];
char group_name[32];
char device_major[8];
char device_minor[8];
char prefix[155];
};
static const char *kTarHeaderMagic = "ustar";
static const char *kOldTarHeaderMagic = "ustar ";
// the relevant entry types
enum {
TAR_FILE = '0',
TAR_FILE2 = '\0',
TAR_DIRECTORY = '5',
TAR_LONG_NAME = 'L',
};
#endif // TAR_FS_H

View File

@ -57,6 +57,9 @@ static file_system_module_info *sFileSystemModules[] = {
#ifdef BOOT_SUPPORT_FILE_SYSTEM_HFS_PLUS
&gHFSPlusFileSystemModule,
#endif
#ifdef BOOT_SUPPORT_FILE_SYSTEM_TARFS
&gTarFileSystemModule,
#endif
};
static const int32 sNumFileSystemModules = sizeof(sFileSystemModules) / sizeof(file_system_module_info *);