1156 lines
27 KiB
C++
1156 lines
27 KiB
C++
/*
|
|
* Copyright 2007-2009, Haiku, Inc.
|
|
* Distributed under the terms of the MIT license.
|
|
*
|
|
* Author:
|
|
* Łukasz 'Sil2100' Zemczak <sil2100@vexillium.org>
|
|
*/
|
|
|
|
|
|
#include "PackageItem.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <Alert.h>
|
|
#include <ByteOrder.h>
|
|
#include <Catalog.h>
|
|
#include <Directory.h>
|
|
#include <FindDirectory.h>
|
|
#include <fs_info.h>
|
|
#include <Locale.h>
|
|
#include <NodeInfo.h>
|
|
#include <OS.h>
|
|
#include <SymLink.h>
|
|
#include <Volume.h>
|
|
|
|
#include "zlib.h"
|
|
|
|
|
|
#undef B_TRANSLATION_CONTEXT
|
|
#define B_TRANSLATION_CONTEXT "PackageItem"
|
|
|
|
enum {
|
|
P_CHUNK_SIZE = 256
|
|
};
|
|
|
|
static const uint32 kDefaultMode = 0777;
|
|
static const uint8 padding[7] = { 0, 0, 0, 0, 0, 0, 0 };
|
|
|
|
extern bool gVerbose;
|
|
|
|
enum {
|
|
P_DATA = 0,
|
|
P_ATTRIBUTE
|
|
};
|
|
|
|
|
|
status_t
|
|
inflate_data(uint8 *in, uint32 inSize, uint8 *out, uint32 outSize)
|
|
{
|
|
parser_debug("inflate_data() called - input_size: %ld, output_size: %ld\n",
|
|
inSize, outSize);
|
|
z_stream stream;
|
|
stream.zalloc = Z_NULL;
|
|
stream.zfree = Z_NULL;
|
|
stream.opaque = Z_NULL;
|
|
stream.avail_in = inSize;
|
|
stream.next_in = in;
|
|
status_t ret;
|
|
|
|
ret = inflateInit(&stream);
|
|
if (ret != Z_OK) {
|
|
parser_debug("inflatInit failed\n");
|
|
return B_ERROR;
|
|
}
|
|
|
|
stream.avail_out = outSize;
|
|
stream.next_out = out;
|
|
|
|
ret = inflate(&stream, Z_NO_FLUSH);
|
|
if (ret != Z_STREAM_END) {
|
|
// Uncompressed file size in package info corrupted
|
|
parser_debug("Left: %d\n", stream.avail_out);
|
|
return B_ERROR;
|
|
}
|
|
|
|
inflateEnd(&stream);
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
static inline int
|
|
inflate_file_to_file(BFile *in, uint64 in_size, BFile *out, uint64 out_size)
|
|
{
|
|
z_stream stream;
|
|
stream.zalloc = Z_NULL;
|
|
stream.zfree = Z_NULL;
|
|
stream.opaque = Z_NULL;
|
|
stream.avail_in = 0;
|
|
stream.next_in = Z_NULL;
|
|
status_t ret;
|
|
|
|
uint8 buffer_out[P_CHUNK_SIZE], buffer_in[P_CHUNK_SIZE];
|
|
uint64 bytes_read = 0, read = P_CHUNK_SIZE, write = 0;
|
|
|
|
ret = inflateInit(&stream);
|
|
if (ret != Z_OK) {
|
|
parser_debug("inflate_file_to_file: inflateInit failed\n");
|
|
return B_ERROR;
|
|
}
|
|
|
|
do {
|
|
bytes_read += P_CHUNK_SIZE;
|
|
if (bytes_read > in_size) {
|
|
read = in_size - (bytes_read - P_CHUNK_SIZE);
|
|
bytes_read = in_size;
|
|
}
|
|
|
|
stream.avail_in = in->Read(buffer_in, read);
|
|
if (stream.avail_in != read) {
|
|
parser_debug("inflate_file_to_file: read failed\n");
|
|
(void)inflateEnd(&stream);
|
|
return B_ERROR;
|
|
}
|
|
stream.next_in = buffer_in;
|
|
|
|
do {
|
|
stream.avail_out = P_CHUNK_SIZE;
|
|
stream.next_out = buffer_out;
|
|
|
|
ret = inflate(&stream, Z_NO_FLUSH);
|
|
if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) {
|
|
parser_debug("inflate_file_to_file: inflate failed with '%s'\n",
|
|
stream.msg);
|
|
(void)inflateEnd(&stream);
|
|
return B_ERROR;
|
|
}
|
|
|
|
write = P_CHUNK_SIZE - stream.avail_out;
|
|
if (static_cast<uint64>(out->Write(buffer_out, write)) != write) {
|
|
parser_debug("inflate_file_to_file: write failed\n");
|
|
(void)inflateEnd(&stream);
|
|
return B_ERROR;
|
|
}
|
|
} while (stream.avail_out == 0);
|
|
} while (bytes_read != in_size);
|
|
|
|
(void)inflateEnd(&stream);
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
// #pragma mark - PackageItem
|
|
|
|
|
|
PackageItem::PackageItem(BFile* parent, const BString& path, uint8 type,
|
|
uint32 ctime, uint32 mtime, uint64 offset, uint64 size)
|
|
{
|
|
SetTo(parent, path, type, ctime, mtime, offset, size);
|
|
}
|
|
|
|
|
|
PackageItem::~PackageItem()
|
|
{
|
|
}
|
|
|
|
|
|
void
|
|
PackageItem::SetTo(BFile* parent, const BString& path, uint8 type, uint32 ctime,
|
|
uint32 mtime, uint64 offset, uint64 size)
|
|
{
|
|
fPackage = parent;
|
|
fPath = path;
|
|
|
|
fOffset = offset;
|
|
fSize = size;
|
|
fPathType = type;
|
|
fCreationTime = ctime;
|
|
fModificationTime = mtime;
|
|
}
|
|
|
|
|
|
status_t
|
|
PackageItem::InitPath(const char* path, BPath* destination)
|
|
{
|
|
status_t ret = B_OK;
|
|
|
|
if (fPathType == P_INSTALL_PATH) {
|
|
if (gVerbose)
|
|
printf("InitPath - relative: %s + %s\n", path, fPath.String());
|
|
if (path == NULL) {
|
|
parser_debug("InitPath path is NULL\n");
|
|
return B_ERROR;
|
|
}
|
|
ret = destination->SetTo(path, fPath.String());
|
|
} else if (fPathType == P_SYSTEM_PATH) {
|
|
if (gVerbose)
|
|
printf("InitPath - absolute: %s\n", fPath.String());
|
|
if (fPath == "")
|
|
fPath = "/";
|
|
ret = destination->SetTo(fPath.String());
|
|
} else {
|
|
if (gVerbose)
|
|
printf("InitPath - volume: %s + %s\n", path, fPath.String());
|
|
if (path == NULL) {
|
|
parser_debug("InitPath path is NULL\n");
|
|
return B_ERROR;
|
|
}
|
|
|
|
BVolume volume(dev_for_path(path));
|
|
ret = volume.InitCheck();
|
|
if (ret == B_OK) {
|
|
BDirectory temp;
|
|
ret = volume.GetRootDirectory(&temp);
|
|
if (ret == B_OK) {
|
|
BPath mountPoint(&temp, NULL);
|
|
ret = destination->SetTo(mountPoint.Path(), fPath.String());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret != B_OK) {
|
|
fprintf(stderr, "InitPath(%s): %s\n", path, strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
BString pathString(destination->Path());
|
|
|
|
// Hardcoded paths, the .pkg files hardcode this to the same
|
|
if (pathString.FindFirst("non-packaged") < 0) {
|
|
bool wasRewritten = false;
|
|
|
|
if (pathString.StartsWith("/boot/beos/system")) {
|
|
BPath systemNonPackagedDir;
|
|
find_directory(B_SYSTEM_NONPACKAGED_DIRECTORY,
|
|
&systemNonPackagedDir);
|
|
pathString.ReplaceFirst("/boot/beos/system",
|
|
systemNonPackagedDir.Path());
|
|
wasRewritten = true;
|
|
} else if (pathString.StartsWith("/boot/system")) {
|
|
BPath systemNonPackagedDir;
|
|
find_directory(B_SYSTEM_NONPACKAGED_DIRECTORY,
|
|
&systemNonPackagedDir);
|
|
pathString.ReplaceFirst("/boot/system",
|
|
systemNonPackagedDir.Path());
|
|
wasRewritten = true;
|
|
} else if (pathString.StartsWith("/boot/home/config")) {
|
|
BPath userNonPackagedDir;
|
|
find_directory(B_USER_NONPACKAGED_DIRECTORY, &userNonPackagedDir);
|
|
pathString.ReplaceFirst("/boot/home/config",
|
|
userNonPackagedDir.Path());
|
|
wasRewritten = true;
|
|
}
|
|
|
|
if (wasRewritten) {
|
|
if (gVerbose)
|
|
printf("rewritten: %s\n", pathString.String());
|
|
destination->SetTo(pathString.String());
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
status_t
|
|
PackageItem::HandleAttributes(BPath *destination, BNode *node,
|
|
const char *header)
|
|
{
|
|
status_t ret = B_OK;
|
|
|
|
BVolume volume(dev_for_path(destination->Path()));
|
|
if (volume.KnowsAttr()) {
|
|
parser_debug("We have an offset\n");
|
|
if (!fPackage)
|
|
return B_ERROR;
|
|
|
|
ret = fPackage->InitCheck();
|
|
if (ret != B_OK)
|
|
return ret;
|
|
|
|
// We need to parse the data section now
|
|
fPackage->Seek(fOffset, SEEK_SET);
|
|
uint8 buffer[7];
|
|
if (fPackage->Read(buffer, 7) != 7 || memcmp(buffer, header, 5))
|
|
return B_ERROR;
|
|
parser_debug("Header validated!\n");
|
|
|
|
char *attrName = 0;
|
|
uint32 nameSize = 0;
|
|
uint8 *attrData = new uint8[P_CHUNK_SIZE];
|
|
uint64 dataSize = P_CHUNK_SIZE;
|
|
uint8 *temp = new uint8[P_CHUNK_SIZE];
|
|
uint64 tempSize = P_CHUNK_SIZE;
|
|
|
|
uint64 attrCSize = 0, attrOSize = 0;
|
|
uint32 attrType = 0; // type_code type
|
|
bool attrStarted = false, done = false;
|
|
|
|
while (fPackage->Read(buffer, 7) == 7) {
|
|
if (!memcmp(buffer, "FBeA", 5))
|
|
continue;
|
|
|
|
ret = ParseAttribute(buffer, node, &attrName, &nameSize, &attrType,
|
|
&attrData, &dataSize, &temp, &tempSize, &attrCSize, &attrOSize,
|
|
&attrStarted, &done);
|
|
if (ret != B_OK || done) {
|
|
if (ret != B_OK) {
|
|
parser_debug("_ParseAttribute failed for %s\n",
|
|
destination->Path());
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
delete[] attrData;
|
|
delete[] temp;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
status_t
|
|
PackageItem::ParseAttribute(uint8* buffer, BNode* node, char** attrName,
|
|
uint32* nameSize, uint32* attrType, uint8** attrData, uint64* dataSize,
|
|
uint8** temp, uint64* tempSize, uint64* attrCSize, uint64* attrOSize,
|
|
bool* attrStarted, bool* done)
|
|
{
|
|
status_t ret = B_OK;
|
|
uint32 length;
|
|
|
|
if (!memcmp(buffer, "BeAI", 5)) {
|
|
parser_debug(" Attribute started.\n");
|
|
if (*attrName)
|
|
*attrName[0] = 0;
|
|
*attrCSize = 0;
|
|
*attrOSize = 0;
|
|
|
|
*attrStarted = true;
|
|
} else if (!memcmp(buffer, "BeAN", 5)) {
|
|
if (!*attrStarted) {
|
|
ret = B_ERROR;
|
|
return ret;
|
|
}
|
|
|
|
parser_debug(" BeAN.\n");
|
|
fPackage->Read(&length, 4);
|
|
swap_data(B_UINT32_TYPE, &length, sizeof(uint32),
|
|
B_SWAP_BENDIAN_TO_HOST);
|
|
|
|
if (*nameSize < (length + 1)) {
|
|
delete[] *attrName;
|
|
*nameSize = length + 1;
|
|
*attrName = new char[*nameSize];
|
|
}
|
|
fPackage->Read(*attrName, length);
|
|
(*attrName)[length] = 0;
|
|
|
|
parser_debug(" (%ld) = %s\n", length, *attrName);
|
|
} else if (!memcmp(buffer, "BeAT", 5)) {
|
|
if (!*attrStarted) {
|
|
ret = B_ERROR;
|
|
return ret;
|
|
}
|
|
|
|
parser_debug(" BeAT.\n");
|
|
fPackage->Read(attrType, 4);
|
|
swap_data(B_UINT32_TYPE, attrType, sizeof(*attrType),
|
|
B_SWAP_BENDIAN_TO_HOST);
|
|
} else if (!memcmp(buffer, "BeAD", 5)) {
|
|
if (!*attrStarted) {
|
|
ret = B_ERROR;
|
|
return ret;
|
|
}
|
|
|
|
parser_debug(" BeAD.\n");
|
|
fPackage->Read(attrCSize, 8);
|
|
swap_data(B_UINT64_TYPE, attrCSize, sizeof(*attrCSize),
|
|
B_SWAP_BENDIAN_TO_HOST);
|
|
|
|
fPackage->Read(attrOSize, 8);
|
|
swap_data(B_UINT64_TYPE, attrOSize, sizeof(*attrOSize),
|
|
B_SWAP_BENDIAN_TO_HOST);
|
|
|
|
fPackage->Seek(4, SEEK_CUR); // TODO: Check what this means
|
|
|
|
if (*tempSize < *attrCSize) {
|
|
delete[] *temp;
|
|
*tempSize = *attrCSize;
|
|
*temp = new uint8[*tempSize];
|
|
}
|
|
if (*dataSize < *attrOSize) {
|
|
delete[] *attrData;
|
|
*dataSize = *attrOSize;
|
|
*attrData = new uint8[*dataSize];
|
|
}
|
|
|
|
if (fPackage->Read(*temp, *attrCSize)
|
|
!= static_cast<ssize_t>(*attrCSize)) {
|
|
ret = B_ERROR;
|
|
return ret;
|
|
}
|
|
|
|
parser_debug(" Data read successfuly. Inflating!\n");
|
|
ret = inflate_data(*temp, *tempSize, *attrData, *dataSize);
|
|
if (ret != B_OK)
|
|
return ret;
|
|
} else if (!memcmp(buffer, padding, 7)) {
|
|
if (!*attrStarted) {
|
|
*done = true;
|
|
return ret;
|
|
}
|
|
|
|
parser_debug(" Padding.\n");
|
|
ssize_t wrote = node->WriteAttr(*attrName, *attrType, 0, *attrData,
|
|
*attrOSize);
|
|
if (wrote != static_cast<ssize_t>(*attrOSize)) {
|
|
parser_debug("Failed to write attribute %s %s\n", *attrName, strerror(wrote));
|
|
return B_ERROR;
|
|
}
|
|
|
|
*attrStarted = false;
|
|
if (*attrName)
|
|
*attrName[0] = 0;
|
|
*attrCSize = 0;
|
|
*attrOSize = 0;
|
|
|
|
parser_debug(" > Attribute added.\n");
|
|
} else {
|
|
parser_debug(" Unknown attribute\n");
|
|
ret = B_ERROR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
status_t
|
|
PackageItem::SkipAttribute(uint8* buffer, bool* attrStarted, bool* done)
|
|
{
|
|
status_t ret = B_OK;
|
|
uint32 length;
|
|
|
|
if (!memcmp(buffer, "BeAI", 5)) {
|
|
parser_debug(" Attribute started.\n");
|
|
*attrStarted = true;
|
|
} else if (!memcmp(buffer, "BeAN", 5)) {
|
|
if (!*attrStarted) {
|
|
ret = B_ERROR;
|
|
return ret;
|
|
}
|
|
|
|
parser_debug(" BeAN.\n");
|
|
fPackage->Read(&length, 4);
|
|
swap_data(B_UINT32_TYPE, &length, sizeof(uint32),
|
|
B_SWAP_BENDIAN_TO_HOST);
|
|
|
|
fPackage->Seek(length, SEEK_CUR);
|
|
} else if (!memcmp(buffer, "BeAT", 5)) {
|
|
if (!*attrStarted) {
|
|
ret = B_ERROR;
|
|
return ret;
|
|
}
|
|
|
|
parser_debug(" BeAT.\n");
|
|
fPackage->Seek(4, SEEK_CUR);
|
|
} else if (!memcmp(buffer, "BeAD", 5)) {
|
|
if (!*attrStarted) {
|
|
ret = B_ERROR;
|
|
return ret;
|
|
}
|
|
|
|
parser_debug(" BeAD.\n");
|
|
uint64 length64;
|
|
fPackage->Read(&length64, 8);
|
|
swap_data(B_UINT64_TYPE, &length64, sizeof(length64),
|
|
B_SWAP_BENDIAN_TO_HOST);
|
|
|
|
fPackage->Seek(12 + length64, SEEK_CUR);
|
|
|
|
parser_debug(" Data skipped successfuly.\n");
|
|
} else if (!memcmp(buffer, padding, 7)) {
|
|
if (!*attrStarted) {
|
|
*done = true;
|
|
return ret;
|
|
}
|
|
|
|
parser_debug(" Padding.\n");
|
|
*attrStarted = false;
|
|
parser_debug(" > Attribute skipped.\n");
|
|
} else {
|
|
parser_debug(" Unknown attribute\n");
|
|
ret = B_ERROR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
status_t
|
|
PackageItem::ParseData(uint8* buffer, BFile* file, uint64 originalSize,
|
|
bool *done)
|
|
{
|
|
status_t ret = B_OK;
|
|
|
|
if (!memcmp(buffer, "FiMF", 5)) {
|
|
parser_debug(" Found file data.\n");
|
|
uint64 compressed, original;
|
|
fPackage->Read(&compressed, 8);
|
|
swap_data(B_UINT64_TYPE, &compressed, sizeof(uint64),
|
|
B_SWAP_BENDIAN_TO_HOST);
|
|
|
|
fPackage->Read(&original, 8);
|
|
swap_data(B_UINT64_TYPE, &original, sizeof(uint64),
|
|
B_SWAP_BENDIAN_TO_HOST);
|
|
parser_debug(" Still good... (%llu : %llu)\n", original,
|
|
originalSize);
|
|
|
|
if (original != originalSize) {
|
|
parser_debug(" File size mismatch\n");
|
|
return B_ERROR; // File size mismatch
|
|
}
|
|
parser_debug(" Still good...\n");
|
|
|
|
if (fPackage->Read(buffer, 4) != 4) {
|
|
parser_debug(" Read(buffer, 4) failed\n");
|
|
return B_ERROR;
|
|
}
|
|
parser_debug(" Still good...\n");
|
|
|
|
ret = inflate_file_to_file(fPackage, compressed, file, original);
|
|
if (ret != B_OK) {
|
|
parser_debug(" inflate_file_to_file failed\n");
|
|
return ret;
|
|
}
|
|
parser_debug(" File data inflation complete!\n");
|
|
} else if (!memcmp(buffer, padding, 7)) {
|
|
*done = true;
|
|
return ret;
|
|
} else {
|
|
parser_debug("_ParseData unknown tag\n");
|
|
ret = B_ERROR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
// #pragma mark - PackageScript
|
|
|
|
|
|
PackageScript::PackageScript(BFile* parent, const BString& path, uint8 type,
|
|
uint64 offset, uint64 size, uint64 originalSize)
|
|
:
|
|
PackageItem(parent, path, type, 0, 0, offset, size),
|
|
fOriginalSize(originalSize),
|
|
fThreadId(-1)
|
|
{
|
|
}
|
|
|
|
|
|
status_t
|
|
PackageScript::DoInstall(const char* path, ItemState* state)
|
|
{
|
|
status_t ret = B_OK;
|
|
parser_debug("Script: DoInstall() called!\n");
|
|
|
|
if (fOffset) {
|
|
parser_debug("We have an offset\n");
|
|
if (!fPackage)
|
|
return B_ERROR;
|
|
|
|
ret = fPackage->InitCheck();
|
|
if (ret != B_OK)
|
|
return ret;
|
|
|
|
// We need to parse the data section now
|
|
fPackage->Seek(fOffset, SEEK_SET);
|
|
uint8 buffer[7];
|
|
bool attrStarted = false, done = false;
|
|
|
|
uint8 section = P_ATTRIBUTE;
|
|
|
|
while (fPackage->Read(buffer, 7) == 7) {
|
|
if (!memcmp(buffer, "FBeA", 5)) {
|
|
parser_debug("-> Attribute\n");
|
|
section = P_ATTRIBUTE;
|
|
continue;
|
|
} else if (!memcmp(buffer, "FiDa", 5)) {
|
|
parser_debug("-> File data\n");
|
|
section = P_DATA;
|
|
continue;
|
|
}
|
|
|
|
switch (section) {
|
|
case P_ATTRIBUTE:
|
|
ret = SkipAttribute(buffer, &attrStarted, &done);
|
|
break;
|
|
|
|
case P_DATA:
|
|
{
|
|
BString script;
|
|
ret = _ParseScript(buffer, fOriginalSize, script, &done);
|
|
if (ret == B_OK) {
|
|
// Rewrite Deskbar entry targets. NOTE: It would
|
|
// also work to Replace("/config/be", "/config...")
|
|
// but it would be less save. For example, an app
|
|
// could have a folder named "config/be..." inside
|
|
// its installation folder.
|
|
// TODO: Use find_paths() or we are no better than
|
|
// these scripts.
|
|
script.ReplaceAll(
|
|
"/boot/beos/system/",
|
|
"/boot/system/");
|
|
script.ReplaceAll(
|
|
"~/config/be",
|
|
"~/config/settings/deskbar/menu");
|
|
script.ReplaceAll(
|
|
"/boot/home/config/be",
|
|
"/boot/home/config/settings/deskbar/menu");
|
|
// Rewrite all sorts of other old BeOS paths
|
|
script.ReplaceAll(
|
|
"/boot/preferences",
|
|
"/boot/system/preferences");
|
|
script.ReplaceAll(
|
|
"/boot/apps",
|
|
"/boot/system/non-packaged/apps");
|
|
script.ReplaceAll(
|
|
"~/config/add-ons/Screen\\ Savers",
|
|
"~/config/non-packaged/add-ons/Screen\\ Savers");
|
|
// TODO: More. These should also be put into a
|
|
// common source location, since it can also be used
|
|
// for the retargetting of install file locations.
|
|
// Packages seem to declare which system paths they
|
|
// use, and then package items can reference one of
|
|
// those global paths by index. A more elegent solution
|
|
// compared to what happens now in InitPath() would be
|
|
// to replace those global package paths. Or maybe
|
|
// that's more fragile... but a common source for
|
|
// the rewriting of BeOS paths is needed.
|
|
|
|
if (gVerbose)
|
|
printf("%s\n", script.String());
|
|
|
|
BPath workingDirectory;
|
|
if (path != NULL)
|
|
ret = InitPath(path, &workingDirectory);
|
|
else
|
|
ret = workingDirectory.SetTo(".");
|
|
if (ret == B_OK)
|
|
ret = _RunScript(workingDirectory.Path(), script);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return B_ERROR;
|
|
}
|
|
|
|
if (ret != B_OK || done)
|
|
break;
|
|
}
|
|
}
|
|
|
|
parser_debug("Ret: %ld %s\n", ret, strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
|
|
const uint32
|
|
PackageScript::ItemKind()
|
|
{
|
|
return P_KIND_SCRIPT;
|
|
}
|
|
|
|
|
|
status_t
|
|
PackageScript::_ParseScript(uint8 *buffer, uint64 originalSize,
|
|
BString& _script, bool *done)
|
|
{
|
|
status_t ret = B_OK;
|
|
|
|
if (!memcmp(buffer, "FiMF", 5)) {
|
|
parser_debug(" Found file (script) data.\n");
|
|
uint64 compressed, original;
|
|
fPackage->Read(&compressed, 8);
|
|
swap_data(B_UINT64_TYPE, &compressed, sizeof(uint64),
|
|
B_SWAP_BENDIAN_TO_HOST);
|
|
|
|
fPackage->Read(&original, 8);
|
|
swap_data(B_UINT64_TYPE, &original, sizeof(uint64),
|
|
B_SWAP_BENDIAN_TO_HOST);
|
|
parser_debug(" Still good... (%llu : %llu)\n", original,
|
|
originalSize);
|
|
|
|
if (original != originalSize) {
|
|
parser_debug(" File size mismatch\n");
|
|
return B_ERROR; // File size mismatch
|
|
}
|
|
parser_debug(" Still good...\n");
|
|
|
|
if (fPackage->Read(buffer, 4) != 4) {
|
|
parser_debug(" Read(buffer, 4) failed\n");
|
|
return B_ERROR;
|
|
}
|
|
parser_debug(" Still good...\n");
|
|
|
|
uint8 *temp = new uint8[compressed];
|
|
if (fPackage->Read(temp, compressed) != (int64)compressed) {
|
|
parser_debug(" Read(temp, compressed) failed\n");
|
|
delete[] temp;
|
|
return B_ERROR;
|
|
}
|
|
|
|
uint8* script = new uint8[original];
|
|
ret = inflate_data(temp, compressed, script, original);
|
|
if (ret != B_OK) {
|
|
parser_debug(" inflate_data failed\n");
|
|
delete[] temp;
|
|
delete[] script;
|
|
return ret;
|
|
}
|
|
|
|
_script.SetTo((char*)script, originalSize);
|
|
|
|
delete[] script;
|
|
delete[] temp;
|
|
parser_debug(" Script data inflation complete!\n");
|
|
} else if (!memcmp(buffer, padding, 7)) {
|
|
*done = true;
|
|
return ret;
|
|
} else {
|
|
parser_debug("_ParseData unknown tag\n");
|
|
ret = B_ERROR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
status_t
|
|
PackageScript::_RunScript(const char* workingDirectory, const BString& script)
|
|
{
|
|
// This function written by Peter Folk <pfolk@uni.uiuc.edu>
|
|
// and published in the BeDevTalk FAQ, modified for use in the
|
|
// PackageInstaller
|
|
// http://www.abisoft.com/faq/BeDevTalk_FAQ.html#FAQ-209
|
|
|
|
// Change current working directory to install path
|
|
char oldWorkingDirectory[B_PATH_NAME_LENGTH];
|
|
getcwd(oldWorkingDirectory, sizeof(oldWorkingDirectory));
|
|
chdir(workingDirectory);
|
|
|
|
// Save current FDs
|
|
int old_in = dup(0);
|
|
int old_out = dup(1);
|
|
int old_err = dup(2);
|
|
|
|
int filedes[2];
|
|
|
|
/* Create new pipe FDs as stdin, stdout, stderr */
|
|
pipe(filedes); dup2(filedes[0], 0); close(filedes[0]);
|
|
int in = filedes[1]; // Write to in, appears on cmd's stdin
|
|
pipe(filedes); dup2(filedes[1], 1); close(filedes[1]);
|
|
pipe(filedes); dup2(filedes[1], 2); close(filedes[1]);
|
|
|
|
const char **argv = new const char * [3];
|
|
argv[0] = strdup("/bin/sh");
|
|
argv[1] = strdup("-s");
|
|
argv[2] = NULL;
|
|
|
|
// "load" command.
|
|
fThreadId = load_image(2, argv, (const char**)environ);
|
|
|
|
int i;
|
|
for (i = 0; i < 2; i++)
|
|
delete argv[i];
|
|
delete [] argv;
|
|
|
|
if (fThreadId < B_OK)
|
|
return fThreadId;
|
|
|
|
// thread id is now suspended.
|
|
setpgid(fThreadId, fThreadId);
|
|
|
|
// Restore old FDs
|
|
close(0); dup(old_in); close(old_in);
|
|
close(1); dup(old_out); close(old_out);
|
|
close(2); dup(old_err); close(old_err);
|
|
|
|
set_thread_priority(fThreadId, B_LOW_PRIORITY);
|
|
resume_thread(fThreadId);
|
|
|
|
// Write the script
|
|
if (write(in, script.String(), script.Length() - 1) != script.Length() - 1
|
|
|| write(in, "\nexit\n", 6) != 6) {
|
|
parser_debug("Writing script failed\n");
|
|
kill_thread(fThreadId);
|
|
return B_ERROR;
|
|
}
|
|
|
|
// Restore current working directory
|
|
chdir(oldWorkingDirectory);
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
// #pragma mark - PackageDirectory
|
|
|
|
|
|
PackageDirectory::PackageDirectory(BFile* parent, const BString& path,
|
|
uint8 type, uint32 ctime, uint32 mtime, uint64 offset, uint64 size)
|
|
:
|
|
PackageItem(parent, path, type, ctime, mtime, offset, size)
|
|
{
|
|
}
|
|
|
|
|
|
status_t
|
|
PackageDirectory::DoInstall(const char* path, ItemState* state)
|
|
{
|
|
BPath &destination = state->destination;
|
|
status_t ret;
|
|
parser_debug("Directory: %s DoInstall() called!\n", fPath.String());
|
|
|
|
ret = InitPath(path, &destination);
|
|
parser_debug("Ret: %ld %s\n", ret, strerror(ret));
|
|
if (ret != B_OK)
|
|
return ret;
|
|
|
|
// Since Haiku is single-user right now, we give the newly
|
|
// created directory default permissions
|
|
ret = create_directory(destination.Path(), kDefaultMode);
|
|
parser_debug("Create dir ret: %ld %s\n", ret, strerror(ret));
|
|
if (ret != B_OK)
|
|
return ret;
|
|
BDirectory dir(destination.Path());
|
|
parser_debug("Directory created!\n");
|
|
|
|
if (fCreationTime)
|
|
dir.SetCreationTime(static_cast<time_t>(fCreationTime));
|
|
|
|
if (fModificationTime)
|
|
dir.SetModificationTime(static_cast<time_t>(fModificationTime));
|
|
|
|
// Since directories can only have attributes in the offset section,
|
|
// we can check here whether it is necessary to continue
|
|
if (fOffset)
|
|
ret = HandleAttributes(&destination, &dir, "FoDa");
|
|
|
|
parser_debug("Ret: %ld %s\n", ret, strerror(ret));
|
|
return ret;
|
|
}
|
|
|
|
|
|
const uint32
|
|
PackageDirectory::ItemKind()
|
|
{
|
|
return P_KIND_DIRECTORY;
|
|
}
|
|
|
|
|
|
// #pragma mark - PackageFile
|
|
|
|
|
|
PackageFile::PackageFile(BFile *parent, const BString &path, uint8 type,
|
|
uint32 ctime, uint32 mtime, uint64 offset, uint64 size,
|
|
uint64 originalSize, uint32 platform, const BString &mime,
|
|
const BString &signature, uint32 mode)
|
|
:
|
|
PackageItem(parent, path, type, ctime, mtime, offset, size),
|
|
fOriginalSize(originalSize),
|
|
fPlatform(platform),
|
|
fMode(mode),
|
|
fMimeType(mime),
|
|
fSignature(signature)
|
|
{
|
|
}
|
|
|
|
|
|
status_t
|
|
PackageFile::DoInstall(const char* path, ItemState* state)
|
|
{
|
|
if (state == NULL)
|
|
return B_ERROR;
|
|
|
|
BPath& destination = state->destination;
|
|
status_t ret = B_OK;
|
|
parser_debug("File: %s DoInstall() called!\n", fPath.String());
|
|
|
|
BFile file;
|
|
if (state->status == B_NO_INIT || destination.InitCheck() != B_OK) {
|
|
ret = InitPath(path, &destination);
|
|
if (ret != B_OK)
|
|
return ret;
|
|
|
|
ret = file.SetTo(destination.Path(),
|
|
B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS);
|
|
if (ret == B_ENTRY_NOT_FOUND) {
|
|
BPath directory;
|
|
destination.GetParent(&directory);
|
|
if (create_directory(directory.Path(), kDefaultMode) != B_OK)
|
|
return B_ERROR;
|
|
|
|
ret = file.SetTo(destination.Path(), B_WRITE_ONLY | B_CREATE_FILE);
|
|
} else if (ret == B_FILE_EXISTS)
|
|
state->status = B_FILE_EXISTS;
|
|
|
|
if (ret != B_OK)
|
|
return ret;
|
|
}
|
|
|
|
if (state->status == B_FILE_EXISTS) {
|
|
switch (state->policy) {
|
|
case P_EXISTS_OVERWRITE:
|
|
ret = file.SetTo(destination.Path(),
|
|
B_WRITE_ONLY | B_ERASE_FILE);
|
|
break;
|
|
|
|
case P_EXISTS_NONE:
|
|
case P_EXISTS_ASK:
|
|
ret = B_FILE_EXISTS;
|
|
break;
|
|
|
|
case P_EXISTS_SKIP:
|
|
return B_OK;
|
|
}
|
|
}
|
|
|
|
if (ret != B_OK)
|
|
return ret;
|
|
|
|
parser_debug(" File created!\n");
|
|
|
|
// Set the file permissions, creation and modification times
|
|
ret = file.SetPermissions(static_cast<mode_t>(fMode));
|
|
if (fCreationTime && ret == B_OK)
|
|
ret = file.SetCreationTime(static_cast<time_t>(fCreationTime));
|
|
if (fModificationTime && ret == B_OK)
|
|
ret = file.SetModificationTime(static_cast<time_t>(fModificationTime));
|
|
|
|
if (ret != B_OK)
|
|
return ret;
|
|
|
|
// Set the mimetype and application signature if present
|
|
BNodeInfo info(&file);
|
|
if (fMimeType.Length() > 0) {
|
|
ret = info.SetType(fMimeType.String());
|
|
if (ret != B_OK)
|
|
return ret;
|
|
}
|
|
if (fSignature.Length() > 0) {
|
|
ret = info.SetPreferredApp(fSignature.String());
|
|
if (ret != B_OK)
|
|
return ret;
|
|
}
|
|
|
|
if (fOffset) {
|
|
parser_debug("We have an offset\n");
|
|
if (!fPackage)
|
|
return B_ERROR;
|
|
|
|
ret = fPackage->InitCheck();
|
|
if (ret != B_OK)
|
|
return ret;
|
|
|
|
// We need to parse the data section now
|
|
fPackage->Seek(fOffset, SEEK_SET);
|
|
uint8 buffer[7];
|
|
|
|
char *attrName = 0;
|
|
uint32 nameSize = 0;
|
|
uint8 *attrData = new uint8[P_CHUNK_SIZE];
|
|
uint64 dataSize = P_CHUNK_SIZE;
|
|
uint8 *temp = new uint8[P_CHUNK_SIZE];
|
|
uint64 tempSize = P_CHUNK_SIZE;
|
|
|
|
uint64 attrCSize = 0, attrOSize = 0;
|
|
uint32 attrType = 0; // type_code type
|
|
bool attrStarted = false, done = false;
|
|
|
|
uint8 section = P_ATTRIBUTE;
|
|
|
|
while (fPackage->Read(buffer, 7) == 7) {
|
|
if (!memcmp(buffer, "FBeA", 5)) {
|
|
parser_debug("-> Attribute\n");
|
|
section = P_ATTRIBUTE;
|
|
continue;
|
|
} else if (!memcmp(buffer, "FiDa", 5)) {
|
|
parser_debug("-> File data\n");
|
|
section = P_DATA;
|
|
continue;
|
|
}
|
|
|
|
switch (section) {
|
|
case P_ATTRIBUTE:
|
|
ret = ParseAttribute(buffer, &file, &attrName, &nameSize,
|
|
&attrType, &attrData, &dataSize, &temp, &tempSize,
|
|
&attrCSize, &attrOSize, &attrStarted, &done);
|
|
break;
|
|
|
|
case P_DATA:
|
|
ret = ParseData(buffer, &file, fOriginalSize, &done);
|
|
break;
|
|
|
|
default:
|
|
return B_ERROR;
|
|
}
|
|
|
|
if (ret != B_OK || done)
|
|
break;
|
|
}
|
|
|
|
delete[] attrData;
|
|
delete[] temp;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
const uint32
|
|
PackageFile::ItemKind()
|
|
{
|
|
return P_KIND_FILE;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
PackageLink::PackageLink(BFile *parent, const BString &path,
|
|
const BString &link, uint8 type, uint32 ctime, uint32 mtime,
|
|
uint32 mode, uint64 offset, uint64 size)
|
|
:
|
|
PackageItem(parent, path, type, ctime, mtime, offset, size),
|
|
fMode(mode),
|
|
fLink(link)
|
|
{
|
|
}
|
|
|
|
|
|
status_t
|
|
PackageLink::DoInstall(const char *path, ItemState *state)
|
|
{
|
|
if (state == NULL)
|
|
return B_ERROR;
|
|
|
|
status_t ret = B_OK;
|
|
BSymLink symlink;
|
|
parser_debug("Symlink: %s DoInstall() called!\n", fPath.String());
|
|
|
|
BPath &destination = state->destination;
|
|
BDirectory *dir = &state->parent;
|
|
|
|
if (state->status == B_NO_INIT || destination.InitCheck() != B_OK
|
|
|| dir->InitCheck() != B_OK) {
|
|
// Not yet initialized
|
|
ret = InitPath(path, &destination);
|
|
if (ret != B_OK)
|
|
return ret;
|
|
|
|
BString linkName(destination.Leaf());
|
|
parser_debug("%s:%s:%s\n", fPath.String(), destination.Path(),
|
|
linkName.String());
|
|
|
|
BPath dirPath;
|
|
ret = destination.GetParent(&dirPath);
|
|
ret = dir->SetTo(dirPath.Path());
|
|
|
|
if (ret == B_ENTRY_NOT_FOUND) {
|
|
ret = create_directory(dirPath.Path(), kDefaultMode);
|
|
if (ret != B_OK) {
|
|
parser_debug("create_directory()) failed\n");
|
|
return B_ERROR;
|
|
}
|
|
}
|
|
if (ret != B_OK) {
|
|
parser_debug("destination InitCheck failed %s for %s\n",
|
|
strerror(ret), dirPath.Path());
|
|
return ret;
|
|
}
|
|
|
|
ret = dir->CreateSymLink(destination.Path(), fLink.String(), &symlink);
|
|
if (ret == B_FILE_EXISTS) {
|
|
// We need to check if the existing symlink is pointing at the same path
|
|
// as our new one - if not, let's prompt the user
|
|
symlink.SetTo(destination.Path());
|
|
BPath oldLink;
|
|
|
|
ret = symlink.MakeLinkedPath(dir, &oldLink);
|
|
chdir(dirPath.Path());
|
|
|
|
if (ret == B_BAD_VALUE || oldLink != fLink.String())
|
|
state->status = ret = B_FILE_EXISTS;
|
|
else
|
|
ret = B_OK;
|
|
}
|
|
}
|
|
|
|
if (state->status == B_FILE_EXISTS) {
|
|
switch (state->policy) {
|
|
case P_EXISTS_OVERWRITE:
|
|
{
|
|
BEntry entry;
|
|
ret = entry.SetTo(destination.Path());
|
|
if (ret != B_OK)
|
|
return ret;
|
|
|
|
entry.Remove();
|
|
ret = dir->CreateSymLink(destination.Path(), fLink.String(),
|
|
&symlink);
|
|
break;
|
|
}
|
|
|
|
case P_EXISTS_NONE:
|
|
case P_EXISTS_ASK:
|
|
ret = B_FILE_EXISTS;
|
|
break;
|
|
|
|
case P_EXISTS_SKIP:
|
|
return B_OK;
|
|
}
|
|
}
|
|
|
|
if (ret != B_OK) {
|
|
parser_debug("CreateSymLink failed\n");
|
|
return ret;
|
|
}
|
|
|
|
parser_debug(" Symlink created!\n");
|
|
|
|
ret = symlink.SetPermissions(static_cast<mode_t>(fMode));
|
|
|
|
if (fCreationTime && ret == B_OK)
|
|
ret = symlink.SetCreationTime(static_cast<time_t>(fCreationTime));
|
|
|
|
if (fModificationTime && ret == B_OK) {
|
|
ret = symlink.SetModificationTime(static_cast<time_t>(
|
|
fModificationTime));
|
|
}
|
|
|
|
if (ret != B_OK) {
|
|
parser_debug("Failed to set symlink attributes\n");
|
|
return ret;
|
|
}
|
|
|
|
if (fOffset) {
|
|
// Symlinks also seem to have attributes - so parse them
|
|
ret = HandleAttributes(&destination, &symlink, "LnDa");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
const uint32
|
|
PackageLink::ItemKind()
|
|
{
|
|
return P_KIND_SYM_LINK;
|
|
}
|
|
|