1/*
2 * Copyright 2014, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "package_support.h"
8
9#include <errno.h>
10#include <stdio.h>
11#include <string.h>
12
13#include <AutoDeleter.h>
14#include <boot/vfs.h>
15#include <package/PackagesDirectoryDefs.h>
16
17
18#define TRACE_PACKAGE_SUPPORT
19#ifdef TRACE_PACKAGE_SUPPORT
20#	define TRACE(...) dprintf(__VA_ARGS__)
21#else
22#	define TRACE(...) do {} while (false)
23#endif
24
25static const char* const kAdministrativeDirectory
26	= PACKAGES_DIRECTORY_ADMIN_DIRECTORY;
27static const char* const kActivatedPackagesFile
28	= PACKAGES_DIRECTORY_ACTIVATION_FILE;
29
30
31static inline bool
32is_system_package(const char* name)
33{
34	// The name must end with ".hpkg".
35	size_t nameLength = strlen(name);
36	if (nameLength < 6 || strcmp(name + nameLength - 5, ".hpkg") != 0)
37		return false;
38
39	// The name must either be "haiku.hpkg" or start with "haiku-".
40	return strcmp(name, "haiku.hpkg") == 0 || strncmp(name, "haiku-", 6) == 0;
41}
42
43
44// #pragma mark - PackageVolumeState
45
46
47PackageVolumeState::PackageVolumeState()
48	:
49	fName(NULL),
50	fDisplayName(NULL),
51	fSystemPackage(NULL)
52{
53}
54
55
56PackageVolumeState::~PackageVolumeState()
57{
58	Unset();
59}
60
61
62status_t
63PackageVolumeState::SetTo(const char* stateName)
64{
65	Unset();
66
67	if (stateName != NULL) {
68		fName = strdup(stateName);
69		if (fName == NULL)
70			return B_NO_MEMORY;
71
72		// Derive the display name from the directory name: Chop off the leading
73		// "state_" and replace underscores by spaces.
74		fDisplayName = strncmp(stateName, "state_", 6) == 0
75			? strdup(stateName + 6) : strdup(stateName);
76		if (fDisplayName == NULL)
77			return B_NO_MEMORY;
78
79		char* remainder = fDisplayName;
80		while (char* underscore = strchr(remainder, '_')) {
81			*underscore = ' ';
82			remainder = underscore + 1;
83		}
84	}
85
86	return B_OK;
87}
88
89
90void
91PackageVolumeState::Unset()
92{
93	free(fName);
94	fName = NULL;
95
96	free(fDisplayName);
97	fDisplayName = NULL;
98
99	free(fSystemPackage);
100	fSystemPackage = NULL;
101}
102
103
104const char*
105PackageVolumeState::DisplayName() const
106{
107	return fDisplayName != NULL ? fDisplayName : "Latest state";
108}
109
110
111status_t
112PackageVolumeState::SetSystemPackage(const char* package)
113{
114	if (fSystemPackage != NULL)
115		free(fSystemPackage);
116
117	fSystemPackage = strdup(package);
118	return fSystemPackage != NULL ? B_OK : B_NO_MEMORY;
119}
120
121
122void
123PackageVolumeState::GetPackagePath(const char* name, char* path,
124	size_t pathSize)
125{
126	if (fName == NULL) {
127		// the current state -- packages are directly in the packages directory
128		strlcpy(path, name, pathSize);
129	} else {
130		// an old state
131		snprintf(path, pathSize, "%s/%s/%s", kAdministrativeDirectory, fName,
132			name);
133	}
134}
135
136
137/*static*/ bool
138PackageVolumeState::IsNewer(const PackageVolumeState* a,
139	const PackageVolumeState* b)
140{
141	if (b->fName == NULL)
142		return false;
143	if (a->fName == NULL)
144		return true;
145	return strcmp(a->fName, b->fName) > 0;
146}
147
148
149// #pragma mark - PackageVolumeInfo
150
151
152PackageVolumeInfo::PackageVolumeInfo()
153	:
154	BReferenceable(),
155	fStates(),
156	fPackagesDir(NULL)
157{
158}
159
160
161PackageVolumeInfo::~PackageVolumeInfo()
162{
163	while (PackageVolumeState* state = fStates.RemoveHead())
164		delete state;
165
166	if (fPackagesDir != NULL)
167		closedir(fPackagesDir);
168}
169
170
171status_t
172PackageVolumeInfo::SetTo(Directory* baseDirectory, const char* packagesPath)
173{
174	TRACE("PackageVolumeInfo::SetTo()\n");
175
176	if (fPackagesDir != NULL)
177		closedir(fPackagesDir);
178
179	// get the packages directory
180	fPackagesDir = open_directory(baseDirectory, packagesPath);
181	if (fPackagesDir == NULL) {
182		TRACE("PackageVolumeInfo::SetTo(): failed to open packages directory: "
183			"%s\n", strerror(errno));
184		return errno;
185	}
186
187	Directory* packagesDirectory = directory_from(fPackagesDir);
188	packagesDirectory->Acquire();
189
190	// add the current state
191	PackageVolumeState* state = _AddState(NULL);
192	if (state == NULL)
193		return B_NO_MEMORY;
194	status_t error = _InitState(packagesDirectory, fPackagesDir, state);
195	if (error != B_OK) {
196		TRACE("PackageVolumeInfo::SetTo(): failed to init current state: "
197			"%s\n", strerror(error));
198		return error;
199	}
200
201	return B_OK;
202}
203
204
205status_t
206PackageVolumeInfo::LoadOldStates()
207{
208	if (fPackagesDir == NULL) {
209		TRACE("PackageVolumeInfo::LoadOldStates(): package directory is NULL");
210		return B_ERROR;
211	}
212
213	Directory* packagesDirectory = directory_from(fPackagesDir);
214	packagesDirectory->Acquire();
215
216	if (DIR* administrativeDir = open_directory(packagesDirectory,
217			kAdministrativeDirectory)) {
218		while (dirent* entry = readdir(administrativeDir)) {
219			if (strncmp(entry->d_name, "state_", 6) == 0) {
220				TRACE("  old state directory \"%s\"\n", entry->d_name);
221				_AddState(entry->d_name);
222			}
223		}
224
225		closedir(administrativeDir);
226
227		fStates.Sort(&PackageVolumeState::IsNewer);
228
229		// initialize the old states
230		PackageVolumeState* state = fStates.Head();
231		status_t error;
232		for (state = fStates.GetNext(state); state != NULL;) {
233			PackageVolumeState* nextState = fStates.GetNext(state);
234			if (state->Name()) {
235				error = _InitState(packagesDirectory, fPackagesDir, state);
236				if (error != B_OK) {
237					TRACE("PackageVolumeInfo::LoadOldStates(): failed to "
238						"init state \"%s\": %s\n", state->Name(),
239						strerror(error));
240					fStates.Remove(state);
241					delete state;
242				}
243			}
244			state = nextState;
245		}
246	} else {
247		TRACE("PackageVolumeInfo::LoadOldStates(): failed to open "
248			"administrative directory: %s\n", strerror(errno));
249	}
250
251	return B_OK;
252}
253
254
255PackageVolumeState*
256PackageVolumeInfo::_AddState(const char* stateName)
257{
258	ObjectDeleter<PackageVolumeState> state(new(std::nothrow) PackageVolumeState);
259	if (!state.IsSet())
260		return NULL;
261
262	if (state->SetTo(stateName) != B_OK) {
263		return NULL;
264	}
265
266	fStates.Add(state.Get());
267	return state.Detach();
268}
269
270
271status_t
272PackageVolumeInfo::_InitState(Directory* packagesDirectory, DIR* dir,
273	PackageVolumeState* state)
274{
275	// find the system package
276	ArrayDeleter<char> systemPackageName(new(std::nothrow) char[B_FILE_NAME_LENGTH]);
277	if (!systemPackageName.IsSet())
278		return B_NO_MEMORY;
279	ArrayDeleter<char> packagePath(new(std::nothrow) char[B_PATH_NAME_LENGTH]);
280	if (!packagePath.IsSet()) {
281		return B_NO_MEMORY;
282	}
283
284	status_t error = _ParseActivatedPackagesFile(packagesDirectory, state,
285		systemPackageName.Get(), B_FILE_NAME_LENGTH);
286	if (error == B_OK) {
287		// check, if package exists
288		for (PackageVolumeState* otherState = state; otherState != NULL;
289				otherState = fStates.GetPrevious(otherState)) {
290			otherState->GetPackagePath(systemPackageName.Get(), packagePath.Get(),
291				B_PATH_NAME_LENGTH);
292			struct stat st;
293			if (get_stat(packagesDirectory, packagePath.Get(), st) == B_OK
294				&& S_ISREG(st.st_mode)) {
295				state->SetSystemPackage(packagePath.Get());
296				break;
297			}
298		}
299	} else {
300		TRACE("PackageVolumeInfo::_InitState(): failed to parse "
301			"activated-packages: %s\n", strerror(error));
302
303		// No or invalid activated-packages file. That is OK for the current
304		// state. We'll iterate through the packages directory to find the
305		// system package. We don't do that for old states, though.
306		if (state->Name() != NULL)
307			return B_ENTRY_NOT_FOUND;
308
309		while (dirent* entry = readdir(dir)) {
310			// The name must end with ".hpkg".
311			if (is_system_package(entry->d_name)) {
312				state->SetSystemPackage(entry->d_name);
313				break;
314			}
315		}
316	}
317
318	if (state->SystemPackage() == NULL)
319		return B_ENTRY_NOT_FOUND;
320
321	return B_OK;
322}
323
324
325status_t
326PackageVolumeInfo::_ParseActivatedPackagesFile(Directory* packagesDirectory,
327	PackageVolumeState* state, char* packageName, size_t packageNameSize)
328{
329	// open the activated-packages file
330	static const size_t kBufferSize = 3 * B_FILE_NAME_LENGTH + 2;
331	ArrayDeleter<char> path(new(std::nothrow) char[kBufferSize]);
332	if (!path.IsSet())
333		return B_NO_MEMORY;
334	snprintf(path.Get(), kBufferSize, "%s/%s/%s",
335		kAdministrativeDirectory, state->Name() != NULL ? state->Name() : "",
336		kActivatedPackagesFile);
337	FileDescriptorCloser fd(open_from(packagesDirectory, path.Get(), O_RDONLY));
338	if (!fd.IsSet())
339		return fd.Get();
340
341	struct stat st;
342	if (fstat(fd.Get(), &st) != 0)
343		return errno;
344	if (!S_ISREG(st.st_mode))
345		return B_ENTRY_NOT_FOUND;
346
347	// read the file until we find the system package line
348	size_t remainingBytes = 0;
349	for (;;) {
350		ssize_t bytesRead = read(fd.Get(), path.Get() + remainingBytes,
351			kBufferSize - remainingBytes - 1);
352		if (bytesRead <= 0)
353			return B_ENTRY_NOT_FOUND;
354
355		remainingBytes += bytesRead;
356		path[remainingBytes] = '\0';
357
358		char* line = path.Get();
359		while (char* lineEnd = strchr(line, '\n')) {
360			*lineEnd = '\0';
361			if (is_system_package(line)) {
362				status_t result = strlcpy(packageName, line, packageNameSize)
363						< packageNameSize
364					?  B_OK : B_NAME_TOO_LONG;
365				return result;
366			}
367
368			line = lineEnd + 1;
369		}
370
371		// move the remainder to the start of the buffer
372		if (line < path.Get() + remainingBytes) {
373			size_t left = path.Get() + remainingBytes - line;
374			memmove(path.Get(), line, left);
375			remainingBytes = left;
376		} else
377			remainingBytes = 0;
378	}
379
380	return B_ENTRY_NOT_FOUND;
381}
382