2 * cache.c : deal with LRU caches
4 * Copyright (c) 2008-2010 Jean-Pierre Andre
6 * This program/include file is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as published
8 * by the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program/include file is distributed in the hope that it will be
12 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program (in the main directory of the NTFS-3G
18 * distribution in the file COPYING); if not, write to the Free Software
19 * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
40 * General functions to deal with LRU caches
42 * The cached data have to be organized in a structure in which
43 * the first fields must follow a mandatory pattern and further
44 * fields may contain any fixed size data. They are stored in an
47 * A compare function must be provided for finding a wanted entry
48 * in the cache. Another function may be provided for invalidating
49 * an entry to facilitate multiple invalidation.
51 * These functions never return error codes. When there is a
52 * shortage of memory, data is simply not cached.
53 * When there is a hashing bug, hashing is dropped, and sequential
58 * Enter a new hash index, after a new record has been inserted
60 * Do not call when a record has been modified (with no key change)
63 static void inserthashindex(struct CACHE_HEADER
*cache
,
64 struct CACHED_GENERIC
*current
)
67 struct HASH_ENTRY
*link
;
68 struct HASH_ENTRY
*first
;
71 h
= cache
->dohash(current
);
72 if ((h
>= 0) && (h
< cache
->max_hash
)) {
73 /* get a free link and insert at top of hash list */
74 link
= cache
->free_hash
;
76 cache
->free_hash
= link
->next
;
77 first
= cache
->first_hash
[h
];
82 link
->entry
= current
;
83 cache
->first_hash
[h
] = link
;
85 ntfs_log_error("No more hash entries,"
86 " cache %s hashing dropped\n",
88 cache
->dohash
= (cache_hash
)NULL
;
91 ntfs_log_error("Illegal hash value,"
92 " cache %s hashing dropped\n",
94 cache
->dohash
= (cache_hash
)NULL
;
100 * Drop a hash index when a record is about to be deleted
103 static void drophashindex(struct CACHE_HEADER
*cache
,
104 const struct CACHED_GENERIC
*current
, int hash
)
106 struct HASH_ENTRY
*link
;
107 struct HASH_ENTRY
*previous
;
110 if ((hash
>= 0) && (hash
< cache
->max_hash
)) {
111 /* find the link and unlink */
112 link
= cache
->first_hash
[hash
];
113 previous
= (struct HASH_ENTRY
*)NULL
;
114 while (link
&& (link
->entry
!= current
)) {
120 previous
->next
= link
->next
;
122 cache
->first_hash
[hash
] = link
->next
;
123 link
->next
= cache
->free_hash
;
124 cache
->free_hash
= link
;
126 ntfs_log_error("Bad hash list,"
127 " cache %s hashing dropped\n",
129 cache
->dohash
= (cache_hash
)NULL
;
132 ntfs_log_error("Illegal hash value,"
133 " cache %s hashing dropped\n",
135 cache
->dohash
= (cache_hash
)NULL
;
141 * Fetch an entry from cache
143 * returns the cache entry, or NULL if not available
144 * The returned entry may be modified, but not freed
147 struct CACHED_GENERIC
*ntfs_fetch_cache(struct CACHE_HEADER
*cache
,
148 const struct CACHED_GENERIC
*wanted
, cache_compare compare
)
150 struct CACHED_GENERIC
*current
;
151 struct CACHED_GENERIC
*previous
;
152 struct HASH_ENTRY
*link
;
155 current
= (struct CACHED_GENERIC
*)NULL
;
159 * When possible, use the hash table to
160 * locate the entry if present
162 h
= cache
->dohash(wanted
);
163 link
= cache
->first_hash
[h
];
164 while (link
&& compare(link
->entry
, wanted
))
167 current
= link
->entry
;
169 if (!cache
->dohash
) {
171 * Search sequentially in LRU list if no hash table
172 * or if hashing has just failed
174 current
= cache
->most_recent_entry
;
176 && compare(current
, wanted
)) {
177 current
= current
->next
;
181 previous
= current
->previous
;
185 * found and not at head of list, unlink from current
186 * position and relink as head of list
188 previous
->next
= current
->next
;
190 current
->next
->previous
195 current
->next
= cache
->most_recent_entry
;
197 = (struct CACHED_GENERIC
*)NULL
;
198 cache
->most_recent_entry
->previous
= current
;
199 cache
->most_recent_entry
= current
;
208 * Enter an inode number into cache
209 * returns the cache entry or NULL if not possible
212 struct CACHED_GENERIC
*ntfs_enter_cache(struct CACHE_HEADER
*cache
,
213 const struct CACHED_GENERIC
*item
,
214 cache_compare compare
)
216 struct CACHED_GENERIC
*current
;
217 struct CACHED_GENERIC
*before
;
218 struct HASH_ENTRY
*link
;
221 current
= (struct CACHED_GENERIC
*)NULL
;
225 * When possible, use the hash table to
226 * find out whether the entry if present
228 h
= cache
->dohash(item
);
229 link
= cache
->first_hash
[h
];
230 while (link
&& compare(link
->entry
, item
))
233 current
= link
->entry
;
236 if (!cache
->dohash
) {
238 * Search sequentially in LRU list to locate the end,
239 * and find out whether the entry is already in list
240 * As we normally go to the end, no statistics is
243 current
= cache
->most_recent_entry
;
245 && compare(current
, item
)) {
246 current
= current
->next
;
252 * Not in list, get a free entry or reuse the
253 * last entry, and relink as head of list
254 * Note : we assume at least three entries, so
255 * before, previous and first are different when
256 * an entry is reused.
259 if (cache
->free_entry
) {
260 current
= cache
->free_entry
;
261 cache
->free_entry
= cache
->free_entry
->next
;
263 current
->variable
= ntfs_malloc(
266 current
->variable
= (void*)NULL
;
267 current
->varsize
= item
->varsize
;
268 if (!cache
->oldest_entry
)
269 cache
->oldest_entry
= current
;
271 /* reusing the oldest entry */
272 current
= cache
->oldest_entry
;
273 before
= current
->previous
;
274 before
->next
= (struct CACHED_GENERIC
*)NULL
;
276 drophashindex(cache
,current
,
277 cache
->dohash(current
));
279 cache
->dofree(current
);
280 cache
->oldest_entry
= current
->previous
;
282 if (current
->varsize
)
283 current
->variable
= realloc(
287 current
->variable
= ntfs_malloc(
290 if (current
->varsize
)
291 free(current
->variable
);
292 current
->variable
= (void*)NULL
;
294 current
->varsize
= item
->varsize
;
296 current
->next
= cache
->most_recent_entry
;
297 current
->previous
= (struct CACHED_GENERIC
*)NULL
;
298 if (cache
->most_recent_entry
)
299 cache
->most_recent_entry
->previous
= current
;
300 cache
->most_recent_entry
= current
;
301 memcpy(current
->payload
, item
->payload
, cache
->fixed_size
);
303 if (current
->variable
) {
304 memcpy(current
->variable
,
305 item
->variable
, item
->varsize
);
308 * no more memory for variable part
309 * recycle entry in free list
310 * not an error, just uncacheable
312 cache
->most_recent_entry
= current
->next
;
313 current
->next
= cache
->free_entry
;
314 cache
->free_entry
= current
;
315 current
= (struct CACHED_GENERIC
*)NULL
;
318 current
->variable
= (void*)NULL
;
319 current
->varsize
= 0;
321 if (cache
->dohash
&& current
)
322 inserthashindex(cache
,current
);
330 * Invalidate a cache entry
331 * The entry is moved to the free entry list
332 * A specific function may be called for entry deletion
335 static void do_invalidate(struct CACHE_HEADER
*cache
,
336 struct CACHED_GENERIC
*current
, int flags
)
338 struct CACHED_GENERIC
*previous
;
340 previous
= current
->previous
;
341 if ((flags
& CACHE_FREE
) && cache
->dofree
)
342 cache
->dofree(current
);
344 * Relink into free list
347 current
->next
->previous
= current
->previous
;
349 cache
->oldest_entry
= current
->previous
;
351 previous
->next
= current
->next
;
353 cache
->most_recent_entry
= current
->next
;
354 current
->next
= cache
->free_entry
;
355 cache
->free_entry
= current
;
356 if (current
->variable
)
357 free(current
->variable
);
358 current
->varsize
= 0;
363 * Invalidate entries in cache
365 * Several entries may have to be invalidated (at least for inodes
366 * associated to directories which have been renamed), a different
367 * compare function may be provided to select entries to invalidate
369 * Returns the number of deleted entries, this can be used by
370 * the caller to signal a cache corruption if the entry was
371 * supposed to be found.
374 int ntfs_invalidate_cache(struct CACHE_HEADER
*cache
,
375 const struct CACHED_GENERIC
*item
, cache_compare compare
,
378 struct CACHED_GENERIC
*current
;
379 struct CACHED_GENERIC
*next
;
380 struct HASH_ENTRY
*link
;
384 current
= (struct CACHED_GENERIC
*)NULL
;
387 if (!(flags
& CACHE_NOHASH
) && cache
->dohash
) {
389 * When possible, use the hash table to
390 * find out whether the entry if present
392 h
= cache
->dohash(item
);
393 link
= cache
->first_hash
[h
];
395 if (compare(link
->entry
, item
))
398 current
= link
->entry
;
401 drophashindex(cache
,current
,h
);
409 if ((flags
& CACHE_NOHASH
) || !cache
->dohash
) {
411 * Search sequentially in LRU list
413 current
= cache
->most_recent_entry
;
415 if (!compare(current
, item
)) {
416 next
= current
->next
;
418 drophashindex(cache
,current
,
419 cache
->dohash(current
));
420 do_invalidate(cache
,current
,flags
);
424 current
= current
->next
;
432 int ntfs_remove_cache(struct CACHE_HEADER
*cache
,
433 struct CACHED_GENERIC
*item
, int flags
)
440 drophashindex(cache
,item
,cache
->dohash(item
));
441 do_invalidate(cache
,item
,flags
);
448 * Free memory allocated to a cache
451 static void ntfs_free_cache(struct CACHE_HEADER
*cache
)
453 struct CACHED_GENERIC
*entry
;
456 for (entry
=cache
->most_recent_entry
; entry
; entry
=entry
->next
) {
458 cache
->dofree(entry
);
460 free(entry
->variable
);
469 * Returns the cache header, or NULL if the cache could not be created
472 static struct CACHE_HEADER
*ntfs_create_cache(const char *name
,
473 cache_free dofree
, cache_hash dohash
,
475 int item_count
, int max_hash
)
477 struct CACHE_HEADER
*cache
;
478 struct CACHED_GENERIC
*pc
;
479 struct CACHED_GENERIC
*qc
;
480 struct HASH_ENTRY
*ph
;
481 struct HASH_ENTRY
*qh
;
482 struct HASH_ENTRY
**px
;
486 size
= sizeof(struct CACHE_HEADER
) + item_count
*full_item_size
;
488 size
+= item_count
*sizeof(struct HASH_ENTRY
)
489 + max_hash
*sizeof(struct HASH_ENTRY
*);
490 cache
= (struct CACHE_HEADER
*)ntfs_malloc(size
);
494 cache
->dofree
= dofree
;
495 if (dohash
&& max_hash
) {
496 cache
->dohash
= dohash
;
497 cache
->max_hash
= max_hash
;
499 cache
->dohash
= (cache_hash
)NULL
;
502 cache
->fixed_size
= full_item_size
- sizeof(struct CACHED_GENERIC
);
506 /* chain the data entries, and mark an invalid entry */
507 cache
->most_recent_entry
= (struct CACHED_GENERIC
*)NULL
;
508 cache
->oldest_entry
= (struct CACHED_GENERIC
*)NULL
;
509 cache
->free_entry
= &cache
->entry
[0];
510 pc
= &cache
->entry
[0];
511 for (i
=0; i
<(item_count
- 1); i
++) {
512 qc
= (struct CACHED_GENERIC
*)((char*)pc
515 pc
->variable
= (void*)NULL
;
519 /* special for the last entry */
520 pc
->next
= (struct CACHED_GENERIC
*)NULL
;
521 pc
->variable
= (void*)NULL
;
525 /* chain the hash entries */
526 ph
= (struct HASH_ENTRY
*)(((char*)pc
) + full_item_size
);
527 cache
->free_hash
= ph
;
528 for (i
=0; i
<(item_count
- 1); i
++) {
533 /* special for the last entry */
535 ph
->next
= (struct HASH_ENTRY
*)NULL
;
537 /* create and initialize the hash indexes */
538 px
= (struct HASH_ENTRY
**)&ph
[1];
539 cache
->first_hash
= px
;
540 for (i
=0; i
<max_hash
; i
++)
541 px
[i
] = (struct HASH_ENTRY
*)NULL
;
543 cache
->free_hash
= (struct HASH_ENTRY
*)NULL
;
544 cache
->first_hash
= (struct HASH_ENTRY
**)NULL
;
551 * Create all LRU caches
553 * No error return, if creation is not possible, cacheing will
554 * just be not available
557 void ntfs_create_lru_caches(ntfs_volume
*vol
)
561 vol
->xinode_cache
= ntfs_create_cache("inode",(cache_free
)NULL
,
562 ntfs_dir_inode_hash
, sizeof(struct CACHED_INODE
),
563 CACHE_INODE_SIZE
, 2*CACHE_INODE_SIZE
);
565 #if CACHE_NIDATA_SIZE
567 vol
->nidata_cache
= ntfs_create_cache("nidata",
568 ntfs_inode_nidata_free
, ntfs_inode_nidata_hash
,
569 sizeof(struct CACHED_NIDATA
),
570 CACHE_NIDATA_SIZE
, 2*CACHE_NIDATA_SIZE
);
572 #if CACHE_LOOKUP_SIZE
574 vol
->lookup_cache
= ntfs_create_cache("lookup",
575 (cache_free
)NULL
, ntfs_dir_lookup_hash
,
576 sizeof(struct CACHED_LOOKUP
),
577 CACHE_LOOKUP_SIZE
, 2*CACHE_LOOKUP_SIZE
);
579 vol
->securid_cache
= ntfs_create_cache("securid",(cache_free
)NULL
,
580 (cache_hash
)NULL
,sizeof(struct CACHED_SECURID
), CACHE_SECURID_SIZE
, 0);
581 #if CACHE_LEGACY_SIZE
582 vol
->legacy_cache
= ntfs_create_cache("legacy",(cache_free
)NULL
,
583 (cache_hash
)NULL
, sizeof(struct CACHED_PERMISSIONS_LEGACY
), CACHE_LEGACY_SIZE
, 0);
588 * Free all LRU caches
591 void ntfs_free_lru_caches(ntfs_volume
*vol
)
594 ntfs_free_cache(vol
->xinode_cache
);
596 #if CACHE_NIDATA_SIZE
597 ntfs_free_cache(vol
->nidata_cache
);
599 #if CACHE_LOOKUP_SIZE
600 ntfs_free_cache(vol
->lookup_cache
);
602 ntfs_free_cache(vol
->securid_cache
);
603 #if CACHE_LEGACY_SIZE
604 ntfs_free_cache(vol
->legacy_cache
);