source: mainline/uspace/srv/fs/exfat/exfat_idx.c@ 5fd05862

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 5fd05862 was 5e801dc, checked in by GitHub <noreply@…>, 6 years ago

Indicate and enforce constness of hash table key in certain functions (#158)

The assumption here is that modifying key in the hash/equal functions in something completely unexpected, and not something you would ever want to do intentionally, so it makes sense to disallow it entirely to get that extra level of checking.

  • Property mode set to 100644
File size: 13.4 KB
Line 
1/*
2 * Copyright (c) 2008 Jakub Jermar
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup exfat
30 * @{
31 */
32
33/**
34 * @file exfat_idx.c
35 * @brief Layer for translating exFAT entities to VFS node indices.
36 */
37
38#include "exfat.h"
39#include "../../vfs/vfs.h"
40#include <errno.h>
41#include <str.h>
42#include <adt/hash_table.h>
43#include <adt/hash.h>
44#include <adt/list.h>
45#include <assert.h>
46#include <fibril_synch.h>
47#include <stdlib.h>
48
49/** Each instance of this type describes one interval of freed VFS indices. */
50typedef struct {
51 link_t link;
52 fs_index_t first;
53 fs_index_t last;
54} freed_t;
55
56/**
57 * Each instance of this type describes state of all VFS indices that
58 * are currently unused.
59 */
60typedef struct {
61 link_t link;
62 service_id_t service_id;
63
64 /** Next unassigned index. */
65 fs_index_t next;
66 /** Number of remaining unassigned indices. */
67 uint64_t remaining;
68
69 /** Sorted list of intervals of freed indices. */
70 list_t freed_list;
71} unused_t;
72
73/** Mutex protecting the list of unused structures. */
74static FIBRIL_MUTEX_INITIALIZE(unused_lock);
75
76/** List of unused structures. */
77static LIST_INITIALIZE(unused_list);
78
79static void unused_initialize(unused_t *u, service_id_t service_id)
80{
81 link_initialize(&u->link);
82 u->service_id = service_id;
83 u->next = 0;
84 u->remaining = ((uint64_t)((fs_index_t)-1)) + 1;
85 list_initialize(&u->freed_list);
86}
87
88static unused_t *unused_find(service_id_t service_id, bool lock)
89{
90 if (lock)
91 fibril_mutex_lock(&unused_lock);
92
93 list_foreach(unused_list, link, unused_t, u) {
94 if (u->service_id == service_id)
95 return u;
96 }
97
98 if (lock)
99 fibril_mutex_unlock(&unused_lock);
100 return NULL;
101}
102
103/** Mutex protecting the up_hash and ui_hash. */
104static FIBRIL_MUTEX_INITIALIZE(used_lock);
105
106/**
107 * Global hash table of all used exfat_idx_t structures.
108 * The index structures are hashed by the service_id, parent node's first
109 * cluster and index within the parent directory.
110 */
111static hash_table_t up_hash;
112
113typedef struct {
114 service_id_t service_id;
115 exfat_cluster_t pfc;
116 unsigned pdi;
117} pos_key_t;
118
119static inline size_t pos_key_hash(const void *key)
120{
121 const pos_key_t *pos = key;
122
123 size_t hash = 0;
124 hash = hash_combine(pos->pfc, pos->pdi);
125 return hash_combine(hash, pos->service_id);
126}
127
128static size_t pos_hash(const ht_link_t *item)
129{
130 exfat_idx_t *fidx = hash_table_get_inst(item, exfat_idx_t, uph_link);
131
132 pos_key_t pkey = {
133 .service_id = fidx->service_id,
134 .pfc = fidx->pfc,
135 .pdi = fidx->pdi,
136 };
137
138 return pos_key_hash(&pkey);
139}
140
141static bool pos_key_equal(const void *key, const ht_link_t *item)
142{
143 const pos_key_t *pos = key;
144 exfat_idx_t *fidx = hash_table_get_inst(item, exfat_idx_t, uph_link);
145
146 return pos->service_id == fidx->service_id &&
147 pos->pdi == fidx->pdi &&
148 pos->pfc == fidx->pfc;
149}
150
151static hash_table_ops_t uph_ops = {
152 .hash = pos_hash,
153 .key_hash = pos_key_hash,
154 .key_equal = pos_key_equal,
155 .equal = NULL,
156 .remove_callback = NULL,
157};
158
159/**
160 * Global hash table of all used fat_idx_t structures.
161 * The index structures are hashed by the service_id and index.
162 */
163static hash_table_t ui_hash;
164
165typedef struct {
166 service_id_t service_id;
167 fs_index_t index;
168} idx_key_t;
169
170static size_t idx_key_hash(const void *key_arg)
171{
172 const idx_key_t *key = key_arg;
173 return hash_combine(key->service_id, key->index);
174}
175
176static size_t idx_hash(const ht_link_t *item)
177{
178 exfat_idx_t *fidx = hash_table_get_inst(item, exfat_idx_t, uih_link);
179 return hash_combine(fidx->service_id, fidx->index);
180}
181
182static bool idx_key_equal(const void *key_arg, const ht_link_t *item)
183{
184 exfat_idx_t *fidx = hash_table_get_inst(item, exfat_idx_t, uih_link);
185 const idx_key_t *key = key_arg;
186
187 return key->index == fidx->index && key->service_id == fidx->service_id;
188}
189
190static void idx_remove_callback(ht_link_t *item)
191{
192 exfat_idx_t *fidx = hash_table_get_inst(item, exfat_idx_t, uih_link);
193
194 free(fidx);
195}
196
197static hash_table_ops_t uih_ops = {
198 .hash = idx_hash,
199 .key_hash = idx_key_hash,
200 .key_equal = idx_key_equal,
201 .equal = NULL,
202 .remove_callback = idx_remove_callback,
203};
204
205/** Allocate a VFS index which is not currently in use. */
206static bool exfat_index_alloc(service_id_t service_id, fs_index_t *index)
207{
208 unused_t *u;
209
210 assert(index);
211 u = unused_find(service_id, true);
212 if (!u)
213 return false;
214
215 if (list_empty(&u->freed_list)) {
216 if (u->remaining) {
217 /*
218 * There are no freed indices, allocate one directly
219 * from the counter.
220 */
221 *index = u->next++;
222 --u->remaining;
223 fibril_mutex_unlock(&unused_lock);
224 return true;
225 }
226 } else {
227 /* There are some freed indices which we can reuse. */
228 freed_t *f = list_get_instance(list_first(&u->freed_list),
229 freed_t, link);
230 *index = f->first;
231 if (f->first++ == f->last) {
232 /* Destroy the interval. */
233 list_remove(&f->link);
234 free(f);
235 }
236 fibril_mutex_unlock(&unused_lock);
237 return true;
238 }
239 /*
240 * We ran out of indices, which is extremely unlikely with FAT16, but
241 * theoretically still possible (e.g. too many open unlinked nodes or
242 * too many zero-sized nodes).
243 */
244 fibril_mutex_unlock(&unused_lock);
245 return false;
246}
247
248/** If possible, coalesce two intervals of freed indices. */
249static void try_coalesce_intervals(link_t *l, link_t *r, link_t *cur)
250{
251 freed_t *fl = list_get_instance(l, freed_t, link);
252 freed_t *fr = list_get_instance(r, freed_t, link);
253
254 if (fl->last + 1 == fr->first) {
255 if (cur == l) {
256 fl->last = fr->last;
257 list_remove(r);
258 free(r);
259 } else {
260 fr->first = fl->first;
261 list_remove(l);
262 free(l);
263 }
264 }
265}
266
267/** Free a VFS index, which is no longer in use. */
268static void exfat_index_free(service_id_t service_id, fs_index_t index)
269{
270 unused_t *u;
271
272 u = unused_find(service_id, true);
273 assert(u);
274
275 if (u->next == index + 1) {
276 /* The index can be returned directly to the counter. */
277 u->next--;
278 u->remaining++;
279 } else {
280 /*
281 * The index must be returned either to an existing freed
282 * interval or a new interval must be created.
283 */
284 link_t *lnk;
285 freed_t *n;
286 for (lnk = u->freed_list.head.next; lnk != &u->freed_list.head;
287 lnk = lnk->next) {
288 freed_t *f = list_get_instance(lnk, freed_t, link);
289 if (f->first == index + 1) {
290 f->first--;
291 if (lnk->prev != &u->freed_list.head)
292 try_coalesce_intervals(lnk->prev, lnk,
293 lnk);
294 fibril_mutex_unlock(&unused_lock);
295 return;
296 }
297 if (f->last == index - 1) {
298 f->last++;
299 if (lnk->next != &u->freed_list.head)
300 try_coalesce_intervals(lnk, lnk->next,
301 lnk);
302 fibril_mutex_unlock(&unused_lock);
303 return;
304 }
305 if (index > f->first) {
306 n = malloc(sizeof(freed_t));
307 /* TODO: sleep until allocation succeeds */
308 assert(n);
309 link_initialize(&n->link);
310 n->first = index;
311 n->last = index;
312 list_insert_before(&n->link, lnk);
313 fibril_mutex_unlock(&unused_lock);
314 return;
315 }
316
317 }
318 /* The index will form the last interval. */
319 n = malloc(sizeof(freed_t));
320 /* TODO: sleep until allocation succeeds */
321 assert(n);
322 link_initialize(&n->link);
323 n->first = index;
324 n->last = index;
325 list_append(&n->link, &u->freed_list);
326 }
327 fibril_mutex_unlock(&unused_lock);
328}
329
330static errno_t exfat_idx_create(exfat_idx_t **fidxp, service_id_t service_id)
331{
332 exfat_idx_t *fidx;
333
334 fidx = (exfat_idx_t *) malloc(sizeof(exfat_idx_t));
335 if (!fidx)
336 return ENOMEM;
337 if (!exfat_index_alloc(service_id, &fidx->index)) {
338 free(fidx);
339 return ENOSPC;
340 }
341
342 fibril_mutex_initialize(&fidx->lock);
343 fidx->service_id = service_id;
344 fidx->pfc = 0; /* no parent yet */
345 fidx->pdi = 0;
346 fidx->nodep = NULL;
347
348 *fidxp = fidx;
349 return EOK;
350}
351
352errno_t exfat_idx_get_new(exfat_idx_t **fidxp, service_id_t service_id)
353{
354 exfat_idx_t *fidx;
355 errno_t rc;
356
357 fibril_mutex_lock(&used_lock);
358 rc = exfat_idx_create(&fidx, service_id);
359 if (rc != EOK) {
360 fibril_mutex_unlock(&used_lock);
361 return rc;
362 }
363
364 hash_table_insert(&ui_hash, &fidx->uih_link);
365 fibril_mutex_lock(&fidx->lock);
366 fibril_mutex_unlock(&used_lock);
367
368 *fidxp = fidx;
369 return EOK;
370}
371
372exfat_idx_t *
373exfat_idx_get_by_pos(service_id_t service_id, exfat_cluster_t pfc, unsigned pdi)
374{
375 exfat_idx_t *fidx;
376
377 pos_key_t pos_key = {
378 .service_id = service_id,
379 .pfc = pfc,
380 .pdi = pdi,
381 };
382
383 fibril_mutex_lock(&used_lock);
384 ht_link_t *l = hash_table_find(&up_hash, &pos_key);
385 if (l) {
386 fidx = hash_table_get_inst(l, exfat_idx_t, uph_link);
387 } else {
388 errno_t rc;
389
390 rc = exfat_idx_create(&fidx, service_id);
391 if (rc != EOK) {
392 fibril_mutex_unlock(&used_lock);
393 return NULL;
394 }
395
396 fidx->pfc = pfc;
397 fidx->pdi = pdi;
398
399 hash_table_insert(&up_hash, &fidx->uph_link);
400 hash_table_insert(&ui_hash, &fidx->uih_link);
401 }
402 fibril_mutex_lock(&fidx->lock);
403 fibril_mutex_unlock(&used_lock);
404
405 return fidx;
406}
407
408void exfat_idx_hashin(exfat_idx_t *idx)
409{
410 fibril_mutex_lock(&used_lock);
411 hash_table_insert(&up_hash, &idx->uph_link);
412 fibril_mutex_unlock(&used_lock);
413}
414
415void exfat_idx_hashout(exfat_idx_t *idx)
416{
417 fibril_mutex_lock(&used_lock);
418 hash_table_remove_item(&up_hash, &idx->uph_link);
419 fibril_mutex_unlock(&used_lock);
420}
421
422exfat_idx_t *
423exfat_idx_get_by_index(service_id_t service_id, fs_index_t index)
424{
425 exfat_idx_t *fidx = NULL;
426
427 idx_key_t idx_key = {
428 .service_id = service_id,
429 .index = index,
430 };
431
432 fibril_mutex_lock(&used_lock);
433 ht_link_t *l = hash_table_find(&ui_hash, &idx_key);
434 if (l) {
435 fidx = hash_table_get_inst(l, exfat_idx_t, uih_link);
436 fibril_mutex_lock(&fidx->lock);
437 }
438 fibril_mutex_unlock(&used_lock);
439
440 return fidx;
441}
442
443/** Destroy the index structure.
444 *
445 * @param idx The index structure to be destroyed.
446 */
447void exfat_idx_destroy(exfat_idx_t *idx)
448{
449 idx_key_t idx_key = {
450 .service_id = idx->service_id,
451 .index = idx->index,
452 };
453
454 /* TODO: assert(idx->pfc == FAT_CLST_RES0); */
455 assert(idx->pfc == 0);
456
457 fibril_mutex_lock(&used_lock);
458 /*
459 * Since we can only free unlinked nodes, the index structure is not
460 * present in the position hash (uph). We therefore hash it out from
461 * the index hash only.
462 */
463 hash_table_remove(&ui_hash, &idx_key);
464 fibril_mutex_unlock(&used_lock);
465 /* Release the VFS index. */
466 exfat_index_free(idx_key.service_id, idx_key.index);
467 /* The index structure itself is freed in idx_remove_callback(). */
468}
469
470errno_t exfat_idx_init(void)
471{
472 if (!hash_table_create(&up_hash, 0, 0, &uph_ops))
473 return ENOMEM;
474 if (!hash_table_create(&ui_hash, 0, 0, &uih_ops)) {
475 hash_table_destroy(&up_hash);
476 return ENOMEM;
477 }
478 return EOK;
479}
480
481void exfat_idx_fini(void)
482{
483 /* We assume the hash tables are empty. */
484 assert(hash_table_empty(&up_hash) && hash_table_empty(&ui_hash));
485 hash_table_destroy(&up_hash);
486 hash_table_destroy(&ui_hash);
487}
488
489errno_t exfat_idx_init_by_service_id(service_id_t service_id)
490{
491 unused_t *u;
492 errno_t rc = EOK;
493
494 u = (unused_t *) malloc(sizeof(unused_t));
495 if (!u)
496 return ENOMEM;
497 unused_initialize(u, service_id);
498 fibril_mutex_lock(&unused_lock);
499 if (!unused_find(service_id, false)) {
500 list_append(&u->link, &unused_list);
501 } else {
502 free(u);
503 rc = EEXIST;
504 }
505 fibril_mutex_unlock(&unused_lock);
506 return rc;
507}
508
509static bool rm_pos_service_id(ht_link_t *item, void *arg)
510{
511 service_id_t service_id = *(service_id_t *)arg;
512 exfat_idx_t *fidx = hash_table_get_inst(item, exfat_idx_t, uph_link);
513
514 if (fidx->service_id == service_id) {
515 hash_table_remove_item(&up_hash, item);
516 }
517
518 return true;
519}
520
521static bool rm_idx_service_id(ht_link_t *item, void *arg)
522{
523 service_id_t service_id = *(service_id_t *)arg;
524 exfat_idx_t *fidx = hash_table_get_inst(item, exfat_idx_t, uih_link);
525
526 if (fidx->service_id == service_id) {
527 hash_table_remove_item(&ui_hash, item);
528 }
529
530 return true;
531}
532
533void exfat_idx_fini_by_service_id(service_id_t service_id)
534{
535 /*
536 * Remove this instance's index structure from up_hash and ui_hash.
537 * Process up_hash first and ui_hash second because the index structure
538 * is actually removed in idx_remove_callback().
539 */
540 fibril_mutex_lock(&used_lock);
541 hash_table_apply(&up_hash, rm_pos_service_id, &service_id);
542 hash_table_apply(&ui_hash, rm_idx_service_id, &service_id);
543 fibril_mutex_unlock(&used_lock);
544
545 /*
546 * Free the unused and freed structures for this instance.
547 */
548 unused_t *u = unused_find(service_id, true);
549 assert(u);
550 list_remove(&u->link);
551 fibril_mutex_unlock(&unused_lock);
552
553 while (!list_empty(&u->freed_list)) {
554 freed_t *f;
555 f = list_get_instance(list_first(&u->freed_list), freed_t, link);
556 list_remove(&f->link);
557 free(f);
558 }
559 free(u);
560}
561
562/**
563 * @}
564 */
Note: See TracBrowser for help on using the repository browser.