From 7a2de9a42eb7af2d2c40f327e088e6c88489241a Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Fri, 12 Feb 2010 13:46:28 -0800 Subject: [PATCH] kernel - TMPFS - Initial port of NetBSD's tmpfs * This is the initial pre-stabilization port of NetBSD's tmpfs, by Naoya Sugioka. Submitted-by: Naoya Sugioka --- sbin/Makefile | 1 + sbin/mount/mount.8 | 1 + sbin/mount_tmpfs/Makefile | 12 + sbin/mount_tmpfs/mount_tmpfs.8 | 143 ++++ sbin/mount_tmpfs/mount_tmpfs.c | 263 +++++++ sbin/mount_tmpfs/mount_tmpfs.h | 37 + share/man/man5/Makefile | 1 + share/man/man5/tmpfs.5 | 120 +++ sys/conf/files | 6 + sys/conf/options | 2 + sys/config/GENERIC | 1 + sys/config/LINT | 1 + sys/config/VKERNEL | 1 + sys/config/X86_64_GENERIC | 1 + sys/kern/Make.tags.inc | 4 +- sys/kern/Makefile | 2 +- sys/kern/Makefile.misc | 2 +- sys/sys/mount.h | 1 + sys/sys/vfscache.h | 2 +- sys/vfs/Makefile | 2 +- sys/vfs/tmpfs/Makefile | 7 + sys/vfs/tmpfs/tmpfs.h | 597 +++++++++++++++ sys/vfs/tmpfs/tmpfs_args.h | 54 ++ sys/vfs/tmpfs/tmpfs_fifoops.c | 99 +++ sys/vfs/tmpfs/tmpfs_fifoops.h | 53 ++ sys/vfs/tmpfs/tmpfs_subr.c | 1414 ++++++++++++++++++++++++++++++++++++ sys/vfs/tmpfs/tmpfs_vfsops.c | 461 ++++++++++++ sys/vfs/tmpfs/tmpfs_vnops.c | 1564 ++++++++++++++++++++++++++++++++++++++++ sys/vfs/tmpfs/tmpfs_vnops.h | 58 ++ 29 files changed, 4905 insertions(+), 5 deletions(-) create mode 100644 sbin/mount_tmpfs/Makefile create mode 100644 sbin/mount_tmpfs/mount_tmpfs.8 create mode 100644 sbin/mount_tmpfs/mount_tmpfs.c create mode 100644 sbin/mount_tmpfs/mount_tmpfs.h create mode 100644 share/man/man5/tmpfs.5 create mode 100644 sys/vfs/tmpfs/Makefile create mode 100644 sys/vfs/tmpfs/tmpfs.h create mode 100644 sys/vfs/tmpfs/tmpfs_args.h create mode 100644 sys/vfs/tmpfs/tmpfs_fifoops.c create mode 100644 sys/vfs/tmpfs/tmpfs_fifoops.h create mode 100644 sys/vfs/tmpfs/tmpfs_subr.c create mode 100644 sys/vfs/tmpfs/tmpfs_vfsops.c create mode 100644 sys/vfs/tmpfs/tmpfs_vnops.c create mode 100644 sys/vfs/tmpfs/tmpfs_vnops.h diff --git a/sbin/Makefile b/sbin/Makefile index 12b42f3843..b380006419 100644 --- a/sbin/Makefile +++ b/sbin/Makefile @@ -57,6 +57,7 @@ SUBDIR= adjkerntz \ mount_null \ mount_portal \ mount_std \ + mount_tmpfs \ mount_udf \ mount_union \ mountd \ diff --git a/sbin/mount/mount.8 b/sbin/mount/mount.8 index f5f4a2ccd8..b1a146b840 100644 --- a/sbin/mount/mount.8 +++ b/sbin/mount/mount.8 @@ -398,6 +398,7 @@ have permission to load the module. .Xr mount_procfs 8 , .Xr mount_smbfs 8 , .Xr mount_std 8 , +.Xr mount_tmpfs 8 , .Xr mount_udf 8 , .Xr mount_union 8 , .Xr sysctl 8 , diff --git a/sbin/mount_tmpfs/Makefile b/sbin/mount_tmpfs/Makefile new file mode 100644 index 0000000000..8c430bcc40 --- /dev/null +++ b/sbin/mount_tmpfs/Makefile @@ -0,0 +1,12 @@ +# +# $DragonFly: src/sbin/mount_hammer/Makefile,v 1.1 2007/10/10 19:35:19 dillon Exp $ + +PROG= mount_tmpfs +SRCS= mount_tmpfs.c getmntopts.c +MAN= mount_tmpfs.8 + +MOUNT= ${.CURDIR}/../mount +CFLAGS+= -I${.CURDIR}/../../sys -I${MOUNT} +.PATH: ${MOUNT} + +.include diff --git a/sbin/mount_tmpfs/mount_tmpfs.8 b/sbin/mount_tmpfs/mount_tmpfs.8 new file mode 100644 index 0000000000..c3a9afc948 --- /dev/null +++ b/sbin/mount_tmpfs/mount_tmpfs.8 @@ -0,0 +1,143 @@ +.\" $NetBSD: mount_tmpfs.8,v 1.14 2008/04/30 13:10:53 martin Exp $ +.\" +.\" Copyright (c) 2005, 2006 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Julio M. Merino Vidal, developed as part of Google's Summer of Code +.\" 2005 program. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd February 13, 2008 +.Dt MOUNT_TMPFS 8 +.Os +.Sh NAME +.Nm mount_tmpfs +.Nd mount an efficient memory file system +.Sh SYNOPSIS +.Nm +.Op Fl g Ar group +.Op Fl m Ar mode +.Op Fl n Ar nodes +.Op Fl o Ar options +.Op Fl s Ar size +.Op Fl u Ar user +.Ar tmpfs +.Ar mount_point +.Sh DESCRIPTION +The +.Nm +command attaches an instance of the efficient memory file system to the +global file system namespace. +The +.Ar tmpfs +parameter only exists for compatibility with the other mount commands and +is ignored. +The directory specified by +.Ar mount_point +is converted to an absolute path before use and its attributes (owner, +group and mode) are inherited unless explicitly overriden by the options +described below. +.Pp +The following options are supported: +.Bl -tag -width XoXoptions +.It Fl g Ar group +Specifies the group name or GID of the root inode of the file system. +Defaults to the mount point's GID. +.It Fl m Ar mode +Specifies the mode (in octal notation) of the root inode of the file system. +Defaults to the mount point's mode. +.It Fl n Ar nodes +Specifies the maximum number of nodes available to the file system. +If not specified, the file system chooses a reasonable maximum given its +size at mount time, which can be limited with +.Fl s . +.It Fl o Ar options +Options are specified with a +.Fl o +flag followed by a comma-separated string of options. +See the +.Xr mount 8 +man page for possible options and their meanings. +.It Fl s Ar size +Specifies the total file system size in bytes. +If zero is given (the default), the available amount of memory (including +main memory and swap space) will be used. +Note that four megabytes are always reserved for the system and cannot +be assigned to the file system. +.It Fl u Ar user +Specifies the user name or UID of the root inode of the file system. +Defaults to the mount point's UID. +.El +.Pp +Every option that accepts a numerical value as its argument can take a +trailing +.Sq b +to indicate bytes (the default), a trailing +.Sq k +to indicate kilobytes, a trailing +.Sq M +to indicate megabytes or a trailing +.Sq G +to indicate gigabytes. +Note that both lowercase and uppercase forms of these letters are allowed. +.Sh EXAMPLES +The following command mounts a tmpfs instance over the +.Pa /tmp +directory, inheriting its owner, group and mode settings: +.Pp +.Ic "mount -t tmpfs tmpfs /tmp" +.Pp +The following command mounts a tmpfs instance over the +.Pa /mnt +directory, setting a 20 megabytes limit in space, owned by the +.Sq joe +user and belonging to the +.Sq users +group, with a restricted 0700 mode: +.Pp +.Ic "mount -t tmpfs -o -s20M -o -ujoe -o -gusers -o -m0700 tmpfs /mnt" +.Pp +See +.Pa /usr/share/examples/fstab/fstab.ramdisk +for some examples on how to add tmpfs entries to +.Pa /etc/fstab . +.Sh SEE ALSO +.Xr fstab 5 , +.Xr mount 8 +.Sh HISTORY +The +.Nm +utility first appeared in +.Nx 4.0 . +.Sh BUGS +File system meta-data is not pageable. +If there is not enough main memory to hold this information, the system may +become unstable or very unresponsive because it will not be able to allocate +required memory. +A malicious user could trigger this condition if he could create lots of +files inside a size-unbounded tmpfs file system. +Limiting the number of nodes per file system +.Pq Fl n +will prevent this; the default value for this setting is also often adjusted +to an adequate value to resolve this. diff --git a/sbin/mount_tmpfs/mount_tmpfs.c b/sbin/mount_tmpfs/mount_tmpfs.c new file mode 100644 index 0000000000..24d934a372 --- /dev/null +++ b/sbin/mount_tmpfs/mount_tmpfs.c @@ -0,0 +1,263 @@ +/* $NetBSD: mount_tmpfs.c,v 1.24 2008/08/05 20:57:45 pooka Exp $ */ + +/* + * Copyright (c) 2005, 2006 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef lint +#endif /* not lint */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mntopts.h" +#include "mount_tmpfs.h" + +/* --------------------------------------------------------------------- */ + +static const struct mntopt mopts[] = { + MOPT_STDOPTS, + MOPT_NULL +}; + +/* --------------------------------------------------------------------- */ + +static gid_t a_gid(char *); +static uid_t a_uid(char *); +static mode_t a_mask(char *); +static void usage(void) __dead2; + +/* --------------------------------------------------------------------- */ + +void +mount_tmpfs_parseargs(int argc, char *argv[], + struct tmpfs_args *args, int *mntflags, + char *canon_dev, char *canon_dir) +{ + int gidset, modeset, uidset; /* Ought to be 'bool'. */ + int ch; + gid_t gid; + uid_t uid; + mode_t mode; + int64_t tmpnumber = 0; + struct stat sb; + char *a; + + /* Set default values for mount point arguments. */ + memset(args, 0, sizeof(*args)); + args->ta_version = TMPFS_ARGS_VERSION; + args->ta_size_max = 0; + args->ta_nodes_max = 0; + *mntflags = 0; + + gidset = 0; gid = 0; + uidset = 0; uid = 0; + modeset = 0; mode = 0; + + optind = optreset = 1; + while ((ch = getopt(argc, argv, "g:m:n:o:s:u:")) != -1 ) { + switch (ch) { + case 'g': + gid = a_gid(optarg); + gidset = 1; + break; + + case 'm': + mode = a_mask(optarg); + modeset = 1; + break; + + case 'n': + for (a = optarg; *optarg && isdigit(*optarg); ++optarg); + if (!*optarg) + tmpnumber = strtoimax(a, NULL, 10); + + args->ta_nodes_max = tmpnumber; + break; + + case 'o': + getmntopts(optarg, mopts, mntflags, 0); + break; + + case 's': + for (a = optarg; *optarg && isdigit(*optarg); ++optarg); + if (!*optarg) + tmpnumber = strtoimax(a, NULL, 10); + + args->ta_size_max = tmpnumber; + break; + + case 'u': + uid = a_uid(optarg); + uidset = 1; + break; + + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc != 2) + usage(); + + strlcpy(canon_dev, argv[0], MAXPATHLEN); + strlcpy(canon_dir, argv[1], MAXPATHLEN); + + if (stat(canon_dir, &sb) == -1) + err(EXIT_FAILURE, "cannot stat `%s'", canon_dir); + + args->ta_root_uid = uidset ? uid : sb.st_uid; + args->ta_root_gid = gidset ? gid : sb.st_gid; + args->ta_root_mode = modeset ? mode : sb.st_mode; +} + +/* --------------------------------------------------------------------- */ + +static gid_t +a_gid(char *s) +{ + struct group *gr; + char *gname; + gid_t gid; + + if ((gr = getgrnam(s)) != NULL) + gid = gr->gr_gid; + else { + for (gname = s; *s && isdigit(*s); ++s); + if (!*s) + gid = atoi(gname); + else + errx(EX_NOUSER, "unknown group id: %s", gname); + } + return (gid); +} + +static uid_t +a_uid(char *s) +{ + struct passwd *pw; + char *uname; + uid_t uid; + + if ((pw = getpwnam(s)) != NULL) + uid = pw->pw_uid; + else { + for (uname = s; *s && isdigit(*s); ++s); + if (!*s) + uid = atoi(uname); + else + errx(EX_NOUSER, "unknown user id: %s", uname); + } + return (uid); +} + +static mode_t +a_mask(char *s) +{ + int done, rv=0; + char *ep; + + done = 0; + if (*s >= '0' && *s <= '7') { + done = 1; + rv = strtol(optarg, &ep, 8); + } + if (!done || rv < 0 || *ep) + errx(EX_USAGE, "invalid file mode: %s", s); + return (rv); +} + +static void +usage(void) +{ + (void)fprintf(stderr, + "Usage: %s [-g group] [-m mode] [-n nodes] [-o options] [-s size]\n" + " [-u user] tmpfs mountpoint\n", getprogname()); + exit(1); +} + +/* --------------------------------------------------------------------- */ + +int +mount_tmpfs(int argc, char *argv[]) +{ + struct tmpfs_args args; + char canon_dev[MAXPATHLEN], canon_dir[MAXPATHLEN]; + int mntflags; + struct vfsconf vfc; + int error; + + mount_tmpfs_parseargs(argc, argv, &args, &mntflags, + canon_dev, canon_dir); + + + error = getvfsbyname("tmpfs", &vfc); + if (error && vfsisloadable("tmpfs")) { + if(vfsload("tmpfs")) + err(EX_OSERR, "vfsload(%s)", "tmpfs"); + endvfsent(); + error = getvfsbyname("tmpfs", &vfc); + } + if (error) + errx(EX_OSERR, "%s filesystem not available", "tmpfs"); + + if (mount(vfc.vfc_name, canon_dir, mntflags, 0) == -1) + err(EXIT_FAILURE, "tmpfs on %s", canon_dir); + + return EXIT_SUCCESS; +} + +#ifndef MOUNT_NOMAIN +int +main(int argc, char *argv[]) +{ + + setprogname(argv[0]); + return mount_tmpfs(argc, argv); +} +#endif diff --git a/sbin/mount_tmpfs/mount_tmpfs.h b/sbin/mount_tmpfs/mount_tmpfs.h new file mode 100644 index 0000000000..96874ef680 --- /dev/null +++ b/sbin/mount_tmpfs/mount_tmpfs.h @@ -0,0 +1,37 @@ +/* $NetBSD: mount_tmpfs.h,v 1.1 2008/08/05 20:57:45 pooka Exp $ */ + +/* + * Copyright (c) 2008 The NetBSD Foundation. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _SBIN_MOUNT_TMPFS_MOUNT_TMPFS_H_ +#define _SBIN_MOUNT_TMPFS_MOUNT_TMPFS_H_ + +#include + +int mount_tmpfs(int, char **); +void mount_tmpfs_parseargs(int, char **, struct tmpfs_args *, int *, + char *, char *); + +#endif /* _SBIN_MOUNT_TMPFS_MOUNT_TMPFS_H_ */ diff --git a/share/man/man5/Makefile b/share/man/man5/Makefile index ecff5f7b9c..0e858f5861 100644 --- a/share/man/man5/Makefile +++ b/share/man/man5/Makefile @@ -53,6 +53,7 @@ MAN= acct.5 \ shells.5 \ stab.5 \ sysctl.conf.5 \ + tmpfs.5 \ utf2.5 \ utf8.5 \ utmp.5 \ diff --git a/share/man/man5/tmpfs.5 b/share/man/man5/tmpfs.5 new file mode 100644 index 0000000000..a8126369c6 --- /dev/null +++ b/share/man/man5/tmpfs.5 @@ -0,0 +1,120 @@ +.\" +.\" Copyright (c) 2007 Xin LI +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission +.\" +.\" THIS DOCUMENTATION IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" $FreeBSD: src/share/man/man5/tmpfs.5,v 1.7 2010/01/29 15:11:50 jh Exp $ +.\" +.Dd February 2, 2010 +.Dt TMPFS 5 +.Os +.Sh NAME +.Nm tmpfs +.Nd "efficient memory file system" +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following line in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "options TMPFS" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +tmpfs_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver will permit the +.Dx +kernel to access +.Tn tmpfs +file systems. +.Sh OPTIONS +The following options are available when +mounting +.Nm +file systems: +.Bl -tag -width indent +.It Cm gid +root group id. +.It Cm uid +root user id. +.It Cm mode +permissions in octal format. +.It Cm inodes +maximum number of inodes. +.It Cm size +maximum size (in bytes) for the file system. +.It Cm maxfilesize +maximum file size (in bytes). +.El +.Sh EXAMPLES +To mount a +.Nm +memory file system: +.Pp +.Dl "mount -t tmpfs tmpfs /tmp" +.Sh SEE ALSO +.Xr nmount 2 , +.Xr unmount 2 , +.Xr fstab 5 , +.Xr mount 8 +.Sh HISTORY +The +.Nm +driver first appeared in +.Dx 2.5.1 . +.Sh AUTHORS +.An -nosplit +The +.Nm +kernel implementation was written by +.An Julio M. Merino Vidal Aq jmmv@NetBSD.org +as a Google SoC project. +.Pp +.An Rohit Jalan +and others ported it from +.Nx +to +.Fx . +.Pp +.An Naoya Sugioka +and others ported it from +.Nx +to +.Dx . +.Pp +This manual page was written by +.An Xin LI Aq delphij@FreeBSD.org . +.Sh BUGS +The +.Nm +kernel implementation is currently considered as +an experimental feature. +Some file system mount +time options are not well supported. diff --git a/sys/conf/files b/sys/conf/files index 430873f394..417660058a 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1426,6 +1426,12 @@ vfs/hammer/hammer_undo.c optional hammer vfs/hammer/hammer_redo.c optional hammer vfs/hammer/hammer_vfsops.c optional hammer vfs/hammer/hammer_vnops.c optional hammer +#tmpfs static filesystem for debugging should be marked as optional tmpfs +vfs/tmpfs/tmpfs_fifoops.c standard +vfs/tmpfs/tmpfs_subr.c standard +vfs/tmpfs/tmpfs_vfsops.c standard +vfs/tmpfs/tmpfs_vnops.c standard +# vm/default_pager.c standard vm/device_pager.c standard vm/phys_pager.c standard diff --git a/sys/conf/options b/sys/conf/options index a78ee65f0b..38b6379030 100644 --- a/sys/conf/options +++ b/sys/conf/options @@ -150,6 +150,7 @@ PORTAL opt_dontuse.h PROCFS opt_dontuse.h UDF opt_dontuse.h NTFS opt_dontuse.h +#TMPFS opt_dontuse.h # These static filesystems has one slightly bogus static dependency in # sys/platform/.../i386/autoconf.c. If any of these filesystems are @@ -161,6 +162,7 @@ FFS NFS NWFS USERFS +TMPFS HAMMER # If you are following the conditions in the copyright, diff --git a/sys/config/GENERIC b/sys/config/GENERIC index d474b584cf..385de25fdc 100644 --- a/sys/config/GENERIC +++ b/sys/config/GENERIC @@ -27,6 +27,7 @@ options FFS_ROOT #FFS usable as root device [keep this!] options SOFTUPDATES #Enable FFS soft updates support options UFS_DIRHASH #Improve performance on big directories options MFS #Memory Filesystem +options TMPFS #Temporary Filesystem options MD_ROOT #MD is a potential root device options NFS #Network Filesystem options NFS_ROOT #NFS usable as root device, NFS required diff --git a/sys/config/LINT b/sys/config/LINT index 00653d46cd..3976dec6c7 100644 --- a/sys/config/LINT +++ b/sys/config/LINT @@ -749,6 +749,7 @@ options PROCFS #Process filesystem options SMBFS #SMB/CIFS filesystem options UDF #UDF filesystem options HAMMER #HAMMER filesystem +options TMPFS #Temporary filesystem # YYY-DR Till we rework the VOP methods for this filesystem #options UNION #Union filesystem diff --git a/sys/config/VKERNEL b/sys/config/VKERNEL index 49b0271457..ac674005ce 100644 --- a/sys/config/VKERNEL +++ b/sys/config/VKERNEL @@ -51,6 +51,7 @@ options FFS_ROOT #FFS usable as root device [keep this!] options SOFTUPDATES #Enable FFS soft updates support options UFS_DIRHASH #Improve performance on big directories options MFS #Memory Filesystem +options TMPFS #Temporary Filesystem options MD_ROOT #MD is a potential root device options NFS #Network Filesystem options NFS_ROOT #NFS usable as root device, NFS required diff --git a/sys/config/X86_64_GENERIC b/sys/config/X86_64_GENERIC index dcc40e1eb5..8ac2b32109 100644 --- a/sys/config/X86_64_GENERIC +++ b/sys/config/X86_64_GENERIC @@ -22,6 +22,7 @@ options FFS_ROOT #FFS usable as root device [keep this!] options SOFTUPDATES #Enable FFS soft updates support options UFS_DIRHASH #Improve performance on big directories options MFS #Memory Filesystem +options TMPFS #Temporary Filesystem options MD_ROOT #MD is a potential root device options NFS #Network Filesystem options NFS_ROOT #NFS usable as root device, NFS required diff --git a/sys/kern/Make.tags.inc b/sys/kern/Make.tags.inc index 80fef82a73..203e8bd776 100644 --- a/sys/kern/Make.tags.inc +++ b/sys/kern/Make.tags.inc @@ -52,6 +52,7 @@ COMM= ${SYS}/conf/*.[ch] \ ${SYS}/emulation/posix4/*.[ch] \ ${SYS}/vfs/mfs/*.[ch] \ ${SYS}/vfs/ufs/*.[ch] \ + ${SYS}/vfs/tmpfs/*.[ch] \ ${SYS}/vm/*.[ch] \ ${SYS}/sys/*.[ch] @@ -91,4 +92,5 @@ COMMDIR2= ${SYS}/dev/disk/advansys \ ${SYS}/vfs/specfs \ ${SYS}/vfs/union \ ${SYS}/vfs/ufs \ - ${SYS}/vfs/mfs + ${SYS}/vfs/mfs \ + ${SYS}/vfs/tmpfs diff --git a/sys/kern/Makefile b/sys/kern/Makefile index 8b59c215b9..a55d8bdff5 100644 --- a/sys/kern/Makefile +++ b/sys/kern/Makefile @@ -27,7 +27,7 @@ DGEN= conf \ vfs vfs/deadfs vfs/fdesc vfs/fifofs \ vfs/nullfs vfs/portal vfs/procfs \ vfs/specfs vfs/union \ - vfs/ufs vfs/mfs vfs/nfs \ + vfs/ufs vfs/mfs vfs/nfs vfs/tmpfs \ net netinet netns sys \ vm diff --git a/sys/kern/Makefile.misc b/sys/kern/Makefile.misc index 922e1a6717..df0563a1e2 100644 --- a/sys/kern/Makefile.misc +++ b/sys/kern/Makefile.misc @@ -42,7 +42,7 @@ DGEN= conf \ vfs vfs/deadfs vfs/fdesc vfs/fifofs \ vfs/nullfs vfs/portal vfs/procfs \ vfs/specfs vfs/union \ - vfs/ufs vfs/mfs vfs/nfs \ + vfs/ufs vfs/mfs vfs/nfs vfs/tmpfs \ net netinet netns sys \ vm diff --git a/sys/sys/mount.h b/sys/sys/mount.h index 399a6ea7c2..7d14dbe96e 100644 --- a/sys/sys/mount.h +++ b/sys/sys/mount.h @@ -290,6 +290,7 @@ struct mount { #define MNTK_WR_MPSAFE 0x00040000 /* vop_write is MPSAFE */ #define MNTK_GA_MPSAFE 0x00080000 /* vop_getattr is MPSAFE */ #define MNTK_IN_MPSAFE 0x00100000 /* vop_inactive is MPSAFE */ +#define MNTK_SG_MPSAFE 0x00200000 /* vop_strategy is MPSAFE */ #define MNTK_NCALIASED 0x00800000 /* namecached aliased */ #define MNTK_UNMOUNT 0x01000000 /* unmount in progress */ #define MNTK_MWAIT 0x02000000 /* waiting for unmount to finish */ diff --git a/sys/sys/vfscache.h b/sys/sys/vfscache.h index e554098da6..bccc26ffef 100644 --- a/sys/sys/vfscache.h +++ b/sys/sys/vfscache.h @@ -114,7 +114,7 @@ enum vtagtype { VT_PORTAL, VT_NULL, VT_UNUSED10, VT_KERNFS, VT_PROCFS, VT_AFS, VT_ISOFS, VT_UNION, VT_MSDOSFS, VT_TFS, VT_VFS, VT_CODA, VT_NTFS, VT_HPFS, VT_NWFS, VT_SMBFS, VT_UDF, VT_EXT2FS, VT_SYNTH, - VT_USERFS, VT_HAMMER, VT_DEVFS + VT_USERFS, VT_HAMMER, VT_DEVFS, VT_TMPFS }; /* diff --git a/sys/vfs/Makefile b/sys/vfs/Makefile index 4771dbd5b2..7cd1e269f3 100644 --- a/sys/vfs/Makefile +++ b/sys/vfs/Makefile @@ -4,6 +4,6 @@ SUBDIR=fifofs msdosfs portal gnu nfs procfs \ hpfs ntfs smbfs isofs fdesc mfs nwfs udf \ - nullfs hammer + nullfs hammer tmpfs .include diff --git a/sys/vfs/tmpfs/Makefile b/sys/vfs/tmpfs/Makefile new file mode 100644 index 0000000000..8f05ea6fea --- /dev/null +++ b/sys/vfs/tmpfs/Makefile @@ -0,0 +1,7 @@ + +KMOD= tmpfs +SRCS= tmpfs_vnops.c tmpfs_subr.c \ + tmpfs_fifoops.c tmpfs_vfsops.c +NOMAN= + +.include diff --git a/sys/vfs/tmpfs/tmpfs.h b/sys/vfs/tmpfs/tmpfs.h new file mode 100644 index 0000000000..73be1431e6 --- /dev/null +++ b/sys/vfs/tmpfs/tmpfs.h @@ -0,0 +1,597 @@ +/* $NetBSD: tmpfs.h,v 1.26 2007/02/22 06:37:00 thorpej Exp $ */ + +/*- + * Copyright (c) 2005, 2006 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/fs/tmpfs/tmpfs.h,v 1.18 2009/10/11 07:03:56 delphij Exp $ + */ + +#ifndef _VFS_TMPFS_TMPFS_H_ +#define _VFS_TMPFS_TMPFS_H_ + +/* --------------------------------------------------------------------- + * KERNEL-SPECIFIC DEFINITIONS + * --------------------------------------------------------------------- */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* --------------------------------------------------------------------- */ +#include +#include +#include +#include + +MALLOC_DECLARE(M_TMPFSMNT); +MALLOC_DECLARE(M_TMPFSNAME); + +/* --------------------------------------------------------------------- */ + +/* + * Internal representation of a tmpfs directory entry. + */ +struct tmpfs_dirent { + TAILQ_ENTRY(tmpfs_dirent) td_entries; + + /* Length of the name stored in this directory entry. This avoids + * the need to recalculate it every time the name is used. */ + uint16_t td_namelen; + + /* The name of the entry, allocated from a string pool. This + * string is not required to be zero-terminated; therefore, the + * td_namelen field must always be used when accessing its value. */ + char * td_name; + + /* Pointer to the node this entry refers to. */ + struct tmpfs_node * td_node; +}; + +/* A directory in tmpfs holds a sorted list of directory entries, which in + * turn point to other files (which can be directories themselves). + * + * In tmpfs, this list is managed by a tail queue, whose head is defined by + * the struct tmpfs_dir type. + * + * It is imporant to notice that directories do not have entries for . and + * .. as other file systems do. These can be generated when requested + * based on information available by other means, such as the pointer to + * the node itself in the former case or the pointer to the parent directory + * in the latter case. This is done to simplify tmpfs's code and, more + * importantly, to remove redundancy. */ +TAILQ_HEAD(tmpfs_dir, tmpfs_dirent); + +/* Each entry in a directory has a cookie that identifies it. Cookies + * supersede offsets within directories because, given how tmpfs stores + * directories in memory, there is no such thing as an offset. (Emulating + * a real offset could be very difficult.) + * + * The '.', '..' and the end of directory markers have fixed cookies which + * cannot collide with the cookies generated by other entries. The cookies + * for the other entries are generated based on the memory address on which + * stores their information is stored. + * + * Ideally, using the entry's memory pointer as the cookie would be enough + * to represent it and it wouldn't cause collisions in any system. + * Unfortunately, this results in "offsets" with very large values which + * later raise problems in the Linux compatibility layer (and maybe in other + * places) as described in PR kern/32034. Hence we need to workaround this + * with a rather ugly hack. + * + * Linux 32-bit binaries, unless built with _FILE_OFFSET_BITS=64, have off_t + * set to 'long', which is a 32-bit *signed* long integer. Regardless of + * the macro value, GLIBC (2.3 at least) always uses the getdents64 + * system call (when calling readdir) which internally returns off64_t + * offsets. In order to make 32-bit binaries work, *GLIBC* converts the + * 64-bit values returned by the kernel to 32-bit ones and aborts with + * EOVERFLOW if the conversion results in values that won't fit in 32-bit + * integers (which it assumes is because the directory is extremely large). + * This wouldn't cause problems if we were dealing with unsigned integers, + * but as we have signed integers, this check fails due to sign expansion. + * + * For example, consider that the kernel returns the 0xc1234567 cookie to + * userspace in a off64_t integer. Later on, GLIBC casts this value to + * off_t (remember, signed) with code similar to: + * system call returns the offset in kernel_value; + * off_t casted_value = kernel_value; + * if (sizeof(off_t) != sizeof(off64_t) && + * kernel_value != casted_value) + * error! + * In this case, casted_value still has 0xc1234567, but when it is compared + * for equality against kernel_value, it is promoted to a 64-bit integer and + * becomes 0xffffffffc1234567, which is different than 0x00000000c1234567. + * Then, GLIBC assumes this is because the directory is very large. + * + * Given that all the above happens in user-space, we have no control over + * it; therefore we must workaround the issue here. We do this by + * truncating the pointer value to a 32-bit integer and hope that there + * won't be collisions. In fact, this will not cause any problems in + * 32-bit platforms but some might arise in 64-bit machines (I'm not sure + * if they can happen at all in practice). + * + * XXX A nicer solution shall be attempted. */ +#ifdef _KERNEL +#define TMPFS_DIRCOOKIE_DOT 0 +#define TMPFS_DIRCOOKIE_DOTDOT 1 +#define TMPFS_DIRCOOKIE_EOF 2 +static __inline +off_t +tmpfs_dircookie(struct tmpfs_dirent *de) +{ + off_t cookie; + + cookie = ((off_t)(uintptr_t)de >> 1) & 0x7FFFFFFF; + KKASSERT(cookie != TMPFS_DIRCOOKIE_DOT); + KKASSERT(cookie != TMPFS_DIRCOOKIE_DOTDOT); + KKASSERT(cookie != TMPFS_DIRCOOKIE_EOF); + + return cookie; +} +#endif + +/* --------------------------------------------------------------------- */ + +/* + * Internal representation of a tmpfs file system node. + * + * This structure is splitted in two parts: one holds attributes common + * to all file types and the other holds data that is only applicable to + * a particular type. The code must be careful to only access those + * attributes that are actually allowed by the node's type. + * + * + * Below is the key of locks used to protected the fields in the following + * structures. + * + */ +struct tmpfs_node { + /* Doubly-linked list entry which links all existing nodes for a + * single file system. This is provided to ease the removal of + * all nodes during the unmount operation. */ + LIST_ENTRY(tmpfs_node) tn_entries; + + /* The node's type. Any of 'VBLK', 'VCHR', 'VDIR', 'VFIFO', + * 'VLNK', 'VREG' and 'VSOCK' is allowed. The usage of vnode + * types instead of a custom enumeration is to make things simpler + * and faster, as we do not need to convert between two types. */ + enum vtype tn_type; + + /* Node identifier. */ + ino_t tn_id; + + /* Node's internal status. This is used by several file system + * operations to do modifications to the node in a delayed + * fashion. */ + int tn_status; +#define TMPFS_NODE_ACCESSED (1 << 1) +#define TMPFS_NODE_MODIFIED (1 << 2) +#define TMPFS_NODE_CHANGED (1 << 3) + + /* The node size. It does not necessarily match the real amount + * of memory consumed by it. */ + off_t tn_size; + + /* Generic node attributes. */ + uid_t tn_uid; + gid_t tn_gid; + mode_t tn_mode; + int tn_flags; + nlink_t tn_links; + int32_t tn_atime; + int32_t tn_atimensec; + int32_t tn_mtime; + int32_t tn_mtimensec; + int32_t tn_ctime; + int32_t tn_ctimensec; + unsigned long tn_gen; + struct lockf tn_advlock; + + /* As there is a single vnode for each active file within the + * system, care has to be taken to avoid allocating more than one + * vnode per file. In order to do this, a bidirectional association + * is kept between vnodes and nodes. + * + * Whenever a vnode is allocated, its v_data field is updated to + * point to the node it references. At the same time, the node's + * tn_vnode field is modified to point to the new vnode representing + * it. Further attempts to allocate a vnode for this same node will + * result in returning a new reference to the value stored in + * tn_vnode. + * + * May be NULL when the node is unused (that is, no vnode has been + * allocated for it or it has been reclaimed). */ + struct vnode * tn_vnode; + + /* interlock to protect tn_vpstate */ + struct lock tn_interlock; + + /* Identify if current node has vnode assiocate with + * or allocating vnode. + */ + int tn_vpstate; + + /* misc data field for different tn_type node */ + union { + /* Valid when tn_type == VBLK || tn_type == VCHR. */ + dev_t tn_rdev; /*int32_t ?*/ + + /* Valid when tn_type == VDIR. */ + struct tn_dir{ + /* Pointer to the parent directory. The root + * directory has a pointer to itself in this field; + * this property identifies the root node. */ + struct tmpfs_node * tn_parent; + + /* Head of a tail-queue that links the contents of + * the directory together. See above for a + * description of its contents. */ + struct tmpfs_dir tn_dirhead; + + /* Number and pointer of the first directory entry + * returned by the readdir operation if it were + * called again to continue reading data from the + * same directory as before. This is used to speed + * up reads of long directories, assuming that no + * more than one read is in progress at a given time. + * Otherwise, these values are discarded and a linear + * scan is performed from the beginning up to the + * point where readdir starts returning values. */ + off_t tn_readdir_lastn; + struct tmpfs_dirent * tn_readdir_lastp; + }tn_dir; + + /* Valid when tn_type == VLNK. */ + /* The link's target, allocated from a string pool. */ + char * tn_link; + + /* Valid when tn_type == VREG. */ + struct tn_reg { + /* The contents of regular files stored in a tmpfs + * file system are represented by a single anonymous + * memory object (aobj, for short). The aobj provides + * direct access to any position within the file, + * because its contents are always mapped in a + * contiguous region of virtual memory. It is a task + * of the memory management subsystem (see uvm(9)) to + * issue the required page ins or page outs whenever + * a position within the file is accessed. */ + vm_object_t tn_aobj; + size_t tn_aobj_pages; + + }tn_reg; + + /* Valid when tn_type = VFIFO */ + struct tn_fifo { + int (*tn_fo_read) (struct file *fp, struct uio *uio, + struct ucred *cred, int flags); + int (*tn_fo_write) (struct file *fp, struct uio *uio, + struct ucred *cred, int flags); + }tn_fifo; + }tn_spec; +}; +LIST_HEAD(tmpfs_node_list, tmpfs_node); + +#define tn_rdev tn_spec.tn_rdev +#define tn_dir tn_spec.tn_dir +#define tn_link tn_spec.tn_link +#define tn_reg tn_spec.tn_reg +#define tn_fifo tn_spec.tn_fifo + +#define TMPFS_NODE_LOCK(node) lockmgr(&(node)->tn_interlock, LK_EXCLUSIVE|LK_RETRY) +#define TMPFS_NODE_UNLOCK(node) lockmgr(&(node)->tn_interlock, LK_RELEASE) +#define TMPFS_NODE_MTX(node) (&(node)->tn_interlock) + +#ifdef INVARIANTS +#define TMPFS_ASSERT_LOCKED(node) do { \ + KKASSERT(node != NULL); \ + KKASSERT(node->tn_vnode != NULL); \ + if (!vn_islocked(node->tn_vnode) && \ + (lockstatus(TMPFS_NODE_MTX(node), curthread) == LK_EXCLUSIVE )) \ + panic("tmpfs: node is not locked: %p", node); \ + } while (0) +#define TMPFS_ASSERT_ELOCKED(node) do { \ + KKASSERT((node) != NULL); \ + KKASSERT((node)->tn_vnode != NULL); \ + KKASSERT(lockstatus(TMPFS_NODE_MTX(node), curthread) == LK_EXCLUSIVE); \ + } while (0) +#else +#define TMPFS_ASSERT_LOCKED(node) (void)0 +#define TMPFS_ASSERT_ELOCKED(node) (void)0 +#endif + +#define TMPFS_VNODE_ALLOCATING 1 +#define TMPFS_VNODE_WANT 2 +#define TMPFS_VNODE_DOOMED 4 +/* --------------------------------------------------------------------- */ + +/* + * Internal representation of a tmpfs mount point. + */ +struct tmpfs_mount { + /* Maximum number of memory pages available for use by the file + * system, set during mount time. This variable must never be + * used directly as it may be bigger than the current amount of + * free memory; in the extreme case, it will hold the SIZE_MAX + * value. Instead, use the TMPFS_PAGES_MAX macro. */ + size_t tm_pages_max; + + /* Number of pages in use by the file system. Cannot be bigger + * than the value returned by TMPFS_PAGES_MAX in any case. */ + size_t tm_pages_used; + + /* Pointer to the node representing the root directory of this + * file system. */ + struct tmpfs_node * tm_root; + + /* Maximum number of possible nodes for this file system; set + * during mount time. We need a hard limit on the maximum number + * of nodes to avoid allocating too much of them; their objects + * cannot be released until the file system is unmounted. + * Otherwise, we could easily run out of memory by creating lots + * of empty files and then simply removing them. */ + ino_t tm_nodes_max; + + /* Number of nodes currently that are in use. */ + ino_t tm_nodes_inuse; + + /* maximum representable file size */ + u_int64_t tm_maxfilesize; + + /* Nodes are organized in two different lists. The used list + * contains all nodes that are currently used by the file system; + * i.e., they refer to existing files. The available list contains + * all nodes that are currently available for use by new files. + * Nodes must be kept in this list (instead of deleting them) + * because we need to keep track of their generation number (tn_gen + * field). + * + * Note that nodes are lazily allocated: if the available list is + * empty and we have enough space to create more nodes, they will be + * created and inserted in the used list. Once these are released, + * they will go into the available list, remaining alive until the + * file system is unmounted. */ + struct tmpfs_node_list tm_nodes_used; + + /* All node lock to protect the node list and tmp_pages_used */ + struct lock allnode_lock; + + /* Pools used to store file system meta data. These are not shared + * across several instances of tmpfs for the reasons described in + * tmpfs_pool.c. */ + struct objcache *tm_dirent_pool; + struct objcache *tm_node_pool; +}; +#define TMPFS_LOCK(tm) lockmgr(&(tm)->allnode_lock, LK_EXCLUSIVE|LK_RETRY) +#define TMPFS_UNLOCK(tm) lockmgr(&(tm)->allnode_lock, LK_RELEASE) + +/* --------------------------------------------------------------------- */ + +/* + * This structure maps a file identifier to a tmpfs node. Used by the + * NFS code. + */ +struct tmpfs_fid { + uint16_t tf_len; + uint16_t tf_pad; + ino_t tf_id; + unsigned long tf_gen; +}; + +/* --------------------------------------------------------------------- */ + +#ifdef _KERNEL +/* + * Prototypes for tmpfs_subr.c. + */ + +int tmpfs_alloc_node(struct tmpfs_mount *, enum vtype, + uid_t uid, gid_t gid, mode_t mode, struct tmpfs_node *, + char *, int, int, struct tmpfs_node **); +void tmpfs_free_node(struct tmpfs_mount *, struct tmpfs_node *); +int tmpfs_alloc_dirent(struct tmpfs_mount *, struct tmpfs_node *, + const char *, uint16_t, struct tmpfs_dirent **); +void tmpfs_free_dirent(struct tmpfs_mount *, struct tmpfs_dirent *, + boolean_t); +int tmpfs_alloc_vp(struct mount *, struct tmpfs_node *, int, + struct vnode **); +void tmpfs_free_vp(struct vnode *); +int tmpfs_alloc_file(struct vnode *, struct vnode **, struct vattr *, + struct namecache *, struct ucred *, char *); +void tmpfs_dir_attach(struct vnode *, struct tmpfs_dirent *); +void tmpfs_dir_detach(struct vnode *, struct tmpfs_dirent *); +struct tmpfs_dirent * tmpfs_dir_lookup(struct tmpfs_node *node, + struct tmpfs_node *f, + struct namecache *ncp); +int tmpfs_dir_getdotdent(struct tmpfs_node *, struct uio *); +int tmpfs_dir_getdotdotdent(struct tmpfs_node *, struct uio *); +struct tmpfs_dirent * tmpfs_dir_lookupbycookie(struct tmpfs_node *, off_t); +int tmpfs_dir_getdents(struct tmpfs_node *, struct uio *, off_t *); +int tmpfs_reg_resize(struct vnode *, off_t, int); +int tmpfs_chflags(struct vnode *, int, struct ucred *); +int tmpfs_chmod(struct vnode *, mode_t, struct ucred *); +int tmpfs_chown(struct vnode *, uid_t, gid_t, struct ucred *); +int tmpfs_chsize(struct vnode *, u_quad_t, struct ucred *); +int tmpfs_chtimes(struct vnode *, struct timespec *, struct timespec *, + int, struct ucred *); +void tmpfs_itimes(struct vnode *, const struct timespec *, + const struct timespec *); + +void tmpfs_update(struct vnode *); +int tmpfs_truncate(struct vnode *, off_t); + +/* --------------------------------------------------------------------- */ + +/* + * Convenience macros to simplify some logical expressions. + */ +#define IMPLIES(a, b) (!(a) || (b)) +#define IFF(a, b) (IMPLIES(a, b) && IMPLIES(b, a)) + +/* --------------------------------------------------------------------- */ + +/* + * Checks that the directory entry pointed by 'de' matches the name 'name' + * with a length of 'len'. + */ +#define TMPFS_DIRENT_MATCHES(de, name, len) \ + (de->td_namelen == (uint16_t)len && \ + bcmp((de)->td_name, (name), (de)->td_namelen) == 0) + +/* --------------------------------------------------------------------- */ + +/* + * Ensures that the node pointed by 'node' is a directory and that its + * contents are consistent with respect to directories. + */ +#define TMPFS_VALIDATE_DIR(node) \ + KKASSERT((node)->tn_type == VDIR); \ + KKASSERT((node)->tn_size % sizeof(struct tmpfs_dirent) == 0); \ + KKASSERT((node)->tn_dir.tn_readdir_lastp == NULL || \ + tmpfs_dircookie((node)->tn_dir.tn_readdir_lastp) == (node)->tn_dir.tn_readdir_lastn); + +/* --------------------------------------------------------------------- */ + +/* + * Memory management stuff. + */ + +/* Amount of memory pages to reserve for the system (e.g., to not use by + * tmpfs). + * XXX: Should this be tunable through sysctl, for instance? */ +#define TMPFS_PAGES_RESERVED (4 * 1024 * 1024 / PAGE_SIZE) + +/* + * Returns information about the number of available memory pages, + * including physical and virtual ones. + * + * If 'total' is TRUE, the value returned is the total amount of memory + * pages configured for the system (either in use or free). + * If it is FALSE, the value returned is the amount of free memory pages. + * + * Remember to remove TMPFS_PAGES_RESERVED from the returned value to avoid + * excessive memory usage. + * + */ +static __inline size_t +tmpfs_mem_info(void) +{ + size_t size; + + size = vm_swap_size + vmstats.v_free_count + vmstats.v_inactive_count; + size -= size > vmstats.v_wire_count ? vmstats.v_wire_count : size; + return size; +} + +/* Returns the maximum size allowed for a tmpfs file system. This macro + * must be used instead of directly retrieving the value from tm_pages_max. + * The reason is that the size of a tmpfs file system is dynamic: it lets + * the user store files as long as there is enough free memory (including + * physical memory and swap space). Therefore, the amount of memory to be + * used is either the limit imposed by the user during mount time or the + * amount of available memory, whichever is lower. To avoid consuming all + * the memory for a given mount point, the system will always reserve a + * minimum of TMPFS_PAGES_RESERVED pages, which is also taken into account + * by this macro (see above). */ +static __inline size_t +TMPFS_PAGES_MAX(struct tmpfs_mount *tmp) +{ + size_t freepages; + + freepages = tmpfs_mem_info(); + freepages -= freepages < TMPFS_PAGES_RESERVED ? + freepages : TMPFS_PAGES_RESERVED; + + return MIN(tmp->tm_pages_max, freepages + tmp->tm_pages_used); +} + +/* Returns the available space for the given file system. */ +#define TMPFS_META_PAGES(tmp) (howmany((tmp)->tm_nodes_inuse * (sizeof(struct tmpfs_node) \ + + sizeof(struct tmpfs_dirent)), PAGE_SIZE)) +#define TMPFS_FILE_PAGES(tmp) ((tmp)->tm_pages_used) + +#define TMPFS_PAGES_AVAIL(tmp) (TMPFS_PAGES_MAX(tmp) > \ + TMPFS_META_PAGES(tmp)+TMPFS_FILE_PAGES(tmp)? \ + TMPFS_PAGES_MAX(tmp) - TMPFS_META_PAGES(tmp) \ + - TMPFS_FILE_PAGES(tmp):0) + +#endif + +/* --------------------------------------------------------------------- */ + +/* + * Macros/functions to convert from generic data structures to tmpfs + * specific ones. + */ + +static inline +struct tmpfs_mount * +VFS_TO_TMPFS(struct mount *mp) +{ + struct tmpfs_mount *tmp; + + KKASSERT((mp) != NULL && (mp)->mnt_data != NULL); + tmp = (struct tmpfs_mount *)(mp)->mnt_data; + return tmp; +} + +static inline +struct tmpfs_node * +VP_TO_TMPFS_NODE(struct vnode *vp) +{ + struct tmpfs_node *node; + + KKASSERT((vp) != NULL && (vp)->v_data != NULL); + node = (struct tmpfs_node *)vp->v_data; + return node; +} + +static inline +struct tmpfs_node * +VP_TO_TMPFS_DIR(struct vnode *vp) +{ + struct tmpfs_node *node; + + node = VP_TO_TMPFS_NODE(vp); + TMPFS_VALIDATE_DIR(node); + return node; +} + +/* --------------------------------------------------------------------- */ +/* + * buffer cache size + */ +#define BSIZE (off_t)16384 /* buffer cache size*/ +#define BMASK (off_t)(BSIZE - 1) + +#endif /* _VFS_TMPFS_TMPFS_H_ */ diff --git a/sys/vfs/tmpfs/tmpfs_args.h b/sys/vfs/tmpfs/tmpfs_args.h new file mode 100644 index 0000000000..6601ebdf54 --- /dev/null +++ b/sys/vfs/tmpfs/tmpfs_args.h @@ -0,0 +1,54 @@ +/* $NetBSD: tmpfs_args.h,v 1.3 2008/07/29 09:10:09 pooka Exp $ */ + +/* + * Copyright (c) 2005, 2006, 2007 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _VFS_TMPFS_TMPFS_ARGS_H_ +#define _VFS_TMPFS_TMPFS_ARGS_H_ + +/* + * This structure is used to communicate mount parameters between userland + * and kernel space. + */ +#define TMPFS_ARGS_VERSION 1 +struct tmpfs_args { + int ta_version; + + /* Size counters. */ + ino_t ta_nodes_max; + off_t ta_size_max; + + /* Root node attributes. */ + uid_t ta_root_uid; + gid_t ta_root_gid; + mode_t ta_root_mode; +}; + +#endif /* _VFS_TMPFS_TMPFS_ARGS_H_ */ diff --git a/sys/vfs/tmpfs/tmpfs_fifoops.c b/sys/vfs/tmpfs/tmpfs_fifoops.c new file mode 100644 index 0000000000..0abdb2606d --- /dev/null +++ b/sys/vfs/tmpfs/tmpfs_fifoops.c @@ -0,0 +1,99 @@ +/* $NetBSD: tmpfs_fifoops.c,v 1.5 2005/12/11 12:24:29 christos Exp $ */ + +/*- + * Copyright (c) 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * tmpfs vnode interface for named pipes. + */ +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_fifo_kqfilter(struct vop_kqfilter_args *ap) +{ + struct vnode *vp; + struct tmpfs_node *node; + + vp = ap->a_vp; + node = VP_TO_TMPFS_NODE(vp); + + switch (ap->a_kn->kn_filter){ + case EVFILT_READ: + node->tn_status |= TMPFS_NODE_ACCESSED; + break; + case EVFILT_WRITE: + node->tn_status |= TMPFS_NODE_MODIFIED; + break; + } + + return fifo_vnode_vops.vop_kqfilter(ap); +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_fifo_close(struct vop_close_args *v) +{ + struct tmpfs_node *node; + node = VP_TO_TMPFS_NODE(v->a_vp); + node->tn_status |= TMPFS_NODE_ACCESSED; + + tmpfs_update(v->a_vp); + return fifo_vnode_vops.vop_close(v); +} + +/* + * vnode operations vector used for fifos stored in a tmpfs file system. + */ +struct vop_ops tmpfs_fifo_vops = { + .vop_default = fifo_vnoperate, + .vop_close = tmpfs_fifo_close, + .vop_reclaim = tmpfs_reclaim, + .vop_access = tmpfs_access, + .vop_getattr = tmpfs_getattr, + .vop_setattr = tmpfs_setattr, + .vop_kqfilter = tmpfs_fifo_kqfilter, +}; diff --git a/sys/vfs/tmpfs/tmpfs_fifoops.h b/sys/vfs/tmpfs/tmpfs_fifoops.h new file mode 100644 index 0000000000..767dd72ac1 --- /dev/null +++ b/sys/vfs/tmpfs/tmpfs_fifoops.h @@ -0,0 +1,53 @@ +/* $NetBSD: tmpfs_fifoops.h,v 1.4 2005/12/03 17:34:44 christos Exp $ */ + +/*- + * Copyright (c) 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/fs/tmpfs/tmpfs_fifoops.h,v 1.4 2008/09/03 18:53:48 delphij Exp $ + */ + +#ifndef _VFS_TMPFS_TMPFS_FIFOOPS_H_ +#define _VFS_TMPFS_TMPFS_FIFOOPS_H_ + +#if !defined(_KERNEL) +#error not supposed to be exposed to userland. +#endif + +#include + +/* --------------------------------------------------------------------- */ + +/* + * Declarations for tmpfs_fifoops.c. + */ + +extern struct vop_vector tmpfs_fifoop_entries; + +/* --------------------------------------------------------------------- */ +#endif /* _VFS_TMPFS_TMPFS_FIFOOPS_H_ */ diff --git a/sys/vfs/tmpfs/tmpfs_subr.c b/sys/vfs/tmpfs/tmpfs_subr.c new file mode 100644 index 0000000000..371c103274 --- /dev/null +++ b/sys/vfs/tmpfs/tmpfs_subr.c @@ -0,0 +1,1414 @@ +/* $NetBSD: tmpfs_subr.c,v 1.35 2007/07/09 21:10:50 ad Exp $ */ + +/*- + * Copyright (c) 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Efficient memory file system supporting functions. + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +static ino_t t_ino = 2; +static struct spinlock ino_lock; +static ino_t tmpfs_fetch_ino(void); + +/* --------------------------------------------------------------------- */ + +/* + * Allocates a new node of type 'type' inside the 'tmp' mount point, with + * its owner set to 'uid', its group to 'gid' and its mode set to 'mode', + * using the credentials of the process 'p'. + * + * If the node type is set to 'VDIR', then the parent parameter must point + * to the parent directory of the node being created. It may only be NULL + * while allocating the root node. + * + * If the node type is set to 'VBLK' or 'VCHR', then the rdev parameter + * specifies the device the node represents. + * + * If the node type is set to 'VLNK', then the parameter target specifies + * the file name of the target file for the symbolic link that is being + * created. + * + * Note that new nodes are retrieved from the available list if it has + * items or, if it is empty, from the node pool as long as there is enough + * space to create them. + * + * Returns zero on success or an appropriate error code on failure. + */ +int +tmpfs_alloc_node(struct tmpfs_mount *tmp, enum vtype type, + uid_t uid, gid_t gid, mode_t mode, struct tmpfs_node *parent, + char *target, int rmajor, int rminor, struct tmpfs_node **node) +{ + struct tmpfs_node *nnode; + struct timespec ts; + udev_t rdev; + + /* If the root directory of the 'tmp' file system is not yet + * allocated, this must be the request to do it. */ + KKASSERT(IMPLIES(tmp->tm_root == NULL, parent == NULL && type == VDIR)); + + KKASSERT(IFF(type == VLNK, target != NULL)); + KKASSERT(IFF(type == VBLK || type == VCHR, rmajor != VNOVAL)); + + if (tmp->tm_nodes_inuse > tmp->tm_nodes_max) + return (ENOSPC); + + nnode = (struct tmpfs_node *)objcache_get(tmp->tm_node_pool, M_WAITOK); + + /* Generic initialization. */ + nnode->tn_type = type; + vfs_timestamp(&ts); + nnode->tn_ctime = nnode->tn_mtime = nnode->tn_atime + = ts.tv_sec; + nnode->tn_ctimensec = nnode->tn_mtimensec = nnode->tn_atimensec + = ts.tv_nsec; + nnode->tn_uid = uid; + nnode->tn_gid = gid; + nnode->tn_mode = mode; + nnode->tn_id = tmpfs_fetch_ino(); + + /* Type-specific initialization. */ + switch (nnode->tn_type) { + case VBLK: + case VCHR: + rdev = makeudev(rmajor, rminor); + if (rdev == NOUDEV) { + return(EINVAL); + } + nnode->tn_rdev = rdev; + break; + + case VDIR: + TAILQ_INIT(&nnode->tn_dir.tn_dirhead); + KKASSERT(parent != nnode); + KKASSERT(IMPLIES(parent == NULL, tmp->tm_root == NULL)); + nnode->tn_dir.tn_parent = (parent == NULL) ? nnode : parent; + nnode->tn_dir.tn_readdir_lastn = 0; + nnode->tn_dir.tn_readdir_lastp = NULL; + nnode->tn_links++; + nnode->tn_size = 0; + TMPFS_NODE_LOCK(nnode->tn_dir.tn_parent); + nnode->tn_dir.tn_parent->tn_links++; + TMPFS_NODE_UNLOCK(nnode->tn_dir.tn_parent); + break; + + case VFIFO: + /* FALLTHROUGH */ + case VSOCK: + break; + + case VLNK: + KKASSERT((strlen(target) +1) < MAXPATHLEN); + nnode->tn_size = strlen(target) +1; + nnode->tn_link = kmalloc(nnode->tn_size, M_TMPFSNAME, + M_WAITOK); + bcopy(target, nnode->tn_link, nnode->tn_size); + nnode->tn_link[nnode->tn_size] = '\0'; + break; + + case VREG: + nnode->tn_reg.tn_aobj = + vm_pager_allocate(OBJT_SWAP, NULL, 0, VM_PROT_DEFAULT, 0); + nnode->tn_reg.tn_aobj_pages = 0; + nnode->tn_size = 0; + break; + + default: + panic("tmpfs_alloc_node: type %p %d", nnode, (int)nnode->tn_type); + } + + TMPFS_NODE_LOCK(nnode); + LIST_INSERT_HEAD(&tmp->tm_nodes_used, nnode, tn_entries); + TMPFS_LOCK(tmp); + tmp->tm_nodes_inuse++; + TMPFS_UNLOCK(tmp); + TMPFS_NODE_UNLOCK(nnode); + + *node = nnode; + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* + * Destroys the node pointed to by node from the file system 'tmp'. + * If the node does not belong to the given mount point, the results are + * unpredicted. + * + * If the node references a directory; no entries are allowed because + * their removal could need a recursive algorithm, something forbidden in + * kernel space. Furthermore, there is not need to provide such + * functionality (recursive removal) because the only primitives offered + * to the user are the removal of empty directories and the deletion of + * individual files. + * + * Note that nodes are not really deleted; in fact, when a node has been + * allocated, it cannot be deleted during the whole life of the file + * system. Instead, they are moved to the available list and remain there + * until reused. + */ +void +tmpfs_free_node(struct tmpfs_mount *tmp, struct tmpfs_node *node) +{ + size_t pages = 0; + + TMPFS_NODE_LOCK(node); + +#ifdef INVARIANTS + TMPFS_ASSERT_ELOCKED(node); + KKASSERT(node->tn_vnode == NULL); + KKASSERT((node->tn_vpstate & TMPFS_VNODE_ALLOCATING) == 0); +#endif + + LIST_REMOVE(node, tn_entries); + TMPFS_LOCK(tmp); + tmp->tm_nodes_inuse--; + TMPFS_UNLOCK(tmp); + + switch (node->tn_type) { + case VNON: + /* Do not do anything. VNON is provided to let the + * allocation routine clean itself easily by avoiding + * duplicating code in it. */ + /* FALLTHROUGH */ + case VBLK: + /* FALLTHROUGH */ + case VCHR: + /* FALLTHROUGH */ + break; + case VDIR: + node->tn_links--; + node->tn_size = 0; + TMPFS_NODE_LOCK(node->tn_dir.tn_parent); + node->tn_dir.tn_parent->tn_links--; + TMPFS_NODE_UNLOCK(node->tn_dir.tn_parent); + break; + case VFIFO: + /* FALLTHROUGH */ + case VSOCK: + break; + + case VLNK: + kfree(node->tn_link, M_TMPFSNAME); + break; + + case VREG: + if (node->tn_reg.tn_aobj != NULL) + vm_pager_deallocate(node->tn_reg.tn_aobj); + node->tn_reg.tn_aobj = NULL; + pages = node->tn_reg.tn_aobj_pages; + break; + + default: + panic("tmpfs_free_node: type %p %d", node, (int)node->tn_type); + } + + objcache_put(tmp->tm_node_pool, node); + + TMPFS_LOCK(tmp); + tmp->tm_pages_used -= pages; + TMPFS_UNLOCK(tmp); + TMPFS_NODE_UNLOCK(node); +} + +/* --------------------------------------------------------------------- */ + +/* + * Allocates a new directory entry for the node node with a name of name. + * The new directory entry is returned in *de. + * + * The link count of node is increased by one to reflect the new object + * referencing it. + * + * Returns zero on success or an appropriate error code on failure. + */ +int +tmpfs_alloc_dirent(struct tmpfs_mount *tmp, struct tmpfs_node *node, + const char *name, uint16_t len, struct tmpfs_dirent **de) +{ + struct tmpfs_dirent *nde; + + + nde = (struct tmpfs_dirent *)objcache_get(tmp->tm_dirent_pool, M_WAITOK); + nde->td_name = kmalloc(len, M_TMPFSNAME, M_WAITOK); + nde->td_namelen = len; + bcopy(name, nde->td_name, len); + nde->td_name[len] = '\0'; + + nde->td_node = node; + + TMPFS_NODE_LOCK(node); + node->tn_links++; + TMPFS_NODE_UNLOCK(node); + + *de = nde; + + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* + * Frees a directory entry. It is the caller's responsibility to destroy + * the node referenced by it if needed. + * + * The link count of node is decreased by one to reflect the removal of an + * object that referenced it. This only happens if 'node_exists' is true; + * otherwise the function will not access the node referred to by the + * directory entry, as it may already have been released from the outside. + */ +void +tmpfs_free_dirent(struct tmpfs_mount *tmp, struct tmpfs_dirent *de, + boolean_t node_exists) +{ + if (node_exists) { + struct tmpfs_node *node; + + node = de->td_node; + + TMPFS_NODE_LOCK(node); + TMPFS_ASSERT_ELOCKED(node); + KKASSERT(node->tn_links > 0); + node->tn_links--; + TMPFS_NODE_UNLOCK(node); + } + + kfree(de->td_name, M_TMPFSNAME); + objcache_put(tmp->tm_dirent_pool, de); +} + +/* --------------------------------------------------------------------- */ + +/* + * Allocates a new vnode for the node node or returns a new reference to + * an existing one if the node had already a vnode referencing it. The + * resulting locked vnode is returned in *vpp. + * + * Returns zero on success or an appropriate error code on failure. + */ +int +tmpfs_alloc_vp(struct mount *mp, struct tmpfs_node *node, int lkflag, + struct vnode **vpp) +{ + int error = 0; + struct vnode *vp; + +loop: + TMPFS_NODE_LOCK(node); + if ((vp = node->tn_vnode) != NULL) { + KKASSERT((node->tn_vpstate & TMPFS_VNODE_DOOMED) == 0); + TMPFS_NODE_UNLOCK(node); + (void) vget(vp, lkflag | LK_EXCLUSIVE | LK_RETRY); + + /* + * Make sure the vnode is still there after + * getting the interlock to avoid racing a free. + */ + if (node->tn_vnode == NULL || node->tn_vnode != vp) { + vput(vp); + goto loop; + } + + goto out; + } + + if ((node->tn_vpstate & TMPFS_VNODE_DOOMED) || + (node->tn_type == VDIR && node->tn_dir.tn_parent == NULL)) { + TMPFS_NODE_UNLOCK(node); + error = ENOENT; + vp = NULL; + goto out; + } + + /* + * otherwise lock the vp list while we call getnewvnode + * since that can block. + */ + if (node->tn_vpstate & TMPFS_VNODE_ALLOCATING) { + node->tn_vpstate |= TMPFS_VNODE_WANT; + error = tsleep((caddr_t) &node->tn_vpstate, + PINTERLOCKED | PCATCH, "tmpfs_alloc_vp", 0); + TMPFS_NODE_UNLOCK(node); + if (error) + return error; + + goto loop; + } else + node->tn_vpstate |= TMPFS_VNODE_ALLOCATING; + + TMPFS_NODE_UNLOCK(node); + + /* Get a new vnode and associate it with our node. */ + error = getnewvnode(VT_TMPFS, mp, &vp, VLKTIMEOUT, LK_CANRECURSE); + if (error != 0) + goto unlock; + KKASSERT(vp != NULL); + + vp->v_data = node; + vp->v_type = node->tn_type; + + /* Type-specific initialization. */ + switch (node->tn_type) { + case VBLK: + /* FALLTHROUGH */ + case VCHR: + /* FALLTHROUGH */ + case VSOCK: + break; + case VREG: + vinitvmio(vp, node->tn_size); + break; + case VLNK: + if (node->tn_size >= vp->v_mount->mnt_maxsymlinklen) + vinitvmio(vp, node->tn_size); + break; + case VFIFO: + vp->v_ops = &mp->mnt_vn_fifo_ops; + break; + case VDIR: + vinitvmio(vp, node->tn_size); + KKASSERT(node->tn_dir.tn_parent != NULL); + break; + + default: + panic("tmpfs_alloc_vp: type %p %d", node, (int)node->tn_type); + } + + insmntque(vp, mp); + +unlock: + TMPFS_NODE_LOCK(node); + + KKASSERT(node->tn_vpstate & TMPFS_VNODE_ALLOCATING); + node->tn_vpstate &= ~TMPFS_VNODE_ALLOCATING; + node->tn_vnode = vp; + + if (node->tn_vpstate & TMPFS_VNODE_WANT) { + node->tn_vpstate &= ~TMPFS_VNODE_WANT; + TMPFS_NODE_UNLOCK(node); + wakeup((caddr_t) &node->tn_vpstate); + } else + TMPFS_NODE_UNLOCK(node); + +out: + *vpp = vp; + + KKASSERT(IFF(error == 0, *vpp != NULL && vn_islocked(*vpp))); +#ifdef INVARIANTS + TMPFS_NODE_LOCK(node); + KKASSERT(*vpp == node->tn_vnode); + TMPFS_NODE_UNLOCK(node); +#endif + + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * Destroys the association between the vnode vp and the node it + * references. + */ +void +tmpfs_free_vp(struct vnode *vp) +{ + struct tmpfs_node *node; + + node = VP_TO_TMPFS_NODE(vp); + + TMPFS_NODE_LOCK(node); + KKASSERT(lockcount(TMPFS_NODE_MTX(node)) > 0); + node->tn_vnode = NULL; + TMPFS_NODE_UNLOCK(node); + vp->v_data = NULL; +} + +/* --------------------------------------------------------------------- */ + +/* + * Allocates a new file of type 'type' and adds it to the parent directory + * 'dvp'; this addition is done using the component name given in 'cnp'. + * The ownership of the new file is automatically assigned based on the + * credentials of the caller (through 'cnp'), the group is set based on + * the parent directory and the mode is determined from the 'vap' argument. + * If successful, *vpp holds a vnode to the newly created file and zero + * is returned. Otherwise *vpp is NULL and the function returns an + * appropriate error code. + */ +int +tmpfs_alloc_file(struct vnode *dvp, struct vnode **vpp, struct vattr *vap, + struct namecache *ncp, struct ucred *cred, char *target) +{ + int error; + struct tmpfs_dirent *de; + struct tmpfs_mount *tmp; + struct tmpfs_node *dnode; + struct tmpfs_node *node; + struct tmpfs_node *parent; + + tmp = VFS_TO_TMPFS(dvp->v_mount); + dnode = VP_TO_TMPFS_DIR(dvp); + *vpp = NULL; + + /* If the entry we are creating is a directory, we cannot overflow + * the number of links of its parent, because it will get a new + * link. */ + if (vap->va_type == VDIR) { + /* Ensure that we do not overflow the maximum number of links + * imposed by the system. */ + KKASSERT(dnode->tn_links <= LINK_MAX); + if (dnode->tn_links == LINK_MAX) { + return EMLINK; + } + + parent = dnode; + KKASSERT(parent != NULL); + } else + parent = NULL; + + /* Allocate a node that represents the new file. */ + error = tmpfs_alloc_node(tmp, vap->va_type, cred->cr_uid, + dnode->tn_gid, vap->va_mode, parent, target, vap->va_rmajor, vap->va_rminor, &node); + if (error != 0) + return error; + + /* Allocate a directory entry that points to the new file. */ + error = tmpfs_alloc_dirent(tmp, node, ncp->nc_name, ncp->nc_nlen, + &de); + if (error != 0) { + tmpfs_free_node(tmp, node); + return error; + } + + /* Allocate a vnode for the new file. */ + error = tmpfs_alloc_vp(dvp->v_mount, node, LK_EXCLUSIVE, vpp); + if (error != 0) { + tmpfs_free_dirent(tmp, de, TRUE); + tmpfs_free_node(tmp, node); + return error; + } + + /* Now that all required items are allocated, we can proceed to + * insert the new node into the directory, an operation that + * cannot fail. */ + tmpfs_dir_attach(dvp, de); + + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * Attaches the directory entry de to the directory represented by vp. + * Note that this does not change the link count of the node pointed by + * the directory entry, as this is done by tmpfs_alloc_dirent. + */ +void +tmpfs_dir_attach(struct vnode *vp, struct tmpfs_dirent *de) +{ + struct tmpfs_node *dnode; + + dnode = VP_TO_TMPFS_DIR(vp); + + crit_enter(); + TAILQ_INSERT_TAIL(&dnode->tn_dir.tn_dirhead, de, td_entries); + crit_exit(); + + TMPFS_NODE_LOCK(dnode); + TMPFS_ASSERT_ELOCKED(dnode); + dnode->tn_size += sizeof(struct tmpfs_dirent); + dnode->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \ + TMPFS_NODE_MODIFIED; + TMPFS_NODE_UNLOCK(dnode); +} + +/* --------------------------------------------------------------------- */ + +/* + * Detaches the directory entry de from the directory represented by vp. + * Note that this does not change the link count of the node pointed by + * the directory entry, as this is done by tmpfs_free_dirent. + */ +void +tmpfs_dir_detach(struct vnode *vp, struct tmpfs_dirent *de) +{ + struct tmpfs_node *dnode; + + dnode = VP_TO_TMPFS_DIR(vp); + + + crit_enter(); + if (dnode->tn_dir.tn_readdir_lastp == de) { + dnode->tn_dir.tn_readdir_lastn = 0; + dnode->tn_dir.tn_readdir_lastp = NULL; + } + + TAILQ_REMOVE(&dnode->tn_dir.tn_dirhead, de, td_entries); + crit_exit(); + + TMPFS_NODE_LOCK(dnode); + TMPFS_ASSERT_ELOCKED(dnode); + dnode->tn_size -= sizeof(struct tmpfs_dirent); + dnode->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \ + TMPFS_NODE_MODIFIED; + TMPFS_NODE_UNLOCK(dnode); +} + +/* --------------------------------------------------------------------- */ + +/* + * Looks for a directory entry in the directory represented by node. + * 'ncp' describes the name of the entry to look for. Note that the . + * and .. components are not allowed as they do not physically exist + * within directories. + * + * Returns a pointer to the entry when found, otherwise NULL. + */ +struct tmpfs_dirent * +tmpfs_dir_lookup(struct tmpfs_node *node, struct tmpfs_node *f, + struct namecache *ncp) +{ + void *found; + struct tmpfs_dirent *de; + int len = ncp->nc_nlen; + + TMPFS_VALIDATE_DIR(node); + + found = 0; + TAILQ_FOREACH(de, &node->tn_dir.tn_dirhead, td_entries) { + if (f != NULL && de->td_node != f) + continue; + if (len == de->td_namelen) { + if (!memcmp(ncp->nc_name, de->td_name, len)) { + found = node; + break; + } + } + } + + TMPFS_NODE_LOCK(node); + node->tn_status |= TMPFS_NODE_ACCESSED; + TMPFS_NODE_UNLOCK(node); + + return found ? de : NULL; +} + +/* --------------------------------------------------------------------- */ + +/* + * Helper function for tmpfs_readdir. Creates a '.' entry for the given + * directory and returns it in the uio space. The function returns 0 + * on success, -1 if there was not enough space in the uio structure to + * hold the directory entry or an appropriate error code if another + * error happens. + */ +int +tmpfs_dir_getdotdent(struct tmpfs_node *node, struct uio *uio) +{ + int error; + struct dirent dent; + int dirsize; + + TMPFS_VALIDATE_DIR(node); + KKASSERT(uio->uio_offset == TMPFS_DIRCOOKIE_DOT); + + dent.d_ino = node->tn_id; + dent.d_type = DT_DIR; + dent.d_namlen = 1; + dent.d_name[0] = '.'; + dent.d_name[1] = '\0'; + dirsize = _DIRENT_DIRSIZ(&dent); + + if (dirsize > uio->uio_resid) + error = -1; + else { + error = uiomove((caddr_t)&dent, dirsize, uio); + if (error == 0) + uio->uio_offset = TMPFS_DIRCOOKIE_DOTDOT; + } + + node->tn_status |= TMPFS_NODE_ACCESSED; + + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * Helper function for tmpfs_readdir. Creates a '..' entry for the given + * directory and returns it in the uio space. The function returns 0 + * on success, -1 if there was not enough space in the uio structure to + * hold the directory entry or an appropriate error code if another + * error happens. + */ +int +tmpfs_dir_getdotdotdent(struct tmpfs_node *node, struct uio *uio) +{ + int error; + struct dirent dent; + int dirsize; + + TMPFS_VALIDATE_DIR(node); + KKASSERT(uio->uio_offset == TMPFS_DIRCOOKIE_DOTDOT); + + /* + * Return ENOENT if the current node is already removed. + */ + if (node->tn_dir.tn_parent == NULL) { + return (ENOENT); + } + + TMPFS_NODE_LOCK(node->tn_dir.tn_parent); + dent.d_ino = node->tn_dir.tn_parent->tn_id; + TMPFS_NODE_UNLOCK(node->tn_dir.tn_parent); + + dent.d_type = DT_DIR; + dent.d_namlen = 2; + dent.d_name[0] = '.'; + dent.d_name[1] = '.'; + dent.d_name[2] = '\0'; + dirsize = _DIRENT_DIRSIZ(&dent); + + if (dirsize > uio->uio_resid) + error = -1; + else { + error = uiomove((caddr_t)&dent, dirsize, uio); + if (error == 0) { + struct tmpfs_dirent *de; + + de = TAILQ_FIRST(&node->tn_dir.tn_dirhead); + if (de == NULL) + uio->uio_offset = TMPFS_DIRCOOKIE_EOF; + else + uio->uio_offset = tmpfs_dircookie(de); + } + } + + node->tn_status |= TMPFS_NODE_ACCESSED; + + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * Lookup a directory entry by its associated cookie. + */ +struct tmpfs_dirent * +tmpfs_dir_lookupbycookie(struct tmpfs_node *node, off_t cookie) +{ + struct tmpfs_dirent *de; + + if (cookie == node->tn_dir.tn_readdir_lastn && + node->tn_dir.tn_readdir_lastp != NULL) { + return node->tn_dir.tn_readdir_lastp; + } + + TAILQ_FOREACH(de, &node->tn_dir.tn_dirhead, td_entries) { + if (tmpfs_dircookie(de) == cookie) { + break; + } + } + + return de; +} + +/* --------------------------------------------------------------------- */ + +/* + * Helper function for tmpfs_readdir. Returns as much directory entries + * as can fit in the uio space. The read starts at uio->uio_offset. + * The function returns 0 on success, -1 if there was not enough space + * in the uio structure to hold the directory entry or an appropriate + * error code if another error happens. + */ +int +tmpfs_dir_getdents(struct tmpfs_node *node, struct uio *uio, off_t *cntp) +{ + int error; + off_t startcookie; + struct tmpfs_dirent *de; + + TMPFS_VALIDATE_DIR(node); + + /* Locate the first directory entry we have to return. We have cached + * the last readdir in the node, so use those values if appropriate. + * Otherwise do a linear scan to find the requested entry. */ + startcookie = uio->uio_offset; + KKASSERT(startcookie != TMPFS_DIRCOOKIE_DOT); + KKASSERT(startcookie != TMPFS_DIRCOOKIE_DOTDOT); + if (startcookie == TMPFS_DIRCOOKIE_EOF) { + return 0; + } else { + de = tmpfs_dir_lookupbycookie(node, startcookie); + } + if (de == NULL) { + return EINVAL; + } + + /* Read as much entries as possible; i.e., until we reach the end of + * the directory or we exhaust uio space. */ + do { + struct dirent d; + int reclen; + + /* Create a dirent structure representing the current + * tmpfs_node and fill it. */ + d.d_ino = de->td_node->tn_id; + switch (de->td_node->tn_type) { + case VBLK: + d.d_type = DT_BLK; + break; + + case VCHR: + d.d_type = DT_CHR; + break; + + case VDIR: + d.d_type = DT_DIR; + break; + + case VFIFO: + d.d_type = DT_FIFO; + break; + + case VLNK: + d.d_type = DT_LNK; + break; + + case VREG: + d.d_type = DT_REG; + break; + + case VSOCK: + d.d_type = DT_SOCK; + break; + + default: + panic("tmpfs_dir_getdents: type %p %d", + de->td_node, (int)de->td_node->tn_type); + } + d.d_namlen = de->td_namelen; + KKASSERT(de->td_namelen < sizeof(d.d_name)); + bcopy(de->td_name, d.d_name, d.d_namlen); + d.d_name[d.d_namlen] = '\0'; + reclen = _DIRENT_RECLEN(d.d_namlen); + + /* Stop reading if the directory entry we are treating is + * bigger than the amount of data that can be returned. */ + if (reclen > uio->uio_resid) { + error = -1; + break; + } + + /* Copy the new dirent structure into the output buffer and + * advance pointers. */ + error = uiomove((caddr_t)&d, reclen, uio); + + (*cntp)++; + de = TAILQ_NEXT(de, td_entries); + } while (error == 0 && uio->uio_resid > 0 && de != NULL); + + /* Update the offset and cache. */ + if (de == NULL) { + uio->uio_offset = TMPFS_DIRCOOKIE_EOF; + node->tn_dir.tn_readdir_lastn = 0; + node->tn_dir.tn_readdir_lastp = NULL; + } else { + node->tn_dir.tn_readdir_lastn = uio->uio_offset = tmpfs_dircookie(de); + node->tn_dir.tn_readdir_lastp = de; + } + node->tn_status |= TMPFS_NODE_ACCESSED; + + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * Resizes the aobj associated to the regular file pointed to by vp to + * the size newsize. 'vp' must point to a vnode that represents a regular + * file. 'newsize' must be positive. + * + * pass trivial as 1 when buf content will be overwritten, otherwise set 0 + * to be zero filled. + * + * Returns zero on success or an appropriate error code on failure. + */ +int +tmpfs_reg_resize(struct vnode *vp, off_t newsize, int trivial) +{ + int error; + size_t newpages, oldpages; + struct tmpfs_mount *tmp; + struct tmpfs_node *node; + off_t oldsize; + int biosize = vp->v_mount->mnt_stat.f_iosize; + +#ifdef INVARIANTS + KKASSERT(vp->v_type == VREG); + KKASSERT(newsize >= 0); +#endif + + node = VP_TO_TMPFS_NODE(vp); + tmp = VFS_TO_TMPFS(vp->v_mount); + + /* Convert the old and new sizes to the number of pages needed to + * store them. It may happen that we do not need to do anything + * because the last allocated page can accommodate the change on + * its own. */ + oldsize = node->tn_size; + oldpages = round_page(oldsize) / PAGE_SIZE; + KKASSERT(oldpages == node->tn_reg.tn_aobj_pages); + newpages = round_page(newsize) / PAGE_SIZE; + + if (newpages > oldpages && + newpages - oldpages > TMPFS_PAGES_AVAIL(tmp)) { + error = ENOSPC; + goto out; + } + + TMPFS_LOCK(tmp); + tmp->tm_pages_used += (newpages - oldpages); + TMPFS_UNLOCK(tmp); + + TMPFS_NODE_LOCK(node); + node->tn_reg.tn_aobj_pages = newpages; + node->tn_size = newsize; + TMPFS_NODE_UNLOCK(node); + + if (newsize < oldsize) + error = nvtruncbuf(vp, newsize, biosize, -1); + else + error = nvextendbuf(vp, oldsize, newsize, biosize, biosize, + -1, -1, trivial); + +out: + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * Change flags of the given vnode. + * Caller should execute tmpfs_update on vp after a successful execution. + * The vnode must be locked on entry and remain locked on exit. + */ +int +tmpfs_chflags(struct vnode *vp, int flags, struct ucred *cred) +{ + int error; + struct tmpfs_node *node; + int fmode, mode; + + KKASSERT(vn_islocked(vp)); + + node = VP_TO_TMPFS_NODE(vp); + + /* Disallow this operation if the file system is mounted read-only. */ + if (vp->v_mount->mnt_flag & MNT_RDONLY) + return EROFS; + + fmode = FFLAGS(node->tn_flags); + mode = 0; + if (((fmode & (FREAD | FWRITE)) == 0) || (fmode & O_CREAT)) + return EINVAL; + if (fmode & (FWRITE | O_TRUNC)) { + if (vp->v_type == VDIR) { + return EISDIR; + } + error = vn_writechk(vp, NULL); + if (error) + return (error); + + mode |= VWRITE; + } + if (fmode & FREAD) + mode |= VREAD; + if (mode) { + error = VOP_ACCESS(vp, mode, cred); + if (error) + return (error); + } + /* + * Unprivileged processes are not permitted to unset system + * flags, or modify flags if any system flags are set. + */ + TMPFS_NODE_LOCK(node); + if (!priv_check_cred(cred, PRIV_VFS_SYSFLAGS, 0)) { +#if 0 + if (node->tn_flags + & (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND)) { + error = securelevel_gt(cred, 0); + if (error) + return (error); + } + /* Snapshot flag cannot be set or cleared */ + if (((flags & SF_SNAPSHOT) != 0 && + (node->tn_flags & SF_SNAPSHOT) == 0) || + ((flags & SF_SNAPSHOT) == 0 && + (node->tn_flags & SF_SNAPSHOT) != 0)) + return (EPERM); +#endif + node->tn_flags = flags; + } else { + if (node->tn_flags + & (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND) || + (flags & UF_SETTABLE) != flags) + return (EPERM); + node->tn_flags &= SF_SETTABLE; + node->tn_flags |= (flags & UF_SETTABLE); + } + node->tn_status |= TMPFS_NODE_CHANGED; + TMPFS_NODE_UNLOCK(node); + + KKASSERT(vn_islocked(vp)); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* + * Change access mode on the given vnode. + * Caller should execute tmpfs_update on vp after a successful execution. + * The vnode must be locked on entry and remain locked on exit. + */ +int +tmpfs_chmod(struct vnode *vp, mode_t mode, struct ucred *cred) +{ + int error; + struct tmpfs_node *node; + int fmode, accmode; + + KKASSERT(vn_islocked(vp)); + + node = VP_TO_TMPFS_NODE(vp); + + /* Disallow this operation if the file system is mounted read-only. */ + if (vp->v_mount->mnt_flag & MNT_RDONLY) + return EROFS; + + /* Immutable or append-only files cannot be modified, either. */ + if (node->tn_flags & (IMMUTABLE | APPEND)) + return EPERM; + + fmode = FFLAGS(node->tn_flags); + accmode = 0; + if (((fmode & (FREAD | FWRITE)) == 0) || (fmode & O_CREAT)) + return EINVAL; + if (fmode & (FWRITE | O_TRUNC)) { + if (vp->v_type == VDIR) { + return EISDIR; + } + error = vn_writechk(vp, NULL); + if (error) + return (error); + + accmode |= VWRITE; + } + if (fmode & FREAD) + accmode |= VREAD; + if (accmode) { + error = VOP_ACCESS(vp, accmode, cred); + if (error) + return (error); + } + + /* + * Privileged processes may set the sticky bit on non-directories, + * as well as set the setgid bit on a file with a group that the + * process is not a member of. + */ + if (vp->v_type != VDIR && (mode & S_ISTXT)) { + if (priv_check_cred(cred, PRIV_VFS_STICKYFILE, 0)) + return (EFTYPE); + } + if (!groupmember(node->tn_gid, cred) && (mode & S_ISGID)) { + error = priv_check_cred(cred, PRIV_VFS_SETGID, 0); + if (error) + return (error); + } + + + TMPFS_NODE_LOCK(node); + node->tn_mode &= ~ALLPERMS; + node->tn_mode |= mode & ALLPERMS; + + node->tn_status |= TMPFS_NODE_CHANGED; + TMPFS_NODE_UNLOCK(node); + + KKASSERT(vn_islocked(vp)); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* + * Change ownership of the given vnode. At least one of uid or gid must + * be different than VNOVAL. If one is set to that value, the attribute + * is unchanged. + * Caller should execute tmpfs_update on vp after a successful execution. + * The vnode must be locked on entry and remain locked on exit. + */ +int +tmpfs_chown(struct vnode *vp, uid_t uid, gid_t gid, struct ucred *cred) +{ + int error; + struct tmpfs_node *node; + uid_t ouid; + gid_t ogid; + int fmode, mode; + + KKASSERT(vn_islocked(vp)); + + node = VP_TO_TMPFS_NODE(vp); + + /* Assign default values if they are unknown. */ + KKASSERT(uid != VNOVAL || gid != VNOVAL); + if (uid == VNOVAL) + uid = node->tn_uid; + if (gid == VNOVAL) + gid = node->tn_gid; + KKASSERT(uid != VNOVAL && gid != VNOVAL); + + /* Disallow this operation if the file system is mounted read-only. */ + if (vp->v_mount->mnt_flag & MNT_RDONLY) + return EROFS; + + /* Immutable or append-only files cannot be modified, either. */ + if (node->tn_flags & (IMMUTABLE | APPEND)) + return EPERM; + + fmode = FFLAGS(node->tn_flags); + mode = 0; + if (((fmode & (FREAD | FWRITE)) == 0) || (fmode & O_CREAT)) + return EINVAL; + if (fmode & (FWRITE | O_TRUNC)) { + if (vp->v_type == VDIR) { + return EISDIR; + } + error = vn_writechk(vp, NULL); + if (error) + return (error); + + mode |= VWRITE; + } + if (fmode & FREAD) + mode |= VREAD; + if (mode) { + error = VOP_ACCESS(vp, mode, cred); + if (error) + return (error); + } + + /* + * To change the owner of a file, or change the group of a file to a + * group of which we are not a member, the caller must have + * privilege. + */ + if ((uid != node->tn_uid || + (gid != node->tn_gid && !groupmember(gid, cred))) && + (error = priv_check_cred(cred, PRIV_VFS_CHOWN, 0))) + return (error); + + ogid = node->tn_gid; + ouid = node->tn_uid; + + TMPFS_NODE_LOCK(node); + node->tn_uid = uid; + node->tn_gid = gid; + + node->tn_status |= TMPFS_NODE_CHANGED; + + if ((node->tn_mode & (S_ISUID | S_ISGID)) && (ouid != uid || ogid != gid)) { + if (priv_check_cred(cred, PRIV_VFS_RETAINSUGID, 0)) + node->tn_mode &= ~(S_ISUID | S_ISGID); + } + TMPFS_NODE_UNLOCK(node); + + KKASSERT(vn_islocked(vp)); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* + * Change size of the given vnode. + * Caller should execute tmpfs_update on vp after a successful execution. + * The vnode must be locked on entry and remain locked on exit. + */ +int +tmpfs_chsize(struct vnode *vp, u_quad_t size, struct ucred *cred) +{ + int error; + struct tmpfs_node *node; + + KKASSERT(vn_islocked(vp)); + + node = VP_TO_TMPFS_NODE(vp); + + /* Decide whether this is a valid operation based on the file type. */ + error = 0; + switch (vp->v_type) { + case VDIR: + return EISDIR; + + case VREG: + if (vp->v_mount->mnt_flag & MNT_RDONLY) + return EROFS; + break; + + case VBLK: + /* FALLTHROUGH */ + case VCHR: + /* FALLTHROUGH */ + case VFIFO: + /* Allow modifications of special files even if in the file + * system is mounted read-only (we are not modifying the + * files themselves, but the objects they represent). */ + return 0; + + default: + /* Anything else is unsupported. */ + return EOPNOTSUPP; + } + + /* Immutable or append-only files cannot be modified, either. */ + if (node->tn_flags & (IMMUTABLE | APPEND)) + return EPERM; + + error = tmpfs_truncate(vp, size); + /* tmpfs_truncate will raise the NOTE_EXTEND and NOTE_ATTRIB kevents + * for us, as will update tn_status; no need to do that here. */ + + KKASSERT(vn_islocked(vp)); + + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * Change access and modification times of the given vnode. + * Caller should execute tmpfs_update on vp after a successful execution. + * The vnode must be locked on entry and remain locked on exit. + */ +int +tmpfs_chtimes(struct vnode *vp, struct timespec *atime, struct timespec *mtime, + int vaflags, struct ucred *cred) +{ + int error; + struct tmpfs_node *node; + int fmode, mode; + + KKASSERT(vn_islocked(vp)); + + node = VP_TO_TMPFS_NODE(vp); + + /* Disallow this operation if the file system is mounted read-only. */ + if (vp->v_mount->mnt_flag & MNT_RDONLY) + return EROFS; + + /* Immutable or append-only files cannot be modified, either. */ + if (node->tn_flags & (IMMUTABLE | APPEND)) + return EPERM; + + /* Determine if the user have proper privilege to update time. */ + fmode = FFLAGS(node->tn_flags); + mode = 0; + if (((fmode & (FREAD | FWRITE)) == 0) || (fmode & O_CREAT)) + return EINVAL; + if (fmode & (FWRITE | O_TRUNC)) { + if (vp->v_type == VDIR) { + return EISDIR; + } + error = vn_writechk(vp, NULL); + if (error) + return (error); + + mode |= VWRITE; + } + if (fmode & FREAD) + mode |= VREAD; + + if (mode) { + if (vaflags & VA_UTIMES_NULL) { + error = VOP_ACCESS(vp, mode, cred); + if (error) + error = VOP_ACCESS(vp, VWRITE, cred); + } else + error = VOP_ACCESS(vp, mode, cred); + if (error) + return (error); + } + + TMPFS_NODE_LOCK(node); + if (atime->tv_sec != VNOVAL && atime->tv_nsec != VNOVAL) + node->tn_status |= TMPFS_NODE_ACCESSED; + + if (mtime->tv_sec != VNOVAL && mtime->tv_nsec != VNOVAL) + node->tn_status |= TMPFS_NODE_MODIFIED; + + TMPFS_NODE_UNLOCK(node); + + tmpfs_itimes(vp, atime, mtime); + + KKASSERT(vn_islocked(vp)); + + return 0; +} + +/* --------------------------------------------------------------------- */ +/* Sync timestamps */ +void +tmpfs_itimes(struct vnode *vp, const struct timespec *acc, + const struct timespec *mod) +{ + struct tmpfs_node *node; + struct timespec now; + + node = VP_TO_TMPFS_NODE(vp); + + if ((node->tn_status & (TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED | + TMPFS_NODE_CHANGED)) == 0) + return; + + vfs_timestamp(&now); + + TMPFS_NODE_LOCK(node); + if (node->tn_status & TMPFS_NODE_ACCESSED) { + if (acc == NULL) + acc = &now; + node->tn_atime = acc->tv_sec; + node->tn_atimensec = acc->tv_nsec; + } + if (node->tn_status & TMPFS_NODE_MODIFIED) { + if (mod == NULL) + mod = &now; + node->tn_mtime = mod->tv_sec; + node->tn_mtimensec = mod->tv_nsec; + } + if (node->tn_status & TMPFS_NODE_CHANGED) { + node->tn_ctime = now.tv_sec; + node->tn_ctimensec = now.tv_nsec; + } + node->tn_status &= + ~(TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED | TMPFS_NODE_CHANGED); + TMPFS_NODE_UNLOCK(node); +} + +/* --------------------------------------------------------------------- */ + +void +tmpfs_update(struct vnode *vp) +{ + + tmpfs_itimes(vp, NULL, NULL); +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_truncate(struct vnode *vp, off_t length) +{ + int error; + struct tmpfs_node *node; + + node = VP_TO_TMPFS_NODE(vp); + + if (length < 0) { + error = EINVAL; + goto out; + } + + if (node->tn_size == length) { + error = 0; + goto out; + } + + if (length > VFS_TO_TMPFS(vp->v_mount)->tm_maxfilesize) + return (EFBIG); + + + error = tmpfs_reg_resize(vp, length, 1); + + if (error == 0) { + TMPFS_NODE_LOCK(node); + node->tn_status |= TMPFS_NODE_CHANGED | TMPFS_NODE_MODIFIED; + TMPFS_NODE_UNLOCK(node); + } + +out: + tmpfs_update(vp); + + return error; +} + +/* --------------------------------------------------------------------- */ + +static ino_t +tmpfs_fetch_ino(void) +{ + ino_t ret; + + spin_lock_wr(&ino_lock); + ret = t_ino++; + spin_unlock_wr(&ino_lock); + + return ret; +} diff --git a/sys/vfs/tmpfs/tmpfs_vfsops.c b/sys/vfs/tmpfs/tmpfs_vfsops.c new file mode 100644 index 0000000000..3dde5a908d --- /dev/null +++ b/sys/vfs/tmpfs/tmpfs_vfsops.c @@ -0,0 +1,461 @@ +/* $NetBSD: tmpfs_vfsops.c,v 1.10 2005/12/11 12:24:29 christos Exp $ */ + +/*- + * Copyright (c) 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Efficient memory file system. + * + * tmpfs is a file system that uses NetBSD's virtual memory sub-system + * (the well-known UVM) to store file data and metadata in an efficient + * way. This means that it does not follow the structure of an on-disk + * file system because it simply does not need to. Instead, it uses + * memory-specific data structures and algorithms to automatically + * allocate and release resources. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +/* + * Default permission for root node + */ +#define TMPFS_DEFAULT_ROOT_MODE (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) + +MALLOC_DEFINE(M_TMPFSMNT, "tmpfs mount", "tmpfs mount structures"); +MALLOC_DEFINE(M_TMPFSNAME, "tmpfs name", "tmpfs file names"); +MALLOC_DEFINE(M_TMPFS_DIRENT, "tmpfs dirent", "tmpfs dirent structures"); +MALLOC_DEFINE(M_TMPFS_NODE, "tmpfs node", "tmpfs node structures"); + +/* --------------------------------------------------------------------- */ + +static int tmpfs_mount(struct mount *, char *, caddr_t, struct ucred *); +static int tmpfs_unmount(struct mount *, int); +static int tmpfs_root(struct mount *, struct vnode **); +static int tmpfs_fhtovp(struct mount *, struct vnode *, struct fid *, struct vnode **); +static int tmpfs_statfs(struct mount *, struct statfs *, struct ucred *cred); + +/* --------------------------------------------------------------------- */ + +#define SWI_MAXMIB 3 +static u_int +get_swpgtotal(void) +{ + struct swdevt swinfo; + char *sname = "vm.swap_info"; + int soid[SWI_MAXMIB], oid[2]; + u_int unswdev, total, dmmax, nswapdev; + size_t mibi, len; + + total = 0; + + len = sizeof(dmmax); + if (kernel_sysctlbyname("vm.dmmax", &dmmax, &len, + NULL, 0, NULL) != 0) + return total; + + len = sizeof(nswapdev); + if (kernel_sysctlbyname("vm.nswapdev", &nswapdev, &len, + NULL, 0, NULL) != 0) + return total; + + mibi = (SWI_MAXMIB - 1) * sizeof(int); + oid[0] = 0; + oid[1] = 3; + + if (kernel_sysctl(oid, 2, + soid, &mibi, (void *)sname, strlen(sname), + NULL) != 0) + return total; + + mibi = (SWI_MAXMIB - 1); + for (unswdev = 0; unswdev < nswapdev; ++unswdev) { + soid[mibi] = unswdev; + len = sizeof(struct swdevt); + if (kernel_sysctl(soid, mibi + 1, &swinfo, &len, NULL, 0, + NULL) != 0) + return total; + if (len == sizeof(struct swdevt)) + total += (swinfo.sw_nblks - dmmax); + } + + return total; +} + +/* --------------------------------------------------------------------- */ +static int +tmpfs_node_ctor(void *obj, void *privdata, int flags) +{ + struct tmpfs_node *node = (struct tmpfs_node *)obj; + + node->tn_gen++; + node->tn_size = 0; + node->tn_status = 0; + node->tn_flags = 0; + node->tn_links = 0; + node->tn_vnode = NULL; + node->tn_vpstate = TMPFS_VNODE_WANT; + + return (1); +} + +static void +tmpfs_node_dtor(void *obj, void *privdata) +{ + struct tmpfs_node *node = (struct tmpfs_node *)obj; + node->tn_type = VNON; + node->tn_vpstate = TMPFS_VNODE_DOOMED; +} + +static void* +tmpfs_node_init(void *args, int flags) +{ + struct tmpfs_node *node = (struct tmpfs_node *)objcache_malloc_alloc(args, flags); + node->tn_id = 0; + + lockinit(&node->tn_interlock, "tmpfs node interlock", 0, LK_CANRECURSE); + node->tn_gen = karc4random(); + + return node; +} + +static void +tmpfs_node_fini(void *obj, void *args) +{ + struct tmpfs_node *node = (struct tmpfs_node *)obj; + lockuninit(&node->tn_interlock); + objcache_malloc_free(obj, args); +} + +struct objcache_malloc_args tmpfs_dirent_pool_malloc_args = + { sizeof(struct tmpfs_dirent), M_TMPFS_DIRENT }; +struct objcache_malloc_args tmpfs_node_pool_malloc_args = + { sizeof(struct tmpfs_node), M_TMPFS_NODE }; + +static int +tmpfs_mount(struct mount *mp, char *path, caddr_t data, struct ucred *cred) +{ + struct tmpfs_mount *tmp; + struct tmpfs_node *root; + size_t pages, mem_size; + ino_t nodes; + int error; + /* Size counters. */ + ino_t nodes_max = 0; + size_t size_max = 0; + size_t size; + + /* Root node attributes. */ + uid_t root_uid = cred->cr_uid; + gid_t root_gid = cred->cr_gid; + mode_t root_mode = (VREAD | VWRITE); + + if (mp->mnt_flag & MNT_UPDATE) { + /* XXX: There is no support yet to update file system + * settings. Should be added. */ + + return EOPNOTSUPP; + } + + kprintf("WARNING: TMPFS is considered to be a highly experimental " + "feature in DragonFly.\n"); + + /* Do not allow mounts if we do not have enough memory to preserve + * the minimum reserved pages. */ + mem_size = vmstats.v_free_count + vmstats.v_inactive_count + get_swpgtotal(); + mem_size -= mem_size > vmstats.v_wire_count ? vmstats.v_wire_count : mem_size; + if (mem_size < TMPFS_PAGES_RESERVED) + return ENOSPC; + + /* + * If mount by non-root, then verify that user has necessary + * permissions on the device. + */ + if (cred->cr_uid != 0) { + root_mode = VREAD; + if ((mp->mnt_flag & MNT_RDONLY) == 0) + root_mode |= VWRITE; + } + + /* Get the maximum number of memory pages this file system is + * allowed to use, based on the maximum size the user passed in + * the mount structure. A value of zero is treated as if the + * maximum available space was requested. */ + if (size_max < PAGE_SIZE || size_max >= SIZE_MAX) + pages = SIZE_MAX; + else + pages = howmany(size_max, PAGE_SIZE); + KKASSERT(pages > 0); + + if (nodes_max <= 3) + nodes = 3 + pages * PAGE_SIZE / 1024; + else + nodes = nodes_max; + KKASSERT(nodes >= 3); + + /* Allocate the tmpfs mount structure and fill it. */ + tmp = (struct tmpfs_mount *)kmalloc(sizeof(struct tmpfs_mount), + M_TMPFSMNT, M_WAITOK | M_ZERO); + + lockinit(&(tmp->allnode_lock), "tmpfs allnode lock", 0, LK_CANRECURSE); + tmp->tm_nodes_max = nodes; + tmp->tm_nodes_inuse = 0; + tmp->tm_maxfilesize = (u_int64_t)(vmstats.v_page_count + get_swpgtotal()) * PAGE_SIZE; + LIST_INIT(&tmp->tm_nodes_used); + + tmp->tm_pages_max = pages; + tmp->tm_pages_used = 0; + tmp->tm_dirent_pool = objcache_create( "tmpfs dirent cache", + 0, 0, + NULL, NULL, NULL, + objcache_malloc_alloc, objcache_malloc_free, + &tmpfs_dirent_pool_malloc_args); + tmp->tm_node_pool = objcache_create( "tmpfs node cache", + 0, 0, + tmpfs_node_ctor, tmpfs_node_dtor, NULL, + tmpfs_node_init, tmpfs_node_fini, + &tmpfs_node_pool_malloc_args); + + /* Allocate the root node. */ + error = tmpfs_alloc_node(tmp, VDIR, root_uid, + root_gid, root_mode & ALLPERMS, NULL, NULL, + VNOVAL, VNOVAL, &root); + + if (error != 0 || root == NULL) { + objcache_destroy(tmp->tm_node_pool); + objcache_destroy(tmp->tm_dirent_pool); + kfree(tmp, M_TMPFSMNT); + return error; + } + KASSERT(root->tn_id >= 0, ("tmpfs root with invalid ino: %d", (int)root->tn_id)); + tmp->tm_root = root; + + mp->mnt_flag |= MNT_LOCAL; + mp->mnt_kern_flag |= MNTK_RD_MPSAFE | MNTK_WR_MPSAFE | MNTK_GA_MPSAFE | + MNTK_IN_MPSAFE | MNTK_SG_MPSAFE; + mp->mnt_data = (qaddr_t)tmp; + vfs_getnewfsid(mp); + + + vfs_add_vnodeops(mp, &tmpfs_vnode_vops, &mp->mnt_vn_norm_ops); + vfs_add_vnodeops(mp, &tmpfs_fifo_vops, &mp->mnt_vn_fifo_ops); + + copystr("tmpfs", mp->mnt_stat.f_mntfromname, MNAMELEN - 1, &size); + bzero(mp->mnt_stat.f_mntfromname +size, MNAMELEN - size); + bzero(mp->mnt_stat.f_mntonname, sizeof(mp->mnt_stat.f_mntonname)); + copyinstr(path, mp->mnt_stat.f_mntonname, + sizeof(mp->mnt_stat.f_mntonname) -1, + &size); + + tmpfs_statfs(mp, &mp->mnt_stat, cred); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* ARGSUSED2 */ +static int +tmpfs_unmount(struct mount *mp, int mntflags) +{ + int error; + int flags = 0; + struct tmpfs_mount *tmp; + struct tmpfs_node *node; + struct vnode *vp; + + /* Handle forced unmounts. */ + if (mntflags & MNT_FORCE) + flags |= FORCECLOSE; + + /* Finalize all pending I/O. */ + error = vflush(mp, 0, flags); + if (error != 0) + return error; + + tmp = VFS_TO_TMPFS(mp); + + /* Free all associated data. The loop iterates over the linked list + * we have containing all used nodes. For each of them that is + * a directory, we free all its directory entries. Note that after + * freeing a node, it will automatically go to the available list, + * so we will later have to iterate over it to release its items. */ + node = LIST_FIRST(&tmp->tm_nodes_used); + while (node != NULL) { + struct tmpfs_node *next; + + if (node->tn_type == VDIR) { + struct tmpfs_dirent *de; + + de = TAILQ_FIRST(&node->tn_dir.tn_dirhead); + while (de != NULL) { + struct tmpfs_dirent *nde; + + nde = TAILQ_NEXT(de, td_entries); + tmpfs_free_dirent(tmp, de, FALSE); + de = nde; + node->tn_size -= sizeof(struct tmpfs_dirent); + } + } + + next = LIST_NEXT(node, tn_entries); + vp = node->tn_vnode; + if (vp != NULL) { + tmpfs_free_vp(vp); + vrecycle(vp); + node->tn_vnode = NULL; + } + tmpfs_free_node(tmp, node); + node = next; + } + + objcache_destroy(tmp->tm_dirent_pool); + objcache_destroy(tmp->tm_node_pool); + + lockuninit(&tmp->allnode_lock); + KKASSERT(tmp->tm_pages_used == 0); + KKASSERT(tmp->tm_nodes_inuse == 0); + + /* Throw away the tmpfs_mount structure. */ + kfree(mp->mnt_data, M_TMPFSMNT); + mp->mnt_data = NULL; + + mp->mnt_flag &= ~MNT_LOCAL; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_root(struct mount *mp, struct vnode **vpp) +{ + int error; + error = tmpfs_alloc_vp(mp, VFS_TO_TMPFS(mp)->tm_root, LK_EXCLUSIVE, vpp); + (*vpp)->v_flag |= VROOT; + (*vpp)->v_type = VDIR; + + return error; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_fhtovp(struct mount *mp, struct vnode *rootvp, struct fid *fhp, struct vnode **vpp) +{ + boolean_t found; + struct tmpfs_fid *tfhp; + struct tmpfs_mount *tmp; + struct tmpfs_node *node; + + tmp = VFS_TO_TMPFS(mp); + + tfhp = (struct tmpfs_fid *)fhp; + if (tfhp->tf_len != sizeof(struct tmpfs_fid)) + return EINVAL; + + if (tfhp->tf_id >= tmp->tm_nodes_max) + return EINVAL; + + found = FALSE; + + TMPFS_LOCK(tmp); + LIST_FOREACH(node, &tmp->tm_nodes_used, tn_entries) { + if (node->tn_id == tfhp->tf_id && + node->tn_gen == tfhp->tf_gen) { + found = TRUE; + break; + } + } + TMPFS_UNLOCK(tmp); + + if (found) + return (tmpfs_alloc_vp(mp, node, LK_EXCLUSIVE, vpp)); + + return (EINVAL); +} + +/* --------------------------------------------------------------------- */ + +/* ARGSUSED2 */ +static int +tmpfs_statfs(struct mount *mp, struct statfs *sbp, struct ucred *cred) +{ + fsfilcnt_t freenodes; + struct tmpfs_mount *tmp; + + tmp = VFS_TO_TMPFS(mp); + + sbp->f_iosize = PAGE_SIZE; + sbp->f_bsize = PAGE_SIZE; + + sbp->f_blocks = TMPFS_PAGES_MAX(tmp); + sbp->f_bavail = sbp->f_bfree = TMPFS_PAGES_AVAIL(tmp); + + freenodes = MIN(tmp->tm_nodes_max - tmp->tm_nodes_inuse, + TMPFS_PAGES_AVAIL(tmp) * PAGE_SIZE / sizeof(struct tmpfs_node)); + + sbp->f_files = freenodes + tmp->tm_nodes_inuse; + sbp->f_ffree = freenodes; + /* sbp->f_owner = tmp->tn_uid; */ + + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* + * tmpfs vfs operations. + */ + +static struct vfsops tmpfs_vfsops = { + .vfs_mount = tmpfs_mount, + .vfs_unmount = tmpfs_unmount, + .vfs_root = tmpfs_root, + .vfs_statfs = tmpfs_statfs, + .vfs_fhtovp = tmpfs_fhtovp, + .vfs_sync = vfs_stdsync +}; + +VFS_SET(tmpfs_vfsops, tmpfs, 0); diff --git a/sys/vfs/tmpfs/tmpfs_vnops.c b/sys/vfs/tmpfs/tmpfs_vnops.c new file mode 100644 index 0000000000..40754e4373 --- /dev/null +++ b/sys/vfs/tmpfs/tmpfs_vnops.c @@ -0,0 +1,1564 @@ +/* $NetBSD: tmpfs_vnops.c,v 1.39 2007/07/23 15:41:01 jmmv Exp $ */ + +/*- + * Copyright (c) 2005, 2006 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * tmpfs vnode interface. + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +MALLOC_DECLARE(M_TMPFS); + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_nresolve(struct vop_nresolve_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct vnode *vp = NULL; + struct namecache *ncp = v->a_nch->ncp; + struct ucred *cred = v->a_cred; + + int error; + struct tmpfs_dirent *de; + struct tmpfs_node *dnode; + + dnode = VP_TO_TMPFS_DIR(dvp); + + if (!vn_islocked(dvp)); + vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY); + + /* Check accessibility of requested node as a first step. */ + error = VOP_ACCESS(dvp, VEXEC, cred); + if (error != 0) + goto out; + + if (dnode->tn_dir.tn_parent == NULL) { + error = ENOENT; + goto out; + } + + de = tmpfs_dir_lookup(dnode, NULL, ncp); + if (de == NULL) { + /* The entry was not found in the directory. + * This is OK if we are creating or renaming an + * entry and are working on the last component of + * the path name. */ + error = VOP_ACCESS(dvp, VWRITE, cred); + if (error != 0) + goto out; + else { + error = ENOENT; + goto out; + } + } else { + struct tmpfs_node *tnode; + + /* The entry was found, so get its associated + * tmpfs_node. */ + tnode = de->td_node; + + /* If we are not at the last path component and + * found a non-directory or non-link entry (which + * may itself be pointing to a directory), raise + * an error. */ + if (tnode->tn_links > 1 && + tnode->tn_type != VDIR && tnode->tn_type != VLNK) { + error = ENOTDIR; + goto out; + } + + error = VOP_ACCESS(dvp, VWRITE, cred); + if (error != 0) + goto out; + + /* Allocate a new vnode on the matching entry. */ + error = tmpfs_alloc_vp(dvp->v_mount, tnode, + LK_EXCLUSIVE | LK_RETRY, &vp); + if (error != 0) + goto out; + + if ((dnode->tn_mode & S_ISTXT) && + VOP_ACCESS(vp, VWRITE, cred)) { + error = EPERM; + vp = NULL; + goto out; + } + } + + KKASSERT(vp); + +out: + vn_unlock(dvp); + /* Store the result of this lookup in the cache. Avoid this if the + * request was for creation, as it does not improve timings on + * emprical tests. */ + if (vp) { + vn_unlock(vp); + cache_setvp(v->a_nch, vp); + vrele(vp); + } + if (error == ENOENT) { + cache_setvp(v->a_nch, NULL); + } + return error; +} + +static int +tmpfs_nlookupdotdot(struct vop_nlookupdotdot_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct vnode **vpp = v->a_vpp; + struct tmpfs_node *dnode = VP_TO_TMPFS_NODE(dvp); + struct ucred *cred = v->a_cred; + int error; + + *vpp = NULL; + /* Check accessibility of requested node as a first step. */ + error = VOP_ACCESS(dvp, VEXEC, cred); + if (error != 0) + return error; + + if (dnode->tn_dir.tn_parent != NULL) { + /* Allocate a new vnode on the matching entry. */ + error = tmpfs_alloc_vp(dvp->v_mount, dnode->tn_dir.tn_parent, + LK_EXCLUSIVE | LK_RETRY, vpp); + + if (*vpp) + vn_unlock(*vpp); + } + + return (*vpp == NULL) ? ENOENT : 0; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_ncreate(struct vop_ncreate_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct vnode **vpp = v->a_vpp; + struct namecache *ncp = v->a_nch->ncp; + struct vattr *vap = v->a_vap; + struct ucred *cred = v->a_cred; + int error; + + KKASSERT(vap->va_type == VREG || vap->va_type == VSOCK); + + vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY); + error = tmpfs_alloc_file(dvp, vpp, vap, ncp, cred, NULL); + if (error == 0) { + cache_setunresolved(v->a_nch); + cache_setvp(v->a_nch, *vpp); + } + vn_unlock(dvp); + + return error; +} +/* --------------------------------------------------------------------- */ + +static int +tmpfs_nmknod(struct vop_nmknod_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct vnode **vpp = v->a_vpp; + struct namecache *ncp = v->a_nch->ncp; + struct vattr *vap = v->a_vap; + struct ucred *cred = v->a_cred; + int error; + + if (vap->va_type != VBLK && vap->va_type != VCHR && + vap->va_type != VFIFO) + return EINVAL; + + vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY); + error = tmpfs_alloc_file(dvp, vpp, vap, ncp, cred, NULL); + if (error == 0) { + cache_setunresolved(v->a_nch); + cache_setvp(v->a_nch, *vpp); + } + vn_unlock(dvp); + + return error; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_open(struct vop_open_args *v) +{ + struct vnode *vp = v->a_vp; + int mode = v->a_mode; + + int error; + struct tmpfs_node *node; + + KKASSERT(vn_islocked(vp)); + + node = VP_TO_TMPFS_NODE(vp); + + /* The file is still active but all its names have been removed + * (e.g. by a "rmdir $(pwd)"). It cannot be opened any more as + * it is about to die. */ + if (node->tn_links < 1) + return (ENOENT); + + /* If the file is marked append-only, deny write requests. */ + if (node->tn_flags & APPEND && (mode & (FWRITE | O_APPEND)) == FWRITE) + error = EPERM; + else { + return (vop_stdopen(v)); + } + + KKASSERT(vn_islocked(vp)); + return error; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_close(struct vop_close_args *v) +{ + struct vnode *vp = v->a_vp; + struct tmpfs_node *node; + + node = VP_TO_TMPFS_NODE(vp); + + if (node->tn_links > 0) { + /* Update node times. No need to do it if the node has + * been deleted, because it will vanish after we return. */ + tmpfs_update(vp); + } + + return vop_stdclose(v); +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_access(struct vop_access_args *v) +{ + struct vnode *vp = v->a_vp; + int error; + struct tmpfs_node *node; + + KKASSERT(vn_islocked(vp)); + + node = VP_TO_TMPFS_NODE(vp); + + switch (vp->v_type) { + case VDIR: + /* FALLTHROUGH */ + case VLNK: + /* FALLTHROUGH */ + case VREG: + if (VWRITE && vp->v_mount->mnt_flag & MNT_RDONLY) { + error = EROFS; + goto out; + } + break; + + case VBLK: + /* FALLTHROUGH */ + case VCHR: + /* FALLTHROUGH */ + case VSOCK: + /* FALLTHROUGH */ + case VFIFO: + break; + + default: + error = EINVAL; + goto out; + } + + if (VWRITE && node->tn_flags & IMMUTABLE) { + error = EPERM; + goto out; + } + + error = vop_helper_access(v, node->tn_uid, node->tn_gid, node->tn_mode, 0); + +out: + + return error; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_getattr(struct vop_getattr_args *v) +{ + struct vnode *vp = v->a_vp; + struct vattr *vap = v->a_vap; + + struct tmpfs_node *node; + int needunlock = 0; + + if(!vn_islocked(vp)) { + needunlock = 1; + vn_lock(vp, LK_SHARED | LK_RETRY); + } + + node = VP_TO_TMPFS_NODE(vp); + + tmpfs_update(vp); + + vap->va_type = vp->v_type; + vap->va_mode = node->tn_mode; + vap->va_nlink = node->tn_links; + vap->va_uid = node->tn_uid; + vap->va_gid = node->tn_gid; + vap->va_fsid = vp->v_mount->mnt_stat.f_fsid.val[0]; + vap->va_fileid = node->tn_id; + vap->va_size = node->tn_size; + vap->va_blocksize = PAGE_SIZE; + vap->va_atime.tv_sec = node->tn_atime; + vap->va_atime.tv_nsec = node->tn_atimensec; + vap->va_mtime.tv_sec = node->tn_mtime; + vap->va_mtime.tv_nsec = node->tn_mtimensec; + vap->va_ctime.tv_sec = node->tn_ctime; + vap->va_ctime.tv_nsec = node->tn_ctimensec; + vap->va_gen = node->tn_gen; + vap->va_flags = node->tn_flags; + if (vp->v_type == VBLK || vp->v_type == VCHR) + { + vap->va_rmajor = umajor(node->tn_rdev); + vap->va_rminor = uminor(node->tn_rdev); + } + vap->va_bytes = round_page(node->tn_size); + vap->va_filerev = 0; + + if (needunlock) + vn_unlock(vp); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_setattr(struct vop_setattr_args *v) +{ + struct vnode *vp = v->a_vp; + struct vattr *vap = v->a_vap; + struct ucred *cred = v->a_cred; + + int error = 0; + int needunlock = 0; + + if(!vn_islocked(vp)) { + needunlock = 1; + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); + } + + /* Abort if any unsettable attribute is given. */ + if (vap->va_type != VNON || + vap->va_nlink != VNOVAL || + vap->va_fsid != VNOVAL || + vap->va_fileid != VNOVAL || + vap->va_blocksize != VNOVAL || + vap->va_gen != VNOVAL || + vap->va_rmajor != VNOVAL || + vap->va_bytes != VNOVAL) + error = EINVAL; + + if (error == 0 && (vap->va_flags != VNOVAL)) + error = tmpfs_chflags(vp, vap->va_flags, cred); + + if (error == 0 && (vap->va_size != VNOVAL)) + error = tmpfs_chsize(vp, vap->va_size, cred); + + if (error == 0 && (vap->va_uid != VNOVAL || vap->va_gid != VNOVAL)) + error = tmpfs_chown(vp, vap->va_uid, vap->va_gid, cred); + + if (error == 0 && (vap->va_mode != (mode_t)VNOVAL)) + error = tmpfs_chmod(vp, vap->va_mode, cred); + + if (error == 0 && ((vap->va_atime.tv_sec != VNOVAL && + vap->va_atime.tv_nsec != VNOVAL) || + (vap->va_mtime.tv_sec != VNOVAL && + vap->va_mtime.tv_nsec != VNOVAL) )) + error = tmpfs_chtimes(vp, &vap->va_atime, &vap->va_mtime, + vap->va_vaflags, cred); + + /* Update the node times. We give preference to the error codes + * generated by this function rather than the ones that may arise + * from tmpfs_update. */ + tmpfs_update(vp); + + if (needunlock) + vn_unlock(vp); + + return error; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_fsync(struct vop_fsync_args *v) +{ + struct vnode *vp = v->a_vp; + + tmpfs_update(vp); + + + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_read (struct vop_read_args *ap) +{ + struct buf *bp; + struct vnode *vp = ap->a_vp; + struct uio *uio = ap->a_uio; + struct tmpfs_node *node; + int error; + off_t offset; + off_t base_offset; + size_t len; + int got_mplock; + + error = 0; + if (uio->uio_resid == 0) { + return error; + } + + node = VP_TO_TMPFS_NODE(vp); + + if (uio->uio_offset < 0) + return (EINVAL); + if (vp->v_type != VREG) + return (EINVAL); + + vn_lock(vp, LK_SHARED | LK_RETRY); + +#ifdef SMP + if(curthread->td_mpcount) + got_mplock = -1; + else + got_mplock = 0; +#else + got_mplock = -1; +#endif + + while (uio->uio_resid > 0 && uio->uio_offset < node->tn_size) { + /* + * Use buffer cache I/O (via tmpfs_strategy) + */ + offset = (off_t)uio->uio_offset & BMASK; + base_offset = (off_t)uio->uio_offset - offset; + bp = getcacheblk(vp, base_offset); + if (bp == NULL) + { + if (got_mplock == 0) { + got_mplock = 1; + get_mplock(); + } + + error = bread(vp, base_offset, BSIZE, &bp); + if (error) { + brelse(bp); + kprintf("tmpfs_read bread error %d\n", error); + break; + } + } + + if (got_mplock == 0) { + got_mplock = 1; + get_mplock(); + } + + /* + * Figure out how many bytes we can actually copy this loop. + */ + len = BSIZE - offset; + if (len > uio->uio_resid) + len = uio->uio_resid; + if (len > node->tn_size - uio->uio_offset) + len = (size_t)(node->tn_size - uio->uio_offset); + + error = uiomove((char *)bp->b_data + offset, len, uio); + bqrelse(bp); + if (error) { + kprintf("tmpfs_read uiomove error %d\n", error); + break; + } + } + + if (got_mplock > 0) + rel_mplock(); + + TMPFS_NODE_LOCK(node); + node->tn_status |= TMPFS_NODE_ACCESSED; + TMPFS_NODE_UNLOCK(node); + + vn_unlock(vp); + + return(error); +} + +static int +tmpfs_write (struct vop_write_args *ap) +{ + struct buf *bp; + struct vnode *vp = ap->a_vp; + struct uio *uio = ap->a_uio; + struct thread *td = uio->uio_td; + struct tmpfs_node *node; + boolean_t extended; + off_t oldsize; + int error; + off_t offset; + off_t base_offset; + size_t len; + struct rlimit limit; + int got_mplock; + int trivial = 0; + + error = 0; + if (uio->uio_resid == 0) { + return error; + } + + node = VP_TO_TMPFS_NODE(vp); + + if (vp->v_type != VREG) + return (EINVAL); + + oldsize = node->tn_size; + if (ap->a_ioflag & IO_APPEND) + uio->uio_offset = node->tn_size; + + /* + * Check for illegal write offsets. + */ + if (uio->uio_offset + uio->uio_resid > + VFS_TO_TMPFS(vp->v_mount)->tm_maxfilesize) + return (EFBIG); + + if (vp->v_type == VREG && td != NULL) { + error = kern_getrlimit(RLIMIT_FSIZE, &limit); + if (error != 0) + return error; + if (uio->uio_offset + uio->uio_resid > limit.rlim_cur) { + ksignal(td->td_proc, SIGXFSZ); + return (EFBIG); + } + } + + + /* + * Extend the file's size if necessary + */ + extended = (uio->uio_offset + uio->uio_resid) > node->tn_size; + + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); +#ifdef SMP + if(curthread->td_mpcount) + got_mplock = -1; + else { + got_mplock = 1; + get_mplock(); + } +#else + got_mplock = -1; +#endif + crit_enter(); + while (uio->uio_resid > 0) { + /* + * Use buffer cache I/O (via tmpfs_strategy) + */ + offset = (off_t)uio->uio_offset & BMASK; + base_offset = (off_t)uio->uio_offset - offset; + len = BSIZE - offset; + if (len > uio->uio_resid) + len = uio->uio_resid; + + if ((uio->uio_offset + len) > node->tn_size) { + trivial = uio->uio_offset <= node->tn_size; + error = tmpfs_reg_resize(vp, uio->uio_offset + len, trivial); + if (error) + break; + } + + bp = getblk(vp, base_offset, BSIZE, GETBLK_BHEAVY, 0); + vfs_bio_clrbuf(bp); + + error = uiomove((char *)bp->b_data + offset, len, uio); + if (error) { + kprintf("tmpfs_write uiomove error %d\n", error); + brelse(bp); + break; + } + + if (uio->uio_offset > node->tn_size) + node->tn_size = uio->uio_offset; + + /* + * The data has been loaded into the buffer, write it out. (via tmpfs_strategy) + * + * call bdwrite() because we don't care about storage io flag (ap->a_ioflag) for a swap I/O + * maybe bawrite() for IO_DIRECT, bwrite() for IO_SYNC + * + * XXX: need to implement tmpfs_bmap() for a dirty bit handling of bdwrite() + */ + bdwrite(bp); + if (bp->b_error) { + kprintf("tmpfs_write bwrite error %d\n", error); + break; + } + } + crit_exit(); + + if (got_mplock > 0) + rel_mplock(); + + if (error) { + if (extended) + (void)tmpfs_reg_resize(vp, oldsize, trivial); + return error; + } + + TMPFS_NODE_LOCK(node); + node->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_MODIFIED | + (extended? TMPFS_NODE_CHANGED : 0); + + if (node->tn_mode & (S_ISUID | S_ISGID)) { + if (priv_check_cred(ap->a_cred, PRIV_VFS_RETAINSUGID, 0)) + node->tn_mode &= ~(S_ISUID | S_ISGID); + } + TMPFS_NODE_UNLOCK(node); + + vn_unlock(vp); + + return(error); +} + +static int +tmpfs_advlock (struct vop_advlock_args *ap) +{ + struct tmpfs_node *node; + struct vnode *vp = ap->a_vp; + + node = VP_TO_TMPFS_NODE(vp); + + return (lf_advlock(ap, &node->tn_advlock, node->tn_size)); +} + + +static int +tmpfs_strategy(struct vop_strategy_args *ap) +{ + struct bio *bio = ap->a_bio; + struct vnode *vp = ap->a_vp; + struct tmpfs_node *node; + vm_object_t uobj; + + if (vp->v_type != VREG) + return EINVAL; + + node = VP_TO_TMPFS_NODE(vp); + + uobj = node->tn_reg.tn_aobj; + /* + * call swap_pager_strategy to store vm object into swap device + */ + swap_pager_strategy(uobj, bio); + + return 0; +} + +static int +tmpfs_bmap(struct vop_bmap_args *ap) +{ + if (ap->a_doffsetp != NULL) + *ap->a_doffsetp = ap->a_loffset; + if (ap->a_runp != NULL) + *ap->a_runp = 0; + if (ap->a_runb != NULL) + *ap->a_runb = 0; + + return 0; +} +/* --------------------------------------------------------------------- */ + +static int +tmpfs_nremove(struct vop_nremove_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct namecache *ncp = v->a_nch->ncp; + struct vnode *vp = ncp->nc_vp; + int error; + struct tmpfs_dirent *de; + struct tmpfs_mount *tmp; + struct tmpfs_node *dnode; + struct tmpfs_node *node; + + vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY); + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); + + if (vp->v_type == VDIR) { + error = EISDIR; + goto out; + } + + dnode = VP_TO_TMPFS_DIR(dvp); + node = VP_TO_TMPFS_NODE(vp); + tmp = VFS_TO_TMPFS(vp->v_mount); + de = tmpfs_dir_lookup(dnode, node, ncp); + if (de == NULL) { + error = ENOENT; + goto out; + } + + /* Files marked as immutable or append-only cannot be deleted. */ + if ((node->tn_flags & (IMMUTABLE | APPEND | NOUNLINK)) || + (dnode->tn_flags & APPEND)) { + error = EPERM; + goto out; + } + + /* Remove the entry from the directory; as it is a file, we do not + * have to change the number of hard links of the directory. */ + tmpfs_dir_detach(dvp, de); + + /* Free the directory entry we just deleted. Note that the node + * referred by it will not be removed until the vnode is really + * reclaimed. */ + tmpfs_free_dirent(tmp, de, TRUE); + + if (node->tn_links > 0) { + TMPFS_NODE_LOCK(node); + node->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \ + TMPFS_NODE_MODIFIED; + TMPFS_NODE_UNLOCK(node); + } + + cache_setunresolved(v->a_nch); + cache_setvp(v->a_nch, NULL); + cache_inval_vp(vp, CINV_DESTROY); + error = 0; + + +out: + vn_unlock(vp); + vn_unlock(dvp); + + return error; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_nlink(struct vop_nlink_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct vnode *vp = v->a_vp; + struct namecache *ncp = v->a_nch->ncp; + + int error; + struct tmpfs_dirent *de; + struct tmpfs_node *node; + + vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY); + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); + + + KKASSERT(dvp != vp); /* XXX When can this be false? */ + + node = VP_TO_TMPFS_NODE(vp); + + /* XXX: Why aren't the following two tests done by the caller? */ + + /* Hard links of directories are forbidden. */ + if (vp->v_type == VDIR) { + error = EPERM; + goto out; + } + + /* Cannot create cross-device links. */ + if (dvp->v_mount != vp->v_mount) { + error = EXDEV; + goto out; + } + + /* Ensure that we do not overflow the maximum number of links imposed + * by the system. */ + KKASSERT(node->tn_links <= LINK_MAX); + if (node->tn_links == LINK_MAX) { + error = EMLINK; + goto out; + } + + /* We cannot create links of files marked immutable or append-only. */ + if (node->tn_flags & (IMMUTABLE | APPEND)) { + error = EPERM; + goto out; + } + + /* Allocate a new directory entry to represent the node. */ + error = tmpfs_alloc_dirent(VFS_TO_TMPFS(vp->v_mount), node, + ncp->nc_name, ncp->nc_nlen, &de); + if (error != 0) + goto out; + + /* Insert the new directory entry into the appropriate directory. */ + tmpfs_dir_attach(dvp, de); + + /* vp link count has changed, so update node times. */ + + TMPFS_NODE_LOCK(node); + node->tn_status |= TMPFS_NODE_CHANGED; + TMPFS_NODE_UNLOCK(node); + tmpfs_update(vp); + + cache_setunresolved(v->a_nch); + cache_setvp(v->a_nch, vp); + error = 0; + +out: + vn_unlock(vp); + vn_unlock(dvp); + + return error; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_nrename(struct vop_nrename_args *v) +{ + struct vnode *fdvp = v->a_fdvp; + struct namecache *fncp = v->a_fnch->ncp; + struct vnode *fvp = fncp->nc_vp; + struct vnode *tdvp = v->a_tdvp; + struct namecache *tncp = v->a_tnch->ncp; + struct vnode *tvp = tncp->nc_vp; + + char *newname; + int error; + struct tmpfs_dirent *de; + struct tmpfs_mount *tmp; + struct tmpfs_node *fdnode; + struct tmpfs_node *fnode; + struct tmpfs_node *tnode; + struct tmpfs_node *tdnode; + + vn_lock(tdvp, LK_EXCLUSIVE | LK_RETRY); + if(tvp != NULL && tdvp != tvp) + vn_lock(tvp, LK_EXCLUSIVE | LK_RETRY); + + tnode = (tvp == NULL) ? NULL : VP_TO_TMPFS_NODE(tvp); + + /* Disallow cross-device renames. + * XXX Why isn't this done by the caller? */ + if (fvp->v_mount != tdvp->v_mount || + (tvp != NULL && fvp->v_mount != tvp->v_mount)) { + error = EXDEV; + goto out; + } + + tmp = VFS_TO_TMPFS(tdvp->v_mount); + tdnode = VP_TO_TMPFS_DIR(tdvp); + + /* If source and target are the same file, there is nothing to do. */ + if (fvp == tvp) { + error = 0; + goto out; + } + + /* If we need to move the directory between entries, lock the + * source so that we can safely operate on it. */ + if (tdvp != fdvp) { + error = vn_lock(fdvp, LK_EXCLUSIVE | LK_RETRY); + if (error != 0) + goto out; + } + fdnode = VP_TO_TMPFS_DIR(fdvp); + fnode = VP_TO_TMPFS_NODE(fvp); + de = tmpfs_dir_lookup(fdnode, fnode, fncp); + + /* Avoid manipulating '.' and '..' entries. */ + if (de == NULL) { + error = ENOENT; + goto out_locked; + } + KKASSERT(de->td_node == fnode); + + /* If re-naming a directory to another preexisting directory + * ensure that the target directory is empty so that its + * removal causes no side effects. + * Kern_rename gurantees the destination to be a directory + * if the source is one. */ + if (tvp != NULL) { + KKASSERT(tnode != NULL); + + if ((tnode->tn_flags & (NOUNLINK | IMMUTABLE | APPEND)) || + (tdnode->tn_flags & (APPEND | IMMUTABLE))) { + error = EPERM; + goto out_locked; + } + + if (fnode->tn_type == VDIR && tnode->tn_type == VDIR) { + if (tnode->tn_size > 0) { + error = ENOTEMPTY; + goto out_locked; + } + } else if (fnode->tn_type == VDIR && tnode->tn_type != VDIR) { + error = ENOTDIR; + goto out_locked; + } else if (fnode->tn_type != VDIR && tnode->tn_type == VDIR) { + error = EISDIR; + goto out_locked; + } else { + KKASSERT(fnode->tn_type != VDIR && + tnode->tn_type != VDIR); + } + } + + if ((fnode->tn_flags & (NOUNLINK | IMMUTABLE | APPEND)) + || (fdnode->tn_flags & (APPEND | IMMUTABLE))) { + error = EPERM; + goto out_locked; + } + + /* Ensure that we have enough memory to hold the new name, if it + * has to be changed. */ + if (fncp->nc_nlen != tncp->nc_nlen || + bcmp(fncp->nc_name, tncp->nc_name, fncp->nc_nlen) != 0) { + newname = kmalloc(tncp->nc_nlen, M_TMPFSNAME, M_WAITOK); + } else + newname = NULL; + + /* If the node is being moved to another directory, we have to do + * the move. */ + if (fdnode != tdnode) { + /* In case we are moving a directory, we have to adjust its + * parent to point to the new parent. */ + if (de->td_node->tn_type == VDIR) { + struct tmpfs_node *n; + + /* Ensure the target directory is not a child of the + * directory being moved. Otherwise, we'd end up + * with stale nodes. */ + n = tdnode; + /* TMPFS_LOCK garanties that no nodes are freed while + * traversing the list. Nodes can only be marked as + * removed: tn_parent == NULL. */ + TMPFS_LOCK(tmp); + TMPFS_NODE_LOCK(n); + while (n != n->tn_dir.tn_parent) { + struct tmpfs_node *parent; + + if (n == fnode) { + TMPFS_NODE_UNLOCK(n); + TMPFS_UNLOCK(tmp); + error = EINVAL; + if (newname != NULL) + kfree(newname, M_TMPFSNAME); + goto out_locked; + } + parent = n->tn_dir.tn_parent; + if (parent == NULL) { + n = NULL; + break; + } + TMPFS_NODE_LOCK(parent); + if (parent->tn_dir.tn_parent == NULL) { + TMPFS_NODE_UNLOCK(parent); + n = NULL; + break; + } + n = parent; + } + TMPFS_NODE_UNLOCK(n); + TMPFS_UNLOCK(tmp); + if (n == NULL) { + error = EINVAL; + if (newname != NULL) + kfree(newname, M_TMPFSNAME); + goto out_locked; + } + + /* Adjust the parent pointer. */ + TMPFS_VALIDATE_DIR(fnode); + TMPFS_NODE_LOCK(de->td_node); + de->td_node->tn_dir.tn_parent = tdnode; + + /* As a result of changing the target of the '..' + * entry, the link count of the source and target + * directories has to be adjusted. */ + TMPFS_NODE_LOCK(tdnode); + TMPFS_ASSERT_LOCKED(tdnode); + TMPFS_NODE_LOCK(fdnode); + TMPFS_ASSERT_LOCKED(fdnode); + + tdnode->tn_links++; + fdnode->tn_links--; + + TMPFS_NODE_UNLOCK(fdnode); + TMPFS_NODE_UNLOCK(tdnode); + TMPFS_NODE_UNLOCK(de->td_node); + } + + /* Do the move: just remove the entry from the source directory + * and insert it into the target one. */ + tmpfs_dir_detach(fdvp, de); + tmpfs_dir_attach(tdvp, de); + } + + /* If the name has changed, we need to make it effective by changing + * it in the directory entry. */ + if (newname != NULL) { + + kfree(de->td_name, M_TMPFSNAME); + de->td_namelen = (uint16_t)tncp->nc_nlen; + bcopy(tncp->nc_name, newname, tncp->nc_nlen); + newname[tncp->nc_nlen] = '\0'; + de->td_name = newname; + + TMPFS_NODE_LOCK(tdnode); + TMPFS_NODE_LOCK(fdnode); + + fnode->tn_status |= TMPFS_NODE_CHANGED; + tdnode->tn_status |= TMPFS_NODE_MODIFIED; + + TMPFS_NODE_UNLOCK(fdnode); + TMPFS_NODE_UNLOCK(tdnode); + } + + /* If we are overwriting an entry, we have to remove the old one + * from the target directory. */ + if (tvp != NULL) { + /* Remove the old entry from the target directory. */ + de = tmpfs_dir_lookup(tdnode, tnode, tncp); + tmpfs_dir_detach(tdvp, de); + + /* Free the directory entry we just deleted. Note that the + * node referred by it will not be removed until the vnode is + * really reclaimed. */ + tmpfs_free_dirent(VFS_TO_TMPFS(tvp->v_mount), de, TRUE); + + cache_inval_vp(tvp, CINV_DESTROY); + } + + cache_rename(v->a_fnch, v->a_tnch); + error = 0; + +out_locked: + if (fdnode != tdnode) + vn_unlock(fdvp); + +out: + /* Release target nodes. */ + /* XXX: I don't understand when tdvp can be the same as tvp, but + * other code takes care of this... */ + if (tdvp == tvp) + vrele(tdvp); + else { + if (tvp != NULL) + vn_unlock(tvp); + vn_unlock(tdvp); + } + + return error; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_nmkdir(struct vop_nmkdir_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct vnode **vpp = v->a_vpp; + struct namecache *ncp = v->a_nch->ncp; + struct vattr *vap = v->a_vap; + struct ucred *cred = v->a_cred; + int error; + + KKASSERT(vap->va_type == VDIR); + + vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY); + error = tmpfs_alloc_file(dvp, vpp, vap, ncp, cred, NULL); + if (error == 0) { + cache_setunresolved(v->a_nch); + cache_setvp(v->a_nch, *vpp); + } + vn_unlock(dvp); + + return error; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_nrmdir(struct vop_nrmdir_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct namecache *ncp = v->a_nch->ncp; + struct vnode *vp = ncp->nc_vp; + + int error; + struct tmpfs_dirent *de; + struct tmpfs_mount *tmp; + struct tmpfs_node *dnode; + struct tmpfs_node *node; + + vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY); + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); + + tmp = VFS_TO_TMPFS(dvp->v_mount); + dnode = VP_TO_TMPFS_DIR(dvp); + node = VP_TO_TMPFS_DIR(vp); + + /* Directories with more than two entries ('.' and '..') cannot be + * removed. */ + if (node->tn_size > 0) { + error = ENOTEMPTY; + goto out; + } + + if ((dnode->tn_flags & APPEND) + || (node->tn_flags & (NOUNLINK | IMMUTABLE | APPEND))) { + error = EPERM; + goto out; + } + + /* This invariant holds only if we are not trying to remove "..". + * We checked for that above so this is safe now. */ + KKASSERT(node->tn_dir.tn_parent == dnode); + + /* Get the directory entry associated with node (vp). This was + * filled by tmpfs_lookup while looking up the entry. */ + de = tmpfs_dir_lookup(dnode, node, ncp); + KKASSERT(TMPFS_DIRENT_MATCHES(de, + ncp->nc_name, + ncp->nc_nlen)); + + /* Check flags to see if we are allowed to remove the directory. */ + if (dnode->tn_flags & APPEND + || node->tn_flags & (NOUNLINK | IMMUTABLE | APPEND)) { + error = EPERM; + goto out; + } + + + /* Detach the directory entry from the directory (dnode). */ + tmpfs_dir_detach(dvp, de); + + /* No vnode should be allocated for this entry from this point */ + TMPFS_NODE_LOCK(node); + TMPFS_ASSERT_ELOCKED(node); + TMPFS_NODE_LOCK(dnode); + TMPFS_ASSERT_ELOCKED(dnode); + + node->tn_links--; + node->tn_dir.tn_parent = NULL; + node->tn_status |= TMPFS_NODE_ACCESSED | TMPFS_NODE_CHANGED | \ + TMPFS_NODE_MODIFIED; + + dnode->tn_links--; + dnode->tn_status |= TMPFS_NODE_ACCESSED | \ + TMPFS_NODE_CHANGED | TMPFS_NODE_MODIFIED; + + TMPFS_NODE_UNLOCK(dnode); + TMPFS_NODE_UNLOCK(node); + + /* Free the directory entry we just deleted. Note that the node + * referred by it will not be removed until the vnode is really + * reclaimed. */ + tmpfs_free_dirent(tmp, de, TRUE); + + /* Release the deleted vnode (will destroy the node, notify + * interested parties and clean it from the cache). */ + + TMPFS_NODE_LOCK(dnode); + dnode->tn_status |= TMPFS_NODE_CHANGED; + TMPFS_NODE_UNLOCK(dnode); + tmpfs_update(dvp); + + cache_setunresolved(v->a_nch); + cache_setvp(v->a_nch, NULL); + cache_inval_vp(vp, CINV_DESTROY); + error = 0; + +out: + vn_unlock(vp); + vn_unlock(dvp); + + return error; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_nsymlink(struct vop_nsymlink_args *v) +{ + struct vnode *dvp = v->a_dvp; + struct vnode **vpp = v->a_vpp; + struct namecache *ncp = v->a_nch->ncp; + struct vattr *vap = v->a_vap; + struct ucred *cred = v->a_cred; + char *target = v->a_target; + int error; + + vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY); + vap->va_type = VLNK; + error = tmpfs_alloc_file(dvp, vpp, vap, ncp, cred, target); + if (error == 0) { + cache_setunresolved(v->a_nch); + cache_setvp(v->a_nch, *vpp); + } + vn_unlock(dvp); + + return error; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_readdir(struct vop_readdir_args *v) +{ + struct vnode *vp = v->a_vp; + struct uio *uio = v->a_uio; + int *eofflag = v->a_eofflag; + off_t **cookies = v->a_cookies; + int *ncookies = v->a_ncookies; + + int error; + off_t startoff; + off_t cnt = 0; + struct tmpfs_node *node; + + /* This operation only makes sense on directory nodes. */ + if (vp->v_type != VDIR) + return ENOTDIR; + + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); + + node = VP_TO_TMPFS_DIR(vp); + startoff = uio->uio_offset; + + if (uio->uio_offset == TMPFS_DIRCOOKIE_DOT) { + error = tmpfs_dir_getdotdent(node, uio); + if (error != 0) + goto outok; + cnt++; + } + + if (uio->uio_offset == TMPFS_DIRCOOKIE_DOTDOT) { + error = tmpfs_dir_getdotdotdent(node, uio); + if (error != 0) + goto outok; + cnt++; + } + + error = tmpfs_dir_getdents(node, uio, &cnt); + +outok: + KKASSERT(error >= -1); + + if (error == -1) + error = 0; + + if (eofflag != NULL) + *eofflag = + (error == 0 && uio->uio_offset == TMPFS_DIRCOOKIE_EOF); + + /* Update NFS-related variables. */ + if (error == 0 && cookies != NULL && ncookies != NULL) { + off_t i; + off_t off = startoff; + struct tmpfs_dirent *de = NULL; + + *ncookies = cnt; + *cookies = kmalloc(cnt * sizeof(off_t), M_TEMP, M_WAITOK); + + for (i = 0; i < cnt; i++) { + KKASSERT(off != TMPFS_DIRCOOKIE_EOF); + if (off == TMPFS_DIRCOOKIE_DOT) { + off = TMPFS_DIRCOOKIE_DOTDOT; + } else { + if (off == TMPFS_DIRCOOKIE_DOTDOT) { + de = TAILQ_FIRST(&node->tn_dir.tn_dirhead); + } else if (de != NULL) { + de = TAILQ_NEXT(de, td_entries); + } else { + de = tmpfs_dir_lookupbycookie(node, + off); + KKASSERT(de != NULL); + de = TAILQ_NEXT(de, td_entries); + } + if (de == NULL) + off = TMPFS_DIRCOOKIE_EOF; + else + off = tmpfs_dircookie(de); + } + + (*cookies)[i] = off; + } + KKASSERT(uio->uio_offset == off); + } + vn_unlock(vp); + + return error; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_readlink(struct vop_readlink_args *v) +{ + struct vnode *vp = v->a_vp; + struct uio *uio = v->a_uio; + + int error; + struct tmpfs_node *node; + + KKASSERT(uio->uio_offset == 0); + KKASSERT(vp->v_type == VLNK); + + node = VP_TO_TMPFS_NODE(vp); + + error = uiomove(node->tn_link, MIN(node->tn_size, uio->uio_resid), + uio); + TMPFS_NODE_LOCK(node); + node->tn_status |= TMPFS_NODE_ACCESSED; + TMPFS_NODE_UNLOCK(node); + + return error; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_inactive(struct vop_inactive_args *v) +{ + struct vnode *vp = v->a_vp; + + struct tmpfs_node *node; + + KKASSERT(vn_islocked(vp)); + + node = VP_TO_TMPFS_NODE(vp); + + TMPFS_NODE_LOCK(node); + if (node->tn_links == 0 && + (node->tn_vpstate & TMPFS_VNODE_DOOMED)) { + TMPFS_NODE_UNLOCK(node); + vrecycle(vp); + } + else + TMPFS_NODE_UNLOCK(node); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +int +tmpfs_reclaim(struct vop_reclaim_args *v) +{ + struct vnode *vp = v->a_vp; + + struct tmpfs_mount *tmp; + struct tmpfs_node *node; + + node = VP_TO_TMPFS_NODE(vp); + tmp = VFS_TO_TMPFS(vp->v_mount); + + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); + tmpfs_free_vp(vp); + + /* If the node referenced by this vnode was deleted by the user, + * we must free its associated data structures (now that the vnode + * is being reclaimed). */ + TMPFS_NODE_LOCK(node); + if (node->tn_links == 0 && + (node->tn_vpstate & TMPFS_VNODE_ALLOCATING) == 0) { + node->tn_vpstate = TMPFS_VNODE_DOOMED; + TMPFS_NODE_UNLOCK(node); + tmpfs_free_node(tmp, node); + } + else + TMPFS_NODE_UNLOCK(node); + + vn_unlock(vp); + + KKASSERT(vp->v_data == NULL); + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_print(struct vop_print_args *v) +{ + struct vnode *vp = v->a_vp; + + struct tmpfs_node *node; + + node = VP_TO_TMPFS_NODE(vp); + + kprintf("tag VT_TMPFS, tmpfs_node %p, flags 0x%x, links %d\n", + node, node->tn_flags, node->tn_links); + kprintf("\tmode 0%o, owner %d, group %d, size %ju, status 0x%x\n", + node->tn_mode, node->tn_uid, node->tn_gid, + (uintmax_t)node->tn_size, node->tn_status); + + if (vp->v_type == VFIFO) + fifo_printinfo(vp); + + kprintf("\n"); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +static int +tmpfs_pathconf(struct vop_pathconf_args *v) +{ + int name = v->a_name; + register_t *retval = v->a_retval; + + int error; + + error = 0; + + switch (name) { + case _PC_LINK_MAX: + *retval = LINK_MAX; + break; + + case _PC_NAME_MAX: + *retval = NAME_MAX; + break; + + case _PC_PATH_MAX: + *retval = PATH_MAX; + break; + + case _PC_PIPE_BUF: + *retval = PIPE_BUF; + break; + + case _PC_CHOWN_RESTRICTED: + *retval = 1; + break; + + case _PC_NO_TRUNC: + *retval = 1; + break; + + case _PC_SYNC_IO: + *retval = 1; + break; + + case _PC_FILESIZEBITS: + *retval = 0; /* XXX Don't know which value should I return. */ + break; + + default: + error = EINVAL; + } + + return error; +} + +/* --------------------------------------------------------------------- */ + +/* + * vnode operations vector used for files stored in a tmpfs file system. + */ +struct vop_ops tmpfs_vnode_vops = { + .vop_default = vop_defaultop, + .vop_getpages = vop_stdgetpages, + .vop_putpages = vop_stdputpages, + .vop_ncreate = tmpfs_ncreate, + .vop_nresolve = tmpfs_nresolve, + .vop_nlookupdotdot = tmpfs_nlookupdotdot, + .vop_nmknod = tmpfs_nmknod, + .vop_open = tmpfs_open, + .vop_close = tmpfs_close, + .vop_access = tmpfs_access, + .vop_getattr = tmpfs_getattr, + .vop_setattr = tmpfs_setattr, + .vop_read = tmpfs_read, + .vop_write = tmpfs_write, + .vop_fsync = tmpfs_fsync, + .vop_nremove = tmpfs_nremove, + .vop_nlink = tmpfs_nlink, + .vop_nrename = tmpfs_nrename, + .vop_nmkdir = tmpfs_nmkdir, + .vop_nrmdir = tmpfs_nrmdir, + .vop_nsymlink = tmpfs_nsymlink, + .vop_readdir = tmpfs_readdir, + .vop_readlink = tmpfs_readlink, + .vop_inactive = tmpfs_inactive, + .vop_reclaim = tmpfs_reclaim, + .vop_print = tmpfs_print, + .vop_pathconf = tmpfs_pathconf, +// .vop_bmap = tmpfs_bmap, + .vop_bmap = (void *)vop_eopnotsupp, + .vop_strategy = tmpfs_strategy, + .vop_advlock = tmpfs_advlock, +}; diff --git a/sys/vfs/tmpfs/tmpfs_vnops.h b/sys/vfs/tmpfs/tmpfs_vnops.h new file mode 100644 index 0000000000..1215bbc761 --- /dev/null +++ b/sys/vfs/tmpfs/tmpfs_vnops.h @@ -0,0 +1,58 @@ +/* $NetBSD: tmpfs_vnops.h,v 1.7 2005/12/03 17:34:44 christos Exp $ */ + +/*- + * Copyright (c) 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julio M. Merino Vidal, developed as part of Google's Summer of Code + * 2005 program. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/fs/tmpfs/tmpfs_vnops.h,v 1.3 2008/09/03 18:53:48 delphij Exp $ + */ + +#ifndef _VFS_TMPFS_TMPFS_VNOPS_H_ +#define _VFS_TMPFS_TMPFS_VNOPS_H_ + +#if !defined(_KERNEL) +#error not supposed to be exposed to userland. +#endif + +/* --------------------------------------------------------------------- */ + +extern struct vop_ops tmpfs_vnode_vops; +extern struct vop_ops tmpfs_fifo_vops; + +/* + * Declarations for tmpfs_vnops.c. + */ + +int tmpfs_access(struct vop_access_args *); +int tmpfs_getattr(struct vop_getattr_args *); +int tmpfs_setattr(struct vop_setattr_args *); +int tmpfs_reclaim(struct vop_reclaim_args *); + +/* --------------------------------------------------------------------- */ + +#endif /* _VFS_TMPFS_TMPFS_VNOPS_H_ */ -- 2.11.4.GIT