2 * Copyright 2005-2007, Ingo Weinhold, bonefish@cs.tu-berlin.de.
3 * Copyright 2005-2013, Axel Dörfler, axeld@pinc-software.de.
5 * Distributed under the terms of the MIT License.
17 #include <AutoDeleter.h>
19 #include <SupportDefs.h>
23 #include <boot/partitions.h>
24 #include <boot/platform.h>
25 #include <util/DoublyLinkedList.h>
30 # define TRACE(x) dprintf x
36 static const uint32 kFloppyArchiveOffset
= BOOT_ARCHIVE_IMAGE_OFFSET
* 1024;
37 // defined at build time, see build/jam/BuildSetup
38 static const size_t kTarRegionSize
= 8 * 1024 * 1024; // 8 MB
48 inline void operator()(void* memory
)
51 platform_free_region(memory
, kTarRegionSize
);
55 struct RegionDeleter
: BPrivate::AutoDeleter
<void, RegionDelete
> {
58 BPrivate::AutoDeleter
<void, RegionDelete
>()
62 RegionDeleter(void* memory
)
64 BPrivate::AutoDeleter
<void, RegionDelete
>(memory
)
71 class Entry
: public DoublyLinkedListLinkImpl
<Entry
> {
73 Entry(const char* name
);
76 const char* Name() const { return fName
; }
77 virtual ::Node
* ToNode() = 0;
78 virtual TarFS::Directory
* ToTarDirectory() { return NULL
; }
86 typedef DoublyLinkedList
<TarFS::Entry
> EntryList
;
87 typedef EntryList::Iterator EntryIterator
;
90 class File
: public ::Node
, public Entry
{
92 File(tar_header
* header
, const char* name
);
95 virtual ssize_t
ReadAt(void* cookie
, off_t pos
, void* buffer
,
97 virtual ssize_t
WriteAt(void* cookie
, off_t pos
,
98 const void* buffer
, size_t bufferSize
);
100 virtual status_t
GetName(char* nameBuffer
,
101 size_t bufferSize
) const;
103 virtual int32
Type() const;
104 virtual off_t
Size() const;
105 virtual ino_t
Inode() const;
107 virtual ::Node
* ToNode() { return this; }
115 class Directory
: public ::Directory
, public Entry
{
117 Directory(Directory
* parent
, const char* name
);
118 virtual ~Directory();
120 virtual status_t
Open(void** _cookie
, int mode
);
121 virtual status_t
Close(void* cookie
);
123 virtual status_t
GetName(char* nameBuffer
,
124 size_t bufferSize
) const;
126 virtual TarFS::Entry
* LookupEntry(const char* name
);
127 virtual ::Node
* LookupDontTraverse(const char* name
);
129 virtual status_t
GetNextEntry(void* cookie
, char* nameBuffer
,
131 virtual status_t
GetNextNode(void* cookie
, Node
** _node
);
132 virtual status_t
Rewind(void* cookie
);
133 virtual bool IsEmpty();
135 virtual ino_t
Inode() const;
137 virtual ::Node
* ToNode() { return this; };
138 virtual TarFS::Directory
* ToTarDirectory() { return this; }
140 status_t
AddDirectory(char* dirName
,
141 TarFS::Directory
** _dir
= NULL
);
142 status_t
AddFile(tar_header
* header
);
145 typedef ::Directory _inherited
;
152 class Symlink
: public ::Node
, public Entry
{
154 Symlink(tar_header
* header
, const char* name
);
157 virtual ssize_t
ReadAt(void* cookie
, off_t pos
, void* buffer
,
159 virtual ssize_t
WriteAt(void* cookie
, off_t pos
,
160 const void* buffer
, size_t bufferSize
);
162 virtual status_t
ReadLink(char* buffer
, size_t bufferSize
);
164 virtual status_t
GetName(char* nameBuffer
,
165 size_t bufferSize
) const;
167 virtual int32
Type() const;
168 virtual off_t
Size() const;
169 virtual ino_t
Inode() const;
171 const char* LinkPath() const { return fHeader
->linkname
; }
173 virtual ::Node
* ToNode() { return this; }
181 class Volume
: public TarFS::Directory
{
186 status_t
Init(boot::Partition
* partition
);
188 TarFS::Directory
* Root() { return this; }
191 status_t
_Inflate(boot::Partition
* partition
,
192 void* cookie
, off_t offset
,
193 RegionDeleter
& regionDeleter
,
194 size_t* inflatedBytes
);
200 static int32 sNextID
= 1;
207 skip_gzip_header(z_stream
* stream
)
209 uint8
* buffer
= (uint8
*)stream
->next_in
;
211 // check magic and skip method
212 if (buffer
[0] != 0x1f || buffer
[1] != 0x8b)
215 // we need the flags field to determine the length of the header
216 int flags
= buffer
[3];
220 if ((flags
& 0x04) != 0) {
222 offset
+= (buffer
[offset
] | (buffer
[offset
+ 1] << 8)) + 2;
223 if (offset
>= stream
->avail_in
)
226 if ((flags
& 0x08) != 0) {
227 // skip original name
228 while (buffer
[offset
++])
231 if ((flags
& 0x10) != 0) {
233 while (buffer
[offset
++])
236 if ((flags
& 0x02) != 0) {
241 if (offset
>= stream
->avail_in
)
244 stream
->next_in
+= offset
;
245 stream
->avail_in
-= offset
;
253 TarFS::Entry::Entry(const char* name
)
264 TarFS::File::File(tar_header
* header
, const char* name
)
265 : TarFS::Entry(name
),
268 fSize
= strtol(header
->size
, NULL
, 8);
278 TarFS::File::ReadAt(void* cookie
, off_t pos
, void* buffer
, size_t bufferSize
)
280 TRACE(("tarfs: read at %" B_PRIdOFF
", %" B_PRIuSIZE
" bytes, fSize = %"
281 B_PRIdOFF
"\n", pos
, bufferSize
, fSize
));
283 if (pos
< 0 || !buffer
)
286 if (pos
>= fSize
|| bufferSize
== 0)
289 size_t toRead
= fSize
- pos
;
290 if (toRead
> bufferSize
)
293 memcpy(buffer
, (char*)fHeader
+ BLOCK_SIZE
+ pos
, toRead
);
300 TarFS::File::WriteAt(void* cookie
, off_t pos
, const void* buffer
,
303 return B_NOT_ALLOWED
;
308 TarFS::File::GetName(char* nameBuffer
, size_t bufferSize
) const
310 return strlcpy(nameBuffer
, Name(), bufferSize
) >= bufferSize
311 ? B_BUFFER_OVERFLOW
: B_OK
;
316 TarFS::File::Type() const
323 TarFS::File::Size() const
330 TarFS::File::Inode() const
338 TarFS::Directory::Directory(Directory
* parent
, const char* name
)
346 TarFS::Directory::~Directory()
348 while (TarFS::Entry
* entry
= fEntries
.Head()) {
349 fEntries
.Remove(entry
);
356 TarFS::Directory::Open(void** _cookie
, int mode
)
358 _inherited::Open(_cookie
, mode
);
360 EntryIterator
* iterator
361 = new(nothrow
) EntryIterator(fEntries
.GetIterator());
362 if (iterator
== NULL
)
372 TarFS::Directory::Close(void* cookie
)
374 _inherited::Close(cookie
);
376 delete (EntryIterator
*)cookie
;
382 TarFS::Directory::GetName(char* nameBuffer
, size_t bufferSize
) const
384 return strlcpy(nameBuffer
, Name(), bufferSize
) >= bufferSize
385 ? B_BUFFER_OVERFLOW
: B_OK
;
390 TarFS::Directory::LookupEntry(const char* name
)
392 if (strcmp(name
, ".") == 0)
394 if (strcmp(name
, "..") == 0)
397 EntryIterator
iterator(fEntries
.GetIterator());
399 while (iterator
.HasNext()) {
400 TarFS::Entry
* entry
= iterator
.Next();
401 if (strcmp(name
, entry
->Name()) == 0)
410 TarFS::Directory::LookupDontTraverse(const char* name
)
412 TarFS::Entry
* entry
= LookupEntry(name
);
416 Node
* node
= entry
->ToNode();
425 TarFS::Directory::GetNextEntry(void* _cookie
, char* name
, size_t size
)
427 EntryIterator
* iterator
= (EntryIterator
*)_cookie
;
428 TarFS::Entry
* entry
= iterator
->Next();
431 strlcpy(name
, entry
->Name(), size
);
435 return B_ENTRY_NOT_FOUND
;
440 TarFS::Directory::GetNextNode(void* _cookie
, Node
** _node
)
442 EntryIterator
* iterator
= (EntryIterator
*)_cookie
;
443 TarFS::Entry
* entry
= iterator
->Next();
446 *_node
= entry
->ToNode();
449 return B_ENTRY_NOT_FOUND
;
454 TarFS::Directory::Rewind(void* _cookie
)
456 EntryIterator
* iterator
= (EntryIterator
*)_cookie
;
457 *iterator
= fEntries
.GetIterator();
463 TarFS::Directory::AddDirectory(char* dirName
, TarFS::Directory
** _dir
)
465 char* subDir
= strchr(dirName
, '/');
468 while (*subDir
== '/') {
473 if (*subDir
== '\0') {
479 // check, whether the directory does already exist
480 Entry
* entry
= LookupEntry(dirName
);
481 TarFS::Directory
* dir
= (entry
? entry
->ToTarDirectory() : NULL
);
486 // doesn't exist yet -- create it
487 dir
= new(nothrow
) TarFS::Directory(this, dirName
);
494 // recursively create the subdirectories
496 status_t error
= dir
->AddDirectory(subDir
, &dir
);
509 TarFS::Directory::AddFile(tar_header
* header
)
511 char* leaf
= strrchr(header
->name
, '/');
512 char* dirName
= NULL
;
514 dirName
= header
->name
;
520 // create the parent directory
521 TarFS::Directory
* dir
= this;
523 status_t error
= AddDirectory(dirName
, &dir
);
530 if (header
->type
== TAR_FILE
|| header
->type
== TAR_FILE2
)
531 entry
= new(nothrow
) TarFS::File(header
, leaf
);
532 else if (header
->type
== TAR_SYMLINK
)
533 entry
= new(nothrow
) TarFS::Symlink(header
, leaf
);
540 dir
->fEntries
.Add(entry
);
547 TarFS::Directory::IsEmpty()
549 return fEntries
.IsEmpty();
554 TarFS::Directory::Inode() const
563 TarFS::Symlink::Symlink(tar_header
* header
, const char* name
)
564 : TarFS::Entry(name
),
567 fSize
= strnlen(header
->linkname
, sizeof(header
->linkname
));
568 // null-terminate for sure (might overwrite a byte of the magic)
569 header
->linkname
[fSize
++] = '\0';
573 TarFS::Symlink::~Symlink()
579 TarFS::Symlink::ReadAt(void* cookie
, off_t pos
, void* buffer
, size_t bufferSize
)
581 return B_NOT_ALLOWED
;
586 TarFS::Symlink::WriteAt(void* cookie
, off_t pos
, const void* buffer
,
589 return B_NOT_ALLOWED
;
594 TarFS::Symlink::ReadLink(char* buffer
, size_t bufferSize
)
596 const char* path
= fHeader
->linkname
;
597 size_t size
= strlen(path
) + 1;
599 if (size
> bufferSize
)
600 return B_BUFFER_OVERFLOW
;
602 memcpy(buffer
, path
, size
);
608 TarFS::Symlink::GetName(char* nameBuffer
, size_t bufferSize
) const
610 return strlcpy(nameBuffer
, Name(), bufferSize
) >= bufferSize
611 ? B_BUFFER_OVERFLOW
: B_OK
;
616 TarFS::Symlink::Type() const
623 TarFS::Symlink::Size() const
630 TarFS::Symlink::Inode() const
639 TarFS::Volume::Volume()
641 TarFS::Directory(this, "Boot from CD-ROM")
646 TarFS::Volume::~Volume()
652 TarFS::Volume::Init(boot::Partition
* partition
)
655 status_t error
= partition
->Open(&cookie
, O_RDONLY
);
659 struct PartitionCloser
{
660 boot::Partition
*partition
;
663 PartitionCloser(boot::Partition
* partition
, void* cookie
)
664 : partition(partition
),
671 partition
->Close(cookie
);
673 } _(partition
, cookie
);
675 // inflate the tar file -- try offset 0 and the archive offset on a floppy
677 RegionDeleter regionDeleter
;
678 size_t inflatedBytes
;
679 status_t status
= _Inflate(partition
, cookie
, 0, regionDeleter
,
681 if (status
!= B_OK
) {
682 status
= _Inflate(partition
, cookie
, kFloppyArchiveOffset
,
683 regionDeleter
, &inflatedBytes
);
688 // parse the tar file
689 char* block
= (char*)regionDeleter
.Get();
690 int blockCount
= inflatedBytes
/ BLOCK_SIZE
;
693 while (blockIndex
< blockCount
) {
695 tar_header
* header
= (tar_header
*)(block
+ blockIndex
* BLOCK_SIZE
);
696 //dump_header(*header);
698 if (header
->magic
[0] == '\0')
701 if (strcmp(header
->magic
, kTarHeaderMagic
) != 0) {
702 if (strcmp(header
->magic
, kOldTarHeaderMagic
) != 0) {
703 dprintf("Bad tar header magic in block %d.\n", blockIndex
);
709 off_t size
= strtol(header
->size
, NULL
, 8);
711 TRACE(("tarfs: \"%s\", %" B_PRIdOFF
" bytes\n", header
->name
, size
));
713 // TODO: this is old-style GNU tar which probably won't work with newer
715 switch (header
->type
) {
719 status
= AddFile(header
);
723 status
= AddDirectory(header
->name
, NULL
);
727 // this is a long file name
728 // TODO: read long name
730 dprintf("tarfs: unsupported file type: %d ('%c')\n",
731 header
->type
, header
->type
);
741 blockIndex
+= (size
+ 2 * BLOCK_SIZE
- 1) / BLOCK_SIZE
;
747 regionDeleter
.Detach();
753 TarFS::Volume::_Inflate(boot::Partition
* partition
, void* cookie
, off_t offset
,
754 RegionDeleter
& regionDeleter
, size_t* inflatedBytes
)
758 (Bytef
*)in
, // next in
759 sizeof(in
), // avail in
775 char* out
= (char*)regionDeleter
.Get();
776 bool headerRead
= false;
779 ssize_t bytesRead
= partition
->ReadAt(cookie
, offset
, in
, sizeof(in
));
780 if (bytesRead
!= (ssize_t
)sizeof(in
)) {
781 if (bytesRead
<= 0) {
782 status
= Z_STREAM_ERROR
;
787 zStream
.avail_in
= bytesRead
;
788 zStream
.next_in
= (Bytef
*)in
;
791 // check and skip gzip header
792 if (!skip_gzip_header(&zStream
))
797 // allocate memory for the uncompressed data
798 if (platform_allocate_region((void**)&out
, kTarRegionSize
,
799 B_READ_AREA
| B_WRITE_AREA
, false) != B_OK
) {
800 TRACE(("tarfs: allocating region failed!\n"));
803 regionDeleter
.SetTo(out
);
806 zStream
.avail_out
= kTarRegionSize
;
807 zStream
.next_out
= (Bytef
*)out
;
809 status
= inflateInit2(&zStream
, -15);
814 status
= inflate(&zStream
, Z_SYNC_FLUSH
);
817 if (zStream
.avail_in
!= 0 && status
!= Z_STREAM_END
)
818 dprintf("tarfs: didn't read whole block: %s\n", zStream
.msg
);
819 } while (status
== Z_OK
);
821 inflateEnd(&zStream
);
823 if (status
!= Z_STREAM_END
) {
824 TRACE(("tarfs: inflating failed: %d!\n", status
));
828 *inflatedBytes
= zStream
.total_out
;
838 tarfs_get_file_system(boot::Partition
* partition
, ::Directory
** _root
)
840 TarFS::Volume
* volume
= new(nothrow
) TarFS::Volume
;
844 if (volume
->Init(partition
) < B_OK
) {
845 TRACE(("Initializing tarfs failed\n"));
850 *_root
= volume
->Root();
855 file_system_module_info gTarFileSystemModule
= {
856 "file_systems/tarfs/v1",
858 NULL
, // identify_file_system
859 tarfs_get_file_system