xref: /haiku/src/apps/haikudepot/util/StorageUtils.cpp (revision d4e4909c6a3fe4290b78be2b78035c4774e3ff18)
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