Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 25 Dec 2009 20:44:19 +0000 (UTC)
From:      Rick Macklem <rmacklem@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r200999 - in head/sys/fs: nfs nfsserver
Message-ID:  <200912252044.nBPKiJQp071077@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: rmacklem
Date: Fri Dec 25 20:44:19 2009
New Revision: 200999
URL: http://svn.freebsd.org/changeset/base/200999

Log:
  Modify the experimental server so that it uses VOP_ACCESSX().
  This is necessary in order to enable NFSv4 ACL support. The
  argument to nfsvno_accchk() was changed to an accmode_t and
  the function nfsrv_aclaccess() was no longer needed and,
  therefore, deleted.
  
  Reviewed by:	trasz
  MFC after:	2 weeks

Modified:
  head/sys/fs/nfs/nfs_commonacl.c
  head/sys/fs/nfs/nfs_var.h
  head/sys/fs/nfsserver/nfs_nfsdport.c
  head/sys/fs/nfsserver/nfs_nfsdserv.c

Modified: head/sys/fs/nfs/nfs_commonacl.c
==============================================================================
--- head/sys/fs/nfs/nfs_commonacl.c	Fri Dec 25 20:21:35 2009	(r200998)
+++ head/sys/fs/nfs/nfs_commonacl.c	Fri Dec 25 20:44:19 2009	(r200999)
@@ -437,70 +437,6 @@ nfsrv_buildacl(struct nfsrv_descript *nd
 }
 
 /*
- * Check access for an NFSv4 acl.
- * The vflags are the basic VREAD, VWRITE, VEXEC. The mask is the NFSV4ACE
- * mask bits for the more detailed check.
- * If the more detailed check fails, due to no acl, do a basic one.
- */
-APPLESTATIC int
-nfsrv_aclaccess(vnode_t vp, accmode_t vflags, u_int32_t mask,
-    struct ucred *cred, NFSPROC_T *p)
-{
-	int error = 0;
-	accmode_t access;
-
-	if (nfsrv_useacl == 0) {
-		error = VOP_ACCESS(vp, vflags, cred, p);
-		return (error);
-	}
-
-	/* Convert NFSV4ACE mask to vaccess_t */
-	access = 0;
-	if (mask & NFSV4ACE_READDATA)
-		access |= VREAD;
-	if (mask & NFSV4ACE_LISTDIRECTORY)
-		access |= VREAD;
-	if (mask & NFSV4ACE_WRITEDATA)
-		access |= VWRITE;
-	if (mask & NFSV4ACE_ADDFILE)
-		access |= VWRITE;
-	if (mask & NFSV4ACE_APPENDDATA)
-		access |= VAPPEND;
-	if (mask & NFSV4ACE_ADDSUBDIRECTORY)
-		access |= VAPPEND;
-	if (mask & NFSV4ACE_READNAMEDATTR)
-		access |= VREAD_NAMED_ATTRS;
-	if (mask & NFSV4ACE_WRITENAMEDATTR)
-		access |= VWRITE_NAMED_ATTRS;
-	if (mask & NFSV4ACE_EXECUTE)
-		access |= VEXEC;
-	if (mask & NFSV4ACE_SEARCH)
-		access |= VEXEC;
-	if (mask & NFSV4ACE_DELETECHILD)
-		access |= VDELETE_CHILD;
-	if (mask & NFSV4ACE_READATTRIBUTES)
-		access |= VREAD_ATTRIBUTES;
-	if (mask & NFSV4ACE_WRITEATTRIBUTES)
-		access |= VWRITE_ATTRIBUTES;
-	if (mask & NFSV4ACE_DELETE)
-		access |= VDELETE;
-	if (mask & NFSV4ACE_READACL)
-		access |= VREAD_ACL;
-	if (mask & NFSV4ACE_WRITEACL)
-		access |= VWRITE_ACL;
-	if (mask & NFSV4ACE_WRITEOWNER)
-		access |= VWRITE_OWNER;
-	if (mask & NFSV4ACE_SYNCHRONIZE)
-		access |= VSYNCHRONIZE;
-
-	if (access != 0)
-		error = VOP_ACCESS(vp, access, cred, p);
-	else
-		error = VOP_ACCESS(vp, vflags, cred, p);
-	return (error);
-}
-
-/*
  * Set an NFSv4 acl.
  */
 APPLESTATIC int

