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