setattr() system call (task from phk TODO list).

Pawel Jakub Dawidek P.Dawidek at prioris.mini.pw.edu.pl
Mon Jul 7 02:37:22 PDT 2003


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                       pawel at dawidek.net
UNIX Systems Programmer/Administrator     http://garage.freebsd.pl
Am I Evil? Yes, I Am!                     http://cerber.sourceforge.net
-------------- next part --------------
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,
-------------- next part --------------
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);
 }
 
 /*


More information about the freebsd-hackers mailing list