2 * Copyright 2009-2013, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
23 #include <AutoDeleter.h>
24 #include <HashString.h>
26 #include <util/OpenHashTable.h>
28 #include <package/hpkg/BlockBufferPoolNoLock.h>
29 #include <package/hpkg/PackageContentHandler.h>
30 #include <package/hpkg/PackageDataReader.h>
31 #include <package/hpkg/PackageEntry.h>
32 #include <package/hpkg/PackageEntryAttribute.h>
33 #include <package/hpkg/PackageReader.h>
34 #include <package/hpkg/StandardErrorOutput.h>
35 #include <package/hpkg/v1/PackageContentHandler.h>
36 #include <package/hpkg/v1/PackageDataReader.h>
37 #include <package/hpkg/v1/PackageEntry.h>
38 #include <package/hpkg/v1/PackageEntryAttribute.h>
39 #include <package/hpkg/v1//PackageReader.h>
44 using BPackageKit::BHPKG::BAbstractBufferedDataReader
;
45 using BPackageKit::BHPKG::BBlockBufferPoolNoLock
;
46 using BPackageKit::BHPKG::BBufferDataReader
;
47 using BPackageKit::BHPKG::BBufferPool
;
48 using BPackageKit::BHPKG::BDataReader
;
49 using BPackageKit::BHPKG::BErrorOutput
;
50 using BPackageKit::BHPKG::BFDDataReader
;
51 using BPackageKit::BHPKG::BPackageInfoAttributeValue
;
52 using BPackageKit::BHPKG::BStandardErrorOutput
;
55 struct VersionPolicyV1
{
56 typedef BPackageKit::BHPKG::V1::BPackageContentHandler
57 PackageContentHandler
;
58 typedef BPackageKit::BHPKG::V1::BPackageData PackageData
;
59 typedef BPackageKit::BHPKG::V1::BPackageEntry PackageEntry
;
60 typedef BPackageKit::BHPKG::V1::BPackageEntryAttribute
61 PackageEntryAttribute
;
62 typedef BPackageKit::BHPKG::V1::BPackageReader PackageReader
;
63 typedef BDataReader HeapReaderBase
;
65 static inline size_t BufferSize()
67 return BPackageKit::BHPKG::V1::B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB
;
70 static inline const char* PackageInfoFileName()
72 return BPackageKit::BHPKG::V1::B_HPKG_PACKAGE_INFO_FILE_NAME
;
75 static inline uint64
PackageDataCompressedSize(const PackageData
& data
)
77 return data
.CompressedSize();
80 static inline uint64
PackageDataUncompressedSize(const PackageData
& data
)
82 return data
.UncompressedSize();
85 static inline status_t
InitReader(PackageReader
& packageReader
,
88 return packageReader
.Init(fileName
);
91 static status_t
GetHeapReader(PackageReader
& packageReader
,
92 HeapReaderBase
*& _heapReader
, bool& _mustDelete
)
94 _heapReader
= new(std::nothrow
) BFDDataReader(
95 packageReader
.PackageFileFD());
97 return _heapReader
!= NULL
? B_OK
: B_NO_MEMORY
;
100 static status_t
CreatePackageDataReader(BBufferPool
* bufferPool
,
101 HeapReaderBase
* heapReader
, const PackageData
& data
,
102 BAbstractBufferedDataReader
*& _reader
)
104 return BPackageKit::BHPKG::V1::BPackageDataReaderFactory(bufferPool
)
105 .CreatePackageDataReader(heapReader
, data
, _reader
);
109 struct VersionPolicyV2
{
110 typedef BPackageKit::BHPKG::BPackageContentHandler PackageContentHandler
;
111 typedef BPackageKit::BHPKG::BPackageData PackageData
;
112 typedef BPackageKit::BHPKG::BPackageEntry PackageEntry
;
113 typedef BPackageKit::BHPKG::BPackageEntryAttribute PackageEntryAttribute
;
114 typedef BPackageKit::BHPKG::BPackageReader PackageReader
;
115 typedef BAbstractBufferedDataReader HeapReaderBase
;
117 static inline size_t BufferSize()
122 static inline const char* PackageInfoFileName()
124 return BPackageKit::BHPKG::B_HPKG_PACKAGE_INFO_FILE_NAME
;
127 static inline uint64
PackageDataCompressedSize(const PackageData
& data
)
132 static inline uint64
PackageDataUncompressedSize(const PackageData
& data
)
137 static inline status_t
InitReader(PackageReader
& packageReader
,
138 const char* fileName
)
140 return packageReader
.Init(fileName
,
142 ::B_HPKG_READER_DONT_PRINT_VERSION_MISMATCH_MESSAGE
);
145 static status_t
GetHeapReader(PackageReader
& packageReader
,
146 HeapReaderBase
*& _heapReader
, bool& _mustDelete
)
148 _heapReader
= packageReader
.HeapReader();
153 static status_t
CreatePackageDataReader(BBufferPool
* bufferPool
,
154 HeapReaderBase
* heapReader
, const PackageData
& data
,
155 BAbstractBufferedDataReader
*& _reader
)
157 return BPackageKit::BHPKG::BPackageDataReaderFactory()
158 .CreatePackageDataReader(heapReader
, data
, _reader
);
164 Entry(Entry
* parent
, char* name
, bool implicit
)
182 return fChildren
.Init();
185 static status_t
Create(Entry
* parent
, const char* name
, bool implicit
,
188 if (parent
!= NULL
) {
189 Entry
* entryInParent
= parent
->FindChild(name
);
190 if (entryInParent
!= NULL
) {
191 _entry
= entryInParent
;
196 char* clonedName
= strdup(name
);
197 if (clonedName
== NULL
)
200 Entry
* entry
= new(std::nothrow
) Entry(parent
, clonedName
, implicit
);
206 status_t error
= entry
->Init();
213 parent
->fChildren
.Insert(entry
);
219 Entry
* Parent() const
224 const char* Name() const
229 bool IsImplicit() const
236 // remove all children and set this entry non-implicit
251 Entry
* FindChild(const char* name
) const
253 return fChildren
.Lookup(name
);
257 struct ChildHashDefinition
{
258 typedef const char* KeyType
;
259 typedef Entry ValueType
;
261 size_t HashKey(const char* key
) const
263 return string_hash(key
);
266 size_t Hash(const Entry
* value
) const
268 return HashKey(value
->Name());
271 bool Compare(const char* key
, const Entry
* value
) const
273 return strcmp(value
->Name(), key
) == 0;
276 Entry
*& GetLink(Entry
* value
) const
278 return value
->fHashTableNext
;
282 typedef BOpenHashTable
<ChildHashDefinition
> ChildTable
;
285 void _DeleteChildren()
287 Entry
* child
= fChildren
.Clear(true);
288 while (child
!= NULL
) {
289 Entry
* next
= child
->fHashTableNext
;
296 Entry
* fHashTableNext
;
303 ChildTable fChildren
;
307 template<typename VersionPolicy
>
308 struct PackageContentExtractHandler
: VersionPolicy::PackageContentHandler
{
309 PackageContentExtractHandler(BBufferPool
* bufferPool
,
310 typename
VersionPolicy::HeapReaderBase
* heapReader
)
312 fBufferPool(bufferPool
),
313 fPackageFileReader(heapReader
),
316 fRootFilterEntry(NULL
, NULL
, true),
317 fBaseDirectory(AT_FDCWD
),
319 fErrorOccurred(false)
323 ~PackageContentExtractHandler()
330 status_t error
= fRootFilterEntry
.Init();
334 fDataBufferSize
= 64 * 1024;
335 fDataBuffer
= malloc(fDataBufferSize
);
336 if (fDataBuffer
== NULL
)
342 void SetBaseDirectory(int fd
)
347 void SetPackageInfoFile(const char* infoFileName
)
349 fInfoFileName
= infoFileName
;
354 fRootFilterEntry
.SetExplicit();
357 status_t
AddFilterEntry(const char* fileName
)
359 // add all components of the path
360 Entry
* entry
= &fRootFilterEntry
;
361 while (*fileName
!= 0) {
362 const char* nextSlash
= strchr(fileName
, '/');
363 // no slash, just add the file name
364 if (nextSlash
== NULL
) {
365 return _AddFilterEntry(entry
, fileName
, strlen(fileName
),
369 // find the start of the next component, skipping slashes
370 const char* nextComponent
= nextSlash
+ 1;
371 while (*nextComponent
== '/')
374 status_t error
= _AddFilterEntry(entry
, fileName
,
375 nextSlash
- fileName
, *nextComponent
!= '\0', entry
);
379 fileName
= nextComponent
;
385 Entry
* FindFilterEntry(const char* fileName
)
387 // add all components of the path
388 Entry
* entry
= &fRootFilterEntry
;
389 while (entry
!= NULL
&& *fileName
!= 0) {
390 const char* nextSlash
= strchr(fileName
, '/');
391 // no slash, just add the file name
392 if (nextSlash
== NULL
)
393 return entry
->FindChild(fileName
);
395 // find the start of the next component, skipping slashes
396 const char* nextComponent
= nextSlash
+ 1;
397 while (*nextComponent
== '/')
400 BString
componentName(fileName
, nextSlash
- fileName
);
401 entry
= entry
->FindChild(componentName
);
403 fileName
= nextComponent
;
409 virtual status_t
HandleEntry(typename
VersionPolicy::PackageEntry
* entry
)
412 Token
* token
= new(std::nothrow
) Token
;
415 ObjectDeleter
<Token
> tokenDeleter(token
);
417 // check whether this entry shall be ignored or is implicit
418 Entry
* parentFilterEntry
;
420 if (entry
->Parent() != NULL
) {
421 Token
* parentToken
= (Token
*)entry
->Parent()->UserToken();
422 if (parentToken
== NULL
) {
423 // parent is ignored, so ignore this entry, too
427 parentFilterEntry
= parentToken
->filterEntry
;
428 implicit
= parentToken
->implicit
;
430 parentFilterEntry
= &fRootFilterEntry
;
431 implicit
= fRootFilterEntry
.IsImplicit();
434 Entry
* filterEntry
= parentFilterEntry
!= NULL
435 ? parentFilterEntry
->FindChild(entry
->Name()) : NULL
;
437 if (implicit
&& filterEntry
== NULL
) {
438 // parent is implicit and the filter doesn't include this entry
443 // If the entry is in the filter, get its implicit flag.
444 if (filterEntry
!= NULL
) {
445 implicit
= filterEntry
->IsImplicit();
446 filterEntry
->SetSeen();
449 token
->filterEntry
= filterEntry
;
450 token
->implicit
= implicit
;
452 // get parent FD and the entry name
454 const char* entryName
;
455 _GetParentFDAndEntryName(entry
, parentFD
, entryName
);
457 // check whether something is in the way
459 bool entryExists
= fstatat(parentFD
, entryName
, &st
,
460 AT_SYMLINK_NOFOLLOW
) == 0;
462 if (S_ISREG(entry
->Mode()) || S_ISLNK(entry
->Mode())) {
463 // If the entry in the way is a regular file or a symlink,
464 // remove it, otherwise fail.
465 if (!S_ISREG(st
.st_mode
) && !S_ISLNK(st
.st_mode
)) {
466 fprintf(stderr
, "Error: Can't create entry \"%s\", since "
467 "something is in the way\n",
468 _EntryPath(entry
).String());
469 return B_FILE_EXISTS
;
472 if (unlinkat(parentFD
, entryName
, 0) != 0) {
473 fprintf(stderr
, "Error: Failed to unlink entry \"%s\": %s\n",
474 _EntryPath(entry
).String(), strerror(errno
));
479 } else if (S_ISDIR(entry
->Mode())) {
480 // If the entry in the way is a directory, merge, otherwise
482 if (!S_ISDIR(st
.st_mode
)) {
483 fprintf(stderr
, "Error: Can't create directory \"%s\", "
484 "since something is in the way\n",
485 _EntryPath(entry
).String());
486 return B_FILE_EXISTS
;
493 if (S_ISREG(entry
->Mode())) {
495 fprintf(stderr
, "Error: File \"%s\" was specified as a "
496 "path directory component.\n", _EntryPath(entry
).String());
501 fd
= openat(parentFD
, entryName
, O_RDWR
| O_CREAT
| O_EXCL
,
503 // Note: We use read+write user permissions now -- so write
504 // operations (e.g. attributes) won't fail, but set them to the
505 // desired ones in HandleEntryDone().
507 fprintf(stderr
, "Error: Failed to create file \"%s\": %s\n",
508 _EntryPath(entry
).String(), strerror(errno
));
513 status_t error
= _ExtractFileData(fPackageFileReader
, entry
->Data(),
517 } else if (S_ISLNK(entry
->Mode())) {
519 fprintf(stderr
, "Error: Symlink \"%s\" was specified as a "
520 "path directory component.\n", _EntryPath(entry
).String());
524 // create the symlink
525 const char* symlinkPath
= entry
->SymlinkPath();
526 if (symlinkat(symlinkPath
!= NULL
? symlinkPath
: "", parentFD
,
528 fprintf(stderr
, "Error: Failed to create symlink \"%s\": %s\n",
529 _EntryPath(entry
).String(), strerror(errno
));
532 // TODO: Set symlink permissions?
533 } else if (S_ISDIR(entry
->Mode())) {
534 // create the directory, if necessary
536 && mkdirat(parentFD
, entryName
, S_IRWXU
) != 0) {
537 // Note: We use read+write+exec user permissions now -- so write
538 // operations (e.g. attributes) won't fail, but set them to the
539 // desired ones in HandleEntryDone().
540 fprintf(stderr
, "Error: Failed to create directory \"%s\": "
541 "%s\n", _EntryPath(entry
).String(), strerror(errno
));
545 fprintf(stderr
, "Error: Invalid file type for entry \"%s\"\n",
546 _EntryPath(entry
).String());
550 // If not done yet (symlink, dir), open the node -- we need the FD.
551 if (fd
< 0 && (!implicit
|| S_ISDIR(entry
->Mode()))) {
552 fd
= openat(parentFD
, entryName
, O_RDONLY
| O_NOTRAVERSE
);
554 fprintf(stderr
, "Error: Failed to open entry \"%s\": %s\n",
555 _EntryPath(entry
).String(), strerror(errno
));
561 // set the file times
562 if (!entryExists
&& !implicit
) {
563 timespec times
[2] = {entry
->AccessTime(), entry
->ModifiedTime()};
570 entry
->SetUserToken(tokenDeleter
.Detach());
574 virtual status_t
HandleEntryAttribute(
575 typename
VersionPolicy::PackageEntry
* entry
,
576 typename
VersionPolicy::PackageEntryAttribute
* attribute
)
578 // don't write attributes of ignored or implicit entries
579 Token
* token
= (Token
*)entry
->UserToken();
580 if (token
== NULL
|| token
->implicit
)
583 int entryFD
= token
->fd
;
585 // create the attribute
586 int fd
= fs_fopen_attr(entryFD
, attribute
->Name(), attribute
->Type(),
587 O_WRONLY
| O_CREAT
| O_TRUNC
);
590 const char* entryName
;
591 _GetParentFDAndEntryName(entry
, parentFD
, entryName
);
593 fprintf(stderr
, "Error: Failed to create attribute \"%s\" of "
594 "file \"%s\": %s\n", attribute
->Name(),
595 _EntryPath(entry
).String(), strerror(errno
));
600 status_t error
= _ExtractFileData(fPackageFileReader
, attribute
->Data(),
608 virtual status_t
HandleEntryDone(
609 typename
VersionPolicy::PackageEntry
* entry
)
611 Token
* token
= (Token
*)entry
->UserToken();
613 // set the node permissions for non-symlinks
614 if (token
!= NULL
&& !S_ISLNK(entry
->Mode())) {
615 // get parent FD and entry name
617 const char* entryName
;
618 _GetParentFDAndEntryName(entry
, parentFD
, entryName
);
620 if (fchmodat(parentFD
, entryName
, entry
->Mode() & ALLPERMS
,
621 /*AT_SYMLINK_NOFOLLOW*/0) != 0) {
622 fprintf(stderr
, "Warning: Failed to set permissions of file "
623 "\"%s\": %s\n", _EntryPath(entry
).String(),
630 entry
->SetUserToken(NULL
);
636 virtual status_t
HandlePackageAttribute(
637 const BPackageInfoAttributeValue
& value
)
642 virtual void HandleErrorOccurred()
644 fErrorOccurred
= true;
669 status_t
_AddFilterEntry(Entry
* parentEntry
, const char* _name
,
670 size_t nameLength
, bool implicit
, Entry
*& _entry
)
672 BString
name(_name
, nameLength
);
676 return Entry::Create(parentEntry
, name
.String(), implicit
, _entry
);
679 void _GetParentFDAndEntryName(typename
VersionPolicy::PackageEntry
* entry
,
680 int& _parentFD
, const char*& _entryName
)
682 _entryName
= entry
->Name();
684 if (fInfoFileName
!= NULL
685 && strcmp(_entryName
, VersionPolicy::PackageInfoFileName()) == 0) {
686 _parentFD
= AT_FDCWD
;
687 _entryName
= fInfoFileName
;
689 _parentFD
= entry
->Parent() != NULL
690 ? ((Token
*)entry
->Parent()->UserToken())->fd
: fBaseDirectory
;
694 BString
_EntryPath(const typename
VersionPolicy::PackageEntry
* entry
)
698 if (const typename
VersionPolicy::PackageEntry
* parent
700 path
= _EntryPath(parent
);
704 path
<< entry
->Name();
708 status_t
_ExtractFileData(
709 typename
VersionPolicy::HeapReaderBase
* dataReader
,
710 const typename
VersionPolicy::PackageData
& data
, int fd
)
712 // create a PackageDataReader
713 BAbstractBufferedDataReader
* reader
;
714 status_t error
= VersionPolicy::CreatePackageDataReader(fBufferPool
,
715 dataReader
, data
, reader
);
718 ObjectDeleter
<BAbstractBufferedDataReader
> readerDeleter(reader
);
721 off_t bytesRemaining
= VersionPolicy::PackageDataUncompressedSize(data
);
723 while (bytesRemaining
> 0) {
725 size_t toCopy
= std::min((off_t
)fDataBufferSize
, bytesRemaining
);
726 error
= reader
->ReadData(offset
, fDataBuffer
, toCopy
);
728 fprintf(stderr
, "Error: Failed to read data: %s\n",
734 ssize_t bytesWritten
= write_pos(fd
, offset
, fDataBuffer
, toCopy
);
735 if (bytesWritten
< 0) {
736 fprintf(stderr
, "Error: Failed to write data: %s\n",
740 if ((size_t)bytesWritten
!= toCopy
) {
741 fprintf(stderr
, "Error: Failed to write all data (%zd of "
742 "%zu)\n", bytesWritten
, toCopy
);
747 bytesRemaining
-= toCopy
;
754 BBufferPool
* fBufferPool
;
755 typename
VersionPolicy::HeapReaderBase
* fPackageFileReader
;
757 size_t fDataBufferSize
;
758 Entry fRootFilterEntry
;
760 const char* fInfoFileName
;
765 template<typename VersionPolicy
>
767 do_extract(const char* packageFileName
, const char* changeToDirectory
,
768 const char* packageInfoFileName
, const char* const* explicitEntries
,
769 int explicitEntryCount
, bool ignoreVersionError
)
772 BStandardErrorOutput errorOutput
;
773 BBlockBufferPoolNoLock
bufferPool(VersionPolicy::BufferSize(), 2);
774 if (bufferPool
.Init() != B_OK
) {
775 errorOutput
.PrintError("Error: Out of memory!\n");
779 typename
VersionPolicy::PackageReader
packageReader(&errorOutput
);
780 status_t error
= VersionPolicy::InitReader(packageReader
, packageFileName
);
782 if (ignoreVersionError
&& error
== B_MISMATCHED_VALUES
)
787 typename
VersionPolicy::HeapReaderBase
* heapReader
;
788 bool mustDeleteHeapReader
;
789 error
= VersionPolicy::GetHeapReader(packageReader
, heapReader
,
790 mustDeleteHeapReader
);
792 fprintf(stderr
, "Error: Failed to create heap reader: \"%s\"\n",
796 ObjectDeleter
<BDataReader
> heapReaderDeleter(
797 mustDeleteHeapReader
? heapReader
: NULL
);
799 PackageContentExtractHandler
<VersionPolicy
> handler(&bufferPool
,
801 error
= handler
.Init();
805 // If entries to extract have been specified explicitly, add those to the
807 if (explicitEntryCount
> 0) {
808 for (int i
= 0; i
< explicitEntryCount
; i
++) {
809 const char* entryName
= explicitEntries
[i
];
810 if (entryName
[0] == '\0' || entryName
[0] == '/') {
811 fprintf(stderr
, "Error: Invalid entry name: \"%s\"\n",
815 if (handler
.AddFilterEntry(entryName
) != B_OK
)
819 handler
.SetExtractAll();
821 // get the target directory, if requested
822 if (changeToDirectory
!= NULL
) {
823 int currentDirFD
= open(changeToDirectory
, O_RDONLY
);
824 if (currentDirFD
< 0) {
825 fprintf(stderr
, "Error: Failed to change the current working "
826 "directory to \"%s\": %s\n", changeToDirectory
,
831 handler
.SetBaseDirectory(currentDirFD
);
834 // If a package info file name is given, set it.
835 if (packageInfoFileName
!= NULL
)
836 handler
.SetPackageInfoFile(packageInfoFileName
);
839 error
= packageReader
.ParseContent(&handler
);
843 // check whether all explicitly specified entries have been extracted
844 if (explicitEntryCount
> 0) {
845 for (int i
= 0; i
< explicitEntryCount
; i
++) {
846 if (Entry
* entry
= handler
.FindFilterEntry(explicitEntries
[i
])) {
847 if (!entry
->Seen()) {
848 fprintf(stderr
, "Warning: Entry \"%s\" not found.\n",
860 command_extract(int argc
, const char* const* argv
)
862 const char* changeToDirectory
= NULL
;
863 const char* packageInfoFileName
= NULL
;
866 static struct option sLongOptions
[] = {
867 { "help", no_argument
, 0, 'h' },
871 opterr
= 0; // don't print errors
872 int c
= getopt_long(argc
, (char**)argv
, "+C:hi:", sLongOptions
, NULL
);
878 changeToDirectory
= optarg
;
882 print_usage_and_exit(false);
886 packageInfoFileName
= optarg
;
890 print_usage_and_exit(true);
895 // At least one argument should remain -- the package file name. Any further
896 // arguments are the names of the entries to extract.
897 if (optind
+ 1 > argc
)
898 print_usage_and_exit(true);
900 const char* packageFileName
= argv
[optind
++];
901 const char* const* explicitEntries
= argv
+ optind
;
902 int explicitEntryCount
= argc
- optind
;
903 do_extract
<VersionPolicyV2
>(packageFileName
, changeToDirectory
,
904 packageInfoFileName
, explicitEntries
, explicitEntryCount
, true);
905 do_extract
<VersionPolicyV1
>(packageFileName
, changeToDirectory
,
906 packageInfoFileName
, explicitEntries
, explicitEntryCount
, false);