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:
PathWithLastAccessTimestamp(const BPath path,uint64 lastAccessMillisSinceEpoch)30 PathWithLastAccessTimestamp(const BPath path, uint64 lastAccessMillisSinceEpoch)
31 :
32 fPath(path),
33 fLastAccessMillisSinceEpoch(lastAccessMillisSinceEpoch)
34 {
35 }
36
~PathWithLastAccessTimestamp()37 ~PathWithLastAccessTimestamp()
38 {
39 }
40
Path() const41 const BPath& Path() const
42 {
43 return fPath;
44 }
45
LastAccessMillisSinceEpoch() const46 int64 LastAccessMillisSinceEpoch() const
47 {
48 return fLastAccessMillisSinceEpoch;
49 }
50
String() const51 BString String() const
52 {
53 BString result;
54 result.SetToFormat("%s; @ %" B_PRIu64, fPath.Leaf(), fLastAccessMillisSinceEpoch);
55 return result;
56 }
57
operator <(const PathWithLastAccessTimestamp & other) const58 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
AreWorkingFilesAvailable()71 StorageUtils::AreWorkingFilesAvailable()
72 {
73 return sAreWorkingFilesAvailable;
74 }
75
76
77 /*static*/ void
SetWorkingFilesUnavailable()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
AppendToString(const BPath & path,BString & result)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
AppendToFile(const BString & input,const BPath & path)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
RemoveWorkingDirectoryContents()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
RemoveDirectoryContents(BPath & path)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
RemoveDirectoryContentsRetainingLatestFiles(BPath & path,uint32 countLatestRetained)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
ExistsObject(const BPath & path,bool * exists,bool * isDirectory,off_t * size)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
CheckCanWriteTo(const BPath & path)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
LocalWorkingFilesPath(const BString leaf,BPath & path,bool failOnCreateDirectory)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
LocalWorkingDirectoryPath(const BString leaf,BPath & path,bool failOnCreateDirectory)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
SwapExtensionOnPath(BPath & path,const char * extension)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
SwapExtensionOnPathComponent(const char * pathComponent,const char * extension)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
DumpExportRepositoryDataPath(BPath & path,const LanguageRef language)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
DumpExportReferenceDataPath(BPath & path,const LanguageRef language)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
IconTarPath(BPath & path)487 StorageUtils::IconTarPath(BPath& path)
488 {
489 return LocalWorkingFilesPath("pkgicon-all.tar", path);
490 }
491
492
493 status_t
DumpExportPkgDataPath(BPath & path,const BString & repositorySourceCode,const LanguageRef language)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