1 /*
2  * Copyright 2006 Richard Wilson <info@tinct.net>
3  *
4  * This file is part of NetSurf, http://www.netsurf-browser.org/
5  *
6  * NetSurf is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * NetSurf is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /** \file
20  * Provides a central method of obtaining unique filenames.
21  *
22  * A maximum of 2^24 files can be allocated at any point in time.
23  */
24 
25 #include <assert.h>
26 #include <sys/types.h>
27 #include <stdbool.h>
28 #include <string.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <errno.h>
32 #include <sys/stat.h>
33 #include <unistd.h>
34 
35 #include "utils/dirent.h"
36 #include "utils/filename.h"
37 #include "utils/log.h"
38 #include "utils/utils.h"
39 
40 #define FULL_WORD (unsigned int)0xffffffffu
41 #define START_PREFIX ('0' + '0' * 10)
42 
43 struct directory {
44 	int numeric_prefix;		/** numeric representation of prefix */
45 	char prefix[10];		/** directory prefix, eg '00/11/52/' */
46 	unsigned int low_used;		/** first 32 files, 1 bit per file */
47 	unsigned int high_used;		/** last 32 files, 1 bit per file */
48 	struct directory *next;		/** next directory (sorted by prefix) */
49 };
50 
51 
52 static struct directory *root = NULL;
53 static char filename_buffer[12];
54 static char filename_directory[256];
55 
56 static struct directory *filename_create_directory(const char *prefix);
57 static bool filename_flush_directory(const char *folder, int depth);
58 static bool filename_delete_recursive(char *folder);
59 
60 /**
61  * Request a new, unique, filename.
62  *
63  * \return a pointer to a shared buffer containing the new filename,
64  *         NULL on failure
65  */
filename_request(void)66 const char *filename_request(void)
67 {
68 	struct directory *dir;
69 	int i = -1;
70 
71 	for (dir = root; dir; dir = dir->next) {
72 		if ((dir->low_used & dir->high_used) != FULL_WORD) {
73 			if (dir->low_used != FULL_WORD) {
74 				for (i = 0; (dir->low_used & (1 << i)); i++);
75 			} else {
76 				for (i = 0; (dir->high_used & (1 << i)); i++);
77 				i += 32;
78 			}
79 			break;
80 		}
81 	}
82 
83 	if (i == -1) {
84 		/* no available slots - create a new directory */
85 		dir = filename_create_directory(NULL);
86 		if (dir == NULL) {
87 			NSLOG(netsurf, INFO,
88 			      "Failed to create a new directory.");
89 			return NULL;
90 		}
91 		i = 63;
92 	}
93 
94 	if (i < 32)
95 		dir->low_used |= (1 << i);
96 	else
97 		dir->high_used |= (1 << (i - 32));
98 
99 	i = i % 99;
100 
101 	snprintf(filename_buffer, sizeof(filename_buffer), "%s%.2i", dir->prefix, i);
102 
103 	return filename_buffer;
104 }
105 
106 
107 /**
108  * Claim a specific filename.
109  *
110  * \param  filename  the filename to claim
111  * \return whether the claim was successful
112  */
filename_claim(const char * filename)113 bool filename_claim(const char *filename)
114 {
115 	char dir_prefix[9];
116 	int file;
117 	struct directory *dir;
118 
119 	/* filename format is always '01/23/45/XX' */
120 	strncpy(dir_prefix, filename, 9);
121 	dir_prefix[8] = '\0';
122 	file = (filename[10] + filename[9] * 10 - START_PREFIX);
123 
124 	/* create the directory */
125 	dir = filename_create_directory(dir_prefix);
126 	if (dir == NULL)
127 		return false;
128 
129 	/* update the entry */
130 	if (file < 32) {
131 		if (dir->low_used & (1 << file))
132 			return false;
133 		dir->low_used |= (1 << file);
134 	} else {
135 		if (dir->high_used & (1 << (file - 32)))
136 			return false;
137 		dir->high_used |= (1 << (file - 32));
138 	}
139 
140 	return true;
141 }
142 
143 
144 /**
145  * Releases a filename for future use.
146  *
147  * \param  filename  the filename to release
148  */
filename_release(const char * filename)149 void filename_release(const char *filename)
150 {
151 	struct directory *dir;
152 	int index, file;
153 
154 	/* filename format is always '01/23/45/XX' */
155 	index = ((filename[7] + filename[6] * 10 - START_PREFIX) |
156 		((filename[4] + filename[3] * 10 - START_PREFIX) << 6) |
157 		((filename[1] + filename[0] * 10 - START_PREFIX) << 12));
158 	file = (filename[10] + filename[9] * 10 - START_PREFIX);
159 
160 	/* modify the correct directory entry */
161 	for (dir = root; dir; dir = dir->next) {
162 		if (dir->numeric_prefix == index) {
163 			if (file < 32)
164 				dir->low_used &= ~(1 << file);
165 			else
166 				dir->high_used &= ~(1 << (file - 32));
167 			return;
168 		}
169 	}
170 }
171 
172 
173 /**
174  * Initialise the filename provider.
175  */
filename_initialise(void)176 bool filename_initialise(void)
177 {
178 	char *directory, *start;
179 	int ret;
180 
181 	directory = strdup(TEMP_FILENAME_PREFIX);
182 	if (directory == NULL)
183 		return false;
184 
185 	for (start = directory; *start != '\0'; start++) {
186 		if (*start == '/') {
187 			*start = '\0';
188 			NSLOG(netsurf, INFO, "Creating \"%s\"", directory);
189 			ret = nsmkdir(directory, S_IRWXU);
190 			if (ret != 0 && errno != EEXIST) {
191 				NSLOG(netsurf, INFO,
192 				      "Failed to create directory \"%s\"",
193 				      directory);
194 				free(directory);
195 				return false;
196 			}
197 
198 			*start = '/';
199 		}
200 	}
201 
202 	NSLOG(netsurf, INFO, "Temporary directory location: %s", directory);
203 	ret = nsmkdir(directory, S_IRWXU);
204 
205 	free(directory);
206 
207 	if (ret != 0) {
208 		return false;
209 	}
210 	return true;
211 }
212 
213 
214 /**
215  * Deletes all files in the cache directory that are not accounted for.
216  */
filename_flush(void)217 void filename_flush(void)
218 {
219 	while (filename_flush_directory(TEMP_FILENAME_PREFIX, 0));
220 }
221 
222 
223 /**
224  * Deletes some files in a directory that are not accounted for.
225  *
226  * A single call to this function may not delete all the files in
227  * a directory. It should be called until it returns false.
228  *
229  * \param folder	the folder to search
230  * \param depth		the folder depth
231  * \returns whether further calls may be needed
232  */
filename_flush_directory(const char * folder,int depth)233 bool filename_flush_directory(const char *folder, int depth)
234 {
235 	DIR *parent;
236 	struct dirent *entry;
237 	bool changed = false;
238 	bool del;
239 	int number, i;
240 	int prefix = 0;
241 	unsigned int prefix_mask = (0x3f << 12);
242 	char child[256];
243 	const char *prefix_start = NULL;
244 	struct directory *dir = NULL;
245 
246 	/* Maximum permissible depth is 3 */
247 	assert(depth <= 3);
248 
249 	if (depth > 0) {
250 		/* Not a top-level directory, so determine the prefix
251 		 * by removing the last /XX component */
252 		prefix_start = folder + strlen(folder) - depth * 3 + 1;
253 	}
254 
255 	/* Calculate the numeric prefix */
256 	for (i = 0; i < depth; i++) {
257 		number = prefix_start[1] + prefix_start[0] * 10 - START_PREFIX;
258 		prefix |= (number << (12 - i * 6));
259 		prefix_mask |= (0x3f << (12 - i * 6));
260 		prefix_start += 3;
261 	}
262 
263 	/* If we're flushing a leaf directory, find it in the list */
264 	if (depth == 3) {
265 		for (dir = root; dir; dir = dir->next) {
266 			if (dir->numeric_prefix == prefix)
267 				break;
268 		}
269 
270 		if (dir == NULL)
271 			return false;
272 	}
273 
274 	parent = opendir(folder);
275 
276 	while ((entry = readdir(parent))) {
277 		int written;
278 		struct stat statbuf;
279 
280 		/* Ignore '.' and '..' */
281 		if (strcmp(entry->d_name, ".") == 0 ||
282 				strcmp(entry->d_name, "..") == 0)
283 			continue;
284 
285 		written = snprintf(child, sizeof(child), "%s/%s",
286 				folder, entry->d_name);
287 		if (written == sizeof(child)) {
288 			child[sizeof(child) - 1] = '\0';
289 		}
290 
291 		if (stat(child, &statbuf) == -1) {
292 			NSLOG(netsurf, INFO, "Unable to stat %s: %s", child,
293 			      strerror(errno));
294 			continue;
295 		}
296 
297 		/* first 3 depths are directories only, then files only */
298 		if (depth < 3) {
299 			/* Delete any unexpected files */
300 			del = !S_ISDIR(statbuf.st_mode);
301 		} else {
302 			/* Delete any unexpected directories */
303 		  	del = S_ISDIR(statbuf.st_mode);
304 		}
305 
306 		/* check we are a file numbered '00' -> '63' */
307 		if (del == false && (entry->d_name[0] >= '0') &&
308 				(entry->d_name[0] <= '6') &&
309 				(entry->d_name[1] >= '0') &&
310 				(entry->d_name[1] <= '9') &&
311 				(entry->d_name[2] == '\0')) {
312 			number = atoi(entry->d_name);
313 
314 			if (number >= 0 && number <= 63) {
315 				if (depth == 3) {
316 					/* File: delete if not in bitfield */
317 					if (number < 32)
318 						del = !(dir->low_used &
319 							(1 << number));
320 					else
321 						del = !(dir->high_used &
322 							(1 << (number - 32)));
323 				} else {
324 					/* Directory: delete unless in list */
325 					del = true;
326 
327 					/* Insert into numeric prefix */
328 					prefix &= ~(0x3f << (12 - depth * 6));
329 					prefix |= (number << (12 - depth * 6));
330 
331 					/* Find in dir list */
332 					for (dir = root; dir; dir = dir->next) {
333 						number = dir->numeric_prefix &
334 								prefix_mask;
335 						if (number == prefix) {
336 							/* In list: retain */
337 							del = false;
338 							break;
339 						}
340 					}
341 				}
342 			} else {
343 				/* Unexpected name: delete */
344 				del = true;
345 			}
346 		} else {
347 			/* Unexpected name: delete */
348 			del = true;
349 		}
350 
351 		/* continue if this is a file we want to retain */
352 		if (del == false && (!S_ISDIR(statbuf.st_mode)))
353 		    	continue;
354 
355 		/* delete or recurse */
356 		if (del) {
357 			if (S_ISDIR(statbuf.st_mode))
358 				filename_delete_recursive(child);
359 
360 			if (remove(child))
361 				NSLOG(netsurf, INFO, "Failed to remove '%s'",
362 				      child);
363 			else
364 				changed = true;
365 		} else {
366 			while (filename_flush_directory(child, depth + 1));
367 		}
368 	}
369 
370 	closedir(parent);
371 
372 	return changed;
373 }
374 
375 
376 /**
377  * Recursively deletes the contents of a directory
378  *
379  * \param folder the directory to delete
380  * \return true on success, false otherwise
381  */
filename_delete_recursive(char * folder)382 bool filename_delete_recursive(char *folder)
383 {
384 	DIR *parent;
385 	struct dirent *entry;
386 	char child[256];
387 	struct stat statbuf;
388 
389 	parent = opendir(folder);
390 
391 	while ((entry = readdir(parent))) {
392 		int written;
393 
394 		/* Ignore '.' and '..' */
395 		if (strcmp(entry->d_name, ".") == 0 ||
396 				strcmp(entry->d_name, "..") == 0)
397 			continue;
398 
399 		written = snprintf(child, sizeof(child), "%s/%s",
400 				folder, entry->d_name);
401 		if (written == sizeof(child)) {
402 			child[sizeof(child) - 1] = '\0';
403 		}
404 
405 		if (stat(child, &statbuf) == -1) {
406 			NSLOG(netsurf, INFO, "Unable to stat %s: %s", child,
407 			      strerror(errno));
408 			continue;
409 		}
410 
411 		if (S_ISDIR(statbuf.st_mode)) {
412 			if (!filename_delete_recursive(child)) {
413 				closedir(parent);
414 				return false;
415 			}
416 		}
417 
418 		if (remove(child)) {
419 			NSLOG(netsurf, INFO, "Failed to remove '%s'", child);
420 			closedir(parent);
421 			return false;
422 		}
423 	}
424 
425 	closedir(parent);
426 
427 	return true;
428 }
429 
430 
431 /**
432  * Creates a new directory.
433  *
434  * \param  prefix  the prefix to use, or NULL to allocate a new one
435  * \return a new directory structure, or NULL on memory exhaustion or
436  * creation failure
437  *
438  * Empty directories are never deleted, except by an explicit call to
439  * filename_flush().
440  */
filename_create_directory(const char * prefix)441 static struct directory *filename_create_directory(const char *prefix)
442 {
443 	char *last_1, *last_2;
444 	int index;
445 	struct directory *old_dir, *new_dir, *prev_dir = NULL;
446 	char dir_prefix[16];
447 	int i;
448 
449 	/* get the lowest unique prefix, or use the provided one */
450 	if (prefix == NULL) {
451 		for (index = 0, old_dir = root; old_dir;
452 				index++, old_dir = old_dir->next) {
453 			if (old_dir->numeric_prefix != index)
454 				break;
455 
456 			prev_dir = old_dir;
457 		}
458 
459 		sprintf(dir_prefix, "%.2i/%.2i/%.2i/",
460 				((index >> 12) & 63),
461 				((index >> 6) & 63),
462 				((index >> 0) & 63));
463 
464 		prefix = dir_prefix;
465 	} else {
466 		/* prefix format is always '01/23/45/' */
467 		index = ((prefix[7] + prefix[6] * 10 - START_PREFIX) |
468 			((prefix[4] + prefix[3] * 10 - START_PREFIX) << 6) |
469 			((prefix[1] + prefix[0] * 10 - START_PREFIX) << 12));
470 
471 		for (old_dir = root; old_dir; old_dir = old_dir->next) {
472 			if (old_dir->numeric_prefix == index)
473 				return old_dir;
474 
475 			else if (old_dir->numeric_prefix > index)
476 				break;
477 
478 			prev_dir = old_dir;
479 		}
480 	}
481 
482 	/* allocate a new directory */
483 	new_dir = malloc(sizeof(struct directory));
484 	if (new_dir == NULL) {
485 		NSLOG(netsurf, INFO, "No memory for malloc()");
486 		return NULL;
487 	}
488 
489 	strncpy(new_dir->prefix, prefix, 9);
490 	new_dir->prefix[9] = '\0';
491 	new_dir->low_used = new_dir->high_used = 0;
492 	new_dir->numeric_prefix = index;
493 
494 	if (prev_dir == NULL) {
495 		new_dir->next = root;
496 		root = new_dir;
497 	} else {
498 		new_dir->next = prev_dir->next;
499 		prev_dir->next = new_dir;
500 	}
501 
502 	/* if the previous directory has the same parent then we can simply
503 	 * create the child. */
504 	if (prev_dir && strncmp(prev_dir->prefix, new_dir->prefix, 6) == 0) {
505 		new_dir->prefix[8] = '\0';
506 		sprintf(filename_directory, "%s/%s",
507 				TEMP_FILENAME_PREFIX,
508 				new_dir->prefix);
509 		new_dir->prefix[8] = '/';
510 
511 		if (!is_dir(filename_directory)) {
512 			if (!nsmkdir(filename_directory, S_IRWXU))
513 				return new_dir;
514 
515 			/* the user has probably deleted the parent directory
516 			 * whilst we are running if there is an error, so we
517 			 * don't report this yet and try to create the
518 			 * structure normally. */
519 			NSLOG(netsurf, INFO,
520 			      "Failed to create optimised structure '%s'",
521 			      filename_directory);
522 		}
523 	}
524 
525 	/* create the directory structure */
526 	sprintf(filename_directory, "%s/", TEMP_FILENAME_PREFIX);
527 	last_1 = filename_directory + SLEN(TEMP_FILENAME_PREFIX) + 1;
528 	last_2 = new_dir->prefix;
529 
530 	/* create each subdirectory, up to the maximum depth of 3 */
531 	for (i = 0; i < 3 && *last_2; i++) {
532 		*last_1++ = *last_2++;
533 		while (*last_2 && *last_2 != '/')
534 			*last_1++ = *last_2++;
535 
536 		if (*last_2) {
537 			last_1[0] = '\0';
538 
539 			if (!is_dir(filename_directory)) {
540 				if (nsmkdir(filename_directory, S_IRWXU)) {
541 					NSLOG(netsurf, INFO,
542 					      "Failed to create directory '%s'",
543 					      filename_directory);
544 					return NULL;
545 				}
546 			}
547 		}
548 	}
549 
550 	return new_dir;
551 }
552