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