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