headers/bsd: Add sys/queue.h.
[haiku.git] / src / system / boot / loader / package_support.cpp
blob30f9fcdd6ea5dfd4b4184b3f309570f2421024e4
1 /*
2 * Copyright 2014, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
7 #include "package_support.h"
9 #include <errno.h>
10 #include <stdio.h>
11 #include <string.h>
13 #include <AutoDeleter.h>
14 #include <boot/vfs.h>
15 #include <package/PackagesDirectoryDefs.h>
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
25 static const char* const kAdministrativeDirectory
26 = PACKAGES_DIRECTORY_ADMIN_DIRECTORY;
27 static const char* const kActivatedPackagesFile
28 = PACKAGES_DIRECTORY_ACTIVATION_FILE;
31 static inline bool
32 is_system_package(const char* name)
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;
39 // The name must either be "haiku.hpkg" or start with "haiku-".
40 return strcmp(name, "haiku.hpkg") == 0 || strncmp(name, "haiku-", 6) == 0;
44 // #pragma mark - PackageVolumeState
47 PackageVolumeState::PackageVolumeState()
49 fName(NULL),
50 fDisplayName(NULL),
51 fSystemPackage(NULL)
56 PackageVolumeState::~PackageVolumeState()
58 Unset();
62 status_t
63 PackageVolumeState::SetTo(const char* stateName)
65 Unset();
67 if (stateName != NULL) {
68 fName = strdup(stateName);
69 if (fName == NULL)
70 return B_NO_MEMORY;
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;
79 char* remainder = fDisplayName;
80 while (char* underscore = strchr(remainder, '_')) {
81 *underscore = ' ';
82 remainder = underscore + 1;
86 return B_OK;
90 void
91 PackageVolumeState::Unset()
93 free(fName);
94 fName = NULL;
96 free(fDisplayName);
97 fDisplayName = NULL;
99 free(fSystemPackage);
100 fSystemPackage = NULL;
104 const char*
105 PackageVolumeState::DisplayName() const
107 return fDisplayName != NULL ? fDisplayName : "Latest version";
111 status_t
112 PackageVolumeState::SetSystemPackage(const char* package)
114 if (fSystemPackage != NULL)
115 free(fSystemPackage);
117 fSystemPackage = strdup(package);
118 return fSystemPackage != NULL ? B_OK : B_NO_MEMORY;
122 void
123 PackageVolumeState::GetPackagePath(const char* name, char* path,
124 size_t pathSize)
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);
137 /*static*/ bool
138 PackageVolumeState::IsNewer(const PackageVolumeState* a,
139 const PackageVolumeState* b)
141 if (b->fName == NULL)
142 return false;
143 if (a->fName == NULL)
144 return true;
145 return strcmp(a->fName, b->fName) > 0;
149 // #pragma mark - PackageVolumeInfo
152 PackageVolumeInfo::PackageVolumeInfo()
154 BReferenceable(),
155 fStates()
160 PackageVolumeInfo::~PackageVolumeInfo()
162 while (PackageVolumeState* state = fStates.RemoveHead())
163 delete state;
167 status_t
168 PackageVolumeInfo::SetTo(Directory* baseDirectory, const char* packagesPath)
170 TRACE("PackageVolumeInfo::SetTo()\n");
172 // get the packages directory
173 DIR* dir = open_directory(baseDirectory, packagesPath);
174 if (dir == NULL) {
175 TRACE("PackageVolumeInfo::SetTo(): failed to open packages directory: "
176 "%s\n", strerror(errno));
177 return errno;
179 CObjectDeleter<DIR, int> dirCloser(dir, &closedir);
181 Directory* packagesDirectory = directory_from(dir);
182 packagesDirectory->Acquire();
184 // add the current state
185 PackageVolumeState* state = _AddState(NULL);
186 if (state == NULL)
187 return B_NO_MEMORY;
188 status_t error = _InitState(packagesDirectory, dir, state);
189 if (error != B_OK) {
190 TRACE("PackageVolumeInfo::SetTo(): failed to init current state: "
191 "%s\n", strerror(error));
192 return error;
195 // Iterate through the packages/administrative directory to find old state
196 // directories.
197 if (DIR* administrativeDir = open_directory(packagesDirectory,
198 kAdministrativeDirectory)) {
199 while (dirent* entry = readdir(administrativeDir)) {
200 if (strncmp(entry->d_name, "state_", 6) == 0) {
201 TRACE(" old state directory \"%s\"\n", entry->d_name);
202 _AddState(entry->d_name);
206 closedir(administrativeDir);
208 fStates.Sort(&PackageVolumeState::IsNewer);
210 // initialize the old states
211 for (state = fStates.GetNext(state); state != NULL;) {
212 PackageVolumeState* nextState = fStates.GetNext(state);
213 if (state->Name()) {
214 error = _InitState(packagesDirectory, dir, state);
215 if (error != B_OK) {
216 TRACE("PackageVolumeInfo::SetTo(): failed to init state "
217 "\"%s\": %s\n", state->Name(), strerror(error));
218 fStates.Remove(state);
219 delete state;
222 state = nextState;
224 } else {
225 TRACE("PackageVolumeInfo::SetTo(): failed to open administrative "
226 "directory: %s\n", strerror(errno));
229 return B_OK;
233 PackageVolumeState*
234 PackageVolumeInfo::_AddState(const char* stateName)
236 PackageVolumeState* state = new(std::nothrow) PackageVolumeState;
237 if (state == NULL)
238 return NULL;
240 if (state->SetTo(stateName) != B_OK) {
241 delete state;
242 return NULL;
245 fStates.Add(state);
246 return state;
250 status_t
251 PackageVolumeInfo::_InitState(Directory* packagesDirectory, DIR* dir,
252 PackageVolumeState* state)
254 // find the system package
255 char systemPackageName[B_FILE_NAME_LENGTH];
256 status_t error = _ParseActivatedPackagesFile(packagesDirectory, state,
257 systemPackageName, sizeof(systemPackageName));
258 if (error == B_OK) {
259 // check, if package exists
260 for (PackageVolumeState* otherState = state; otherState != NULL;
261 otherState = fStates.GetPrevious(otherState)) {
262 char packagePath[B_PATH_NAME_LENGTH];
263 otherState->GetPackagePath(systemPackageName, packagePath,
264 sizeof(packagePath));
265 struct stat st;
266 if (get_stat(packagesDirectory, packagePath, st) == B_OK
267 && S_ISREG(st.st_mode)) {
268 state->SetSystemPackage(packagePath);
269 break;
272 } else {
273 TRACE("PackageVolumeInfo::_InitState(): failed to parse "
274 "activated-packages: %s\n", strerror(error));
276 // No or invalid activated-packages file. That is OK for the current
277 // state. We'll iterate through the packages directory to find the
278 // system package. We don't do that for old states, though.
279 if (state->Name() != NULL)
280 return B_ENTRY_NOT_FOUND;
282 while (dirent* entry = readdir(dir)) {
283 // The name must end with ".hpkg".
284 if (is_system_package(entry->d_name)) {
285 state->SetSystemPackage(entry->d_name);
286 break;
291 if (state->SystemPackage() == NULL)
292 return B_ENTRY_NOT_FOUND;
294 return B_OK;
298 status_t
299 PackageVolumeInfo::_ParseActivatedPackagesFile(Directory* packagesDirectory,
300 PackageVolumeState* state, char* packageName, size_t packageNameSize)
302 // open the activated-packages file
303 char path[3 * B_FILE_NAME_LENGTH + 2];
304 snprintf(path, sizeof(path), "%s/%s/%s",
305 kAdministrativeDirectory, state->Name() != NULL ? state->Name() : "",
306 kActivatedPackagesFile);
307 int fd = open_from(packagesDirectory, path, O_RDONLY);
308 if (fd < 0)
309 return fd;
310 FileDescriptorCloser fdCloser(fd);
312 struct stat st;
313 if (fstat(fd, &st) != 0)
314 return errno;
315 if (!S_ISREG(st.st_mode))
316 return B_ENTRY_NOT_FOUND;
318 // read the file until we find the system package line
319 size_t remainingBytes = 0;
320 for (;;) {
321 ssize_t bytesRead = read(fd, path + remainingBytes,
322 sizeof(path) - remainingBytes - 1);
323 if (bytesRead <= 0)
324 return B_ENTRY_NOT_FOUND;
326 remainingBytes += bytesRead;
327 path[remainingBytes] = '\0';
329 char* line = path;
330 while (char* lineEnd = strchr(line, '\n')) {
331 *lineEnd = '\0';
332 if (is_system_package(line)) {
333 return strlcpy(packageName, line, packageNameSize)
334 < packageNameSize
335 ? B_OK : B_NAME_TOO_LONG;
338 line = lineEnd + 1;
341 // move the remainder to the start of the buffer
342 if (line < path + remainingBytes) {
343 size_t left = path + remainingBytes - line;
344 memmove(path, line, left);
345 remainingBytes = left;
346 } else
347 remainingBytes = 0;
350 return B_ENTRY_NOT_FOUND;