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