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 = 9 * 1024 * 1024;	// 9 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
55typedef BPrivate::AutoDeleter<void, RegionDelete> RegionDeleter;
56
57class Directory;
58
59class Entry : public DoublyLinkedListLinkImpl<Entry> {
60public:
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
68protected:
69			const char*			fName;
70			int32				fID;
71};
72
73
74typedef DoublyLinkedList<TarFS::Entry>	EntryList;
75typedef EntryList::Iterator	EntryIterator;
76
77
78class File : public ::Node, public Entry {
79public:
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
97private:
98			tar_header*			fHeader;
99			off_t				fSize;
100};
101
102
103class Directory : public ::Directory, public Entry {
104public:
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
132private:
133			typedef ::Directory _inherited;
134
135			Directory*			fParent;
136			EntryList			fEntries;
137};
138
139
140class Symlink : public ::Node, public Entry {
141public:
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
163private:
164			tar_header*			fHeader;
165			size_t				fSize;
166};
167
168
169class Volume : public TarFS::Directory {
170public:
171								Volume();
172								~Volume();
173
174			status_t			Init(boot::Partition* partition);
175
176			TarFS::Directory*	Root() { return this; }
177
178private:
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
188static int32 sNextID = 1;
189
190
191// #pragma mark -
192
193
194bool
195skip_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
241TarFS::Entry::Entry(const char* name)
242	:
243	fName(name),
244	fID(sNextID++)
245{
246}
247
248
249// #pragma mark -
250
251
252TarFS::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
260TarFS::File::~File()
261{
262}
263
264
265ssize_t
266TarFS::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
287ssize_t
288TarFS::File::WriteAt(void* cookie, off_t pos, const void* buffer,
289	size_t bufferSize)
290{
291	return B_NOT_ALLOWED;
292}
293
294
295status_t
296TarFS::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
303int32
304TarFS::File::Type() const
305{
306	return S_IFREG;
307}
308
309
310off_t
311TarFS::File::Size() const
312{
313	return fSize;
314}
315
316
317ino_t
318TarFS::File::Inode() const
319{
320	return fID;
321}
322
323
324// #pragma mark -
325
326TarFS::Directory::Directory(Directory* parent, const char* name)
327	:
328	TarFS::Entry(name),
329	fParent(parent)
330{
331}
332
333
334TarFS::Directory::~Directory()
335{
336	while (TarFS::Entry* entry = fEntries.Head()) {
337		fEntries.Remove(entry);
338		delete entry;
339	}
340}
341
342
343status_t
344TarFS::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
359status_t
360TarFS::Directory::Close(void* cookie)
361{
362	_inherited::Close(cookie);
363
364	delete (EntryIterator*)cookie;
365	return B_OK;
366}
367
368
369status_t
370TarFS::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
377TarFS::Entry*
378TarFS::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*
398TarFS::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
412status_t
413TarFS::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
427status_t
428TarFS::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
441status_t
442TarFS::Directory::Rewind(void* _cookie)
443{
444	EntryIterator* iterator = (EntryIterator*)_cookie;
445	*iterator = fEntries.GetIterator();
446	return B_OK;
447}
448
449
450status_t
451TarFS::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
496status_t
497TarFS::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
534bool
535TarFS::Directory::IsEmpty()
536{
537	return fEntries.IsEmpty();
538}
539
540
541ino_t
542TarFS::Directory::Inode() const
543{
544	return fID;
545}
546
547
548// #pragma mark -
549
550
551TarFS::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
561TarFS::Symlink::~Symlink()
562{
563}
564
565
566ssize_t
567TarFS::Symlink::ReadAt(void* cookie, off_t pos, void* buffer, size_t bufferSize)
568{
569	return B_NOT_ALLOWED;
570}
571
572
573ssize_t
574TarFS::Symlink::WriteAt(void* cookie, off_t pos, const void* buffer,
575	size_t bufferSize)
576{
577	return B_NOT_ALLOWED;
578}
579
580
581status_t
582TarFS::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
595status_t
596TarFS::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
603int32
604TarFS::Symlink::Type() const
605{
606	return S_IFLNK;
607}
608
609
610off_t
611TarFS::Symlink::Size() const
612{
613	return fSize;
614}
615
616
617ino_t
618TarFS::Symlink::Inode() const
619{
620	return fID;
621}
622
623
624// #pragma mark -
625
626
627TarFS::Volume::Volume()
628	:
629	TarFS::Directory(this, "Boot from CD-ROM")
630{
631}
632
633
634TarFS::Volume::~Volume()
635{
636}
637
638
639status_t
640TarFS::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
740status_t
741TarFS::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
829static status_t
830tarfs_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
847file_system_module_info gTarFileSystemModule = {
848	"file_systems/tarfs/v1",
849	kPartitionTypeTarFS,
850	NULL,	// identify_file_system
851	tarfs_get_file_system
852};
853
854