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 version"; 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 { 157 } 158 159 160 PackageVolumeInfo::~PackageVolumeInfo() 161 { 162 while (PackageVolumeState* state = fStates.RemoveHead()) 163 delete state; 164 } 165 166 167 status_t 168 PackageVolumeInfo::SetTo(Directory* baseDirectory, const char* packagesPath) 169 { 170 TRACE("PackageVolumeInfo::SetTo()\n"); 171 172 // get the packages directory 173 DIR* dir = open_directory(baseDirectory, packagesPath); 174 if (dir == NULL) { 175 TRACE("PackageVolumeInfo::SetTo(): failed to open packages directory: " 176 "%s\n", strerror(errno)); 177 return errno; 178 } 179 CObjectDeleter<DIR, int> dirCloser(dir, &closedir); 180 181 Directory* packagesDirectory = directory_from(dir); 182 packagesDirectory->Acquire(); 183 184 // add the current state 185 PackageVolumeState* state = _AddState(NULL); 186 if (state == NULL) 187 return B_NO_MEMORY; 188 status_t error = _InitState(packagesDirectory, dir, state); 189 if (error != B_OK) { 190 TRACE("PackageVolumeInfo::SetTo(): failed to init current state: " 191 "%s\n", strerror(error)); 192 return error; 193 } 194 195 // Iterate through the packages/administrative directory to find old state 196 // directories. 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); 203 } 204 } 205 206 closedir(administrativeDir); 207 208 fStates.Sort(&PackageVolumeState::IsNewer); 209 210 // initialize the old states 211 for (state = fStates.GetNext(state); state != NULL;) { 212 PackageVolumeState* nextState = fStates.GetNext(state); 213 if (state->Name()) { 214 error = _InitState(packagesDirectory, dir, state); 215 if (error != B_OK) { 216 TRACE("PackageVolumeInfo::SetTo(): failed to init state " 217 "\"%s\": %s\n", state->Name(), strerror(error)); 218 fStates.Remove(state); 219 delete state; 220 } 221 } 222 state = nextState; 223 } 224 } else { 225 TRACE("PackageVolumeInfo::SetTo(): failed to open administrative " 226 "directory: %s\n", strerror(errno)); 227 } 228 229 return B_OK; 230 } 231 232 233 PackageVolumeState* 234 PackageVolumeInfo::_AddState(const char* stateName) 235 { 236 PackageVolumeState* state = new(std::nothrow) PackageVolumeState; 237 if (state == NULL) 238 return NULL; 239 240 if (state->SetTo(stateName) != B_OK) { 241 delete state; 242 return NULL; 243 } 244 245 fStates.Add(state); 246 return state; 247 } 248 249 250 status_t 251 PackageVolumeInfo::_InitState(Directory* packagesDirectory, DIR* dir, 252 PackageVolumeState* state) 253 { 254 // find the system package 255 char systemPackageName[B_FILE_NAME_LENGTH]; 256 status_t error = _ParseActivatedPackagesFile(packagesDirectory, state, 257 systemPackageName, sizeof(systemPackageName)); 258 if (error == B_OK) { 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)); 265 struct stat st; 266 if (get_stat(packagesDirectory, packagePath, st) == B_OK 267 && S_ISREG(st.st_mode)) { 268 state->SetSystemPackage(packagePath); 269 break; 270 } 271 } 272 } else { 273 TRACE("PackageVolumeInfo::_InitState(): failed to parse " 274 "activated-packages: %s\n", strerror(error)); 275 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; 281 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); 286 break; 287 } 288 } 289 } 290 291 if (state->SystemPackage() == NULL) 292 return B_ENTRY_NOT_FOUND; 293 294 return B_OK; 295 } 296 297 298 status_t 299 PackageVolumeInfo::_ParseActivatedPackagesFile(Directory* packagesDirectory, 300 PackageVolumeState* state, char* packageName, size_t packageNameSize) 301 { 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); 308 if (fd < 0) 309 return fd; 310 FileDescriptorCloser fdCloser(fd); 311 312 struct stat st; 313 if (fstat(fd, &st) != 0) 314 return errno; 315 if (!S_ISREG(st.st_mode)) 316 return B_ENTRY_NOT_FOUND; 317 318 // read the file until we find the system package line 319 size_t remainingBytes = 0; 320 for (;;) { 321 ssize_t bytesRead = read(fd, path + remainingBytes, 322 sizeof(path) - remainingBytes - 1); 323 if (bytesRead <= 0) 324 return B_ENTRY_NOT_FOUND; 325 326 remainingBytes += bytesRead; 327 path[remainingBytes] = '\0'; 328 329 char* line = path; 330 while (char* lineEnd = strchr(line, '\n')) { 331 *lineEnd = '\0'; 332 if (is_system_package(line)) { 333 return strlcpy(packageName, line, packageNameSize) 334 < packageNameSize 335 ? B_OK : B_NAME_TOO_LONG; 336 } 337 338 line = lineEnd + 1; 339 } 340 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; 346 } else 347 remainingBytes = 0; 348 } 349 350 return B_ENTRY_NOT_FOUND; 351 } 352