xref: /haiku/src/system/boot/loader/package_support.cpp (revision 344ded80d400028c8f561b4b876257b94c12db4a)
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 
25 static const char* const kAdministrativeDirectory
26 	= PACKAGES_DIRECTORY_ADMIN_DIRECTORY;
27 static const char* const kActivatedPackagesFile
28 	= PACKAGES_DIRECTORY_ACTIVATION_FILE;
29 
30 
31 static inline bool
32 is_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 
47 PackageVolumeState::PackageVolumeState()
48 	:
49 	fName(NULL),
50 	fDisplayName(NULL),
51 	fSystemPackage(NULL)
52 {
53 }
54 
55 
56 PackageVolumeState::~PackageVolumeState()
57 {
58 	Unset();
59 }
60 
61 
62 status_t
63 PackageVolumeState::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 
90 void
91 PackageVolumeState::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 
104 const char*
105 PackageVolumeState::DisplayName() const
106 {
107 	return fDisplayName;
108 }
109 
110 
111 status_t
112 PackageVolumeState::SetSystemPackage(const char* package)
113 {
114 	if (fSystemPackage != NULL)
115 		free(fSystemPackage);
116 
117 	fSystemPackage = strdup(package);
118 	if (fSystemPackage == NULL)
119 		return B_NO_MEMORY;
120 
121 	if (fName == NULL) {
122 		free(fDisplayName);
123 		fDisplayName = NULL;
124 
125 		const char* packageVersion = strchr(package, '-');
126 		if (packageVersion == NULL) {
127 			fDisplayName = strdup("Latest state");
128 		} else {
129 			ArrayDeleter<char> newDisplayName(new(std::nothrow) char[B_FILE_NAME_LENGTH]);
130 			if (!newDisplayName.IsSet())
131 				return B_NO_MEMORY;
132 
133 			packageVersion++;
134 			const char* packageVersionEnd = strchr(packageVersion, '-');
135 			int length = -1;
136 			if (packageVersionEnd != NULL)
137 				length = packageVersionEnd - packageVersion;
138 
139 			snprintf(newDisplayName.Get(), B_FILE_NAME_LENGTH,
140 				"Latest state (%.*s)", length, packageVersion);
141 			fDisplayName = newDisplayName.Detach();
142 		}
143 	}
144 
145 	return B_OK;
146 }
147 
148 
149 void
150 PackageVolumeState::GetPackagePath(const char* name, char* path,
151 	size_t pathSize)
152 {
153 	if (fName == NULL) {
154 		// the current state -- packages are directly in the packages directory
155 		strlcpy(path, name, pathSize);
156 	} else {
157 		// an old state
158 		snprintf(path, pathSize, "%s/%s/%s", kAdministrativeDirectory, fName,
159 			name);
160 	}
161 }
162 
163 
164 /*static*/ bool
165 PackageVolumeState::IsNewer(const PackageVolumeState* a,
166 	const PackageVolumeState* b)
167 {
168 	if (b->fName == NULL)
169 		return false;
170 	if (a->fName == NULL)
171 		return true;
172 	return strcmp(a->fName, b->fName) > 0;
173 }
174 
175 
176 // #pragma mark - PackageVolumeInfo
177 
178 
179 PackageVolumeInfo::PackageVolumeInfo()
180 	:
181 	BReferenceable(),
182 	fStates(),
183 	fPackagesDir(NULL)
184 {
185 }
186 
187 
188 PackageVolumeInfo::~PackageVolumeInfo()
189 {
190 	while (PackageVolumeState* state = fStates.RemoveHead())
191 		delete state;
192 
193 	if (fPackagesDir != NULL)
194 		closedir(fPackagesDir);
195 }
196 
197 
198 status_t
199 PackageVolumeInfo::SetTo(Directory* baseDirectory, const char* packagesPath)
200 {
201 	TRACE("PackageVolumeInfo::SetTo()\n");
202 
203 	if (fPackagesDir != NULL)
204 		closedir(fPackagesDir);
205 
206 	// get the packages directory
207 	fPackagesDir = open_directory(baseDirectory, packagesPath);
208 	if (fPackagesDir == NULL) {
209 		TRACE("PackageVolumeInfo::SetTo(): failed to open packages directory: "
210 			"%s\n", strerror(errno));
211 		return errno;
212 	}
213 
214 	Directory* packagesDirectory = directory_from(fPackagesDir);
215 	packagesDirectory->Acquire();
216 
217 	// add the current state
218 	PackageVolumeState* state = _AddState(NULL);
219 	if (state == NULL)
220 		return B_NO_MEMORY;
221 	status_t error = _InitState(packagesDirectory, fPackagesDir, state);
222 	if (error != B_OK) {
223 		TRACE("PackageVolumeInfo::SetTo(): failed to init current state: "
224 			"%s\n", strerror(error));
225 		return error;
226 	}
227 
228 	return B_OK;
229 }
230 
231 
232 status_t
233 PackageVolumeInfo::LoadOldStates()
234 {
235 	if (fPackagesDir == NULL) {
236 		TRACE("PackageVolumeInfo::LoadOldStates(): package directory is NULL");
237 		return B_ERROR;
238 	}
239 
240 	Directory* packagesDirectory = directory_from(fPackagesDir);
241 	packagesDirectory->Acquire();
242 
243 	if (DIR* administrativeDir = open_directory(packagesDirectory,
244 			kAdministrativeDirectory)) {
245 		while (dirent* entry = readdir(administrativeDir)) {
246 			if (strncmp(entry->d_name, "state_", 6) == 0) {
247 				TRACE("  old state directory \"%s\"\n", entry->d_name);
248 				_AddState(entry->d_name);
249 			}
250 		}
251 
252 		closedir(administrativeDir);
253 
254 		fStates.Sort(&PackageVolumeState::IsNewer);
255 
256 		// initialize the old states
257 		PackageVolumeState* state = fStates.Head();
258 		status_t error;
259 		for (state = fStates.GetNext(state); state != NULL;) {
260 			PackageVolumeState* nextState = fStates.GetNext(state);
261 			if (state->Name() != NULL) {
262 				error = _InitState(packagesDirectory, fPackagesDir, state);
263 				if (error != B_OK) {
264 					TRACE("PackageVolumeInfo::LoadOldStates(): failed to "
265 						"init state \"%s\": %s\n", state->Name(),
266 						strerror(error));
267 					fStates.Remove(state);
268 					delete state;
269 				}
270 			}
271 			state = nextState;
272 		}
273 	} else {
274 		TRACE("PackageVolumeInfo::LoadOldStates(): failed to open "
275 			"administrative directory: %s\n", strerror(errno));
276 	}
277 
278 	return B_OK;
279 }
280 
281 
282 PackageVolumeState*
283 PackageVolumeInfo::_AddState(const char* stateName)
284 {
285 	ObjectDeleter<PackageVolumeState> state(new(std::nothrow) PackageVolumeState);
286 	if (!state.IsSet())
287 		return NULL;
288 
289 	if (state->SetTo(stateName) != B_OK) {
290 		return NULL;
291 	}
292 
293 	fStates.Add(state.Get());
294 	return state.Detach();
295 }
296 
297 
298 status_t
299 PackageVolumeInfo::_InitState(Directory* packagesDirectory, DIR* dir,
300 	PackageVolumeState* state)
301 {
302 	// find the system package
303 	ArrayDeleter<char> systemPackageName(new(std::nothrow) char[B_FILE_NAME_LENGTH]);
304 	if (!systemPackageName.IsSet())
305 		return B_NO_MEMORY;
306 	ArrayDeleter<char> packagePath(new(std::nothrow) char[B_PATH_NAME_LENGTH]);
307 	if (!packagePath.IsSet())
308 		return B_NO_MEMORY;
309 
310 	status_t error = _ParseActivatedPackagesFile(packagesDirectory, state,
311 		systemPackageName.Get(), B_FILE_NAME_LENGTH);
312 	if (error == B_OK) {
313 		// check, if package exists
314 		for (PackageVolumeState* otherState = state; otherState != NULL;
315 				otherState = fStates.GetPrevious(otherState)) {
316 			otherState->GetPackagePath(systemPackageName.Get(), packagePath.Get(),
317 				B_PATH_NAME_LENGTH);
318 			struct stat st;
319 			if (get_stat(packagesDirectory, packagePath.Get(), st) == B_OK
320 					&& S_ISREG(st.st_mode)) {
321 				state->SetSystemPackage(packagePath.Get());
322 				break;
323 			}
324 		}
325 	} else {
326 		TRACE("PackageVolumeInfo::_InitState(): failed to parse "
327 			"activated-packages: %s\n", strerror(error));
328 
329 		// No or invalid activated-packages file. That is OK for the current
330 		// state. We'll iterate through the packages directory to find the
331 		// system package. We don't do that for old states, though.
332 		if (state->Name() != NULL)
333 			return B_ENTRY_NOT_FOUND;
334 
335 		while (dirent* entry = readdir(dir)) {
336 			// The name must end with ".hpkg".
337 			if (is_system_package(entry->d_name)) {
338 				state->SetSystemPackage(entry->d_name);
339 				break;
340 			}
341 		}
342 	}
343 
344 	if (state->SystemPackage() == NULL)
345 		return B_ENTRY_NOT_FOUND;
346 
347 	return B_OK;
348 }
349 
350 
351 status_t
352 PackageVolumeInfo::_ParseActivatedPackagesFile(Directory* packagesDirectory,
353 	PackageVolumeState* state, char* packageName, size_t packageNameSize)
354 {
355 	// open the activated-packages file
356 	static const size_t kBufferSize = 3 * B_FILE_NAME_LENGTH + 2;
357 	ArrayDeleter<char> path(new(std::nothrow) char[kBufferSize]);
358 	if (!path.IsSet())
359 		return B_NO_MEMORY;
360 	snprintf(path.Get(), kBufferSize, "%s/%s/%s",
361 		kAdministrativeDirectory, state->Name() != NULL ? state->Name() : "",
362 		kActivatedPackagesFile);
363 	FileDescriptorCloser fd(open_from(packagesDirectory, path.Get(), O_RDONLY));
364 	if (!fd.IsSet())
365 		return fd.Get();
366 
367 	struct stat st;
368 	if (fstat(fd.Get(), &st) != 0)
369 		return errno;
370 	if (!S_ISREG(st.st_mode))
371 		return B_ENTRY_NOT_FOUND;
372 
373 	// read the file until we find the system package line
374 	size_t remainingBytes = 0;
375 	for (;;) {
376 		ssize_t bytesRead = read(fd.Get(), path.Get() + remainingBytes,
377 			kBufferSize - remainingBytes - 1);
378 		if (bytesRead <= 0)
379 			return B_ENTRY_NOT_FOUND;
380 
381 		remainingBytes += bytesRead;
382 		path[remainingBytes] = '\0';
383 
384 		char* line = path.Get();
385 		while (char* lineEnd = strchr(line, '\n')) {
386 			*lineEnd = '\0';
387 			if (is_system_package(line)) {
388 				status_t result = strlcpy(packageName, line, packageNameSize)
389 						< packageNameSize
390 					?  B_OK : B_NAME_TOO_LONG;
391 				return result;
392 			}
393 
394 			line = lineEnd + 1;
395 		}
396 
397 		// move the remainder to the start of the buffer
398 		if (line < path.Get() + remainingBytes) {
399 			size_t left = path.Get() + remainingBytes - line;
400 			memmove(path.Get(), line, left);
401 			remainingBytes = left;
402 		} else
403 			remainingBytes = 0;
404 	}
405 
406 	return B_ENTRY_NOT_FOUND;
407 }
408