Fedora kernel-2.6.17-1.2142_FC4 patched with stable patch-2.6.17.4-vs2.0.2-rc26.diff
[linux-2.6.git] / fs / nfsd / vfs.c
index e3e9d21..9564556 100644 (file)
 #endif /* CONFIG_NFSD_V3 */
 #include <linux/nfsd/nfsfh.h>
 #include <linux/quotaops.h>
-#include <linux/dnotify.h>
-#ifdef CONFIG_NFSD_V4
+#include <linux/fsnotify.h>
 #include <linux/posix_acl.h>
 #include <linux/posix_acl_xattr.h>
-#include <linux/xattr_acl.h>
 #include <linux/xattr.h>
+#ifdef CONFIG_NFSD_V4
 #include <linux/nfs4.h>
 #include <linux/nfs4_acl.h>
 #include <linux/nfsd_idmap.h>
@@ -255,12 +254,19 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
 
        /* Get inode */
        err = fh_verify(rqstp, fhp, ftype, accmode);
-       if (err || !iap->ia_valid)
+       if (err)
                goto out;
 
        dentry = fhp->fh_dentry;
        inode = dentry->d_inode;
 
+       /* Ignore any mode updates on symlinks */
+       if (S_ISLNK(inode->i_mode))
+               iap->ia_valid &= ~ATTR_MODE;
+
+       if (!iap->ia_valid)
+               goto out;
+
        /* NFSv2 does not differentiate between "set-[ac]time-to-now"
         * which only requires access, and "set-[ac]time-to-X" which
         * requires ownership.
@@ -359,8 +365,26 @@ out_nfserr:
        goto out;
 }
 
-#if defined(CONFIG_NFSD_V4)
+#if defined(CONFIG_NFSD_V2_ACL) || \
+    defined(CONFIG_NFSD_V3_ACL) || \
+    defined(CONFIG_NFSD_V4)
+static ssize_t nfsd_getxattr(struct dentry *dentry, char *key, void **buf)
+{
+       ssize_t buflen;
+
+       buflen = vfs_getxattr(dentry, key, NULL, 0);
+       if (buflen <= 0)
+               return buflen;
+
+       *buf = kmalloc(buflen, GFP_KERNEL);
+       if (!*buf)
+               return -ENOMEM;
+
+       return vfs_getxattr(dentry, key, *buf, buflen);
+}
+#endif
 
+#if defined(CONFIG_NFSD_V4)
 static int
 set_nfsv4_acl_one(struct dentry *dentry, struct posix_acl *pacl, char *key)
 {
@@ -368,7 +392,6 @@ set_nfsv4_acl_one(struct dentry *dentry, struct posix_acl *pacl, char *key)
        size_t buflen;
        char *buf = NULL;
        int error = 0;
-       struct inode *inode = dentry->d_inode;
 
        buflen = posix_acl_xattr_size(pacl->a_count);
        buf = kmalloc(buflen, GFP_KERNEL);
@@ -382,15 +405,7 @@ set_nfsv4_acl_one(struct dentry *dentry, struct posix_acl *pacl, char *key)
                goto out;
        }
 
-       error = -EOPNOTSUPP;
-       if (inode->i_op && inode->i_op->setxattr) {
-               down(&inode->i_sem);
-               security_inode_setxattr(dentry, key, buf, len, 0);
-               error = inode->i_op->setxattr(dentry, key, buf, len, 0);
-               if (!error)
-                       security_inode_post_setxattr(dentry, key, buf, len, 0);
-               up(&inode->i_sem);
-       }
+       error = vfs_setxattr(dentry, key, buf, len, 0);
 out:
        kfree(buf);
        return error;
@@ -424,13 +439,13 @@ nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
                goto out_nfserr;
 
        if (pacl) {
-               error = set_nfsv4_acl_one(dentry, pacl, XATTR_NAME_ACL_ACCESS);
+               error = set_nfsv4_acl_one(dentry, pacl, POSIX_ACL_XATTR_ACCESS);
                if (error < 0)
                        goto out_nfserr;
        }
 
        if (dpacl) {
-               error = set_nfsv4_acl_one(dentry, dpacl, XATTR_NAME_ACL_DEFAULT);
+               error = set_nfsv4_acl_one(dentry, dpacl, POSIX_ACL_XATTR_DEFAULT);
                if (error < 0)
                        goto out_nfserr;
        }
@@ -449,44 +464,19 @@ out_nfserr:
 static struct posix_acl *
 _get_posix_acl(struct dentry *dentry, char *key)
 {
-       struct inode *inode = dentry->d_inode;
-       char *buf = NULL;
-       int buflen, error = 0;
+       void *buf = NULL;
        struct posix_acl *pacl = NULL;
+       int buflen;
 
-       error = -EOPNOTSUPP;
-       if (inode->i_op == NULL)
-               goto out_err;
-       if (inode->i_op->getxattr == NULL)
-               goto out_err;
-
-       error = security_inode_getxattr(dentry, key);
-       if (error)
-               goto out_err;
-
-       buflen = inode->i_op->getxattr(dentry, key, NULL, 0);
-       if (buflen <= 0) {
-               error = buflen < 0 ? buflen : -ENODATA;
-               goto out_err;
-       }
-
-       buf = kmalloc(buflen, GFP_KERNEL);
-       if (buf == NULL) {
-               error = -ENOMEM;
-               goto out_err;
-       }
-
-       error = inode->i_op->getxattr(dentry, key, buf, buflen);
-       if (error < 0)
-               goto out_err;
+       buflen = nfsd_getxattr(dentry, key, &buf);
+       if (!buflen)
+               buflen = -ENODATA;
+       if (buflen <= 0)
+               return ERR_PTR(buflen);
 
        pacl = posix_acl_from_xattr(buf, buflen);
- out:
        kfree(buf);
        return pacl;
- out_err:
-       pacl = ERR_PTR(error);
-       goto out;
 }
 
 int
@@ -497,7 +487,7 @@ nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry, struct nfs4_ac
        struct posix_acl *pacl = NULL, *dpacl = NULL;
        unsigned int flags = 0;
 
-       pacl = _get_posix_acl(dentry, XATTR_NAME_ACL_ACCESS);
+       pacl = _get_posix_acl(dentry, POSIX_ACL_XATTR_ACCESS);
        if (IS_ERR(pacl) && PTR_ERR(pacl) == -ENODATA)
                pacl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
        if (IS_ERR(pacl)) {
@@ -507,7 +497,7 @@ nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry, struct nfs4_ac
        }
 
        if (S_ISDIR(inode->i_mode)) {
-               dpacl = _get_posix_acl(dentry, XATTR_NAME_ACL_DEFAULT);
+               dpacl = _get_posix_acl(dentry, POSIX_ACL_XATTR_DEFAULT);
                if (IS_ERR(dpacl) && PTR_ERR(dpacl) == -ENODATA)
                        dpacl = NULL;
                else if (IS_ERR(dpacl)) {
@@ -711,33 +701,40 @@ nfsd_close(struct file *filp)
  * As this calls fsync (not fdatasync) there is no need for a write_inode
  * after it.
  */
