Hello. I think I got patch for setattr() syscall (attached as setattr.patch) and it works fine, but there is a problem in ufs_setattr(). It looks like ufs_setattr() will don't handle property changes of many file attibutes, because there is many places where it can modify some attributes, but return with error, because next change will fail. For now there is also no chance to change flags to '[su]chg'/'[su]appnd' and other attributes in one call of ufs_setattr(), because flags are changed first and other modifications aren't permitted because of that. If we put flags changing code at the end, there will be no chance to change other attributes when flags are '[su]chg' or '[su]appnd'. So I'm sending also patch for UFS that allow other attributes changes when before or after setattr() there is/will be no [su](chg|appnd) flags (patch attached: ufs_setattr.patch). It isn't the best way. The best why will be made a copy of inode struct go through changes and copy it back only if everything succeeded. This cost performance of course, so...
PS. This syscall will be quite usefull in tar(1) I think. -- Pawel Jakub Dawidek [EMAIL PROTECTED] UNIX Systems Programmer/Administrator http://garage.freebsd.pl Am I Evil? Yes, I Am! http://cerber.sourceforge.net
diff -ur /usr/src/sys/kern/syscalls.master src/sys/kern/syscalls.master --- /usr/src/sys/kern/syscalls.master Sat Jul 5 16:54:11 2003 +++ src/sys/kern/syscalls.master Sat Jul 5 16:54:05 2003 @@ -637,6 +637,9 @@ int attrnamespace, void *data, size_t nbytes); } 439 STD BSD { ssize_t extattr_list_link(const char *path, \ int attrnamespace, void *data, size_t nbytes); } +440 STD BSD { int setattr(char *path, struct stat *sb, int op); } +441 STD BSD { int lsetattr(char *path, struct stat *sb, int op); } +442 STD BSD { int fsetattr(int fd, struct stat *sb, int op); } ; Please copy any additions and changes to the following compatability tables: ; sys/ia64/ia32/syscalls.master (take a best guess) diff -ur /usr/src/sys/kern/vfs_syscalls.c src/sys/kern/vfs_syscalls.c --- /usr/src/sys/kern/vfs_syscalls.c Tue Jul 1 11:29:38 2003 +++ src/sys/kern/vfs_syscalls.c Sat Jul 5 21:12:23 2003 @@ -1940,48 +1940,254 @@ return (error); } -/* - * Common implementation code for chflags() and fchflags(). - */ static int -setfflags(td, vp, flags) - struct thread *td; - struct vnode *vp; - int flags; +setfattr(struct thread *td, struct vnode *vp, struct stat *sb, u_int op, + int locked) { - int error; struct mount *mp; struct vattr vattr; + int error; - /* - * Prevent non-root users from setting flags on devices. When - * a device is reused, users can retain ownership of the device - * if they are allowed to set flags and programs assume that - * chown can't fail when done as root. - */ - if (vp->v_type == VCHR || vp->v_type == VBLK) { - error = suser_cred(td->td_ucred, PRISON_ROOT); - if (error) + if ((op & SA_OP_ALL) == 0) + return (0); + + if ((op & SA_OP_FLAGS) != 0) { + /* + * Prevent non-root users from setting flags on devices. When + * a device is reused, users can retain ownership of the device + * if they are allowed to set flags and programs assume that + * chown can't fail when done as root. + */ + if (vp->v_type == VCHR || vp->v_type == VBLK) { + error = suser_cred(td->td_ucred, PRISON_ROOT); + if (error) + return (error); + } + } + + if (!locked) { + if ((error = vn_start_write(vp, &mp, V_WAIT | PCATCH)) != 0) return (error); + VOP_LEASE(vp, td, td->td_ucred, LEASE_WRITE); + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, td); } - if ((error = vn_start_write(vp, &mp, V_WAIT | PCATCH)) != 0) - return (error); - VOP_LEASE(vp, td, td->td_ucred, LEASE_WRITE); - vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, td); VATTR_NULL(&vattr); - vattr.va_flags = flags; + + if ((op & SA_OP_FLAGS) != 0) { + vattr.va_flags = sb->st_flags; #ifdef MAC - error = mac_check_vnode_setflags(td->td_ucred, vp, vattr.va_flags); - if (error == 0) + error = mac_check_vnode_setflags(td->td_ucred, vp, + vattr.va_flags); + if (error != 0) + goto fail; #endif - error = VOP_SETATTR(vp, &vattr, td->td_ucred, td); - VOP_UNLOCK(vp, 0, td); - vn_finished_write(mp); + } + if ((op & SA_OP_MODE) != 0) { + vattr.va_mode = sb->st_mode & ALLPERMS; +#ifdef MAC + error = mac_check_vnode_setmode(td->td_ucred, vp, + vattr.va_mode); + if (error != 0) + goto fail; +#endif + } + if ((op & SA_OP_OWNER) != 0) { + vattr.va_uid = sb->st_uid; + vattr.va_gid = sb->st_gid; +#ifdef MAC + error = mac_check_vnode_setowner(td->td_ucred, vp, vattr.va_uid, + vattr.va_gid); + if (error != 0) + goto fail; +#endif + } + if ((op & SA_OP_UTIMES) != 0) { + if ((op & SA_OP_ATIME) != 0) + vattr.va_atime = sb->st_atimespec; + if ((op & SA_OP_MTIME) != 0) + vattr.va_mtime = sb->st_mtimespec; + if ((op & SA_OP_BIRTHTIME) != 0) + vattr.va_birthtime = sb->st_birthtimespec; +#ifdef MAC + error = mac_check_vnode_setutimes(td->td_ucred, vp, + vattr.va_atime, vattr.va_mtime); + if (error != 0) + goto fail; +#endif + if ((op & SA_OP_UTIMES_NULL) != 0) + vattr.va_vaflags |= VA_UTIMES_NULL; + } + + error = VOP_SETATTR(vp, &vattr, td->td_ucred, td); +fail: + if (!locked) { + VOP_UNLOCK(vp, 0, td); + vn_finished_write(mp); + } + + return (error); +} + +/* + * Change attributes of a file given path name. + */ +#ifndef _SYS_SYSPROTO_H_ +struct setattr_args { + char *path; + struct stat *sb; + u_int op; +}; +#endif +/* ARGSUSED */ +int +setattr(td, uap) + struct thread *td; + register struct setattr_args /* { + char *path; + struct stat *sb; + u_int op; + } */ *uap; +{ + + return (kern_setattr(td, uap->path, UIO_USERSPACE, uap->sb, + UIO_USERSPACE, uap->op)); +} + +int +kern_setattr(struct thread *td, char *path, enum uio_seg pathseg, + struct stat *sb, enum uio_seg sbseg, u_int op) +{ + int error; + struct nameidata nd; + struct stat stb; + + if (sbseg == UIO_USERSPACE) { + if ((error = copyin(sb, &stb, sizeof(stb))) != 0) + return (error); + } else { + stb = *sb; + } + NDINIT(&nd, LOOKUP, FOLLOW, pathseg, path, td); + if ((error = namei(&nd)) != 0) + return (error); + NDFREE(&nd, NDF_ONLY_PNBUF); + error = setfattr(td, nd.ni_vp, &stb, op, 0); + vrele(nd.ni_vp); + return error; +} + +/* + * Change attributes of a file given path name (don't follow links.) + */ +#ifndef _SYS_SYSPROTO_H_ +struct lsetattr_args { + char *path; + struct stat *sb; + u_int op; +}; +#endif +/* ARGSUSED */ +int +lsetattr(td, uap) + struct thread *td; + register struct lsetattr_args /* { + char *path; + struct stat *sb; + u_int op; + } */ *uap; +{ + + return (kern_lsetattr(td, uap->path, UIO_USERSPACE, uap->sb, + UIO_USERSPACE, uap->op)); +} + +int +kern_lsetattr(struct thread *td, char *path, enum uio_seg pathseg, + struct stat *sb, enum uio_seg sbseg, u_int op) +{ + int error; + struct nameidata nd; + struct stat stb; + + if (sbseg == UIO_USERSPACE) { + if ((error = copyin(sb, &stb, sizeof(stb))) != 0) + return (error); + } else { + stb = *sb; + } + NDINIT(&nd, LOOKUP, NOFOLLOW, pathseg, path, td); + if ((error = namei(&nd)) != 0) + return (error); + NDFREE(&nd, NDF_ONLY_PNBUF); + error = setfattr(td, nd.ni_vp, &stb, op, 0); + vrele(nd.ni_vp); + return error; +} + +/* + * Change attributes of a file given a file descriptor. + */ +#ifndef _SYS_SYSPROTO_H_ +struct fsetattr_args { + int fd; + struct stat *sb; + u_int op; +}; +#endif +/* ARGSUSED */ +int +fsetattr(td, uap) + struct thread *td; + register struct fsetattr_args /* { + int fd; + struct stat *sb; + u_int op; + } */ *uap; +{ + + return (kern_fsetattr(td, uap->fd, uap->sb, UIO_USERSPACE, uap->op)); +} + +int +kern_fsetattr(struct thread *td, int fd, struct stat *sb, enum uio_seg sbseg, + u_int op) +{ + int error; + struct file *fp; + struct stat stb; + + if (sbseg == UIO_USERSPACE) { + if ((error = copyin(sb, &stb, sizeof(stb))) != 0) + return (error); + } else { + stb = *sb; + } + if ((error = getvnode(td->td_proc->p_fd, fd, &fp)) != 0) + return (error); + error = setfattr(td, fp->f_vnode, &stb, op, 0); + fdrop(fp, td); + return (error); } /* + * Common implementation code for chflags() and fchflags(). + */ +static int __inline +setfflags(td, vp, flags) + struct thread *td; + struct vnode *vp; + int flags; +{ + struct stat sb; + + sb.st_flags = flags; + + return (setfattr(td, vp, &sb, SA_OP_FLAGS, 0)); +} + +/* * Change flags of a file given a path name. */ #ifndef _SYS_SYSPROTO_H_ @@ -2065,30 +2271,17 @@ /* * Common implementation code for chmod(), lchmod() and fchmod(). */ -static int +static int __inline setfmode(td, vp, mode) struct thread *td; struct vnode *vp; int mode; { - int error; - struct mount *mp; - struct vattr vattr; + struct stat sb; - if ((error = vn_start_write(vp, &mp, V_WAIT | PCATCH)) != 0) - return (error); - VOP_LEASE(vp, td, td->td_ucred, LEASE_WRITE); - vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, td); - VATTR_NULL(&vattr); - vattr.va_mode = mode & ALLPERMS; -#ifdef MAC - error = mac_check_vnode_setmode(td->td_ucred, vp, vattr.va_mode); - if (error == 0) -#endif - error = VOP_SETATTR(vp, &vattr, td->td_ucred, td); - VOP_UNLOCK(vp, 0, td); - vn_finished_write(mp); - return error; + sb.st_mode = mode; + + return (setfattr(td, vp, &sb, SA_OP_MODE, 0)); } /* @@ -2189,33 +2382,19 @@ /* * Common implementation for chown(), lchown(), and fchown() */ -static int +static int __inline setfown(td, vp, uid, gid) struct thread *td; struct vnode *vp; uid_t uid; gid_t gid; { - int error; - struct mount *mp; - struct vattr vattr; + struct stat sb; - if ((error = vn_start_write(vp, &mp, V_WAIT | PCATCH)) != 0) - return (error); - VOP_LEASE(vp, td, td->td_ucred, LEASE_WRITE); - vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, td); - VATTR_NULL(&vattr); - vattr.va_uid = uid; - vattr.va_gid = gid; -#ifdef MAC - error = mac_check_vnode_setowner(td->td_ucred, vp, vattr.va_uid, - vattr.va_gid); - if (error == 0) -#endif - error = VOP_SETATTR(vp, &vattr, td->td_ucred, td); - VOP_UNLOCK(vp, 0, td); - vn_finished_write(mp); - return error; + sb.st_uid = uid; + sb.st_gid = gid; + + return (setfattr(td, vp, &sb, SA_OP_OWNER, 0)); } /* @@ -2363,7 +2542,7 @@ /* * Common implementation code for utimes(), lutimes(), and futimes(). */ -static int +static int __inline setutimes(td, vp, ts, numtimes, nullflag) struct thread *td; struct vnode *vp; @@ -2374,6 +2553,8 @@ int error, setbirthtime; struct mount *mp; struct vattr vattr; + struct stat sb; + u_int op; if ((error = vn_start_write(vp, &mp, V_WAIT | PCATCH)) != 0) return (error); @@ -2383,24 +2564,26 @@ if (numtimes < 3 && VOP_GETATTR(vp, &vattr, td->td_ucred, td) == 0 && timespeccmp(&ts[1], &vattr.va_birthtime, < )) setbirthtime = 1; - VATTR_NULL(&vattr); - vattr.va_atime = ts[0]; - vattr.va_mtime = ts[1]; - if (setbirthtime) - vattr.va_birthtime = ts[1]; - if (numtimes > 2) - vattr.va_birthtime = ts[2]; + sb.st_atimespec = ts[0]; + sb.st_mtimespec = ts[1]; + op = SA_OP_ATIME | SA_OP_MTIME; + if (setbirthtime) { + sb.st_birthtimespec = ts[1]; + op |= SA_OP_BIRTHTIME; + } + if (numtimes > 2) { + sb.st_birthtimespec = ts[2]; + op |= SA_OP_BIRTHTIME; + } if (nullflag) - vattr.va_vaflags |= VA_UTIMES_NULL; -#ifdef MAC - error = mac_check_vnode_setutimes(td->td_ucred, vp, vattr.va_atime, - vattr.va_mtime); -#endif - if (error == 0) - error = VOP_SETATTR(vp, &vattr, td->td_ucred, td); + op |= SA_OP_UTIMES_NULL; + + error = setfattr(td, vp, &sb, op, 1); + VOP_UNLOCK(vp, 0, td); vn_finished_write(mp); - return error; + + return (error); } /* diff -ur /usr/src/sys/sys/stat.h src/sys/sys/stat.h --- /usr/src/sys/sys/stat.h Thu May 22 19:07:57 2003 +++ src/sys/sys/stat.h Mon Jul 7 11:34:36 2003 @@ -288,6 +288,22 @@ #define SF_NOUNLINK 0x00100000 /* file may not be removed or renamed */ #define SF_SNAPSHOT 0x00200000 /* snapshot inode */ +#if __BSD_VISIBLE +/* + * Avaliable options for setattr() syscall. + */ +#define SA_OP_FLAGS 0x01 +#define SA_OP_MODE 0x02 +#define SA_OP_OWNER 0x04 +#define SA_OP_ATIME 0x08 +#define SA_OP_MTIME 0x10 +#define SA_OP_BIRTHTIME 0x20 +#define SA_OP_UTIMES (SA_OP_ATIME | SA_OP_MTIME | SA_OP_BIRTHTIME) +#define SA_OP_ALL (SA_OP_FLAGS | SA_OP_MODE | SA_OP_OWNER | SA_OP_UTIMES) +/* Additional options: */ +#define SA_OP_UTIMES_NULL 0x0100 +#endif + #ifdef _KERNEL /* * Shorthand abbreviations of above. @@ -303,6 +319,9 @@ #ifndef _KERNEL __BEGIN_DECLS #if __BSD_VISIBLE +int setattr(const char *, struct stat *, unsigned op); +int lsetattr(const char *, struct stat *, unsigned op); +int fsetattr(int, struct stat *, unsigned op); int chflags(const char *, unsigned long); #endif int chmod(const char *, mode_t); diff -ur /usr/src/sys/sys/syscall.h src/sys/sys/syscall.h --- /usr/src/sys/sys/syscall.h Tue Jul 1 11:30:18 2003 +++ src/sys/sys/syscall.h Sat Jul 5 21:06:39 2003 @@ -2,8 +2,8 @@ * System call numbers. * * DO NOT EDIT-- this file is automatically generated. - * $FreeBSD: src/sys/sys/syscall.h,v 1.138 2003/06/28 08:29:04 davidxu Exp $ - * created from FreeBSD: src/sys/kern/syscalls.master,v 1.150 2003/06/04 03:49:31 rwatson Exp + * $FreeBSD$ + * created from FreeBSD: src/sys/kern/syscalls.master,v 1.151 2003/06/28 08:29:05 davidxu Exp */ #define SYS_syscall 0 @@ -347,4 +347,7 @@ #define SYS_extattr_list_fd 437 #define SYS_extattr_list_file 438 #define SYS_extattr_list_link 439 -#define SYS_MAXSYSCALL 440 +#define SYS_setattr 440 +#define SYS_lsetattr 441 +#define SYS_fsetattr 442 +#define SYS_MAXSYSCALL 443 diff -ur /usr/src/sys/sys/syscall.mk src/sys/sys/syscall.mk --- /usr/src/sys/sys/syscall.mk Tue Jul 1 11:30:18 2003 +++ src/sys/sys/syscall.mk Sat Jul 5 21:06:39 2003 @@ -1,7 +1,7 @@ # FreeBSD system call names. # DO NOT EDIT-- this file is automatically generated. -# $FreeBSD: src/sys/sys/syscall.mk,v 1.93 2003/06/28 08:29:04 davidxu Exp $ -# created from FreeBSD: src/sys/kern/syscalls.master,v 1.150 2003/06/04 03:49:31 rwatson Exp +# $FreeBSD$ +# created from FreeBSD: src/sys/kern/syscalls.master,v 1.151 2003/06/28 08:29:05 davidxu Exp MIASM = \ syscall.o \ exit.o \ @@ -292,4 +292,7 @@ jail_attach.o \ extattr_list_fd.o \ extattr_list_file.o \ - extattr_list_link.o + extattr_list_link.o \ + setattr.o \ + lsetattr.o \ + fsetattr.o diff -ur /usr/src/sys/sys/syscallsubr.h src/sys/sys/syscallsubr.h --- /usr/src/sys/sys/syscallsubr.h Sat Jul 5 17:01:40 2003 +++ src/sys/sys/syscallsubr.h Sat Jul 5 19:58:10 2003 @@ -30,6 +30,7 @@ #include <sys/signal.h> #include <sys/uio.h> +#include <sys/stat.h> struct sockaddr; struct msghdr; @@ -41,6 +42,12 @@ int flags); int kern_bind(struct thread *td, int fd, struct sockaddr *sa); int kern_chdir(struct thread *td, char *path, enum uio_seg pathseg); +int kern_setattr(struct thread *td, char *path, enum uio_seg pathseg, + struct stat *sb, enum uio_seg sbseg, u_int op); +int kern_lsetattr(struct thread *td, char *path, enum uio_seg pathseg, + struct stat *sb, enum uio_seg sbseg, u_int op); +int kern_fsetattr(struct thread *td, int fd, struct stat *sb, + enum uio_seg sbseg, u_int op); int kern_chmod(struct thread *td, char *path, enum uio_seg pathseg, int mode); int kern_chown(struct thread *td, char *path, enum uio_seg pathseg, int uid,
diff -ur /usr/src/sys/ufs/ufs/ufs_vnops.c src/sys/ufs/ufs/ufs_vnops.c --- /usr/src/sys/ufs/ufs/ufs_vnops.c Tue Jul 1 11:30:20 2003 +++ src/sys/ufs/ufs/ufs_vnops.c Sun Jul 6 11:27:12 2003 @@ -87,6 +87,7 @@ static int ufs_access(struct vop_access_args *); static int ufs_advlock(struct vop_advlock_args *); +static int ufs_chflags(struct vnode *, u_int32_t, struct ucred *, struct thread *); static int ufs_chmod(struct vnode *, int, struct ucred *, struct thread *); static int ufs_chown(struct vnode *, uid_t, gid_t, struct ucred *, struct thread *); static int ufs_close(struct vop_close_args *); @@ -487,51 +488,28 @@ return (EINVAL); } if (vap->va_flags != VNOVAL) { - if (vp->v_mount->mnt_flag & MNT_RDONLY) - return (EROFS); /* - * Callers may only modify the file flags on objects they - * have VADMIN rights for. + * If we have for example 'schg' flag on file and we're + * trying to remove it, but set 'sappnd' flag no other + * modifications are permitted. */ - if ((error = VOP_ACCESS(vp, VADMIN, cred, td))) + if ((vap->va_flags & (IMMUTABLE | APPEND)) && + (ip->i_flags & (IMMUTABLE | APPEND)) && + (vap->va_uid != (uid_t)VNOVAL || + vap->va_gid != (gid_t)VNOVAL || + vap->va_size != VNOVAL || + vap->va_atime.tv_sec != VNOVAL || + vap->va_mtime.tv_sec != VNOVAL || + vap->va_birthtime.tv_sec != VNOVAL || + vap->va_mode != (mode_t)VNOVAL)) { + return (EPERM); + } + error = ufs_chflags(vp, (u_int32_t)vap->va_flags, cred, td); + if (error != 0) return (error); - /* - * Unprivileged processes and privileged processes in - * jail() are not permitted to unset system flags, or - * modify flags if any system flags are set. - * Privileged non-jail processes may not modify system flags - * if securelevel > 0 and any existing system flags are set. - */ - if (!suser_cred(cred, PRISON_ROOT)) { - if (ip->i_flags - & (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND)) { - error = securelevel_gt(cred, 0); - if (error) - return (error); - } - /* Snapshot flag cannot be set or cleared */ - if (((vap->va_flags & SF_SNAPSHOT) != 0 && - (ip->i_flags & SF_SNAPSHOT) == 0) || - ((vap->va_flags & SF_SNAPSHOT) == 0 && - (ip->i_flags & SF_SNAPSHOT) != 0)) - return (EPERM); - ip->i_flags = vap->va_flags; - DIP(ip, i_flags) = vap->va_flags; - } else { - if (ip->i_flags - & (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND) || - (vap->va_flags & UF_SETTABLE) != vap->va_flags) - return (EPERM); - ip->i_flags &= SF_SETTABLE; - ip->i_flags |= (vap->va_flags & UF_SETTABLE); - DIP(ip, i_flags) = ip->i_flags; - } - ip->i_flag |= IN_CHANGE; - if (vap->va_flags & (IMMUTABLE | APPEND)) - return (0); - } - if (ip->i_flags & (IMMUTABLE | APPEND)) + } else if (ip->i_flags & (IMMUTABLE | APPEND)) { return (EPERM); + } /* * Go through the fields and update iff not VNOVAL. */ @@ -618,8 +596,63 @@ return (EPERM); error = ufs_chmod(vp, (int)vap->va_mode, cred, td); } + if (error == 0 && vap->va_flags != VNOVAL && !chflags) + error = ufs_chflags(vp, (u_int32_t)vap->va_flags, cred, td); VN_KNOTE(vp, NOTE_ATTRIB); return (error); +} + +/* + * Perform chflags operation on inode ip; + * inode must be locked prior to call. + */ +static int +ufs_chflags(struct vnode *vp, u_int32_t flags, struct ucred *cred, + struct thread *td) +{ + struct inode *ip = VTOI(vp); + int error; + + if (vp->v_mount->mnt_flag & MNT_RDONLY) + return (EROFS); + /* + * Callers may only modify the file flags on objects they + * have VADMIN rights for. + */ + if ((error = VOP_ACCESS(vp, VADMIN, cred, td))) + return (error); + /* + * Unprivileged processes and privileged processes in + * jail() are not permitted to unset system flags, or + * modify flags if any system flags are set. + * Privileged non-jail processes may not modify system flags + * if securelevel > 0 and any existing system flags are set. + */ + if (!suser_cred(cred, PRISON_ROOT)) { + if (ip->i_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 && + (ip->i_flags & SF_SNAPSHOT) == 0) || + ((flags & SF_SNAPSHOT) == 0 && + (ip->i_flags & SF_SNAPSHOT) != 0)) + return (EPERM); + ip->i_flags = flags; + DIP(ip, i_flags) = flags; + } else { + if (ip->i_flags & (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND) || + (flags & UF_SETTABLE) != flags) + return (EPERM); + ip->i_flags &= SF_SETTABLE; + ip->i_flags |= (flags & UF_SETTABLE); + DIP(ip, i_flags) = ip->i_flags; + } + ip->i_flag |= IN_CHANGE; + + return (0); } /*
_______________________________________________ [EMAIL PROTECTED] mailing list http://lists.freebsd.org/mailman/listinfo/freebsd-hackers To unsubscribe, send any mail to "[EMAIL PROTECTED]"