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