NFS: Don't ignore suid/sgid bit changes after a successful write
[pandora-kernel.git] / fs / nfs / write.c
index c1d5482..cd7c651 100644 (file)
@@ -909,9 +909,14 @@ bool nfs_ctx_key_to_expire(struct nfs_open_context *ctx)
  */
 static bool nfs_write_pageuptodate(struct page *page, struct inode *inode)
 {
+       struct nfs_inode *nfsi = NFS_I(inode);
+
        if (nfs_have_delegated_attributes(inode))
                goto out;
-       if (NFS_I(inode)->cache_validity & (NFS_INO_INVALID_DATA|NFS_INO_REVAL_PAGECACHE))
+       if (nfsi->cache_validity & (NFS_INO_INVALID_DATA|NFS_INO_REVAL_PAGECACHE))
+               return false;
+       smp_rmb();
+       if (test_bit(NFS_INO_INVALIDATING, &nfsi->flags))
                return false;
 out:
        return PageUptodate(page) != 0;
@@ -922,19 +927,20 @@ out:
  * extend the write to cover the entire page in order to avoid fragmentation
  * inefficiencies.
  *
- * If the file is opened for synchronous writes or if we have a write delegation
- * from the server then we can just skip the rest of the checks.
+ * If the file is opened for synchronous writes then we can just skip the rest
+ * of the checks.
  */
 static int nfs_can_extend_write(struct file *file, struct page *page, struct inode *inode)
 {
        if (file->f_flags & O_DSYNC)
                return 0;
+       if (!nfs_write_pageuptodate(page, inode))
+               return 0;
        if (NFS_PROTO(inode)->have_delegation(inode, FMODE_WRITE))
                return 1;
-       if (nfs_write_pageuptodate(page, inode) && (inode->i_flock == NULL ||
-                       (inode->i_flock->fl_start == 0 &&
+       if (inode->i_flock == NULL || (inode->i_flock->fl_start == 0 &&
                        inode->i_flock->fl_end == OFFSET_MAX &&
-                       inode->i_flock->fl_type != F_RDLCK)))
+                       inode->i_flock->fl_type != F_RDLCK))
                return 1;
        return 0;
 }
@@ -1013,10 +1019,10 @@ int nfs_initiate_write(struct rpc_clnt *clnt,
        NFS_PROTO(inode)->write_setup(data, &msg);
 
        dprintk("NFS: %5u initiated write call "
-               "(req %s/%lld, %u bytes @ offset %llu)\n",
+               "(req %s/%llu, %u bytes @ offset %llu)\n",
                data->task.tk_pid,
                inode->i_sb->s_id,
-               (long long)NFS_FILEID(inode),
+               (unsigned long long)NFS_FILEID(inode),
                data->args.count,
                (unsigned long long)data->args.offset);
 
@@ -1347,6 +1353,30 @@ static const struct rpc_call_ops nfs_write_common_ops = {
        .rpc_release = nfs_writeback_release_common,
 };
 
+/*
+ * Special version of should_remove_suid() that ignores capabilities.
+ */
+static int nfs_should_remove_suid(const struct inode *inode)
+{
+       umode_t mode = inode->i_mode;
+       int kill = 0;
+
+       /* suid always must be killed */
+       if (unlikely(mode & S_ISUID))
+               kill = ATTR_KILL_SUID;
+
+       /*
+        * sgid without any exec bits is just a mandatory locking mark; leave
+        * it alone.  If some exec bits are set, it's a real sgid; kill it.
+        */
+       if (unlikely((mode & S_ISGID) && (mode & S_IXGRP)))
+               kill |= ATTR_KILL_SGID;
+
+       if (unlikely(kill && S_ISREG(mode)))
+               return kill;
+
+       return 0;
+}
 
 /*
  * This function is called when the WRITE call is complete.
@@ -1395,9 +1425,16 @@ void nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data)
                }
        }
 #endif
-       if (task->tk_status < 0)
+       if (task->tk_status < 0) {
                nfs_set_pgio_error(data->header, task->tk_status, argp->offset);
-       else if (resp->count < argp->count) {
+               return;
+       }
+
+       /* Deal with the suid/sgid bit corner case */
+       if (nfs_should_remove_suid(inode))
+               nfs_mark_for_revalidate(inode);
+
+       if (resp->count < argp->count) {
                static unsigned long    complain;
 
                /* This a short write! */
@@ -1606,9 +1643,9 @@ static void nfs_commit_release_pages(struct nfs_commit_data *data)
                nfs_list_remove_request(req);
                nfs_clear_page_commit(req->wb_page);
 
-               dprintk("NFS:       commit (%s/%lld %d@%lld)",
+               dprintk("NFS:       commit (%s/%llu %d@%lld)",
                        req->wb_context->dentry->d_sb->s_id,
-                       (long long)NFS_FILEID(req->wb_context->dentry->d_inode),
+                       (unsigned long long)NFS_FILEID(req->wb_context->dentry->d_inode),
                        req->wb_bytes,
                        (long long)req_offset(req));
                if (status < 0) {