xref: /haiku/src/system/boot/loader/file_systems/tarfs/tarfs.cpp (revision 4f00613311d0bd6b70fa82ce19931c41f071ea4e)
1 /*
2  * Copyright 2005, Ingo Weinhold, bonefish@cs.tu-berlin.de. All rights reserved.
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 kCompressedArchiveOffset = 192 * 1024;	// at 192 kB
37 static const size_t kTarRegionSize = 4 * 1024 * 1024;		// 4 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 class Volume : public TarFS::Directory {
130 	public:
131 		Volume();
132 		~Volume();
133 
134 		status_t			Init(boot::Partition *partition);
135 
136 		TarFS::Directory	*Root() { return this; }
137 };
138 
139 }	// namespace TarFS
140 
141 
142 static int32 sNextID = 1;
143 
144 
145 // #pragma mark -
146 
147 
148 bool
149 skip_gzip_header(z_stream *stream)
150 {
151 	uint8 *buffer = (uint8 *)stream->next_in;
152 
153 	// check magic and skip method
154 	if (buffer[0] != 0x1f || buffer[1] != 0x8b)
155 		return false;
156 
157 	uint32 offset = 3;
158 
159 	// we need the flags field to determine the length of the header
160 	int flags = buffer[offset++];
161 
162 	offset += 6;
163 
164 	if ((flags & 0x04) != 0) {
165 		// skip extra field
166 		offset += buffer[offset++] | (buffer[offset++] << 8);
167 		if (offset >= stream->avail_in)
168 			return false;
169 	}
170 	if ((flags & 0x08) != 0) {
171 		// skip original name
172 		while (buffer[offset++])
173 			;
174 	}
175 	if ((flags & 0x10) != 0) {
176 		// skip comment
177 		while (buffer[offset++])
178 			;
179 	}
180 	if ((flags & 0x02) != 0) {
181 		// skip CRC
182 		offset += 2;
183 	}
184 
185 	if (offset >= stream->avail_in)
186 		return false;
187 
188 	stream->next_in += offset;
189 	stream->avail_in -= offset;
190 	return true;
191 }
192 
193 
194 // #pragma mark -
195 
196 
197 TarFS::Entry::Entry(const char *name)
198 	:
199 	fName(name),
200 	fID(sNextID++)
201 {
202 }
203 
204 
205 // #pragma mark -
206 
207 
208 TarFS::File::File(tar_header *header, const char *name)
209 	: TarFS::Entry(name),
210 	fHeader(header)
211 {
212 	fSize = strtol(header->size, NULL, 8);
213 }
214 
215 
216 TarFS::File::~File()
217 {
218 }
219 
220 
221 ssize_t
222 TarFS::File::ReadAt(void *cookie, off_t pos, void *buffer, size_t bufferSize)
223 {
224 	TRACE(("tarfs: read at %Ld, %lu bytes, fSize = %Ld\n", pos, bufferSize, fSize));
225 
226 	if (pos < 0 || !buffer)
227 		return B_BAD_VALUE;
228 
229 	if (pos >= fSize || bufferSize == 0)
230 		return 0;
231 
232 	size_t toRead = fSize - pos;
233 	if (toRead > bufferSize)
234 		toRead = bufferSize;
235 
236 	memcpy(buffer, (char*)fHeader + BLOCK_SIZE + pos, toRead);
237 
238 	return toRead;
239 }
240 
241 
242 ssize_t
243 TarFS::File::WriteAt(void *cookie, off_t pos, const void *buffer, size_t bufferSize)
244 {
245 	return B_NOT_ALLOWED;
246 }
247 
248 
249 status_t
250 TarFS::File::GetName(char *nameBuffer, size_t bufferSize) const
251 {
252 	return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize ? B_BUFFER_OVERFLOW : B_OK;
253 }
254 
255 
256 int32
257 TarFS::File::Type() const
258 {
259 	return S_IFREG;
260 }
261 
262 
263 off_t
264 TarFS::File::Size() const
265 {
266 	return fSize;
267 }
268 
269 
270 ino_t
271 TarFS::File::Inode() const
272 {
273 	return fID;
274 }
275 
276 
277 // #pragma mark -
278 
279 TarFS::Directory::Directory(const char *name)
280 	: TarFS::Entry(name)
281 {
282 }
283 
284 
285 TarFS::Directory::~Directory()
286 {
287 	while (TarFS::Entry *entry = fEntries.Head()) {
288 		fEntries.Remove(entry);
289 		delete entry;
290 	}
291 }
292 
293 
294 status_t
295 TarFS::Directory::Open(void **_cookie, int mode)
296 {
297 	_inherited::Open(_cookie, mode);
298 
299 	EntryIterator *iterator = new EntryIterator(fEntries.GetIterator());
300 	if (iterator == NULL)
301 		return B_NO_MEMORY;
302 
303 	*_cookie = iterator;
304 
305 	return B_OK;
306 }
307 
308 
309 status_t
310 TarFS::Directory::Close(void *cookie)
311 {
312 	_inherited::Close(cookie);
313 
314 	delete (EntryIterator *)cookie;
315 	return B_OK;
316 }
317 
318 
319 status_t
320 TarFS::Directory::GetName(char *nameBuffer, size_t bufferSize) const
321 {
322 	return strlcpy(nameBuffer, Name(), bufferSize) >= bufferSize ? B_BUFFER_OVERFLOW : B_OK;
323 }
324 
325 
326 TarFS::Entry *
327 TarFS::Directory::LookupEntry(const char *name)
328 {
329 	EntryIterator iterator(fEntries.GetIterator());
330 
331 	while (iterator.HasNext()) {
332 		TarFS::Entry *entry = iterator.Next();
333 		if (strcmp(name, entry->Name()) == 0)
334 			return entry;
335 	}
336 
337 	return NULL;
338 }
339 
340 
341 ::Node *
342 TarFS::Directory::Lookup(const char *name, bool /*traverseLinks*/)
343 {
344 	if (TarFS::Entry *entry = LookupEntry(name)) {
345 		entry->ToNode()->Acquire();
346 			// our entries are not supposed to be deleted after use
347 		return entry->ToNode();
348 	}
349 
350 	return NULL;
351 }
352 
353 
354 status_t
355 TarFS::Directory::GetNextEntry(void *_cookie, char *name, size_t size)
356 {
357 	EntryIterator *iterator = (EntryIterator *)_cookie;
358 	TarFS::Entry *entry = iterator->Next();
359 
360 	if (entry != NULL) {
361 		strlcpy(name, entry->Name(), size);
362 		return B_OK;
363 	}
364 
365 	return B_ENTRY_NOT_FOUND;
366 }
367 
368 
369 status_t
370 TarFS::Directory::GetNextNode(void *_cookie, Node **_node)
371 {
372 	EntryIterator *iterator = (EntryIterator *)_cookie;
373 	TarFS::Entry *entry = iterator->Next();
374 
375 	if (entry != NULL) {
376 		*_node = entry->ToNode();
377 		return B_OK;
378 	}
379 	return B_ENTRY_NOT_FOUND;
380 }
381 
382 
383 status_t
384 TarFS::Directory::Rewind(void *_cookie)
385 {
386 	EntryIterator *iterator = (EntryIterator *)_cookie;
387 	*iterator = fEntries.GetIterator();
388 	return B_OK;
389 }
390 
391 
392 status_t
393 TarFS::Directory::AddDirectory(char *dirName, TarFS::Directory **_dir)
394 {
395 	char *subDir = strchr(dirName, '/');
396 	if (subDir) {
397 		// skip slashes
398 		while (*subDir == '/') {
399 			*subDir = '\0';
400 			subDir++;
401 		}
402 
403 		if (*subDir == '\0') {
404 			// a trailing slash
405 			subDir = NULL;
406 		}
407 	}
408 
409 	// check, whether the directory does already exist
410 	Entry *entry = LookupEntry(dirName);
411 	TarFS::Directory *dir = (entry ? entry->ToTarDirectory() : NULL);
412 	if (entry) {
413 		if (!dir)
414 			return B_ERROR;
415 	} else {
416 		// doesn't exist yet -- create it
417 		dir = new TarFS::Directory(dirName);
418 		if (!dir)
419 			return B_NO_MEMORY;
420 
421 		fEntries.Add(dir);
422 	}
423 
424 	// recursively create the subdirectories
425 	if (subDir) {
426 		status_t error = dir->AddDirectory(subDir, &dir);
427 		if (error != B_OK)
428 			return error;
429 	}
430 
431 	if (_dir)
432 		*_dir = dir;
433 
434 	return B_OK;
435 }
436 
437 
438 status_t
439 TarFS::Directory::AddFile(tar_header *header)
440 {
441 	char *leaf = strrchr(header->name, '/');
442 	char *dirName = NULL;
443 	if (leaf) {
444 		dirName = header->name;
445 		*leaf = '\0';
446 		leaf++;
447 	} else
448 		leaf = header->name;
449 
450 	// create the parent directory
451 	TarFS::Directory *dir = this;
452 	if (dirName) {
453 		status_t error = AddDirectory(dirName, &dir);
454 		if (error != B_OK)
455 			return error;
456 	}
457 
458 	// create the file
459 	TarFS::File *file = new TarFS::File(header, leaf);
460 	if (!file)
461 		return B_NO_MEMORY;
462 
463 	dir->fEntries.Add(file);
464 
465 	return B_OK;
466 }
467 
468 
469 bool
470 TarFS::Directory::IsEmpty()
471 {
472 	return fEntries.IsEmpty();
473 }
474 
475 
476 ino_t
477 TarFS::Directory::Inode() const
478 {
479 	return fID;
480 }
481 
482 
483 // #pragma mark -
484 
485 
486 TarFS::Volume::Volume()
487 	: TarFS::Directory("Boot from CD-ROM")
488 {
489 }
490 
491 
492 TarFS::Volume::~Volume()
493 {
494 }
495 
496 
497 status_t
498 TarFS::Volume::Init(boot::Partition *partition)
499 {
500 	void *cookie;
501 	status_t error = partition->Open(&cookie, O_RDONLY);
502 	if (error != B_OK)
503 		return error;
504 
505 	struct PartitionCloser {
506 		boot::Partition	*partition;
507 		void			*cookie;
508 
509 		PartitionCloser(boot::Partition *partition, void *cookie)
510 			: partition(partition),
511 			  cookie(cookie)
512 		{
513 		}
514 
515 		~PartitionCloser()
516 		{
517 			partition->Close(cookie);
518 		}
519 	} _(partition, cookie);
520 
521 	RegionDeleter regionDeleter;
522 
523 	char *out = NULL;
524 
525 	char in[2048];
526 	z_stream zStream = {
527 		(Bytef*)in,		// next in
528 		sizeof(in),		// avail in
529 		0,				// total in
530 		NULL,			// next out
531 		0,				// avail out
532 		0,				// total out
533 		0,				// msg
534 		0,				// state
535 		Z_NULL,			// zalloc
536 		Z_NULL,			// zfree
537 		Z_NULL,			// opaque
538 		0,				// data type
539 		0,				// adler
540 		0,				// reserved
541 	};
542 
543 	int status;
544 	uint32 offset = kCompressedArchiveOffset;
545 
546 	do {
547 		if (partition->ReadAt(cookie, offset, in, sizeof(in)) != sizeof(in)) {
548 			status = Z_STREAM_ERROR;
549 			break;
550 		}
551 
552 		zStream.avail_in = sizeof(in);
553 		zStream.next_in = (Bytef *)in;
554 
555 		if (offset == kCompressedArchiveOffset) {
556 			// check and skip gzip header
557 			if (!skip_gzip_header(&zStream))
558 				return B_BAD_DATA;
559 
560 			if (platform_allocate_region((void **)&out, kTarRegionSize,
561 					B_READ_AREA | B_WRITE_AREA) != B_OK) {
562 				TRACE(("tarfs: allocating region failed!\n"));
563 				return B_NO_MEMORY;
564 			}
565 
566 			regionDeleter.SetTo(out);
567 			zStream.avail_out = kTarRegionSize;
568 			zStream.next_out = (Bytef *)out;
569 
570 			status = inflateInit2(&zStream, -15);
571 			if (status != Z_OK)
572 				return B_ERROR;
573 		}
574 
575 		status = inflate(&zStream, Z_SYNC_FLUSH);
576 		offset += sizeof(in);
577 
578 		if (zStream.avail_in != 0 && status != Z_STREAM_END)
579 			dprintf("tarfs: didn't read whole block!\n");
580 	} while (status == Z_OK);
581 
582 	inflateEnd(&zStream);
583 
584 	if (status != Z_STREAM_END) {
585 		TRACE(("tarfs: inflating failed: %d!\n", status));
586 		return B_BAD_DATA;
587 	}
588 
589 	status = B_OK;
590 
591 	// parse the tar file
592 	char *block = out;
593 	int blockCount = zStream.total_out / BLOCK_SIZE;
594 	int blockIndex = 0;
595 
596 	while (blockIndex < blockCount) {
597 		// check header
598 		tar_header *header = (tar_header*)(block + blockIndex * BLOCK_SIZE);
599 		//dump_header(*header);
600 
601 		if (header->magic[0] == '\0')
602 			break;
603 
604 		if (strcmp(header->magic, kTarHeaderMagic) != 0) {
605 			if (strcmp(header->magic, kOldTarHeaderMagic) != 0) {
606 				dprintf("Bad tar header magic in block %d.\n", blockIndex);
607 				status = B_BAD_DATA;
608 				break;
609 			}
610 		}
611 
612 		off_t size = strtol(header->size, NULL, 8);
613 
614 		TRACE(("tarfs: \"%s\", %Ld bytes\n", header->name, size));
615 
616 		// TODO: this is old-style GNU tar which probably won't work with newer ones...
617 		switch (header->type) {
618 			case TAR_FILE:
619 			case TAR_FILE2:
620 				status = AddFile(header);
621 				break;
622 
623 			case TAR_DIRECTORY:
624 				status = AddDirectory(header->name, NULL);
625 				break;
626 
627 			case TAR_LONG_NAME:
628 				// this is a long file name
629 				// TODO: read long name
630 			default:
631 				// unsupported type
632 				status = B_ERROR;
633 				break;
634 		}
635 
636 		if (status != B_OK)
637 			return status;
638 
639 		// next block
640 		blockIndex += (size + 2 * BLOCK_SIZE - 1) / BLOCK_SIZE;
641 	}
642 
643 	if (status != B_OK)
644 		return status;
645 
646 	regionDeleter.Detach();
647 	return B_OK;
648 }
649 
650 
651 //	#pragma mark -
652 
653 
654 static status_t
655 tarfs_get_file_system(boot::Partition *partition, ::Directory **_root)
656 {
657 	TarFS::Volume *volume = new TarFS::Volume;
658 	if (volume == NULL)
659 		return B_NO_MEMORY;
660 
661 	if (volume->Init(partition) < B_OK) {
662 		TRACE(("Initializing tarfs failed\n"));
663 		delete volume;
664 		return B_ERROR;
665 	}
666 
667 	*_root = volume->Root();
668 	return B_OK;
669 }
670 
671 
672 file_system_module_info gTarFileSystemModule = {
673 	"file_systems/tarfs/v1",
674 	kPartitionTypeTarFS,
675 	tarfs_get_file_system
676 };
677 
678