diff --git a/src/system/boot/loader/Jamfile b/src/system/boot/loader/Jamfile index 95cedb7d19..0c5c833a99 100644 --- a/src/system/boot/loader/Jamfile +++ b/src/system/boot/loader/Jamfile @@ -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) ] ; diff --git a/src/system/boot/loader/file_systems/Jamfile b/src/system/boot/loader/file_systems/Jamfile index dbf010063d..9a2b705d42 100644 --- a/src/system/boot/loader/file_systems/Jamfile +++ b/src/system/boot/loader/file_systems/Jamfile @@ -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 ; diff --git a/src/system/boot/loader/file_systems/tarfs/Jamfile b/src/system/boot/loader/file_systems/tarfs/Jamfile new file mode 100644 index 0000000000..17f195c80d --- /dev/null +++ b/src/system/boot/loader/file_systems/tarfs/Jamfile @@ -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 + ; diff --git a/src/system/boot/loader/file_systems/tarfs/tarfs.cpp b/src/system/boot/loader/file_systems/tarfs/tarfs.cpp new file mode 100644 index 0000000000..50417ca885 --- /dev/null +++ b/src/system/boot/loader/file_systems/tarfs/tarfs.cpp @@ -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 +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + + +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 { + RegionDeleter() : BPrivate::AutoDeleter() {} + RegionDeleter(void *memory) : BPrivate::AutoDeleter(memory) {} +}; + +class Directory; + +class Entry : public DoublyLinkedListLinkImpl { + 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 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 +}; + diff --git a/src/system/boot/loader/file_systems/tarfs/tarfs.h b/src/system/boot/loader/file_systems/tarfs/tarfs.h new file mode 100644 index 0000000000..457aa12958 --- /dev/null +++ b/src/system/boot/loader/file_systems/tarfs/tarfs.h @@ -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 diff --git a/src/system/boot/loader/partitions.cpp b/src/system/boot/loader/partitions.cpp index b74f68eaf6..a2d8b902fd 100644 --- a/src/system/boot/loader/partitions.cpp +++ b/src/system/boot/loader/partitions.cpp @@ -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 *);