1 /* $NetBSD: kern_fileassoc.c,v 1.38 2023/12/28 12:49:06 hannken Exp $ */
2 
3 /*-
4  * Copyright (c) 2006 Elad Efrat <elad@NetBSD.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. The name of the author may not be used to endorse or promote products
16  *    derived from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <sys/cdefs.h>
31 __KERNEL_RCSID(0, "$NetBSD: kern_fileassoc.c,v 1.38 2023/12/28 12:49:06 hannken Exp $");
32 
33 #include "opt_fileassoc.h"
34 
35 #include <sys/param.h>
36 #include <sys/mount.h>
37 #include <sys/queue.h>
38 #include <sys/vnode.h>
39 #include <sys/errno.h>
40 #include <sys/fileassoc.h>
41 #include <sys/specificdata.h>
42 #include <sys/hash.h>
43 #include <sys/kmem.h>
44 #include <sys/once.h>
45 #include <sys/mutex.h>
46 #include <sys/xcall.h>
47 
48 #define   FILEASSOC_INITIAL_TABLESIZE   128
49 
50 static specificdata_domain_t fileassoc_domain = NULL;
51 static specificdata_key_t fileassoc_mountspecific_key;
52 static ONCE_DECL(control);
53 
54 /*
55  * Assoc entry.
56  * Includes the assoc name for identification and private clear callback.
57  */
58 struct fileassoc {
59           LIST_ENTRY(fileassoc) assoc_list;
60           const char *assoc_name;                                     /* Name. */
61           fileassoc_cleanup_cb_t assoc_cleanup_cb;          /* Clear callback. */
62           specificdata_key_t assoc_key;
63 };
64 
65 static LIST_HEAD(, fileassoc) fileassoc_list;
66 
67 /* An entry in the per-mount hash table. */
68 struct fileassoc_file {
69           fhandle_t *faf_handle;                                      /* File handle */
70           specificdata_reference faf_data;                  /* Assoc data. */
71           u_int faf_nassocs;                                /* # of assocs. */
72           LIST_ENTRY(fileassoc_file) faf_list;              /* List pointer. */
73 };
74 
75 LIST_HEAD(fileassoc_hash_entry, fileassoc_file);
76 
77 struct fileassoc_table {
78           struct fileassoc_hash_entry *tbl_hash;
79           u_long tbl_mask;                                  /* Hash table mask. */
80           size_t tbl_nslots;                                /* Number of slots. */
81           size_t tbl_nused;                                 /* # of used slots. */
82           specificdata_reference tbl_data;
83 };
84 
85 /*
86  * Hashing function: Takes a number modulus the mask to give back an
87  * index into the hash table.
88  */
89 #define FILEASSOC_HASH(tbl, handle)     \
90           (hash32_buf((handle), FHANDLE_SIZE(handle), HASH32_BUF_INIT) \
91            & ((tbl)->tbl_mask))
92 
93 /*
94  * Global usage counting.  This is bad for parallelism of updates, but
95  * good for avoiding calls to fileassoc when it's not in use.  Unclear
96  * if parallelism of updates matters much.  If you want to improve
97  * fileassoc(9) update performance, feel free to rip this out as long
98  * as you don't cause the fast paths to take any global locks or incur
99  * memory barriers when fileassoc(9) is not in use.
100  */
101 static struct {
102           kmutex_t lock;
103           uint64_t nassocs;
104           volatile bool inuse;
105 } fileassoc_global __cacheline_aligned;
106 
107 static void
fileassoc_incuse(void)108 fileassoc_incuse(void)
109 {
110 
111           mutex_enter(&fileassoc_global.lock);
112           if (fileassoc_global.nassocs++ == 0) {
113                     KASSERT(!fileassoc_global.inuse);
114                     atomic_store_relaxed(&fileassoc_global.inuse, true);
115                     xc_barrier(0);
116           }
117           mutex_exit(&fileassoc_global.lock);
118 }
119 
120 static void
fileassoc_decuse(void)121 fileassoc_decuse(void)
122 {
123 
124           mutex_enter(&fileassoc_global.lock);
125           KASSERT(fileassoc_global.nassocs > 0);
126           KASSERT(fileassoc_global.inuse);
127           if (--fileassoc_global.nassocs == 0)
128                     atomic_store_relaxed(&fileassoc_global.inuse, false);
129           mutex_exit(&fileassoc_global.lock);
130 }
131 
132 static bool
fileassoc_inuse(void)133 fileassoc_inuse(void)
134 {
135 
136           return __predict_false(atomic_load_relaxed(&fileassoc_global.inuse));
137 }
138 
139 static void *
file_getdata(struct fileassoc_file * faf,const struct fileassoc * assoc)140 file_getdata(struct fileassoc_file *faf, const struct fileassoc *assoc)
141 {
142 
143           return specificdata_getspecific(fileassoc_domain, &faf->faf_data,
144               assoc->assoc_key);
145 }
146 
147 static void
file_setdata(struct fileassoc_file * faf,const struct fileassoc * assoc,void * data)148 file_setdata(struct fileassoc_file *faf, const struct fileassoc *assoc,
149     void *data)
150 {
151 
152           specificdata_setspecific(fileassoc_domain, &faf->faf_data,
153               assoc->assoc_key, data);
154 }
155 
156 static void
file_cleanup(struct fileassoc_file * faf,const struct fileassoc * assoc)157 file_cleanup(struct fileassoc_file *faf, const struct fileassoc *assoc)
158 {
159           fileassoc_cleanup_cb_t cb;
160           void *data;
161 
162           cb = assoc->assoc_cleanup_cb;
163           if (cb == NULL) {
164                     return;
165           }
166           data = file_getdata(faf, assoc);
167           (*cb)(data);
168 }
169 
170 static void
file_free(struct fileassoc_file * faf)171 file_free(struct fileassoc_file *faf)
172 {
173           struct fileassoc *assoc;
174 
175           LIST_REMOVE(faf, faf_list);
176 
177           LIST_FOREACH(assoc, &fileassoc_list, assoc_list) {
178                     file_cleanup(faf, assoc);
179                     fileassoc_decuse();
180           }
181           vfs_composefh_free(faf->faf_handle);
182           specificdata_fini(fileassoc_domain, &faf->faf_data);
183           kmem_free(faf, sizeof(*faf));
184 }
185 
186 static void
table_dtor(void * v)187 table_dtor(void *v)
188 {
189           struct fileassoc_table *tbl = v;
190           u_long i;
191 
192           /* Remove all entries from the table and lists */
193           for (i = 0; i < tbl->tbl_nslots; i++) {
194                     struct fileassoc_file *faf;
195 
196                     while ((faf = LIST_FIRST(&tbl->tbl_hash[i])) != NULL) {
197                               file_free(faf);
198                     }
199           }
200 
201           /* Remove hash table and sysctl node */
202           hashdone(tbl->tbl_hash, HASH_LIST, tbl->tbl_mask);
203           specificdata_fini(fileassoc_domain, &tbl->tbl_data);
204           kmem_free(tbl, sizeof(*tbl));
205 }
206 
207 /*
208  * Initialize the fileassoc subsystem.
209  */
210 static int
fileassoc_init(void)211 fileassoc_init(void)
212 {
213           int error;
214 
215           error = mount_specific_key_create(&fileassoc_mountspecific_key,
216               table_dtor);
217           if (error) {
218                     return error;
219           }
220           fileassoc_domain = specificdata_domain_create();
221 
222           mutex_init(&fileassoc_global.lock, MUTEX_DEFAULT, IPL_NONE);
223 
224           return 0;
225 }
226 
227 /*
228  * Register a new assoc.
229  */
230 int
fileassoc_register(const char * name,fileassoc_cleanup_cb_t cleanup_cb,fileassoc_t * result)231 fileassoc_register(const char *name, fileassoc_cleanup_cb_t cleanup_cb,
232     fileassoc_t *result)
233 {
234           int error;
235           specificdata_key_t key;
236           struct fileassoc *assoc;
237 
238           error = RUN_ONCE(&control, fileassoc_init);
239           if (error) {
240                     return error;
241           }
242           error = specificdata_key_create(fileassoc_domain, &key, NULL);
243           if (error) {
244                     return error;
245           }
246           assoc = kmem_alloc(sizeof(*assoc), KM_SLEEP);
247           assoc->assoc_name = name;
248           assoc->assoc_cleanup_cb = cleanup_cb;
249           assoc->assoc_key = key;
250 
251           LIST_INSERT_HEAD(&fileassoc_list, assoc, assoc_list);
252 
253           *result = assoc;
254 
255           return 0;
256 }
257 
258 /*
259  * Deregister an assoc.
260  */
261 int
fileassoc_deregister(fileassoc_t assoc)262 fileassoc_deregister(fileassoc_t assoc)
263 {
264 
265           LIST_REMOVE(assoc, assoc_list);
266           specificdata_key_delete(fileassoc_domain, assoc->assoc_key);
267           kmem_free(assoc, sizeof(*assoc));
268 
269           return 0;
270 }
271 
272 /*
273  * Get the hash table for the specified device.
274  */
275 static struct fileassoc_table *
fileassoc_table_lookup(struct mount * mp)276 fileassoc_table_lookup(struct mount *mp)
277 {
278           int error;
279 
280           if (!fileassoc_inuse())
281                     return NULL;
282 
283           error = RUN_ONCE(&control, fileassoc_init);
284           if (error) {
285                     return NULL;
286           }
287           return mount_getspecific(mp, fileassoc_mountspecific_key);
288 }
289 
290 /*
291  * Perform a lookup on a hash table.  If hint is non-zero then use the value
292  * of the hint as the identifier instead of performing a lookup for the
293  * fileid.
294  */
295 static struct fileassoc_file *
fileassoc_file_lookup(struct vnode * vp,fhandle_t * hint)296 fileassoc_file_lookup(struct vnode *vp, fhandle_t *hint)
297 {
298           struct fileassoc_table *tbl;
299           struct fileassoc_hash_entry *hash_entry;
300           struct fileassoc_file *faf;
301           size_t indx;
302           fhandle_t *th;
303           int error;
304 
305           tbl = fileassoc_table_lookup(vp->v_mount);
306           if (tbl == NULL) {
307                     return NULL;
308           }
309 
310           if (hint == NULL) {
311                     error = vfs_composefh_alloc(vp, &th);
312                     if (error)
313                               return (NULL);
314           } else {
315                     th = hint;
316           }
317 
318           indx = FILEASSOC_HASH(tbl, th);
319           hash_entry = &(tbl->tbl_hash[indx]);
320 
321           LIST_FOREACH(faf, hash_entry, faf_list) {
322                     if (((FHANDLE_FILEID(faf->faf_handle)->fid_len ==
323                          FHANDLE_FILEID(th)->fid_len)) &&
324                         (memcmp(FHANDLE_FILEID(faf->faf_handle), FHANDLE_FILEID(th),
325                                  (FHANDLE_FILEID(th))->fid_len) == 0)) {
326                               break;
327                     }
328           }
329 
330           if (hint == NULL)
331                     vfs_composefh_free(th);
332 
333           return faf;
334 }
335 
336 /*
337  * Return assoc data associated with a vnode.
338  */
339 void *
fileassoc_lookup(struct vnode * vp,fileassoc_t assoc)340 fileassoc_lookup(struct vnode *vp, fileassoc_t assoc)
341 {
342           struct fileassoc_file *faf;
343 
344           faf = fileassoc_file_lookup(vp, NULL);
345           if (faf == NULL)
346                     return (NULL);
347 
348           return file_getdata(faf, assoc);
349 }
350 
351 static struct fileassoc_table *
fileassoc_table_resize(struct fileassoc_table * tbl)352 fileassoc_table_resize(struct fileassoc_table *tbl)
353 {
354           struct fileassoc_table *newtbl;
355           u_long i;
356 
357           /*
358            * Allocate a new table. Like the condition in fileassoc_file_add(),
359            * this is also temporary -- just double the number of slots.
360            */
361           newtbl = kmem_zalloc(sizeof(*newtbl), KM_SLEEP);
362           newtbl->tbl_nslots = (tbl->tbl_nslots * 2);
363           if (newtbl->tbl_nslots < tbl->tbl_nslots)
364                     newtbl->tbl_nslots = tbl->tbl_nslots;
365           newtbl->tbl_hash = hashinit(newtbl->tbl_nslots, HASH_LIST,
366               true, &newtbl->tbl_mask);
367           newtbl->tbl_nused = 0;
368           specificdata_init(fileassoc_domain, &newtbl->tbl_data);
369 
370           /* XXX we need to make sure nothing uses fileassoc here! */
371 
372           for (i = 0; i < tbl->tbl_nslots; i++) {
373                     struct fileassoc_file *faf;
374 
375                     while ((faf = LIST_FIRST(&tbl->tbl_hash[i])) != NULL) {
376                               struct fileassoc_hash_entry *hash_entry;
377                               size_t indx;
378 
379                               LIST_REMOVE(faf, faf_list);
380 
381                               indx = FILEASSOC_HASH(newtbl, faf->faf_handle);
382                               hash_entry = &(newtbl->tbl_hash[indx]);
383 
384                               LIST_INSERT_HEAD(hash_entry, faf, faf_list);
385 
386                               newtbl->tbl_nused++;
387                     }
388           }
389 
390           if (tbl->tbl_nused != newtbl->tbl_nused)
391                     panic("fileassoc_table_resize: inconsistency detected! "
392                         "needed %zu entries, got %zu", tbl->tbl_nused,
393                         newtbl->tbl_nused);
394 
395           hashdone(tbl->tbl_hash, HASH_LIST, tbl->tbl_mask);
396           specificdata_fini(fileassoc_domain, &tbl->tbl_data);
397           kmem_free(tbl, sizeof(*tbl));
398 
399           return (newtbl);
400 }
401 
402 /*
403  * Create a new fileassoc table.
404  */
405 static struct fileassoc_table *
fileassoc_table_add(struct mount * mp)406 fileassoc_table_add(struct mount *mp)
407 {
408           struct fileassoc_table *tbl;
409 
410           /* Check for existing table for device. */
411           tbl = fileassoc_table_lookup(mp);
412           if (tbl != NULL)
413                     return (tbl);
414 
415           /* Allocate and initialize a table. */
416           tbl = kmem_zalloc(sizeof(*tbl), KM_SLEEP);
417           tbl->tbl_nslots = FILEASSOC_INITIAL_TABLESIZE;
418           tbl->tbl_hash = hashinit(tbl->tbl_nslots, HASH_LIST, true,
419               &tbl->tbl_mask);
420           tbl->tbl_nused = 0;
421           specificdata_init(fileassoc_domain, &tbl->tbl_data);
422 
423           mount_setspecific(mp, fileassoc_mountspecific_key, tbl);
424 
425           return (tbl);
426 }
427 
428 /*
429  * Delete a table.
430  */
431 int
fileassoc_table_delete(struct mount * mp)432 fileassoc_table_delete(struct mount *mp)
433 {
434           struct fileassoc_table *tbl;
435 
436           tbl = fileassoc_table_lookup(mp);
437           if (tbl == NULL)
438                     return (EEXIST);
439 
440           mount_setspecific(mp, fileassoc_mountspecific_key, NULL);
441           table_dtor(tbl);
442 
443           return (0);
444 }
445 
446 /*
447  * Run a callback for each assoc in a table.
448  */
449 int
fileassoc_table_run(struct mount * mp,fileassoc_t assoc,fileassoc_cb_t cb,void * cookie)450 fileassoc_table_run(struct mount *mp, fileassoc_t assoc, fileassoc_cb_t cb,
451     void *cookie)
452 {
453           struct fileassoc_table *tbl;
454           u_long i;
455 
456           tbl = fileassoc_table_lookup(mp);
457           if (tbl == NULL)
458                     return (EEXIST);
459 
460           for (i = 0; i < tbl->tbl_nslots; i++) {
461                     struct fileassoc_file *faf;
462 
463                     LIST_FOREACH(faf, &tbl->tbl_hash[i], faf_list) {
464                               void *data;
465 
466                               data = file_getdata(faf, assoc);
467                               if (data != NULL)
468                                         cb(data, cookie);
469                     }
470           }
471 
472           return (0);
473 }
474 
475 /*
476  * Clear a table for a given assoc.
477  */
478 int
fileassoc_table_clear(struct mount * mp,fileassoc_t assoc)479 fileassoc_table_clear(struct mount *mp, fileassoc_t assoc)
480 {
481           struct fileassoc_table *tbl;
482           u_long i;
483 
484           tbl = fileassoc_table_lookup(mp);
485           if (tbl == NULL)
486                     return (EEXIST);
487 
488           for (i = 0; i < tbl->tbl_nslots; i++) {
489                     struct fileassoc_file *faf;
490 
491                     LIST_FOREACH(faf, &tbl->tbl_hash[i], faf_list) {
492                               file_cleanup(faf, assoc);
493                               file_setdata(faf, assoc, NULL);
494                               /* XXX missing faf->faf_nassocs--? */
495                               fileassoc_decuse();
496                     }
497           }
498 
499           return (0);
500 }
501 
502 /*
503  * Add a file entry to a table.
504  */
505 static struct fileassoc_file *
fileassoc_file_add(struct vnode * vp,fhandle_t * hint)506 fileassoc_file_add(struct vnode *vp, fhandle_t *hint)
507 {
508           struct fileassoc_table *tbl;
509           struct fileassoc_hash_entry *hash_entry;
510           struct fileassoc_file *faf;
511           size_t indx;
512           fhandle_t *th;
513           int error;
514 
515           if (hint == NULL) {
516                     error = vfs_composefh_alloc(vp, &th);
517                     if (error)
518                               return (NULL);
519           } else
520                     th = hint;
521 
522           faf = fileassoc_file_lookup(vp, th);
523           if (faf != NULL) {
524                     if (hint == NULL)
525                               vfs_composefh_free(th);
526 
527                     return (faf);
528           }
529 
530           tbl = fileassoc_table_lookup(vp->v_mount);
531           if (tbl == NULL) {
532                     tbl = fileassoc_table_add(vp->v_mount);
533           }
534 
535           indx = FILEASSOC_HASH(tbl, th);
536           hash_entry = &(tbl->tbl_hash[indx]);
537 
538           faf = kmem_zalloc(sizeof(*faf), KM_SLEEP);
539           faf->faf_handle = th;
540           specificdata_init(fileassoc_domain, &faf->faf_data);
541           LIST_INSERT_HEAD(hash_entry, faf, faf_list);
542 
543           /*
544            * This decides when we need to resize the table. For now,
545            * resize it whenever we "filled" up the number of slots it
546            * has. That's not really true unless of course we had zero
547            * collisions. Think positive! :)
548            */
549           if (++(tbl->tbl_nused) == tbl->tbl_nslots) {
550                     struct fileassoc_table *newtbl;
551 
552                     newtbl = fileassoc_table_resize(tbl);
553                     mount_setspecific(vp->v_mount, fileassoc_mountspecific_key,
554                         newtbl);
555           }
556 
557           return (faf);
558 }
559 
560 /*
561  * Delete a file entry from a table.
562  */
563 int
fileassoc_file_delete(struct vnode * vp)564 fileassoc_file_delete(struct vnode *vp)
565 {
566           struct fileassoc_table *tbl;
567           struct fileassoc_file *faf;
568 
569           if (!fileassoc_inuse())
570                     return ENOENT;
571 
572           KERNEL_LOCK(1, NULL);
573 
574           faf = fileassoc_file_lookup(vp, NULL);
575           if (faf == NULL) {
576                     KERNEL_UNLOCK_ONE(NULL);
577                     return (ENOENT);
578           }
579 
580           file_free(faf);
581 
582           tbl = fileassoc_table_lookup(vp->v_mount);
583           KASSERT(tbl != NULL);
584           --(tbl->tbl_nused); /* XXX gc? */
585 
586           KERNEL_UNLOCK_ONE(NULL);
587 
588           return (0);
589 }
590 
591 /*
592  * Add an assoc to a vnode.
593  */
594 int
fileassoc_add(struct vnode * vp,fileassoc_t assoc,void * data)595 fileassoc_add(struct vnode *vp, fileassoc_t assoc, void *data)
596 {
597           struct fileassoc_file *faf;
598           void *olddata;
599 
600           faf = fileassoc_file_lookup(vp, NULL);
601           if (faf == NULL) {
602                     faf = fileassoc_file_add(vp, NULL);
603                     if (faf == NULL)
604                               return (ENOTDIR);
605           }
606 
607           olddata = file_getdata(faf, assoc);
608           if (olddata != NULL)
609                     return (EEXIST);
610 
611           fileassoc_incuse();
612 
613           file_setdata(faf, assoc, data);
614 
615           faf->faf_nassocs++;
616 
617           return (0);
618 }
619 
620 /*
621  * Clear an assoc from a vnode.
622  */
623 int
fileassoc_clear(struct vnode * vp,fileassoc_t assoc)624 fileassoc_clear(struct vnode *vp, fileassoc_t assoc)
625 {
626           struct fileassoc_file *faf;
627 
628           faf = fileassoc_file_lookup(vp, NULL);
629           if (faf == NULL)
630                     return (ENOENT);
631 
632           file_cleanup(faf, assoc);
633           file_setdata(faf, assoc, NULL);
634 
635           --(faf->faf_nassocs); /* XXX gc? */
636 
637           fileassoc_decuse();
638 
639           return (0);
640 }
641