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