xref: /haiku/src/system/boot/loader/file_systems/tarfs/tarfs.cpp (revision aa3083e086e5a929c061c72983e09d916c548a38)
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 
41 using std::nothrow;
42 
43 
44 namespace TarFS {
45 
46 
47 struct RegionDelete {
48 	inline void operator()(void* memory)
49 	{
50 		if (memory != NULL)
51 			platform_free_region(memory, kTarRegionSize);
52 	}
53 };
54 
55 struct RegionDeleter : BPrivate::AutoDeleter<void, RegionDelete> {
56 	RegionDeleter()
57 		:
58 		BPrivate::AutoDeleter<void, RegionDelete>()
59 	{
60 	}
61 
62 	RegionDeleter(void* memory)
63 		:
64 		BPrivate::AutoDeleter<void, RegionDelete>(memory)
65 	{
66 	}
67 };
68 
69 class Directory;
70 
71 class Entry : public DoublyLinkedListLinkImpl<Entry> {
72 public:
73 								Entry(const char* name);
74 	virtual						~Entry() {}
75 
76 			const char*			Name() const { return fName; }
77 	virtual	::Node*				ToNode() = 0;
78 	virtual TarFS::Directory*	ToTarDirectory() { return NULL; }
79 
80 protected:
81 			const char*			fName;
82 			int32				fID;
83 };
84 
85 
86 typedef DoublyLinkedList<TarFS::Entry>	EntryList;
87 typedef EntryList::Iterator	EntryIterator;
88 
89 
90 class File : public ::Node, public Entry {
91 public:
92 								File(tar_header* header, const char* name);
93 	virtual						~File();
94 
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);
99 
100 	virtual	status_t			GetName(char* nameBuffer,
101 									size_t bufferSize) const;
102 
103 	virtual	int32				Type() const;
104 	virtual	off_t				Size() const;
105 	virtual	ino_t				Inode() const;
106 
107 	virtual	::Node*				ToNode() { return this; }
108 
109 private:
110 			tar_header*			fHeader;
111 			off_t				fSize;
112 };
113 
114 
115 class Directory : public ::Directory, public Entry {
116 public:
117 								Directory(Directory* parent, const char* name);
118 	virtual						~Directory();
119 
120 	virtual	status_t			Open(void** _cookie, int mode);
121 	virtual	status_t			Close(void* cookie);
122 
123 	virtual	status_t			GetName(char* nameBuffer,
124 									size_t bufferSize) const;
125 
126 	virtual	TarFS::Entry*		LookupEntry(const char* name);
127 	virtual	::Node*				LookupDontTraverse(const char* name);
128 
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();
134 
135 	virtual	ino_t				Inode() const;
136 
137 	virtual ::Node*				ToNode() { return this; };
138 	virtual TarFS::Directory*	ToTarDirectory() { return this; }
139 
140 			status_t			AddDirectory(char* dirName,
141 									TarFS::Directory** _dir = NULL);
142 			status_t			AddFile(tar_header* header);
143 
144 private:
145 			typedef ::Directory _inherited;
146 
147 			Directory*			fParent;
148 			EntryList			fEntries;
149 };
150 
151 
152 class Symlink : public ::Node, public Entry {
153 public:
154 								Symlink(tar_header* header, const char* name);
155 	virtual						~Symlink();
156 
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);
161 
162 	virtual	status_t			ReadLink(char* buffer, size_t bufferSize);
163 
164 	virtual	status_t			GetName(char* nameBuffer,
165 									size_t bufferSize) const;
166 
167 	virtual	int32				Type() const;
168 	virtual	off_t				Size() const;
169 	virtual	ino_t				Inode() const;
170 
171 			const char*			LinkPath() const { return fHeader->linkname; }
172 
173 	virtual ::Node*				ToNode() { return this; }
174 
175 private:
176 			tar_header*			fHeader;
177 			size_t				fSize;
178 };
179 
180 
181 class Volume : public TarFS::Directory {
182 public:
183 								Volume();
184 								~Volume();
185 
186 			status_t			Init(boot::Partition* partition);
187 
188 			TarFS::Directory*	Root() { return this; }
189 
190 private:
191 			status_t			_Inflate(boot::Partition* partition,
192 									void* cookie, off_t offset,
193 									RegionDeleter& regionDeleter,
194 									size_t* inflatedBytes);
195 };
196 
197 }	// namespace TarFS
198 
199 
200 static int32 sNextID = 1;
201 
202 
203 // #pragma mark -
204 
205 
206 bool
207 skip_gzip_header(z_stream* stream)
208 {
209 	uint8* buffer = (uint8*)stream->next_in;
210 
211 	// check magic and skip method
212 	if (buffer[0] != 0x1f || buffer[1] != 0x8b)
213 		return false;
214 
215 	// we need the flags field to determine the length of the header
216 	int flags = buffer[3];
217 
218 	uint32 offset = 10;
219 
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;
225 	}
226 	if ((flags & 0x08) != 0) {
227 		// skip original name
228 		while (buffer[offset++])
229 			;
230 	}
231 	if ((flags & 0x10) != 0) {
232 		// skip comment
233 		while (buffer[offset++])
234 			;
235 	}
236 	if ((flags & 0x02) != 0) {
237 		// skip CRC
238 		offset += 2;
239 	}
240 
241 	if (offset >= stream->avail_in)
242 		return false;
243 
244 	stream->next_in += offset;
245 	stream->avail_in -= offset;
246 	return true;
247 }
248 
249 
250 // #pragma mark -
251 
252 
253 TarFS::Entry::Entry(const char* name)
254 	:
255 	fName(name),
256 	fID(sNextID++)
257 {
258 }
259 
260 
261 // #pragma mark -
262 
263 
264 TarFS::File::File(tar_header* header, const char* name)
265 	: TarFS::Entry(name),
266 	fHeader(header)
267 {
268 	fSize = strtol(header->size, NULL, 8);
269 }
270 
271 
272 TarFS::File::~File()
273 {
274 }
275 
276 
277 ssize_t
278 TarFS::File::ReadAt(void* cookie, off_t pos, void* buffer, size_t bufferSize)
279 {
280 	TRACE(("tarfs: read at %" B_PRIdOFF ", %" B_PRIuSIZE " bytes, fSize = %"
281 		B_PRIdOFF "\n", pos, bufferSize, fSize));
282 
283 	if (pos < 0 || !buffer)
284 		return B_BAD_VALUE;
285 
286 	if (pos >= fSize || bufferSize == 0)
287 		return 0;
288 
289 	size_t toRead = fSize - pos;
290 	if (toRead > bufferSize)
291 		toRead = bufferSize;
292 
293 	memcpy(buffer, (char*)fHeader + BLOCK_SIZE + pos, toRead);
294 
295 	return toRead;
296 }
297 
298 
299 ssize_t
300 TarFS::File::WriteAt(void* cookie, off_t pos, const void* buffer,
301 	size_t bufferSize)
302 {
303 	return B_NOT_ALLOWED;
304 }
305 
306 
307 status_t
308 TarFS::File::GetName(char* nameBuffer, size_t bufferSize) const
309 {
310 	return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize
311 		? B_BUFFER_OVERFLOW : B_OK;
312 }
313 
314 
315 int32
316 TarFS::File::Type() const
317 {
318 	return S_IFREG;
319 }
320 
321 
322 off_t
323 TarFS::File::Size() const
324 {
325 	return fSize;
326 }
327 
328 
329 ino_t
330 TarFS::File::Inode() const
331 {
332 	return fID;
333 }
334 
335 
336 // #pragma mark -
337 
338 TarFS::Directory::Directory(Directory* parent, const char* name)
339 	:
340 	TarFS::Entry(name),
341 	fParent(parent)
342 {
343 }
344 
345 
346 TarFS::Directory::~Directory()
347 {
348 	while (TarFS::Entry* entry = fEntries.Head()) {
349 		fEntries.Remove(entry);
350 		delete entry;
351 	}
352 }
353 
354 
355 status_t
356 TarFS::Directory::Open(void** _cookie, int mode)
357 {
358 	_inherited::Open(_cookie, mode);
359 
360 	EntryIterator* iterator
361 		= new(nothrow) EntryIterator(fEntries.GetIterator());
362 	if (iterator == NULL)
363 		return B_NO_MEMORY;
364 
365 	*_cookie = iterator;
366 
367 	return B_OK;
368 }
369 
370 
371 status_t
372 TarFS::Directory::Close(void* cookie)
373 {
374 	_inherited::Close(cookie);
375 
376 	delete (EntryIterator*)cookie;
377 	return B_OK;
378 }
379 
380 
381 status_t
382 TarFS::Directory::GetName(char* nameBuffer, size_t bufferSize) const
383 {
384 	return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize
385 		? B_BUFFER_OVERFLOW : B_OK;
386 }
387 
388 
389 TarFS::Entry*
390 TarFS::Directory::LookupEntry(const char* name)
391 {
392 	if (strcmp(name, ".") == 0)
393 		return this;
394 	if (strcmp(name, "..") == 0)
395 		return fParent;
396 
397 	EntryIterator iterator(fEntries.GetIterator());
398 
399 	while (iterator.HasNext()) {
400 		TarFS::Entry* entry = iterator.Next();
401 		if (strcmp(name, entry->Name()) == 0)
402 			return entry;
403 	}
404 
405 	return NULL;
406 }
407 
408 
409 ::Node*
410 TarFS::Directory::LookupDontTraverse(const char* name)
411 {
412 	TarFS::Entry* entry = LookupEntry(name);
413 	if (!entry)
414 		return NULL;
415 
416 	Node* node = entry->ToNode();
417 	if (node)
418 		node->Acquire();
419 
420 	return node;
421 }
422 
423 
424 status_t
425 TarFS::Directory::GetNextEntry(void* _cookie, char* name, size_t size)
426 {
427 	EntryIterator* iterator = (EntryIterator*)_cookie;
428 	TarFS::Entry* entry = iterator->Next();
429 
430 	if (entry != NULL) {
431 		strlcpy(name, entry->Name(), size);
432 		return B_OK;
433 	}
434 
435 	return B_ENTRY_NOT_FOUND;
436 }
437 
438 
439 status_t
440 TarFS::Directory::GetNextNode(void* _cookie, Node** _node)
441 {
442 	EntryIterator* iterator = (EntryIterator*)_cookie;
443 	TarFS::Entry* entry = iterator->Next();
444 
445 	if (entry != NULL) {
446 		*_node = entry->ToNode();
447 		return B_OK;
448 	}
449 	return B_ENTRY_NOT_FOUND;
450 }
451 
452 
453 status_t
454 TarFS::Directory::Rewind(void* _cookie)
455 {
456 	EntryIterator* iterator = (EntryIterator*)_cookie;
457 	*iterator = fEntries.GetIterator();
458 	return B_OK;
459 }
460 
461 
462 status_t
463 TarFS::Directory::AddDirectory(char* dirName, TarFS::Directory** _dir)
464 {
465 	char* subDir = strchr(dirName, '/');
466 	if (subDir) {
467 		// skip slashes
468 		while (*subDir == '/') {
469 			*subDir = '\0';
470 			subDir++;
471 		}
472 
473 		if (*subDir == '\0') {
474 			// a trailing slash
475 			subDir = NULL;
476 		}
477 	}
478 
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;
490 
491 		fEntries.Add(dir);
492 	}
493 
494 	// recursively create the subdirectories
495 	if (subDir) {
496 		status_t error = dir->AddDirectory(subDir, &dir);
497 		if (error != B_OK)
498 			return error;
499 	}
500 
501 	if (_dir)
502 		*_dir = dir;
503 
504 	return B_OK;
505 }
506 
507 
508 status_t
509 TarFS::Directory::AddFile(tar_header* header)
510 {
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;
519 
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;
526 	}
527 
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;
536 
537 	if (!entry)
538 		return B_NO_MEMORY;
539 
540 	dir->fEntries.Add(entry);
541 
542 	return B_OK;
543 }
544 
545 
546 bool
547 TarFS::Directory::IsEmpty()
548 {
549 	return fEntries.IsEmpty();
550 }
551 
552 
553 ino_t
554 TarFS::Directory::Inode() const
555 {
556 	return fID;
557 }
558 
559 
560 // #pragma mark -
561 
562 
563 TarFS::Symlink::Symlink(tar_header* header, const char* name)
564 	: TarFS::Entry(name),
565 	fHeader(header)
566 {
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';
570 }
571 
572 
573 TarFS::Symlink::~Symlink()
574 {
575 }
576 
577 
578 ssize_t
579 TarFS::Symlink::ReadAt(void* cookie, off_t pos, void* buffer, size_t bufferSize)
580 {
581 	return B_NOT_ALLOWED;
582 }
583 
584 
585 ssize_t
586 TarFS::Symlink::WriteAt(void* cookie, off_t pos, const void* buffer,
587 	size_t bufferSize)
588 {
589 	return B_NOT_ALLOWED;
590 }
591 
592 
593 status_t
594 TarFS::Symlink::ReadLink(char* buffer, size_t bufferSize)
595 {
596 	const char* path = fHeader->linkname;
597 	size_t size = strlen(path) + 1;
598 
599 	if (size > bufferSize)
600 		return B_BUFFER_OVERFLOW;
601 
602 	memcpy(buffer, path, size);
603 	return B_OK;
604 }
605 
606 
607 status_t
608 TarFS::Symlink::GetName(char* nameBuffer, size_t bufferSize) const
609 {
610 	return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize
611 		? B_BUFFER_OVERFLOW : B_OK;
612 }
613 
614 
615 int32
616 TarFS::Symlink::Type() const
617 {
618 	return S_IFLNK;
619 }
620 
621 
622 off_t
623 TarFS::Symlink::Size() const
624 {
625 	return fSize;
626 }
627 
628 
629 ino_t
630 TarFS::Symlink::Inode() const
631 {
632 	return fID;
633 }
634 
635 
636 // #pragma mark -
637 
638 
639 TarFS::Volume::Volume()
640 	:
641 	TarFS::Directory(this, "Boot from CD-ROM")
642 {
643 }
644 
645 
646 TarFS::Volume::~Volume()
647 {
648 }
649 
650 
651 status_t
652 TarFS::Volume::Init(boot::Partition* partition)
653 {
654 	void* cookie;
655 	status_t error = partition->Open(&cookie, O_RDONLY);
656 	if (error != B_OK)
657 		return error;
658 
659 	struct PartitionCloser {
660 		boot::Partition	*partition;
661 		void			*cookie;
662 
663 		PartitionCloser(boot::Partition* partition, void* cookie)
664 			: partition(partition),
665 			  cookie(cookie)
666 		{
667 		}
668 
669 		~PartitionCloser()
670 		{
671 			partition->Close(cookie);
672 		}
673 	} _(partition, cookie);
674 
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);
684 	}
685 	if (status != B_OK)
686 		return status;
687 
688 	// parse the tar file
689 	char* block = (char*)regionDeleter.Get();
690 	int blockCount = inflatedBytes / BLOCK_SIZE;
691 	int blockIndex = 0;
692 
693 	while (blockIndex < blockCount) {
694 		// check header
695 		tar_header* header = (tar_header*)(block + blockIndex * BLOCK_SIZE);
696 		//dump_header(*header);
697 
698 		if (header->magic[0] == '\0')
699 			break;
700 
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;
706 			}
707 		}
708 
709 		off_t size = strtol(header->size, NULL, 8);
710 
711 		TRACE(("tarfs: \"%s\", %" B_PRIdOFF " bytes\n", header->name, size));
712 
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;
721 
722 			case TAR_DIRECTORY:
723 				status = AddDirectory(header->name, NULL);
724 				break;
725 
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;
735 		}
736 
737 		if (status != B_OK)
738 			return status;
739 
740 		// next block
741 		blockIndex += (size + 2 * BLOCK_SIZE - 1) / BLOCK_SIZE;
742 	}
743 
744 	if (status != B_OK)
745 		return status;
746 
747 	regionDeleter.Detach();
748 	return B_OK;
749 }
750 
751 
752 status_t
753 TarFS::Volume::_Inflate(boot::Partition* partition, void* cookie, off_t offset,
754 	RegionDeleter& regionDeleter, size_t* inflatedBytes)
755 {
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
772 	};
773 
774 	int status;
775 	char* out = (char*)regionDeleter.Get();
776 	bool headerRead = false;
777 
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;
784 			}
785 		}
786 
787 		zStream.avail_in = bytesRead;
788 		zStream.next_in = (Bytef*)in;
789 
790 		if (!headerRead) {
791 			// check and skip gzip header
792 			if (!skip_gzip_header(&zStream))
793 				return B_BAD_DATA;
794 			headerRead = true;
795 
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;
802 				}
803 				regionDeleter.SetTo(out);
804 			}
805 
806 			zStream.avail_out = kTarRegionSize;
807 			zStream.next_out = (Bytef*)out;
808 
809 			status = inflateInit2(&zStream, -15);
810 			if (status != Z_OK)
811 				return B_ERROR;
812 		}
813 
814 		status = inflate(&zStream, Z_SYNC_FLUSH);
815 		offset += bytesRead;
816 
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);
820 
821 	inflateEnd(&zStream);
822 
823 	if (status != Z_STREAM_END) {
824 		TRACE(("tarfs: inflating failed: %d!\n", status));
825 		return B_BAD_DATA;
826 	}
827 
828 	*inflatedBytes = zStream.total_out;
829 
830 	return B_OK;
831 }
832 
833 
834 //	#pragma mark -
835 
836 
837 static status_t
838 tarfs_get_file_system(boot::Partition* partition, ::Directory** _root)
839 {
840 	TarFS::Volume* volume = new(nothrow) TarFS::Volume;
841 	if (volume == NULL)
842 		return B_NO_MEMORY;
843 
844 	if (volume->Init(partition) < B_OK) {
845 		TRACE(("Initializing tarfs failed\n"));
846 		delete volume;
847 		return B_ERROR;
848 	}
849 
850 	*_root = volume->Root();
851 	return B_OK;
852 }
853 
854 
855 file_system_module_info gTarFileSystemModule = {
856 	"file_systems/tarfs/v1",
857 	kPartitionTypeTarFS,
858 	NULL,	// identify_file_system
859 	tarfs_get_file_system
860 };
861 
862