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 ObjectDeleter<PackageVolumeState> state(new(std::nothrow) PackageVolumeState); 259 if (!state.IsSet()) 260 return NULL; 261 262 if (state->SetTo(stateName) != B_OK) { 263 return NULL; 264 } 265 266 fStates.Add(state.Get()); 267 return state.Detach(); 268 } 269 270 271 status_t 272 PackageVolumeInfo::_InitState(Directory* packagesDirectory, DIR* dir, 273 PackageVolumeState* state) 274 { 275 // find the system package 276 ArrayDeleter<char> systemPackageName(new(std::nothrow) char[B_FILE_NAME_LENGTH]); 277 if (!systemPackageName.IsSet()) 278 return B_NO_MEMORY; 279 ArrayDeleter<char> packagePath(new(std::nothrow) char[B_PATH_NAME_LENGTH]); 280 if (!packagePath.IsSet()) { 281 return B_NO_MEMORY; 282 } 283 284 status_t error = _ParseActivatedPackagesFile(packagesDirectory, state, 285 systemPackageName.Get(), B_FILE_NAME_LENGTH); 286 if (error == B_OK) { 287 // check, if package exists 288 for (PackageVolumeState* otherState = state; otherState != NULL; 289 otherState = fStates.GetPrevious(otherState)) { 290 otherState->GetPackagePath(systemPackageName.Get(), packagePath.Get(), 291 B_PATH_NAME_LENGTH); 292 struct stat st; 293 if (get_stat(packagesDirectory, packagePath.Get(), st) == B_OK 294 && S_ISREG(st.st_mode)) { 295 state->SetSystemPackage(packagePath.Get()); 296 break; 297 } 298 } 299 } else { 300 TRACE("PackageVolumeInfo::_InitState(): failed to parse " 301 "activated-packages: %s\n", strerror(error)); 302 303 // No or invalid activated-packages file. That is OK for the current 304 // state. We'll iterate through the packages directory to find the 305 // system package. We don't do that for old states, though. 306 if (state->Name() != NULL) 307 return B_ENTRY_NOT_FOUND; 308 309 while (dirent* entry = readdir(dir)) { 310 // The name must end with ".hpkg". 311 if (is_system_package(entry->d_name)) { 312 state->SetSystemPackage(entry->d_name); 313 break; 314 } 315 } 316 } 317 318 if (state->SystemPackage() == NULL) 319 return B_ENTRY_NOT_FOUND; 320 321 return B_OK; 322 } 323 324 325 status_t 326 PackageVolumeInfo::_ParseActivatedPackagesFile(Directory* packagesDirectory, 327 PackageVolumeState* state, char* packageName, size_t packageNameSize) 328 { 329 // open the activated-packages file 330 static const size_t kBufferSize = 3 * B_FILE_NAME_LENGTH + 2; 331 ArrayDeleter<char> path(new(std::nothrow) char[kBufferSize]); 332 if (!path.IsSet()) 333 return B_NO_MEMORY; 334 snprintf(path.Get(), kBufferSize, "%s/%s/%s", 335 kAdministrativeDirectory, state->Name() != NULL ? state->Name() : "", 336 kActivatedPackagesFile); 337 FileDescriptorCloser fd(open_from(packagesDirectory, path.Get(), O_RDONLY)); 338 if (!fd.IsSet()) 339 return fd.Get(); 340 341 struct stat st; 342 if (fstat(fd.Get(), &st) != 0) 343 return errno; 344 if (!S_ISREG(st.st_mode)) 345 return B_ENTRY_NOT_FOUND; 346 347 // read the file until we find the system package line 348 size_t remainingBytes = 0; 349 for (;;) { 350 ssize_t bytesRead = read(fd.Get(), path.Get() + remainingBytes, 351 kBufferSize - remainingBytes - 1); 352 if (bytesRead <= 0) 353 return B_ENTRY_NOT_FOUND; 354 355 remainingBytes += bytesRead; 356 path[remainingBytes] = '\0'; 357 358 char* line = path.Get(); 359 while (char* lineEnd = strchr(line, '\n')) { 360 *lineEnd = '\0'; 361 if (is_system_package(line)) { 362 status_t result = strlcpy(packageName, line, packageNameSize) 363 < packageNameSize 364 ? B_OK : B_NAME_TOO_LONG; 365 return result; 366 } 367 368 line = lineEnd + 1; 369 } 370 371 // move the remainder to the start of the buffer 372 if (line < path.Get() + remainingBytes) { 373 size_t left = path.Get() + remainingBytes - line; 374 memmove(path.Get(), line, left); 375 remainingBytes = left; 376 } else 377 remainingBytes = 0; 378 } 379 380 return B_ENTRY_NOT_FOUND; 381 } 382