xref: /haiku/src/system/boot/loader/package_support.cpp (revision 4c8e85b316c35a9161f5a1c50ad70bc91c83a76f)
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 != NULL ? fDisplayName : "Latest state";
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 	return fSystemPackage != NULL ? B_OK : B_NO_MEMORY;
119 }
120 
121 
122 void
123 PackageVolumeState::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
138 PackageVolumeState::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 
152 PackageVolumeInfo::PackageVolumeInfo()
153 	:
154 	BReferenceable(),
155 	fStates(),
156 	fPackagesDir(NULL)
157 {
158 }
159 
160 
161 PackageVolumeInfo::~PackageVolumeInfo()
162 {
163 	while (PackageVolumeState* state = fStates.RemoveHead())
164 		delete state;
165 
166 	if (fPackagesDir != NULL)
167 		closedir(fPackagesDir);
168 }
169 
170 
171 status_t
172 PackageVolumeInfo::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 
205 status_t
206 PackageVolumeInfo::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 
255 PackageVolumeState*
256 PackageVolumeInfo::_AddState(const char* stateName)
257 {
258 	PackageVolumeState* state = new(std::nothrow) PackageVolumeState;
259 	if (state == NULL)
260 		return NULL;
261 
262 	if (state->SetTo(stateName) != B_OK) {
263 		delete state;
264 		return NULL;
265 	}
266 
267 	fStates.Add(state);
268 	return state;
269 }
270 
271 
272 status_t
273 PackageVolumeInfo::_InitState(Directory* packagesDirectory, DIR* dir,
274 	PackageVolumeState* state)
275 {
276 	// find the system package
277 	char* systemPackageName = (char*)malloc(B_FILE_NAME_LENGTH);
278 	if (systemPackageName == NULL)
279 		return B_NO_MEMORY;
280 	char* packagePath = (char*)malloc(B_PATH_NAME_LENGTH);
281 	if (packagePath == NULL) {
282 		free(systemPackageName);
283 		return B_NO_MEMORY;
284 	}
285 
286 	status_t error = _ParseActivatedPackagesFile(packagesDirectory, state,
287 		systemPackageName, B_FILE_NAME_LENGTH);
288 	if (error == B_OK) {
289 		// check, if package exists
290 		for (PackageVolumeState* otherState = state; otherState != NULL;
291 				otherState = fStates.GetPrevious(otherState)) {
292 			otherState->GetPackagePath(systemPackageName, packagePath,
293 				B_PATH_NAME_LENGTH);
294 			struct stat st;
295 			if (get_stat(packagesDirectory, packagePath, st) == B_OK
296 				&& S_ISREG(st.st_mode)) {
297 				state->SetSystemPackage(packagePath);
298 				break;
299 			}
300 		}
301 	} else {
302 		TRACE("PackageVolumeInfo::_InitState(): failed to parse "
303 			"activated-packages: %s\n", strerror(error));
304 
305 		// No or invalid activated-packages file. That is OK for the current
306 		// state. We'll iterate through the packages directory to find the
307 		// system package. We don't do that for old states, though.
308 		if (state->Name() != NULL)
309 			return B_ENTRY_NOT_FOUND;
310 
311 		while (dirent* entry = readdir(dir)) {
312 			// The name must end with ".hpkg".
313 			if (is_system_package(entry->d_name)) {
314 				state->SetSystemPackage(entry->d_name);
315 				break;
316 			}
317 		}
318 	}
319 
320 	free(packagePath);
321 	free(systemPackageName);
322 	if (state->SystemPackage() == NULL)
323 		return B_ENTRY_NOT_FOUND;
324 
325 	return B_OK;
326 }
327 
328 
329 status_t
330 PackageVolumeInfo::_ParseActivatedPackagesFile(Directory* packagesDirectory,
331 	PackageVolumeState* state, char* packageName, size_t packageNameSize)
332 {
333 	// open the activated-packages file
334 	static const size_t kBufferSize = 3 * B_FILE_NAME_LENGTH + 2;
335 	char* path = (char*)malloc(kBufferSize);
336 	if (path == NULL)
337 		return B_NO_MEMORY;
338 	snprintf(path, kBufferSize, "%s/%s/%s",
339 		kAdministrativeDirectory, state->Name() != NULL ? state->Name() : "",
340 		kActivatedPackagesFile);
341 	int fd = open_from(packagesDirectory, path, O_RDONLY);
342 	if (fd < 0) {
343 		free(path);
344 		return fd;
345 	}
346 	FileDescriptorCloser fdCloser(fd);
347 
348 	struct stat st;
349 	if (fstat(fd, &st) != 0) {
350 		free(path);
351 		return errno;
352 	}
353 	if (!S_ISREG(st.st_mode)) {
354 		free(path);
355 		return B_ENTRY_NOT_FOUND;
356 	}
357 
358 	// read the file until we find the system package line
359 	size_t remainingBytes = 0;
360 	for (;;) {
361 		ssize_t bytesRead = read(fd, path + remainingBytes,
362 			kBufferSize - remainingBytes - 1);
363 		if (bytesRead <= 0) {
364 			free(path);
365 			return B_ENTRY_NOT_FOUND;
366 		}
367 
368 		remainingBytes += bytesRead;
369 		path[remainingBytes] = '\0';
370 
371 		char* line = path;
372 		while (char* lineEnd = strchr(line, '\n')) {
373 			*lineEnd = '\0';
374 			if (is_system_package(line)) {
375 				status_t result = strlcpy(packageName, line, packageNameSize)
376 						< packageNameSize
377 					?  B_OK : B_NAME_TOO_LONG;
378 				free(path);
379 				return result;
380 			}
381 
382 			line = lineEnd + 1;
383 		}
384 
385 		// move the remainder to the start of the buffer
386 		if (line < path + remainingBytes) {
387 			size_t left = path + remainingBytes - line;
388 			memmove(path, line, left);
389 			remainingBytes = left;
390 		} else
391 			remainingBytes = 0;
392 	}
393 
394 	free(path);
395 	return B_ENTRY_NOT_FOUND;
396 }
397