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