1 /* 2 * Copyright 2017-2023, Andrew Lindesay <apl@lindesay.co.nz>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 #include "StorageUtils.h" 7 8 #include <errno.h> 9 #include <stdlib.h> 10 #include <vector> 11 #include <algorithm> 12 13 #include <Directory.h> 14 #include <File.h> 15 #include <FindDirectory.h> 16 #include <Entry.h> 17 #include <String.h> 18 19 #include "HaikuDepotConstants.h" 20 #include "Logger.h" 21 22 #define FILE_TO_STRING_BUFFER_LEN 64 23 24 25 static bool sAreWorkingFilesAvailable = true; 26 27 28 class PathWithLastAccessTimestamp { 29 public: 30 PathWithLastAccessTimestamp(const BPath path, uint64 lastAccessMillisSinceEpoch) 31 : 32 fPath(path), 33 fLastAccessMillisSinceEpoch(lastAccessMillisSinceEpoch) 34 { 35 } 36 37 ~PathWithLastAccessTimestamp() 38 { 39 } 40 41 const BPath& Path() const 42 { 43 return fPath; 44 } 45 46 int64 LastAccessMillisSinceEpoch() const 47 { 48 return fLastAccessMillisSinceEpoch; 49 } 50 51 BString String() const 52 { 53 BString result; 54 result.SetToFormat("%s; @ %" B_PRIu64, fPath.Leaf(), fLastAccessMillisSinceEpoch); 55 return result; 56 } 57 58 bool operator<(const PathWithLastAccessTimestamp& other) const 59 { 60 return fLastAccessMillisSinceEpoch < other.fLastAccessMillisSinceEpoch && 61 strcmp(fPath.Path(), other.fPath.Path()) < 0; 62 } 63 64 private: 65 BPath fPath; 66 uint64 fLastAccessMillisSinceEpoch; 67 }; 68 69 70 /*static*/ bool 71 StorageUtils::AreWorkingFilesAvailable() 72 { 73 return sAreWorkingFilesAvailable; 74 } 75 76 77 /*static*/ void 78 StorageUtils::SetWorkingFilesUnavailable() 79 { 80 sAreWorkingFilesAvailable = false; 81 } 82 83 84 /* This method will append the contents of the file at the supplied path to the 85 * string provided. 86 */ 87 88 /*static*/ status_t 89 StorageUtils::AppendToString(const BPath& path, BString& result) 90 { 91 BFile file(path.Path(), O_RDONLY); 92 uint8_t buffer[FILE_TO_STRING_BUFFER_LEN]; 93 size_t buffer_read; 94 95 while((buffer_read = file.Read(buffer, FILE_TO_STRING_BUFFER_LEN)) > 0) 96 result.Append((char *) buffer, buffer_read); 97 98 return (status_t) buffer_read; 99 } 100 101 102 /*static*/ status_t 103 StorageUtils::AppendToFile(const BString& input, const BPath& path) 104 { 105 BFile file(path.Path(), O_WRONLY | O_CREAT | O_APPEND); 106 const char* cstr = input.String(); 107 size_t cstrLen = strlen(cstr); 108 return file.WriteExactly(cstr, cstrLen); 109 } 110 111 112 /*static*/ status_t 113 StorageUtils::RemoveWorkingDirectoryContents() 114 { 115 BPath path; 116 status_t result = B_OK; 117 118 if (result == B_OK) 119 result = find_directory(B_USER_CACHE_DIRECTORY, &path); 120 if (result == B_OK) 121 result = path.Append(CACHE_DIRECTORY_APP); 122 123 bool exists; 124 bool isDirectory; 125 126 if (result == B_OK) 127 result = ExistsObject(path, &exists, &isDirectory, NULL); 128 129 if (result == B_OK && exists && !isDirectory) { 130 HDERROR("the working directory at [%s] is not a directory", 131 path.Path()); 132 result = B_ERROR; 133 } 134 135 if (result == B_OK && exists) 136 result = RemoveDirectoryContents(path); 137 138 return result; 139 } 140 141 142 /* This method will traverse the directory structure and will remove all of the 143 * files that are present in the directories as well as the directories 144 * themselves. 145 */ 146 147 status_t 148 StorageUtils::RemoveDirectoryContents(BPath& path) 149 { 150 BDirectory directory(path.Path()); 151 BEntry directoryEntry; 152 status_t result = B_OK; 153 154 while (result == B_OK && 155 directory.GetNextEntry(&directoryEntry) != B_ENTRY_NOT_FOUND) { 156 157 bool exists = false; 158 bool isDirectory = false; 159 BPath directoryEntryPath; 160 161 result = directoryEntry.GetPath(&directoryEntryPath); 162 163 if (result == B_OK) { 164 result = ExistsObject(directoryEntryPath, &exists, &isDirectory, 165 NULL); 166 } 167 168 if (result == B_OK) { 169 if (isDirectory) 170 RemoveDirectoryContents(directoryEntryPath); 171 172 if (remove(directoryEntryPath.Path()) == 0) { 173 HDDEBUG("did delete contents under [%s]", 174 directoryEntryPath.Path()); 175 } else { 176 HDERROR("unable to delete [%s]", directoryEntryPath.Path()); 177 result = B_ERROR; 178 } 179 } 180 181 } 182 183 return result; 184 } 185 186 187 /*! This function will delete all of the files in a directory except for the most 188 recent `countLatestRetained` files. 189 */ 190 191 /*static*/ status_t 192 StorageUtils::RemoveDirectoryContentsRetainingLatestFiles(BPath& path, uint32 countLatestRetained) 193 { 194 std::vector<PathWithLastAccessTimestamp> pathAndTimestampses; 195 BDirectory directory(path.Path()); 196 BEntry directoryEntry; 197 status_t result = B_OK; 198 struct stat s; 199 200 while (result == B_OK && 201 directory.GetNextEntry(&directoryEntry) != B_ENTRY_NOT_FOUND) { 202 BPath directoryEntryPath; 203 result = directoryEntry.GetPath(&directoryEntryPath); 204 205 if (result == B_OK) { 206 if (-1 == stat(directoryEntryPath.Path(), &s)) 207 result = B_ERROR; 208 } 209 210 if (result == B_OK) { 211 pathAndTimestampses.push_back(PathWithLastAccessTimestamp( 212 directoryEntryPath, 213 (static_cast<uint64>(s.st_atim.tv_sec) * 1000) + (static_cast<uint64>(s.st_atim.tv_nsec) / 1000))); 214 } 215 } 216 217 if (pathAndTimestampses.size() > countLatestRetained) { 218 219 // sort the list with the oldest files first (smallest fLastAccessMillisSinceEpoch) 220 std::sort(pathAndTimestampses.begin(), pathAndTimestampses.end()); 221 222 std::vector<PathWithLastAccessTimestamp>::iterator it; 223 224 if (Logger::IsTraceEnabled()) { 225 for (it = pathAndTimestampses.begin(); it != pathAndTimestampses.end(); it++) { 226 PathWithLastAccessTimestamp pathAndTimestamp = *it; 227 HDTRACE("delete candidate [%s]", pathAndTimestamp.String().String()); 228 } 229 } 230 231 for (it = pathAndTimestampses.begin(); it != pathAndTimestampses.end() - countLatestRetained; it++) { 232 PathWithLastAccessTimestamp pathAndTimestamp = *it; 233 const char* pathStr = pathAndTimestamp.Path().Path(); 234 235 if (remove(pathStr) == 0) 236 HDDEBUG("did delete [%s]", pathStr); 237 else { 238 HDERROR("unable to delete [%s]", pathStr); 239 result = B_ERROR; 240 } 241 } 242 } 243 244 return result; 245 } 246 247 248 /* This method checks to see if a file object exists at the path specified. If 249 * something does exist then the value of the 'exists' pointer is set to true. 250 * If the object is a directory then this value is also set to true. 251 */ 252 253 status_t 254 StorageUtils::ExistsObject(const BPath& path, 255 bool* exists, 256 bool* isDirectory, 257 off_t* size) 258 { 259 struct stat s; 260 261 if (exists != NULL) 262 *exists = false; 263 264 if (isDirectory != NULL) 265 *isDirectory = false; 266 267 if (size != NULL) 268 *size = 0; 269 270 if (-1 == stat(path.Path(), &s)) { 271 if (ENOENT != errno) 272 return B_ERROR; 273 } else { 274 if (exists != NULL) 275 *exists = true; 276 277 if (isDirectory != NULL) 278 *isDirectory = S_ISDIR(s.st_mode); 279 280 if (size != NULL) 281 *size = s.st_size; 282 } 283 284 return B_OK; 285 } 286 287 288 /*! This method will check that it is possible to write to the specified file. 289 This may create the file, write some data to it and then read that data 290 back again to be sure. This can be used as an effective safety measure as 291 the application starts up in order to ensure that the storage systems are 292 in place for the application to startup. 293 294 It is assumed here that the directory containing the test file exists. 295 */ 296 297 /*static*/ status_t 298 StorageUtils::CheckCanWriteTo(const BPath& path) 299 { 300 status_t result = B_OK; 301 bool exists = false; 302 uint8 buffer[16]; 303 304 // create some random latin letters into the buffer to write. 305 for (int i = 0; i < 16; i++) 306 buffer[i] = 65 + (abs(rand()) % 26); 307 308 if (result == B_OK) 309 result = ExistsObject(path, &exists, NULL, NULL); 310 311 if (result == B_OK && exists) { 312 HDTRACE("an object exists at the candidate path " 313 "[%s] - it will be deleted", path.Path()); 314 315 if (remove(path.Path()) == 0) { 316 HDTRACE("did delete the candidate file [%s]", path.Path()); 317 } else { 318 HDERROR("unable to delete the candidate file [%s]", path.Path()); 319 result = B_ERROR; 320 } 321 } 322 323 if (result == B_OK) { 324 BFile file(path.Path(), O_WRONLY | O_CREAT); 325 if (file.Write(buffer, 16) != 16) { 326 HDERROR("unable to write test data to candidate file [%s]", 327 path.Path()); 328 result = B_ERROR; 329 } 330 } 331 332 if (result == B_OK) { 333 BFile file(path.Path(), O_RDONLY); 334 uint8 readBuffer[16]; 335 if (file.Read(readBuffer, 16) != 16) { 336 HDERROR("unable to read test data from candidate file [%s]", 337 path.Path()); 338 result = B_ERROR; 339 } 340 341 for (int i = 0; result == B_OK && i < 16; i++) { 342 if (readBuffer[i] != buffer[i]) { 343 HDERROR("mismatched read..write check on candidate file [%s]", 344 path.Path()); 345 result = B_ERROR; 346 } 347 } 348 } 349 350 return result; 351 } 352 353 354 /*! As the application runs it will need to store some files into the local 355 disk system. This method, given a leafname, will write into the supplied 356 path variable, a final path where this leafname should be stored. 357 */ 358 359 /*static*/ status_t 360 StorageUtils::LocalWorkingFilesPath(const BString leaf, BPath& path, 361 bool failOnCreateDirectory) 362 { 363 BPath resultPath; 364 status_t result = B_OK; 365 366 if (result == B_OK) 367 result = find_directory(B_USER_CACHE_DIRECTORY, &resultPath); 368 369 if (result == B_OK) 370 result = resultPath.Append(CACHE_DIRECTORY_APP); 371 372 if (result == B_OK) { 373 if (failOnCreateDirectory) 374 result = create_directory(resultPath.Path(), 0777); 375 else 376 create_directory(resultPath.Path(), 0777); 377 } 378 379 if (result == B_OK) 380 result = resultPath.Append(leaf); 381 382 if (result == B_OK) 383 path.SetTo(resultPath.Path()); 384 else { 385 path.Unset(); 386 HDERROR("unable to find the user cache file for " 387 "[%s] data; %s", leaf.String(), strerror(result)); 388 } 389 390 return result; 391 } 392 393 394 /*static*/ status_t 395 StorageUtils::LocalWorkingDirectoryPath(const BString leaf, BPath& path, 396 bool failOnCreateDirectory) 397 { 398 BPath resultPath; 399 status_t result = B_OK; 400 401 if (result == B_OK) 402 result = find_directory(B_USER_CACHE_DIRECTORY, &resultPath); 403 404 if (result == B_OK) 405 result = resultPath.Append(CACHE_DIRECTORY_APP); 406 407 if (result == B_OK) 408 result = resultPath.Append(leaf); 409 410 if (result == B_OK) { 411 if (failOnCreateDirectory) 412 result = create_directory(resultPath.Path(), 0777); 413 else 414 create_directory(resultPath.Path(), 0777); 415 } 416 417 if (result == B_OK) 418 path.SetTo(resultPath.Path()); 419 else { 420 path.Unset(); 421 HDERROR("unable to find the user cache directory for " 422 "[%s] data; %s", leaf.String(), strerror(result)); 423 } 424 425 return result; 426 } 427 428 429 /*static*/ status_t 430 StorageUtils::SwapExtensionOnPath(BPath& path, const char* extension) 431 { 432 BPath parent; 433 status_t result = path.GetParent(&parent); 434 if (result == B_OK) { 435 path.SetTo(parent.Path(), 436 SwapExtensionOnPathComponent(path.Leaf(), extension).String()); 437 } 438 return result; 439 } 440 441 442 /*static*/ BString 443 StorageUtils::SwapExtensionOnPathComponent(const char* pathComponent, 444 const char* extension) 445 { 446 BString result(pathComponent); 447 int32 lastDot = result.FindLast("."); 448 if (lastDot != B_ERROR) { 449 result.Truncate(lastDot); 450 } 451 result.Append("."); 452 result.Append(extension); 453 return result; 454 } 455 456 457 /*! When bulk repository data comes down from the server, it will 458 arrive as a json.gz payload. This is stored locally as a cache 459 and this method will provide the on-disk storage location for 460 this file. 461 */ 462 463 status_t 464 StorageUtils::DumpExportRepositoryDataPath(BPath& path, const LanguageRef language) 465 { 466 BString leaf; 467 leaf.SetToFormat("repository-all_%s.json.gz", language->ID()); 468 return LocalWorkingFilesPath(leaf, path); 469 } 470 471 472 /*! When the system downloads reference data (eg; categories) from the server 473 then the downloaded data is stored and cached at the path defined by this 474 method. 475 */ 476 477 status_t 478 StorageUtils::DumpExportReferenceDataPath(BPath& path, const LanguageRef language) 479 { 480 BString leaf; 481 leaf.SetToFormat("reference-all_%s.json.gz", language->ID()); 482 return LocalWorkingFilesPath(leaf, path); 483 } 484 485 486 status_t 487 StorageUtils::IconTarPath(BPath& path) 488 { 489 return LocalWorkingFilesPath("pkgicon-all.tar", path); 490 } 491 492 493 status_t 494 StorageUtils::DumpExportPkgDataPath(BPath& path, const BString& repositorySourceCode, 495 const LanguageRef language) 496 { 497 BString leaf; 498 leaf.SetToFormat("pkg-all-%s-%s.json.gz", repositorySourceCode.String(), language->ID()); 499 return LocalWorkingFilesPath(leaf, path); 500 } 501