1 /* 2 * Copyright 2017-2021, 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 11 #include <Directory.h> 12 #include <File.h> 13 #include <FindDirectory.h> 14 #include <Entry.h> 15 #include <String.h> 16 17 #include "HaikuDepotConstants.h" 18 #include "Logger.h" 19 20 #define FILE_TO_STRING_BUFFER_LEN 64 21 22 23 static bool sAreWorkingFilesAvailable = true; 24 25 26 /*static*/ bool 27 StorageUtils::AreWorkingFilesAvailable() 28 { 29 return sAreWorkingFilesAvailable; 30 } 31 32 33 /*static*/ void 34 StorageUtils::SetWorkingFilesUnavailable() 35 { 36 sAreWorkingFilesAvailable = false; 37 } 38 39 40 /* This method will append the contents of the file at the supplied path to the 41 * string provided. 42 */ 43 44 /*static*/ status_t 45 StorageUtils::AppendToString(const BPath& path, BString& result) 46 { 47 BFile file(path.Path(), O_RDONLY); 48 uint8_t buffer[FILE_TO_STRING_BUFFER_LEN]; 49 size_t buffer_read; 50 51 while((buffer_read = file.Read(buffer, FILE_TO_STRING_BUFFER_LEN)) > 0) 52 result.Append((char *) buffer, buffer_read); 53 54 return (status_t) buffer_read; 55 } 56 57 58 /*static*/ status_t 59 StorageUtils::AppendToFile(const BString& input, const BPath& path) 60 { 61 BFile file(path.Path(), O_WRONLY | O_CREAT | O_APPEND); 62 const char* cstr = input.String(); 63 size_t cstrLen = strlen(cstr); 64 return file.WriteExactly(cstr, cstrLen); 65 } 66 67 68 /*static*/ status_t 69 StorageUtils::RemoveWorkingDirectoryContents() 70 { 71 BPath path; 72 status_t result = B_OK; 73 74 if (result == B_OK) 75 result = find_directory(B_USER_CACHE_DIRECTORY, &path); 76 if (result == B_OK) 77 result = path.Append(CACHE_DIRECTORY_APP); 78 79 bool exists; 80 bool isDirectory; 81 82 if (result == B_OK) 83 result = ExistsObject(path, &exists, &isDirectory, NULL); 84 85 if (result == B_OK && exists && !isDirectory) { 86 HDERROR("the working directory at [%s] is not a directory", 87 path.Path()); 88 result = B_ERROR; 89 } 90 91 if (result == B_OK && exists) 92 result = RemoveDirectoryContents(path); 93 94 return result; 95 } 96 97 98 /* This method will traverse the directory structure and will remove all of the 99 * files that are present in the directories as well as the directories 100 * themselves. 101 */ 102 103 status_t 104 StorageUtils::RemoveDirectoryContents(BPath& path) 105 { 106 BDirectory directory(path.Path()); 107 BEntry directoryEntry; 108 status_t result = B_OK; 109 110 while (result == B_OK && 111 directory.GetNextEntry(&directoryEntry) != B_ENTRY_NOT_FOUND) { 112 113 bool exists = false; 114 bool isDirectory = false; 115 BPath directoryEntryPath; 116 117 result = directoryEntry.GetPath(&directoryEntryPath); 118 119 if (result == B_OK) { 120 result = ExistsObject(directoryEntryPath, &exists, &isDirectory, 121 NULL); 122 } 123 124 if (result == B_OK) { 125 if (isDirectory) 126 RemoveDirectoryContents(directoryEntryPath); 127 128 if (remove(directoryEntryPath.Path()) == 0) { 129 HDDEBUG("did delete contents under [%s]", 130 directoryEntryPath.Path()); 131 } else { 132 HDERROR("unable to delete [%s]", directoryEntryPath.Path()); 133 result = B_ERROR; 134 } 135 } 136 137 } 138 139 return result; 140 } 141 142 143 /* This method checks to see if a file object exists at the path specified. If 144 * something does exist then the value of the 'exists' pointer is set to true. 145 * If the object is a directory then this value is also set to true. 146 */ 147 148 status_t 149 StorageUtils::ExistsObject(const BPath& path, 150 bool* exists, 151 bool* isDirectory, 152 off_t* size) 153 { 154 struct stat s; 155 156 if (exists != NULL) 157 *exists = false; 158 159 if (isDirectory != NULL) 160 *isDirectory = false; 161 162 if (size != NULL) 163 *size = 0; 164 165 if (-1 == stat(path.Path(), &s)) { 166 if (ENOENT != errno) 167 return B_ERROR; 168 } else { 169 if (exists != NULL) 170 *exists = true; 171 172 if (isDirectory != NULL) 173 *isDirectory = S_ISDIR(s.st_mode); 174 175 if (size != NULL) 176 *size = s.st_size; 177 } 178 179 return B_OK; 180 } 181 182 183 /*! This method will check that it is possible to write to the specified file. 184 This may create the file, write some data to it and then read that data 185 back again to be sure. This can be used as an effective safety measure as 186 the application starts up in order to ensure that the storage systems are 187 in place for the application to startup. 188 189 It is assumed here that the directory containing the test file exists. 190 */ 191 192 /*static*/ status_t 193 StorageUtils::CheckCanWriteTo(const BPath& path) 194 { 195 status_t result = B_OK; 196 bool exists = false; 197 uint8 buffer[16]; 198 199 // create some random latin letters into the buffer to write. 200 for (int i = 0; i < 16; i++) 201 buffer[i] = 65 + (abs(rand()) % 26); 202 203 if (result == B_OK) 204 result = ExistsObject(path, &exists, NULL, NULL); 205 206 if (result == B_OK && exists) { 207 HDTRACE("an object exists at the candidate path " 208 "[%s] - it will be deleted", path.Path()); 209 210 if (remove(path.Path()) == 0) { 211 HDTRACE("did delete the candidate file [%s]", path.Path()); 212 } else { 213 HDERROR("unable to delete the candidate file [%s]", path.Path()); 214 result = B_ERROR; 215 } 216 } 217 218 if (result == B_OK) { 219 BFile file(path.Path(), O_WRONLY | O_CREAT); 220 if (file.Write(buffer, 16) != 16) { 221 HDERROR("unable to write test data to candidate file [%s]", 222 path.Path()); 223 result = B_ERROR; 224 } 225 } 226 227 if (result == B_OK) { 228 BFile file(path.Path(), O_RDONLY); 229 uint8 readBuffer[16]; 230 if (file.Read(readBuffer, 16) != 16) { 231 HDERROR("unable to read test data from candidate file [%s]", 232 path.Path()); 233 result = B_ERROR; 234 } 235 236 for (int i = 0; result == B_OK && i < 16; i++) { 237 if (readBuffer[i] != buffer[i]) { 238 HDERROR("mismatched read..write check on candidate file [%s]", 239 path.Path()); 240 result = B_ERROR; 241 } 242 } 243 } 244 245 return result; 246 } 247 248 249 /*! As the application runs it will need to store some files into the local 250 disk system. This method, given a leafname, will write into the supplied 251 path variable, a final path where this leafname should be stored. 252 */ 253 254 /*static*/ status_t 255 StorageUtils::LocalWorkingFilesPath(const BString leaf, BPath& path, 256 bool failOnCreateDirectory) 257 { 258 BPath resultPath; 259 status_t result = B_OK; 260 261 if (result == B_OK) 262 result = find_directory(B_USER_CACHE_DIRECTORY, &resultPath); 263 264 if (result == B_OK) 265 result = resultPath.Append(CACHE_DIRECTORY_APP); 266 267 if (result == B_OK) { 268 if (failOnCreateDirectory) 269 result = create_directory(resultPath.Path(), 0777); 270 else 271 create_directory(resultPath.Path(), 0777); 272 } 273 274 if (result == B_OK) 275 result = resultPath.Append(leaf); 276 277 if (result == B_OK) 278 path.SetTo(resultPath.Path()); 279 else { 280 path.Unset(); 281 HDERROR("unable to find the user cache file for " 282 "[%s] data; %s", leaf.String(), strerror(result)); 283 } 284 285 return result; 286 } 287 288 289 /*static*/ status_t 290 StorageUtils::LocalWorkingDirectoryPath(const BString leaf, BPath& path, 291 bool failOnCreateDirectory) 292 { 293 BPath resultPath; 294 status_t result = B_OK; 295 296 if (result == B_OK) 297 result = find_directory(B_USER_CACHE_DIRECTORY, &resultPath); 298 299 if (result == B_OK) 300 result = resultPath.Append(CACHE_DIRECTORY_APP); 301 302 if (result == B_OK) 303 result = resultPath.Append(leaf); 304 305 if (result == B_OK) { 306 if (failOnCreateDirectory) 307 result = create_directory(resultPath.Path(), 0777); 308 else 309 create_directory(resultPath.Path(), 0777); 310 } 311 312 if (result == B_OK) 313 path.SetTo(resultPath.Path()); 314 else { 315 path.Unset(); 316 HDERROR("unable to find the user cache directory for " 317 "[%s] data; %s", leaf.String(), strerror(result)); 318 } 319 320 return result; 321 } 322 323 324 /*static*/ status_t 325 StorageUtils::SwapExtensionOnPath(BPath& path, const char* extension) 326 { 327 BPath parent; 328 status_t result = path.GetParent(&parent); 329 if (result == B_OK) { 330 path.SetTo(parent.Path(), 331 SwapExtensionOnPathComponent(path.Leaf(), extension).String()); 332 } 333 return result; 334 } 335 336 337 /*static*/ BString 338 StorageUtils::SwapExtensionOnPathComponent(const char* pathComponent, 339 const char* extension) 340 { 341 BString result(pathComponent); 342 int32 lastDot = result.FindLast("."); 343 if (lastDot != B_ERROR) { 344 result.Truncate(lastDot); 345 } 346 result.Append("."); 347 result.Append(extension); 348 return result; 349 } 350