xref: /haiku/src/system/boot/loader/file_systems/tarfs/tarfs.cpp (revision 46d4471af7fad4e52cfbd09174598cf5318aceed)
1 /*
2  * Copyright 2005-2007, Ingo Weinhold, bonefish@cs.tu-berlin.de.
3  * Copyright 2005-2013, Axel Dörfler, axeld@pinc-software.de.
4  *
5  * Distributed under the terms of the MIT License.
6  */
7 
8 
9 #include "tarfs.h"
10 
11 #include <fcntl.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <unistd.h>
16 
17 #include <AutoDeleter.h>
18 #include <OS.h>
19 #include <SupportDefs.h>
20 
21 #include <zlib.h>
22 
23 #include <boot/partitions.h>
24 #include <boot/platform.h>
25 #include <util/DoublyLinkedList.h>
26 
27 
28 //#define TRACE_TARFS
29 #ifdef TRACE_TARFS
30 #	define TRACE(x) dprintf x
31 #else
32 #	define TRACE(x) ;
33 #endif
34 
35 
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
39 
40 namespace TarFS {
41 
42 
43 struct RegionDelete {
44 	inline void operator()(void* memory)
45 	{
46 		if (memory != NULL)
47 			platform_free_region(memory, kTarRegionSize);
48 	}
49 };
50 
51 struct RegionDeleter : BPrivate::AutoDeleter<void, RegionDelete> {
52 	RegionDeleter()
53 		:
54 		BPrivate::AutoDeleter<void, RegionDelete>()
55 	{
56 	}
57 
58 	RegionDeleter(void* memory)
59 		:
60 		BPrivate::AutoDeleter<void, RegionDelete>(memory)
61 	{
62 	}
63 };
64 
65 class Directory;
66 
67 class Entry : public DoublyLinkedListLinkImpl<Entry> {
68 public:
69 								Entry(const char* name);
70 	virtual						~Entry() {}
71 
72 			const char*			Name() const { return fName; }
73 	virtual	::Node*				ToNode() = 0;
74 	virtual TarFS::Directory*	ToTarDirectory() { return NULL; }
75 
76 protected:
77 			const char*			fName;
78 			int32				fID;
79 };
80 
81 
82 typedef DoublyLinkedList<TarFS::Entry>	EntryList;
83 typedef EntryList::Iterator	EntryIterator;
84 
85 
86 class File : public ::Node, public Entry {
87 public:
88 								File(tar_header* header, const char* name);
89 	virtual						~File();
90 
91 	virtual	ssize_t				ReadAt(void* cookie, off_t pos, void* buffer,
92 									size_t bufferSize);
93 	virtual	ssize_t				WriteAt(void* cookie, off_t pos,
94 									const void* buffer, size_t bufferSize);
95 
96 	virtual	status_t			GetName(char* nameBuffer,
97 									size_t bufferSize) const;
98 
99 	virtual	int32				Type() const;
100 	virtual	off_t				Size() const;
101 	virtual	ino_t				Inode() const;
102 
103 	virtual	::Node*				ToNode() { return this; }
104 
105 private:
106 			tar_header*			fHeader;
107 			off_t				fSize;
108 };
109 
110 
111 class Directory : public ::Directory, public Entry {
112 public:
113 								Directory(Directory* parent, const char* name);
114 	virtual						~Directory();
115 
116 	virtual	status_t			Open(void** _cookie, int mode);
117 	virtual	status_t			Close(void* cookie);
118 
119 	virtual	status_t			GetName(char* nameBuffer,
120 									size_t bufferSize) const;
121 
122 	virtual	TarFS::Entry*		LookupEntry(const char* name);
123 	virtual	::Node*				LookupDontTraverse(const char* name);
124 
125 	virtual	status_t			GetNextEntry(void* cookie, char* nameBuffer,
126 									size_t bufferSize);
127 	virtual	status_t			GetNextNode(void* cookie, Node** _node);
128 	virtual	status_t			Rewind(void* cookie);
129 	virtual	bool				IsEmpty();
130 
131 	virtual	ino_t				Inode() const;
132 
133 	virtual ::Node*				ToNode() { return this; };
134 	virtual TarFS::Directory*	ToTarDirectory() { return this; }
135 
136 			status_t			AddDirectory(char* dirName,
137 									TarFS::Directory** _dir = NULL);
138 			status_t			AddFile(tar_header* header);
139 
140 private:
141 			typedef ::Directory _inherited;
142 
143 			Directory*			fParent;
144 			EntryList			fEntries;
145 };
146 
147 
148 class Symlink : public ::Node, public Entry {
149 public:
150 								Symlink(tar_header* header, const char* name);
151 	virtual						~Symlink();
152 
153 	virtual	ssize_t				ReadAt(void* cookie, off_t pos, void* buffer,
154 									size_t bufferSize);
155 	virtual	ssize_t				WriteAt(void* cookie, off_t pos,
156 									const void* buffer, size_t bufferSize);
157 
158 	virtual	status_t			ReadLink(char* buffer, size_t bufferSize);
159 
160 	virtual	status_t			GetName(char* nameBuffer,
161 									size_t bufferSize) const;
162 
163 	virtual	int32				Type() const;
164 	virtual	off_t				Size() const;
165 	virtual	ino_t				Inode() const;
166 
167 			const char*			LinkPath() const { return fHeader->linkname; }
168 
169 	virtual ::Node*				ToNode() { return this; }
170 
171 private:
172 			tar_header*			fHeader;
173 			size_t				fSize;
174 };
175 
176 
177 class Volume : public TarFS::Directory {
178 public:
179 								Volume();
180 								~Volume();
181 
182 			status_t			Init(boot::Partition* partition);
183 
184 			TarFS::Directory*	Root() { return this; }
185 
186 private:
187 			status_t			_Inflate(boot::Partition* partition,
188 									void* cookie, off_t offset,
189 									RegionDeleter& regionDeleter,
190 									size_t* inflatedBytes);
191 };
192 
193 }	// namespace TarFS
194 
195 
196 static int32 sNextID = 1;
197 
198 
199 // #pragma mark -
200 
201 
202 bool
203 skip_gzip_header(z_stream* stream)
204 {
205 	uint8* buffer = (uint8*)stream->next_in;
206 
207 	// check magic and skip method
208 	if (buffer[0] != 0x1f || buffer[1] != 0x8b)
209 		return false;
210 
211 	// we need the flags field to determine the length of the header
212 	int flags = buffer[3];
213 
214 	uint32 offset = 10;
215 
216 	if ((flags & 0x04) != 0) {
217 		// skip extra field
218 		offset += (buffer[offset] | (buffer[offset + 1] << 8)) + 2;
219 		if (offset >= stream->avail_in)
220 			return false;
221 	}
222 	if ((flags & 0x08) != 0) {
223 		// skip original name
224 		while (buffer[offset++])
225 			;
226 	}
227 	if ((flags & 0x10) != 0) {
228 		// skip comment
229 		while (buffer[offset++])
230 			;
231 	}
232 	if ((flags & 0x02) != 0) {
233 		// skip CRC
234 		offset += 2;
235 	}
236 
237 	if (offset >= stream->avail_in)
238 		return false;
239 
240 	stream->next_in += offset;
241 	stream->avail_in -= offset;
242 	return true;
243 }
244 
245 
246 // #pragma mark -
247 
248 
249 TarFS::Entry::Entry(const char* name)
250 	:
251 	fName(name),
252 	fID(sNextID++)
253 {
254 }
255 
256 
257 // #pragma mark -
258 
259 
260 TarFS::File::File(tar_header* header, const char* name)
261 	: TarFS::Entry(name),
262 	fHeader(header)
263 {
264 	fSize = strtol(header->size, NULL, 8);
265 }
266 
267 
268 TarFS::File::~File()
269 {
270 }
271 
272 
273 ssize_t
274 TarFS::File::ReadAt(void* cookie, off_t pos, void* buffer, size_t bufferSize)
275 {
276 	TRACE(("tarfs: read at %" B_PRIdOFF ", %" B_PRIuSIZE " bytes, fSize = %"
277 		B_PRIdOFF "\n", pos, bufferSize, fSize));
278 
279 	if (pos < 0 || !buffer)
280 		return B_BAD_VALUE;
281 
282 	if (pos >= fSize || bufferSize == 0)
283 		return 0;
284 
285 	size_t toRead = fSize - pos;
286 	if (toRead > bufferSize)
287 		toRead = bufferSize;
288 
289 	memcpy(buffer, (char*)fHeader + BLOCK_SIZE + pos, toRead);
290 
291 	return toRead;
292 }
293 
294 
295 ssize_t
296 TarFS::File::WriteAt(void* cookie, off_t pos, const void* buffer,
297 	size_t bufferSize)
298 {
299 	return B_NOT_ALLOWED;
300 }
301 
302 
303 status_t
304 TarFS::File::GetName(char* nameBuffer, size_t bufferSize) const
305 {
306 	return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize
307 		? B_BUFFER_OVERFLOW : B_OK;
308 }
309 
310 
311 int32
312 TarFS::File::Type() const
313 {
314 	return S_IFREG;
315 }
316 
317 
318 off_t
319 TarFS::File::Size() const
320 {
321 	return fSize;
322 }
323 
324 
325 ino_t
326 TarFS::File::Inode() const
327 {
328 	return fID;
329 }
330 
331 
332 // #pragma mark -
333 
334 TarFS::Directory::Directory(Directory* parent, const char* name)
335 	:
336 	TarFS::Entry(name),
337 	fParent(parent)
338 {
339 }
340 
341 
342 TarFS::Directory::~Directory()
343 {
344 	while (TarFS::Entry* entry = fEntries.Head()) {
345 		fEntries.Remove(entry);
346 		delete entry;
347 	}
348 }
349 
350 
351 status_t
352 TarFS::Directory::Open(void** _cookie, int mode)
353 {
354 	_inherited::Open(_cookie, mode);
355 
356 	EntryIterator* iterator
357 		= new(std::nothrow) EntryIterator(fEntries.GetIterator());
358 	if (iterator == NULL)
359 		return B_NO_MEMORY;
360 
361 	*_cookie = iterator;
362 
363 	return B_OK;
364 }
365 
366 
367 status_t
368 TarFS::Directory::Close(void* cookie)
369 {
370 	_inherited::Close(cookie);
371 
372 	delete (EntryIterator*)cookie;
373 	return B_OK;
374 }
375 
376 
377 status_t
378 TarFS::Directory::GetName(char* nameBuffer, size_t bufferSize) const
379 {
380 	return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize
381 		? B_BUFFER_OVERFLOW : B_OK;
382 }
383 
384 
385 TarFS::Entry*
386 TarFS::Directory::LookupEntry(const char* name)
387 {
388 	if (strcmp(name, ".") == 0)
389 		return this;
390 	if (strcmp(name, "..") == 0)
391 		return fParent;
392 
393 	EntryIterator iterator(fEntries.GetIterator());
394 
395 	while (iterator.HasNext()) {
396 		TarFS::Entry* entry = iterator.Next();
397 		if (strcmp(name, entry->Name()) == 0)
398 			return entry;
399 	}
400 
401 	return NULL;
402 }
403 
404 
405 ::Node*
406 TarFS::Directory::LookupDontTraverse(const char* name)
407 {
408 	TarFS::Entry* entry = LookupEntry(name);
409 	if (!entry)
410 		return NULL;
411 
412 	Node* node = entry->ToNode();
413 	if (node)
414 		node->Acquire();
415 
416 	return node;
417 }
418 
419 
420 status_t
421 TarFS::Directory::GetNextEntry(void* _cookie, char* name, size_t size)
422 {
423 	EntryIterator* iterator = (EntryIterator*)_cookie;
424 	TarFS::Entry* entry = iterator->Next();
425 
426 	if (entry != NULL) {
427 		strlcpy(name, entry->Name(), size);
428 		return B_OK;
429 	}
430 
431 	return B_ENTRY_NOT_FOUND;
432 }
433 
434 
435 status_t
436 TarFS::Directory::GetNextNode(void* _cookie, Node** _node)
437 {
438 	EntryIterator* iterator = (EntryIterator*)_cookie;
439 	TarFS::Entry* entry = iterator->Next();
440 
441 	if (entry != NULL) {
442 		*_node = entry->ToNode();
443 		return B_OK;
444 	}
445 	return B_ENTRY_NOT_FOUND;
446 }
447 
448 
449 status_t
450 TarFS::Directory::Rewind(void* _cookie)
451 {
452 	EntryIterator* iterator = (EntryIterator*)_cookie;
453 	*iterator = fEntries.GetIterator();
454 	return B_OK;
455 }
456 
457 
458 status_t
459 TarFS::Directory::AddDirectory(char* dirName, TarFS::Directory** _dir)
460 {
461 	char* subDir = strchr(dirName, '/');
462 	if (subDir) {
463 		// skip slashes
464 		while (*subDir == '/') {
465 			*subDir = '\0';
466 			subDir++;
467 		}
468 
469 		if (*subDir == '\0') {
470 			// a trailing slash
471 			subDir = NULL;
472 		}
473 	}
474 
475 	// check, whether the directory does already exist
476 	Entry* entry = LookupEntry(dirName);
477 	TarFS::Directory* dir = (entry ? entry->ToTarDirectory() : NULL);
478 	if (entry) {
479 		if (!dir)
480 			return B_ERROR;
481 	} else {
482 		// doesn't exist yet -- create it
483 		dir = new(nothrow) TarFS::Directory(this, dirName);
484 		if (!dir)
485 			return B_NO_MEMORY;
486 
487 		fEntries.Add(dir);
488 	}
489 
490 	// recursively create the subdirectories
491 	if (subDir) {
492 		status_t error = dir->AddDirectory(subDir, &dir);
493 		if (error != B_OK)
494 			return error;
495 	}
496 
497 	if (_dir)
498 		*_dir = dir;
499 
500 	return B_OK;
501 }
502 
503 
504 status_t
505 TarFS::Directory::AddFile(tar_header* header)
506 {
507 	char* leaf = strrchr(header->name, '/');
508 	char* dirName = NULL;
509 	if (leaf) {
510 		dirName = header->name;
511 		*leaf = '\0';
512 		leaf++;
513 	} else
514 		leaf = header->name;
515 
516 	// create the parent directory
517 	TarFS::Directory* dir = this;
518 	if (dirName) {
519 		status_t error = AddDirectory(dirName, &dir);
520 		if (error != B_OK)
521 			return error;
522 	}
523 
524 	// create the entry
525 	TarFS::Entry* entry;
526 	if (header->type == TAR_FILE || header->type == TAR_FILE2)
527 		entry = new(nothrow) TarFS::File(header, leaf);
528 	else if (header->type == TAR_SYMLINK)
529 		entry = new(nothrow) TarFS::Symlink(header, leaf);
530 	else
531 		return B_BAD_VALUE;
532 
533 	if (!entry)
534 		return B_NO_MEMORY;
535 
536 	dir->fEntries.Add(entry);
537 
538 	return B_OK;
539 }
540 
541 
542 bool
543 TarFS::Directory::IsEmpty()
544 {
545 	return fEntries.IsEmpty();
546 }
547 
548 
549 ino_t
550 TarFS::Directory::Inode() const
551 {
552 	return fID;
553 }
554 
555 
556 // #pragma mark -
557 
558 
559 TarFS::Symlink::Symlink(tar_header* header, const char* name)
560 	: TarFS::Entry(name),
561 	fHeader(header)
562 {
563 	fSize = strnlen(header->linkname, sizeof(header->linkname));
564 	// null-terminate for sure (might overwrite a byte of the magic)
565 	header->linkname[fSize++] = '\0';
566 }
567 
568 
569 TarFS::Symlink::~Symlink()
570 {
571 }
572 
573 
574 ssize_t
575 TarFS::Symlink::ReadAt(void* cookie, off_t pos, void* buffer, size_t bufferSize)
576 {
577 	return B_NOT_ALLOWED;
578 }
579 
580 
581 ssize_t
582 TarFS::Symlink::WriteAt(void* cookie, off_t pos, const void* buffer,
583 	size_t bufferSize)
584 {
585 	return B_NOT_ALLOWED;
586 }
587 
588 
589 status_t
590 TarFS::Symlink::ReadLink(char* buffer, size_t bufferSize)
591 {
592 	const char* path = fHeader->linkname;
593 	size_t size = strlen(path) + 1;
594 
595 	if (size > bufferSize)
596 		return B_BUFFER_OVERFLOW;
597 
598 	memcpy(buffer, path, size);
599 	return B_OK;
600 }
601 
602 
603 status_t
604 TarFS::Symlink::GetName(char* nameBuffer, size_t bufferSize) const
605 {
606 	return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize
607 		? B_BUFFER_OVERFLOW : B_OK;
608 }
609 
610 
611 int32
612 TarFS::Symlink::Type() const
613 {
614 	return S_IFLNK;
615 }
616 
617 
618 off_t
619 TarFS::Symlink::Size() const
620 {
621 	return fSize;
622 }
623 
624 
625 ino_t
626 TarFS::Symlink::Inode() const
627 {
628 	return fID;
629 }
630 
631 
632 // #pragma mark -
633 
634 
635 TarFS::Volume::Volume()
636 	:
637 	TarFS::Directory(this, "Boot from CD-ROM")
638 {
639 }
640 
641 
642 TarFS::Volume::~Volume()
643 {
644 }
645 
646 
647 status_t
648 TarFS::Volume::Init(boot::Partition* partition)
649 {
650 	void* cookie;
651 	status_t error = partition->Open(&cookie, O_RDONLY);
652 	if (error != B_OK)
653 		return error;
654 
655 	struct PartitionCloser {
656 		boot::Partition	*partition;
657 		void			*cookie;
658 
659 		PartitionCloser(boot::Partition* partition, void* cookie)
660 			: partition(partition),
661 			  cookie(cookie)
662 		{
663 		}
664 
665 		~PartitionCloser()
666 		{
667 			partition->Close(cookie);
668 		}
669 	} _(partition, cookie);
670 
671 	// inflate the tar file -- try offset 0 and the archive offset on a floppy
672 	// disk
673 	RegionDeleter regionDeleter;
674 	size_t inflatedBytes;
675 	status_t status = _Inflate(partition, cookie, 0, regionDeleter,
676 		&inflatedBytes);
677 	if (status != B_OK) {
678 		status = _Inflate(partition, cookie, kFloppyArchiveOffset,
679 			regionDeleter, &inflatedBytes);
680 	}
681 	if (status != B_OK)
682 		return status;
683 
684 	// parse the tar file
685 	char* block = (char*)regionDeleter.Get();
686 	int blockCount = inflatedBytes / BLOCK_SIZE;
687 	int blockIndex = 0;
688 
689 	while (blockIndex < blockCount) {
690 		// check header
691 		tar_header* header = (tar_header*)(block + blockIndex * BLOCK_SIZE);
692 		//dump_header(*header);
693 
694 		if (header->magic[0] == '\0')
695 			break;
696 
697 		if (strcmp(header->magic, kTarHeaderMagic) != 0) {
698 			if (strcmp(header->magic, kOldTarHeaderMagic) != 0) {
699 				dprintf("Bad tar header magic in block %d.\n", blockIndex);
700 				status = B_BAD_DATA;
701 				break;
702 			}
703 		}
704 
705 		off_t size = strtol(header->size, NULL, 8);
706 
707 		TRACE(("tarfs: \"%s\", %" B_PRIdOFF " bytes\n", header->name, size));
708 
709 		// TODO: this is old-style GNU tar which probably won't work with newer
710 		// ones...
711 		switch (header->type) {
712 			case TAR_FILE:
713 			case TAR_FILE2:
714 			case TAR_SYMLINK:
715 				status = AddFile(header);
716 				break;
717 
718 			case TAR_DIRECTORY:
719 				status = AddDirectory(header->name, NULL);
720 				break;
721 
722 			case TAR_LONG_NAME:
723 				// this is a long file name
724 				// TODO: read long name
725 			default:
726 				dprintf("tarfs: unsupported file type: %d ('%c')\n",
727 					header->type, header->type);
728 				// unsupported type
729 				status = B_ERROR;
730 				break;
731 		}
732 
733 		if (status != B_OK)
734 			return status;
735 
736 		// next block
737 		blockIndex += (size + 2 * BLOCK_SIZE - 1) / BLOCK_SIZE;
738 	}
739 
740 	if (status != B_OK)
741 		return status;
742 
743 	regionDeleter.Detach();
744 	return B_OK;
745 }
746 
747 
748 status_t
749 TarFS::Volume::_Inflate(boot::Partition* partition, void* cookie, off_t offset,
750 	RegionDeleter& regionDeleter, size_t* inflatedBytes)
751 {
752 	char in[2048];
753 	z_stream zStream = {
754 		(Bytef*)in,		// next in
755 		sizeof(in),		// avail in
756 		0,				// total in
757 		NULL,			// next out
758 		0,				// avail out
759 		0,				// total out
760 		0,				// msg
761 		0,				// state
762 		Z_NULL,			// zalloc
763 		Z_NULL,			// zfree
764 		Z_NULL,			// opaque
765 		0,				// data type
766 		0,				// adler
767 		0,				// reserved
768 	};
769 
770 	int status;
771 	char* out = (char*)regionDeleter.Get();
772 	bool headerRead = false;
773 
774 	do {
775 		ssize_t bytesRead = partition->ReadAt(cookie, offset, in, sizeof(in));
776 		if (bytesRead != (ssize_t)sizeof(in)) {
777 			if (bytesRead <= 0) {
778 				status = Z_STREAM_ERROR;
779 				break;
780 			}
781 		}
782 
783 		zStream.avail_in = bytesRead;
784 		zStream.next_in = (Bytef*)in;
785 
786 		if (!headerRead) {
787 			// check and skip gzip header
788 			if (!skip_gzip_header(&zStream))
789 				return B_BAD_DATA;
790 			headerRead = true;
791 
792 			if (!out) {
793 				// allocate memory for the uncompressed data
794 				if (platform_allocate_region((void**)&out, kTarRegionSize,
795 						B_READ_AREA | B_WRITE_AREA, false) != B_OK) {
796 					TRACE(("tarfs: allocating region failed!\n"));
797 					return B_NO_MEMORY;
798 				}
799 				regionDeleter.SetTo(out);
800 			}
801 
802 			zStream.avail_out = kTarRegionSize;
803 			zStream.next_out = (Bytef*)out;
804 
805 			status = inflateInit2(&zStream, -15);
806 			if (status != Z_OK)
807 				return B_ERROR;
808 		}
809 
810 		status = inflate(&zStream, Z_SYNC_FLUSH);
811 		offset += bytesRead;
812 
813 		if (zStream.avail_in != 0 && status != Z_STREAM_END)
814 			dprintf("tarfs: didn't read whole block: %s\n", zStream.msg);
815 	} while (status == Z_OK);
816 
817 	inflateEnd(&zStream);
818 
819 	if (status != Z_STREAM_END) {
820 		TRACE(("tarfs: inflating failed: %d!\n", status));
821 		return B_BAD_DATA;
822 	}
823 
824 	*inflatedBytes = zStream.total_out;
825 
826 	return B_OK;
827 }
828 
829 
830 //	#pragma mark -
831 
832 
833 static status_t
834 tarfs_get_file_system(boot::Partition* partition, ::Directory** _root)
835 {
836 	TarFS::Volume* volume = new(nothrow) TarFS::Volume;
837 	if (volume == NULL)
838 		return B_NO_MEMORY;
839 
840 	if (volume->Init(partition) < B_OK) {
841 		TRACE(("Initializing tarfs failed\n"));
842 		delete volume;
843 		return B_ERROR;
844 	}
845 
846 	*_root = volume->Root();
847 	return B_OK;
848 }
849 
850 
851 file_system_module_info gTarFileSystemModule = {
852 	"file_systems/tarfs/v1",
853 	kPartitionTypeTarFS,
854 	NULL,	// identify_file_system
855 	tarfs_get_file_system
856 };
857 
858