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
36 static const uint32 kFloppyArchiveOffset = BOOT_ARCHIVE_IMAGE_OFFSET * 1024;
37 // defined at build time, see build/jam/BuildSetup
38 static const size_t kTarRegionSize = 9 * 1024 * 1024; // 9 MB
39
40
41 using std::nothrow;
42
43
44 namespace TarFS {
45
46
47 struct RegionDelete {
operator ()TarFS::RegionDelete48 inline void operator()(void* memory)
49 {
50 if (memory != NULL)
51 platform_free_region(memory, kTarRegionSize);
52 }
53 };
54
55 typedef BPrivate::AutoDeleter<void, RegionDelete> RegionDeleter;
56
57 class Directory;
58
59 class Entry : public DoublyLinkedListLinkImpl<Entry> {
60 public:
61 Entry(const char* name);
~Entry()62 virtual ~Entry() {}
63
Name() const64 const char* Name() const { return fName; }
65 virtual ::Node* ToNode() = 0;
ToTarDirectory()66 virtual TarFS::Directory* ToTarDirectory() { return NULL; }
67
68 protected:
69 const char* fName;
70 int32 fID;
71 };
72
73
74 typedef DoublyLinkedList<TarFS::Entry> EntryList;
75 typedef EntryList::Iterator EntryIterator;
76
77
78 class File : public ::Node, public Entry {
79 public:
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
ToNode()95 virtual ::Node* ToNode() { return this; }
96
97 private:
98 tar_header* fHeader;
99 off_t fSize;
100 };
101
102
103 class Directory : public ::Directory, public Entry {
104 public:
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
ToNode()125 virtual ::Node* ToNode() { return this; };
ToTarDirectory()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
132 private:
133 typedef ::Directory _inherited;
134
135 Directory* fParent;
136 EntryList fEntries;
137 };
138
139
140 class Symlink : public ::Node, public Entry {
141 public:
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
LinkPath() const159 const char* LinkPath() const { return fHeader->linkname; }
160
ToNode()161 virtual ::Node* ToNode() { return this; }
162
163 private:
164 tar_header* fHeader;
165 size_t fSize;
166 };
167
168
169 class Volume : public TarFS::Directory {
170 public:
171 Volume();
172 ~Volume();
173
174 status_t Init(boot::Partition* partition);
175
Root()176 TarFS::Directory* Root() { return this; }
177
178 private:
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
188 static int32 sNextID = 1;
189
190
191 // #pragma mark -
192
193
194 bool
skip_gzip_header(z_stream * stream)195 skip_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
Entry(const char * name)241 TarFS::Entry::Entry(const char* name)
242 :
243 fName(name),
244 fID(sNextID++)
245 {
246 }
247
248
249 // #pragma mark -
250
251
File(tar_header * header,const char * name)252 TarFS::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
~File()260 TarFS::File::~File()
261 {
262 }
263
264
265 ssize_t
ReadAt(void * cookie,off_t pos,void * buffer,size_t bufferSize)266 TarFS::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
287 ssize_t
WriteAt(void * cookie,off_t pos,const void * buffer,size_t bufferSize)288 TarFS::File::WriteAt(void* cookie, off_t pos, const void* buffer,
289 size_t bufferSize)
290 {
291 return B_NOT_ALLOWED;
292 }
293
294
295 status_t
GetName(char * nameBuffer,size_t bufferSize) const296 TarFS::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
303 int32
Type() const304 TarFS::File::Type() const
305 {
306 return S_IFREG;
307 }
308
309
310 off_t
Size() const311 TarFS::File::Size() const
312 {
313 return fSize;
314 }
315
316
317 ino_t
Inode() const318 TarFS::File::Inode() const
319 {
320 return fID;
321 }
322
323
324 // #pragma mark -
325
Directory(Directory * parent,const char * name)326 TarFS::Directory::Directory(Directory* parent, const char* name)
327 :
328 TarFS::Entry(name),
329 fParent(parent)
330 {
331 }
332
333
~Directory()334 TarFS::Directory::~Directory()
335 {
336 while (TarFS::Entry* entry = fEntries.Head()) {
337 fEntries.Remove(entry);
338 delete entry;
339 }
340 }
341
342
343 status_t
Open(void ** _cookie,int mode)344 TarFS::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
359 status_t
Close(void * cookie)360 TarFS::Directory::Close(void* cookie)
361 {
362 _inherited::Close(cookie);
363
364 delete (EntryIterator*)cookie;
365 return B_OK;
366 }
367
368
369 status_t
GetName(char * nameBuffer,size_t bufferSize) const370 TarFS::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
377 TarFS::Entry*
LookupEntry(const char * name)378 TarFS::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*
LookupDontTraverse(const char * name)398 TarFS::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
412 status_t
GetNextEntry(void * _cookie,char * name,size_t size)413 TarFS::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
427 status_t
GetNextNode(void * _cookie,Node ** _node)428 TarFS::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
441 status_t
Rewind(void * _cookie)442 TarFS::Directory::Rewind(void* _cookie)
443 {
444 EntryIterator* iterator = (EntryIterator*)_cookie;
445 *iterator = fEntries.GetIterator();
446 return B_OK;
447 }
448
449
450 status_t
AddDirectory(char * dirName,TarFS::Directory ** _dir)451 TarFS::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
496 status_t
AddFile(tar_header * header)497 TarFS::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
534 bool
IsEmpty()535 TarFS::Directory::IsEmpty()
536 {
537 return fEntries.IsEmpty();
538 }
539
540
541 ino_t
Inode() const542 TarFS::Directory::Inode() const
543 {
544 return fID;
545 }
546
547
548 // #pragma mark -
549
550
Symlink(tar_header * header,const char * name)551 TarFS::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
~Symlink()561 TarFS::Symlink::~Symlink()
562 {
563 }
564
565
566 ssize_t
ReadAt(void * cookie,off_t pos,void * buffer,size_t bufferSize)567 TarFS::Symlink::ReadAt(void* cookie, off_t pos, void* buffer, size_t bufferSize)
568 {
569 return B_NOT_ALLOWED;
570 }
571
572
573 ssize_t
WriteAt(void * cookie,off_t pos,const void * buffer,size_t bufferSize)574 TarFS::Symlink::WriteAt(void* cookie, off_t pos, const void* buffer,
575 size_t bufferSize)
576 {
577 return B_NOT_ALLOWED;
578 }
579
580
581 status_t
ReadLink(char * buffer,size_t bufferSize)582 TarFS::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
595 status_t
GetName(char * nameBuffer,size_t bufferSize) const596 TarFS::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
603 int32
Type() const604 TarFS::Symlink::Type() const
605 {
606 return S_IFLNK;
607 }
608
609
610 off_t
Size() const611 TarFS::Symlink::Size() const
612 {
613 return fSize;
614 }
615
616
617 ino_t
Inode() const618 TarFS::Symlink::Inode() const
619 {
620 return fID;
621 }
622
623
624 // #pragma mark -
625
626
Volume()627 TarFS::Volume::Volume()
628 :
629 TarFS::Directory(this, "Boot from CD-ROM")
630 {
631 }
632
633
~Volume()634 TarFS::Volume::~Volume()
635 {
636 }
637
638
639 status_t
Init(boot::Partition * partition)640 TarFS::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
740 status_t
_Inflate(boot::Partition * partition,void * cookie,off_t offset,RegionDeleter & regionDeleter,size_t * inflatedBytes)741 TarFS::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
829 static status_t
tarfs_get_file_system(boot::Partition * partition,::Directory ** _root)830 tarfs_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
847 file_system_module_info gTarFileSystemModule = {
848 "file_systems/tarfs/v1",
849 kPartitionTypeTarFS,
850 NULL, // identify_file_system
851 tarfs_get_file_system
852 };
853
854