Many, many thanks to Masanori OZAWA <ozawa@ongs.co.jp>

and Daichi GOTO <daichi@FreeBSD.org> for submitting this
major rewrite of unionfs.  This rewrite was done to
try to solve many of the longstanding crashing and locking
issues in the existing unionfs implementation.  This
implementation also adds a 'MASQUERADE mode', which allows
the user to set different user, group, and file permission
modes in the upper layer.

Submitted by:	daichi, Masanori OZAWA
Reviewed by:	rodrigc (modified for minor style issues)
This commit is contained in:
rodrigc 2006-12-02 19:35:56 +00:00
parent 113db579f0
commit c4618bacd3
7 changed files with 3725 additions and 3355 deletions

View File

@ -7,7 +7,7 @@ MAN= mount_unionfs.8
MOUNT= ${.CURDIR}/../mount
CFLAGS+=-I${MOUNT}
WARNS?= 0
WARNS?= 3
.PATH: ${MOUNT}

View File

@ -31,7 +31,7 @@
.\" @(#)mount_union.8 8.6 (Berkeley) 3/27/94
.\" $FreeBSD$
.\"
.Dd March 27, 1994
.Dd November 30, 2006
.Dt MOUNT_UNIONFS 8
.Os
.Sh NAME
@ -64,6 +64,17 @@ layer.
The options are as follows:
.Bl -tag -width indent
.It Fl b
Deprecated. Use
.Fl o
.Ar below
instead.
.It Fl o
Options are specified with a
.Fl o
flag followed by an option.
The following options are available:
.Bl -tag -width indent
.It Cm below
Invert the default position, so that
.Ar directory
becomes the lower layer and
@ -72,16 +83,36 @@ becomes the upper layer.
However,
.Ar uniondir
remains the mount point.
.It Fl o
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 r
Hide the lower layer completely in the same way as mounting with
.Xr mount_nullfs 8 .
.It Cm copymode=traditional | transparent | masquerade
Specifies the way to create a file or a directory in the upper layer
automatically when needed.
.Ar traditional
uses the same way as the old unionfs for backward compatibility, and
.Ar transparent
duplicates the file and directory mode bits and the ownership in the
lower layer to the created file in the upper layer.
For behavior of the
.Ar masquerade
mode, see
.Sx MASQUERADE MODE .
.It Cm udir=mode
Specifies directory mode bits in octal for
.Ar masquerade
mode.
.It Cm ufile=mode
Specifies file mode bits in octal for
.Ar masquerade
mode.
.It Cm gid=gid
Specifies group for
.Ar masquerade
mode.
.It Cm uid=uid
.uid
Specifies user for
.Ar masquerade
mode.
.El
.El
.Pp
To enforce file system security, the user mounting the file system
@ -91,6 +122,13 @@ In addition, the
.Va vfs.usermount
.Xr sysctl 8
variable must be set to 1 to permit file system mounting by ordinary users.
However, note that
.Ar transparent
and
.Ar masquerade
mode require
.Va vfs.usermount
be set to 0 because this functionality can only be used by superusers.
.Pp
Filenames are looked up in the upper layer and then in the
lower layer.
@ -98,10 +136,14 @@ If a directory is found in the lower layer, and there is no entry
in the upper layer, then a
.Em shadow
directory will be created in the upper layer.
It will be owned by the user who originally did the union mount,
with mode
.Dq rwxrwxrwx
(0777) modified by the umask in effect at that time.
The ownership and the mode bits are set depending on the
.Ar copymode
option. In
.Ar traditional
mode, it will be owned by the user who originally did the
union mount, with mode 0777
.Dq rwxrwxrwx
modified by the umask in effect at that time.
.Pp
If a file exists in the upper layer then there is no way to access
a file with the same name in the lower layer.
@ -142,15 +184,74 @@ option to
.Xr mount 8
which only applies the union operation to the mount point itself,
and then only for lookups.
.Sh MASQUERADE MODE
When a file
.Pq or a directory
is created in the upper layer, the
.Ar masquerade
mode sets it the fixed access mode bits given in
.Ar ufile Pq for files
or
.Ar udir Pq for directories
option and the owner given in
.Ar udir
and
.Ar gid
options, instead of ones in the lower layer. Note that in the
.Ar masquerade
mode and when owner of the file or directory matches
one specified in
.Ar uid
option, only mode bits for the owner will be modified.
More specifically, the file mode bits in the upper layer will
be
.Pq mode in the lower layer
OR
.Pq Po mode given in .Ar ufile
AND 0700
.Pc , and the ownership will be the same as one in the lower layer.
.Pp
The default values for
.Ar ufile , udir , uid ,
and
.Ar gid
are as follow:
.Pp
.Bl -bullet -compact
.It
If both
.Ar ufile
and
.Ar udir
are not specified, access mode bits in the mount point will be used.
.It
If both
.Ar uid
and
.Ar gid
are not specified, ownership in the mount point will be used.
.It
If either
.Ar udir
or
.Ar ufile
is not specified, the other will be the same as the specified one.
.It
If either
.Ar uid
or
.Ar gid
is not specified, the other will be the same as the specified one.
.El
.Sh EXAMPLES
The commands
.Bd -literal -offset indent
mount -t cd9660 -o ro /dev/cd0a /usr/src
mount -t unionfs /var/obj /usr/src
mount -t cd9660 -o ro /dev/cd0 /usr/src
mount -t unionfs -o noatime /var/obj /usr/src
.Ed
.Pp
mount the CD-ROM drive
.Pa /dev/cd0a
.Pa /dev/cd0
on
.Pa /usr/src
and then attaches
@ -158,11 +259,42 @@ and then attaches
on top.
For most purposes the effect of this is to make the
source tree appear writable
even though it is stored on a CD-ROM.
even though it is stored on a CD-ROM. The
.Fl o Ar noatime
option is useful to avoid unnecessary copying from the lower to the
upper layer.
.Pp
The commands
.Bd -literal -offset indent
mount -t cd9660 -o ro /dev/cd0 /usr/src
chown 2020 /usr/src
mount -t unionfs -o noatime -o copymode=masquerade -o uid=builder \\
-o udir=755 -o ufile=644 /var/obj /usr/src
.Ed
.Pp
also mount the CD-ROM drive
.Pa /dev/cd0
on
.Pa /usr/src
and then attaches
.Pa /var/obj
on top. Furthermore, the owner of all files and directories in /usr/src
is a regular user with uid
.Pq 2020
when seen from the upper layer. Note that for the access mode bits,
ones in the lower layer
.Pq on the CD-ROM, in this example
are still used without change.
Thus, write privilege to the upper layer can be controlled
independently from access mode bits and ownership in the lower layer.
If a user does not have read privilege from the lower layer,
one cannot still read even when the upper layer is mounted by using
.Ar masquerade
mode.
.Pp
The command
.Bd -literal -offset indent
mount -t unionfs -o -b /sys $HOME/sys
mount -t unionfs -o noatime -o below /sys $HOME/sys
.Ed
.Pp
attaches the system source tree below the
@ -186,8 +318,20 @@ The
.Nm
utility first appeared in
.Bx 4.4 .
It first worked in
.Fx Ns -(fill this in) .
.Pp
The
.Fl r
option for hiding the lower layer completely was removed in
.Fx 7.0
because this is identical to using
.Xr mount_nullfs 8 .
.Sh AUTHORS
In
.Fx 7.0 ,
.An Masanori OZAWA Aq ozawa@ongs.co.jp
reimplemented handling of locking, whiteout, and file mode bits, and
.An Hiroki Sato Aq hrs@FreeBSD.org
wrote about the changes in this manual page.
.Sh BUGS
THIS FILE SYSTEM TYPE IS NOT YET FULLY SUPPORTED (READ: IT DOESN'T WORK)
AND USING IT MAY, IN FACT, DESTROY DATA ON YOUR SYSTEM.
@ -198,7 +342,7 @@ SLIPPERY WHEN WET.
.Pp
This code also needs an owner in order to be less dangerous - serious
hackers can apply by sending mail to
.Aq hackers@FreeBSD.org
.Aq freebsd-fs@FreeBSD.org
and announcing
their intent to take it over.
.Pp
@ -214,3 +358,20 @@ Running
.Xr find 1
over a union tree has the side-effect of creating
a tree of shadow directories in the upper layer.
.Pp
The current implementation does not support copying extended attributes
for
.Xr acl 9 ,
.Xr mac 9 ,
or so on to the upper layer. Note that this may be a security issue.
.Pp
A shadow directory, which is one automatically created in the upper
layer when it exists in the lower layer and does not exist in the
upper layer, is always created with the superuser privilege.
However, a file copied from the lower layer in the same way
is created by the user who accessed it. Because of this,
if the user is not the superuser, even in
.Ar transparent
mode the access mode bits in the copied file in the upper layer
will not always be the same as ones in the lower layer.
This behavior should be fixed.

View File

@ -1,6 +1,9 @@
/*
/*-
* Copyright (c) 1992, 1993, 1994
* The Regents of the University of California. All rights reserved.
* The Regents of the University of California.
* Copyright (c) 2005, 2006 Masanori Ozawa <ozawa@ongs.co.jp>, ONGS Inc.
* Copyright (c) 2006 Daichi Goto <daichi@freebsd.org>
* All rights reserved.
*
* This code is derived from software donated to Berkeley by
* Jan-Simon Pendry.
@ -48,6 +51,7 @@ static const char rcsid[] =
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/uio.h>
#include <sys/errno.h>
#include <err.h>
#include <stdio.h>
@ -55,54 +59,115 @@ static const char rcsid[] =
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include <grp.h>
#include <pwd.h>
#include "mntopts.h"
static struct mntopt mopts[] = {
MOPT_STDOPTS,
MOPT_END
};
static int subdir(const char *, const char *);
static void usage (void) __dead2;
int
main(argc, argv)
int argc;
char *argv[];
static int
subdir(const char *p, const char *dir)
{
struct iovec iov[8];
int ch, mntflags;
char source[MAXPATHLEN];
char target[MAXPATHLEN];
int iovcnt;
int l;
iovcnt = 6;
l = strlen(dir);
if (l <= 1)
return (1);
if ((strncmp(p, dir, l) == 0) && (p[l] == '/' || p[l] == '\0'))
return (1);
return (0);
}
static void
usage(void)
{
(void)fprintf(stderr,
"usage: mount_unionfs [-o options] directory uniondir\n");
exit(EX_USAGE);
}
static void
parse_gid(const char *s, char *buf, size_t bufsize)
{
struct group *gr;
char *inval;
if ((gr = getgrnam(s)) != NULL)
snprintf(buf, bufsize, "%d", gr->gr_gid);
else {
strtol(s, &inval, 10);
if (*inval != 0) {
errx(EX_NOUSER, "unknown group id: %s", s);
usage();
} else {
strncpy(buf, s, bufsize);
}
}
}
static uid_t
parse_uid(const char *s, char *buf, size_t bufsize)
{
struct passwd *pw;
char *inval;
if ((pw = getpwnam(s)) != NULL)
snprintf(buf, bufsize, "%d", pw->pw_uid);
else {
strtol(s, &inval, 10);
if (*inval != 0) {
errx(EX_NOUSER, "unknown user id: %s", s);
usage();
} else {
strncpy(buf, s, bufsize);
}
}
}
int
main(int argc, char *argv[])
{
struct iovec *iov;
int ch, mntflags, iovlen;
char source [MAXPATHLEN], target[MAXPATHLEN], errmsg[255];
char uid_str[20], gid_str[20];
char *p, *val;
iov = NULL;
iovlen = 0;
mntflags = 0;
while ((ch = getopt(argc, argv, "bo:r")) != -1)
memset(errmsg, 0, sizeof(errmsg));
while ((ch = getopt(argc, argv, "bo:")) != -1) {
switch (ch) {
case 'b':
iov[6].iov_base = "below";
iov[6].iov_len = strlen(iov[6].iov_base) + 1;
iov[7].iov_base = NULL;
iov[7].iov_len = 0;
iovcnt = 8;
printf("\n -b is deprecated. Use \"-o below\" instead\n");
build_iovec(&iov, &iovlen, "below", NULL, 0);
break;
case 'o':
getmntopts(optarg, mopts, &mntflags, 0);
break;
case 'r':
iov[6].iov_base = "replace";
iov[6].iov_len = strlen(iov[6].iov_base) + 1;
iov[7].iov_base = NULL;
iov[7].iov_len = 0;
iovcnt = 8;
p = strchr(optarg, '=');
val = NULL;
if (p != NULL) {
*p = '\0';
val = p + 1;
if (strncmp(optarg, "gid", 3) == 0) {
parse_gid(val, gid_str, sizeof(gid_str));
val = gid_str;
}
else if (strncmp(optarg, "uid", 3) == 0) {
parse_uid(val, uid_str, sizeof(uid_str));
val = uid_str;
}
}
build_iovec(&iov, &iovlen, optarg, val, (size_t)-1);
break;
case '?':
default:
usage();
/* NOTREACHED */
}
}
argc -= optind;
argv += optind;
@ -115,46 +180,14 @@ main(argc, argv)
if (subdir(target, source) || subdir(source, target))
errx(EX_USAGE, "%s (%s) and %s (%s) are not distinct paths",
argv[0], target, argv[1], source);
argv[0], target, argv[1], source);
iov[0].iov_base = "fstype";
iov[0].iov_len = strlen(iov[0].iov_base) + 1;
iov[1].iov_base = "unionfs";
iov[1].iov_len = strlen(iov[1].iov_base) + 1;
iov[2].iov_base = "fspath";
iov[2].iov_len = strlen(iov[2].iov_base) + 1;
iov[3].iov_base = source;
iov[3].iov_len = strlen(source) + 1;
iov[4].iov_base = "target";
iov[4].iov_len = strlen(iov[4].iov_base) + 1;
iov[5].iov_base = target;
iov[5].iov_len = strlen(target) + 1;
if (nmount(iov, iovcnt, mntflags))
err(EX_OSERR, "%s", target);
build_iovec(&iov, &iovlen, "fstype", "unionfs", (size_t)-1);
build_iovec(&iov, &iovlen, "fspath", source, (size_t)-1);
build_iovec(&iov, &iovlen, "from", target, (size_t)-1);
build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
if (nmount(iov, iovlen, mntflags))
err(EX_OSERR, "%s: %s", source, errmsg);
exit(0);
}
int
subdir(p, dir)
const char *p;
const char *dir;
{
int l;
l = strlen(dir);
if (l <= 1)
return (1);
if ((strncmp(p, dir, l) == 0) && (p[l] == '/' || p[l] == '\0'))
return (1);
return (0);
}
void
usage()
{
(void)fprintf(stderr,
"usage: mount_unionfs [-br] [-o options] directory uniondir\n");
exit(EX_USAGE);
}