-static inline void nfsd_dosync(struct file *filp, struct dentry *dp,
-                              struct file_operations *fop)
+static inline int nfsd_dosync(struct file *filp, struct dentry *dp,
+                             const struct file_operations *fop)
 {
        struct inode *inode = dp->d_inode;
        int (*fsync) (struct file *, struct dentry *, int);
+       int err;
+
+       err = filemap_fdatawrite(inode->i_mapping);
+       if (err == 0 && fop && (fsync = fop->fsync))
+               err = fsync(filp, dp, 0);
+       if (err == 0)
+               err = filemap_fdatawait(inode->i_mapping);
 
-       filemap_fdatawrite(inode->i_mapping);
-       if (fop && (fsync = fop->fsync))
-               fsync(filp, dp, 0);
-       filemap_fdatawait(inode->i_mapping);
+       return err;
 }
        
 
-static void
+static int
 nfsd_sync(struct file *filp)
 {
+        int err;
        struct inode *inode = filp->f_dentry->d_inode;
        dprintk("nfsd: sync file %s\n", filp->f_dentry->d_name.name);
-       down(&inode->i_sem);
-       nfsd_dosync(filp, filp->f_dentry, filp->f_op);
-       up(&inode->i_sem);
+       mutex_lock(&inode->i_mutex);
+       err=nfsd_dosync(filp, filp->f_dentry, filp->f_op);
+       mutex_unlock(&inode->i_mutex);
+
+       return err;
 }
 
