1/*
2 * Copyright 2013-2020, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Ingo Weinhold <ingo_weinhold@gmx.de>
7 *		Andrew Lindesay <apl@lindesay.co.nz>
8 */
9
10
11#include <package/manager/RepositoryBuilder.h>
12
13#include <errno.h>
14#include <dirent.h>
15
16#include <Entry.h>
17#include <package/RepositoryCache.h>
18#include <Path.h>
19
20#include <AutoDeleter.h>
21#include <AutoDeleterPosix.h>
22
23#include "PackageManagerUtils.h"
24
25
26namespace BPackageKit {
27
28namespace BManager {
29
30namespace BPrivate {
31
32
33namespace {
34
35
36class PackageInfoErrorListener : public BPackageInfo::ParseErrorListener {
37public:
38	PackageInfoErrorListener(const char* context)
39		:
40		fContext(context)
41	{
42	}
43
44	virtual void OnError(const BString& message, int line, int column)
45	{
46		fErrors << BString().SetToFormat("%s: parse error in line %d:%d: %s\n",
47			fContext, line, column, message.String());
48	}
49
50	const BString& Errors() const
51	{
52		return fErrors;
53	}
54
55private:
56	const char*	fContext;
57	BString		fErrors;
58};
59
60
61} // unnamed namespace
62
63
64BRepositoryBuilder::BRepositoryBuilder(BSolverRepository& repository)
65	:
66	fRepository(repository),
67	fErrorName(repository.Name()),
68	fPackagePaths(NULL)
69{
70}
71
72
73BRepositoryBuilder::BRepositoryBuilder(BSolverRepository& repository,
74	const BString& name, const BString& errorName)
75	:
76	fRepository(repository),
77	fErrorName(errorName.IsEmpty() ? name : errorName),
78	fPackagePaths(NULL)
79{
80	status_t error = fRepository.SetTo(name);
81	if (error != B_OK)
82		DIE(error, "failed to init %s repository", fErrorName.String());
83}
84
85
86BRepositoryBuilder::BRepositoryBuilder(BSolverRepository& repository,
87	const BRepositoryConfig& config)
88	:
89	fRepository(repository),
90	fErrorName(fRepository.Name()),
91	fPackagePaths(NULL)
92{
93	status_t error = fRepository.SetTo(config);
94	if (error != B_OK)
95		DIE(error, "failed to init %s repository", fErrorName.String());
96}
97
98
99BRepositoryBuilder::BRepositoryBuilder(BSolverRepository& repository,
100	const BRepositoryCache& cache, const BString& errorName)
101	:
102	fRepository(repository),
103	fErrorName(errorName.IsEmpty() ? cache.Info().Name() : errorName),
104	fPackagePaths(NULL)
105{
106	status_t error = fRepository.SetTo(cache);
107	if (error != B_OK)
108		DIE(error, "failed to init %s repository", fErrorName.String());
109	fErrorName = fRepository.Name();
110}
111
112
113BRepositoryBuilder&
114BRepositoryBuilder::SetPackagePathMap(BPackagePathMap* packagePaths)
115{
116	fPackagePaths = packagePaths;
117	return *this;
118}
119
120
121BRepositoryBuilder&
122BRepositoryBuilder::AddPackage(const BPackageInfo& info,
123	const char* packageErrorName, BSolverPackage** _package)
124{
125	status_t error = fRepository.AddPackage(info, _package);
126	if (error != B_OK) {
127		DIE(error, "failed to add %s to %s repository",
128			packageErrorName != NULL
129				? packageErrorName
130				: (BString("package ") << info.Name()).String(),
131			fErrorName.String());
132	}
133	return *this;
134}
135
136
137BRepositoryBuilder&
138BRepositoryBuilder::AddPackage(const char* path, BSolverPackage** _package)
139{
140	// read a package info from the (HPKG or package info) file
141	BPackageInfo packageInfo;
142
143	size_t pathLength = strlen(path);
144	status_t error;
145	PackageInfoErrorListener errorListener(path);
146	BEntry entry(path, true);
147
148	if (!entry.Exists()) {
149		DIE_DETAILS(errorListener.Errors(), B_ENTRY_NOT_FOUND,
150			"the package data file does not exist at \"%s\"", path);
151	}
152
153	struct stat entryStat;
154	error = entry.GetStat(&entryStat);
155
156	if (error != B_OK) {
157		DIE_DETAILS(errorListener.Errors(), error,
158			"failed to access the package data file at \"%s\"", path);
159	}
160
161	if (entryStat.st_size == 0) {
162		DIE_DETAILS(errorListener.Errors(), B_BAD_DATA,
163			"empty package data file at \"%s\"", path);
164	}
165
166	if (pathLength > 5 && strcmp(path + pathLength - 5, ".hpkg") == 0) {
167		// a package file
168		error = packageInfo.ReadFromPackageFile(path);
169	} else {
170		// a package info file (supposedly)
171		error = packageInfo.ReadFromConfigFile(entry, &errorListener);
172	}
173
174	if (error != B_OK) {
175		DIE_DETAILS(errorListener.Errors(), error,
176			"failed to read package data file at \"%s\"", path);
177	}
178
179	// add the package
180	BSolverPackage* package;
181	AddPackage(packageInfo, path, &package);
182
183	// enter the package path in the path map, if given
184	if (fPackagePaths != NULL)
185		(*fPackagePaths)[package] = path;
186
187	if (_package != NULL)
188		*_package = package;
189
190	return *this;
191}
192
193
194BRepositoryBuilder&
195BRepositoryBuilder::AddPackages(BPackageInstallationLocation location,
196	const char* locationErrorName)
197{
198	status_t error = fRepository.AddPackages(location);
199	if (error != B_OK) {
200		DIE(error, "failed to add %s packages to %s repository",
201			locationErrorName, fErrorName.String());
202	}
203	return *this;
204}
205
206
207BRepositoryBuilder&
208BRepositoryBuilder::AddPackagesDirectory(const char* path)
209{
210	// open directory
211	DirCloser dir(opendir(path));
212	if (!dir.IsSet())
213		DIE(errno, "failed to open package directory \"%s\"", path);
214
215	// iterate through directory entries
216	while (dirent* entry = readdir(dir.Get())) {
217		// skip "." and ".."
218		const char* name = entry->d_name;
219		if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
220			continue;
221
222		// stat() the entry and skip any non-file
223		BPath entryPath;
224		status_t error = entryPath.SetTo(path, name);
225		if (error != B_OK)
226			DIE(errno, "failed to construct path");
227
228		struct stat st;
229		if (stat(entryPath.Path(), &st) != 0)
230			DIE(errno, "failed to stat() %s", entryPath.Path());
231
232		if (!S_ISREG(st.st_mode))
233			continue;
234
235		AddPackage(entryPath.Path());
236	}
237
238	return *this;
239}
240
241
242BRepositoryBuilder&
243BRepositoryBuilder::AddToSolver(BSolver* solver, bool isInstalled)
244{
245	fRepository.SetInstalled(isInstalled);
246
247	status_t error = solver->AddRepository(&fRepository);
248	if (error != B_OK) {
249		DIE(error, "failed to add %s repository to solver",
250			fErrorName.String());
251	}
252	return *this;
253}
254
255
256}	// namespace BPrivate
257
258}	// namespace BManager
259
260}	// namespace BPackageKit
261