2 * Copyright 2014, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
7 #include "package_support.h"
13 #include <AutoDeleter.h>
15 #include <package/PackagesDirectoryDefs.h>
18 #define TRACE_PACKAGE_SUPPORT
19 #ifdef TRACE_PACKAGE_SUPPORT
20 # define TRACE(...) dprintf(__VA_ARGS__)
22 # define TRACE(...) do {} while (false)
25 static const char* const kAdministrativeDirectory
26 = PACKAGES_DIRECTORY_ADMIN_DIRECTORY
;
27 static const char* const kActivatedPackagesFile
28 = PACKAGES_DIRECTORY_ACTIVATION_FILE
;
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)
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()
56 PackageVolumeState::~PackageVolumeState()
63 PackageVolumeState::SetTo(const char* stateName
)
67 if (stateName
!= NULL
) {
68 fName
= strdup(stateName
);
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
)
79 char* remainder
= fDisplayName
;
80 while (char* underscore
= strchr(remainder
, '_')) {
82 remainder
= underscore
+ 1;
91 PackageVolumeState::Unset()
100 fSystemPackage
= NULL
;
105 PackageVolumeState::DisplayName() const
107 return fDisplayName
!= NULL
? fDisplayName
: "Latest version";
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
;
123 PackageVolumeState::GetPackagePath(const char* name
, char* path
,
127 // the current state -- packages are directly in the packages directory
128 strlcpy(path
, name
, pathSize
);
131 snprintf(path
, pathSize
, "%s/%s/%s", kAdministrativeDirectory
, fName
,
138 PackageVolumeState::IsNewer(const PackageVolumeState
* a
,
139 const PackageVolumeState
* b
)
141 if (b
->fName
== NULL
)
143 if (a
->fName
== NULL
)
145 return strcmp(a
->fName
, b
->fName
) > 0;
149 // #pragma mark - PackageVolumeInfo
152 PackageVolumeInfo::PackageVolumeInfo()
160 PackageVolumeInfo::~PackageVolumeInfo()
162 while (PackageVolumeState
* state
= fStates
.RemoveHead())
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
);
175 TRACE("PackageVolumeInfo::SetTo(): failed to open packages directory: "
176 "%s\n", strerror(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
);
188 status_t error
= _InitState(packagesDirectory
, dir
, state
);
190 TRACE("PackageVolumeInfo::SetTo(): failed to init current state: "
191 "%s\n", strerror(error
));
195 // Iterate through the packages/administrative directory to find old state
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
);
214 error
= _InitState(packagesDirectory
, dir
, state
);
216 TRACE("PackageVolumeInfo::SetTo(): failed to init state "
217 "\"%s\": %s\n", state
->Name(), strerror(error
));
218 fStates
.Remove(state
);
225 TRACE("PackageVolumeInfo::SetTo(): failed to open administrative "
226 "directory: %s\n", strerror(errno
));
234 PackageVolumeInfo::_AddState(const char* stateName
)
236 PackageVolumeState
* state
= new(std::nothrow
) PackageVolumeState
;
240 if (state
->SetTo(stateName
) != B_OK
) {
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
));
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
));
266 if (get_stat(packagesDirectory
, packagePath
, st
) == B_OK
267 && S_ISREG(st
.st_mode
)) {
268 state
->SetSystemPackage(packagePath
);
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
);
291 if (state
->SystemPackage() == NULL
)
292 return B_ENTRY_NOT_FOUND
;
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
);
310 FileDescriptorCloser
fdCloser(fd
);
313 if (fstat(fd
, &st
) != 0)
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;
321 ssize_t bytesRead
= read(fd
, path
+ remainingBytes
,
322 sizeof(path
) - remainingBytes
- 1);
324 return B_ENTRY_NOT_FOUND
;
326 remainingBytes
+= bytesRead
;
327 path
[remainingBytes
] = '\0';
330 while (char* lineEnd
= strchr(line
, '\n')) {
332 if (is_system_package(line
)) {
333 return strlcpy(packageName
, line
, packageNameSize
)
335 ? B_OK
: B_NAME_TOO_LONG
;
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
;
350 return B_ENTRY_NOT_FOUND
;