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
is_system_package(const char * name)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
PackageVolumeState()47 PackageVolumeState::PackageVolumeState()
48 :
49 fName(NULL),
50 fDisplayName(NULL),
51 fSystemPackage(NULL)
52 {
53 }
54
55
~PackageVolumeState()56 PackageVolumeState::~PackageVolumeState()
57 {
58 Unset();
59 }
60
61
62 status_t
SetTo(const char * stateName)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
Unset()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*
DisplayName() const105 PackageVolumeState::DisplayName() const
106 {
107 return fDisplayName;
108 }
109
110
111 status_t
SetSystemPackage(const char * package)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
GetPackagePath(const char * name,char * path,size_t pathSize)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
IsNewer(const PackageVolumeState * a,const PackageVolumeState * b)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
PackageVolumeInfo()179 PackageVolumeInfo::PackageVolumeInfo()
180 :
181 BReferenceable(),
182 fStates(),
183 fPackagesDir(NULL)
184 {
185 }
186
187
~PackageVolumeInfo()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
SetTo(Directory * baseDirectory,const char * packagesPath)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
LoadOldStates()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*
_AddState(const char * stateName)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
_InitState(Directory * packagesDirectory,DIR * dir,PackageVolumeState * state)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
_ParseActivatedPackagesFile(Directory * packagesDirectory,PackageVolumeState * state,char * packageName,size_t packageNameSize)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