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 if (fPackagesDir == NULL) { 208 TRACE("PackageVolumeInfo::LoadOldStates(): package directory is NULL"); 209 return B_ERROR; 210 } 211 212 Directory* packagesDirectory = directory_from(fPackagesDir); 213 packagesDirectory->Acquire(); 214 215 if (DIR* administrativeDir = open_directory(packagesDirectory, 216 kAdministrativeDirectory)) { 217 while (dirent* entry = readdir(administrativeDir)) { 218 if (strncmp(entry->d_name, "state_", 6) == 0) { 219 TRACE(" old state directory \"%s\"\n", entry->d_name); 220 _AddState(entry->d_name); 221 } 222 } 223 224 closedir(administrativeDir); 225 226 fStates.Sort(&PackageVolumeState::IsNewer); 227 228 // initialize the old states 229 PackageVolumeState* state = NULL; 230 status_t error; 231 for (state = fStates.GetNext(state); state != NULL;) { 232 PackageVolumeState* nextState = fStates.GetNext(state); 233 if (state->Name()) { 234 error = _InitState(packagesDirectory, fPackagesDir, state); 235 if (error != B_OK) { 236 TRACE("PackageVolumeInfo::LoadOldStates(): failed to " 237 "init state \"%s\": %s\n", state->Name(), 238 strerror(error)); 239 fStates.Remove(state); 240 delete state; 241 } 242 } 243 state = nextState; 244 } 245 } else { 246 TRACE("PackageVolumeInfo::LoadOldStates(): failed to open " 247 "administrative directory: %s\n", strerror(errno)); 248 } 249 250 return B_OK; 251 } 252 253 254 PackageVolumeState* 255 PackageVolumeInfo::_AddState(const char* stateName) 256 { 257 PackageVolumeState* state = new(std::nothrow) PackageVolumeState; 258 if (state == NULL) 259 return NULL; 260 261 if (state->SetTo(stateName) != B_OK) { 262 delete state; 263 return NULL; 264 } 265 266 fStates.Add(state); 267 return state; 268 } 269 270 271 status_t 272 PackageVolumeInfo::_InitState(Directory* packagesDirectory, DIR* dir, 273 PackageVolumeState* state) 274 { 275 // find the system package 276 char systemPackageName[B_FILE_NAME_LENGTH]; 277 status_t error = _ParseActivatedPackagesFile(packagesDirectory, state, 278 systemPackageName, sizeof(systemPackageName)); 279 if (error == B_OK) { 280 // check, if package exists 281 for (PackageVolumeState* otherState = state; otherState != NULL; 282 otherState = fStates.GetPrevious(otherState)) { 283 char packagePath[B_PATH_NAME_LENGTH]; 284 otherState->GetPackagePath(systemPackageName, packagePath, 285 sizeof(packagePath)); 286 struct stat st; 287 if (get_stat(packagesDirectory, packagePath, st) == B_OK 288 && S_ISREG(st.st_mode)) { 289 state->SetSystemPackage(packagePath); 290 break; 291 } 292 } 293 } else { 294 TRACE("PackageVolumeInfo::_InitState(): failed to parse " 295 "activated-packages: %s\n", strerror(error)); 296 297 // No or invalid activated-packages file. That is OK for the current 298 // state. We'll iterate through the packages directory to find the 299 // system package. We don't do that for old states, though. 300 if (state->Name() != NULL) 301 return B_ENTRY_NOT_FOUND; 302 303 while (dirent* entry = readdir(dir)) { 304 // The name must end with ".hpkg". 305 if (is_system_package(entry->d_name)) { 306 state->SetSystemPackage(entry->d_name); 307 break; 308 } 309 } 310 } 311 312 if (state->SystemPackage() == NULL) 313 return B_ENTRY_NOT_FOUND; 314 315 return B_OK; 316 } 317 318 319 status_t 320 PackageVolumeInfo::_ParseActivatedPackagesFile(Directory* packagesDirectory, 321 PackageVolumeState* state, char* packageName, size_t packageNameSize) 322 { 323 // open the activated-packages file 324 char path[3 * B_FILE_NAME_LENGTH + 2]; 325 snprintf(path, sizeof(path), "%s/%s/%s", 326 kAdministrativeDirectory, state->Name() != NULL ? state->Name() : "", 327 kActivatedPackagesFile); 328 int fd = open_from(packagesDirectory, path, O_RDONLY); 329 if (fd < 0) 330 return fd; 331 FileDescriptorCloser fdCloser(fd); 332 333 struct stat st; 334 if (fstat(fd, &st) != 0) 335 return errno; 336 if (!S_ISREG(st.st_mode)) 337 return B_ENTRY_NOT_FOUND; 338 339 // read the file until we find the system package line 340 size_t remainingBytes = 0; 341 for (;;) { 342 ssize_t bytesRead = read(fd, path + remainingBytes, 343 sizeof(path) - remainingBytes - 1); 344 if (bytesRead <= 0) 345 return B_ENTRY_NOT_FOUND; 346 347 remainingBytes += bytesRead; 348 path[remainingBytes] = '\0'; 349 350 char* line = path; 351 while (char* lineEnd = strchr(line, '\n')) { 352 *lineEnd = '\0'; 353 if (is_system_package(line)) { 354 return strlcpy(packageName, line, packageNameSize) 355 < packageNameSize 356 ? B_OK : B_NAME_TOO_LONG; 357 } 358 359 line = lineEnd + 1; 360 } 361 362 // move the remainder to the start of the buffer 363 if (line < path + remainingBytes) { 364 size_t left = path + remainingBytes - line; 365 memmove(path, line, left); 366 remainingBytes = left; 367 } else 368 remainingBytes = 0; 369 } 370 371 return B_ENTRY_NOT_FOUND; 372 } 373