Annotation of src/sys/fs/tmpfs/tmpfs_subr.c, Revision 1.17
1.17 ! christos 1: /* $NetBSD: tmpfs_subr.c,v 1.16 2005/11/28 22:28:36 dan Exp $ */
1.1 jmmv 2:
3: /*
4: * Copyright (c) 2005 The NetBSD Foundation, Inc.
5: * All rights reserved.
6: *
7: * This code is derived from software contributed to The NetBSD Foundation
1.8 jmmv 8: * by Julio M. Merino Vidal, developed as part of Google's Summer of Code
9: * 2005 program.
1.1 jmmv 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: * 3. All advertising materials mentioning features or use of this software
20: * must display the following acknowledgement:
21: * This product includes software developed by the NetBSD
22: * Foundation, Inc. and its contributors.
23: * 4. Neither the name of The NetBSD Foundation nor the names of its
24: * contributors may be used to endorse or promote products derived
25: * from this software without specific prior written permission.
26: *
27: * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29: * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37: * POSSIBILITY OF SUCH DAMAGE.
38: */
39:
40: /*
1.2 jmmv 41: * Efficient memory file system supporting functions.
1.1 jmmv 42: */
43:
44: #include <sys/cdefs.h>
1.17 ! christos 45: __KERNEL_RCSID(0, "$NetBSD: tmpfs_subr.c,v 1.16 2005/11/28 22:28:36 dan Exp $");
1.1 jmmv 46:
47: #include <sys/param.h>
48: #include <sys/dirent.h>
49: #include <sys/event.h>
50: #include <sys/malloc.h>
51: #include <sys/mount.h>
52: #include <sys/namei.h>
53: #include <sys/time.h>
54: #include <sys/stat.h>
55: #include <sys/systm.h>
56: #include <sys/swap.h>
57: #include <sys/vnode.h>
58:
59: #include <uvm/uvm.h>
60:
61: #include <miscfs/specfs/specdev.h>
62: #include <fs/tmpfs/tmpfs.h>
63: #include <fs/tmpfs/tmpfs_fifoops.h>
64: #include <fs/tmpfs/tmpfs_specops.h>
65: #include <fs/tmpfs/tmpfs_vnops.h>
66:
67: /* --------------------------------------------------------------------- */
68:
1.8 jmmv 69: /*
70: * Allocates a new node of type 'type' inside the 'tmp' mount point, with
71: * its owner set to 'uid', its group to 'gid' and its mode set to 'mode',
72: * using the credentials of the process 'p'.
73: *
74: * If the node type is set to 'VDIR', then the parent parameter must point
75: * to the parent directory of the node being created. It may only be NULL
76: * while allocating the root node.
77: *
78: * If the node type is set to 'VBLK' or 'VCHR', then the rdev parameter
79: * specifies the device the node represents.
80: *
81: * If the node type is set to 'VLNK', then the parameter target specifies
82: * the file name of the target file for the symbolic link that is being
83: * created.
84: *
85: * Note that new nodes are retrieved from the available list if it has
86: * items or, if it is empty, from the node pool as long as there is enough
87: * space to create them.
88: *
89: * Returns zero on success or an appropriate error code on failure.
90: */
1.1 jmmv 91: int
92: tmpfs_alloc_node(struct tmpfs_mount *tmp, enum vtype type,
93: uid_t uid, gid_t gid, mode_t mode, struct tmpfs_node *parent,
94: char *target, dev_t rdev, struct proc *p, struct tmpfs_node **node)
95: {
96: struct tmpfs_node *nnode;
97:
1.2 jmmv 98: /* If the root directory of the 'tmp' file system is not yet
1.1 jmmv 99: * allocated, this must be the request to do it. */
100: KASSERT(IMPLIES(tmp->tm_root == NULL, parent == NULL && type == VDIR));
101:
102: KASSERT(IFF(type == VLNK, target != NULL));
103: KASSERT(IFF(type == VBLK || type == VCHR, rdev != VNOVAL));
104:
105: KASSERT(uid != VNOVAL && gid != VNOVAL && mode != VNOVAL);
106:
107: nnode = NULL;
108: if (LIST_EMPTY(&tmp->tm_nodes_avail)) {
109: KASSERT(tmp->tm_nodes_last <= tmp->tm_nodes_max);
110: if (tmp->tm_nodes_last == tmp->tm_nodes_max)
111: return ENOSPC;
112:
113: nnode =
114: (struct tmpfs_node *)TMPFS_POOL_GET(&tmp->tm_node_pool, 0);
115: if (nnode == NULL)
116: return ENOSPC;
117: nnode->tn_id = tmp->tm_nodes_last++;
118: nnode->tn_gen = 0;
119: } else {
120: nnode = LIST_FIRST(&tmp->tm_nodes_avail);
121: LIST_REMOVE(nnode, tn_entries);
122: nnode->tn_gen++;
123: }
124: KASSERT(nnode != NULL);
125: LIST_INSERT_HEAD(&tmp->tm_nodes_used, nnode, tn_entries);
126:
127: /* Generic initialization. */
128: nnode->tn_type = type;
129: nnode->tn_size = 0;
130: nnode->tn_status = 0;
131: nnode->tn_flags = 0;
132: nnode->tn_links = 0;
1.3 christos 133: (void)nanotime(&nnode->tn_atime);
1.1 jmmv 134: nnode->tn_birthtime = nnode->tn_ctime = nnode->tn_mtime =
135: nnode->tn_atime;
136: nnode->tn_uid = uid;
137: nnode->tn_gid = gid;
138: nnode->tn_mode = mode;
1.11 jmmv 139: nnode->tn_lockf = NULL;
1.1 jmmv 140: nnode->tn_vnode = NULL;
141:
142: /* Type-specific initialization. */
143: switch (nnode->tn_type) {
144: case VBLK:
145: case VCHR:
146: nnode->tn_rdev = rdev;
147: break;
148:
149: case VDIR:
150: TAILQ_INIT(&nnode->tn_dir);
151: nnode->tn_parent = (parent == NULL) ? nnode : parent;
152: nnode->tn_readdir_lastn = 0;
153: nnode->tn_readdir_lastp = NULL;
154: nnode->tn_links++;
155: nnode->tn_parent->tn_links++;
156: break;
157:
158: case VFIFO:
159: /* FALLTHROUGH */
160: case VSOCK:
161: break;
162:
163: case VLNK:
164: KASSERT(strlen(target) < MAXPATHLEN);
1.7 yamt 165: nnode->tn_size = strlen(target);
1.1 jmmv 166: nnode->tn_link = tmpfs_str_pool_get(&tmp->tm_str_pool,
1.7 yamt 167: nnode->tn_size, 0);
1.1 jmmv 168: if (nnode->tn_link == NULL) {
169: nnode->tn_type = VNON;
170: tmpfs_free_node(tmp, nnode);
171: return ENOSPC;
172: }
1.7 yamt 173: memcpy(nnode->tn_link, target, nnode->tn_size);
1.1 jmmv 174: break;
175:
176: case VREG:
1.4 yamt 177: nnode->tn_aobj = uao_create(INT32_MAX - PAGE_SIZE, 0);
1.1 jmmv 178: nnode->tn_aobj_pages = 0;
179: break;
180:
181: default:
182: KASSERT(0);
183: }
184:
185: *node = nnode;
186: return 0;
187: }
188:
189: /* --------------------------------------------------------------------- */
190:
1.8 jmmv 191: /*
192: * Destroys the node pointed to by node from the file system 'tmp'.
193: * If the node does not belong to the given mount point, the results are
194: * unpredicted.
195: *
196: * If the node references a directory; no entries are allowed because
197: * their removal could need a recursive algorithm, something forbidden in
198: * kernel space. Furthermore, there is not need to provide such
199: * functionality (recursive removal) because the only primitives offered
200: * to the user are the removal of empty directories and the deletion of
201: * individual files.
202: *
203: * Note that nodes are not really deleted; in fact, when a node has been
204: * allocated, it cannot be deleted during the whole life of the file
205: * system. Instead, they are moved to the available list and remain there
206: * until reused.
207: */
1.1 jmmv 208: void
209: tmpfs_free_node(struct tmpfs_mount *tmp, struct tmpfs_node *node)
210: {
211: ino_t id;
212: unsigned long gen;
213: size_t pages;
214:
215: switch (node->tn_type) {
216: case VNON:
217: /* Do not do anything. VNON is provided to let the
218: * allocation routine clean itself easily by avoiding
219: * duplicating code in it. */
220: /* FALLTHROUGH */
221: case VBLK:
222: /* FALLTHROUGH */
223: case VCHR:
224: /* FALLTHROUGH */
225: case VDIR:
226: /* FALLTHROUGH */
227: case VFIFO:
228: /* FALLTHROUGH */
229: case VSOCK:
230: pages = 0;
231: break;
232:
233: case VLNK:
234: tmpfs_str_pool_put(&tmp->tm_str_pool, node->tn_link,
1.7 yamt 235: node->tn_size);
1.1 jmmv 236: pages = 0;
237: break;
238:
239: case VREG:
240: if (node->tn_aobj != NULL)
241: uao_detach(node->tn_aobj);
242: pages = node->tn_aobj_pages;
243: break;
244:
245: default:
246: KASSERT(0);
247: pages = 0; /* Shut up gcc when !DIAGNOSTIC. */
248: break;
249: }
250:
251: tmp->tm_pages_used -= pages;
252:
253: LIST_REMOVE(node, tn_entries);
254: id = node->tn_id;
255: gen = node->tn_gen;
256: memset(node, 0, sizeof(struct tmpfs_node));
257: node->tn_id = id;
258: node->tn_type = VNON;
259: node->tn_gen = gen;
260: LIST_INSERT_HEAD(&tmp->tm_nodes_avail, node, tn_entries);
261: }
262:
263: /* --------------------------------------------------------------------- */
264:
1.8 jmmv 265: /*
266: * Allocates a new directory entry for the node node with a name of name.
267: * The new directory entry is returned in *de.
268: *
269: * The link count of node is increased by one to reflect the new object
270: * referencing it.
271: *
272: * Returns zero on success or an appropriate error code on failure.
273: */
1.1 jmmv 274: int
275: tmpfs_alloc_dirent(struct tmpfs_mount *tmp, struct tmpfs_node *node,
276: const char *name, uint16_t len, struct tmpfs_dirent **de)
277: {
278: struct tmpfs_dirent *nde;
279:
280: nde = (struct tmpfs_dirent *)TMPFS_POOL_GET(&tmp->tm_dirent_pool, 0);
281: if (nde == NULL)
282: return ENOSPC;
283:
284: nde->td_name = tmpfs_str_pool_get(&tmp->tm_str_pool, len, 0);
285: if (nde->td_name == NULL) {
286: TMPFS_POOL_PUT(&tmp->tm_dirent_pool, nde);
287: return ENOSPC;
288: }
289: nde->td_namelen = len;
290: memcpy(nde->td_name, name, len);
291: nde->td_node = node;
292:
293: node->tn_links++;
294: *de = nde;
295:
296: return 0;
297: }
298:
299: /* --------------------------------------------------------------------- */
300:
1.8 jmmv 301: /*
302: * Frees a directory entry. It is the caller's responsibility to destroy
303: * the node referenced by it if needed.
304: *
305: * The link count of node is decreased by one to reflect the removal of an
306: * object that referenced it. This only happens if 'node_exists' is true;
307: * otherwise the function will not access the node referred to by the
308: * directory entry, as it may already have been released from the outside.
309: */
1.1 jmmv 310: void
311: tmpfs_free_dirent(struct tmpfs_mount *tmp, struct tmpfs_dirent *de,
312: boolean_t node_exists)
313: {
314: if (node_exists) {
315: struct tmpfs_node *node;
316:
317: node = de->td_node;
318:
319: KASSERT(node->tn_links > 0);
320: node->tn_links--;
321: }
322:
323: tmpfs_str_pool_put(&tmp->tm_str_pool, de->td_name, de->td_namelen);
324: TMPFS_POOL_PUT(&tmp->tm_dirent_pool, de);
325: }
326:
327: /* --------------------------------------------------------------------- */
328:
1.8 jmmv 329: /*
330: * Allocates a new vnode for the node node or returns a new reference to
331: * an existing one if the node had already a vnode referencing it. The
332: * resulting locked vnode is returned in *vpp.
333: *
334: * Returns zero on success or an appropriate error code on failure.
335: */
1.1 jmmv 336: int
337: tmpfs_alloc_vp(struct mount *mp, struct tmpfs_node *node, struct vnode **vpp)
338: {
339: int error;
340: struct vnode *nvp;
341: struct vnode *vp;
342:
343: vp = NULL;
344:
345: if (node->tn_vnode != NULL) {
346: vp = node->tn_vnode;
347: vget(vp, LK_EXCLUSIVE | LK_RETRY);
348: error = 0;
349: goto out;
350: }
351:
352: /* Get a new vnode and associate it with our node. */
353: error = getnewvnode(VT_TMPFS, mp, tmpfs_vnodeop_p, &vp);
354: if (error != 0)
355: goto out;
356: KASSERT(vp != NULL);
357:
358: error = vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
359: if (error != 0) {
360: vp->v_data = NULL;
361: ungetnewvnode(vp);
362: vp = NULL;
363: goto out;
364: }
365:
366: vp->v_data = node;
367: vp->v_type = node->tn_type;
368:
369: /* Type-specific initialization. */
370: switch (node->tn_type) {
371: case VBLK:
372: /* FALLTHROUGH */
373: case VCHR:
374: vp->v_op = tmpfs_specop_p;
375: nvp = checkalias(vp, node->tn_rdev, mp);
376: if (nvp != NULL) {
377: /* Discard unneeded vnode, but save its inode. */
378: nvp->v_data = vp->v_data;
379: vp->v_data = NULL;
380:
381: /* XXX spec_vnodeops has no locking, so we have to
382: * do it explicitly. */
383: VOP_UNLOCK(vp, 0);
384: vp->v_op = spec_vnodeop_p;
385: vp->v_flag &= ~VLOCKSWORK;
386: vrele(vp);
387: vgone(vp);
388:
389: /* Reinitialize aliased node. */
390: vp = nvp;
391: error = vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
392: if (error != 0) {
393: vp->v_data = NULL;
394: vp = NULL;
395: goto out;
396: }
397: }
398: break;
399:
400: case VDIR:
401: vp->v_flag = node->tn_parent == node ? VROOT : 0;
402: break;
403:
404: case VFIFO:
405: vp->v_op = tmpfs_fifoop_p;
406: break;
407:
408: case VLNK:
409: /* FALLTHROUGH */
410: case VREG:
411: /* FALLTHROUGH */
412: case VSOCK:
413: break;
414:
415: default:
416: KASSERT(0);
417: }
418:
419: uvm_vnp_setsize(vp, node->tn_size);
420:
421: error = 0;
422:
423: out:
424: *vpp = node->tn_vnode = vp;
425:
426: KASSERT(IFF(error == 0, *vpp != NULL && VOP_ISLOCKED(*vpp)));
427: KASSERT(*vpp == node->tn_vnode);
428:
429: return error;
430: }
431:
432: /* --------------------------------------------------------------------- */
433:
1.8 jmmv 434: /*
435: * Destroys the association between the vnode vp and the node it
436: * references.
437: */
1.1 jmmv 438: void
439: tmpfs_free_vp(struct vnode *vp)
440: {
441: struct tmpfs_node *node;
442:
443: node = VP_TO_TMPFS_NODE(vp);
444:
445: node->tn_vnode = NULL;
446: vp->v_data = NULL;
447: }
448:
449: /* --------------------------------------------------------------------- */
450:
1.9 jmmv 451: /*
452: * Allocates a new file of type 'type' and adds it to the parent directory
1.1 jmmv 453: * 'dvp'; this addition is done using the component name given in 'cnp'.
454: * The ownership of the new file is automatically assigned based on the
455: * credentials of the caller (through 'cnp'), the group is set based on
456: * the parent directory and the mode is determined from the 'vap' argument.
457: * If successful, *vpp holds a vnode to the newly created file and zero
458: * is returned. Otherwise *vpp is NULL and the function returns an
1.9 jmmv 459: * appropriate error code.
460: */
1.1 jmmv 461: int
462: tmpfs_alloc_file(struct vnode *dvp, struct vnode **vpp, struct vattr *vap,
463: struct componentname *cnp, char *target)
464: {
465: int error;
466: struct tmpfs_dirent *de;
467: struct tmpfs_mount *tmp;
468: struct tmpfs_node *dnode;
469: struct tmpfs_node *node;
470: struct tmpfs_node *parent;
471:
472: KASSERT(VOP_ISLOCKED(dvp));
473: KASSERT(cnp->cn_flags & HASBUF);
474:
475: tmp = VFS_TO_TMPFS(dvp->v_mount);
476: dnode = VP_TO_TMPFS_DIR(dvp);
477: *vpp = NULL;
478:
479: /* If the entry we are creating is a directory, we cannot overflow
480: * the number of links of its parent, because it will get a new
481: * link. */
482: if (vap->va_type == VDIR) {
483: /* Ensure that we do not overflow the maximum number of links
484: * imposed by the system. */
485: KASSERT(dnode->tn_links <= LINK_MAX);
486: if (dnode->tn_links == LINK_MAX) {
487: error = EMLINK;
488: goto out;
489: }
490:
491: parent = dnode;
492: } else
493: parent = NULL;
494:
495: /* Allocate a node that represents the new file. */
496: error = tmpfs_alloc_node(tmp, vap->va_type, cnp->cn_cred->cr_uid,
497: dnode->tn_gid, vap->va_mode, parent, target, vap->va_rdev,
1.17 ! christos 498: cnp->cn_lwp->l_proc, &node);
1.1 jmmv 499: if (error != 0)
500: goto out;
501:
502: /* Allocate a directory entry that points to the new file. */
503: error = tmpfs_alloc_dirent(tmp, node, cnp->cn_nameptr, cnp->cn_namelen,
504: &de);
505: if (error != 0) {
506: tmpfs_free_node(tmp, node);
507: goto out;
508: }
509:
510: /* Allocate a vnode for the new file. */
511: error = tmpfs_alloc_vp(dvp->v_mount, node, vpp);
512: if (error != 0) {
513: tmpfs_free_dirent(tmp, de, TRUE);
514: tmpfs_free_node(tmp, node);
515: goto out;
516: }
517:
518: /* Now that all required items are allocated, we can proceed to
519: * insert the new node into the directory, an operation that
520: * cannot fail. */
521: tmpfs_dir_attach(dvp, de);
522: VN_KNOTE(dvp, NOTE_WRITE);
523:
524: out:
525: if (error != 0 || !(cnp->cn_flags & SAVESTART))
526: PNBUF_PUT(cnp->cn_pnbuf);
527: vput(dvp);
528:
529: KASSERT(!VOP_ISLOCKED(dvp));
530: KASSERT(IFF(error == 0, *vpp != NULL));
531:
532: return error;
533: }
534:
535: /* --------------------------------------------------------------------- */
536:
1.8 jmmv 537: /*
538: * Attaches the directory entry de to the directory represented by vp.
539: * Note that this does not change the link count of the node pointed by
540: * the directory entry, as this is done by tmpfs_alloc_dirent.
541: */
1.1 jmmv 542: void
543: tmpfs_dir_attach(struct vnode *vp, struct tmpfs_dirent *de)
544: {
545: struct tmpfs_node *dnode;
546:
547: dnode = VP_TO_TMPFS_DIR(vp);
548:
549: TAILQ_INSERT_TAIL(&dnode->tn_dir, de, td_entries);
550: dnode->tn_size += sizeof(struct tmpfs_dirent);
551: dnode->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \
552: TMPFS_NODE_MODIFIED;
553: uvm_vnp_setsize(vp, dnode->tn_size);
554: }
555:
556: /* --------------------------------------------------------------------- */
557:
1.8 jmmv 558: /*
559: * Detaches the directory entry de from the directory represented by vp.
560: * Note that this does not change the link count of the node pointed by
561: * the directory entry, as this is done by tmpfs_free_dirent.
562: */
1.1 jmmv 563: void
564: tmpfs_dir_detach(struct vnode *vp, struct tmpfs_dirent *de)
565: {
566: struct tmpfs_node *dnode;
567:
1.5 yamt 568: KASSERT(VOP_ISLOCKED(vp));
569:
1.1 jmmv 570: dnode = VP_TO_TMPFS_DIR(vp);
571:
1.5 yamt 572: if (dnode->tn_readdir_lastp == de) {
573: dnode->tn_readdir_lastn = 0;
574: dnode->tn_readdir_lastp = NULL;
575: }
576:
1.1 jmmv 577: TAILQ_REMOVE(&dnode->tn_dir, de, td_entries);
578: dnode->tn_size -= sizeof(struct tmpfs_dirent);
579: dnode->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \
580: TMPFS_NODE_MODIFIED;
581: uvm_vnp_setsize(vp, dnode->tn_size);
582: }
583:
584: /* --------------------------------------------------------------------- */
585:
1.8 jmmv 586: /*
587: * Looks for a directory entry in the directory represented by node.
588: * 'cnp' describes the name of the entry to look for. Note that the .
589: * and .. components are not allowed as they do not physically exist
590: * within directories.
591: *
592: * Returns a pointer to the entry when found, otherwise NULL.
593: */
1.1 jmmv 594: struct tmpfs_dirent *
595: tmpfs_dir_lookup(struct tmpfs_node *node, struct componentname *cnp)
596: {
597: boolean_t found;
598: struct tmpfs_dirent *de;
599:
600: KASSERT(IMPLIES(cnp->cn_namelen == 1, cnp->cn_nameptr[0] != '.'));
601: KASSERT(IMPLIES(cnp->cn_namelen == 2, !(cnp->cn_nameptr[0] == '.' &&
602: cnp->cn_nameptr[1] == '.')));
603: TMPFS_VALIDATE_DIR(node);
604:
605: node->tn_status |= TMPFS_NODE_ACCESSED;
606:
607: found = 0;
608: TAILQ_FOREACH(de, &node->tn_dir, td_entries) {
609: KASSERT(cnp->cn_namelen < 0xffff);
610: if (de->td_namelen == (uint16_t)cnp->cn_namelen &&
611: memcmp(de->td_name, cnp->cn_nameptr, de->td_namelen) == 0) {
612: found = 1;
613: break;
614: }
615: }
616:
617: return found ? de : NULL;
618: }
619:
620: /* --------------------------------------------------------------------- */
621:
1.9 jmmv 622: /*
623: * Helper function for tmpfs_readdir. Creates a '.' entry for the given
1.1 jmmv 624: * directory and returns it in the uio space. The function returns 0
625: * on success, -1 if there was not enough space in the uio structure to
626: * hold the directory entry or an appropriate error code if another
1.9 jmmv 627: * error happens.
628: */
1.1 jmmv 629: int
630: tmpfs_dir_getdotdent(struct tmpfs_node *node, struct uio *uio)
631: {
632: int error;
633: struct dirent dent;
634:
635: TMPFS_VALIDATE_DIR(node);
1.5 yamt 636: KASSERT(uio->uio_offset == TMPFS_DIRCOOKIE_DOT);
1.1 jmmv 637:
638: dent.d_fileno = node->tn_id;
639: dent.d_type = DT_DIR;
640: dent.d_namlen = 1;
641: dent.d_name[0] = '.';
642: dent.d_name[1] = '\0';
643: dent.d_reclen = _DIRENT_SIZE(&dent);
644:
645: if (dent.d_reclen > uio->uio_resid)
646: error = -1;
647: else {
648: error = uiomove(&dent, dent.d_reclen, uio);
649: if (error == 0)
1.5 yamt 650: uio->uio_offset = TMPFS_DIRCOOKIE_DOTDOT;
1.1 jmmv 651: }
652:
653: node->tn_status |= TMPFS_NODE_ACCESSED;
654:
655: return error;
656: }
657:
658: /* --------------------------------------------------------------------- */
659:
1.9 jmmv 660: /*
661: * Helper function for tmpfs_readdir. Creates a '..' entry for the given
1.1 jmmv 662: * directory and returns it in the uio space. The function returns 0
663: * on success, -1 if there was not enough space in the uio structure to
664: * hold the directory entry or an appropriate error code if another
1.9 jmmv 665: * error happens.
666: */
1.1 jmmv 667: int
668: tmpfs_dir_getdotdotdent(struct tmpfs_node *node, struct uio *uio)
669: {
670: int error;
671: struct dirent dent;
672:
673: TMPFS_VALIDATE_DIR(node);
1.5 yamt 674: KASSERT(uio->uio_offset == TMPFS_DIRCOOKIE_DOTDOT);
1.1 jmmv 675:
1.6 yamt 676: dent.d_fileno = node->tn_parent->tn_id;
1.1 jmmv 677: dent.d_type = DT_DIR;
678: dent.d_namlen = 2;
679: dent.d_name[0] = '.';
680: dent.d_name[1] = '.';
681: dent.d_name[2] = '\0';
682: dent.d_reclen = _DIRENT_SIZE(&dent);
683:
684: if (dent.d_reclen > uio->uio_resid)
685: error = -1;
686: else {
687: error = uiomove(&dent, dent.d_reclen, uio);
1.5 yamt 688: if (error == 0) {
689: struct tmpfs_dirent *de;
690:
691: de = TAILQ_FIRST(&node->tn_dir);
692: if (de == NULL)
693: uio->uio_offset = TMPFS_DIRCOOKIE_EOF;
694: else
695: uio->uio_offset = TMPFS_DIRCOOKIE(de);
696: }
1.1 jmmv 697: }
698:
699: node->tn_status |= TMPFS_NODE_ACCESSED;
700:
701: return error;
702: }
703:
704: /* --------------------------------------------------------------------- */
705:
1.9 jmmv 706: /*
707: * Lookup a directory entry by its associated cookie.
708: */
1.5 yamt 709: struct tmpfs_dirent *
710: tmpfs_dir_lookupbycookie(struct tmpfs_node *node, off_t cookie)
711: {
712: struct tmpfs_dirent *de;
713:
714: if (cookie == node->tn_readdir_lastn &&
715: node->tn_readdir_lastp != NULL) {
716: return node->tn_readdir_lastp;
717: }
718:
719: TAILQ_FOREACH(de, &node->tn_dir, td_entries) {
720: if (TMPFS_DIRCOOKIE(de) == cookie) {
721: break;
722: }
723: }
724:
725: return de;
726: }
727:
728: /* --------------------------------------------------------------------- */
729:
1.9 jmmv 730: /*
731: * Helper function for tmpfs_readdir. Returns as much directory entries
1.1 jmmv 732: * as can fit in the uio space. The read starts at uio->uio_offset.
733: * The function returns 0 on success, -1 if there was not enough space
734: * in the uio structure to hold the directory entry or an appropriate
1.9 jmmv 735: * error code if another error happens.
736: */
1.1 jmmv 737: int
1.5 yamt 738: tmpfs_dir_getdents(struct tmpfs_node *node, struct uio *uio, off_t *cntp)
1.1 jmmv 739: {
740: int error;
1.5 yamt 741: off_t startcookie;
1.1 jmmv 742: struct tmpfs_dirent *de;
743:
744: TMPFS_VALIDATE_DIR(node);
745:
746: /* Locate the first directory entry we have to return. We have cached
747: * the last readdir in the node, so use those values if appropriate.
748: * Otherwise do a linear scan to find the requested entry. */
1.5 yamt 749: startcookie = uio->uio_offset;
750: KASSERT(startcookie != TMPFS_DIRCOOKIE_DOT);
751: KASSERT(startcookie != TMPFS_DIRCOOKIE_DOTDOT);
752: if (startcookie == TMPFS_DIRCOOKIE_EOF) {
753: return 0;
1.1 jmmv 754: } else {
1.5 yamt 755: de = tmpfs_dir_lookupbycookie(node, startcookie);
756: }
757: if (de == NULL) {
758: return EINVAL;
1.1 jmmv 759: }
760:
761: /* Read as much entries as possible; i.e., until we reach the end of
762: * the directory or we exhaust uio space. */
763: do {
764: struct dirent d;
765:
766: /* Create a dirent structure representing the current
767: * tmpfs_node and fill it. */
768: d.d_fileno = de->td_node->tn_id;
769: switch (de->td_node->tn_type) {
770: case VBLK:
771: d.d_type = DT_BLK;
772: break;
773:
774: case VCHR:
775: d.d_type = DT_CHR;
776: break;
777:
778: case VDIR:
779: d.d_type = DT_DIR;
780: break;
781:
782: case VFIFO:
783: d.d_type = DT_FIFO;
784: break;
785:
786: case VLNK:
787: d.d_type = DT_LNK;
788: break;
789:
790: case VREG:
791: d.d_type = DT_REG;
792: break;
793:
794: case VSOCK:
795: d.d_type = DT_SOCK;
796: break;
797:
798: default:
799: KASSERT(0);
800: }
801: d.d_namlen = de->td_namelen;
802: KASSERT(de->td_namelen < sizeof(d.d_name));
803: (void)memcpy(d.d_name, de->td_name, de->td_namelen);
804: d.d_name[de->td_namelen] = '\0';
805: d.d_reclen = _DIRENT_SIZE(&d);
806:
807: /* Stop reading if the directory entry we are treating is
808: * bigger than the amount of data that can be returned. */
809: if (d.d_reclen > uio->uio_resid) {
810: error = -1;
811: break;
812: }
813:
814: /* Copy the new dirent structure into the output buffer and
815: * advance pointers. */
816: error = uiomove(&d, d.d_reclen, uio);
817:
1.5 yamt 818: (*cntp)++;
1.1 jmmv 819: de = TAILQ_NEXT(de, td_entries);
820: } while (error == 0 && uio->uio_resid > 0 && de != NULL);
821:
1.5 yamt 822: /* Update the offset and cache. */
1.1 jmmv 823: if (de == NULL) {
1.5 yamt 824: uio->uio_offset = TMPFS_DIRCOOKIE_EOF;
1.1 jmmv 825: node->tn_readdir_lastn = 0;
826: node->tn_readdir_lastp = NULL;
827: } else {
1.5 yamt 828: node->tn_readdir_lastn = uio->uio_offset = TMPFS_DIRCOOKIE(de);
1.1 jmmv 829: node->tn_readdir_lastp = de;
830: }
831:
832: node->tn_status |= TMPFS_NODE_ACCESSED;
833:
834: return error;
835: }
836:
837: /* --------------------------------------------------------------------- */
838:
1.8 jmmv 839: /*
840: * Resizes the aobj associated to the regular file pointed to by vp to
841: * the size newsize. 'vp' must point to a vnode that represents a regular
842: * file. 'newsize' must be positive.
843: *
844: * Returns zero on success or an appropriate error code on failure.
845: */
1.1 jmmv 846: int
847: tmpfs_reg_resize(struct vnode *vp, off_t newsize)
848: {
849: int error;
850: size_t newpages, oldpages;
851: struct tmpfs_mount *tmp;
852: struct tmpfs_node *node;
1.13 yamt 853: off_t oldsize;
1.1 jmmv 854:
855: KASSERT(vp->v_type == VREG);
856: KASSERT(newsize >= 0);
857:
858: node = VP_TO_TMPFS_NODE(vp);
859: tmp = VFS_TO_TMPFS(vp->v_mount);
860:
861: /* Convert the old and new sizes to the number of pages needed to
862: * store them. It may happen that we do not need to do anything
863: * because the last allocated page can accommodate the change on
864: * its own. */
1.13 yamt 865: oldsize = node->tn_size;
866: oldpages = round_page(oldsize) / PAGE_SIZE;
1.1 jmmv 867: KASSERT(oldpages == node->tn_aobj_pages);
868: newpages = round_page(newsize) / PAGE_SIZE;
869:
870: if (newpages > oldpages &&
871: newpages - oldpages > TMPFS_PAGES_AVAIL(tmp)) {
872: error = ENOSPC;
873: goto out;
874: }
875:
1.4 yamt 876: node->tn_aobj_pages = newpages;
1.1 jmmv 877:
878: tmp->tm_pages_used += (newpages - oldpages);
879: node->tn_size = newsize;
880: uvm_vnp_setsize(vp, newsize);
1.13 yamt 881: if (newsize < oldsize) {
882: int zerolen = MIN(round_page(newsize), node->tn_size) - newsize;
883:
884: /*
885: * free "backing store"
886: */
887:
888: if (newpages < oldpages) {
889: struct uvm_object *uobj = node->tn_aobj;
890:
891: simple_lock(&uobj->vmobjlock);
892: uao_dropswap_range(uobj, newpages, oldpages);
893: simple_unlock(&uobj->vmobjlock);
894: }
895:
896: /*
897: * zero out the truncated part of the last page.
898: */
899:
900: uvm_vnp_zerorange(vp, newsize, zerolen);
901: }
1.1 jmmv 902:
903: error = 0;
904:
905: out:
906: return error;
907: }
908:
909: /* --------------------------------------------------------------------- */
910:
1.9 jmmv 911: /*
912: * Returns information about the number of available memory pages,
1.1 jmmv 913: * including physical and virtual ones.
914: *
915: * If 'total' is TRUE, the value returned is the total amount of memory
916: * pages configured for the system (either in use or free).
917: * If it is FALSE, the value returned is the amount of free memory pages.
918: *
919: * Remember to remove TMPFS_PAGES_RESERVED from the returned value to avoid
920: * excessive memory usage.
921: *
1.9 jmmv 922: */
1.1 jmmv 923: size_t
924: tmpfs_mem_info(boolean_t total)
925: {
926: size_t size;
927:
928: size = 0;
1.15 dan 929: size += uvmexp.swpgavail;
930: if (!total) {
931: size -= uvmexp.swpgonly;
1.1 jmmv 932: }
933: size += uvmexp.free;
1.16 dan 934: size += uvmexp.filepages;
935: if (size > uvmexp.wired) {
936: size -= uvmexp.wired;
937: } else {
938: size = 0;
939: }
1.1 jmmv 940:
941: return size;
942: }
943:
944: /* --------------------------------------------------------------------- */
945:
1.9 jmmv 946: /*
947: * Change flags of the given vnode.
1.12 yamt 948: * Caller should execute tmpfs_update on vp after a successful execution.
1.9 jmmv 949: * The vnode must be locked on entry and remain locked on exit.
950: */
1.1 jmmv 951: int
952: tmpfs_chflags(struct vnode *vp, int flags, struct ucred *cred, struct proc *p)
953: {
954: int error;
955: struct tmpfs_node *node;
956:
957: KASSERT(VOP_ISLOCKED(vp));
958:
959: node = VP_TO_TMPFS_NODE(vp);
960:
961: /* Disallow this operation if the file system is mounted read-only. */
962: if (vp->v_mount->mnt_flag & MNT_RDONLY)
963: return EROFS;
964:
965: /* XXX: The following comes from UFS code, and can be found in
966: * several other file systems. Shouldn't this be centralized
967: * somewhere? */
968: if (cred->cr_uid != node->tn_uid &&
969: (error = suser(cred, &p->p_acflag)))
970: return error;
971: if (cred->cr_uid == 0) {
972: /* The super-user is only allowed to change flags if the file
973: * wasn't protected before and the securelevel is zero. */
974: if ((node->tn_flags & (SF_IMMUTABLE | SF_APPEND)) &&
975: securelevel > 0)
976: return EPERM;
977: node->tn_flags = flags;
978: } else {
979: /* Regular users can change flags provided they only want to
980: * change user-specific ones, not those reserved for the
981: * super-user. */
982: if ((node->tn_flags & (SF_IMMUTABLE | SF_APPEND)) ||
983: (flags & UF_SETTABLE) != flags)
984: return EPERM;
985: if ((node->tn_flags & SF_SETTABLE) != (flags & SF_SETTABLE))
986: return EPERM;
987: node->tn_flags &= SF_SETTABLE;
988: node->tn_flags |= (flags & UF_SETTABLE);
989: }
990:
991: node->tn_status |= TMPFS_NODE_CHANGED;
992: VN_KNOTE(vp, NOTE_ATTRIB);
993:
994: KASSERT(VOP_ISLOCKED(vp));
995:
996: return 0;
997: }
998:
999: /* --------------------------------------------------------------------- */
1000:
1.9 jmmv 1001: /*
1002: * Change access mode on the given vnode.
1.12 yamt 1003: * Caller should execute tmpfs_update on vp after a successful execution.
1.9 jmmv 1004: * The vnode must be locked on entry and remain locked on exit.
1005: */
1.1 jmmv 1006: int
1007: tmpfs_chmod(struct vnode *vp, mode_t mode, struct ucred *cred, struct proc *p)
1008: {
1009: int error;
1010: struct tmpfs_node *node;
1011:
1012: KASSERT(VOP_ISLOCKED(vp));
1013:
1014: node = VP_TO_TMPFS_NODE(vp);
1015:
1016: /* Disallow this operation if the file system is mounted read-only. */
1017: if (vp->v_mount->mnt_flag & MNT_RDONLY)
1018: return EROFS;
1019:
1020: /* Immutable or append-only files cannot be modified, either. */
1021: if (node->tn_flags & (IMMUTABLE | APPEND))
1022: return EPERM;
1023:
1024: /* XXX: The following comes from UFS code, and can be found in
1025: * several other file systems. Shouldn't this be centralized
1026: * somewhere? */
1027: if (cred->cr_uid != node->tn_uid &&
1028: (error = suser(cred, &p->p_acflag)))
1029: return error;
1030: if (cred->cr_uid != 0) {
1031: if (vp->v_type != VDIR && (mode & S_ISTXT))
1032: return EFTYPE;
1033:
1034: if (!groupmember(node->tn_gid, cred) && (mode & S_ISGID))
1035: return EPERM;
1036: }
1037:
1038: node->tn_mode = (mode & ALLPERMS);
1039:
1040: node->tn_status |= TMPFS_NODE_CHANGED;
1041: VN_KNOTE(vp, NOTE_ATTRIB);
1042:
1043: KASSERT(VOP_ISLOCKED(vp));
1044:
1045: return 0;
1046: }
1047:
1048: /* --------------------------------------------------------------------- */
1049:
1.9 jmmv 1050: /*
1051: * Change ownership of the given vnode. At least one of uid or gid must
1.1 jmmv 1052: * be different than VNOVAL. If one is set to that value, the attribute
1053: * is unchanged.
1.12 yamt 1054: * Caller should execute tmpfs_update on vp after a successful execution.
1.9 jmmv 1055: * The vnode must be locked on entry and remain locked on exit.
1056: */
1.1 jmmv 1057: int
1058: tmpfs_chown(struct vnode *vp, uid_t uid, gid_t gid, struct ucred *cred,
1059: struct proc *p)
1060: {
1061: int error;
1062: struct tmpfs_node *node;
1063:
1064: KASSERT(VOP_ISLOCKED(vp));
1065:
1066: node = VP_TO_TMPFS_NODE(vp);
1067:
1068: /* Assign default values if they are unknown. */
1069: KASSERT(uid != VNOVAL || gid != VNOVAL);
1070: if (uid == VNOVAL)
1071: uid = node->tn_uid;
1072: if (gid == VNOVAL)
1073: gid = node->tn_gid;
1074: KASSERT(uid != VNOVAL && gid != VNOVAL);
1075:
1076: /* Disallow this operation if the file system is mounted read-only. */
1077: if (vp->v_mount->mnt_flag & MNT_RDONLY)
1078: return EROFS;
1079:
1080: /* Immutable or append-only files cannot be modified, either. */
1081: if (node->tn_flags & (IMMUTABLE | APPEND))
1082: return EPERM;
1083:
1084: /* XXX: The following comes from UFS code, and can be found in
1085: * several other file systems. Shouldn't this be centralized
1086: * somewhere? */
1087: if ((cred->cr_uid != node->tn_uid || uid != node->tn_uid ||
1088: (gid != node->tn_gid && !(cred->cr_gid == node->tn_gid ||
1089: groupmember(gid, cred)))) &&
1090: ((error = suser(cred, &p->p_acflag)) != 0))
1091: return error;
1092:
1093: node->tn_uid = uid;
1094: node->tn_gid = gid;
1095:
1096: node->tn_status |= TMPFS_NODE_CHANGED;
1097: VN_KNOTE(vp, NOTE_ATTRIB);
1098:
1099: KASSERT(VOP_ISLOCKED(vp));
1100:
1101: return 0;
1102: }
1103:
1104: /* --------------------------------------------------------------------- */
1105:
1.9 jmmv 1106: /*
1107: * Change size of the given vnode.
1.12 yamt 1108: * Caller should execute tmpfs_update on vp after a successful execution.
1.9 jmmv 1109: * The vnode must be locked on entry and remain locked on exit.
1110: */
1.1 jmmv 1111: int
1112: tmpfs_chsize(struct vnode *vp, u_quad_t size, struct ucred *cred,
1113: struct proc *p)
1114: {
1115: int error;
1116: struct tmpfs_node *node;
1117:
1118: KASSERT(VOP_ISLOCKED(vp));
1119:
1120: node = VP_TO_TMPFS_NODE(vp);
1121:
1122: /* Decide whether this is a valid operation based on the file type. */
1123: error = 0;
1124: switch (vp->v_type) {
1125: case VDIR:
1126: return EISDIR;
1127:
1128: case VREG:
1129: if (vp->v_mount->mnt_flag & MNT_RDONLY)
1130: return EROFS;
1131: break;
1132:
1133: case VBLK:
1134: /* FALLTHROUGH */
1135: case VCHR:
1136: /* FALLTHROUGH */
1137: case VFIFO:
1138: /* Allow modifications of special files even if in the file
1139: * system is mounted read-only (we are not modifying the
1140: * files themselves, but the objects they represent). */
1.14 yamt 1141: return 0;
1.1 jmmv 1142:
1143: default:
1144: /* Anything else is unsupported. */
1.14 yamt 1145: return EOPNOTSUPP;
1.1 jmmv 1146: }
1147:
1148: /* Immutable or append-only files cannot be modified, either. */
1149: if (node->tn_flags & (IMMUTABLE | APPEND))
1150: return EPERM;
1151:
1.12 yamt 1152: error = tmpfs_truncate(vp, size);
1.1 jmmv 1153: /* tmpfs_truncate will raise the NOTE_EXTEND and NOTE_ATTRIB kevents
1154: * for us, as will update tn_status; no need to do that here. */
1155:
1156: KASSERT(VOP_ISLOCKED(vp));
1157:
1158: return error;
1159: }
1160:
1161: /* --------------------------------------------------------------------- */
1162:
1.9 jmmv 1163: /*
1164: * Change access and modification times of the given vnode.
1.12 yamt 1165: * Caller should execute tmpfs_update on vp after a successful execution.
1.9 jmmv 1166: * The vnode must be locked on entry and remain locked on exit.
1167: */
1.1 jmmv 1168: int
1169: tmpfs_chtimes(struct vnode *vp, struct timespec *atime, struct timespec *mtime,
1.17 ! christos 1170: int vaflags, struct ucred *cred, struct lwp *l)
1.1 jmmv 1171: {
1172: int error;
1173: struct tmpfs_node *node;
1174:
1175: KASSERT(VOP_ISLOCKED(vp));
1176:
1177: node = VP_TO_TMPFS_NODE(vp);
1178:
1179: /* Disallow this operation if the file system is mounted read-only. */
1180: if (vp->v_mount->mnt_flag & MNT_RDONLY)
1181: return EROFS;
1182:
1183: /* Immutable or append-only files cannot be modified, either. */
1184: if (node->tn_flags & (IMMUTABLE | APPEND))
1185: return EPERM;
1186:
1187: /* XXX: The following comes from UFS code, and can be found in
1188: * several other file systems. Shouldn't this be centralized
1189: * somewhere? */
1190: if (cred->cr_uid != node->tn_uid &&
1.17 ! christos 1191: (error = suser(cred, &l->l_proc->p_acflag)) &&
1.1 jmmv 1192: ((vaflags & VA_UTIMES_NULL) == 0 ||
1.17 ! christos 1193: (error = VOP_ACCESS(vp, VWRITE, cred, l))))
1.1 jmmv 1194: return error;
1195:
1196: if (atime->tv_sec != VNOVAL && atime->tv_nsec != VNOVAL)
1197: node->tn_status |= TMPFS_NODE_ACCESSED;
1198:
1199: if (mtime->tv_sec != VNOVAL && mtime->tv_nsec != VNOVAL)
1200: node->tn_status |= TMPFS_NODE_MODIFIED;
1201:
1.12 yamt 1202: tmpfs_update(vp, atime, mtime, 0);
1.1 jmmv 1203:
1204: KASSERT(VOP_ISLOCKED(vp));
1205:
1.12 yamt 1206: return 0;
1.1 jmmv 1207: }
1.10 yamt 1208:
1209: /* --------------------------------------------------------------------- */
1210:
1211: /* Sync timestamps */
1212: void
1213: tmpfs_itimes(struct vnode *vp, const struct timespec *acc,
1214: const struct timespec *mod)
1215: {
1216: struct tmpfs_node *node;
1217: const struct timespec *ts = NULL;
1218: struct timespec tsb;
1219:
1220: node = VP_TO_TMPFS_NODE(vp);
1221:
1222: if ((node->tn_status & (TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED |
1223: TMPFS_NODE_CHANGED)) == 0)
1224: return;
1225:
1226: if (node->tn_status & TMPFS_NODE_ACCESSED) {
1227: if (acc == NULL)
1228: acc = ts == NULL ? (ts = nanotime(&tsb)) : ts;
1229: node->tn_atime = *acc;
1230: }
1231: if (node->tn_status & TMPFS_NODE_MODIFIED) {
1232: if (mod == NULL)
1233: mod = ts == NULL ? (ts = nanotime(&tsb)) : ts;
1234: node->tn_mtime = *mod;
1235: }
1236: if (node->tn_status & TMPFS_NODE_CHANGED) {
1237: if (ts == NULL)
1238: ts = nanotime(&tsb);
1239: node->tn_ctime = *ts;
1240: }
1241: node->tn_status &=
1242: ~(TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED | TMPFS_NODE_CHANGED);
1243: }
1.12 yamt 1244:
1245: /* --------------------------------------------------------------------- */
1246:
1247: void
1248: tmpfs_update(struct vnode *vp, const struct timespec *acc,
1249: const struct timespec *mod, int flags)
1250: {
1251:
1252: struct tmpfs_node *node;
1253:
1254: KASSERT(VOP_ISLOCKED(vp));
1255:
1256: node = VP_TO_TMPFS_NODE(vp);
1257:
1258: if (flags & UPDATE_CLOSE)
1259: ; /* XXX Need to do anything special? */
1260:
1261: tmpfs_itimes(vp, acc, mod);
1262:
1263: KASSERT(VOP_ISLOCKED(vp));
1264: }
1265:
1266: /* --------------------------------------------------------------------- */
1267:
1268: int
1269: tmpfs_truncate(struct vnode *vp, off_t length)
1270: {
1271: boolean_t extended;
1272: int error;
1273: struct tmpfs_node *node;
1274:
1275: node = VP_TO_TMPFS_NODE(vp);
1276: extended = length > node->tn_size;
1277:
1278: if (length < 0) {
1279: error = EINVAL;
1280: goto out;
1281: }
1282:
1283: if (node->tn_size == length) {
1284: error = 0;
1285: goto out;
1286: }
1287:
1288: error = tmpfs_reg_resize(vp, length);
1289: if (error == 0) {
1290: VN_KNOTE(vp, NOTE_ATTRIB | (extended ? NOTE_EXTEND : 0));
1291: node->tn_status |= TMPFS_NODE_CHANGED | TMPFS_NODE_MODIFIED;
1292: }
1293:
1294: out:
1295: tmpfs_update(vp, NULL, NULL, 0);
1296:
1297: return error;
1298: }
CVSweb <webmaster@jp.NetBSD.org>