View File

@ -1,6 +1,8 @@
/*-
* Copyright (c) 1994 The Regents of the University of California.
* Copyright (c) 1994 Jan-Simon Pendry.
* Copyright (c) 2005, 2006 Masanori Ozawa <ozawa@ongs.co.jp>, ONGS Inc.
* Copyright (c) 2006 Daichi Goto <daichi@freebsd.org>
* All rights reserved.
*
* This code is derived from software donated to Berkeley by
@ -34,107 +36,100 @@
* $FreeBSD$
*/
#define UNMNT_ABOVE 0x0001 /* Target appears above mount point */
#define UNMNT_BELOW 0x0002 /* Target appears below mount point */
#define UNMNT_REPLACE 0x0003 /* Target replaces mount point */
struct union_mount {
struct vnode *um_uppervp; /* UN_ULOCK holds locking state */
struct vnode *um_lowervp; /* Left unlocked */
struct ucred *um_cred; /* Credentials of user calling mount */
int um_cmode; /* cmask from mount process */
int um_op; /* Operation mode */
dev_t um_upperdev; /* Upper root node fsid[0]*/
};
#ifdef _KERNEL
#ifndef DIAGNOSTIC
#define DIAGNOSTIC
#endif
/* copy method of attr from lower to upper */
typedef enum _unionfs_copymode {
UNIONFS_TRADITIONAL = 0,
UNIONFS_TRANSPARENT,
UNIONFS_MASQUERADE
} unionfs_copymode;
/*
* DEFDIRMODE is the mode bits used to create a shadow directory.
*/
#define VRWXMODE (VREAD|VWRITE|VEXEC)
#define VRWMODE (VREAD|VWRITE)
#define UN_DIRMODE ((VRWXMODE)|(VRWXMODE>>3)|(VRWXMODE>>6))
#define UN_FILEMODE ((VRWMODE)|(VRWMODE>>3)|(VRWMODE>>6))
/*
* A cache of vnode references (hangs off v_data)
*/
struct union_node {
LIST_ENTRY(union_node) un_cache; /* Hash chain */
struct vnode *un_vnode; /* Back pointer */
struct vnode *un_uppervp; /* overlaying object */
struct vnode *un_lowervp; /* underlying object */
struct vnode *un_dirvp; /* Parent dir of uppervp */
struct vnode *un_pvp; /* Parent vnode */
char *un_path; /* saved component name */
int un_openl; /* # of opens on lowervp */
int un_exclcnt; /* exclusive count */
unsigned int un_flags;
struct vnode **un_dircache; /* cached union stack */
off_t un_uppersz; /* size of upper object */
off_t un_lowersz; /* size of lower object */
#ifdef DIAGNOSTIC
pid_t un_pid;
#endif
struct unionfs_mount {
struct vnode *um_lowervp; /* VREFed once */
struct vnode *um_uppervp; /* VREFed once */
struct vnode *um_rootvp; /* ROOT vnode */
unionfs_copymode um_copymode;
uid_t um_uid;
gid_t um_gid;
u_short um_udir;
u_short um_ufile;
};
/*
* XXX UN_ULOCK - indicates that the uppervp is locked
*
* UN_CACHED - node is in the union cache
*/
/* unionfs status list */
struct unionfs_node_status {
LIST_ENTRY(unionfs_node_status) uns_list; /* Status list */
lwpid_t uns_tid; /* current thread id */
int uns_node_flag; /* uns flag */
int uns_lower_opencnt; /* open count of lower */
int uns_upper_opencnt; /* open count of upper */
int uns_lower_openmode; /* open mode of lower */
int uns_lower_fdidx; /* open fdidx of lower */
int uns_readdir_status; /* read status of readdir */
};
/*#define UN_ULOCK 0x04*/ /* Upper node is locked */
#define UN_CACHED 0x10 /* In union cache */
/* union node status flags */
#define UNS_OPENL_4_READDIR 0x01 /* open lower layer for readdir */
/*
* Hash table locking flags
*/
/* A cache of vnode references */
struct unionfs_node {
LIST_ENTRY(unionfs_node) un_hash; /* Hash list */
struct vnode *un_lowervp; /* lower side vnode */
struct vnode *un_uppervp; /* upper side vnode */
struct vnode *un_dvp; /* parent unionfs vnode */
struct vnode *un_vnode; /* Back pointer */
LIST_HEAD(, unionfs_node_status) un_unshead; /* unionfs status head */
char *un_path; /* path */
int un_flag; /* unionfs node flag */
};
#define UNVP_WANT 0x01
#define UNVP_LOCKED 0x02
/* unionfs node flags */
#define UNIONFS_CACHED 0x01 /* is cached */
#define UNIONFS_OPENEXTL 0x02 /* openextattr (lower) */
#define UNIONFS_OPENEXTU 0x04 /* openextattr (upper) */
extern int union_allocvp(struct vnode **, struct mount *,
struct vnode *,
struct vnode *,
struct componentname *, struct vnode *,
struct vnode *, int);
extern int union_freevp(struct vnode *);
extern struct vnode *union_dircache_get(struct vnode *, struct thread *);
extern void union_dircache_free(struct union_node *);
extern int union_copyup(struct union_node *, int, struct ucred *,
struct thread *);
extern int union_dowhiteout(struct union_node *, struct ucred *,
struct thread *);
extern int union_mkshadow(struct union_mount *, struct vnode *,
struct componentname *, struct vnode **);
extern int union_mkwhiteout(struct union_mount *, struct vnode *,
struct componentname *, char *);
extern int union_cn_close(struct vnode *, int, struct ucred *,
struct thread *);
extern void union_removed_upper(struct union_node *un);
extern struct vnode *union_lowervp(struct vnode *);
extern void union_newsize(struct vnode *, off_t, off_t);
#define MOUNTTOUNIONFSMOUNT(mp) ((struct unionfs_mount *)((mp)->mnt_data))
#define VTOUNIONFS(vp) ((struct unionfs_node *)(vp)->v_data)
#define UNIONFSTOV(xp) ((xp)->un_vnode)
extern int (*union_dircheckp)(struct thread *, struct vnode **,
struct file *);
int unionfs_init(struct vfsconf *vfsp);
int unionfs_uninit(struct vfsconf *vfsp);
int unionfs_nodeget(struct mount *mp, struct vnode *uppervp, struct vnode *lowervp, struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, struct thread *td);
void unionfs_hashrem(struct vnode *vp, struct thread *td);
void unionfs_get_node_status(struct unionfs_node *unp, struct thread *td, struct unionfs_node_status **unspp);
void unionfs_tryrem_node_status(struct unionfs_node *unp, struct thread *td, struct unionfs_node_status *unsp);
#define MOUNTTOUNIONMOUNT(mp) ((struct union_mount *)((mp)->mnt_data))
#define VTOUNION(vp) ((struct union_node *)(vp)->v_data)
#define UNIONTOV(un) ((un)->un_vnode)
#define LOWERVP(vp) (VTOUNION(vp)->un_lowervp)
#define UPPERVP(vp) (VTOUNION(vp)->un_uppervp)
#define OTHERVP(vp) (UPPERVP(vp) ? UPPERVP(vp) : LOWERVP(vp))
int unionfs_check_rmdir(struct vnode *vp, struct ucred *cred, struct thread *td);
int unionfs_copyfile(struct unionfs_node *unp, int docopy, struct ucred *cred, struct thread *td);
void unionfs_create_uppervattr_core(struct unionfs_mount *ump, struct vattr *lva, struct vattr *uva, struct thread *td);
int unionfs_create_uppervattr(struct unionfs_mount *ump, struct vnode *lvp, struct vattr *uva, struct ucred *cred, struct thread *td);
int unionfs_mkshadowdir(struct unionfs_mount *ump, struct vnode *duvp, struct unionfs_node *unp, struct componentname *cnp, struct thread *td);
int unionfs_mkwhiteout(struct vnode *dvp, struct componentname *cnp, struct thread *td, char *path);
int unionfs_relookup_for_create(struct vnode *dvp, struct componentname *cnp, struct thread *td);
int unionfs_relookup_for_delete(struct vnode *dvp, struct componentname *cnp, struct thread *td);
int unionfs_relookup_for_rename(struct vnode *dvp, struct componentname *cnp, struct thread *td);
#define UDEBUG(x) if (uniondebug) printf x
#define UDEBUG_ENABLED 1
#ifdef DIAGNOSTIC
struct vnode *unionfs_checklowervp(struct vnode *vp, char *fil, int lno);
struct vnode *unionfs_checkuppervp(struct vnode *vp, char *fil, int lno);
#define UNIONFSVPTOLOWERVP(vp) unionfs_checklowervp((vp), __FILE__, __LINE__)
#define UNIONFSVPTOUPPERVP(vp) unionfs_checkuppervp((vp), __FILE__, __LINE__)
#else
#define UNIONFSVPTOLOWERVP(vp) (VTOUNIONFS(vp)->un_lowervp)
#define UNIONFSVPTOUPPERVP(vp) (VTOUNIONFS(vp)->un_uppervp)
#endif
extern struct vop_vector union_vnodeops;
extern int uniondebug;
extern struct vop_vector unionfs_vnodeops;
#endif /* _KERNEL */
#ifdef MALLOC_DECLARE
MALLOC_DECLARE(M_UNIONFSNODE);
MALLOC_DECLARE(M_UNIONFSPATH);
#endif
#ifdef UNIONFS_DEBUG
#define UNIONFSDEBUG(format, args...) printf(format ,## args)
#else
#define UNIONFSDEBUG(format, args...)
#endif /* UNIONFS_DEBUG */
#endif /* _KERNEL */

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,8 @@
/*-
* Copyright (c) 1994, 1995 The Regents of the University of California.
* Copyright (c) 1994, 1995 Jan-Simon Pendry.
* Copyright (c) 2005, 2006 Masanori Ozawa <ozawa@ongs.co.jp>, ONGS Inc.
* Copyright (c) 2006 Daichi Goto <daichi@freebsd.org>
* All rights reserved.
*
* This code is derived from software donated to Berkeley by
@ -34,437 +36,438 @@
* $FreeBSD$
*/
/*
* Union Layer
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kdb.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/malloc.h>
#include <sys/filedesc.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/stat.h>
#include <fs/unionfs/union.h>
static MALLOC_DEFINE(M_UNIONFSMNT, "union_mount", "UNION mount structure");
static MALLOC_DEFINE(M_UNIONFSMNT, "UNIONFS mount", "UNIONFS mount structure");
extern vfs_init_t union_init;
static vfs_root_t union_root;
static vfs_mount_t union_mount;
static vfs_statfs_t union_statfs;
static vfs_unmount_t union_unmount;
static vfs_fhtovp_t unionfs_fhtovp;
static vfs_checkexp_t unionfs_checkexp;
static vfs_mount_t unionfs_domount;
static vfs_quotactl_t unionfs_quotactl;
static vfs_root_t unionfs_root;
static vfs_sync_t unionfs_sync;
static vfs_statfs_t unionfs_statfs;
static vfs_unmount_t unionfs_unmount;
static vfs_vget_t unionfs_vget;
static vfs_vptofh_t unionfs_vptofh;
static vfs_extattrctl_t unionfs_extattrctl;
static struct vfsops unionfs_vfsops;
/*
* Mount union filesystem.
* Exchange from userland file mode to vmode.
*/
static u_short
mode2vmode(mode_t mode)
{
u_short ret;
ret = 0;
/* other */
if (mode & S_IXOTH)
ret |= VEXEC >> 6;
if (mode & S_IWOTH)
ret |= VWRITE >> 6;
if (mode & S_IROTH)
ret |= VREAD >> 6;
/* group */
if (mode & S_IXGRP)
ret |= VEXEC >> 3;
if (mode & S_IWGRP)
ret |= VWRITE >> 3;
if (mode & S_IRGRP)
ret |= VREAD >> 3;
/* owner */
if (mode & S_IXUSR)
ret |= VEXEC;
if (mode & S_IWUSR)
ret |= VWRITE;
if (mode & S_IRUSR)
ret |= VREAD;
return (ret);
}
/*
* Mount unionfs layer.
*/
static int
union_mount(mp, td)
struct mount *mp;
struct thread *td;
unionfs_domount(struct mount *mp, struct thread *td)
{
int error = 0;
struct vfsoptlist *opts;
struct vnode *lowerrootvp = NULLVP;
struct vnode *upperrootvp = NULLVP;
struct union_mount *um = 0;
struct vattr va;
char *cp = 0, *target;
int op;
int len;
size_t size;
int error;
struct vnode *lowerrootvp;
struct vnode *upperrootvp;
struct unionfs_mount *ump;
char *target;
char *tmp;
char *ep;
int len;
size_t done;
int below;
uid_t uid;
gid_t gid;
u_short udir;
u_short ufile;
unionfs_copymode copymode;
struct componentname fakecn;
struct nameidata nd, *ndp = &nd;
struct nameidata nd, *ndp;
struct vattr va;
UDEBUG(("union_mount(mp = %p)\n", (void *)mp));
UNIONFSDEBUG("unionfs_mount(mp = %p)\n", (void *)mp);
opts = mp->mnt_optnew;
/*
* Disable clustered write, otherwise system becomes unstable.
*/
MNT_ILOCK(mp);
mp->mnt_flag |= MNT_NOCLUSTERW;
MNT_IUNLOCK(mp);
error = 0;
below = 0;
uid = 0;
gid = 0;
udir = 0;
ufile = 0;
copymode = UNIONFS_TRADITIONAL; /* default */
ndp = &nd;
if (mp->mnt_flag & MNT_ROOTFS)
return (EOPNOTSUPP);
/*
* Update is a no-op
* Update is a no operation.
*/
if (mp->mnt_flag & MNT_UPDATE)
/*
* Need to provide:
* 1. a way to convert between rdonly and rdwr mounts.
* 2. support for nfs exports.
*/
return (EOPNOTSUPP);
/*
* Get arguments.
* Get argument
*/
error = vfs_getopt(opts, "target", (void **)&target, &len);
if (error || target[len - 1] != '\0')
return (EINVAL);
op = 0;
if (vfs_getopt(opts, "below", NULL, NULL) == 0)
op = UNMNT_BELOW;
if (vfs_getopt(opts, "replace", NULL, NULL) == 0) {
/* These options are mutually exclusive. */
if (op)
return (EINVAL);
op = UNMNT_REPLACE;
}
/*
* UNMNT_ABOVE is the default.
*/
if (op == 0)
op = UNMNT_ABOVE;
/*
* Obtain lower vnode. Vnode is stored in mp->mnt_vnodecovered.
* We need to reference it but not lock it.
*/
lowerrootvp = mp->mnt_vnodecovered;
VREF(lowerrootvp);
/*
* Obtain upper vnode by calling namei() on the path. The
* upperrootvp will be turned referenced and locked.
*/
NDINIT(ndp, LOOKUP, FOLLOW|LOCKLEAF, UIO_SYSSPACE, target, td);
error = namei(ndp);
error = vfs_getopt(mp->mnt_optnew, "target", (void **)&target, &len);
if (error)
goto bad;
error = vfs_getopt(mp->mnt_optnew, "from", (void **)&target,
&len);
if (error || target[len - 1] != '\0') {
vfs_mount_error(mp, "Invalid target");
return (EINVAL);
}
if (vfs_getopt(mp->mnt_optnew, "below", NULL, NULL) == 0)
below = 1;
if (vfs_getopt(mp->mnt_optnew, "udir", (void **)&tmp, NULL) == 0) {
if (tmp != NULL)
udir = (mode_t)strtol(tmp, &ep, 8);
if (tmp == NULL || *ep) {
vfs_mount_error(mp, "Invalid udir");
return (EINVAL);
}
udir = mode2vmode(udir);
}
if (vfs_getopt(mp->mnt_optnew, "ufile", (void **)&tmp, NULL) == 0) {
if (tmp != NULL)
ufile = (mode_t)strtol(tmp, &ep, 8);
if (tmp == NULL || *ep) {
vfs_mount_error(mp, "Invalid ufile");
return (EINVAL);
}
ufile = mode2vmode(ufile);
}
/* check umask, uid and gid */
if (udir == 0 && ufile != 0)
udir = ufile;
if (ufile == 0 && udir != 0)
ufile = udir;
vn_lock(mp->mnt_vnodecovered, LK_SHARED | LK_RETRY, td);
error = VOP_GETATTR(mp->mnt_vnodecovered, &va, mp->mnt_cred, td);
if (!error) {
if (udir == 0)
udir = va.va_mode;
if (ufile == 0)
ufile = va.va_mode;
uid = va.va_uid;
gid = va.va_gid;
}
VOP_UNLOCK(mp->mnt_vnodecovered, 0, td);
if (error)
return (error);
if (mp->mnt_cred->cr_ruid == 0) { /* root only */
if (vfs_getopt(mp->mnt_optnew, "uid", (void **)&tmp,
NULL) == 0) {
if (tmp != NULL)
uid = (uid_t)strtol(tmp, &ep, 10);
if (tmp == NULL || *ep) {
vfs_mount_error(mp, "Invalid uid");
return (EINVAL);
}
}
if (vfs_getopt(mp->mnt_optnew, "gid", (void **)&tmp,
NULL) == 0) {
if (tmp != NULL)
gid = (gid_t)strtol(tmp, &ep, 10);
if (tmp == NULL || *ep) {
vfs_mount_error(mp, "Invalid gid");
return (EINVAL);
}
}
if (vfs_getopt(mp->mnt_optnew, "copymode", (void **)&tmp,
NULL) == 0) {
if (tmp == NULL) {
vfs_mount_error(mp, "Invalid copymode");
return (EINVAL);
} else if (strcasecmp(tmp, "traditional") == 0)
copymode = UNIONFS_TRADITIONAL;
else if (strcasecmp(tmp, "transparent") == 0)
copymode = UNIONFS_TRANSPARENT;
else if (strcasecmp(tmp, "masquerade") == 0)
copymode = UNIONFS_MASQUERADE;
else {
vfs_mount_error(mp, "Invalid copymode");
return (EINVAL);
}
}
}
/* If copymode is UNIONFS_TRADITIONAL, uid/gid is mounted user. */
if (copymode == UNIONFS_TRADITIONAL) {
uid = mp->mnt_cred->cr_ruid;
gid = mp->mnt_cred->cr_rgid;
}
UNIONFSDEBUG("unionfs_mount: uid=%d, gid=%d\n", uid, gid);
UNIONFSDEBUG("unionfs_mount: udir=0%03o, ufile=0%03o\n", udir, ufile);
UNIONFSDEBUG("unionfs_mount: copymode=%d\n", copymode);
/*
* Find upper node
*/
NDINIT(ndp, LOOKUP, FOLLOW | WANTPARENT | LOCKLEAF, UIO_SYSSPACE, target, td);
if ((error = namei(ndp)))
return (error);
NDFREE(ndp, NDF_ONLY_PNBUF);
/* get root vnodes */
lowerrootvp = mp->mnt_vnodecovered;
upperrootvp = ndp->ni_vp;
UDEBUG(("mount_root UPPERVP %p locked = %d\n", upperrootvp,
VOP_ISLOCKED(upperrootvp, NULL)));
vrele(ndp->ni_dvp);
ndp->ni_dvp = NULLVP;
/* create unionfs_mount */
ump = (struct unionfs_mount *)malloc(sizeof(struct unionfs_mount),
M_UNIONFSMNT, M_WAITOK | M_ZERO);
/*
* Check multi union mount to avoid `lock myself again' panic.
* Also require that it be a directory.
* Save reference
*/
if (upperrootvp == VTOUNION(lowerrootvp)->un_uppervp) {
#ifdef DIAGNOSTIC
printf("union_mount: multi union mount?\n");
#endif
error = EDEADLK;
goto bad;
}
if (upperrootvp->v_type != VDIR) {
error = EINVAL;
goto bad;
}
/*
* Allocate our union_mount structure and populate the fields.
* The vnode references are stored in the union_mount as held,
* unlocked references. Depending on the _BELOW flag, the
* filesystems are viewed in a different order. In effect this
* is the same as providing a mount-under option to the mount
* syscall.
*/
um = (struct union_mount *) malloc(sizeof(struct union_mount),
M_UNIONFSMNT, M_WAITOK | M_ZERO);
um->um_op = op;
error = VOP_GETATTR(upperrootvp, &va, td->td_ucred, td);
if (error)
goto bad;
um->um_upperdev = va.va_fsid;
switch (um->um_op) {
case UNMNT_ABOVE:
um->um_lowervp = lowerrootvp;
um->um_uppervp = upperrootvp;
upperrootvp = NULL;
lowerrootvp = NULL;
break;
case UNMNT_BELOW:
if (below) {
VOP_UNLOCK(upperrootvp, 0, td);
vn_lock(lowerrootvp, LK_RETRY|LK_EXCLUSIVE, td);
um->um_lowervp = upperrootvp;
um->um_uppervp = lowerrootvp;
upperrootvp = NULL;
lowerrootvp = NULL;
break;
case UNMNT_REPLACE:
vrele(lowerrootvp);
lowerrootvp = NULL;
um->um_uppervp = upperrootvp;
um->um_lowervp = lowerrootvp;
upperrootvp = NULL;
break;
default:
error = EINVAL;
goto bad;
vn_lock(lowerrootvp, LK_EXCLUSIVE | LK_RETRY, td);
ump->um_lowervp = upperrootvp;
ump->um_uppervp = lowerrootvp;
} else {
ump->um_lowervp = lowerrootvp;
ump->um_uppervp = upperrootvp;
}
ump->um_rootvp = NULLVP;
ump->um_uid = uid;
ump->um_gid = gid;
ump->um_udir = udir;
ump->um_ufile = ufile;
ump->um_copymode = copymode;
mp->mnt_data = (qaddr_t)ump;
/*
* Unless the mount is readonly, ensure that the top layer
* supports whiteout operations.
* Copy upper layer's RDONLY flag.
*/
mp->mnt_flag |= ump->um_uppervp->v_mount->mnt_flag & MNT_RDONLY;
/*
* Check whiteout
*/
if ((mp->mnt_flag & MNT_RDONLY) == 0) {
/*
* XXX Fake up a struct componentname with only cn_nameiop
* and cn_thread valid; union_whiteout() needs to use the
* thread pointer to lock the vnode.
*/
bzero(&fakecn, sizeof(fakecn));
memset(&fakecn, 0, sizeof(fakecn));
fakecn.cn_nameiop = LOOKUP;
fakecn.cn_thread = td;
error = VOP_WHITEOUT(um->um_uppervp, &fakecn, LOOKUP);
if (error)
goto bad;
}
VOP_UNLOCK(um->um_uppervp, 0, td);
um->um_cred = crhold(td->td_ucred);
FILEDESC_LOCK_FAST(td->td_proc->p_fd);
um->um_cmode = UN_DIRMODE &~ td->td_proc->p_fd->fd_cmask;
FILEDESC_UNLOCK_FAST(td->td_proc->p_fd);
/*
* Depending on what you think the MNT_LOCAL flag might mean,
* you may want the && to be || on the conditional below.
* At the moment it has been defined that the filesystem is
* only local if it is all local, ie the MNT_LOCAL flag implies
* that the entire namespace is local. If you think the MNT_LOCAL
* flag implies that some of the files might be stored locally
* then you will want to change the conditional.
*/
if (um->um_op == UNMNT_ABOVE) {
if (((um->um_lowervp == NULLVP) ||
(um->um_lowervp->v_mount->mnt_flag & MNT_LOCAL)) &&
(um->um_uppervp->v_mount->mnt_flag & MNT_LOCAL)) {
MNT_ILOCK(mp);
mp->mnt_flag |= MNT_LOCAL;
MNT_IUNLOCK(mp);
error = VOP_WHITEOUT(ump->um_uppervp, &fakecn, LOOKUP);
if (error) {
if (below) {
VOP_UNLOCK(ump->um_uppervp, 0, td);
vrele(upperrootvp);
} else
vput(ump->um_uppervp);
free(ump, M_UNIONFSMNT);
mp->mnt_data = NULL;
return (error);
}
}
/*
* Copy in the upper layer's RDONLY flag. This is for the benefit
* of lookup() which explicitly checks the flag, rather than asking
* the filesystem for its own opinion. This means, that an update
* mount of the underlying filesystem to go from rdonly to rdwr
* will leave the unioned view as read-only.
* Unlock the node
*/
MNT_ILOCK(mp);
mp->mnt_flag |= (um->um_uppervp->v_mount->mnt_flag & MNT_RDONLY);
MNT_IUNLOCK(mp);
VOP_UNLOCK(ump->um_uppervp, 0, td);
mp->mnt_data = (qaddr_t) um;
/*
* Get the unionfs root vnode.
*/
error = unionfs_nodeget(mp, ump->um_uppervp, ump->um_lowervp,
NULLVP, &(ump->um_rootvp), NULL, td);
if (error) {
vrele(upperrootvp);
free(ump, M_UNIONFSMNT);
mp->mnt_data = NULL;
return (error);
}
/*
* Check mnt_flag
*/
if ((ump->um_lowervp->v_mount->mnt_flag & MNT_LOCAL) &&
(ump->um_uppervp->v_mount->mnt_flag & MNT_LOCAL))
mp->mnt_flag |= MNT_LOCAL;
/*
* Get new fsid
*/
vfs_getnewfsid(mp);
switch (um->um_op) {
case UNMNT_ABOVE:
cp = "<above>:";
break;
case UNMNT_BELOW:
cp = "<below>:";
break;
case UNMNT_REPLACE:
cp = "";
break;
}
len = strlen(cp);
bcopy(cp, mp->mnt_stat.f_mntfromname, len);
len = MNAMELEN - 1;
tmp = mp->mnt_stat.f_mntfromname;
copystr((below ? "<below>:" : "<above>:"), tmp, len, &done);
len -= done - 1;
tmp += done - 1;
copystr(target, tmp, len, NULL);
cp = mp->mnt_stat.f_mntfromname + len;
len = MNAMELEN - len;
UNIONFSDEBUG("unionfs_mount: from %s, on %s\n",
mp->mnt_stat.f_mntfromname, mp->mnt_stat.f_mntonname);
(void) copystr(target, cp, len - 1, &size);
bzero(cp + size, len - size);
UDEBUG(("union_mount: from %s, on %s\n",
mp->mnt_stat.f_mntfromname, mp->mnt_stat.f_mntonname));
return (0);
bad:
if (um) {
if (um->um_uppervp)
vput(um->um_uppervp);
if (um->um_lowervp)
vrele(um->um_lowervp);
/* XXX other fields */
free(um, M_UNIONFSMNT);
}
if (upperrootvp)
vput(upperrootvp);
if (lowerrootvp)
vrele(lowerrootvp);
return (error);
}
/*
* Free reference to union layer.
* Free reference to unionfs layer
*/
static int
union_unmount(mp, mntflags, td)
struct mount *mp;
int mntflags;
struct thread *td;
unionfs_unmount(struct mount *mp, int mntflags, struct thread *td)
{
struct union_mount *um = MOUNTTOUNIONMOUNT(mp);
int error;
int freeing;
int flags = 0;
struct unionfs_mount *ump;
int error;
int num;
int freeing;
int flags;
UDEBUG(("union_unmount(mp = %p)\n", (void *)mp));
UNIONFSDEBUG("unionfs_unmount: mp = %p\n", (void *)mp);
ump = MOUNTTOUNIONFSMOUNT(mp);
flags = 0;
if (mntflags & MNT_FORCE)
flags |= FORCECLOSE;
/*
* Keep flushing vnodes from the mount list.
* This is needed because of the un_pvp held
* reference to the parent vnode.
* If more vnodes have been freed on a given pass,
* the try again. The loop will iterate at most
* (d) times, where (d) is the maximum tree depth
* in the filesystem.
*/
for (freeing = 0; (error = vflush(mp, 0, flags, td)) != 0;) {
int n;
/* count #vnodes held on mount list */
n = mp->mnt_nvnodelistsize;
/* if this is unchanged then stop */
if (n == freeing)
/* vflush (no need to call vrele) */
for (freeing = 0; (error = vflush(mp, 1, flags, td)) != 0;) {
num = mp->mnt_nvnodelistsize;
if (num == freeing)
break;
/* otherwise try once more time */
freeing = n;
freeing = num;
}
/*
* If the most recent vflush failed, the filesystem is still busy.
*/
if (error)
return (error);
/*
* Discard references to upper and lower target vnodes.
*/
if (um->um_lowervp)
vrele(um->um_lowervp);
vrele(um->um_uppervp);
crfree(um->um_cred);
/*
* Finally, throw away the union_mount structure.
*/
free(mp->mnt_data, M_UNIONFSMNT); /* XXX */
free(ump, M_UNIONFSMNT);
mp->mnt_data = 0;
return (0);
}
static int
union_root(mp, flags, vpp, td)
struct mount *mp;
int flags;
struct vnode **vpp;
struct thread *td;
unionfs_root(struct mount *mp, int flags, struct vnode **vpp, struct thread *td)
{
struct union_mount *um = MOUNTTOUNIONMOUNT(mp);
int error;
struct unionfs_mount *ump;
struct unionfs_node *unp;
struct vnode *vp;
/*
* Supply an unlocked reference to um_uppervp and to um_lowervp. It
* is possible for um_uppervp to be locked without the associated
* root union_node being locked. We let union_allocvp() deal with
* it.
*/
UDEBUG(("union_root UPPERVP %p locked = %d\n", um->um_uppervp,
VOP_ISLOCKED(um->um_uppervp, NULL)));
ump = MOUNTTOUNIONFSMOUNT(mp);
vp = ump->um_rootvp;
unp = VTOUNIONFS(vp);
VREF(um->um_uppervp);
if (um->um_lowervp)
VREF(um->um_lowervp);
UNIONFSDEBUG("unionfs_root: rootvp=%p locked=%x\n",
vp, VOP_ISLOCKED(vp, td));
error = union_allocvp(vpp, mp, NULLVP, NULLVP, NULL,
um->um_uppervp, um->um_lowervp, 1);
UDEBUG(("error %d\n", error));
UDEBUG(("union_root2 UPPERVP %p locked = %d\n", um->um_uppervp,
VOP_ISLOCKED(um->um_uppervp, NULL)));
vref(vp);
if (flags & LK_TYPE_MASK)
vn_lock(vp, flags, td);
return (error);
*vpp = vp;
return (0);
}
static int
union_statfs(mp, sbp, td)
struct mount *mp;
struct statfs *sbp;
struct thread *td;
unionfs_quotactl(struct mount *mp, int cmd, uid_t uid, void *arg,
struct thread *td)
{
int error;
struct union_mount *um = MOUNTTOUNIONMOUNT(mp);
struct statfs mstat;
int lbsize;
struct unionfs_mount *ump;
UDEBUG(("union_statfs(mp = %p, lvp = %p, uvp = %p)\n",
(void *)mp, (void *)um->um_lowervp, (void *)um->um_uppervp));
ump = MOUNTTOUNIONFSMOUNT(mp);
/*
* Writing is always performed to upper vnode.
*/
return (VFS_QUOTACTL(ump->um_uppervp->v_mount, cmd, uid, arg, td));
}
static int
unionfs_statfs(struct mount *mp, struct statfs *sbp, struct thread *td)
{
struct unionfs_mount *ump;
int error;
struct statfs mstat;
uint64_t lbsize;
ump = MOUNTTOUNIONFSMOUNT(mp);
UNIONFSDEBUG("unionfs_statfs(mp = %p, lvp = %p, uvp = %p)\n",
(void *)mp, (void *)ump->um_lowervp, (void *)ump->um_uppervp);
bzero(&mstat, sizeof(mstat));
if (um->um_lowervp) {
error = VFS_STATFS(um->um_lowervp->v_mount, &mstat, td);
if (error)
return (error);
}
error = VFS_STATFS(ump->um_lowervp->v_mount, &mstat, td);
if (error)
return (error);
/* now copy across the "interesting" information and fake the rest */
sbp->f_blocks = mstat.f_blocks;
sbp->f_files = mstat.f_files;
lbsize = mstat.f_bsize;
error = VFS_STATFS(ump->um_uppervp->v_mount, &mstat, td);
if (error)
return (error);
/*
* Now copy across the "interesting" information and fake the rest.
* The FS type etc is copy from upper vfs.
* (write able vfs have priority)
*/
#if 0
sbp->f_type = mstat.f_type;
sbp->f_flags = mstat.f_flags;
sbp->f_bsize = mstat.f_bsize;
sbp->f_iosize = mstat.f_iosize;
#endif
lbsize = mstat.f_bsize;
sbp->f_blocks = mstat.f_blocks;
sbp->f_bfree = mstat.f_bfree;
sbp->f_bavail = mstat.f_bavail;
sbp->f_files = mstat.f_files;
sbp->f_ffree = mstat.f_ffree;
error = VFS_STATFS(um->um_uppervp->v_mount, &mstat, td);
if (error)
return (error);
sbp->f_flags = mstat.f_flags;
sbp->f_bsize = mstat.f_bsize;
sbp->f_iosize = mstat.f_iosize;
/*
* If the lower and upper blocksizes differ, then frig the
* block counts so that the sizes reported by df make some
* kind of sense. None of this makes sense though.
*/
if (mstat.f_bsize != lbsize)
sbp->f_blocks = ((off_t) sbp->f_blocks * lbsize) / mstat.f_bsize;
sbp->f_blocks = ((off_t)sbp->f_blocks * lbsize) / mstat.f_bsize;
/*
* The "total" fields count total resources in all layers,
* the "free" fields count only those resources which are
* free in the upper layer (since only the upper layer
* is writeable).
*/
sbp->f_blocks += mstat.f_blocks;
sbp->f_bfree = mstat.f_bfree;
sbp->f_bavail = mstat.f_bavail;
@ -473,12 +476,71 @@ union_statfs(mp, sbp, td)
return (0);
}
static struct vfsops union_vfsops = {
.vfs_init = union_init,
.vfs_mount = union_mount,
.vfs_root = union_root,
.vfs_statfs = union_statfs,
.vfs_unmount = union_unmount,
static int
unionfs_sync(struct mount *mp, int waitfor, struct thread *td)
{
/* nothing to do */
return (0);
}
static int
unionfs_vget(struct mount *mp, ino_t ino, int flags, struct vnode **vpp)
{
return (EOPNOTSUPP);
}
static int
unionfs_fhtovp(struct mount *mp, struct fid *fidp, struct vnode **vpp)
{
return (EOPNOTSUPP);
}
static int
unionfs_checkexp(struct mount *mp, struct sockaddr *nam, int *extflagsp,
struct ucred **credanonp)
{
return (EOPNOTSUPP);
}
static int
unionfs_vptofh(struct vnode *vp, struct fid *fhp)
{
return (EOPNOTSUPP);
}
static int
unionfs_extattrctl(struct mount *mp, int cmd, struct vnode *filename_vp,
int namespace, const char *attrname, struct thread *td)
{
struct unionfs_mount *ump;
struct unionfs_node *unp;
ump = MOUNTTOUNIONFSMOUNT(mp);
unp = VTOUNIONFS(filename_vp);
if (unp->un_uppervp != NULLVP) {
return (VFS_EXTATTRCTL(ump->um_uppervp->v_mount, cmd,
unp->un_uppervp, namespace, attrname, td));
} else {
return (VFS_EXTATTRCTL(ump->um_lowervp->v_mount, cmd,
unp->un_lowervp, namespace, attrname, td));
}
}
static struct vfsops unionfs_vfsops = {
.vfs_checkexp = unionfs_checkexp,
.vfs_extattrctl = unionfs_extattrctl,
.vfs_fhtovp = unionfs_fhtovp,
.vfs_init = unionfs_init,
.vfs_mount = unionfs_domount,
.vfs_quotactl = unionfs_quotactl,
.vfs_root = unionfs_root,
.vfs_statfs = unionfs_statfs,
.vfs_sync = unionfs_sync,
.vfs_uninit = unionfs_uninit,
.vfs_unmount = unionfs_unmount,
.vfs_vget = unionfs_vget,
.vfs_vptofh = unionfs_vptofh,
};
VFS_SET(union_vfsops, unionfs, VFCF_LOOPBACK);
VFS_SET(unionfs_vfsops, unionfs, VFCF_LOOPBACK);

File diff suppressed because it is too large Load Diff