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; 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 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 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 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 179 PackageVolumeInfo::PackageVolumeInfo() 180 : 181 BReferenceable(), 182 fStates(), 183 fPackagesDir(NULL) 184 { 185 } 186 187 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 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 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* 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 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 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