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