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