/*
* Copyright 2009-2013, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <algorithm>
#include <new>
#include <fs_attr.h>
#include <String.h>
#include <AutoDeleter.h>
#include <HashString.h>
#include <util/OpenHashTable.h>
#include <package/hpkg/BlockBufferPoolNoLock.h>
#include <package/hpkg/PackageContentHandler.h>
#include <package/hpkg/PackageDataReader.h>
#include <package/hpkg/PackageEntry.h>
#include <package/hpkg/PackageEntryAttribute.h>
#include <package/hpkg/PackageReader.h>
#include <package/hpkg/StandardErrorOutput.h>
#include <package/hpkg/v1/PackageContentHandler.h>
#include <package/hpkg/v1/PackageDataReader.h>
#include <package/hpkg/v1/PackageEntry.h>
#include <package/hpkg/v1/PackageEntryAttribute.h>
#include <package/hpkg/v1//PackageReader.h>
#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<ChildHashDefinition> 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<typename VersionPolicy>
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<Token> 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<BAbstractBufferedDataReader> 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<typename VersionPolicy>
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<BDataReader> heapReaderDeleter(
mustDeleteHeapReader ? heapReader : NULL);
PackageContentExtractHandler<VersionPolicy> 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<VersionPolicyV2>(packageFileName, changeToDirectory,
packageInfoFileName, explicitEntries, explicitEntryCount, true);
do_extract<VersionPolicyV1>(packageFileName, changeToDirectory,
packageInfoFileName, explicitEntries, explicitEntryCount, false);
return 0;
}
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fHashTableNext.