-static void
+int
 nfsd_sync_dir(struct dentry *dp)
 {
-       nfsd_dosync(NULL, dp, dp->d_inode->i_fop);
+       return nfsd_dosync(NULL, dp, dp->d_inode->i_fop);
 }
 
 /*
@@ -814,7 +811,7 @@ nfsd_read_actor(read_descriptor_t *desc, struct page *page, unsigned long offset
        return size;
 }
 
-static inline int
+static int
 nfsd_vfs_read(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
               loff_t offset, struct kvec *vec, int vlen, unsigned long *count)
 {
@@ -861,14 +858,24 @@ nfsd_vfs_read(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
                nfsdstats.io_read += err;
                *count = err;
                err = 0;
-               dnotify_parent(file->f_dentry, DN_ACCESS);
+               fsnotify_access(file->f_dentry);
        } else 
                err = nfserrno(err);
 out:
        return err;
 }
 
-static inline int
+static void kill_suid(struct dentry *dentry)
+{
+       struct iattr    ia;
+       ia.ia_valid = ATTR_KILL_SUID | ATTR_KILL_SGID;
+
+       mutex_lock(&dentry->d_inode->i_mutex);
+       notify_change(dentry, &ia);
+       mutex_unlock(&dentry->d_inode->i_mutex);
+}
+
+static int
 nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
                                loff_t offset, struct kvec *vec, int vlen,
                                unsigned long cnt, int *stablep)
@@ -880,9 +887,9 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
        int                     err = 0;
        int                     stable = *stablep;
 
+#ifdef MSNFS
        err = nfserr_perm;
 
-#ifdef MSNFS
        if ((fhp->fh_export->ex_flags & NFSEXP_MSNFS) &&
                (!lock_may_write(file->f_dentry->d_inode, offset, cnt)))
                goto out;
@@ -917,18 +924,12 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
        set_fs(oldfs);
        if (err >= 0) {
                nfsdstats.io_write += cnt;
-               dnotify_parent(file->f_dentry, DN_MODIFY);
+               fsnotify_modify(file->f_dentry);
        }
 
        /* clear setuid/setgid flag after write */
-       if (err >= 0 && (inode->i_mode & (S_ISUID | S_ISGID))) {
-               struct iattr    ia;
-               ia.ia_valid = ATTR_KILL_SUID | ATTR_KILL_SGID;
-
-               down(&inode->i_sem);
-               notify_change(dentry, &ia);
-               up(&inode->i_sem);
-       }
+       if (err >= 0 && (inode->i_mode & (S_ISUID | S_ISGID)))
+               kill_suid(dentry);
 
        if (err >= 0 && stable) {
                static ino_t    last_ino;
@@ -956,7 +957,7 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file,
 
                        if (inode->i_state & I_DIRTY) {
                                dprintk("nfsd: write sync %d\n", current->pid);
-                               nfsd_sync(file);
+                               err=nfsd_sync(file);
                        }
 #if 0
                        wake_up(&inode->i_wait);
@@ -1060,7 +1061,7 @@ nfsd_commit(struct svc_rqst *rqstp, struct svc_fh *fhp,
                return err;
        if (EX_ISSYNC(fhp->fh_export)) {
                if (file->f_op && file->f_op->fsync) {
-                       nfsd_sync(file);
+                       err = nfserrno(nfsd_sync(file));
                } else {
                        err = nfserr_notsupp;
                }
@@ -1128,7 +1129,7 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
                                "nfsd_create: parent %s/%s not locked!\n",
                                dentry->d_parent->d_name.name,
                                dentry->d_name.name);
-                       err = -EIO;
+                       err = nfserr_io;
                        goto out;
                }
        }
@@ -1155,13 +1156,13 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
                err = vfs_create(dirp, dchild, iap->ia_mode, NULL);
                break;
        case S_IFDIR:
-               err = vfs_mkdir(dirp, dchild, iap->ia_mode);
+               err = vfs_mkdir(dirp, dchild, iap->ia_mode, NULL);
                break;
        case S_IFCHR:
        case S_IFBLK:
        case S_IFIFO:
        case S_IFSOCK:
-               err = vfs_mknod(dirp, dchild, iap->ia_mode, rdev);
+               err = vfs_mknod(dirp, dchild, iap->ia_mode, rdev, NULL);
                break;
        default:
                printk("nfsd: bad file type %o in nfsd_create\n", type);
@@ -1171,7 +1172,7 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
                goto out_nfserr;
 
        if (EX_ISSYNC(fhp->fh_export)) {
-               nfsd_sync_dir(dentry);
+               err = nfserrno(nfsd_sync_dir(dentry));
                write_inode_now(dchild->d_inode, 1);
        }
 
@@ -1181,9 +1182,11 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
         * send along the gid when it tries to implement setgid
         * directories via NFS.
         */
