CIFS: Fix SMB2 readdir error handling
[platform/adaptation/renesas_rcar/renesas_kernel.git] / fs / cifs / file.c
index 7555846..d375322 100644 (file)
@@ -244,7 +244,7 @@ cifs_nt_open(char *full_path, struct inode *inode, struct cifs_sb_info *cifs_sb,
                                              xid);
        else
                rc = cifs_get_inode_info(&inode, full_path, buf, inode->i_sb,
-                                        xid, &fid->netfid);
+                                        xid, fid);
 
 out:
        kfree(buf);
@@ -762,7 +762,7 @@ int cifs_closedir(struct inode *inode, struct file *file)
 
        cifs_dbg(FYI, "Freeing private data in close dir\n");
        spin_lock(&cifs_file_list_lock);
-       if (!cfile->srch_inf.endOfSearch && !cfile->invalidHandle) {
+       if (server->ops->dir_needs_close(cfile)) {
                cfile->invalidHandle = true;
                spin_unlock(&cifs_file_list_lock);
                if (server->ops->close_dir)
@@ -2389,7 +2389,7 @@ cifs_iovec_write(struct file *file, const struct iovec *iov,
                 unsigned long nr_segs, loff_t *poffset)
 {
        unsigned long nr_pages, i;
-       size_t copied, len, cur_len;
+       size_t bytes, copied, len, cur_len;
        ssize_t total_written = 0;
        loff_t offset;
        struct iov_iter it;
@@ -2444,14 +2444,45 @@ cifs_iovec_write(struct file *file, const struct iovec *iov,
 
                save_len = cur_len;
                for (i = 0; i < nr_pages; i++) {
-                       copied = min_t(const size_t, cur_len, PAGE_SIZE);
+                       bytes = min_t(const size_t, cur_len, PAGE_SIZE);
                        copied = iov_iter_copy_from_user(wdata->pages[i], &it,
-                                                        0, copied);
+                                                        0, bytes);
                        cur_len -= copied;
                        iov_iter_advance(&it, copied);
+                       /*
+                        * If we didn't copy as much as we expected, then that
+                        * may mean we trod into an unmapped area. Stop copying
+                        * at that point. On the next pass through the big
+                        * loop, we'll likely end up getting a zero-length
+                        * write and bailing out of it.
+                        */
+                       if (copied < bytes)
+                               break;
                }
                cur_len = save_len - cur_len;
 
+               /*
+                * If we have no data to send, then that probably means that
+                * the copy above failed altogether. That's most likely because
+                * the address in the iovec was bogus. Set the rc to -EFAULT,
+                * free anything we allocated and bail out.
+                */
+               if (!cur_len) {
+                       for (i = 0; i < nr_pages; i++)
+                               put_page(wdata->pages[i]);
+                       kfree(wdata);
+                       rc = -EFAULT;
+                       break;
+               }
+
+               /*
+                * i + 1 now represents the number of pages we actually used in
+                * the copy phase above. Bring nr_pages down to that, and free
+                * any pages that we didn't use.
+                */
+               for ( ; nr_pages > i + 1; nr_pages--)
+                       put_page(wdata->pages[nr_pages - 1]);
+
                wdata->sync_mode = WB_SYNC_ALL;
                wdata->nr_pages = nr_pages;
                wdata->offset = (__u64)offset;
@@ -2548,31 +2579,19 @@ cifs_writev(struct kiocb *iocb, const struct iovec *iov,
        struct cifsInodeInfo *cinode = CIFS_I(inode);
        struct TCP_Server_Info *server = tlink_tcon(cfile->tlink)->ses->server;
        ssize_t rc = -EACCES;
+       loff_t lock_pos = pos;
 
-       BUG_ON(iocb->ki_pos != pos);
-
+       if (file->f_flags & O_APPEND)
+               lock_pos = i_size_read(inode);
        /*
         * We need to hold the sem to be sure nobody modifies lock list
         * with a brlock that prevents writing.
         */
        down_read(&cinode->lock_sem);
-       if (!cifs_find_lock_conflict(cfile, pos, iov_length(iov, nr_segs),
+       if (!cifs_find_lock_conflict(cfile, lock_pos, iov_length(iov, nr_segs),
                                     server->vals->exclusive_lock_type, NULL,
-                                    CIFS_WRITE_OP)) {
-               mutex_lock(&inode->i_mutex);
-               rc = __generic_file_aio_write(iocb, iov, nr_segs,
-                                              &iocb->ki_pos);
-               mutex_unlock(&inode->i_mutex);
-       }
-
-       if (rc > 0) {
-               ssize_t err;
-
-               err = generic_write_sync(file, iocb->ki_pos - rc, rc);
-               if (err < 0)
-                       rc = err;
-       }
-
+                                    CIFS_WRITE_OP))
+               rc = generic_file_aio_write(iocb, iov, nr_segs, pos);
        up_read(&cinode->lock_sem);
        return rc;
 }
@@ -2589,12 +2608,20 @@ cifs_strict_writev(struct kiocb *iocb, const struct iovec *iov,
        struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
        ssize_t written;
 
+       written = cifs_get_writer(cinode);
+       if (written)
+               return written;
+
        if (CIFS_CACHE_WRITE(cinode)) {
                if (cap_unix(tcon->ses) &&
                (CIFS_UNIX_FCNTL_CAP & le64_to_cpu(tcon->fsUnixInfo.Capability))
-                   && ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NOPOSIXBRL) == 0))
-                       return generic_file_aio_write(iocb, iov, nr_segs, pos);
-               return cifs_writev(iocb, iov, nr_segs, pos);
+                 && ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NOPOSIXBRL) == 0)) {
+                       written = generic_file_aio_write(
+                                       iocb, iov, nr_segs, pos);
+                       goto out;
+               }
+               written = cifs_writev(iocb, iov, nr_segs, pos);
+               goto out;
        }
        /*
         * For non-oplocked files in strict cache mode we need to write the data
@@ -2614,6 +2641,8 @@ cifs_strict_writev(struct kiocb *iocb, const struct iovec *iov,
                         inode);
                cinode->oplock = 0;
        }
+out:
+       cifs_put_writer(cinode);
        return written;
 }
 
@@ -2815,7 +2844,7 @@ cifs_uncached_read_into_pages(struct TCP_Server_Info *server,
                total_read += result;
        }
 
-       return total_read > 0 ? total_read : result;
+       return total_read > 0 && result != -EAGAIN ? total_read : result;
 }
 
 static ssize_t
@@ -3238,7 +3267,7 @@ cifs_readpages_read_into_pages(struct TCP_Server_Info *server,
                total_read += result;
        }
 
-       return total_read > 0 ? total_read : result;
+       return total_read > 0 && result != -EAGAIN ? total_read : result;
 }
 
 static int cifs_readpages(struct file *file, struct address_space *mapping,
@@ -3625,6 +3654,13 @@ static int cifs_launder_page(struct page *page)
        return rc;
 }
 
+static int
+cifs_pending_writers_wait(void *unused)
+{
+       schedule();
+       return 0;
+}
+
 void cifs_oplock_break(struct work_struct *work)
 {
        struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
@@ -3632,8 +3668,15 @@ void cifs_oplock_break(struct work_struct *work)
        struct inode *inode = cfile->dentry->d_inode;
        struct cifsInodeInfo *cinode = CIFS_I(inode);
        struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+       struct TCP_Server_Info *server = tcon->ses->server;
        int rc = 0;
 
+       wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS,
+                       cifs_pending_writers_wait, TASK_UNINTERRUPTIBLE);
+
+       server->ops->downgrade_oplock(server, cinode,
+               test_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, &cinode->flags));
+
        if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
                                                cifs_has_mand_locks(cinode)) {
                cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
@@ -3670,6 +3713,7 @@ void cifs_oplock_break(struct work_struct *work)
                                                             cinode);
                cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
        }
+       cifs_done_oplock_break(cinode);
 }
 
 /*