vfs: check userland buffers before reading them.
[haiku.git] / src / system / boot / loader / file_systems / tarfs / tarfs.cpp
blob552d8003b603d3e3540fc1ba5010c37dc067b75f
1 /*
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.
6 */
9 #include "tarfs.h"
11 #include <fcntl.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <unistd.h>
17 #include <AutoDeleter.h>
18 #include <OS.h>
19 #include <SupportDefs.h>
21 #include <zlib.h>
23 #include <boot/partitions.h>
24 #include <boot/platform.h>
25 #include <util/DoublyLinkedList.h>
28 //#define TRACE_TARFS
29 #ifdef TRACE_TARFS
30 # define TRACE(x) dprintf x
31 #else
32 # define TRACE(x) ;
33 #endif
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
41 using std::nothrow;
44 namespace TarFS {
47 struct RegionDelete {
48 inline void operator()(void* memory)
50 if (memory != NULL)
51 platform_free_region(memory, kTarRegionSize);
55 struct RegionDeleter : BPrivate::AutoDeleter<void, RegionDelete> {
56 RegionDeleter()
58 BPrivate::AutoDeleter<void, RegionDelete>()
62 RegionDeleter(void* memory)
64 BPrivate::AutoDeleter<void, RegionDelete>(memory)
69 class Directory;
71 class Entry : public DoublyLinkedListLinkImpl<Entry> {
72 public:
73 Entry(const char* name);
74 virtual ~Entry() {}
76 const char* Name() const { return fName; }
77 virtual ::Node* ToNode() = 0;
78 virtual TarFS::Directory* ToTarDirectory() { return NULL; }
80 protected:
81 const char* fName;
82 int32 fID;
86 typedef DoublyLinkedList<TarFS::Entry> EntryList;
87 typedef EntryList::Iterator EntryIterator;
90 class File : public ::Node, public Entry {
91 public:
92 File(tar_header* header, const char* name);
93 virtual ~File();
95 virtual ssize_t ReadAt(void* cookie, off_t pos, void* buffer,
96 size_t bufferSize);
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; }
109 private:
110 tar_header* fHeader;
111 off_t fSize;
115 class Directory : public ::Directory, public Entry {
116 public:
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,
130 size_t bufferSize);
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);
144 private:
145 typedef ::Directory _inherited;
147 Directory* fParent;
148 EntryList fEntries;
152 class Symlink : public ::Node, public Entry {
153 public:
154 Symlink(tar_header* header, const char* name);
155 virtual ~Symlink();
157 virtual ssize_t ReadAt(void* cookie, off_t pos, void* buffer,
158 size_t bufferSize);
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; }
175 private:
176 tar_header* fHeader;
177 size_t fSize;
181 class Volume : public TarFS::Directory {
182 public:
183 Volume();
184 ~Volume();
186 status_t Init(boot::Partition* partition);
188 TarFS::Directory* Root() { return this; }
190 private:
191 status_t _Inflate(boot::Partition* partition,
192 void* cookie, off_t offset,
193 RegionDeleter& regionDeleter,
194 size_t* inflatedBytes);
197 } // namespace TarFS
200 static int32 sNextID = 1;
203 // #pragma mark -
206 bool
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)
213 return false;
215 // we need the flags field to determine the length of the header
216 int flags = buffer[3];
218 uint32 offset = 10;
220 if ((flags & 0x04) != 0) {
221 // skip extra field
222 offset += (buffer[offset] | (buffer[offset + 1] << 8)) + 2;
223 if (offset >= stream->avail_in)
224 return false;
226 if ((flags & 0x08) != 0) {
227 // skip original name
228 while (buffer[offset++])
231 if ((flags & 0x10) != 0) {
232 // skip comment
233 while (buffer[offset++])
236 if ((flags & 0x02) != 0) {
237 // skip CRC
238 offset += 2;
241 if (offset >= stream->avail_in)
242 return false;
244 stream->next_in += offset;
245 stream->avail_in -= offset;
246 return true;
250 // #pragma mark -
253 TarFS::Entry::Entry(const char* name)
255 fName(name),
256 fID(sNextID++)
261 // #pragma mark -
264 TarFS::File::File(tar_header* header, const char* name)
265 : TarFS::Entry(name),
266 fHeader(header)
268 fSize = strtol(header->size, NULL, 8);
272 TarFS::File::~File()
277 ssize_t
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)
284 return B_BAD_VALUE;
286 if (pos >= fSize || bufferSize == 0)
287 return 0;
289 size_t toRead = fSize - pos;
290 if (toRead > bufferSize)
291 toRead = bufferSize;
293 memcpy(buffer, (char*)fHeader + BLOCK_SIZE + pos, toRead);
295 return toRead;
299 ssize_t
300 TarFS::File::WriteAt(void* cookie, off_t pos, const void* buffer,
301 size_t bufferSize)
303 return B_NOT_ALLOWED;
307 status_t
308 TarFS::File::GetName(char* nameBuffer, size_t bufferSize) const
310 return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize
311 ? B_BUFFER_OVERFLOW : B_OK;
315 int32
316 TarFS::File::Type() const
318 return S_IFREG;
322 off_t
323 TarFS::File::Size() const
325 return fSize;
329 ino_t
330 TarFS::File::Inode() const
332 return fID;
336 // #pragma mark -
338 TarFS::Directory::Directory(Directory* parent, const char* name)
340 TarFS::Entry(name),
341 fParent(parent)
346 TarFS::Directory::~Directory()
348 while (TarFS::Entry* entry = fEntries.Head()) {
349 fEntries.Remove(entry);
350 delete entry;
355 status_t
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)
363 return B_NO_MEMORY;
365 *_cookie = iterator;
367 return B_OK;
371 status_t
372 TarFS::Directory::Close(void* cookie)
374 _inherited::Close(cookie);
376 delete (EntryIterator*)cookie;
377 return B_OK;
381 status_t
382 TarFS::Directory::GetName(char* nameBuffer, size_t bufferSize) const
384 return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize
385 ? B_BUFFER_OVERFLOW : B_OK;
389 TarFS::Entry*
390 TarFS::Directory::LookupEntry(const char* name)
392 if (strcmp(name, ".") == 0)
393 return this;
394 if (strcmp(name, "..") == 0)
395 return fParent;
397 EntryIterator iterator(fEntries.GetIterator());
399 while (iterator.HasNext()) {
400 TarFS::Entry* entry = iterator.Next();
401 if (strcmp(name, entry->Name()) == 0)
402 return entry;
405 return NULL;
409 ::Node*
410 TarFS::Directory::LookupDontTraverse(const char* name)
412 TarFS::Entry* entry = LookupEntry(name);
413 if (!entry)
414 return NULL;
416 Node* node = entry->ToNode();
417 if (node)
418 node->Acquire();
420 return node;
424 status_t
425 TarFS::Directory::GetNextEntry(void* _cookie, char* name, size_t size)
427 EntryIterator* iterator = (EntryIterator*)_cookie;
428 TarFS::Entry* entry = iterator->Next();
430 if (entry != NULL) {
431 strlcpy(name, entry->Name(), size);
432 return B_OK;
435 return B_ENTRY_NOT_FOUND;
439 status_t
440 TarFS::Directory::GetNextNode(void* _cookie, Node** _node)
442 EntryIterator* iterator = (EntryIterator*)_cookie;
443 TarFS::Entry* entry = iterator->Next();
445 if (entry != NULL) {
446 *_node = entry->ToNode();
447 return B_OK;
449 return B_ENTRY_NOT_FOUND;
453 status_t
454 TarFS::Directory::Rewind(void* _cookie)
456 EntryIterator* iterator = (EntryIterator*)_cookie;
457 *iterator = fEntries.GetIterator();
458 return B_OK;
462 status_t
463 TarFS::Directory::AddDirectory(char* dirName, TarFS::Directory** _dir)
465 char* subDir = strchr(dirName, '/');
466 if (subDir) {
467 // skip slashes
468 while (*subDir == '/') {
469 *subDir = '\0';
470 subDir++;
473 if (*subDir == '\0') {
474 // a trailing slash
475 subDir = NULL;
479 // check, whether the directory does already exist
480 Entry* entry = LookupEntry(dirName);
481 TarFS::Directory* dir = (entry ? entry->ToTarDirectory() : NULL);
482 if (entry) {
483 if (!dir)
484 return B_ERROR;
485 } else {
486 // doesn't exist yet -- create it
487 dir = new(nothrow) TarFS::Directory(this, dirName);
488 if (!dir)
489 return B_NO_MEMORY;
491 fEntries.Add(dir);
494 // recursively create the subdirectories
495 if (subDir) {
496 status_t error = dir->AddDirectory(subDir, &dir);
497 if (error != B_OK)
498 return error;
501 if (_dir)
502 *_dir = dir;
504 return B_OK;
508 status_t
509 TarFS::Directory::AddFile(tar_header* header)
511 char* leaf = strrchr(header->name, '/');
512 char* dirName = NULL;
513 if (leaf) {
514 dirName = header->name;
515 *leaf = '\0';
516 leaf++;
517 } else
518 leaf = header->name;
520 // create the parent directory
521 TarFS::Directory* dir = this;
522 if (dirName) {
523 status_t error = AddDirectory(dirName, &dir);
524 if (error != B_OK)
525 return error;
528 // create the entry
529 TarFS::Entry* entry;
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);
534 else
535 return B_BAD_VALUE;
537 if (!entry)
538 return B_NO_MEMORY;
540 dir->fEntries.Add(entry);
542 return B_OK;
546 bool
547 TarFS::Directory::IsEmpty()
549 return fEntries.IsEmpty();
553 ino_t
554 TarFS::Directory::Inode() const
556 return fID;
560 // #pragma mark -
563 TarFS::Symlink::Symlink(tar_header* header, const char* name)
564 : TarFS::Entry(name),
565 fHeader(header)
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()
578 ssize_t
579 TarFS::Symlink::ReadAt(void* cookie, off_t pos, void* buffer, size_t bufferSize)
581 return B_NOT_ALLOWED;
585 ssize_t
586 TarFS::Symlink::WriteAt(void* cookie, off_t pos, const void* buffer,
587 size_t bufferSize)
589 return B_NOT_ALLOWED;
593 status_t
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);
603 return B_OK;
607 status_t
608 TarFS::Symlink::GetName(char* nameBuffer, size_t bufferSize) const
610 return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize
611 ? B_BUFFER_OVERFLOW : B_OK;
615 int32
616 TarFS::Symlink::Type() const
618 return S_IFLNK;
622 off_t
623 TarFS::Symlink::Size() const
625 return fSize;
629 ino_t
630 TarFS::Symlink::Inode() const
632 return fID;
636 // #pragma mark -
639 TarFS::Volume::Volume()
641 TarFS::Directory(this, "Boot from CD-ROM")
646 TarFS::Volume::~Volume()
651 status_t
652 TarFS::Volume::Init(boot::Partition* partition)
654 void* cookie;
655 status_t error = partition->Open(&cookie, O_RDONLY);
656 if (error != B_OK)
657 return error;
659 struct PartitionCloser {
660 boot::Partition *partition;
661 void *cookie;
663 PartitionCloser(boot::Partition* partition, void* cookie)
664 : partition(partition),
665 cookie(cookie)
669 ~PartitionCloser()
671 partition->Close(cookie);
673 } _(partition, cookie);
675 // inflate the tar file -- try offset 0 and the archive offset on a floppy
676 // disk
677 RegionDeleter regionDeleter;
678 size_t inflatedBytes;
679 status_t status = _Inflate(partition, cookie, 0, regionDeleter,
680 &inflatedBytes);
681 if (status != B_OK) {
682 status = _Inflate(partition, cookie, kFloppyArchiveOffset,
683 regionDeleter, &inflatedBytes);
685 if (status != B_OK)
686 return status;
688 // parse the tar file
689 char* block = (char*)regionDeleter.Get();
690 int blockCount = inflatedBytes / BLOCK_SIZE;
691 int blockIndex = 0;
693 while (blockIndex < blockCount) {
694 // check header
695 tar_header* header = (tar_header*)(block + blockIndex * BLOCK_SIZE);
696 //dump_header(*header);
698 if (header->magic[0] == '\0')
699 break;
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);
704 status = B_BAD_DATA;
705 break;
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
714 // ones...
715 switch (header->type) {
716 case TAR_FILE:
717 case TAR_FILE2:
718 case TAR_SYMLINK:
719 status = AddFile(header);
720 break;
722 case TAR_DIRECTORY:
723 status = AddDirectory(header->name, NULL);
724 break;
726 case TAR_LONG_NAME:
727 // this is a long file name
728 // TODO: read long name
729 default:
730 dprintf("tarfs: unsupported file type: %d ('%c')\n",
731 header->type, header->type);
732 // unsupported type
733 status = B_ERROR;
734 break;
737 if (status != B_OK)
738 return status;
740 // next block
741 blockIndex += (size + 2 * BLOCK_SIZE - 1) / BLOCK_SIZE;
744 if (status != B_OK)
745 return status;
747 regionDeleter.Detach();
748 return B_OK;
752 status_t
753 TarFS::Volume::_Inflate(boot::Partition* partition, void* cookie, off_t offset,
754 RegionDeleter& regionDeleter, size_t* inflatedBytes)
756 char in[2048];
757 z_stream zStream = {
758 (Bytef*)in, // next in
759 sizeof(in), // avail in
760 0, // total in
761 NULL, // next out
762 0, // avail out
763 0, // total out
764 0, // msg
765 0, // state
766 Z_NULL, // zalloc
767 Z_NULL, // zfree
768 Z_NULL, // opaque
769 0, // data type
770 0, // adler
771 0, // reserved
774 int status;
775 char* out = (char*)regionDeleter.Get();
776 bool headerRead = false;
778 do {
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;
783 break;
787 zStream.avail_in = bytesRead;
788 zStream.next_in = (Bytef*)in;
790 if (!headerRead) {
791 // check and skip gzip header
792 if (!skip_gzip_header(&zStream))
793 return B_BAD_DATA;
794 headerRead = true;
796 if (!out) {
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"));
801 return B_NO_MEMORY;
803 regionDeleter.SetTo(out);
806 zStream.avail_out = kTarRegionSize;
807 zStream.next_out = (Bytef*)out;
809 status = inflateInit2(&zStream, -15);
810 if (status != Z_OK)
811 return B_ERROR;
814 status = inflate(&zStream, Z_SYNC_FLUSH);
815 offset += bytesRead;
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));
825 return B_BAD_DATA;
828 *inflatedBytes = zStream.total_out;
830 return B_OK;
834 // #pragma mark -
837 static status_t
838 tarfs_get_file_system(boot::Partition* partition, ::Directory** _root)
840 TarFS::Volume* volume = new(nothrow) TarFS::Volume;
841 if (volume == NULL)
842 return B_NO_MEMORY;
844 if (volume->Init(partition) < B_OK) {
845 TRACE(("Initializing tarfs failed\n"));
846 delete volume;
847 return B_ERROR;
850 *_root = volume->Root();
851 return B_OK;
855 file_system_module_info gTarFileSystemModule = {
856 "file_systems/tarfs/v1",
857 kPartitionTypeTarFS,
858 NULL, // identify_file_system
859 tarfs_get_file_system