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