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