/* * Copyright 2009-2013, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "package.h" using BPackageKit::BHPKG::BAbstractBufferedDataReader; using BPackageKit::BHPKG::BBlockBufferPoolNoLock; using BPackageKit::BHPKG::BBufferDataReader; using BPackageKit::BHPKG::BBufferPool; using BPackageKit::BHPKG::BDataReader; using BPackageKit::BHPKG::BErrorOutput; using BPackageKit::BHPKG::BFDDataReader; using BPackageKit::BHPKG::BPackageInfoAttributeValue; using BPackageKit::BHPKG::BStandardErrorOutput; struct VersionPolicyV1 { typedef BPackageKit::BHPKG::V1::BPackageContentHandler PackageContentHandler; typedef BPackageKit::BHPKG::V1::BPackageData PackageData; typedef BPackageKit::BHPKG::V1::BPackageEntry PackageEntry; typedef BPackageKit::BHPKG::V1::BPackageEntryAttribute PackageEntryAttribute; typedef BPackageKit::BHPKG::V1::BPackageReader PackageReader; typedef BDataReader HeapReaderBase; static inline size_t BufferSize() { return BPackageKit::BHPKG::V1::B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB; } static inline const char* PackageInfoFileName() { return BPackageKit::BHPKG::V1::B_HPKG_PACKAGE_INFO_FILE_NAME; } static inline uint64 PackageDataCompressedSize(const PackageData& data) { return data.CompressedSize(); } static inline uint64 PackageDataUncompressedSize(const PackageData& data) { return data.UncompressedSize(); } static inline status_t InitReader(PackageReader& packageReader, const char* fileName) { return packageReader.Init(fileName); } static status_t GetHeapReader(PackageReader& packageReader, HeapReaderBase*& _heapReader, bool& _mustDelete) { _heapReader = new(std::nothrow) BFDDataReader( packageReader.PackageFileFD()); _mustDelete = false; return _heapReader != NULL ? B_OK : B_NO_MEMORY; } static status_t CreatePackageDataReader(BBufferPool* bufferPool, HeapReaderBase* heapReader, const PackageData& data, BAbstractBufferedDataReader*& _reader) { return BPackageKit::BHPKG::V1::BPackageDataReaderFactory(bufferPool) .CreatePackageDataReader(heapReader, data, _reader); } }; struct VersionPolicyV2 { typedef BPackageKit::BHPKG::BPackageContentHandler PackageContentHandler; typedef BPackageKit::BHPKG::BPackageData PackageData; typedef BPackageKit::BHPKG::BPackageEntry PackageEntry; typedef BPackageKit::BHPKG::BPackageEntryAttribute PackageEntryAttribute; typedef BPackageKit::BHPKG::BPackageReader PackageReader; typedef BAbstractBufferedDataReader HeapReaderBase; static inline size_t BufferSize() { return 64 * 1024; } static inline const char* PackageInfoFileName() { return BPackageKit::BHPKG::B_HPKG_PACKAGE_INFO_FILE_NAME; } static inline uint64 PackageDataCompressedSize(const PackageData& data) { return data.Size(); } static inline uint64 PackageDataUncompressedSize(const PackageData& data) { return data.Size(); } static inline status_t InitReader(PackageReader& packageReader, const char* fileName) { return packageReader.Init(fileName, BPackageKit::BHPKG ::B_HPKG_READER_DONT_PRINT_VERSION_MISMATCH_MESSAGE); } static status_t GetHeapReader(PackageReader& packageReader, HeapReaderBase*& _heapReader, bool& _mustDelete) { _heapReader = packageReader.HeapReader(); _mustDelete = false; return B_OK; } static status_t CreatePackageDataReader(BBufferPool* bufferPool, HeapReaderBase* heapReader, const PackageData& data, BAbstractBufferedDataReader*& _reader) { return BPackageKit::BHPKG::BPackageDataReaderFactory() .CreatePackageDataReader(heapReader, data, _reader); } }; struct Entry { Entry(Entry* parent, char* name, bool implicit) : fParent(parent), fName(name), fImplicit(implicit), fSeen(false) { } ~Entry() { _DeleteChildren(); free(fName); } status_t Init() { return fChildren.Init(); } static status_t Create(Entry* parent, const char* name, bool implicit, Entry*& _entry) { if (parent != NULL) { Entry* entryInParent = parent->FindChild(name); if (entryInParent != NULL) { _entry = entryInParent; return B_OK; } } char* clonedName = strdup(name); if (clonedName == NULL) return B_NO_MEMORY; Entry* entry = new(std::nothrow) Entry(parent, clonedName, implicit); if (entry == NULL) { free(clonedName); return B_NO_MEMORY; } status_t error = entry->Init(); if (error != B_OK) { delete entry; return error; } if (parent != NULL) parent->fChildren.Insert(entry); _entry = entry; return B_OK; } Entry* Parent() const { return fParent; } const char* Name() const { return fName; } bool IsImplicit() const { return fImplicit; } void SetExplicit() { // remove all children and set this entry non-implicit _DeleteChildren(); fImplicit = false; } void SetSeen() { fSeen = true; } bool Seen() const { return fSeen; } Entry* FindChild(const char* name) const { return fChildren.Lookup(name); } private: struct ChildHashDefinition { typedef const char* KeyType; typedef Entry ValueType; size_t HashKey(const char* key) const { return string_hash(key); } size_t Hash(const Entry* value) const { return HashKey(value->Name()); } bool Compare(const char* key, const Entry* value) const { return strcmp(value->Name(), key) == 0; } Entry*& GetLink(Entry* value) const { return value->fHashTableNext; } }; typedef BOpenHashTable ChildTable; private: void _DeleteChildren() { Entry* child = fChildren.Clear(true); while (child != NULL) { Entry* next = child->fHashTableNext; delete child; child = next; } } public: Entry* fHashTableNext; private: Entry* fParent; char* fName; bool fImplicit; bool fSeen; ChildTable fChildren; }; template struct PackageContentExtractHandler : VersionPolicy::PackageContentHandler { PackageContentExtractHandler(BBufferPool* bufferPool, typename VersionPolicy::HeapReaderBase* heapReader) : fBufferPool(bufferPool), fPackageFileReader(heapReader), fDataBuffer(NULL), fDataBufferSize(0), fRootFilterEntry(NULL, NULL, true), fBaseDirectory(AT_FDCWD), fInfoFileName(NULL), fErrorOccurred(false) { } ~PackageContentExtractHandler() { free(fDataBuffer); } status_t Init() { status_t error = fRootFilterEntry.Init(); if (error != B_OK) return error; fDataBufferSize = 64 * 1024; fDataBuffer = malloc(fDataBufferSize); if (fDataBuffer == NULL) return B_NO_MEMORY; return B_OK; } void SetBaseDirectory(int fd) { fBaseDirectory = fd; } void SetPackageInfoFile(const char* infoFileName) { fInfoFileName = infoFileName; } void SetExtractAll() { fRootFilterEntry.SetExplicit(); } status_t AddFilterEntry(const char* fileName) { // add all components of the path Entry* entry = &fRootFilterEntry; while (*fileName != 0) { const char* nextSlash = strchr(fileName, '/'); // no slash, just add the file name if (nextSlash == NULL) { return _AddFilterEntry(entry, fileName, strlen(fileName), false, entry); } // find the start of the next component, skipping slashes const char* nextComponent = nextSlash + 1; while (*nextComponent == '/') nextComponent++; status_t error = _AddFilterEntry(entry, fileName, nextSlash - fileName, *nextComponent != '\0', entry); if (error != B_OK) return error; fileName = nextComponent; } return B_OK; } Entry* FindFilterEntry(const char* fileName) { // add all components of the path Entry* entry = &fRootFilterEntry; while (entry != NULL && *fileName != 0) { const char* nextSlash = strchr(fileName, '/'); // no slash, just add the file name if (nextSlash == NULL) return entry->FindChild(fileName); // find the start of the next component, skipping slashes const char* nextComponent = nextSlash + 1; while (*nextComponent == '/') nextComponent++; BString componentName(fileName, nextSlash - fileName); entry = entry->FindChild(componentName); fileName = nextComponent; } return entry; } virtual status_t HandleEntry(typename VersionPolicy::PackageEntry* entry) { // create a token Token* token = new(std::nothrow) Token; if (token == NULL) return B_NO_MEMORY; ObjectDeleter tokenDeleter(token); // check whether this entry shall be ignored or is implicit Entry* parentFilterEntry; bool implicit; if (entry->Parent() != NULL) { Token* parentToken = (Token*)entry->Parent()->UserToken(); if (parentToken == NULL) { // parent is ignored, so ignore this entry, too return B_OK; } parentFilterEntry = parentToken->filterEntry; implicit = parentToken->implicit; } else { parentFilterEntry = &fRootFilterEntry; implicit = fRootFilterEntry.IsImplicit(); } Entry* filterEntry = parentFilterEntry != NULL ? parentFilterEntry->FindChild(entry->Name()) : NULL; if (implicit && filterEntry == NULL) { // parent is implicit and the filter doesn't include this entry // -- ignore it return B_OK; } // If the entry is in the filter, get its implicit flag. if (filterEntry != NULL) { implicit = filterEntry->IsImplicit(); filterEntry->SetSeen(); } token->filterEntry = filterEntry; token->implicit = implicit; // get parent FD and the entry name int parentFD; const char* entryName; _GetParentFDAndEntryName(entry, parentFD, entryName); // check whether something is in the way struct stat st; bool entryExists = fstatat(parentFD, entryName, &st, AT_SYMLINK_NOFOLLOW) == 0; if (entryExists) { if (S_ISREG(entry->Mode()) || S_ISLNK(entry->Mode())) { // If the entry in the way is a regular file or a symlink, // remove it, otherwise fail. if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) { fprintf(stderr, "Error: Can't create entry \"%s\", since " "something is in the way\n", _EntryPath(entry).String()); return B_FILE_EXISTS; } if (unlinkat(parentFD, entryName, 0) != 0) { fprintf(stderr, "Error: Failed to unlink entry \"%s\": %s\n", _EntryPath(entry).String(), strerror(errno)); return errno; } entryExists = false; } else if (S_ISDIR(entry->Mode())) { // If the entry in the way is a directory, merge, otherwise // fail. if (!S_ISDIR(st.st_mode)) { fprintf(stderr, "Error: Can't create directory \"%s\", " "since something is in the way\n", _EntryPath(entry).String()); return B_FILE_EXISTS; } } } // create the entry int fd = -1; if (S_ISREG(entry->Mode())) { if (implicit) { fprintf(stderr, "Error: File \"%s\" was specified as a " "path directory component.\n", _EntryPath(entry).String()); return B_BAD_VALUE; } // create the file fd = openat(parentFD, entryName, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); // Note: We use read+write user permissions now -- so write // operations (e.g. attributes) won't fail, but set them to the // desired ones in HandleEntryDone(). if (fd < 0) { fprintf(stderr, "Error: Failed to create file \"%s\": %s\n", _EntryPath(entry).String(), strerror(errno)); return errno; } // write data status_t error = _ExtractFileData(fPackageFileReader, entry->Data(), fd); if (error != B_OK) return error; } else if (S_ISLNK(entry->Mode())) { if (implicit) { fprintf(stderr, "Error: Symlink \"%s\" was specified as a " "path directory component.\n", _EntryPath(entry).String()); return B_BAD_VALUE; } // create the symlink const char* symlinkPath = entry->SymlinkPath(); if (symlinkat(symlinkPath != NULL ? symlinkPath : "", parentFD, entryName) != 0) { fprintf(stderr, "Error: Failed to create symlink \"%s\": %s\n", _EntryPath(entry).String(), strerror(errno)); return errno; } // TODO: Set symlink permissions? } else if (S_ISDIR(entry->Mode())) { // create the directory, if necessary if (!entryExists && mkdirat(parentFD, entryName, S_IRWXU) != 0) { // Note: We use read+write+exec user permissions now -- so write // operations (e.g. attributes) won't fail, but set them to the // desired ones in HandleEntryDone(). fprintf(stderr, "Error: Failed to create directory \"%s\": " "%s\n", _EntryPath(entry).String(), strerror(errno)); return errno; } } else { fprintf(stderr, "Error: Invalid file type for entry \"%s\"\n", _EntryPath(entry).String()); return B_BAD_DATA; } // If not done yet (symlink, dir), open the node -- we need the FD. if (fd < 0 && (!implicit || S_ISDIR(entry->Mode()))) { fd = openat(parentFD, entryName, O_RDONLY | O_NOTRAVERSE); if (fd < 0) { fprintf(stderr, "Error: Failed to open entry \"%s\": %s\n", _EntryPath(entry).String(), strerror(errno)); return errno; } } token->fd = fd; // set the file times if (!entryExists && !implicit) { timespec times[2] = {entry->AccessTime(), entry->ModifiedTime()}; futimens(fd, times); // set user/group // TODO:... } entry->SetUserToken(tokenDeleter.Detach()); return B_OK; } virtual status_t HandleEntryAttribute( typename VersionPolicy::PackageEntry* entry, typename VersionPolicy::PackageEntryAttribute* attribute) { // don't write attributes of ignored or implicit entries Token* token = (Token*)entry->UserToken(); if (token == NULL || token->implicit) return B_OK; int entryFD = token->fd; // create the attribute int fd = fs_fopen_attr(entryFD, attribute->Name(), attribute->Type(), O_WRONLY | O_CREAT | O_TRUNC); if (fd < 0) { int parentFD; const char* entryName; _GetParentFDAndEntryName(entry, parentFD, entryName); fprintf(stderr, "Error: Failed to create attribute \"%s\" of " "file \"%s\": %s\n", attribute->Name(), _EntryPath(entry).String(), strerror(errno)); return errno; } // write data status_t error = _ExtractFileData(fPackageFileReader, attribute->Data(), fd); fs_close_attr(fd); return error; } virtual status_t HandleEntryDone( typename VersionPolicy::PackageEntry* entry) { Token* token = (Token*)entry->UserToken(); // set the node permissions for non-symlinks if (token != NULL && !S_ISLNK(entry->Mode())) { // get parent FD and entry name int parentFD; const char* entryName; _GetParentFDAndEntryName(entry, parentFD, entryName); if (fchmodat(parentFD, entryName, entry->Mode() & ALLPERMS, /*AT_SYMLINK_NOFOLLOW*/0) != 0) { fprintf(stderr, "Warning: Failed to set permissions of file " "\"%s\": %s\n", _EntryPath(entry).String(), strerror(errno)); } } if (token != NULL) { delete token; entry->SetUserToken(NULL); } return B_OK; } virtual status_t HandlePackageAttribute( const BPackageInfoAttributeValue& value) { return B_OK; } virtual void HandleErrorOccurred() { fErrorOccurred = true; } private: struct Token { Entry* filterEntry; int fd; bool implicit; Token() : filterEntry(NULL), fd(-1), implicit(true) { } ~Token() { if (fd >= 0) close(fd); } }; private: status_t _AddFilterEntry(Entry* parentEntry, const char* _name, size_t nameLength, bool implicit, Entry*& _entry) { BString name(_name, nameLength); if (name.IsEmpty()) return B_NO_MEMORY; return Entry::Create(parentEntry, name.String(), implicit, _entry); } void _GetParentFDAndEntryName(typename VersionPolicy::PackageEntry* entry, int& _parentFD, const char*& _entryName) { _entryName = entry->Name(); if (fInfoFileName != NULL && strcmp(_entryName, VersionPolicy::PackageInfoFileName()) == 0) { _parentFD = AT_FDCWD; _entryName = fInfoFileName; } else { _parentFD = entry->Parent() != NULL ? ((Token*)entry->Parent()->UserToken())->fd : fBaseDirectory; } } BString _EntryPath(const typename VersionPolicy::PackageEntry* entry) { BString path; if (const typename VersionPolicy::PackageEntry* parent = entry->Parent()) { path = _EntryPath(parent); path << '/'; } path << entry->Name(); return path; } status_t _ExtractFileData( typename VersionPolicy::HeapReaderBase* dataReader, const typename VersionPolicy::PackageData& data, int fd) { // create a PackageDataReader BAbstractBufferedDataReader* reader; status_t error = VersionPolicy::CreatePackageDataReader(fBufferPool, dataReader, data, reader); if (error != B_OK) return error; ObjectDeleter readerDeleter(reader); // write the data off_t bytesRemaining = VersionPolicy::PackageDataUncompressedSize(data); off_t offset = 0; while (bytesRemaining > 0) { // read size_t toCopy = std::min((off_t)fDataBufferSize, bytesRemaining); error = reader->ReadData(offset, fDataBuffer, toCopy); if (error != B_OK) { fprintf(stderr, "Error: Failed to read data: %s\n", strerror(error)); return error; } // write ssize_t bytesWritten = write_pos(fd, offset, fDataBuffer, toCopy); if (bytesWritten < 0) { fprintf(stderr, "Error: Failed to write data: %s\n", strerror(errno)); return errno; } if ((size_t)bytesWritten != toCopy) { fprintf(stderr, "Error: Failed to write all data (%zd of " "%zu)\n", bytesWritten, toCopy); return B_ERROR; } offset += toCopy; bytesRemaining -= toCopy; } return B_OK; } private: BBufferPool* fBufferPool; typename VersionPolicy::HeapReaderBase* fPackageFileReader; void* fDataBuffer; size_t fDataBufferSize; Entry fRootFilterEntry; int fBaseDirectory; const char* fInfoFileName; bool fErrorOccurred; }; template static void do_extract(const char* packageFileName, const char* changeToDirectory, const char* packageInfoFileName, const char* const* explicitEntries, int explicitEntryCount, bool ignoreVersionError) { // open package BStandardErrorOutput errorOutput; BBlockBufferPoolNoLock bufferPool(VersionPolicy::BufferSize(), 2); if (bufferPool.Init() != B_OK) { errorOutput.PrintError("Error: Out of memory!\n"); exit(1); } typename VersionPolicy::PackageReader packageReader(&errorOutput); status_t error = VersionPolicy::InitReader(packageReader, packageFileName); if (error != B_OK) { if (ignoreVersionError && error == B_MISMATCHED_VALUES) return; exit(1); } typename VersionPolicy::HeapReaderBase* heapReader; bool mustDeleteHeapReader; error = VersionPolicy::GetHeapReader(packageReader, heapReader, mustDeleteHeapReader); if (error != B_OK) { fprintf(stderr, "Error: Failed to create heap reader: \"%s\"\n", strerror(error)); exit(1); } ObjectDeleter heapReaderDeleter( mustDeleteHeapReader ? heapReader : NULL); PackageContentExtractHandler handler(&bufferPool, heapReader); error = handler.Init(); if (error != B_OK) exit(1); // If entries to extract have been specified explicitly, add those to the // filtered ones. if (explicitEntryCount > 0) { for (int i = 0; i < explicitEntryCount; i++) { const char* entryName = explicitEntries[i]; if (entryName[0] == '\0' || entryName[0] == '/') { fprintf(stderr, "Error: Invalid entry name: \"%s\"\n", entryName); exit(1); } if (handler.AddFilterEntry(entryName) != B_OK) exit(1); } } else handler.SetExtractAll(); // get the target directory, if requested if (changeToDirectory != NULL) { int currentDirFD = open(changeToDirectory, O_RDONLY); if (currentDirFD < 0) { fprintf(stderr, "Error: Failed to change the current working " "directory to \"%s\": %s\n", changeToDirectory, strerror(errno)); exit(1); } handler.SetBaseDirectory(currentDirFD); } // If a package info file name is given, set it. if (packageInfoFileName != NULL) handler.SetPackageInfoFile(packageInfoFileName); // extract error = packageReader.ParseContent(&handler); if (error != B_OK) exit(1); // check whether all explicitly specified entries have been extracted if (explicitEntryCount > 0) { for (int i = 0; i < explicitEntryCount; i++) { if (Entry* entry = handler.FindFilterEntry(explicitEntries[i])) { if (!entry->Seen()) { fprintf(stderr, "Warning: Entry \"%s\" not found.\n", explicitEntries[i]); } } } } exit(0); } int command_extract(int argc, const char* const* argv) { const char* changeToDirectory = NULL; const char* packageInfoFileName = NULL; while (true) { static struct option sLongOptions[] = { { "help", no_argument, 0, 'h' }, { 0, 0, 0, 0 } }; opterr = 0; // don't print errors int c = getopt_long(argc, (char**)argv, "+C:hi:", sLongOptions, NULL); if (c == -1) break; switch (c) { case 'C': changeToDirectory = optarg; break; case 'h': print_usage_and_exit(false); break; case 'i': packageInfoFileName = optarg; break; default: print_usage_and_exit(true); break; } } // At least one argument should remain -- the package file name. Any further // arguments are the names of the entries to extract. if (optind + 1 > argc) print_usage_and_exit(true); const char* packageFileName = argv[optind++]; const char* const* explicitEntries = argv + optind; int explicitEntryCount = argc - optind; do_extract(packageFileName, changeToDirectory, packageInfoFileName, explicitEntries, explicitEntryCount, true); do_extract(packageFileName, changeToDirectory, packageInfoFileName, explicitEntries, explicitEntryCount, false); return 0; }