⛏️ index : haiku.git

/*
 * 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;
}