* Copyright 2014, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "package_support.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <AutoDeleter.h>
#include <boot/vfs.h>
#include <package/PackagesDirectoryDefs.h>
#define TRACE_PACKAGE_SUPPORT
#ifdef TRACE_PACKAGE_SUPPORT
# define TRACE(...) dprintf(__VA_ARGS__)
#else
# define TRACE(...) do {} while (false)
#endif
static const char* const kAdministrativeDirectory
= PACKAGES_DIRECTORY_ADMIN_DIRECTORY;
static const char* const kActivatedPackagesFile
= PACKAGES_DIRECTORY_ACTIVATION_FILE;
static inline bool
is_system_package(const char* name)
{
size_t nameLength = strlen(name);
if (nameLength < 6 || strcmp(name + nameLength - 5, ".hpkg") != 0)
return false;
return strcmp(name, "haiku.hpkg") == 0 || strncmp(name, "haiku-", 6) == 0;
}
PackageVolumeState::PackageVolumeState()
:
fName(NULL),
fDisplayName(NULL),
fSystemPackage(NULL)
{
}
PackageVolumeState::~PackageVolumeState()
{
Unset();
}
status_t
PackageVolumeState::SetTo(const char* stateName)
{
Unset();
if (stateName != NULL) {
fName = strdup(stateName);
if (fName == NULL)
return B_NO_MEMORY;
fDisplayName = strncmp(stateName, "state_", 6) == 0
? strdup(stateName + 6) : strdup(stateName);
if (fDisplayName == NULL)
return B_NO_MEMORY;
char* remainder = fDisplayName;
while (char* underscore = strchr(remainder, '_')) {
*underscore = ' ';
remainder = underscore + 1;
}
}
return B_OK;
}
void
PackageVolumeState::Unset()
{
free(fName);
fName = NULL;
free(fDisplayName);
fDisplayName = NULL;
free(fSystemPackage);
fSystemPackage = NULL;
}
const char*
PackageVolumeState::DisplayName() const
{
return fDisplayName;
}
status_t
PackageVolumeState::SetSystemPackage(const char* package)
{
if (fSystemPackage != NULL)
free(fSystemPackage);
fSystemPackage = strdup(package);
if (fSystemPackage == NULL)
return B_NO_MEMORY;
if (fName == NULL) {
free(fDisplayName);
fDisplayName = NULL;
const char* packageVersion = strchr(package, '-');
if (packageVersion == NULL) {
fDisplayName = strdup("Latest state");
} else {
ArrayDeleter<char> newDisplayName(new(std::nothrow) char[B_FILE_NAME_LENGTH]);
if (!newDisplayName.IsSet())
return B_NO_MEMORY;
packageVersion++;
const char* packageVersionEnd = strchr(packageVersion, '-');
int length = -1;
if (packageVersionEnd != NULL)
length = packageVersionEnd - packageVersion;
snprintf(newDisplayName.Get(), B_FILE_NAME_LENGTH,
"Latest state (%.*s)", length, packageVersion);
fDisplayName = newDisplayName.Detach();
}
}
return B_OK;
}
void
PackageVolumeState::GetPackagePath(const char* name, char* path,
size_t pathSize)
{
if (fName == NULL) {
strlcpy(path, name, pathSize);
} else {
snprintf(path, pathSize, "%s/%s/%s", kAdministrativeDirectory, fName,
name);
}
}
bool
PackageVolumeState::IsNewer(const PackageVolumeState* a,
const PackageVolumeState* b)
{
if (b->fName == NULL)
return false;
if (a->fName == NULL)
return true;
return strcmp(a->fName, b->fName) > 0;
}
PackageVolumeInfo::PackageVolumeInfo()
:
BReferenceable(),
fStates(),
fPackagesDir(NULL)
{
}
PackageVolumeInfo::~PackageVolumeInfo()
{
while (PackageVolumeState* state = fStates.RemoveHead())
delete state;
if (fPackagesDir != NULL)
closedir(fPackagesDir);
}
status_t
PackageVolumeInfo::SetTo(Directory* baseDirectory, const char* packagesPath)
{
TRACE("PackageVolumeInfo::SetTo()\n");
if (fPackagesDir != NULL)
closedir(fPackagesDir);
fPackagesDir = open_directory(baseDirectory, packagesPath);
if (fPackagesDir == NULL) {
TRACE("PackageVolumeInfo::SetTo(): failed to open packages directory: "
"%s\n", strerror(errno));
return errno;
}
Directory* packagesDirectory = directory_from(fPackagesDir);
packagesDirectory->Acquire();
PackageVolumeState* state = _AddState(NULL);
if (state == NULL)
return B_NO_MEMORY;
status_t error = _InitState(packagesDirectory, fPackagesDir, state);
if (error != B_OK) {
TRACE("PackageVolumeInfo::SetTo(): failed to init current state: "
"%s\n", strerror(error));
return error;
}
return B_OK;
}
status_t
PackageVolumeInfo::LoadOldStates()
{
if (fPackagesDir == NULL) {
TRACE("PackageVolumeInfo::LoadOldStates(): package directory is NULL");
return B_ERROR;
}
Directory* packagesDirectory = directory_from(fPackagesDir);
packagesDirectory->Acquire();
if (DIR* administrativeDir = open_directory(packagesDirectory,
kAdministrativeDirectory)) {
while (dirent* entry = readdir(administrativeDir)) {
if (strncmp(entry->d_name, "state_", 6) == 0) {
TRACE(" old state directory \"%s\"\n", entry->d_name);
_AddState(entry->d_name);
}
}
closedir(administrativeDir);
fStates.Sort(&PackageVolumeState::IsNewer);
PackageVolumeState* state = fStates.Head();
status_t error;
for (state = fStates.GetNext(state); state != NULL;) {
PackageVolumeState* nextState = fStates.GetNext(state);
if (state->Name() != NULL) {
error = _InitState(packagesDirectory, fPackagesDir, state);
if (error != B_OK) {
TRACE("PackageVolumeInfo::LoadOldStates(): failed to "
"init state \"%s\": %s\n", state->Name(),
strerror(error));
fStates.Remove(state);
delete state;
}
}
state = nextState;
}
} else {
TRACE("PackageVolumeInfo::LoadOldStates(): failed to open "
"administrative directory: %s\n", strerror(errno));
}
return B_OK;
}
PackageVolumeState*
PackageVolumeInfo::_AddState(const char* stateName)
{
ObjectDeleter<PackageVolumeState> state(new(std::nothrow) PackageVolumeState);
if (!state.IsSet())
return NULL;
if (state->SetTo(stateName) != B_OK) {
return NULL;
}
fStates.Add(state.Get());
return state.Detach();
}
status_t
PackageVolumeInfo::_InitState(Directory* packagesDirectory, DIR* dir,
PackageVolumeState* state)
{
ArrayDeleter<char> systemPackageName(new(std::nothrow) char[B_FILE_NAME_LENGTH]);
if (!systemPackageName.IsSet())
return B_NO_MEMORY;
ArrayDeleter<char> packagePath(new(std::nothrow) char[B_PATH_NAME_LENGTH]);
if (!packagePath.IsSet())
return B_NO_MEMORY;
status_t error = _ParseActivatedPackagesFile(packagesDirectory, state,
systemPackageName.Get(), B_FILE_NAME_LENGTH);
if (error == B_OK) {
for (PackageVolumeState* otherState = state; otherState != NULL;
otherState = fStates.GetPrevious(otherState)) {
otherState->GetPackagePath(systemPackageName.Get(), packagePath.Get(),
B_PATH_NAME_LENGTH);
struct stat st;
if (get_stat(packagesDirectory, packagePath.Get(), st) == B_OK
&& S_ISREG(st.st_mode)) {
state->SetSystemPackage(packagePath.Get());
break;
}
}
} else {
TRACE("PackageVolumeInfo::_InitState(): failed to parse "
"activated-packages: %s\n", strerror(error));
if (state->Name() != NULL)
return B_ENTRY_NOT_FOUND;
while (dirent* entry = readdir(dir)) {
if (is_system_package(entry->d_name)) {
state->SetSystemPackage(entry->d_name);
break;
}
}
}
if (state->SystemPackage() == NULL)
return B_ENTRY_NOT_FOUND;
return B_OK;
}
status_t
PackageVolumeInfo::_ParseActivatedPackagesFile(Directory* packagesDirectory,
PackageVolumeState* state, char* packageName, size_t packageNameSize)
{
static const size_t kBufferSize = 3 * B_FILE_NAME_LENGTH + 2;
ArrayDeleter<char> path(new(std::nothrow) char[kBufferSize]);
if (!path.IsSet())
return B_NO_MEMORY;
snprintf(path.Get(), kBufferSize, "%s/%s/%s",
kAdministrativeDirectory, state->Name() != NULL ? state->Name() : "",
kActivatedPackagesFile);
FileDescriptorCloser fd(open_from(packagesDirectory, path.Get(), O_RDONLY));
if (!fd.IsSet())
return fd.Get();
struct stat st;
if (fstat(fd.Get(), &st) != 0)
return errno;
if (!S_ISREG(st.st_mode))
return B_ENTRY_NOT_FOUND;
size_t remainingBytes = 0;
for (;;) {
ssize_t bytesRead = read(fd.Get(), path.Get() + remainingBytes,
kBufferSize - remainingBytes - 1);
if (bytesRead <= 0)
return B_ENTRY_NOT_FOUND;
remainingBytes += bytesRead;
path[remainingBytes] = '\0';
char* line = path.Get();
while (char* lineEnd = strchr(line, '\n')) {
*lineEnd = '\0';
if (is_system_package(line)) {
status_t result = strlcpy(packageName, line, packageNameSize)
< packageNameSize
? B_OK : B_NAME_TOO_LONG;
return result;
}
line = lineEnd + 1;
}
if (line < path.Get() + remainingBytes) {
size_t left = path.Get() + remainingBytes - line;
memmove(path.Get(), line, left);
remainingBytes = left;
} else
remainingBytes = 0;
}
return B_ENTRY_NOT_FOUND;
}