-       err = 0;
-       if ((iap->ia_valid &= ~(ATTR_UID|ATTR_GID|ATTR_MODE)) != 0)
-               err = nfsd_setattr(rqstp, resfhp, iap, 0, (time_t)0);
+       if ((iap->ia_valid &= ~(ATTR_UID|ATTR_GID|ATTR_MODE)) != 0) {
+               int err2 = nfsd_setattr(rqstp, resfhp, iap, 0, (time_t)0);
+               if (err2)
+                       err = err2;
+       }
        /*
         * Update the file handle to get the new inode info.
         */
@@ -1302,17 +1305,10 @@ nfsd_create_v3(struct svc_rqst *rqstp, struct svc_fh *fhp,
                goto out_nfserr;
 
        if (EX_ISSYNC(fhp->fh_export)) {
-               nfsd_sync_dir(dentry);
+               err = nfserrno(nfsd_sync_dir(dentry));
                /* setattr will sync the child (or not) */
        }
 
-       /*
-        * Update the filehandle to get the new inode info.
-        */
-       err = fh_update(resfhp);
-       if (err)
-               goto out;
-
        if (createmode == NFS3_CREATE_EXCLUSIVE) {
                /* Cram the verifier into atime/mtime/mode */
                iap->ia_valid = ATTR_MTIME|ATTR_ATIME
@@ -1333,8 +1329,17 @@ nfsd_create_v3(struct svc_rqst *rqstp, struct svc_fh *fhp,
         * implement setgid directories via NFS. Clear out all that cruft.
         */
  set_attr:
-       if ((iap->ia_valid &= ~(ATTR_UID|ATTR_GID)) != 0)
-               err = nfsd_setattr(rqstp, resfhp, iap, 0, (time_t)0);
+       if ((iap->ia_valid &= ~(ATTR_UID|ATTR_GID)) != 0) {
+               int err2 = nfsd_setattr(rqstp, resfhp, iap, 0, (time_t)0);
+               if (err2)
+                       err = err2;
+       }
+
+       /*
+        * Update the filehandle to get the new inode info.
+        */
+       if (!err)
+               err = fh_update(resfhp);
 
  out:
        fh_unlock(fhp);
@@ -1437,16 +1442,18 @@ nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp,
                else {
                        strncpy(path_alloced, path, plen);
                        path_alloced[plen] = 0;
-                       err = vfs_symlink(dentry->d_inode, dnew, path_alloced, mode);
+                       err = vfs_symlink(dentry->d_inode, dnew,
+                               path_alloced, mode, NULL);
                        kfree(path_alloced);
                }
        } else
-               err = vfs_symlink(dentry->d_inode, dnew, path, mode);
+               err = vfs_symlink(dentry->d_inode, dnew,
+                       path, mode, NULL);
 
-       if (!err) {
+       if (!err)
                if (EX_ISSYNC(fhp->fh_export))
-                       nfsd_sync_dir(dentry);
-       } else
+                       err = nfsd_sync_dir(dentry);
+       if (err)
                err = nfserrno(err);
        fh_unlock(fhp);
 
@@ -1499,10 +1506,10 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
        dold = tfhp->fh_dentry;
        dest = dold->d_inode;
 
-       err = vfs_link(dold, dirp, dnew);
+       err = vfs_link(dold, dirp, dnew, NULL);
        if (!err) {
                if (EX_ISSYNC(ffhp->fh_export)) {
-                       nfsd_sync_dir(ddir);
+                       err = nfserrno(nfsd_sync_dir(ddir));
                        write_inode_now(dest, 1);
                }
        } else {
@@ -1586,13 +1593,14 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
        if ((ffhp->fh_export->ex_flags & NFSEXP_MSNFS) &&
                ((atomic_read(&odentry->d_count) > 1)
                 || (atomic_read(&ndentry->d_count) > 1))) {
-                       err = nfserr_perm;
+                       err = -EPERM;
        } else
 #endif
        err = vfs_rename(fdir, odentry, tdir, ndentry);
        if (!err && EX_ISSYNC(tfhp->fh_export)) {
-               nfsd_sync_dir(tdentry);
-               nfsd_sync_dir(fdentry);
+               err = nfsd_sync_dir(tdentry);
+               if (!err)
+                       err = nfsd_sync_dir(fdentry);
        }
 
  out_dput_new:
@@ -1657,27 +1665,24 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
 #ifdef MSNFS
                if ((fhp->fh_export->ex_flags & NFSEXP_MSNFS) &&
                        (atomic_read(&rdentry->d_count) > 1)) {
-                       err = nfserr_perm;
+                       err = -EPERM;
                } else
 #endif
-               err = vfs_unlink(dirp, rdentry);
+               err = vfs_unlink(dirp, rdentry, NULL);
        } else { /* It's RMDIR */
-               err = vfs_rmdir(dirp, rdentry);
+               err = vfs_rmdir(dirp, rdentry, NULL);
        }
 
        dput(rdentry);
 
-       if (err)
-               goto out_nfserr;
-       if (EX_ISSYNC(fhp->fh_export)) 
-               nfsd_sync_dir(dentry);
-
-out:
-       return err;
+       if (err == 0 &&
+           EX_ISSYNC(fhp->fh_export))
+                       err = nfsd_sync_dir(dentry);
 
 out_nfserr:
        err = nfserrno(err);
-       goto out;
+out:
+       return err;
 }
 
 /*
@@ -1774,7 +1779,8 @@ nfsd_permission(struct svc_export *exp, struct dentry *dentry, int acc)
         */
        if (!(acc & MAY_LOCAL_ACCESS))
                if (acc & (MAY_WRITE | MAY_SATTR | MAY_TRUNC)) {
-                       if (EX_RDONLY(exp) || IS_RDONLY(inode))
+                       if (EX_RDONLY(exp) || IS_RDONLY(inode)
+                               || MNT_IS_RDONLY(exp->ex_mnt))
                                return nfserr_rofs;
                        if (/* (acc & MAY_WRITE) && */ IS_IMMUTABLE(inode))
                                return nfserr_perm;
@@ -1857,3 +1863,89 @@ nfsd_racache_init(int cache_size)
        nfsdstats.ra_size = cache_size;
        return 0;
 }
+
+#if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL)
+struct posix_acl *
+nfsd_get_posix_acl(struct svc_fh *fhp, int type)
+{
+       struct inode *inode = fhp->fh_dentry->d_inode;
+       char *name;
+       void *value = NULL;
+       ssize_t size;
+       struct posix_acl *acl;
+
+       if (!IS_POSIXACL(inode))
+               return ERR_PTR(-EOPNOTSUPP);
+
+       switch (type) {
+       case ACL_TYPE_ACCESS:
+               name = POSIX_ACL_XATTR_ACCESS;
+               break;
+       case ACL_TYPE_DEFAULT:
+               name = POSIX_ACL_XATTR_DEFAULT;
+               break;
+       default:
+               return ERR_PTR(-EOPNOTSUPP);
+       }
+
+       size = nfsd_getxattr(fhp->fh_dentry, name, &value);
+       if (size < 0)
+               return ERR_PTR(size);
+
+       acl = posix_acl_from_xattr(value, size);
+       kfree(value);
+       return acl;
+}
+
+int
+nfsd_set_posix_acl(struct svc_fh *fhp, int type, struct posix_acl *acl)
+{
+       struct inode *inode = fhp->fh_dentry->d_inode;
+       char *name;
+       void *value = NULL;
+       size_t size;
+       int error;
+
+       if (!IS_POSIXACL(inode) || !inode->i_op ||
+           !inode->i_op->setxattr || !inode->i_op->removexattr)
+               return -EOPNOTSUPP;
+       switch(type) {
+               case ACL_TYPE_ACCESS:
+                       name = POSIX_ACL_XATTR_ACCESS;
+                       break;
+               case ACL_TYPE_DEFAULT:
+                       name = POSIX_ACL_XATTR_DEFAULT;
+                       break;
+               default:
+                       return -EOPNOTSUPP;
+       }
+
+       if (acl && acl->a_count) {
+               size = posix_acl_xattr_size(acl->a_count);
+               value = kmalloc(size, GFP_KERNEL);
+               if (!value)
+                       return -ENOMEM;
+               error = posix_acl_to_xattr(acl, value, size);
+               if (error < 0)
+                       goto getout;
+               size = error;
+       } else
+               size = 0;
+
+       if (size)
+               error = vfs_setxattr(fhp->fh_dentry, name, value, size, 0);
+       else {
+               if (!S_ISDIR(inode->i_mode) && type == ACL_TYPE_DEFAULT)
+                       error = 0;
+               else {
+                       error = vfs_removexattr(fhp->fh_dentry, name);
+                       if (error == -ENODATA)
+                               error = 0;
+               }
+       }
+
+getout:
+       kfree(value);
+       return error;
+}
+#endif  /* defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL) */