1 /*        $NetBSD: tmpfs_subr.c,v 1.117 2023/04/29 08:15:13 riastradh Exp $     */
2 
3 /*
4  * Copyright (c) 2005-2020 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Julio M. Merino Vidal, developed as part of Google's Summer of Code
9  * 2005 program, and by Mindaugas Rasiukevicius.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 /*
34  * Efficient memory file system: interfaces for inode and directory entry
35  * construction, destruction and manipulation.
36  *
37  * Reference counting
38  *
39  *        The link count of inode (tmpfs_node_t::tn_links) is used as a
40  *        reference counter.  However, it has slightly different semantics.
41  *
42  *        For directories - link count represents directory entries, which
43  *        refer to the directories.  In other words, it represents the count
44  *        of sub-directories.  It also takes into account the virtual '.'
45  *        entry (which has no real entry in the list).  For files - link count
46  *        represents the hard links.  Since only empty directories can be
47  *        removed - link count aligns the reference counting requirements
48  *        enough.  Note: to check whether directory is not empty, the inode
49  *        size (tmpfs_node_t::tn_size) can be used.
50  *
51  *        The inode itself, as an object, gathers its first reference when
52  *        directory entry is attached via tmpfs_dir_attach(9).  For instance,
53  *        after regular tmpfs_create(), a file would have a link count of 1,
54  *        while directory after tmpfs_mkdir() would have 2 (due to '.').
55  *
56  * Reclamation
57  *
58  *        It should be noted that tmpfs inodes rely on a combination of vnode
59  *        reference counting and link counting.  That is, an inode can only be
60  *        destroyed if its associated vnode is inactive.  The destruction is
61  *        done on vnode reclamation i.e. tmpfs_reclaim().  It should be noted
62  *        that tmpfs_node_t::tn_links being 0 is a destruction criterion.
63  *
64  *        If an inode has references within the file system (tn_links > 0) and
65  *        its inactive vnode gets reclaimed/recycled - then the association is
66  *        broken in tmpfs_reclaim().  In such case, an inode will always pass
67  *        tmpfs_lookup() and thus vcache_get() to associate a new vnode.
68  *
69  * Lock order
70  *
71  *        vnode_t::v_vlock ->
72  *                  vnode_t::v_interlock
73  */
74 
75 #include <sys/cdefs.h>
76 __KERNEL_RCSID(0, "$NetBSD: tmpfs_subr.c,v 1.117 2023/04/29 08:15:13 riastradh Exp $");
77 
78 #include <sys/param.h>
79 #include <sys/cprng.h>
80 #include <sys/dirent.h>
81 #include <sys/event.h>
82 #include <sys/kmem.h>
83 #include <sys/mount.h>
84 #include <sys/namei.h>
85 #include <sys/time.h>
86 #include <sys/stat.h>
87 #include <sys/systm.h>
88 #include <sys/vnode.h>
89 #include <sys/kauth.h>
90 #include <sys/atomic.h>
91 
92 #include <uvm/uvm_aobj.h>
93 #include <uvm/uvm_extern.h>
94 #include <uvm/uvm_object.h>
95 
96 #include <miscfs/specfs/specdev.h>
97 #include <miscfs/genfs/genfs.h>
98 #include <fs/tmpfs/tmpfs.h>
99 #include <fs/tmpfs/tmpfs_fifoops.h>
100 #include <fs/tmpfs/tmpfs_specops.h>
101 #include <fs/tmpfs/tmpfs_vnops.h>
102 
103 static void         tmpfs_dir_putseq(tmpfs_node_t *, tmpfs_dirent_t *);
104 
105 /*
106  * Initialize vnode with tmpfs node.
107  */
108 static void
tmpfs_init_vnode(struct vnode * vp,tmpfs_node_t * node)109 tmpfs_init_vnode(struct vnode *vp, tmpfs_node_t *node)
110 {
111           krwlock_t *slock;
112 
113           KASSERT(node->tn_vnode == NULL);
114 
115           /* Share the interlock with the node. */
116           if (node->tn_type == VREG) {
117                     slock = node->tn_spec.tn_reg.tn_aobj->vmobjlock;
118                     rw_obj_hold(slock);
119                     uvm_obj_setlock(&vp->v_uobj, slock);
120           }
121 
122           vp->v_tag = VT_TMPFS;
123           vp->v_type = node->tn_type;
124 
125           /* Type-specific initialization. */
126           switch (vp->v_type) {
127           case VBLK:
128           case VCHR:
129                     vp->v_op = tmpfs_specop_p;
130                     spec_node_init(vp, node->tn_spec.tn_dev.tn_rdev);
131                     break;
132           case VFIFO:
133                     vp->v_op = tmpfs_fifoop_p;
134                     break;
135           case VDIR:
136                     if (node->tn_spec.tn_dir.tn_parent == node)
137                               vp->v_vflag |= VV_ROOT;
138                     /* FALLTHROUGH */
139           case VLNK:
140           case VREG:
141           case VSOCK:
142                     vp->v_op = tmpfs_vnodeop_p;
143                     break;
144           default:
145                     panic("bad node type %d", vp->v_type);
146                     break;
147           }
148 
149           vp->v_data = node;
150           node->tn_vnode = vp;
151           uvm_vnp_setsize(vp, node->tn_size);
152           KASSERT(node->tn_mode != VNOVAL);
153           cache_enter_id(vp, node->tn_mode, node->tn_uid, node->tn_gid, true);
154 }
155 
156 /*
157  * tmpfs_loadvnode: initialise a vnode for a specified inode.
158  */
159 int
tmpfs_loadvnode(struct mount * mp,struct vnode * vp,const void * key,size_t key_len,const void ** new_key)160 tmpfs_loadvnode(struct mount *mp, struct vnode *vp,
161     const void *key, size_t key_len, const void **new_key)
162 {
163           tmpfs_node_t *node;
164 
165           KASSERT(key_len == sizeof(node));
166           memcpy(&node, key, key_len);
167 
168           if (node->tn_links == 0)
169                     return ENOENT;
170 
171           tmpfs_init_vnode(vp, node);
172 
173           *new_key = &vp->v_data;
174 
175           return 0;
176 }
177 
178 /*
179  * tmpfs_newvnode: allocate a new inode of a specified type and
180  * attach the vonode.
181  */
182 int
tmpfs_newvnode(struct mount * mp,struct vnode * dvp,struct vnode * vp,struct vattr * vap,kauth_cred_t cred,void * extra,size_t * key_len,const void ** new_key)183 tmpfs_newvnode(struct mount *mp, struct vnode *dvp, struct vnode *vp,
184     struct vattr *vap, kauth_cred_t cred, void *extra,
185     size_t *key_len, const void **new_key)
186 {
187           tmpfs_mount_t *tmp = VFS_TO_TMPFS(mp);
188           tmpfs_node_t *node, *dnode;
189 
190           if (dvp != NULL) {
191                     KASSERT(VOP_ISLOCKED(dvp));
192                     dnode = VP_TO_TMPFS_DIR(dvp);
193                     if (dnode->tn_links == 0)
194                               return ENOENT;
195                     if (vap->va_type == VDIR) {
196                               /* Check for maximum links limit. */
197                               if (dnode->tn_links == LINK_MAX)
198                                         return EMLINK;
199                               KASSERT(dnode->tn_links < LINK_MAX);
200                     }
201           } else
202                     dnode = NULL;
203 
204           node = tmpfs_node_get(tmp);
205           if (node == NULL)
206                     return ENOSPC;
207 
208           /* Initially, no references and no associations. */
209           node->tn_links = 0;
210           node->tn_vnode = NULL;
211           node->tn_holdcount = 0;
212           node->tn_dirent_hint = NULL;
213 
214           /*
215            * XXX Where the pool is backed by a map larger than (4GB *
216            * sizeof(*node)), this may produce duplicate inode numbers
217            * for applications that do not understand 64-bit ino_t.
218            */
219           node->tn_id = (ino_t)((uintptr_t)node / sizeof(*node));
220           /*
221            * Make sure the generation number is not zero.
222            * tmpfs_inactive() uses generation zero to mark dead nodes.
223            */
224           do {
225                     node->tn_gen = TMPFS_NODE_GEN_MASK & cprng_fast32();
226           } while (node->tn_gen == 0);
227 
228           /* Generic initialization. */
229           KASSERT((int)vap->va_type != VNOVAL);
230           node->tn_type = vap->va_type;
231           node->tn_size = 0;
232           node->tn_flags = 0;
233           node->tn_lockf = NULL;
234 
235           node->tn_tflags = 0;
236           vfs_timestamp(&node->tn_atime);
237           node->tn_birthtime = node->tn_atime;
238           node->tn_ctime = node->tn_atime;
239           node->tn_mtime = node->tn_atime;
240           mutex_init(&node->tn_timelock, MUTEX_DEFAULT, IPL_NONE);
241 
242           if (dvp == NULL) {
243                     KASSERT(vap->va_uid != VNOVAL && vap->va_gid != VNOVAL);
244                     node->tn_uid = vap->va_uid;
245                     node->tn_gid = vap->va_gid;
246                     vp->v_vflag |= VV_ROOT;
247           } else {
248                     KASSERT(dnode != NULL);
249                     node->tn_uid = kauth_cred_geteuid(cred);
250                     node->tn_gid = dnode->tn_gid;
251           }
252           KASSERT(vap->va_mode != VNOVAL);
253           node->tn_mode = vap->va_mode;
254 
255           /* Type-specific initialization. */
256           switch (node->tn_type) {
257           case VBLK:
258           case VCHR:
259                     /* Character/block special device. */
260                     KASSERT(vap->va_rdev != VNOVAL);
261                     node->tn_spec.tn_dev.tn_rdev = vap->va_rdev;
262                     break;
263           case VDIR:
264                     /* Directory. */
265                     TAILQ_INIT(&node->tn_spec.tn_dir.tn_dir);
266                     node->tn_spec.tn_dir.tn_parent = NULL;
267                     node->tn_spec.tn_dir.tn_seq_arena = NULL;
268                     node->tn_spec.tn_dir.tn_next_seq = TMPFS_DIRSEQ_START;
269                     node->tn_spec.tn_dir.tn_readdir_lastp = NULL;
270 
271                     /* Extra link count for the virtual '.' entry. */
272                     node->tn_links++;
273                     break;
274           case VFIFO:
275           case VSOCK:
276                     break;
277           case VLNK:
278                     node->tn_size = 0;
279                     node->tn_spec.tn_lnk.tn_link = NULL;
280                     break;
281           case VREG:
282                     /* Regular file.  Create an underlying UVM object. */
283                     node->tn_spec.tn_reg.tn_aobj =
284                         uao_create(INT64_MAX - PAGE_SIZE, 0);
285                     node->tn_spec.tn_reg.tn_aobj_pages = 0;
286                     break;
287           default:
288                     panic("bad node type %d", vp->v_type);
289                     break;
290           }
291 
292           tmpfs_init_vnode(vp, node);
293 
294           mutex_enter(&tmp->tm_lock);
295           LIST_INSERT_HEAD(&tmp->tm_nodes, node, tn_entries);
296           mutex_exit(&tmp->tm_lock);
297 
298           *key_len = sizeof(vp->v_data);
299           *new_key = &vp->v_data;
300 
301           return 0;
302 }
303 
304 /*
305  * tmpfs_free_node: remove the inode from a list in the mount point and
306  * destroy the inode structures.
307  */
308 void
tmpfs_free_node(tmpfs_mount_t * tmp,tmpfs_node_t * node)309 tmpfs_free_node(tmpfs_mount_t *tmp, tmpfs_node_t *node)
310 {
311           size_t objsz;
312           uint32_t hold;
313 
314           mutex_enter(&tmp->tm_lock);
315           hold = atomic_or_32_nv(&node->tn_holdcount, TMPFS_NODE_RECLAIMED);
316           /* Defer destruction to last thread holding this node. */
317           if (hold != TMPFS_NODE_RECLAIMED) {
318                     mutex_exit(&tmp->tm_lock);
319                     return;
320           }
321           LIST_REMOVE(node, tn_entries);
322           mutex_exit(&tmp->tm_lock);
323 
324           switch (node->tn_type) {
325           case VLNK:
326                     if (node->tn_size > 0) {
327                               tmpfs_strname_free(tmp, node->tn_spec.tn_lnk.tn_link,
328                                   node->tn_size);
329                     }
330                     break;
331           case VREG:
332                     /*
333                      * Calculate the size of inode data, decrease the used-memory
334                      * counter, and destroy the unerlying UVM object (if any).
335                      */
336                     objsz = PAGE_SIZE * node->tn_spec.tn_reg.tn_aobj_pages;
337                     if (objsz != 0) {
338                               tmpfs_mem_decr(tmp, objsz);
339                     }
340                     if (node->tn_spec.tn_reg.tn_aobj != NULL) {
341                               uao_detach(node->tn_spec.tn_reg.tn_aobj);
342                     }
343                     break;
344           case VDIR:
345                     KASSERT(node->tn_size == 0);
346                     KASSERT(node->tn_spec.tn_dir.tn_seq_arena == NULL);
347                     KASSERT(TAILQ_EMPTY(&node->tn_spec.tn_dir.tn_dir));
348                     KASSERT(node->tn_spec.tn_dir.tn_parent == NULL ||
349                         node == tmp->tm_root);
350                     break;
351           default:
352                     break;
353           }
354           KASSERT(node->tn_vnode == NULL);
355           KASSERT(node->tn_links == 0);
356 
357           mutex_destroy(&node->tn_timelock);
358           tmpfs_node_put(tmp, node);
359 }
360 
361 /*
362  * tmpfs_construct_node: allocate a new file of specified type and adds it
363  * into the parent directory.
364  *
365  * => Credentials of the caller are used.
366  */
367 int
tmpfs_construct_node(vnode_t * dvp,vnode_t ** vpp,struct vattr * vap,struct componentname * cnp,char * target)368 tmpfs_construct_node(vnode_t *dvp, vnode_t **vpp, struct vattr *vap,
369     struct componentname *cnp, char *target)
370 {
371           tmpfs_mount_t *tmp = VFS_TO_TMPFS(dvp->v_mount);
372           tmpfs_node_t *dnode = VP_TO_TMPFS_DIR(dvp), *node;
373           tmpfs_dirent_t *de, *wde;
374           char *slink = NULL;
375           int ssize = 0;
376           int error;
377 
378           /* Allocate symlink target. */
379           if (target != NULL) {
380                     KASSERT(vap->va_type == VLNK);
381                     ssize = strlen(target);
382                     KASSERT(ssize < MAXPATHLEN);
383                     if (ssize > 0) {
384                               slink = tmpfs_strname_alloc(tmp, ssize);
385                               if (slink == NULL)
386                                         return ENOSPC;
387                               memcpy(slink, target, ssize);
388                     }
389           }
390 
391           /* Allocate a directory entry that points to the new file. */
392           error = tmpfs_alloc_dirent(tmp, cnp->cn_nameptr, cnp->cn_namelen, &de);
393           if (error) {
394                     if (slink != NULL)
395                               tmpfs_strname_free(tmp, slink, ssize);
396                     return error;
397           }
398 
399           /* Allocate a vnode that represents the new file. */
400           error = vcache_new(dvp->v_mount, dvp, vap, cnp->cn_cred, NULL, vpp);
401           if (error) {
402                     if (slink != NULL)
403                               tmpfs_strname_free(tmp, slink, ssize);
404                     tmpfs_free_dirent(tmp, de);
405                     return error;
406           }
407           error = vn_lock(*vpp, LK_EXCLUSIVE);
408           if (error) {
409                     vrele(*vpp);
410                     *vpp = NULL;
411                     if (slink != NULL)
412                               tmpfs_strname_free(tmp, slink, ssize);
413                     tmpfs_free_dirent(tmp, de);
414                     return error;
415           }
416 
417           node = VP_TO_TMPFS_NODE(*vpp);
418 
419           if (slink != NULL) {
420                     node->tn_spec.tn_lnk.tn_link = slink;
421                     node->tn_size = ssize;
422           }
423 
424           /* Remove whiteout before adding the new entry. */
425           if (cnp->cn_flags & ISWHITEOUT) {
426                     wde = tmpfs_dir_lookup(dnode, cnp);
427                     KASSERT(wde != NULL && wde->td_node == TMPFS_NODE_WHITEOUT);
428                     tmpfs_dir_detach(dnode, wde);
429                     tmpfs_free_dirent(tmp, wde);
430           }
431 
432           /* Associate inode and attach the entry into the directory. */
433           tmpfs_dir_attach(dnode, de, node);
434 
435           /* Make node opaque if requested. */
436           if (cnp->cn_flags & ISWHITEOUT)
437                     node->tn_flags |= UF_OPAQUE;
438 
439           /* Update the parent's timestamps. */
440           tmpfs_update(dvp, TMPFS_UPDATE_MTIME | TMPFS_UPDATE_CTIME);
441 
442           VOP_UNLOCK(*vpp);
443 
444           cache_enter(dvp, *vpp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_flags);
445           return 0;
446 }
447 
448 /*
449  * tmpfs_alloc_dirent: allocates a new directory entry for the inode.
450  * The directory entry contains a path name component.
451  */
452 int
tmpfs_alloc_dirent(tmpfs_mount_t * tmp,const char * name,uint16_t len,tmpfs_dirent_t ** de)453 tmpfs_alloc_dirent(tmpfs_mount_t *tmp, const char *name, uint16_t len,
454     tmpfs_dirent_t **de)
455 {
456           tmpfs_dirent_t *nde;
457 
458           nde = tmpfs_dirent_get(tmp);
459           if (nde == NULL)
460                     return ENOSPC;
461 
462           nde->td_name = tmpfs_strname_alloc(tmp, len);
463           if (nde->td_name == NULL) {
464                     tmpfs_dirent_put(tmp, nde);
465                     return ENOSPC;
466           }
467           nde->td_namelen = len;
468           memcpy(nde->td_name, name, len);
469           nde->td_seq = TMPFS_DIRSEQ_NONE;
470           nde->td_node = NULL; /* for asserts */
471 
472           *de = nde;
473           return 0;
474 }
475 
476 /*
477  * tmpfs_free_dirent: free a directory entry.
478  */
479 void
tmpfs_free_dirent(tmpfs_mount_t * tmp,tmpfs_dirent_t * de)480 tmpfs_free_dirent(tmpfs_mount_t *tmp, tmpfs_dirent_t *de)
481 {
482           KASSERT(de->td_node == NULL);
483           KASSERT(de->td_seq == TMPFS_DIRSEQ_NONE);
484           tmpfs_strname_free(tmp, de->td_name, de->td_namelen);
485           tmpfs_dirent_put(tmp, de);
486 }
487 
488 /*
489  * tmpfs_dir_attach: associate directory entry with a specified inode,
490  * and attach the entry into the directory, specified by vnode.
491  *
492  * => Increases link count on the associated node.
493  * => Increases link count on directory node if our node is VDIR.
494  * => It is caller's responsibility to check for the LINK_MAX limit.
495  * => Triggers kqueue events here.
496  */
497 void
tmpfs_dir_attach(tmpfs_node_t * dnode,tmpfs_dirent_t * de,tmpfs_node_t * node)498 tmpfs_dir_attach(tmpfs_node_t *dnode, tmpfs_dirent_t *de, tmpfs_node_t *node)
499 {
500           vnode_t *dvp = dnode->tn_vnode;
501           int events = NOTE_WRITE;
502 
503           KASSERT(dvp != NULL);
504           KASSERT(VOP_ISLOCKED(dvp));
505 
506           /* Get a new sequence number. */
507           KASSERT(de->td_seq == TMPFS_DIRSEQ_NONE);
508           de->td_seq = tmpfs_dir_getseq(dnode, de);
509 
510           /* Associate directory entry and the inode. */
511           de->td_node = node;
512           if (node != TMPFS_NODE_WHITEOUT) {
513                     KASSERT(node->tn_links < LINK_MAX);
514                     node->tn_links++;
515 
516                     /* Save the hint (might overwrite). */
517                     node->tn_dirent_hint = de;
518           } else if ((dnode->tn_gen & TMPFS_WHITEOUT_BIT) == 0) {
519                     /* Flag that there are whiteout entries. */
520                     atomic_or_32(&dnode->tn_gen, TMPFS_WHITEOUT_BIT);
521           }
522 
523           /* Insert the entry to the directory (parent of inode). */
524           TAILQ_INSERT_TAIL(&dnode->tn_spec.tn_dir.tn_dir, de, td_entries);
525           KASSERT(dnode->tn_size <= __type_max(off_t) - sizeof(tmpfs_dirent_t));
526           dnode->tn_size += sizeof(tmpfs_dirent_t);
527           uvm_vnp_setsize(dvp, dnode->tn_size);
528 
529           if (node != TMPFS_NODE_WHITEOUT && node->tn_type == VDIR) {
530                     /* Set parent. */
531                     KASSERT(node->tn_spec.tn_dir.tn_parent == NULL);
532                     node->tn_spec.tn_dir.tn_parent = dnode;
533 
534                     /* Increase the link count of parent. */
535                     KASSERT(dnode->tn_links < LINK_MAX);
536                     dnode->tn_links++;
537                     events |= NOTE_LINK;
538 
539                     TMPFS_VALIDATE_DIR(node);
540           }
541 }
542 
543 /*
544  * tmpfs_dir_detach: disassociate directory entry and its inode,
545  * and detach the entry from the directory, specified by vnode.
546  *
547  * => Decreases link count on the associated node.
548  * => Decreases the link count on directory node, if our node is VDIR.
549  * => Triggers kqueue events here.
550  *
551  * => Note: dvp and vp may be NULL only if called by tmpfs_unmount().
552  */
553 void
tmpfs_dir_detach(tmpfs_node_t * dnode,tmpfs_dirent_t * de)554 tmpfs_dir_detach(tmpfs_node_t *dnode, tmpfs_dirent_t *de)
555 {
556           tmpfs_node_t *node = de->td_node;
557           vnode_t *dvp = dnode->tn_vnode;
558 
559           KASSERT(dvp == NULL || VOP_ISLOCKED(dvp));
560 
561           if (__predict_true(node != TMPFS_NODE_WHITEOUT)) {
562                     /* Deassociate the inode and entry. */
563                     node->tn_dirent_hint = NULL;
564 
565                     KASSERT(node->tn_links > 0);
566                     node->tn_links--;
567 
568                     /* If directory - decrease the link count of parent. */
569                     if (node->tn_type == VDIR) {
570                               KASSERT(node->tn_spec.tn_dir.tn_parent == dnode);
571                               node->tn_spec.tn_dir.tn_parent = NULL;
572 
573                               KASSERT(dnode->tn_links > 0);
574                               dnode->tn_links--;
575                     }
576           }
577           de->td_node = NULL;
578 
579           /* Remove the entry from the directory. */
580           if (dnode->tn_spec.tn_dir.tn_readdir_lastp == de) {
581                     dnode->tn_spec.tn_dir.tn_readdir_lastp = NULL;
582           }
583           TAILQ_REMOVE(&dnode->tn_spec.tn_dir.tn_dir, de, td_entries);
584           KASSERT(dnode->tn_size >= sizeof(tmpfs_dirent_t));
585           dnode->tn_size -= sizeof(tmpfs_dirent_t);
586           tmpfs_dir_putseq(dnode, de);
587 
588           if (dvp) {
589                     uvm_vnp_setsize(dvp, dnode->tn_size);
590           }
591 }
592 
593 /*
594  * tmpfs_dir_lookup: find a directory entry in the specified inode.
595  *
596  * Note that the . and .. components are not allowed as they do not
597  * physically exist within directories.
598  */
599 tmpfs_dirent_t *
tmpfs_dir_lookup(tmpfs_node_t * node,struct componentname * cnp)600 tmpfs_dir_lookup(tmpfs_node_t *node, struct componentname *cnp)
601 {
602           const char *name = cnp->cn_nameptr;
603           const uint16_t nlen = cnp->cn_namelen;
604           tmpfs_dirent_t *de;
605 
606           KASSERT(VOP_ISLOCKED(node->tn_vnode));
607           KASSERT(nlen != 1 || !(name[0] == '.'));
608           KASSERT(nlen != 2 || !(name[0] == '.' && name[1] == '.'));
609           TMPFS_VALIDATE_DIR(node);
610 
611           TAILQ_FOREACH(de, &node->tn_spec.tn_dir.tn_dir, td_entries) {
612                     if (de->td_namelen != nlen)
613                               continue;
614                     if (memcmp(de->td_name, name, nlen) != 0)
615                               continue;
616                     break;
617           }
618           return de;
619 }
620 
621 /*
622  * tmpfs_dir_cached: get a cached directory entry if it is valid.  Used to
623  * avoid unnecessary tmpfs_dir_lookup().
624  *
625  * => The vnode must be locked.
626  */
627 tmpfs_dirent_t *
tmpfs_dir_cached(tmpfs_node_t * node)628 tmpfs_dir_cached(tmpfs_node_t *node)
629 {
630           tmpfs_dirent_t *de = node->tn_dirent_hint;
631 
632           KASSERT(VOP_ISLOCKED(node->tn_vnode));
633 
634           if (de == NULL) {
635                     return NULL;
636           }
637           KASSERT(de->td_node == node);
638 
639           /*
640            * Directories always have a valid hint.  For files, check if there
641            * are any hard links.  If there are - hint might be invalid.
642            */
643           return (node->tn_type != VDIR && node->tn_links > 1) ? NULL : de;
644 }
645 
646 /*
647  * tmpfs_dir_getseq: get a per-directory sequence number for the entry.
648  *
649  * => Shall not be larger than 2^31 for linux32 compatibility.
650  */
651 uint32_t
tmpfs_dir_getseq(tmpfs_node_t * dnode,tmpfs_dirent_t * de)652 tmpfs_dir_getseq(tmpfs_node_t *dnode, tmpfs_dirent_t *de)
653 {
654           uint32_t seq = de->td_seq;
655           vmem_t *seq_arena;
656           vmem_addr_t off;
657           int error __diagused;
658 
659           TMPFS_VALIDATE_DIR(dnode);
660 
661           if (__predict_true(seq != TMPFS_DIRSEQ_NONE)) {
662                     /* Already set. */
663                     KASSERT(seq >= TMPFS_DIRSEQ_START);
664                     return seq;
665           }
666 
667           /*
668            * The "." and ".." and the end-of-directory have reserved numbers.
669            * The other sequence numbers are allocated as following:
670            *
671            * - The first half of the 2^31 is assigned incrementally.
672            *
673            * - If that range is exceeded, then the second half of 2^31
674            * is used, but managed by vmem(9).
675            */
676 
677           seq = dnode->tn_spec.tn_dir.tn_next_seq;
678           KASSERT(seq >= TMPFS_DIRSEQ_START);
679 
680           if (__predict_true(seq < TMPFS_DIRSEQ_END)) {
681                     /* First half: just increment and return. */
682                     dnode->tn_spec.tn_dir.tn_next_seq++;
683                     return seq;
684           }
685 
686           /*
687            * First half exceeded, use the second half.  May need to create
688            * vmem(9) arena for the directory first.
689            */
690           if ((seq_arena = dnode->tn_spec.tn_dir.tn_seq_arena) == NULL) {
691                     seq_arena = vmem_create("tmpfscoo", 0,
692                         TMPFS_DIRSEQ_END - 1, 1, NULL, NULL, NULL, 0,
693                         VM_SLEEP, IPL_NONE);
694                     dnode->tn_spec.tn_dir.tn_seq_arena = seq_arena;
695                     KASSERT(seq_arena != NULL);
696           }
697           error = vmem_alloc(seq_arena, 1, VM_SLEEP | VM_BESTFIT, &off);
698           KASSERT(error == 0);
699 
700           KASSERT(off < TMPFS_DIRSEQ_END);
701           seq = off | TMPFS_DIRSEQ_END;
702           return seq;
703 }
704 
705 static void
tmpfs_dir_putseq(tmpfs_node_t * dnode,tmpfs_dirent_t * de)706 tmpfs_dir_putseq(tmpfs_node_t *dnode, tmpfs_dirent_t *de)
707 {
708           vmem_t *seq_arena = dnode->tn_spec.tn_dir.tn_seq_arena;
709           uint32_t seq = de->td_seq;
710 
711           TMPFS_VALIDATE_DIR(dnode);
712 
713           if (seq == TMPFS_DIRSEQ_NONE || seq < TMPFS_DIRSEQ_END) {
714                     /* First half (or no sequence number set yet). */
715                     KASSERT(de->td_seq >= TMPFS_DIRSEQ_START);
716           } else {
717                     /* Second half. */
718                     KASSERT(seq_arena != NULL);
719                     KASSERT(seq >= TMPFS_DIRSEQ_END);
720                     seq &= ~TMPFS_DIRSEQ_END;
721                     vmem_free(seq_arena, seq, 1);
722           }
723           de->td_seq = TMPFS_DIRSEQ_NONE;
724 
725           /* Empty?  We can reset. */
726           if (seq_arena && dnode->tn_size == 0) {
727                     dnode->tn_spec.tn_dir.tn_seq_arena = NULL;
728                     dnode->tn_spec.tn_dir.tn_next_seq = TMPFS_DIRSEQ_START;
729                     vmem_destroy(seq_arena);
730           }
731 }
732 
733 /*
734  * tmpfs_dir_lookupbyseq: lookup a directory entry by the sequence number.
735  */
736 tmpfs_dirent_t *
tmpfs_dir_lookupbyseq(tmpfs_node_t * node,off_t seq)737 tmpfs_dir_lookupbyseq(tmpfs_node_t *node, off_t seq)
738 {
739           tmpfs_dirent_t *de = node->tn_spec.tn_dir.tn_readdir_lastp;
740 
741           TMPFS_VALIDATE_DIR(node);
742 
743           /*
744            * First, check the cache.  If does not match - perform a lookup.
745            */
746           if (de && de->td_seq == seq) {
747                     KASSERT(de->td_seq >= TMPFS_DIRSEQ_START);
748                     KASSERT(de->td_seq != TMPFS_DIRSEQ_NONE);
749                     return de;
750           }
751           TAILQ_FOREACH(de, &node->tn_spec.tn_dir.tn_dir, td_entries) {
752                     KASSERT(de->td_seq >= TMPFS_DIRSEQ_START);
753                     KASSERT(de->td_seq != TMPFS_DIRSEQ_NONE);
754                     if (de->td_seq == seq)
755                               return de;
756           }
757           return NULL;
758 }
759 
760 /*
761  * tmpfs_dir_getdotents: helper function for tmpfs_readdir() to get the
762  * dot meta entries, that is, "." or "..".  Copy it to the UIO space.
763  */
764 static int
tmpfs_dir_getdotents(tmpfs_node_t * node,struct dirent * dp,struct uio * uio)765 tmpfs_dir_getdotents(tmpfs_node_t *node, struct dirent *dp, struct uio *uio)
766 {
767           tmpfs_dirent_t *de;
768           off_t next = 0;
769           int error;
770 
771           switch (uio->uio_offset) {
772           case TMPFS_DIRSEQ_DOT:
773                     dp->d_fileno = node->tn_id;
774                     strlcpy(dp->d_name, ".", sizeof(dp->d_name));
775                     next = TMPFS_DIRSEQ_DOTDOT;
776                     break;
777           case TMPFS_DIRSEQ_DOTDOT:
778                     dp->d_fileno = node->tn_spec.tn_dir.tn_parent->tn_id;
779                     strlcpy(dp->d_name, "..", sizeof(dp->d_name));
780                     de = TAILQ_FIRST(&node->tn_spec.tn_dir.tn_dir);
781                     next = de ? tmpfs_dir_getseq(node, de) : TMPFS_DIRSEQ_EOF;
782                     break;
783           default:
784                     KASSERT(false);
785           }
786           dp->d_type = DT_DIR;
787           dp->d_namlen = strlen(dp->d_name);
788           dp->d_reclen = _DIRENT_SIZE(dp);
789 
790           if (dp->d_reclen > uio->uio_resid) {
791                     return EJUSTRETURN;
792           }
793           if ((error = uiomove(dp, dp->d_reclen, uio)) != 0) {
794                     return error;
795           }
796 
797           uio->uio_offset = next;
798           return error;
799 }
800 
801 /*
802  * tmpfs_dir_getdents: helper function for tmpfs_readdir.
803  *
804  * => Returns as much directory entries as can fit in the uio space.
805  * => The read starts at uio->uio_offset.
806  */
807 int
tmpfs_dir_getdents(tmpfs_node_t * node,struct uio * uio,off_t * cntp)808 tmpfs_dir_getdents(tmpfs_node_t *node, struct uio *uio, off_t *cntp)
809 {
810           tmpfs_dirent_t *de;
811           struct dirent dent;
812           int error = 0;
813 
814           KASSERT(VOP_ISLOCKED(node->tn_vnode));
815           TMPFS_VALIDATE_DIR(node);
816 
817           /*
818            * First check for the "." and ".." cases.
819            * Note: tmpfs_dir_getdotents() will "seek" for us.
820            */
821           memset(&dent, 0, sizeof(dent));
822 
823           if (uio->uio_offset == TMPFS_DIRSEQ_DOT) {
824                     if ((error = tmpfs_dir_getdotents(node, &dent, uio)) != 0) {
825                               goto done;
826                     }
827                     (*cntp)++;
828           }
829           if (uio->uio_offset == TMPFS_DIRSEQ_DOTDOT) {
830                     if ((error = tmpfs_dir_getdotents(node, &dent, uio)) != 0) {
831                               goto done;
832                     }
833                     (*cntp)++;
834           }
835 
836           /* Done if we reached the end. */
837           if (uio->uio_offset == TMPFS_DIRSEQ_EOF) {
838                     goto done;
839           }
840 
841           /* Locate the directory entry given by the given sequence number. */
842           de = tmpfs_dir_lookupbyseq(node, uio->uio_offset);
843           if (de == NULL) {
844                     error = EINVAL;
845                     goto done;
846           }
847 
848           /*
849            * Read as many entries as possible; i.e., until we reach the end
850            * of the directory or we exhaust UIO space.
851            */
852           do {
853                     if (de->td_node == TMPFS_NODE_WHITEOUT) {
854                               dent.d_fileno = 1;
855                               dent.d_type = DT_WHT;
856                     } else {
857                               dent.d_fileno = de->td_node->tn_id;
858                               dent.d_type = vtype2dt(de->td_node->tn_type);
859                     }
860                     dent.d_namlen = de->td_namelen;
861                     KASSERT(de->td_namelen < sizeof(dent.d_name));
862                     memcpy(dent.d_name, de->td_name, de->td_namelen);
863                     dent.d_name[de->td_namelen] = '\0';
864                     dent.d_reclen = _DIRENT_SIZE(&dent);
865 
866                     if (dent.d_reclen > uio->uio_resid) {
867                               /* Exhausted UIO space. */
868                               error = EJUSTRETURN;
869                               break;
870                     }
871 
872                     /* Copy out the directory entry and continue. */
873                     error = uiomove(&dent, dent.d_reclen, uio);
874                     if (error) {
875                               break;
876                     }
877                     (*cntp)++;
878                     de = TAILQ_NEXT(de, td_entries);
879 
880           } while (uio->uio_resid > 0 && de);
881 
882           /* Cache the last entry or clear and mark EOF. */
883           uio->uio_offset = de ? tmpfs_dir_getseq(node, de) : TMPFS_DIRSEQ_EOF;
884           node->tn_spec.tn_dir.tn_readdir_lastp = de;
885 done:
886           tmpfs_update(node->tn_vnode, TMPFS_UPDATE_ATIME);
887 
888           if (error == EJUSTRETURN) {
889                     /* Exhausted UIO space - just return. */
890                     error = 0;
891           }
892           KASSERT(error >= 0);
893           return error;
894 }
895 
896 /*
897  * tmpfs_reg_resize: resize the underlying UVM object associated with the
898  * specified regular file.
899  */
900 int
tmpfs_reg_resize(struct vnode * vp,off_t newsize)901 tmpfs_reg_resize(struct vnode *vp, off_t newsize)
902 {
903           tmpfs_mount_t *tmp = VFS_TO_TMPFS(vp->v_mount);
904           tmpfs_node_t *node = VP_TO_TMPFS_NODE(vp);
905           struct uvm_object *uobj = node->tn_spec.tn_reg.tn_aobj;
906           size_t newpages, oldpages;
907           off_t oldsize;
908 
909           KASSERT(vp->v_type == VREG);
910           KASSERT(newsize >= 0);
911 
912           if (newsize > __type_max(off_t) - PAGE_SIZE + 1)
913                     return EFBIG;
914 
915           oldsize = node->tn_size;
916           oldpages = round_page(oldsize) >> PAGE_SHIFT;
917           newpages = round_page(newsize) >> PAGE_SHIFT;
918           KASSERT(oldpages == node->tn_spec.tn_reg.tn_aobj_pages);
919 
920           if (newsize == oldsize) {
921                     return 0;
922           }
923 
924           if (newpages > oldpages) {
925                     /* Increase the used-memory counter if getting extra pages. */
926                     if (!tmpfs_mem_incr(tmp, (newpages - oldpages) << PAGE_SHIFT)) {
927                               return ENOSPC;
928                     }
929           } else if (newsize < oldsize) {
930                     size_t zerolen;
931 
932                     zerolen = MIN(round_page(newsize), node->tn_size) - newsize;
933                     ubc_zerorange(uobj, newsize, zerolen, UBC_VNODE_FLAGS(vp));
934           }
935 
936           node->tn_spec.tn_reg.tn_aobj_pages = newpages;
937           node->tn_size = newsize;
938           uvm_vnp_setsize(vp, newsize);
939 
940           /*
941            * Free "backing store".
942            */
943           if (newpages < oldpages) {
944                     rw_enter(uobj->vmobjlock, RW_WRITER);
945                     uao_dropswap_range(uobj, newpages, oldpages);
946                     rw_exit(uobj->vmobjlock);
947 
948                     /* Decrease the used-memory counter. */
949                     tmpfs_mem_decr(tmp, (oldpages - newpages) << PAGE_SHIFT);
950           }
951           return 0;
952 }
953 
954 /*
955  * tmpfs_chflags: change flags of the given vnode.
956  */
957 int
tmpfs_chflags(vnode_t * vp,int flags,kauth_cred_t cred,lwp_t * l)958 tmpfs_chflags(vnode_t *vp, int flags, kauth_cred_t cred, lwp_t *l)
959 {
960           tmpfs_node_t *node = VP_TO_TMPFS_NODE(vp);
961           kauth_action_t action = KAUTH_VNODE_WRITE_FLAGS;
962           int error;
963           bool changing_sysflags = false;
964 
965           KASSERT(VOP_ISLOCKED(vp));
966 
967           /* Disallow this operation if the file system is mounted read-only. */
968           if (vp->v_mount->mnt_flag & MNT_RDONLY)
969                     return EROFS;
970 
971           /*
972            * If the new flags have non-user flags that are different than
973            * those on the node, we need special permission to change them.
974            */
975           if ((flags & SF_SETTABLE) != (node->tn_flags & SF_SETTABLE)) {
976                     action |= KAUTH_VNODE_WRITE_SYSFLAGS;
977                     changing_sysflags = true;
978           }
979 
980           /*
981            * Indicate that this node's flags have system attributes in them if
982            * that's the case.
983            */
984           if (node->tn_flags & (SF_IMMUTABLE | SF_APPEND)) {
985                     action |= KAUTH_VNODE_HAS_SYSFLAGS;
986           }
987 
988           error = kauth_authorize_vnode(cred, action, vp, NULL,
989               genfs_can_chflags(vp, cred, node->tn_uid, changing_sysflags));
990           if (error)
991                     return error;
992 
993           /*
994            * Set the flags. If we're not setting non-user flags, be careful not
995            * to overwrite them.
996            *
997            * XXX: Can't we always assign here? if the system flags are different,
998            *      the code above should catch attempts to change them without
999            *      proper permissions, and if we're here it means it's okay to
1000            *      change them...
1001            */
1002           if (!changing_sysflags) {
1003                     /* Clear all user-settable flags and re-set them. */
1004                     node->tn_flags &= SF_SETTABLE;
1005                     node->tn_flags |= (flags & UF_SETTABLE);
1006           } else {
1007                     node->tn_flags = flags;
1008           }
1009           tmpfs_update(vp, TMPFS_UPDATE_CTIME);
1010           return 0;
1011 }
1012 
1013 /*
1014  * tmpfs_chmod: change access mode on the given vnode.
1015  */
1016 int
tmpfs_chmod(vnode_t * vp,mode_t mode,kauth_cred_t cred,lwp_t * l)1017 tmpfs_chmod(vnode_t *vp, mode_t mode, kauth_cred_t cred, lwp_t *l)
1018 {
1019           tmpfs_node_t *node = VP_TO_TMPFS_NODE(vp);
1020           int error;
1021 
1022           KASSERT(VOP_ISLOCKED(vp));
1023 
1024           /* Disallow this operation if the file system is mounted read-only. */
1025           if (vp->v_mount->mnt_flag & MNT_RDONLY)
1026                     return EROFS;
1027 
1028           /* Immutable or append-only files cannot be modified, either. */
1029           if (node->tn_flags & (IMMUTABLE | APPEND))
1030                     return EPERM;
1031 
1032           error = kauth_authorize_vnode(cred, KAUTH_VNODE_WRITE_SECURITY, vp,
1033               NULL, genfs_can_chmod(vp, cred, node->tn_uid, node->tn_gid, mode));
1034           if (error) {
1035                     return error;
1036           }
1037           node->tn_mode = (mode & ALLPERMS);
1038           tmpfs_update(vp, TMPFS_UPDATE_CTIME);
1039           cache_enter_id(vp, node->tn_mode, node->tn_uid, node->tn_gid, true);
1040           return 0;
1041 }
1042 
1043 /*
1044  * tmpfs_chown: change ownership of the given vnode.
1045  *
1046  * => At least one of uid or gid must be different than VNOVAL.
1047  * => Attribute is unchanged for VNOVAL case.
1048  */
1049 int
tmpfs_chown(vnode_t * vp,uid_t uid,gid_t gid,kauth_cred_t cred,lwp_t * l)1050 tmpfs_chown(vnode_t *vp, uid_t uid, gid_t gid, kauth_cred_t cred, lwp_t *l)
1051 {
1052           tmpfs_node_t *node = VP_TO_TMPFS_NODE(vp);
1053           int error;
1054 
1055           KASSERT(VOP_ISLOCKED(vp));
1056 
1057           /* Assign default values if they are unknown. */
1058           KASSERT(uid != VNOVAL || gid != VNOVAL);
1059           if (uid == VNOVAL) {
1060                     uid = node->tn_uid;
1061           }
1062           if (gid == VNOVAL) {
1063                     gid = node->tn_gid;
1064           }
1065 
1066           /* Disallow this operation if the file system is mounted read-only. */
1067           if (vp->v_mount->mnt_flag & MNT_RDONLY)
1068                     return EROFS;
1069 
1070           /* Immutable or append-only files cannot be modified, either. */
1071           if (node->tn_flags & (IMMUTABLE | APPEND))
1072                     return EPERM;
1073 
1074           error = kauth_authorize_vnode(cred, KAUTH_VNODE_CHANGE_OWNERSHIP, vp,
1075               NULL, genfs_can_chown(vp, cred, node->tn_uid, node->tn_gid, uid,
1076               gid));
1077           if (error) {
1078                     return error;
1079           }
1080           node->tn_uid = uid;
1081           node->tn_gid = gid;
1082           tmpfs_update(vp, TMPFS_UPDATE_CTIME);
1083           cache_enter_id(vp, node->tn_mode, node->tn_uid, node->tn_gid, true);
1084           return 0;
1085 }
1086 
1087 /*
1088  * tmpfs_chsize: change size of the given vnode.
1089  */
1090 int
tmpfs_chsize(vnode_t * vp,u_quad_t size,kauth_cred_t cred,lwp_t * l)1091 tmpfs_chsize(vnode_t *vp, u_quad_t size, kauth_cred_t cred, lwp_t *l)
1092 {
1093           tmpfs_node_t *node = VP_TO_TMPFS_NODE(vp);
1094           const off_t length = size;
1095           int error;
1096 
1097           KASSERT(VOP_ISLOCKED(vp));
1098 
1099           /* Decide whether this is a valid operation based on the file type. */
1100           switch (vp->v_type) {
1101           case VDIR:
1102                     return EISDIR;
1103           case VREG:
1104                     if (vp->v_mount->mnt_flag & MNT_RDONLY) {
1105                               return EROFS;
1106                     }
1107                     break;
1108           case VBLK:
1109           case VCHR:
1110           case VFIFO:
1111                     /*
1112                      * Allow modifications of special files even if in the file
1113                      * system is mounted read-only (we are not modifying the
1114                      * files themselves, but the objects they represent).
1115                      */
1116                     return 0;
1117           default:
1118                     return EOPNOTSUPP;
1119           }
1120 
1121           /* Immutable or append-only files cannot be modified, either. */
1122           if (node->tn_flags & (IMMUTABLE | APPEND)) {
1123                     return EPERM;
1124           }
1125 
1126           if (length < 0) {
1127                     return EINVAL;
1128           }
1129 
1130           /* Note: tmpfs_reg_resize() will raise NOTE_EXTEND and NOTE_ATTRIB. */
1131           if (node->tn_size != length &&
1132               (error = tmpfs_reg_resize(vp, length)) != 0) {
1133                     return error;
1134           }
1135           tmpfs_update(vp, TMPFS_UPDATE_CTIME | TMPFS_UPDATE_MTIME);
1136           return 0;
1137 }
1138 
1139 /*
1140  * tmpfs_chtimes: change access and modification times for vnode.
1141  */
1142 int
tmpfs_chtimes(vnode_t * vp,const struct timespec * atime,const struct timespec * mtime,const struct timespec * btime,int vaflags,kauth_cred_t cred,lwp_t * l)1143 tmpfs_chtimes(vnode_t *vp, const struct timespec *atime,
1144     const struct timespec *mtime, const struct timespec *btime,
1145     int vaflags, kauth_cred_t cred, lwp_t *l)
1146 {
1147           tmpfs_node_t *node = VP_TO_TMPFS_NODE(vp);
1148           int error;
1149 
1150           KASSERT(VOP_ISLOCKED(vp));
1151 
1152           /* Disallow this operation if the file system is mounted read-only. */
1153           if (vp->v_mount->mnt_flag & MNT_RDONLY)
1154                     return EROFS;
1155 
1156           /* Immutable or append-only files cannot be modified, either. */
1157           if (node->tn_flags & (IMMUTABLE | APPEND))
1158                     return EPERM;
1159 
1160           error = kauth_authorize_vnode(cred, KAUTH_VNODE_WRITE_TIMES, vp, NULL,
1161               genfs_can_chtimes(vp, cred, node->tn_uid, vaflags));
1162           if (error)
1163                     return error;
1164 
1165           mutex_enter(&node->tn_timelock);
1166           if (atime->tv_sec != VNOVAL) {
1167                     atomic_and_uint(&node->tn_tflags, ~TMPFS_UPDATE_ATIME);
1168                     node->tn_atime = *atime;
1169           }
1170           if (mtime->tv_sec != VNOVAL) {
1171                     atomic_and_uint(&node->tn_tflags, ~TMPFS_UPDATE_MTIME);
1172                     node->tn_mtime = *mtime;
1173           }
1174           if (btime->tv_sec != VNOVAL) {
1175                     node->tn_birthtime = *btime;
1176           }
1177           mutex_exit(&node->tn_timelock);
1178           return 0;
1179 }
1180 
1181 /*
1182  * tmpfs_update_locked: update the timestamps as indicated by the flags.
1183  */
1184 void
tmpfs_update_locked(vnode_t * vp,unsigned tflags)1185 tmpfs_update_locked(vnode_t *vp, unsigned tflags)
1186 {
1187           tmpfs_node_t *node = VP_TO_TMPFS_NODE(vp);
1188           struct timespec nowtm;
1189 
1190           KASSERT(mutex_owned(&node->tn_timelock));
1191 
1192           if ((tflags |= atomic_swap_uint(&node->tn_tflags, 0)) == 0) {
1193                     return;
1194           }
1195           vfs_timestamp(&nowtm);
1196 
1197           if (tflags & TMPFS_UPDATE_ATIME) {
1198                     node->tn_atime = nowtm;
1199           }
1200           if (tflags & TMPFS_UPDATE_MTIME) {
1201                     node->tn_mtime = nowtm;
1202           }
1203           if (tflags & TMPFS_UPDATE_CTIME) {
1204                     node->tn_ctime = nowtm;
1205           }
1206 }
1207 
1208 /*
1209  * tmpfs_update: update the timestamps as indicated by the flags.
1210  */
1211 void
tmpfs_update(vnode_t * vp,unsigned tflags)1212 tmpfs_update(vnode_t *vp, unsigned tflags)
1213 {
1214           tmpfs_node_t *node = VP_TO_TMPFS_NODE(vp);
1215 
1216           if ((tflags | atomic_load_relaxed(&node->tn_tflags)) == 0) {
1217                     return;
1218           }
1219 
1220           mutex_enter(&node->tn_timelock);
1221           tmpfs_update_locked(vp, tflags);
1222           mutex_exit(&node->tn_timelock);
1223 }
1224 
1225 /*
1226  * tmpfs_update_lazily: schedule a deferred timestamp update.
1227  */
1228 void
tmpfs_update_lazily(vnode_t * vp,unsigned tflags)1229 tmpfs_update_lazily(vnode_t *vp, unsigned tflags)
1230 {
1231           tmpfs_node_t *node = VP_TO_TMPFS_NODE(vp);
1232           unsigned cur;
1233 
1234           cur = atomic_load_relaxed(&node->tn_tflags);
1235           if ((cur & tflags) != tflags) {
1236                     atomic_or_uint(&node->tn_tflags, tflags);
1237                     return;
1238           }
1239 }
1240