Modified: head/sys/fs/nfs/nfs_var.h
==============================================================================
--- head/sys/fs/nfs/nfs_var.h	Fri Dec 25 20:21:35 2009	(r200998)
+++ head/sys/fs/nfs/nfs_var.h	Fri Dec 25 20:44:19 2009	(r200999)
@@ -331,8 +331,6 @@ int nfsrv_dissectace(struct nfsrv_descri
     int *, int *, NFSPROC_T *);
 int nfsrv_buildacl(struct nfsrv_descript *, NFSACL_T *, enum vtype,
     NFSPROC_T *);
-int nfsrv_aclaccess(vnode_t, accmode_t, u_int32_t, struct ucred *,
-    NFSPROC_T *);
 int nfsrv_setacl(vnode_t, NFSACL_T *, struct ucred *,
     NFSPROC_T *);
 int nfsrv_compareacl(NFSACL_T *, NFSACL_T *);
@@ -514,8 +512,8 @@ int nfsvno_getattr(vnode_t, struct nfsva
 int nfsvno_setattr(vnode_t, struct nfsvattr *, struct ucred *,
     NFSPROC_T *, struct nfsexstuff *);
 int nfsvno_getfh(vnode_t, fhandle_t *, NFSPROC_T *);
-int nfsvno_accchk(vnode_t, u_int32_t, struct ucred *,
-    struct nfsexstuff *, NFSPROC_T *, int, int);
+int nfsvno_accchk(vnode_t, accmode_t, struct ucred *,
+    struct nfsexstuff *, NFSPROC_T *, int, int, u_int32_t *);
 int nfsvno_namei(struct nfsrv_descript *, struct nameidata *,
     vnode_t, int, struct nfsexstuff *, NFSPROC_T *, vnode_t *);
 void nfsvno_setpathbuf(struct nameidata *, char **, u_long **);

Modified: head/sys/fs/nfsserver/nfs_nfsdport.c
==============================================================================
--- head/sys/fs/nfsserver/nfs_nfsdport.c	Fri Dec 25 20:21:35 2009	(r200998)
+++ head/sys/fs/nfsserver/nfs_nfsdport.c	Fri Dec 25 20:44:19 2009	(r200999)
@@ -131,32 +131,20 @@ nfsvno_getfh(struct vnode *vp, fhandle_t
 /*
  * Perform access checking for vnodes obtained from file handles that would
  * refer to files already opened by a Unix client. You cannot just use
- * vn_writechk() and VOP_ACCESS() for two reasons.
- * 1 - You must check for exported rdonly as well as MNT_RDONLY for the write case
+ * vn_writechk() and VOP_ACCESSX() for two reasons.
+ * 1 - You must check for exported rdonly as well as MNT_RDONLY for the write
+ *     case.
  * 2 - The owner is to be given access irrespective of mode bits for some
  *     operations, so that processes that chmod after opening a file don't
  *     break.
  */
 int
-nfsvno_accchk(struct vnode *vp, u_int32_t accessbits, struct ucred *cred,
-    struct nfsexstuff *exp, struct thread *p, int override, int vpislocked)
+nfsvno_accchk(struct vnode *vp, accmode_t accmode, struct ucred *cred,
+    struct nfsexstuff *exp, struct thread *p, int override, int vpislocked,
+    u_int32_t *supportedtypep)
 {
 	struct vattr vattr;
 	int error = 0, getret = 0;
-	accmode_t accmode;
-
-	/*
-	 * Convert accessbits to Vxxx flags.
-	 */
-	if (accessbits & (NFSV4ACE_WRITEDATA | NFSV4ACE_APPENDDATA |
-	    NFSV4ACE_ADDFILE | NFSV4ACE_ADDSUBDIRECTORY |
-	    NFSV4ACE_DELETECHILD | NFSV4ACE_WRITEATTRIBUTES |
-	    NFSV4ACE_DELETE | NFSV4ACE_WRITEACL | NFSV4ACE_WRITEOWNER))
-		accmode = VWRITE;
-	else if (accessbits & (NFSV4ACE_EXECUTE | NFSV4ACE_SEARCH))
-		accmode = VEXEC;
-	else
-		accmode = VREAD;
 
 	if (accmode & VWRITE) {
 		/* Just vn_writechk() changed to check rdonly */
@@ -166,7 +154,7 @@ nfsvno_accchk(struct vnode *vp, u_int32_
 		 * device resident on the file system.
 		 */
 		if (NFSVNO_EXRDONLY(exp) ||
-			(vp->v_mount->mnt_flag & MNT_RDONLY)) {
+		    (vp->v_mount->mnt_flag & MNT_RDONLY)) {
 			switch (vp->v_type) {
 			case VREG:
 			case VDIR:
@@ -187,22 +175,26 @@ nfsvno_accchk(struct vnode *vp, u_int32_
 	if (vpislocked == 0)
 		NFSVOPLOCK(vp, LK_EXCLUSIVE | LK_RETRY, p);
 
-#if defined(NFS4_ACL_EXTATTR_NAME) && defined(notyet)
-	/*
-	 * This function should be called once FFS has NFSv4 ACL support
-	 * in it.
-	 */
 	/*
 	 * Should the override still be applied when ACLs are enabled?
 	 */
-	if (nfsrv_useacl != 0 && NFSHASNFS4ACL(vp->v_mount))
-		error = nfsrv_aclaccess(vp, accmode, accessbits, cred, p);
-	else
-#endif
-	if (accessbits == NFSV4ACE_READATTRIBUTES)
-		error = 0;
-	else
-		error = VOP_ACCESS(vp, accmode, cred, p);
+	error = VOP_ACCESSX(vp, accmode, cred, p);
+	if (error != 0 && (accmode & (VDELETE | VDELETE_CHILD))) {
+		/*
+		 * Try again with VEXPLICIT_DENY, to see if the test for
+		 * deletion is supported.
+		 */
+		error = VOP_ACCESSX(vp, accmode | VEXPLICIT_DENY, cred, p);
+		if (error == 0) {
+			if (vp->v_type == VDIR) {
+				accmode &= ~(VDELETE | VDELETE_CHILD);
+				accmode |= VWRITE;
+				error = VOP_ACCESSX(vp, accmode, cred, p);
+			} else if (supportedtypep != NULL) {
+				*supportedtypep &= ~NFSACCESS_DELETE;
+			}
+		}
+	}
 
 	/*
 	 * Allow certain operations for the owner (reads and writes
@@ -790,9 +782,9 @@ nfsvno_createsub(struct nfsrv_descript *
 		else
 			vput(ndp->ni_dvp);
 		if (!error && nvap->na_size != VNOVAL) {
-			error = nfsvno_accchk(*vpp, NFSV4ACE_ADDFILE,
+			error = nfsvno_accchk(*vpp, VWRITE,
 			    nd->nd_cred, exp, p, NFSACCCHK_NOOVERRIDE,
-			    NFSACCCHK_VPISLOCKED);
+			    NFSACCCHK_VPISLOCKED, NULL);
 			if (!error) {
 				tempsize = nvap->na_size;
 				NFSVNO_ATTRINIT(nvap);
@@ -1334,8 +1326,9 @@ nfsvno_open(struct nfsrv_descript *nd, s
 				else
 					NFSVNO_EXINIT(&nes);
 				nd->nd_repstat = nfsvno_accchk(vp, 
-				    NFSV4ACE_ADDFILE, cred, &nes, p,
-				    NFSACCCHK_NOOVERRIDE,NFSACCCHK_VPISLOCKED);
+				    VWRITE, cred, &nes, p,
+				    NFSACCCHK_NOOVERRIDE,
+				    NFSACCCHK_VPISLOCKED, NULL);
 				nd->nd_repstat = nfsrv_opencheck(clientid,
 				    stateidp, stp, vp, nd, p, nd->nd_repstat);
 				if (!nd->nd_repstat) {
@@ -1481,9 +1474,9 @@ nfsrvd_readdir(struct nfsrv_descript *nd
 #endif
 	}
 	if (!nd->nd_repstat)
-		nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_SEARCH,
+		nd->nd_repstat = nfsvno_accchk(vp, VEXEC,
 		    nd->nd_cred, exp, p, NFSACCCHK_NOOVERRIDE,
-		    NFSACCCHK_VPISLOCKED);
+		    NFSACCCHK_VPISLOCKED, NULL);
 	if (nd->nd_repstat) {
 		vput(vp);
 		if (nd->nd_flag & ND_NFSV3)
@@ -1752,9 +1745,9 @@ nfsrvd_readdirplus(struct nfsrv_descript
 	if (!nd->nd_repstat && cnt == 0)
 		nd->nd_repstat = NFSERR_TOOSMALL;
 	if (!nd->nd_repstat)
-		nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_SEARCH,
+		nd->nd_repstat = nfsvno_accchk(vp, VEXEC,
 		    nd->nd_cred, exp, p, NFSACCCHK_NOOVERRIDE,
-		    NFSACCCHK_VPISLOCKED);
+		    NFSACCCHK_VPISLOCKED, NULL);
 	if (nd->nd_repstat) {
 		vput(vp);
 		if (nd->nd_flag & ND_NFSV3)

Modified: head/sys/fs/nfsserver/nfs_nfsdserv.c
==============================================================================
--- head/sys/fs/nfsserver/nfs_nfsdserv.c	Fri Dec 25 20:21:35 2009	(r200998)
+++ head/sys/fs/nfsserver/nfs_nfsdserv.c	Fri Dec 25 20:44:19 2009	(r200999)
@@ -88,6 +88,7 @@ nfsrvd_access(struct nfsrv_descript *nd,
 	int getret, error = 0;
 	struct nfsvattr nva;
 	u_int32_t testmode, nfsmode, supported = 0;
+	accmode_t deletebit;
 
 	if (nd->nd_repstat) {
 		nfsrv_postopattr(nd, 1, &nva);
@@ -105,26 +106,30 @@ nfsrvd_access(struct nfsrv_descript *nd,
 	}
 	if (nfsmode & NFSACCESS_READ) {
 		supported |= NFSACCESS_READ;
-		if (nfsvno_accchk(vp, NFSV4ACE_READDATA, nd->nd_cred, exp, p,
-		    NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED))
+		if (nfsvno_accchk(vp, VREAD, nd->nd_cred, exp, p,
+		    NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED, &supported))
 			nfsmode &= ~NFSACCESS_READ;
 	}
 	if (nfsmode & NFSACCESS_MODIFY) {
 		supported |= NFSACCESS_MODIFY;
-		if (nfsvno_accchk(vp, NFSV4ACE_WRITEDATA, nd->nd_cred, exp, p,
-		    NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED))
+		if (nfsvno_accchk(vp, VWRITE, nd->nd_cred, exp, p,
+		    NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED, &supported))
 			nfsmode &= ~NFSACCESS_MODIFY;
 	}
 	if (nfsmode & NFSACCESS_EXTEND) {
 		supported |= NFSACCESS_EXTEND;
-		if (nfsvno_accchk(vp, NFSV4ACE_APPENDDATA, nd->nd_cred, exp, p,
-		    NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED))
+		if (nfsvno_accchk(vp, VWRITE | VAPPEND, nd->nd_cred, exp, p,
+		    NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED, &supported))
 			nfsmode &= ~NFSACCESS_EXTEND;
 	}
 	if (nfsmode & NFSACCESS_DELETE) {
 		supported |= NFSACCESS_DELETE;
-		if (nfsvno_accchk(vp, NFSV4ACE_DELETE, nd->nd_cred, exp, p,
-		    NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED))
+		if (vp->v_type == VDIR)
+			deletebit = VDELETE_CHILD;
+		else
+			deletebit = VDELETE;
+		if (nfsvno_accchk(vp, deletebit, nd->nd_cred, exp, p,
+		    NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED, &supported))
 			nfsmode &= ~NFSACCESS_DELETE;
 	}
 	if (vnode_vtype(vp) == VDIR)
@@ -133,8 +138,8 @@ nfsrvd_access(struct nfsrv_descript *nd,
 		testmode = NFSACCESS_EXECUTE;
 	if (nfsmode & testmode) {
 		supported |= (nfsmode & testmode);
-		if (nfsvno_accchk(vp, NFSV4ACE_EXECUTE, nd->nd_cred, exp, p,
-		    NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED))
+		if (nfsvno_accchk(vp, VEXEC, nd->nd_cred, exp, p,
+		    NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED, &supported))
 			nfsmode &= ~testmode;
 	}
 	nfsmode &= supported;
@@ -189,9 +194,9 @@ nfsrvd_getattr(struct nfsrv_descript *nd
 		}
 		if (!nd->nd_repstat)
 			nd->nd_repstat = nfsvno_accchk(vp,
-			    NFSV4ACE_READATTRIBUTES,
-			    nd->nd_cred, exp, p,
-			    NFSACCCHK_NOOVERRIDE, NFSACCCHK_VPISLOCKED);
+			    VREAD_ATTRIBUTES,
+			    nd->nd_cred, exp, p, NFSACCCHK_NOOVERRIDE,
+			    NFSACCCHK_VPISLOCKED, NULL);
 	}
 	if (!nd->nd_repstat)
 		nd->nd_repstat = nfsvno_getattr(vp, &nva, nd->nd_cred, p);
@@ -291,8 +296,9 @@ nfsrvd_setattr(struct nfsrv_descript *nd
 			else if (nva2.na_uid != nd->nd_cred->cr_uid ||
 			    NFSVNO_EXSTRICTACCESS(exp))
 				nd->nd_repstat = nfsvno_accchk(vp,
-				    NFSV4ACE_WRITEDATA, nd->nd_cred, exp, p,
-				    NFSACCCHK_NOOVERRIDE,NFSACCCHK_VPISLOCKED);
+				    VWRITE, nd->nd_cred, exp, p,
+				    NFSACCCHK_NOOVERRIDE,
+				    NFSACCCHK_VPISLOCKED, NULL);
 		}
 	}
 	if (!nd->nd_repstat && (nd->nd_flag & ND_NFSV4))
@@ -612,13 +618,13 @@ nfsrvd_read(struct nfsrv_descript *nd, _
 	if (!nd->nd_repstat &&
 	    (nva.na_uid != nd->nd_cred->cr_uid ||
 	     NFSVNO_EXSTRICTACCESS(exp))) {
-		nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_READDATA,
+		nd->nd_repstat = nfsvno_accchk(vp, VREAD,
 		    nd->nd_cred, exp, p,
-		    NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED);
+		    NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED, NULL);
 		if (nd->nd_repstat)
-			nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_EXECUTE,
-			    nd->nd_cred, exp, p,
-			    NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED);
+			nd->nd_repstat = nfsvno_accchk(vp, VEXEC,
+			    nd->nd_cred, exp, p, NFSACCCHK_ALLOWOWNER,
+			    NFSACCCHK_VPISLOCKED, NULL);
 	}
 	if ((nd->nd_flag & ND_NFSV4) && !nd->nd_repstat)
 		nd->nd_repstat = nfsrv_lockctrl(vp, &stp, &lop, NULL, clientid,
@@ -788,9 +794,9 @@ nfsrvd_write(struct nfsrv_descript *nd, 
 	if (!nd->nd_repstat &&
 	    (forat.na_uid != nd->nd_cred->cr_uid ||
 	     NFSVNO_EXSTRICTACCESS(exp)))
-		nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_WRITEDATA,
+		nd->nd_repstat = nfsvno_accchk(vp, VWRITE,
 		    nd->nd_cred, exp, p,
-		    NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED);
+		    NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED, NULL);
 	if ((nd->nd_flag & ND_NFSV4) && !nd->nd_repstat) {
 		nd->nd_repstat = nfsrv_lockctrl(vp, &stp, &lop, NULL, clientid,
 		    &stateid, exp, nd, p);
@@ -2146,17 +2152,17 @@ nfsrvd_lock(struct nfsrv_descript *nd, _
 	}
 	if (!nd->nd_repstat) {
 	    if (lflags & NFSLCK_WRITE) {
-		nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_WRITEDATA,
+		nd->nd_repstat = nfsvno_accchk(vp, VWRITE,
 		    nd->nd_cred, exp, p, NFSACCCHK_ALLOWOWNER,
-		    NFSACCCHK_VPISLOCKED);
+		    NFSACCCHK_VPISLOCKED, NULL);
 	    } else {
-		nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_READDATA,
+		nd->nd_repstat = nfsvno_accchk(vp, VREAD,
 		    nd->nd_cred, exp, p, NFSACCCHK_ALLOWOWNER,
-		    NFSACCCHK_VPISLOCKED);
+		    NFSACCCHK_VPISLOCKED, NULL);
 		if (nd->nd_repstat)
-		    nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_EXECUTE,
+		    nd->nd_repstat = nfsvno_accchk(vp, VEXEC,
 			nd->nd_cred, exp, p, NFSACCCHK_ALLOWOWNER,
-			NFSACCCHK_VPISLOCKED);
+			NFSACCCHK_VPISLOCKED, NULL);
 	    }
 	}
 
@@ -2672,15 +2678,15 @@ nfsrvd_open(struct nfsrv_descript *nd, _
 		nd->nd_repstat = NFSERR_INVAL;
 	}
 	if (!nd->nd_repstat && (stp->ls_flags & NFSLCK_WRITEACCESS))
-	    nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_WRITEDATA, nd->nd_cred,
-	        exp, p, NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED);
+	    nd->nd_repstat = nfsvno_accchk(vp, VWRITE, nd->nd_cred,
+	        exp, p, NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED, NULL);
 	if (!nd->nd_repstat && (stp->ls_flags & NFSLCK_READACCESS)) {
-	    nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_READDATA, nd->nd_cred,
-	        exp, p, NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED);
+	    nd->nd_repstat = nfsvno_accchk(vp, VREAD, nd->nd_cred,
+	        exp, p, NFSACCCHK_ALLOWOWNER, NFSACCCHK_VPISLOCKED, NULL);
 	    if (nd->nd_repstat)
-		nd->nd_repstat = nfsvno_accchk(vp, NFSV4ACE_EXECUTE,
+		nd->nd_repstat = nfsvno_accchk(vp, VEXEC,
 		    nd->nd_cred, exp, p, NFSACCCHK_ALLOWOWNER,
-		    NFSACCCHK_VPISLOCKED);
+		    NFSACCCHK_VPISLOCKED, NULL);
 	}
 
 	if (!nd->nd_repstat) {



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200912252044.nBPKiJQp071077>