* WIP-commit of the first parts of the package kit and the pkgman

(console-)tool


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@40261 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Oliver Tappe 2011-01-21 22:18:58 +00:00
parent 90a81ace91
commit 500bb6305c
33 changed files with 2236 additions and 2 deletions

View File

@ -529,7 +529,8 @@ HAIKU_BUILD_DESCRIPTION ?= "Unknown Build" ;
{
local i ;
for i in be bnetapi debug game GL locale mail media midi midi2 network
opengl root screensaver textencoding tracker translation z {
opengl package root screensaver textencoding tracker translation
z {
HAIKU_LIBRARY_NAME_MAP_$(i) = lib$(i).so ;
}
HAIKU_LIBRARY_NAME_MAP_libstdc++ = $(HAIKU_LIBSTDC++) ;

View File

@ -0,0 +1,42 @@
/*
* Copyright 2011, Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#ifndef _HAIKU__PACKAGE__ADD_REPOSITORY_REQUEST_H_
#define _HAIKU__PACKAGE__ADD_REPOSITORY_REQUEST_H_
#include <String.h>
#include <package/Context.h>
#include <package/Request.h>
namespace Haiku {
namespace Package {
class AddRepositoryRequest : public Request {
typedef Request inherited;
public:
AddRepositoryRequest(const Context& context,
const BString& repositoryURL,
bool asUserRepository);
virtual ~AddRepositoryRequest();
virtual status_t CreateJobsToRun(JobQueue& jobQueue);
private:
BString fRepositoryURL;
bool fAsUserRepository;
};
} // namespace Package
} // namespace Haiku
#endif // _HAIKU__PACKAGE__ADD_REPOSITORY_REQUEST_H_

View File

@ -0,0 +1,42 @@
/*
* Copyright 2011, Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#ifndef _HAIKU__PACKAGE__CONTEXT_H_
#define _HAIKU__PACKAGE__CONTEXT_H_
#include <package/TempEntryManager.h>
namespace Haiku {
namespace Package {
class JobStateListener;
class Context {
public:
Context();
~Context();
TempEntryManager& GetTempEntryManager() const;
JobStateListener* DefaultJobStateListener() const;
void SetDefaultJobStateListener(
JobStateListener* listener);
private:
mutable TempEntryManager fTempEntryManager;
JobStateListener* fDefaultJobStateListener;
};
} // namespace Package
} // namespace Haiku
#endif // _HAIKU__PACKAGE__CONTEXT_H_

88
headers/os/package/Job.h Normal file
View File

@ -0,0 +1,88 @@
/*
* Copyright 2011, Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#ifndef _HAIKU__PACKAGE__JOB_H_
#define _HAIKU__PACKAGE__JOB_H_
#include <ObjectList.h>
#include <String.h>
namespace Haiku {
namespace Package {
class Job;
struct JobStateListener {
virtual ~JobStateListener();
// these default implementations do nothing
virtual void JobStarted(Job* job);
virtual void JobSucceeded(Job* job);
virtual void JobFailed(Job* job);
virtual void JobAborted(Job* job);
};
enum JobState {
JOB_STATE_WAITING_TO_RUN,
JOB_STATE_RUNNING,
JOB_STATE_SUCCEEDED,
JOB_STATE_FAILED,
JOB_STATE_ABORTED,
};
class Job {
public:
Job(const BString& title);
virtual ~Job();
status_t InitCheck() const;
virtual status_t Run();
const BString& Title() const;
JobState State() const;
status_t Result() const;
status_t AddStateListener(JobStateListener* listener);
status_t RemoveStateListener(
JobStateListener* listener);
status_t AddDependency(Job* job);
status_t RemoveDependency(Job* job);
int32 CountDependencies() const;
Job* DependantJobAt(int32 index) const;
protected:
virtual status_t Execute() = 0;
virtual void Cleanup(status_t jobResult);
void NotifyStateListeners();
private:
status_t fInitStatus;
BString fTitle;
JobState fState;
status_t fResult;
typedef BObjectList<Job> JobList;
JobList fDependencies;
JobList fDependantJobs;
typedef BObjectList<JobStateListener> StateListenerList;
StateListenerList fStateListeners;
};
} // namespace Package
} // namespace Haiku
#endif // _HAIKU__PACKAGE__JOB_H_

View File

@ -0,0 +1,51 @@
/*
* Copyright 2011, Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#ifndef _HAIKU__PACKAGE__JOB_QUEUE_H_
#define _HAIKU__PACKAGE__JOB_QUEUE_H_
#include <Locker.h>
#include <SupportDefs.h>
#include <package/Job.h>
namespace Haiku {
namespace Package {
class JobQueue : public JobStateListener {
public:
JobQueue();
~JobQueue();
status_t AddJob(Job* job);
status_t RemoveJob(Job* job);
Job* Pop();
// JobStateListener
virtual void JobSucceeded(Job* job);
virtual void JobFailed(Job* job);
private:
struct JobPriorityLess;
class JobPriorityQueue;
private:
void _UpdateDependantJobsOf(Job* job);
BLocker fLock;
JobPriorityQueue* fQueuedJobs;
};
} // namespace Package
} // namespace Haiku
#endif // _HAIKU__PACKAGE__JOB_QUEUE_H_

View File

@ -0,0 +1,24 @@
/*
* Copyright 2011, Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#ifndef _REPOSITORY_H_
#define _REPOSITORY_H_
#include <Archivable.h>
class BMessage;
class BRepository {
public:
BRepository(const char* url);
public:
static BArchivable* Instantiate(BMessage* archive);
};
#endif // _REPOSITORY_H_

View File

@ -0,0 +1,75 @@
/*
* Copyright 2011, Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#ifndef _HAIKU__PACKAGE__REPOSITORY_CONFIG_H_
#define _HAIKU__PACKAGE__REPOSITORY_CONFIG_H_
#include <Archivable.h>
#include <String.h>
class BEntry;
namespace Haiku {
namespace Package {
class RepositoryConfig : public BArchivable {
typedef BArchivable inherited;
public:
RepositoryConfig();
RepositoryConfig(const BString& name,
const BString& url,
uint8 priority = kDefaultPriority);
RepositoryConfig(const BEntry& entry);
RepositoryConfig(BMessage* data);
virtual ~RepositoryConfig();
virtual status_t Archive(BMessage* data, bool deep = true) const;
status_t StoreAsConfigFile(const BEntry& entry) const;
status_t InitCheck() const;
const BString& Name() const;
const BString& URL() const;
uint8 Priority() const;
bool IsUserSpecific() const;
void SetName(const BString& name);
void SetURL(const BString& url);
void SetPriority(uint8 priority);
void SetIsUserSpecific(bool isUserSpecific);
public:
static RepositoryConfig* Instantiate(BMessage* data);
static const uint8 kDefaultPriority = 50;
static const char* kNameField;
static const char* kURLField;
static const char* kPriorityField;
private:
status_t _InitFrom(const BEntry& entry);
status_t _InitFrom(const BMessage* data);
status_t fInitStatus;
BString fName;
BString fURL;
uint8 fPriority;
bool fIsUserSpecific;
};
} // namespace Package
} // namespace Haiku
#endif // _HAIKU__PACKAGE__REPOSITORY_CONFIG_H_

View File

@ -0,0 +1,43 @@
/*
* Copyright 2011, Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#ifndef _HAIKU__PACKAGE__REQUEST_H_
#define _HAIKU__PACKAGE__REQUEST_H_
#include <SupportDefs.h>
namespace Haiku {
namespace Package {
class Context;
class Job;
class JobQueue;
class Request {
public:
Request(const Context& context);
virtual ~Request();
virtual status_t CreateJobsToRun(JobQueue& jobQueue) = 0;
const Context& GetContext() const;
protected:
status_t QueueJob(Job* job, JobQueue& jobQueue) const;
private:
const Context& fContext;
};
} // namespace Package
} // namespace Haiku
#endif // _HAIKU__PACKAGE__REQUEST_H_

View File

@ -0,0 +1,55 @@
/*
* Copyright 2011, Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#ifndef _HAIKU__PACKAGE__ROSTER_H_
#define _HAIKU__PACKAGE__ROSTER_H_
#include <Entry.h>
#include <Path.h>
#include <SupportDefs.h>
namespace Haiku {
namespace Package {
struct RepositoryConfigVisitor {
virtual ~RepositoryConfigVisitor()
{
}
virtual status_t operator()(const BEntry& entry) = 0;
};
class Roster {
public:
Roster();
~Roster();
status_t GetCommonRepositoryConfigPath(BPath* path,
bool create = false) const;
status_t GetUserRepositoryConfigPath(BPath* path,
bool create = false) const;
status_t VisitCommonRepositoryConfigs(
RepositoryConfigVisitor& visitor);
status_t VisitUserRepositoryConfigs(
RepositoryConfigVisitor& visitor);
private:
status_t _VisitRepositoryConfigs(const BPath& path,
RepositoryConfigVisitor& visitor);
};
} // namespace Package
} // namespace Haiku
#endif // _HAIKU__PACKAGE__ROSTER_H_

View File

@ -0,0 +1,43 @@
/*
* Copyright 2011, Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#ifndef _HAIKU__PACKAGE__TEMP_ENTRY_MANAGER_H_
#define _HAIKU__PACKAGE__TEMP_ENTRY_MANAGER_H_
#include <Directory.h>
#include <Entry.h>
#include <String.h>
#include <SupportDefs.h>
namespace Haiku {
namespace Package {
class TempEntryManager {
public:
TempEntryManager();
~TempEntryManager();
void SetBaseDirectory(const BDirectory& baseDir);
BEntry Create(const BString& baseName = kDefaultName);
private:
static const BString kDefaultName;
private:
BDirectory fBaseDirectory;
vint32 fNextNumber;
};
} // namespace Package
} // namespace Haiku
#endif // _HAIKU__PACKAGE__TEMP_ENTRY_MANAGER_H_

View File

@ -0,0 +1,47 @@
/*
* Copyright 2011, Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#ifndef _HAIKU__PACKAGE__ACTIVATE_REPOSITORY_CONFIG_JOB_H_
#define _HAIKU__PACKAGE__ACTIVATE_REPOSITORY_CONFIG_JOB_H_
#include <Directory.h>
#include <Entry.h>
#include <String.h>
#include <package/Job.h>
namespace Haiku {
namespace Package {
class ActivateRepositoryConfigJob : public Job {
typedef Job inherited;
public:
ActivateRepositoryConfigJob(
const BString& title,
const BEntry& archivedRepoConfigEntry,
const BDirectory& targetDirectory);
virtual ~ActivateRepositoryConfigJob();
protected:
virtual status_t Execute();
virtual void Cleanup(status_t jobResult);
private:
BEntry fArchivedRepoConfigEntry;
BDirectory fTargetDirectory;
BEntry fTargetEntry;
};
} // namespace Package
} // namespace Haiku
#endif // _HAIKU__PACKAGE__ACTIVATE_REPOSITORY_CONFIG_JOB_H_

View File

@ -0,0 +1,44 @@
/*
* Copyright 2011, Haiku, Inc.
* Distributed under the terms of the MIT License.
*/
#ifndef _HAIKU__PACKAGE__FETCH_FILE_JOB_H_
#define _HAIKU__PACKAGE__FETCH_FILE_JOB_H_
#include <Entry.h>
#include <String.h>
#include <package/Job.h>
namespace Haiku {
namespace Package {
class FetchFileJob : public Job {
typedef Job inherited;
public:
FetchFileJob(const BString& title,
const BString& fileURL,
const BEntry& targetEntry);
virtual ~FetchFileJob();
protected:
virtual status_t Execute();
virtual void Cleanup(status_t jobResult);
private:
BString fFileURL;
BEntry fTargetEntry;
};
} // namespace Package
} // namespace Haiku
#endif // _HAIKU__PACKAGE__FETCH_FILE_JOB_H_

View File

@ -258,6 +258,7 @@ SubInclude HAIKU_TOP src bin package ;
SubInclude HAIKU_TOP src bin patch ;
SubInclude HAIKU_TOP src bin pc ;
SubInclude HAIKU_TOP src bin pcmcia-cs ;
SubInclude HAIKU_TOP src bin pkgman ;
SubInclude HAIKU_TOP src bin playsound ;
SubInclude HAIKU_TOP src bin rc ;
SubInclude HAIKU_TOP src bin rmd160 ;

12
src/bin/pkgman/Jamfile Normal file
View File

@ -0,0 +1,12 @@
SubDir HAIKU_TOP src bin pkgman ;
UsePrivateHeaders shared support ;
BinCommand pkgman :
command_add_repo.cpp
command_list_repos.cpp
pkgman.cpp
:
package be
$(TARGET_LIBSUPC++)
;

View File

@ -0,0 +1,120 @@
/*
* Copyright 2011, Oliver Tappe <zooey@hirschkaefere.de>
* Distributed under the terms of the MIT License.
*/
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <Errors.h>
#include <SupportDefs.h>
#include <package/AddRepositoryRequest.h>
#include <package/Context.h>
#include <package/JobQueue.h>
#include "pkgman.h"
// TODO: internationalization!
using namespace Haiku::Package;
static const char* kCommandUsage =
"Usage: %s add-repo <repo-URL> [<repo-URL> ...]\n"
"Adds one or more repositories by downloading them from the given URL(s).\n"
"\n"
;
static void
print_command_usage_and_exit(bool error)
{
fprintf(error ? stderr : stdout, kCommandUsage, kProgramName);
exit(error ? 1 : 0);
}
struct Listener : public JobStateListener {
virtual void JobStarted(Job* job)
{
printf("%s ...\n", job->Title().String());
}
virtual void JobSucceeded(Job* job)
{
}
virtual void JobFailed(Job* job)
{
DIE(job->Result(), "failed!");
}
virtual void JobAborted(Job* job)
{
DIE(job->Result(), "aborted");
}
};
int
command_add_repo(int argc, const char* const* argv)
{
bool asUserRepository = false;
while (true) {
static struct option sLongOptions[] = {
{ "help", no_argument, 0, 'h' },
{ "user", no_argument, 0, 'u' },
{ 0, 0, 0, 0 }
};
opterr = 0; // don't print errors
int c = getopt_long(argc, (char**)argv, "hu", sLongOptions, NULL);
if (c == -1)
break;
switch (c) {
case 'h':
print_command_usage_and_exit(false);
break;
case 'u':
asUserRepository = true;
break;
default:
print_command_usage_and_exit(true);
break;
}
}
// The remaining arguments are repo URLs, i. e. at least one more argument.
if (argc < optind + 1)
print_command_usage_and_exit(true);
const char* const* repoURLs = argv + optind;
int urlCount = argc - optind;
Context context;
status_t result;
Listener listener;
context.SetDefaultJobStateListener(&listener);
for (int i = 0; i < urlCount; ++i) {
AddRepositoryRequest request(context, repoURLs[i], asUserRepository);
JobQueue jobQueue;
result = request.CreateJobsToRun(jobQueue);
if (result != B_OK)
DIE(result, "unable to create necessary jobs");
while (Job* job = jobQueue.Pop()) {
result = job->Run();
delete job;
if (result == B_INTERRUPTED)
break;
}
}
return 0;
}

View File

@ -0,0 +1,145 @@
/*
* Copyright 2011, Oliver Tappe <zooey@hirschkaefere.de>
* Distributed under the terms of the MIT License.
*/
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <new>
#include <Entry.h>
#include <Errors.h>
#include <ObjectList.h>
#include <Path.h>
#include <package/RepositoryConfig.h>
#include <package/Roster.h>
#include "pkgman.h"
// TODO: internationalization!
using namespace Haiku::Package;
static const char* kCommandUsage =
"Usage:\n"
" %s list-repos [options]\n"
"Lists all configured package repositories.\n"
"\n"
;
static void
print_command_usage_and_exit(bool error)
{
fprintf(error ? stderr : stdout, kCommandUsage, kProgramName);
exit(error ? 1 : 0);
}
typedef BObjectList<RepositoryConfig> RepositoryConfigList;
struct RepositoryConfigCollector : public RepositoryConfigVisitor {
RepositoryConfigCollector(RepositoryConfigList& _repositoryConfigList);
status_t operator()(const BEntry& entry);
RepositoryConfigList& repositoryConfigList;
};
RepositoryConfigCollector::RepositoryConfigCollector(
RepositoryConfigList& _repositoryConfigList)
:
repositoryConfigList(_repositoryConfigList)
{
}
status_t
RepositoryConfigCollector::operator()(const BEntry& entry)
{
RepositoryConfig* repoConfig = new (std::nothrow) RepositoryConfig(entry);
if (repoConfig == NULL)
DIE(B_NO_MEMORY, "can't create repository-config object");
status_t result = repoConfig->InitCheck();
if (result != B_OK) {
BPath path;
entry.GetPath(&path);
WARN(result, "skipping repository-config '%s'", path.Path());
delete repoConfig;
return B_OK;
// let collector continue
}
return repositoryConfigList.AddItem(repoConfig) ? B_OK : B_NO_MEMORY;
}
int
command_list_repos(int argc, const char* const* argv)
{
bool verbose = false;
while (true) {
static struct option sLongOptions[] = {
{ "help", no_argument, 0, 'h' },
{ "verbose", no_argument, 0, 'v' },
{ 0, 0, 0, 0 }
};
opterr = 0; // don't print errors
int c = getopt_long(argc, (char**)argv, "hv", sLongOptions, NULL);
if (c == -1)
break;
switch (c) {
case 'h':
print_command_usage_and_exit(false);
break;
case 'v':
verbose = true;
break;
default:
print_command_usage_and_exit(true);
break;
}
}
// No remaining arguments.
if (argc != optind)
print_command_usage_and_exit(true);
Roster roster;
RepositoryConfigList repositoryConfigs;
RepositoryConfigCollector repositoryConfigCollector(repositoryConfigs);
status_t result
= roster.VisitCommonRepositoryConfigs(repositoryConfigCollector);
if (result != B_OK && result != B_ENTRY_NOT_FOUND)
DIE(result, "can't collect common repository configs");
result = roster.VisitUserRepositoryConfigs(repositoryConfigCollector);
if (result != B_OK && result != B_ENTRY_NOT_FOUND)
DIE(result, "can't collect user's repository configs");
int32 count = repositoryConfigs.CountItems();
for (int32 i = 0; i < count; ++i) {
RepositoryConfig* repoConfig = repositoryConfigs.ItemAt(i);
printf(" %s %s\n",
repoConfig->IsUserSpecific() ? "[User]" : " ",
repoConfig->Name().String());
}
return 0;
}

72
src/bin/pkgman/pkgman.cpp Normal file
View File

@ -0,0 +1,72 @@
/*
* Copyright 2011, Oliver Tappe <zooey@hirschkaefer.de>
* Distributed under the terms of the MIT License.
*/
#include "pkgman.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern const char* __progname;
const char* kProgramName = __progname;
static const char* kUsage =
"Usage: %s <command> <command args>\n"
"Creates, inspects, or extracts a Haiku package.\n"
"\n"
"Commands:\n"
" add-repo <repo-base-url>\n"
" Adds the repository with the given <repo-base-URL>.\n"
"\n"
" list-repos\n"
" Lists all repositories.\n"
"\n"
" drop-repo <repo-name>\n"
" Drops the repository with the given <repo-name>.\n"
"\n"
"Common Options:\n"
" -h, --help - Print this usage info.\n"
;
void
print_usage_and_exit(bool error)
{
fprintf(error ? stderr : stdout, kUsage, kProgramName);
exit(error ? 1 : 0);
}
int
main(int argc, const char* const* argv)
{
if (argc < 2)
print_usage_and_exit(true);
const char* command = argv[1];
if (strcmp(command, "add-repo") == 0)
return command_add_repo(argc - 1, argv + 1);
// if (strcmp(command, "drop-repo") == 0)
// return command_drop_repo(argc - 1, argv + 1);
if (strcmp(command, "list-repos") == 0)
return command_list_repos(argc - 1, argv + 1);
// if (strcmp(command, "search") == 0)
// return command_search(argc - 1, argv + 1);
if (strcmp(command, "help") == 0)
print_usage_and_exit(false);
else
print_usage_and_exit(true);
// never gets here
return 0;
}

44
src/bin/pkgman/pkgman.h Normal file
View File

@ -0,0 +1,44 @@
/*
* Copyright 2011, Oliver Tappe <zooey@hirschkaefer.de>
* Distributed under the terms of the MIT License.
*/
#ifndef PKGMAN_H
#define PKGMAN_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern const char* kProgramName;
#define DIE(result, msg...) \
do { \
fprintf(stderr, "*** " msg); \
fprintf(stderr, " : %s\n", strerror(result)); \
exit(5); \
} while(0)
#define ERROR(result, msg...) \
do { \
fprintf(stderr, "*** " msg); \
fprintf(stderr, " : %s\n", strerror(result)); \
} while(0)
#define WARN(result, msg...) \
do { \
fprintf(stderr, "* " msg); \
fprintf(stderr, " : %s\n", strerror(result)); \
} while(0)
void print_usage_and_exit(bool error);
int command_add_repo(int argc, const char* const* argv);
int command_drop_repo(int argc, const char* const* argv);
int command_list_repos(int argc, const char* const* argv);
#endif // PKGMAN_H

View File

@ -83,6 +83,7 @@ SubInclude HAIKU_TOP src kits midi2 ;
SubInclude HAIKU_TOP src kits network ;
SubInclude HAIKU_TOP src kits notification ;
SubInclude HAIKU_TOP src kits opengl ;
SubInclude HAIKU_TOP src kits package ;
SubInclude HAIKU_TOP src kits print ;
SubInclude HAIKU_TOP src kits screensaver ;
SubInclude HAIKU_TOP src kits shared ;

View File

@ -0,0 +1,92 @@
#include <stdio.h>
#include <Path.h>
/*
* Copyright 2011, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Oliver Tappe <zooey@hirschkaefer.de>
*/
#include <package/ActivateRepositoryConfigJob.h>
#include <stdlib.h>
#include <File.h>
#include <package/RepositoryConfig.h>
namespace Haiku {
namespace Package {
ActivateRepositoryConfigJob::ActivateRepositoryConfigJob(const BString& title,
const BEntry& archivedRepoConfigEntry, const BDirectory& targetDirectory)
:
inherited(title),
fArchivedRepoConfigEntry(archivedRepoConfigEntry),
fTargetDirectory(targetDirectory)
{
}
ActivateRepositoryConfigJob::~ActivateRepositoryConfigJob()
{
}
status_t
ActivateRepositoryConfigJob::Execute()
{
BFile archiveFile(&fArchivedRepoConfigEntry, B_READ_ONLY);
BPath p;
fArchivedRepoConfigEntry.GetPath(&p);
printf("Execute(): arce=%s\n", p.Path());
status_t result = archiveFile.InitCheck();
if (result != B_OK)
return result;
printf("Execute(): 2\n");
BMessage archive;
if ((result = archive.Unflatten(&archiveFile)) != B_OK)
return result;
printf("Execute(): 3\n");
RepositoryConfig* repoConfig = RepositoryConfig::Instantiate(&archive);
if (repoConfig == NULL)
return B_BAD_DATA;
printf("Execute(): 4\n");
if ((result = repoConfig->InitCheck()) != B_OK)
return result;
printf("Execute(): 5\n");
fTargetEntry.SetTo(&fTargetDirectory, repoConfig->Name().String());
if (fTargetEntry.Exists()) {
// TODO: ask user whether to clobber or not
printf("Execute(): 5b\n");
return B_INTERRUPTED;
}
printf("Execute(): 6\n");
if ((result = repoConfig->StoreAsConfigFile(fTargetEntry)) != B_OK)
return result;
printf("Execute(): 7\n");
return B_OK;
}
void
ActivateRepositoryConfigJob::Cleanup(status_t jobResult)
{
if (jobResult != B_OK && State() != JOB_STATE_ABORTED)
fTargetEntry.Remove();
}
} // namespace Package
} // namespace Haiku

View File

@ -0,0 +1,83 @@
/*
* Copyright 2011, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Oliver Tappe <zooey@hirschkaefer.de>
*/
#include <package/AddRepositoryRequest.h>
#include <Directory.h>
#include <Path.h>
#include <package/ActivateRepositoryConfigJob.h>
#include <package/FetchFileJob.h>
#include <package/JobQueue.h>
#include <package/Roster.h>
namespace Haiku {
namespace Package {
AddRepositoryRequest::AddRepositoryRequest(const Context& context,
const BString& repositoryURL, bool asUserRepository)
:
inherited(context),
fRepositoryURL(repositoryURL),
fAsUserRepository(asUserRepository)
{
}
AddRepositoryRequest::~AddRepositoryRequest()
{
}
status_t
AddRepositoryRequest::CreateJobsToRun(JobQueue& jobQueue)
{
BEntry tempEntry
= GetContext().GetTempEntryManager().Create("repoconfig-");
FetchFileJob* fetchJob = new (std::nothrow) FetchFileJob(
BString("Fetching repository-config from ") << fRepositoryURL,
fRepositoryURL, tempEntry);
if (fetchJob == NULL)
return B_NO_MEMORY;
status_t result = QueueJob(fetchJob, jobQueue);
if (result != B_OK) {
delete fetchJob;
return result;
}
Roster roster;
BPath targetRepoConfigPath;
result = fAsUserRepository
? roster.GetUserRepositoryConfigPath(&targetRepoConfigPath, true)
: roster.GetCommonRepositoryConfigPath(&targetRepoConfigPath, true);
if (result != B_OK)
return result;
BDirectory targetDirectory(targetRepoConfigPath.Path());
ActivateRepositoryConfigJob* activateJob
= new (std::nothrow) ActivateRepositoryConfigJob(
BString("Activating repository-config from ") << fRepositoryURL,
tempEntry, targetDirectory);
if (activateJob == NULL)
return B_NO_MEMORY;
activateJob->AddDependency(fetchJob);
if ((result = QueueJob(activateJob, jobQueue)) != B_OK) {
delete activateJob;
return result;
}
return B_OK;
}
} // namespace Package
} // namespace Haiku

View File

@ -0,0 +1,67 @@
/*
* Copyright 2011, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Oliver Tappe <zooey@hirschkaefer.de>
*/
#include <package/Context.h>
#include <Directory.h>
#include <FindDirectory.h>
#include <OS.h>
#include <Path.h>
namespace Haiku {
namespace Package {
Context::Context()
:
fDefaultJobStateListener(NULL)
{
BPath tempPath;
if (find_directory(B_COMMON_TEMP_DIRECTORY, &tempPath) != B_OK)
tempPath.SetTo("/tmp");
BDirectory tempDirectory(tempPath.Path());
BString contextName = BString("pkgkit-context-") << find_thread(NULL);
BDirectory baseDirectory;
tempDirectory.CreateDirectory(contextName.String(), &baseDirectory);
fTempEntryManager.SetBaseDirectory(baseDirectory);
}
Context::~Context()
{
}
TempEntryManager&
Context::GetTempEntryManager() const
{
return fTempEntryManager;
}
JobStateListener*
Context::DefaultJobStateListener() const
{
return fDefaultJobStateListener;
}
void
Context::SetDefaultJobStateListener(JobStateListener* listener)
{
fDefaultJobStateListener = listener;
}
} // namespace Package
} // namespace Haiku

View File

@ -0,0 +1,70 @@
/*
* Copyright 2011, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Oliver Tappe <zooey@hirschkaefer.de>
*/
#include <package/FetchFileJob.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <Path.h>
namespace Haiku {
namespace Package {
FetchFileJob::FetchFileJob(const BString& title, const BString& fileURL,
const BEntry& targetEntry)
:
inherited(title),
fFileURL(fileURL),
fTargetEntry(targetEntry)
{
}
FetchFileJob::~FetchFileJob()
{
}
status_t
FetchFileJob::Execute()
{
BPath targetPath;
status_t result = fTargetEntry.GetPath(&targetPath);
if (result != B_OK)
return result;
// TODO: implement for real, maybe using the "service kit"-GSOC HTTP-stuff?
BString cmd = BString("curl -# -o ") << targetPath.Path() << " "
<< fFileURL.String();
int cmdResult = system(cmd.String());
if (WIFSIGNALED(cmdResult)
&& (WTERMSIG(cmdResult) == SIGINT || WTERMSIG(cmdResult) == SIGQUIT)) {
return B_INTERRUPTED;
}
return cmdResult == 0 ? B_OK : B_ERROR;
}
void
FetchFileJob::Cleanup(status_t jobResult)
{
if (jobResult != B_OK)
fTargetEntry.Remove();
}
} // namespace Package
} // namespace Haiku

19
src/kits/package/Jamfile Normal file
View File

@ -0,0 +1,19 @@
SubDir HAIKU_TOP src kits package ;
UsePrivateHeaders package ;
SharedLibrary libpackage.so
:
ActivateRepositoryConfigJob.cpp
AddRepositoryRequest.cpp
Context.cpp
FetchFileJob.cpp
Job.cpp
JobQueue.cpp
RepositoryConfig.cpp
Request.cpp
Roster.cpp
TempEntryManager.cpp
:
be $(TARGET_LIBSTDC++)
;

207
src/kits/package/Job.cpp Normal file
View File

@ -0,0 +1,207 @@
/*
* Copyright 2011, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Oliver Tappe <zooey@hirschkaefer.de>
*/
#include <package/Job.h>
#include <Errors.h>
namespace Haiku {
namespace Package {
JobStateListener::~JobStateListener()
{
}
void
JobStateListener::JobStarted(Job* job)
{
}
void
JobStateListener::JobSucceeded(Job* job)
{
}
void
JobStateListener::JobFailed(Job* job)
{
}
void
JobStateListener::JobAborted(Job* job)
{
}
Job::Job(const BString& title)
:
fTitle(title),
fState(JOB_STATE_WAITING_TO_RUN)
{
if (fTitle.Length() == 0)
fInitStatus = B_BAD_VALUE;
else
fInitStatus = B_OK;
}
Job::~Job()
{
}
status_t
Job::InitCheck() const
{
return fInitStatus;
}
const BString&
Job::Title() const
{
return fTitle;
}
JobState
Job::State() const
{
return fState;
}
status_t
Job::Result() const
{
return fResult;
}
status_t
Job::Run()
{
if (fState != JOB_STATE_WAITING_TO_RUN)
return B_NOT_ALLOWED;
fState = JOB_STATE_RUNNING;
NotifyStateListeners();
fResult = Execute();
Cleanup(fResult);
fState = fResult == B_OK
? JOB_STATE_SUCCEEDED
: fResult == B_INTERRUPTED
? JOB_STATE_ABORTED
: JOB_STATE_FAILED;
NotifyStateListeners();
return fResult;
}
void
Job::Cleanup(status_t /*jobResult*/)
{
}
status_t
Job::AddStateListener(JobStateListener* listener)
{
return fStateListeners.AddItem(listener) ? B_OK : B_ERROR;
}
status_t
Job::RemoveStateListener(JobStateListener* listener)
{
return fStateListeners.RemoveItem(listener) ? B_OK : B_ERROR;
}
status_t
Job::AddDependency(Job* job)
{
if (fDependencies.HasItem(job))
return B_ERROR;
if (fDependencies.AddItem(job) && job->fDependantJobs.AddItem(this))
return B_OK;
return B_ERROR;
}
status_t
Job::RemoveDependency(Job* job)
{
if (!fDependencies.HasItem(job))
return B_ERROR;
if (fDependencies.RemoveItem(job) && job->fDependantJobs.RemoveItem(this))
return B_OK;
return B_ERROR;
}
int32
Job::CountDependencies() const
{
return fDependencies.CountItems();
}
Job*
Job::DependantJobAt(int32 index) const
{
return fDependantJobs.ItemAt(index);
}
void
Job::NotifyStateListeners()
{
int32 count = fStateListeners.CountItems();
for (int i = 0; i < count; ++i) {
JobStateListener* listener = fStateListeners.ItemAt(i);
if (listener == NULL)
continue;
switch (fState) {
case JOB_STATE_RUNNING:
listener->JobStarted(this);
break;
case JOB_STATE_SUCCEEDED:
listener->JobSucceeded(this);
break;
case JOB_STATE_FAILED:
listener->JobFailed(this);
break;
case JOB_STATE_ABORTED:
listener->JobAborted(this);
break;
default:
break;
}
}
}
} // namespace Package
} // namespace Haiku

View File

@ -0,0 +1,155 @@
/*
* Copyright 2011, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Oliver Tappe <zooey@hirschkaefer.de>
*/
#include <package/JobQueue.h>
#include <set>
#include <Autolock.h>
#include <package/Job.h>
namespace Haiku {
namespace Package {
struct JobQueue::JobPriorityLess {
bool operator()(const Job* left, const Job* right) const;
};
bool
JobQueue::JobPriorityLess::operator()(const Job* left, const Job* right) const
{
int32 difference = left->CountDependencies() - right->CountDependencies();
if (difference < 0)
return true;
if (difference > 0)
return false;
return left->Title() < right->Title();
};
class JobQueue::JobPriorityQueue
: public std::set<Job*, JobPriorityLess> {
};
JobQueue::JobQueue()
:
fLock("job queue"),
fQueuedJobs(new (std::nothrow) JobPriorityQueue())
{
}
JobQueue::~JobQueue()
{
}
status_t
JobQueue::AddJob(Job* job)
{
if (fQueuedJobs == NULL)
return B_NO_INIT;
BAutolock lock(&fLock);
if (lock.IsLocked()) {
try {
fQueuedJobs->insert(job);
} catch (const std::bad_alloc& e) {
return B_NO_MEMORY;
} catch (...) {
return B_NO_MEMORY;
}
job->AddStateListener(this);
}
return B_OK;
}
status_t
JobQueue::RemoveJob(Job* job)
{
if (fQueuedJobs == NULL)
return B_NO_INIT;
BAutolock lock(&fLock);
if (lock.IsLocked()) {
try {
fQueuedJobs->erase(job);
} catch (...) {
return B_ERROR;
}
job->RemoveStateListener(this);
}
return B_OK;
}
void
JobQueue::JobSucceeded(Job* job)
{
_UpdateDependantJobsOf(job);
}
void
JobQueue::JobFailed(Job* job)
{
_UpdateDependantJobsOf(job);
}
Job*
JobQueue::Pop()
{
BAutolock lock(&fLock);
if (lock.IsLocked()) {
JobPriorityQueue::iterator head = fQueuedJobs->begin();
if (head == fQueuedJobs->end())
return NULL;
fQueuedJobs->erase(head);
return *head;
}
return NULL;
}
void
JobQueue::_UpdateDependantJobsOf(Job* job)
{
BAutolock lock(&fLock);
if (lock.IsLocked()) {
while (Job* dependantJob = job->DependantJobAt(0)) {
try {
fQueuedJobs->erase(dependantJob);
} catch (...) {
}
dependantJob->RemoveDependency(job);
try {
fQueuedJobs->insert(dependantJob);
} catch (...) {
}
}
}
}
} // namespace Package
} // namespace Haiku

View File

@ -0,0 +1,261 @@
/*
* Copyright 2011, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Oliver Tappe <zooey@hirschkaefer.de>
*/
#include <package/RepositoryConfig.h>
#include <stdlib.h>
#include <new>
#include <Directory.h>
#include <driver_settings.h>
#include <Entry.h>
#include <File.h>
#include <FindDirectory.h>
#include <Path.h>
namespace Haiku {
namespace Package {
const char* RepositoryConfig::kNameField = "name";
const char* RepositoryConfig::kURLField = "url";
const char* RepositoryConfig::kPriorityField = "priority";
RepositoryConfig::RepositoryConfig()
:
fInitStatus(B_NO_INIT),
fPriority(kDefaultPriority),
fIsUserSpecific(false)
{
}
RepositoryConfig::RepositoryConfig(const BEntry& entry)
{
fInitStatus = _InitFrom(entry);
}
RepositoryConfig::RepositoryConfig(BMessage* data)
:
inherited(data)
{
fInitStatus = _InitFrom(data);
}
RepositoryConfig::RepositoryConfig(const BString& name, const BString& url,
uint8 priority)
:
fInitStatus(B_OK),
fName(name),
fURL(url),
fPriority(priority),
fIsUserSpecific(false)
{
}
RepositoryConfig::~RepositoryConfig()
{
}
/*static*/ RepositoryConfig*
RepositoryConfig::Instantiate(BMessage* data)
{
if (validate_instantiation(data, "Haiku::Package::RepositoryConfig"))
return new (std::nothrow) RepositoryConfig(data);
return NULL;
}
status_t
RepositoryConfig::Archive(BMessage* data, bool deep) const
{
status_t result = inherited::Archive(data, deep);
if (result != B_OK)
return result;
if ((result = data->AddString(kNameField, fName)) != B_OK)
return result;
if ((result = data->AddString(kURLField, fURL)) != B_OK)
return result;
if ((result = data->AddUInt8(kPriorityField, fPriority)) != B_OK)
return result;
return B_OK;
}
status_t
RepositoryConfig::StoreAsConfigFile(const BEntry& entry) const
{
BFile file(&entry, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
status_t result = file.InitCheck();
if (result != B_OK)
return result;
BString configString;
configString
<< "name=" << fName << "\n"
<< "url=" << fURL << "\n"
<< "priority=" << fPriority << "\n";
int32 size = configString.Length();
if ((result = file.Write(configString.String(), size)) < size)
return (result >= 0) ? B_ERROR : result;
return B_OK;
}
status_t
RepositoryConfig::InitCheck() const
{
return fInitStatus;
}
const BString&
RepositoryConfig::Name() const
{
return fName;
}
const BString&
RepositoryConfig::URL() const
{
return fURL;
}
uint8
RepositoryConfig::Priority() const
{
return fPriority;
}
bool
RepositoryConfig::IsUserSpecific() const
{
return fIsUserSpecific;
}
void
RepositoryConfig::SetName(const BString& name)
{
fName = name;
}
void
RepositoryConfig::SetURL(const BString& url)
{
fURL = url;
}
void
RepositoryConfig::SetPriority(uint8 priority)
{
fPriority = priority;
}
void
RepositoryConfig::SetIsUserSpecific(bool isUserSpecific)
{
fIsUserSpecific = isUserSpecific;
}
status_t
RepositoryConfig::_InitFrom(const BEntry& entry)
{
BFile file(&entry, B_READ_ONLY);
status_t result = file.InitCheck();
if (result != B_OK)
return result;
off_t size;
if ((result = file.GetSize(&size)) != B_OK)
return result;
BString configString;
char* buffer = configString.LockBuffer(size);
if (buffer == NULL)
return B_NO_MEMORY;
if ((result = file.Read(buffer, size)) < size)
return (result >= 0) ? B_ERROR : result;
configString.UnlockBuffer(size);
void* settingsHandle = parse_driver_settings_string(configString.String());
if (settingsHandle == NULL)
return B_BAD_DATA;
const char* name = get_driver_parameter(settingsHandle, "name", NULL, NULL);
const char* url = get_driver_parameter(settingsHandle, "url", NULL, NULL);
const char* priorityString
= get_driver_parameter(settingsHandle, "priority", NULL, NULL);
unload_driver_settings(settingsHandle);
if (name == NULL || *name == '\0' || url == NULL || *url == '\0')
return B_BAD_DATA;
fName = name;
fURL = url;
fPriority = priorityString == NULL
? kDefaultPriority : atoi(priorityString);
BPath userSettingsPath;
if (find_directory(B_USER_SETTINGS_DIRECTORY, &userSettingsPath) == B_OK) {
BDirectory userSettingsDir(userSettingsPath.Path());
fIsUserSpecific = userSettingsDir.Contains(&entry);
} else
fIsUserSpecific = false;
return B_OK;
}
status_t
RepositoryConfig::_InitFrom(const BMessage* data)
{
if (data == NULL)
return B_BAD_VALUE;
status_t result;
if ((result = data->FindString(kNameField, &fName)) != B_OK)
return result;
if ((result = data->FindString(kURLField, &fURL)) != B_OK)
return result;
if ((result = data->FindUInt8(kPriorityField, &fPriority)) != B_OK)
return result;
fIsUserSpecific = false;
return B_OK;
}
} // namespace Package
} // namespace Haiku

View File

@ -0,0 +1,54 @@
/*
* Copyright 2011, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Oliver Tappe <zooey@hirschkaefer.de>
*/
#include <package/Request.h>
#include <package/Context.h>
#include <package/Job.h>
#include <package/JobQueue.h>
namespace Haiku {
namespace Package {
Request::Request(const Context& context)
:
fContext(context)
{
}
Request::~Request()
{
}
const Context&
Request::GetContext() const
{
return fContext;
}
status_t
Request::QueueJob(Job* job, JobQueue& jobQueue) const
{
JobStateListener* defaultListener = fContext.DefaultJobStateListener();
if (defaultListener != NULL)
job->AddStateListener(defaultListener);
return jobQueue.AddJob(job);
}
} // namespace Package
} // namespace Haiku

130
src/kits/package/Roster.cpp Normal file
View File

@ -0,0 +1,130 @@
/*
* Copyright 2011, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Oliver Tappe <zooey@hirschkaefer.de>
*/
#include <package/Roster.h>
#include <errno.h>
#include <sys/stat.h>
#include <Directory.h>
#include <Entry.h>
#include <Path.h>
#include <FindDirectory.h>
namespace Haiku {
namespace Package {
Roster::Roster()
{
}
Roster::~Roster()
{
}
status_t
Roster::GetCommonRepositoryConfigPath(BPath* path, bool create) const
{
if (path == NULL)
return B_BAD_VALUE;
status_t result = find_directory(B_COMMON_SETTINGS_DIRECTORY, path);
if (result != B_OK)
return result;
if ((result = path->Append("package-repositories")) != B_OK)
return result;
if (create) {
BEntry entry(path->Path(), true);
if (!entry.Exists()) {
if (mkdir(path->Path(), 0755) != 0)
return errno;
}
}
return B_OK;
}
status_t
Roster::GetUserRepositoryConfigPath(BPath* path, bool create) const
{
if (path == NULL)
return B_BAD_VALUE;
status_t result = find_directory(B_USER_SETTINGS_DIRECTORY, path);
if (result != B_OK)
return result;
if ((result = path->Append("package-repositories")) != B_OK)
return result;
if (create) {
BEntry entry(path->Path(), true);
if (!entry.Exists()) {
if (mkdir(path->Path(), 0755) != 0)
return errno;
}
}
return B_OK;
}
status_t
Roster::VisitCommonRepositoryConfigs(RepositoryConfigVisitor& visitor)
{
BPath commonRepositoryConfigPath;
status_t result
= GetCommonRepositoryConfigPath(&commonRepositoryConfigPath);
if (result == B_OK)
result = _VisitRepositoryConfigs(commonRepositoryConfigPath, visitor);
return result;
}
status_t
Roster::VisitUserRepositoryConfigs(RepositoryConfigVisitor& visitor)
{
BPath userRepositoryConfigPath;
status_t result = GetUserRepositoryConfigPath(&userRepositoryConfigPath);
if (result == B_OK)
result = _VisitRepositoryConfigs(userRepositoryConfigPath, visitor);
return result;
}
status_t
Roster::_VisitRepositoryConfigs(const BPath& path,
RepositoryConfigVisitor& visitor)
{
BDirectory directory(path.Path());
status_t result = directory.InitCheck();
if (result != B_OK)
return result;
BEntry entry;
while (directory.GetNextEntry(&entry, true) == B_OK) {
if ((result = visitor(entry)) != B_OK)
return result;
}
return B_OK;
}
} // namespace Package
} // namespace Haiku

View File

@ -0,0 +1,61 @@
/*
* Copyright 2011, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Oliver Tappe <zooey@hirschkaefer.de>
*/
#include <package/TempEntryManager.h>
namespace Haiku {
namespace Package {
const BString TempEntryManager::kDefaultName = "tmp-pkgkit-file-";
TempEntryManager::TempEntryManager()
:
fNextNumber(1)
{
}
TempEntryManager::~TempEntryManager()
{
if (fBaseDirectory.InitCheck() != B_OK)
return;
fBaseDirectory.Rewind();
BEntry entry;
while (fBaseDirectory.GetNextEntry(&entry) == B_OK)
entry.Remove();
fBaseDirectory.GetEntry(&entry);
entry.Remove();
}
void
TempEntryManager::SetBaseDirectory(const BDirectory& baseDirectory)
{
fBaseDirectory = baseDirectory;
}
BEntry
TempEntryManager::Create(const BString& baseName)
{
BString name = BString(baseName) << atomic_add(&fNextNumber, 1);
return BEntry(&fBaseDirectory, name.String());
}
} // namespace Package
} // namespace Haiku

View File

@ -7,9 +7,10 @@ SubInclude HAIKU_TOP src tests kits locale ;
SubInclude HAIKU_TOP src tests kits media ;
SubInclude HAIKU_TOP src tests kits midi ;
SubInclude HAIKU_TOP src tests kits net ;
SubInclude HAIKU_TOP src tests kits opengl ;
SubInclude HAIKU_TOP src tests kits package ;
SubInclude HAIKU_TOP src tests kits shared ;
SubInclude HAIKU_TOP src tests kits storage ;
SubInclude HAIKU_TOP src tests kits support ;
SubInclude HAIKU_TOP src tests kits translation ;
SubInclude HAIKU_TOP src tests kits opengl ;

View File

@ -0,0 +1,4 @@
SubDir HAIKU_TOP src tests kits package ;
SimpleTest make_repo : make_repo.cpp : package be ;

View File

@ -0,0 +1,80 @@
#include <stdio.h>
#include <stdlib.h>
#include <File.h>
#include <Message.h>
#include <String.h>
#include <package/RepositoryConfig.h>
using namespace Haiku::Package;
int
main(int argc, const char** argv)
{
if (argc < 5) {
fprintf(stderr, "usage: %s <name> <url> <priority> <pkg-count>\n",
argv[0]);
return 1;
}
RepositoryConfig repoConfig(argv[1], argv[2], atoi(argv[3]));
status_t status = repoConfig.InitCheck();
if (status != B_OK) {
fprintf(stderr, "couldn't initialize repository-config\n");
return 1;
}
BMessage repoConfigArchive;
if ((status = repoConfig.Archive(&repoConfigArchive)) != B_OK) {
fprintf(stderr, "couldn't archive repository-config\n");
return 1;
}
BFile output(argv[1], B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
if ((status = repoConfigArchive.Flatten(&output)) != B_OK) {
fprintf(stderr, "couldn't flatten repository-config archive\n");
return 1;
}
int pkgCount = atoi(argv[4]);
for (int i = 0; i < pkgCount; ++i) {
BMessage pkg('PKGp');
BString name = BString("pkg") << i + 1;
pkg.AddString("name", name.String());
BString majorVersion
= BString() << 1 + i % 5 << "." << i % 10;
BString minorVersion
= BString() << i % 100;
pkg.AddString("version",
(BString() << majorVersion << "." << minorVersion).String());
pkg.AddString("provides",
(BString() << name << "-" << majorVersion).String());
if (i % 2 == 1)
pkg.AddString("provides", (BString("lib") << name).String());
if (i % 3 != 1)
pkg.AddString("provides", (BString("cmd:") << name).String());
if (i > 1) {
int requiresCount = rand() % 10 % i;
for (int r = 0; r < requiresCount; ++r) {
int reqIndex = rand() % i;
pkg.AddString("requires",
(BString("pkg") << reqIndex).String());
}
}
status = pkg.Flatten(&output);
if (status != B_OK) {
fprintf(stderr, "couldn't flatten pkg message #%d\n", i + 1);
return 1;
}
}
return 0;
}