mirror of
				https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
				synced 2025-10-31 09:36:25 +00:00 
			
		
		
		
	 4cb50dc2ea
			
		
	
	
		4cb50dc2ea
		
	
	
	
	
		
			
			Remove inclusions of linux/mpage.h that are no longer necessary due to the transfer of generic_writepages(). Signed-Off-By: David Howells <dhowells@redhat.com> Signed-off-by: Jens Axboe <axboe@kernel.dk>
		
			
				
	
	
		
			2035 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2035 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  *   fs/cifs/file.c
 | |
|  *
 | |
|  *   vfs operations that deal with files
 | |
|  * 
 | |
|  *   Copyright (C) International Business Machines  Corp., 2002,2003
 | |
|  *   Author(s): Steve French (sfrench@us.ibm.com)
 | |
|  *              Jeremy Allison (jra@samba.org)
 | |
|  *
 | |
|  *   This library is free software; you can redistribute it and/or modify
 | |
|  *   it under the terms of the GNU Lesser General Public License as published
 | |
|  *   by the Free Software Foundation; either version 2.1 of the License, or
 | |
|  *   (at your option) any later version.
 | |
|  *
 | |
|  *   This library is distributed in the hope that it will be useful,
 | |
|  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
 | |
|  *   the GNU Lesser General Public License for more details.
 | |
|  *
 | |
|  *   You should have received a copy of the GNU Lesser General Public License
 | |
|  *   along with this library; if not, write to the Free Software
 | |
|  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 | |
|  */
 | |
| #include <linux/fs.h>
 | |
| #include <linux/backing-dev.h>
 | |
| #include <linux/stat.h>
 | |
| #include <linux/fcntl.h>
 | |
| #include <linux/pagemap.h>
 | |
| #include <linux/pagevec.h>
 | |
| #include <linux/smp_lock.h>
 | |
| #include <linux/writeback.h>
 | |
| #include <linux/delay.h>
 | |
| #include <asm/div64.h>
 | |
| #include "cifsfs.h"
 | |
| #include "cifspdu.h"
 | |
| #include "cifsglob.h"
 | |
| #include "cifsproto.h"
 | |
| #include "cifs_unicode.h"
 | |
| #include "cifs_debug.h"
 | |
| #include "cifs_fs_sb.h"
 | |
| 
 | |
| static inline struct cifsFileInfo *cifs_init_private(
 | |
| 	struct cifsFileInfo *private_data, struct inode *inode,
 | |
| 	struct file *file, __u16 netfid)
 | |
| {
 | |
| 	memset(private_data, 0, sizeof(struct cifsFileInfo));
 | |
| 	private_data->netfid = netfid;
 | |
| 	private_data->pid = current->tgid;	
 | |
| 	init_MUTEX(&private_data->fh_sem);
 | |
| 	init_MUTEX(&private_data->lock_sem);
 | |
| 	INIT_LIST_HEAD(&private_data->llist);
 | |
| 	private_data->pfile = file; /* needed for writepage */
 | |
| 	private_data->pInode = inode;
 | |
| 	private_data->invalidHandle = FALSE;
 | |
| 	private_data->closePend = FALSE;
 | |
| 	/* we have to track num writers to the inode, since writepages
 | |
| 	does not tell us which handle the write is for so there can
 | |
| 	be a close (overlapping with write) of the filehandle that
 | |
| 	cifs_writepages chose to use */
 | |
| 	atomic_set(&private_data->wrtPending,0); 
 | |
| 
 | |
| 	return private_data;
 | |
| }
 | |
| 
 | |
| static inline int cifs_convert_flags(unsigned int flags)
 | |
| {
 | |
| 	if ((flags & O_ACCMODE) == O_RDONLY)
 | |
| 		return GENERIC_READ;
 | |
| 	else if ((flags & O_ACCMODE) == O_WRONLY)
 | |
| 		return GENERIC_WRITE;
 | |
| 	else if ((flags & O_ACCMODE) == O_RDWR) {
 | |
| 		/* GENERIC_ALL is too much permission to request
 | |
| 		   can cause unnecessary access denied on create */
 | |
| 		/* return GENERIC_ALL; */
 | |
| 		return (GENERIC_READ | GENERIC_WRITE);
 | |
| 	}
 | |
| 
 | |
| 	return 0x20197;
 | |
| }
 | |
| 
 | |
| static inline int cifs_get_disposition(unsigned int flags)
 | |
| {
 | |
| 	if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
 | |
| 		return FILE_CREATE;
 | |
| 	else if ((flags & (O_CREAT | O_TRUNC)) == (O_CREAT | O_TRUNC))
 | |
| 		return FILE_OVERWRITE_IF;
 | |
| 	else if ((flags & O_CREAT) == O_CREAT)
 | |
| 		return FILE_OPEN_IF;
 | |
| 	else if ((flags & O_TRUNC) == O_TRUNC)
 | |
| 		return FILE_OVERWRITE;
 | |
| 	else
 | |
| 		return FILE_OPEN;
 | |
| }
 | |
| 
 | |
| /* all arguments to this function must be checked for validity in caller */
 | |
| static inline int cifs_open_inode_helper(struct inode *inode, struct file *file,
 | |
| 	struct cifsInodeInfo *pCifsInode, struct cifsFileInfo *pCifsFile,
 | |
| 	struct cifsTconInfo *pTcon, int *oplock, FILE_ALL_INFO *buf,
 | |
| 	char *full_path, int xid)
 | |
| {
 | |
| 	struct timespec temp;
 | |
| 	int rc;
 | |
| 
 | |
| 	/* want handles we can use to read with first
 | |
| 	   in the list so we do not have to walk the
 | |
| 	   list to search for one in prepare_write */
 | |
| 	if ((file->f_flags & O_ACCMODE) == O_WRONLY) {
 | |
| 		list_add_tail(&pCifsFile->flist, 
 | |
| 			      &pCifsInode->openFileList);
 | |
| 	} else {
 | |
| 		list_add(&pCifsFile->flist,
 | |
| 			 &pCifsInode->openFileList);
 | |
| 	}
 | |
| 	write_unlock(&GlobalSMBSeslock);
 | |
| 	if (pCifsInode->clientCanCacheRead) {
 | |
| 		/* we have the inode open somewhere else
 | |
| 		   no need to discard cache data */
 | |
| 		goto client_can_cache;
 | |
| 	}
 | |
| 
 | |
| 	/* BB need same check in cifs_create too? */
 | |
| 	/* if not oplocked, invalidate inode pages if mtime or file
 | |
| 	   size changed */
 | |
| 	temp = cifs_NTtimeToUnix(le64_to_cpu(buf->LastWriteTime));
 | |
| 	if (timespec_equal(&file->f_dentry->d_inode->i_mtime, &temp) && 
 | |
| 			   (file->f_dentry->d_inode->i_size == 
 | |
| 			    (loff_t)le64_to_cpu(buf->EndOfFile))) {
 | |
| 		cFYI(1, ("inode unchanged on server"));
 | |
| 	} else {
 | |
| 		if (file->f_dentry->d_inode->i_mapping) {
 | |
| 		/* BB no need to lock inode until after invalidate
 | |
| 		   since namei code should already have it locked? */
 | |
| 			filemap_write_and_wait(file->f_dentry->d_inode->i_mapping);
 | |
| 		}
 | |
| 		cFYI(1, ("invalidating remote inode since open detected it "
 | |
| 			 "changed"));
 | |
| 		invalidate_remote_inode(file->f_dentry->d_inode);
 | |
| 	}
 | |
| 
 | |
| client_can_cache:
 | |
| 	if (pTcon->ses->capabilities & CAP_UNIX)
 | |
| 		rc = cifs_get_inode_info_unix(&file->f_dentry->d_inode,
 | |
| 			full_path, inode->i_sb, xid);
 | |
| 	else
 | |
| 		rc = cifs_get_inode_info(&file->f_dentry->d_inode,
 | |
| 			full_path, buf, inode->i_sb, xid);
 | |
| 
 | |
| 	if ((*oplock & 0xF) == OPLOCK_EXCLUSIVE) {
 | |
| 		pCifsInode->clientCanCacheAll = TRUE;
 | |
| 		pCifsInode->clientCanCacheRead = TRUE;
 | |
| 		cFYI(1, ("Exclusive Oplock granted on inode %p",
 | |
| 			 file->f_dentry->d_inode));
 | |
| 	} else if ((*oplock & 0xF) == OPLOCK_READ)
 | |
| 		pCifsInode->clientCanCacheRead = TRUE;
 | |
| 
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| int cifs_open(struct inode *inode, struct file *file)
 | |
| {
 | |
| 	int rc = -EACCES;
 | |
| 	int xid, oplock;
 | |
| 	struct cifs_sb_info *cifs_sb;
 | |
| 	struct cifsTconInfo *pTcon;
 | |
| 	struct cifsFileInfo *pCifsFile;
 | |
| 	struct cifsInodeInfo *pCifsInode;
 | |
| 	struct list_head *tmp;
 | |
| 	char *full_path = NULL;
 | |
| 	int desiredAccess;
 | |
| 	int disposition;
 | |
| 	__u16 netfid;
 | |
| 	FILE_ALL_INFO *buf = NULL;
 | |
| 
 | |
| 	xid = GetXid();
 | |
| 
 | |
| 	cifs_sb = CIFS_SB(inode->i_sb);
 | |
| 	pTcon = cifs_sb->tcon;
 | |
| 
 | |
| 	if (file->f_flags & O_CREAT) {
 | |
| 		/* search inode for this file and fill in file->private_data */
 | |
| 		pCifsInode = CIFS_I(file->f_dentry->d_inode);
 | |
| 		read_lock(&GlobalSMBSeslock);
 | |
| 		list_for_each(tmp, &pCifsInode->openFileList) {
 | |
| 			pCifsFile = list_entry(tmp, struct cifsFileInfo,
 | |
| 					       flist);
 | |
| 			if ((pCifsFile->pfile == NULL) &&
 | |
| 			    (pCifsFile->pid == current->tgid)) {
 | |
| 				/* mode set in cifs_create */
 | |
| 
 | |
| 				/* needed for writepage */
 | |
| 				pCifsFile->pfile = file;
 | |
| 				
 | |
| 				file->private_data = pCifsFile;
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		read_unlock(&GlobalSMBSeslock);
 | |
| 		if (file->private_data != NULL) {
 | |
| 			rc = 0;
 | |
| 			FreeXid(xid);
 | |
| 			return rc;
 | |
| 		} else {
 | |
| 			if (file->f_flags & O_EXCL)
 | |
| 				cERROR(1, ("could not find file instance for "
 | |
| 					   "new file %p", file));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	full_path = build_path_from_dentry(file->f_dentry);
 | |
| 	if (full_path == NULL) {
 | |
| 		FreeXid(xid);
 | |
| 		return -ENOMEM;
 | |
| 	}
 | |
| 
 | |
| 	cFYI(1, (" inode = 0x%p file flags are 0x%x for %s",
 | |
| 		 inode, file->f_flags, full_path));
 | |
| 	desiredAccess = cifs_convert_flags(file->f_flags);
 | |
| 
 | |
| /*********************************************************************
 | |
|  *  open flag mapping table:
 | |
|  *  
 | |
|  *	POSIX Flag            CIFS Disposition
 | |
|  *	----------            ---------------- 
 | |
|  *	O_CREAT               FILE_OPEN_IF
 | |
|  *	O_CREAT | O_EXCL      FILE_CREATE
 | |
|  *	O_CREAT | O_TRUNC     FILE_OVERWRITE_IF
 | |
|  *	O_TRUNC               FILE_OVERWRITE
 | |
|  *	none of the above     FILE_OPEN
 | |
|  *
 | |
|  *	Note that there is not a direct match between disposition
 | |
|  *	FILE_SUPERSEDE (ie create whether or not file exists although 
 | |
|  *	O_CREAT | O_TRUNC is similar but truncates the existing
 | |
|  *	file rather than creating a new file as FILE_SUPERSEDE does
 | |
|  *	(which uses the attributes / metadata passed in on open call)
 | |
|  *?
 | |
|  *?  O_SYNC is a reasonable match to CIFS writethrough flag  
 | |
|  *?  and the read write flags match reasonably.  O_LARGEFILE
 | |
|  *?  is irrelevant because largefile support is always used
 | |
|  *?  by this client. Flags O_APPEND, O_DIRECT, O_DIRECTORY,
 | |
|  *	 O_FASYNC, O_NOFOLLOW, O_NONBLOCK need further investigation
 | |
|  *********************************************************************/
 | |
| 
 | |
| 	disposition = cifs_get_disposition(file->f_flags);
 | |
| 
 | |
| 	if (oplockEnabled)
 | |
| 		oplock = REQ_OPLOCK;
 | |
| 	else
 | |
| 		oplock = FALSE;
 | |
| 
 | |
| 	/* BB pass O_SYNC flag through on file attributes .. BB */
 | |
| 
 | |
| 	/* Also refresh inode by passing in file_info buf returned by SMBOpen
 | |
| 	   and calling get_inode_info with returned buf (at least helps
 | |
| 	   non-Unix server case) */
 | |
| 
 | |
| 	/* BB we can not do this if this is the second open of a file 
 | |
| 	   and the first handle has writebehind data, we might be 
 | |
| 	   able to simply do a filemap_fdatawrite/filemap_fdatawait first */
 | |
| 	buf = kmalloc(sizeof(FILE_ALL_INFO), GFP_KERNEL);
 | |
| 	if (!buf) {
 | |
| 		rc = -ENOMEM;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	if (cifs_sb->tcon->ses->capabilities & CAP_NT_SMBS)
 | |
| 		rc = CIFSSMBOpen(xid, pTcon, full_path, disposition, 
 | |
| 			 desiredAccess, CREATE_NOT_DIR, &netfid, &oplock, buf,
 | |
| 			 cifs_sb->local_nls, cifs_sb->mnt_cifs_flags
 | |
| 				 & CIFS_MOUNT_MAP_SPECIAL_CHR);
 | |
| 	else
 | |
| 		rc = -EIO; /* no NT SMB support fall into legacy open below */
 | |
| 
 | |
| 	if (rc == -EIO) {
 | |
| 		/* Old server, try legacy style OpenX */
 | |
| 		rc = SMBLegacyOpen(xid, pTcon, full_path, disposition,
 | |
| 			desiredAccess, CREATE_NOT_DIR, &netfid, &oplock, buf,
 | |
| 			cifs_sb->local_nls, cifs_sb->mnt_cifs_flags
 | |
| 				& CIFS_MOUNT_MAP_SPECIAL_CHR);
 | |
| 	}
 | |
| 	if (rc) {
 | |
| 		cFYI(1, ("cifs_open returned 0x%x", rc));
 | |
| 		goto out;
 | |
| 	}
 | |
| 	file->private_data =
 | |
| 		kmalloc(sizeof(struct cifsFileInfo), GFP_KERNEL);
 | |
| 	if (file->private_data == NULL) {
 | |
| 		rc = -ENOMEM;
 | |
| 		goto out;
 | |
| 	}
 | |
| 	pCifsFile = cifs_init_private(file->private_data, inode, file, netfid);
 | |
| 	write_lock(&GlobalSMBSeslock);
 | |
| 	list_add(&pCifsFile->tlist, &pTcon->openFileList);
 | |
| 
 | |
| 	pCifsInode = CIFS_I(file->f_dentry->d_inode);
 | |
| 	if (pCifsInode) {
 | |
| 		rc = cifs_open_inode_helper(inode, file, pCifsInode,
 | |
| 					    pCifsFile, pTcon,
 | |
| 					    &oplock, buf, full_path, xid);
 | |
| 	} else {
 | |
| 		write_unlock(&GlobalSMBSeslock);
 | |
| 	}
 | |
| 
 | |
| 	if (oplock & CIFS_CREATE_ACTION) {           
 | |
| 		/* time to set mode which we can not set earlier due to
 | |
| 		   problems creating new read-only files */
 | |
| 		if (cifs_sb->tcon->ses->capabilities & CAP_UNIX) {
 | |
| 			CIFSSMBUnixSetPerms(xid, pTcon, full_path,
 | |
| 					    inode->i_mode,
 | |
| 					    (__u64)-1, (__u64)-1, 0 /* dev */,
 | |
| 					    cifs_sb->local_nls,
 | |
| 					    cifs_sb->mnt_cifs_flags & 
 | |
| 						CIFS_MOUNT_MAP_SPECIAL_CHR);
 | |
| 		} else {
 | |
| 			/* BB implement via Windows security descriptors eg
 | |
| 			   CIFSSMBWinSetPerms(xid, pTcon, full_path, mode,
 | |
| 					      -1, -1, local_nls);
 | |
| 			   in the meantime could set r/o dos attribute when
 | |
| 			   perms are eg: mode & 0222 == 0 */
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| out:
 | |
| 	kfree(buf);
 | |
| 	kfree(full_path);
 | |
| 	FreeXid(xid);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| /* Try to reacquire byte range locks that were released when session */
 | |
| /* to server was lost */
 | |
| static int cifs_relock_file(struct cifsFileInfo *cifsFile)
 | |
| {
 | |
| 	int rc = 0;
 | |
| 
 | |
| /* BB list all locks open on this file and relock */
 | |
| 
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| static int cifs_reopen_file(struct inode *inode, struct file *file, 
 | |
| 	int can_flush)
 | |
| {
 | |
| 	int rc = -EACCES;
 | |
| 	int xid, oplock;
 | |
| 	struct cifs_sb_info *cifs_sb;
 | |
| 	struct cifsTconInfo *pTcon;
 | |
| 	struct cifsFileInfo *pCifsFile;
 | |
| 	struct cifsInodeInfo *pCifsInode;
 | |
| 	char *full_path = NULL;
 | |
| 	int desiredAccess;
 | |
| 	int disposition = FILE_OPEN;
 | |
| 	__u16 netfid;
 | |
| 
 | |
| 	if (inode == NULL)
 | |
| 		return -EBADF;
 | |
| 	if (file->private_data) {
 | |
| 		pCifsFile = (struct cifsFileInfo *)file->private_data;
 | |
| 	} else
 | |
| 		return -EBADF;
 | |
| 
 | |
| 	xid = GetXid();
 | |
| 	down(&pCifsFile->fh_sem);
 | |
| 	if (pCifsFile->invalidHandle == FALSE) {
 | |
| 		up(&pCifsFile->fh_sem);
 | |
| 		FreeXid(xid);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	if (file->f_dentry == NULL) {
 | |
| 		up(&pCifsFile->fh_sem);
 | |
| 		cFYI(1, ("failed file reopen, no valid name if dentry freed"));
 | |
| 		FreeXid(xid);
 | |
| 		return -EBADF;
 | |
| 	}
 | |
| 	cifs_sb = CIFS_SB(inode->i_sb);
 | |
| 	pTcon = cifs_sb->tcon;
 | |
| /* can not grab rename sem here because various ops, including
 | |
|    those that already have the rename sem can end up causing writepage
 | |
|    to get called and if the server was down that means we end up here,
 | |
|    and we can never tell if the caller already has the rename_sem */
 | |
| 	full_path = build_path_from_dentry(file->f_dentry);
 | |
| 	if (full_path == NULL) {
 | |
| 		up(&pCifsFile->fh_sem);
 | |
| 		FreeXid(xid);
 | |
| 		return -ENOMEM;
 | |
| 	}
 | |
| 
 | |
| 	cFYI(1, (" inode = 0x%p file flags are 0x%x for %s",
 | |
| 		 inode, file->f_flags,full_path));
 | |
| 	desiredAccess = cifs_convert_flags(file->f_flags);
 | |
| 
 | |
| 	if (oplockEnabled)
 | |
| 		oplock = REQ_OPLOCK;
 | |
| 	else
 | |
| 		oplock = FALSE;
 | |
| 
 | |
| 	/* Can not refresh inode by passing in file_info buf to be returned
 | |
| 	   by SMBOpen and then calling get_inode_info with returned buf 
 | |
| 	   since file might have write behind data that needs to be flushed 
 | |
| 	   and server version of file size can be stale. If we knew for sure
 | |
| 	   that inode was not dirty locally we could do this */
 | |
| 
 | |
| /*	buf = kmalloc(sizeof(FILE_ALL_INFO), GFP_KERNEL);
 | |
| 	if (buf == 0) {
 | |
| 		up(&pCifsFile->fh_sem);
 | |
| 		kfree(full_path);
 | |
| 		FreeXid(xid);
 | |
| 		return -ENOMEM;
 | |
| 	} */
 | |
| 	rc = CIFSSMBOpen(xid, pTcon, full_path, disposition, desiredAccess,
 | |
| 			 CREATE_NOT_DIR, &netfid, &oplock, NULL,
 | |
| 			 cifs_sb->local_nls, cifs_sb->mnt_cifs_flags & 
 | |
| 				CIFS_MOUNT_MAP_SPECIAL_CHR);
 | |
| 	if (rc) {
 | |
| 		up(&pCifsFile->fh_sem);
 | |
| 		cFYI(1, ("cifs_open returned 0x%x", rc));
 | |
| 		cFYI(1, ("oplock: %d", oplock));
 | |
| 	} else {
 | |
| 		pCifsFile->netfid = netfid;
 | |
| 		pCifsFile->invalidHandle = FALSE;
 | |
| 		up(&pCifsFile->fh_sem);
 | |
| 		pCifsInode = CIFS_I(inode);
 | |
| 		if (pCifsInode) {
 | |
| 			if (can_flush) {
 | |
| 				filemap_write_and_wait(inode->i_mapping);
 | |
| 			/* temporarily disable caching while we
 | |
| 			   go to server to get inode info */
 | |
| 				pCifsInode->clientCanCacheAll = FALSE;
 | |
| 				pCifsInode->clientCanCacheRead = FALSE;
 | |
| 				if (pTcon->ses->capabilities & CAP_UNIX)
 | |
| 					rc = cifs_get_inode_info_unix(&inode,
 | |
| 						full_path, inode->i_sb, xid);
 | |
| 				else
 | |
| 					rc = cifs_get_inode_info(&inode,
 | |
| 						full_path, NULL, inode->i_sb,
 | |
| 						xid);
 | |
| 			} /* else we are writing out data to server already
 | |
| 			     and could deadlock if we tried to flush data, and
 | |
| 			     since we do not know if we have data that would
 | |
| 			     invalidate the current end of file on the server
 | |
| 			     we can not go to the server to get the new inod
 | |
| 			     info */
 | |
| 			if ((oplock & 0xF) == OPLOCK_EXCLUSIVE) {
 | |
| 				pCifsInode->clientCanCacheAll = TRUE;
 | |
| 				pCifsInode->clientCanCacheRead = TRUE;
 | |
| 				cFYI(1, ("Exclusive Oplock granted on inode %p",
 | |
| 					 file->f_dentry->d_inode));
 | |
| 			} else if ((oplock & 0xF) == OPLOCK_READ) {
 | |
| 				pCifsInode->clientCanCacheRead = TRUE;
 | |
| 				pCifsInode->clientCanCacheAll = FALSE;
 | |
| 			} else {
 | |
| 				pCifsInode->clientCanCacheRead = FALSE;
 | |
| 				pCifsInode->clientCanCacheAll = FALSE;
 | |
| 			}
 | |
| 			cifs_relock_file(pCifsFile);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	kfree(full_path);
 | |
| 	FreeXid(xid);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| int cifs_close(struct inode *inode, struct file *file)
 | |
| {
 | |
| 	int rc = 0;
 | |
| 	int xid;
 | |
| 	struct cifs_sb_info *cifs_sb;
 | |
| 	struct cifsTconInfo *pTcon;
 | |
| 	struct cifsFileInfo *pSMBFile =
 | |
| 		(struct cifsFileInfo *)file->private_data;
 | |
| 
 | |
| 	xid = GetXid();
 | |
| 
 | |
| 	cifs_sb = CIFS_SB(inode->i_sb);
 | |
| 	pTcon = cifs_sb->tcon;
 | |
| 	if (pSMBFile) {
 | |
| 		struct cifsLockInfo *li, *tmp;
 | |
| 
 | |
| 		pSMBFile->closePend = TRUE;
 | |
| 		if (pTcon) {
 | |
| 			/* no sense reconnecting to close a file that is
 | |
| 			   already closed */
 | |
| 			if (pTcon->tidStatus != CifsNeedReconnect) {
 | |
| 				int timeout = 2;
 | |
| 				while((atomic_read(&pSMBFile->wrtPending) != 0)
 | |
| 					 && (timeout < 1000) ) {
 | |
| 					/* Give write a better chance to get to
 | |
| 					server ahead of the close.  We do not
 | |
| 					want to add a wait_q here as it would
 | |
| 					increase the memory utilization as
 | |
| 					the struct would be in each open file,
 | |
| 					but this should give enough time to 
 | |
| 					clear the socket */
 | |
| 					cERROR(1,("close with pending writes"));
 | |
| 					msleep(timeout);
 | |
| 					timeout *= 4;
 | |
| 				} 
 | |
| 				rc = CIFSSMBClose(xid, pTcon,
 | |
| 						  pSMBFile->netfid);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		/* Delete any outstanding lock records.
 | |
| 		   We'll lose them when the file is closed anyway. */
 | |
| 		down(&pSMBFile->lock_sem);
 | |
| 		list_for_each_entry_safe(li, tmp, &pSMBFile->llist, llist) {
 | |
| 			list_del(&li->llist);
 | |
| 			kfree(li);
 | |
| 		}
 | |
| 		up(&pSMBFile->lock_sem);
 | |
| 
 | |
| 		write_lock(&GlobalSMBSeslock);
 | |
| 		list_del(&pSMBFile->flist);
 | |
| 		list_del(&pSMBFile->tlist);
 | |
| 		write_unlock(&GlobalSMBSeslock);
 | |
| 		kfree(pSMBFile->search_resume_name);
 | |
| 		kfree(file->private_data);
 | |
| 		file->private_data = NULL;
 | |
| 	} else
 | |
| 		rc = -EBADF;
 | |
| 
 | |
| 	if (list_empty(&(CIFS_I(inode)->openFileList))) {
 | |
| 		cFYI(1, ("closing last open instance for inode %p", inode));
 | |
| 		/* if the file is not open we do not know if we can cache info
 | |
| 		   on this inode, much less write behind and read ahead */
 | |
| 		CIFS_I(inode)->clientCanCacheRead = FALSE;
 | |
| 		CIFS_I(inode)->clientCanCacheAll  = FALSE;
 | |
| 	}
 | |
| 	if ((rc ==0) && CIFS_I(inode)->write_behind_rc)
 | |
| 		rc = CIFS_I(inode)->write_behind_rc;
 | |
| 	FreeXid(xid);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| int cifs_closedir(struct inode *inode, struct file *file)
 | |
| {
 | |
| 	int rc = 0;
 | |
| 	int xid;
 | |
| 	struct cifsFileInfo *pCFileStruct =
 | |
| 	    (struct cifsFileInfo *)file->private_data;
 | |
| 	char *ptmp;
 | |
| 
 | |
| 	cFYI(1, ("Closedir inode = 0x%p", inode));
 | |
| 
 | |
| 	xid = GetXid();
 | |
| 
 | |
| 	if (pCFileStruct) {
 | |
| 		struct cifsTconInfo *pTcon;
 | |
| 		struct cifs_sb_info *cifs_sb = CIFS_SB(file->f_dentry->d_sb);
 | |
| 
 | |
| 		pTcon = cifs_sb->tcon;
 | |
| 
 | |
| 		cFYI(1, ("Freeing private data in close dir"));
 | |
| 		if ((pCFileStruct->srch_inf.endOfSearch == FALSE) &&
 | |
| 		   (pCFileStruct->invalidHandle == FALSE)) {
 | |
| 			pCFileStruct->invalidHandle = TRUE;
 | |
| 			rc = CIFSFindClose(xid, pTcon, pCFileStruct->netfid);
 | |
| 			cFYI(1, ("Closing uncompleted readdir with rc %d",
 | |
| 				 rc));
 | |
| 			/* not much we can do if it fails anyway, ignore rc */
 | |
| 			rc = 0;
 | |
| 		}
 | |
| 		ptmp = pCFileStruct->srch_inf.ntwrk_buf_start;
 | |
| 		if (ptmp) {
 | |
| 			cFYI(1, ("closedir free smb buf in srch struct"));
 | |
| 			pCFileStruct->srch_inf.ntwrk_buf_start = NULL;
 | |
| 			if(pCFileStruct->srch_inf.smallBuf)
 | |
| 				cifs_small_buf_release(ptmp);
 | |
| 			else
 | |
| 				cifs_buf_release(ptmp);
 | |
| 		}
 | |
| 		ptmp = pCFileStruct->search_resume_name;
 | |
| 		if (ptmp) {
 | |
| 			cFYI(1, ("closedir free resume name"));
 | |
| 			pCFileStruct->search_resume_name = NULL;
 | |
| 			kfree(ptmp);
 | |
| 		}
 | |
| 		kfree(file->private_data);
 | |
| 		file->private_data = NULL;
 | |
| 	}
 | |
| 	/* BB can we lock the filestruct while this is going on? */
 | |
| 	FreeXid(xid);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| static int store_file_lock(struct cifsFileInfo *fid, __u64 len,
 | |
| 				__u64 offset, __u8 lockType)
 | |
| {
 | |
| 	struct cifsLockInfo *li = kmalloc(sizeof(struct cifsLockInfo), GFP_KERNEL);
 | |
| 	if (li == NULL)
 | |
| 		return -ENOMEM;
 | |
| 	li->offset = offset;
 | |
| 	li->length = len;
 | |
| 	li->type = lockType;
 | |
| 	down(&fid->lock_sem);
 | |
| 	list_add(&li->llist, &fid->llist);
 | |
| 	up(&fid->lock_sem);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int cifs_lock(struct file *file, int cmd, struct file_lock *pfLock)
 | |
| {
 | |
| 	int rc, xid;
 | |
| 	__u32 numLock = 0;
 | |
| 	__u32 numUnlock = 0;
 | |
| 	__u64 length;
 | |
| 	int wait_flag = FALSE;
 | |
| 	struct cifs_sb_info *cifs_sb;
 | |
| 	struct cifsTconInfo *pTcon;
 | |
| 	__u16 netfid;
 | |
| 	__u8 lockType = LOCKING_ANDX_LARGE_FILES;
 | |
| 	int posix_locking;
 | |
| 
 | |
| 	length = 1 + pfLock->fl_end - pfLock->fl_start;
 | |
| 	rc = -EACCES;
 | |
| 	xid = GetXid();
 | |
| 
 | |
| 	cFYI(1, ("Lock parm: 0x%x flockflags: "
 | |
| 		 "0x%x flocktype: 0x%x start: %lld end: %lld",
 | |
| 	        cmd, pfLock->fl_flags, pfLock->fl_type, pfLock->fl_start,
 | |
| 	        pfLock->fl_end));
 | |
| 
 | |
| 	if (pfLock->fl_flags & FL_POSIX)
 | |
| 		cFYI(1, ("Posix"));
 | |
| 	if (pfLock->fl_flags & FL_FLOCK)
 | |
| 		cFYI(1, ("Flock"));
 | |
| 	if (pfLock->fl_flags & FL_SLEEP) {
 | |
| 		cFYI(1, ("Blocking lock"));
 | |
| 		wait_flag = TRUE;
 | |
| 	}
 | |
| 	if (pfLock->fl_flags & FL_ACCESS)
 | |
| 		cFYI(1, ("Process suspended by mandatory locking - "
 | |
| 			 "not implemented yet"));
 | |
| 	if (pfLock->fl_flags & FL_LEASE)
 | |
| 		cFYI(1, ("Lease on file - not implemented yet"));
 | |
| 	if (pfLock->fl_flags & 
 | |
| 	    (~(FL_POSIX | FL_FLOCK | FL_SLEEP | FL_ACCESS | FL_LEASE)))
 | |
| 		cFYI(1, ("Unknown lock flags 0x%x", pfLock->fl_flags));
 | |
| 
 | |
| 	if (pfLock->fl_type == F_WRLCK) {
 | |
| 		cFYI(1, ("F_WRLCK "));
 | |
| 		numLock = 1;
 | |
| 	} else if (pfLock->fl_type == F_UNLCK) {
 | |
| 		cFYI(1, ("F_UNLCK"));
 | |
| 		numUnlock = 1;
 | |
| 		/* Check if unlock includes more than
 | |
| 		one lock range */
 | |
| 	} else if (pfLock->fl_type == F_RDLCK) {
 | |
| 		cFYI(1, ("F_RDLCK"));
 | |
| 		lockType |= LOCKING_ANDX_SHARED_LOCK;
 | |
| 		numLock = 1;
 | |
| 	} else if (pfLock->fl_type == F_EXLCK) {
 | |
| 		cFYI(1, ("F_EXLCK"));
 | |
| 		numLock = 1;
 | |
| 	} else if (pfLock->fl_type == F_SHLCK) {
 | |
| 		cFYI(1, ("F_SHLCK"));
 | |
| 		lockType |= LOCKING_ANDX_SHARED_LOCK;
 | |
| 		numLock = 1;
 | |
| 	} else
 | |
| 		cFYI(1, ("Unknown type of lock"));
 | |
| 
 | |
| 	cifs_sb = CIFS_SB(file->f_dentry->d_sb);
 | |
| 	pTcon = cifs_sb->tcon;
 | |
| 
 | |
| 	if (file->private_data == NULL) {
 | |
| 		FreeXid(xid);
 | |
| 		return -EBADF;
 | |
| 	}
 | |
| 	netfid = ((struct cifsFileInfo *)file->private_data)->netfid;
 | |
| 
 | |
| 	posix_locking = (cifs_sb->tcon->ses->capabilities & CAP_UNIX) &&
 | |
| 			(CIFS_UNIX_FCNTL_CAP & le64_to_cpu(cifs_sb->tcon->fsUnixInfo.Capability));
 | |
| 
 | |
| 	/* BB add code here to normalize offset and length to
 | |
| 	account for negative length which we can not accept over the
 | |
| 	wire */
 | |
| 	if (IS_GETLK(cmd)) {
 | |
| 		if(posix_locking) {
 | |
| 			int posix_lock_type;
 | |
| 			if(lockType & LOCKING_ANDX_SHARED_LOCK)
 | |
| 				posix_lock_type = CIFS_RDLCK;
 | |
| 			else
 | |
| 				posix_lock_type = CIFS_WRLCK;
 | |
| 			rc = CIFSSMBPosixLock(xid, pTcon, netfid, 1 /* get */,
 | |
| 					length,	pfLock,
 | |
| 					posix_lock_type, wait_flag);
 | |
| 			FreeXid(xid);
 | |
| 			return rc;
 | |
| 		}
 | |
| 
 | |
| 		/* BB we could chain these into one lock request BB */
 | |
| 		rc = CIFSSMBLock(xid, pTcon, netfid, length, pfLock->fl_start,
 | |
| 				 0, 1, lockType, 0 /* wait flag */ );
 | |
| 		if (rc == 0) {
 | |
| 			rc = CIFSSMBLock(xid, pTcon, netfid, length, 
 | |
| 					 pfLock->fl_start, 1 /* numUnlock */ ,
 | |
| 					 0 /* numLock */ , lockType,
 | |
| 					 0 /* wait flag */ );
 | |
| 			pfLock->fl_type = F_UNLCK;
 | |
| 			if (rc != 0)
 | |
| 				cERROR(1, ("Error unlocking previously locked "
 | |
| 					   "range %d during test of lock", rc));
 | |
| 			rc = 0;
 | |
| 
 | |
| 		} else {
 | |
| 			/* if rc == ERR_SHARING_VIOLATION ? */
 | |
| 			rc = 0;	/* do not change lock type to unlock
 | |
| 				   since range in use */
 | |
| 		}
 | |
| 
 | |
| 		FreeXid(xid);
 | |
| 		return rc;
 | |
| 	}
 | |
| 
 | |
| 	if (!numLock && !numUnlock) {
 | |
| 		/* if no lock or unlock then nothing
 | |
| 		to do since we do not know what it is */
 | |
| 		FreeXid(xid);
 | |
| 		return -EOPNOTSUPP;
 | |
| 	}
 | |
| 
 | |
| 	if (posix_locking) {
 | |
| 		int posix_lock_type;
 | |
| 		if(lockType & LOCKING_ANDX_SHARED_LOCK)
 | |
| 			posix_lock_type = CIFS_RDLCK;
 | |
| 		else
 | |
| 			posix_lock_type = CIFS_WRLCK;
 | |
| 		
 | |
| 		if(numUnlock == 1)
 | |
| 			posix_lock_type = CIFS_UNLCK;
 | |
| 
 | |
| 		rc = CIFSSMBPosixLock(xid, pTcon, netfid, 0 /* set */,
 | |
| 				      length, pfLock,
 | |
| 				      posix_lock_type, wait_flag);
 | |
| 	} else {
 | |
| 		struct cifsFileInfo *fid = (struct cifsFileInfo *)file->private_data;
 | |
| 
 | |
| 		if (numLock) {
 | |
| 			rc = CIFSSMBLock(xid, pTcon, netfid, length, pfLock->fl_start,
 | |
| 					0, numLock, lockType, wait_flag);
 | |
| 
 | |
| 			if (rc == 0) {
 | |
| 				/* For Windows locks we must store them. */
 | |
| 				rc = store_file_lock(fid, length,
 | |
| 						pfLock->fl_start, lockType);
 | |
| 			}
 | |
| 		} else if (numUnlock) {
 | |
| 			/* For each stored lock that this unlock overlaps
 | |
| 			   completely, unlock it. */
 | |
| 			int stored_rc = 0;
 | |
| 			struct cifsLockInfo *li, *tmp;
 | |
| 
 | |
| 			rc = 0;
 | |
| 			down(&fid->lock_sem);
 | |
| 			list_for_each_entry_safe(li, tmp, &fid->llist, llist) {
 | |
| 				if (pfLock->fl_start <= li->offset &&
 | |
| 						length >= li->length) {
 | |
| 					stored_rc = CIFSSMBLock(xid, pTcon, netfid,
 | |
| 							li->length, li->offset,
 | |
| 							1, 0, li->type, FALSE);
 | |
| 					if (stored_rc)
 | |
| 						rc = stored_rc;
 | |
| 
 | |
| 					list_del(&li->llist);
 | |
| 					kfree(li);
 | |
| 				}
 | |
| 			}
 | |
| 			up(&fid->lock_sem);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (pfLock->fl_flags & FL_POSIX)
 | |
| 		posix_lock_file_wait(file, pfLock);
 | |
| 	FreeXid(xid);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| ssize_t cifs_user_write(struct file *file, const char __user *write_data,
 | |
| 	size_t write_size, loff_t *poffset)
 | |
| {
 | |
| 	int rc = 0;
 | |
| 	unsigned int bytes_written = 0;
 | |
| 	unsigned int total_written;
 | |
| 	struct cifs_sb_info *cifs_sb;
 | |
| 	struct cifsTconInfo *pTcon;
 | |
| 	int xid, long_op;
 | |
| 	struct cifsFileInfo *open_file;
 | |
| 
 | |
| 	if (file->f_dentry == NULL)
 | |
| 		return -EBADF;
 | |
| 
 | |
| 	cifs_sb = CIFS_SB(file->f_dentry->d_sb);
 | |
| 	if (cifs_sb == NULL)
 | |
| 		return -EBADF;
 | |
| 
 | |
| 	pTcon = cifs_sb->tcon;
 | |
| 
 | |
| 	/* cFYI(1,
 | |
| 	   (" write %d bytes to offset %lld of %s", write_size,
 | |
| 	   *poffset, file->f_dentry->d_name.name)); */
 | |
| 
 | |
| 	if (file->private_data == NULL)
 | |
| 		return -EBADF;
 | |
| 	else
 | |
| 		open_file = (struct cifsFileInfo *) file->private_data;
 | |
| 	
 | |
| 	xid = GetXid();
 | |
| 	if (file->f_dentry->d_inode == NULL) {
 | |
| 		FreeXid(xid);
 | |
| 		return -EBADF;
 | |
| 	}
 | |
| 
 | |
| 	if (*poffset > file->f_dentry->d_inode->i_size)
 | |
| 		long_op = 2; /* writes past end of file can take a long time */
 | |
| 	else
 | |
| 		long_op = 1;
 | |
| 
 | |
| 	for (total_written = 0; write_size > total_written;
 | |
| 	     total_written += bytes_written) {
 | |
| 		rc = -EAGAIN;
 | |
| 		while (rc == -EAGAIN) {
 | |
| 			if (file->private_data == NULL) {
 | |
| 				/* file has been closed on us */
 | |
| 				FreeXid(xid);
 | |
| 			/* if we have gotten here we have written some data
 | |
| 			   and blocked, and the file has been freed on us while
 | |
| 			   we blocked so return what we managed to write */
 | |
| 				return total_written;
 | |
| 			} 
 | |
| 			if (open_file->closePend) {
 | |
| 				FreeXid(xid);
 | |
| 				if (total_written)
 | |
| 					return total_written;
 | |
| 				else
 | |
| 					return -EBADF;
 | |
| 			}
 | |
| 			if (open_file->invalidHandle) {
 | |
| 				if ((file->f_dentry == NULL) ||
 | |
| 				    (file->f_dentry->d_inode == NULL)) {
 | |
| 					FreeXid(xid);
 | |
| 					return total_written;
 | |
| 				}
 | |
| 				/* we could deadlock if we called
 | |
| 				   filemap_fdatawait from here so tell
 | |
| 				   reopen_file not to flush data to server
 | |
| 				   now */
 | |
| 				rc = cifs_reopen_file(file->f_dentry->d_inode,
 | |
| 					file, FALSE);
 | |
| 				if (rc != 0)
 | |
| 					break;
 | |
| 			}
 | |
| 
 | |
| 			rc = CIFSSMBWrite(xid, pTcon,
 | |
| 				open_file->netfid,
 | |
| 				min_t(const int, cifs_sb->wsize,
 | |
| 				      write_size - total_written),
 | |
| 				*poffset, &bytes_written,
 | |
| 				NULL, write_data + total_written, long_op);
 | |
| 		}
 | |
| 		if (rc || (bytes_written == 0)) {
 | |
| 			if (total_written)
 | |
| 				break;
 | |
| 			else {
 | |
| 				FreeXid(xid);
 | |
| 				return rc;
 | |
| 			}
 | |
| 		} else
 | |
| 			*poffset += bytes_written;
 | |
| 		long_op = FALSE; /* subsequent writes fast -
 | |
| 				    15 seconds is plenty */
 | |
| 	}
 | |
| 
 | |
| 	cifs_stats_bytes_written(pTcon, total_written);
 | |
| 
 | |
| 	/* since the write may have blocked check these pointers again */
 | |
| 	if (file->f_dentry) {
 | |
| 		if (file->f_dentry->d_inode) {
 | |
| 			struct inode *inode = file->f_dentry->d_inode;
 | |
| 			inode->i_ctime = inode->i_mtime =
 | |
| 				current_fs_time(inode->i_sb);
 | |
| 			if (total_written > 0) {
 | |
| 				if (*poffset > file->f_dentry->d_inode->i_size)
 | |
| 					i_size_write(file->f_dentry->d_inode,
 | |
| 					*poffset);
 | |
| 			}
 | |
| 			mark_inode_dirty_sync(file->f_dentry->d_inode);
 | |
| 		}
 | |
| 	}
 | |
| 	FreeXid(xid);
 | |
| 	return total_written;
 | |
| }
 | |
| 
 | |
| static ssize_t cifs_write(struct file *file, const char *write_data,
 | |
| 	size_t write_size, loff_t *poffset)
 | |
| {
 | |
| 	int rc = 0;
 | |
| 	unsigned int bytes_written = 0;
 | |
| 	unsigned int total_written;
 | |
| 	struct cifs_sb_info *cifs_sb;
 | |
| 	struct cifsTconInfo *pTcon;
 | |
| 	int xid, long_op;
 | |
| 	struct cifsFileInfo *open_file;
 | |
| 
 | |
| 	if (file->f_dentry == NULL)
 | |
| 		return -EBADF;
 | |
| 
 | |
| 	cifs_sb = CIFS_SB(file->f_dentry->d_sb);
 | |
| 	if (cifs_sb == NULL)
 | |
| 		return -EBADF;
 | |
| 
 | |
| 	pTcon = cifs_sb->tcon;
 | |
| 
 | |
| 	cFYI(1,("write %zd bytes to offset %lld of %s", write_size,
 | |
| 	   *poffset, file->f_dentry->d_name.name));
 | |
| 
 | |
| 	if (file->private_data == NULL)
 | |
| 		return -EBADF;
 | |
| 	else
 | |
| 		open_file = (struct cifsFileInfo *)file->private_data;
 | |
| 	
 | |
| 	xid = GetXid();
 | |
| 	if (file->f_dentry->d_inode == NULL) {
 | |
| 		FreeXid(xid);
 | |
| 		return -EBADF;
 | |
| 	}
 | |
| 
 | |
| 	if (*poffset > file->f_dentry->d_inode->i_size)
 | |
| 		long_op = 2; /* writes past end of file can take a long time */
 | |
| 	else
 | |
| 		long_op = 1;
 | |
| 
 | |
| 	for (total_written = 0; write_size > total_written;
 | |
| 	     total_written += bytes_written) {
 | |
| 		rc = -EAGAIN;
 | |
| 		while (rc == -EAGAIN) {
 | |
| 			if (file->private_data == NULL) {
 | |
| 				/* file has been closed on us */
 | |
| 				FreeXid(xid);
 | |
| 			/* if we have gotten here we have written some data
 | |
| 			   and blocked, and the file has been freed on us
 | |
| 			   while we blocked so return what we managed to 
 | |
| 			   write */
 | |
| 				return total_written;
 | |
| 			} 
 | |
| 			if (open_file->closePend) {
 | |
| 				FreeXid(xid);
 | |
| 				if (total_written)
 | |
| 					return total_written;
 | |
| 				else
 | |
| 					return -EBADF;
 | |
| 			}
 | |
| 			if (open_file->invalidHandle) {
 | |
| 				if ((file->f_dentry == NULL) ||
 | |
| 				   (file->f_dentry->d_inode == NULL)) {
 | |
| 					FreeXid(xid);
 | |
| 					return total_written;
 | |
| 				}
 | |
| 				/* we could deadlock if we called
 | |
| 				   filemap_fdatawait from here so tell
 | |
| 				   reopen_file not to flush data to 
 | |
| 				   server now */
 | |
| 				rc = cifs_reopen_file(file->f_dentry->d_inode,
 | |
| 					file, FALSE);
 | |
| 				if (rc != 0)
 | |
| 					break;
 | |
| 			}
 | |
| 			if(experimEnabled || (pTcon->ses->server &&
 | |
| 				((pTcon->ses->server->secMode & 
 | |
| 				(SECMODE_SIGN_REQUIRED | SECMODE_SIGN_ENABLED))
 | |
| 				== 0))) {
 | |
| 				struct kvec iov[2];
 | |
| 				unsigned int len;
 | |
| 
 | |
| 				len = min((size_t)cifs_sb->wsize,
 | |
| 					  write_size - total_written);
 | |
| 				/* iov[0] is reserved for smb header */
 | |
| 				iov[1].iov_base = (char *)write_data +
 | |
| 						  total_written;
 | |
| 				iov[1].iov_len = len;
 | |
| 				rc = CIFSSMBWrite2(xid, pTcon,
 | |
| 						open_file->netfid, len,
 | |
| 						*poffset, &bytes_written,
 | |
| 						iov, 1, long_op);
 | |
| 			} else
 | |
| 				rc = CIFSSMBWrite(xid, pTcon,
 | |
| 					 open_file->netfid,
 | |
| 					 min_t(const int, cifs_sb->wsize,
 | |
| 					       write_size - total_written),
 | |
| 					 *poffset, &bytes_written,
 | |
| 					 write_data + total_written,
 | |
| 					 NULL, long_op);
 | |
| 		}
 | |
| 		if (rc || (bytes_written == 0)) {
 | |
| 			if (total_written)
 | |
| 				break;
 | |
| 			else {
 | |
| 				FreeXid(xid);
 | |
| 				return rc;
 | |
| 			}
 | |
| 		} else
 | |
| 			*poffset += bytes_written;
 | |
| 		long_op = FALSE; /* subsequent writes fast - 
 | |
| 				    15 seconds is plenty */
 | |
| 	}
 | |
| 
 | |
| 	cifs_stats_bytes_written(pTcon, total_written);
 | |
| 
 | |
| 	/* since the write may have blocked check these pointers again */
 | |
| 	if (file->f_dentry) {
 | |
| 		if (file->f_dentry->d_inode) {
 | |
| 			file->f_dentry->d_inode->i_ctime = 
 | |
| 			file->f_dentry->d_inode->i_mtime = CURRENT_TIME;
 | |
| 			if (total_written > 0) {
 | |
| 				if (*poffset > file->f_dentry->d_inode->i_size)
 | |
| 					i_size_write(file->f_dentry->d_inode, 
 | |
| 						     *poffset);
 | |
| 			}
 | |
| 			mark_inode_dirty_sync(file->f_dentry->d_inode);
 | |
| 		}
 | |
| 	}
 | |
| 	FreeXid(xid);
 | |
| 	return total_written;
 | |
| }
 | |
| 
 | |
| struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode)
 | |
| {
 | |
| 	struct cifsFileInfo *open_file;
 | |
| 	int rc;
 | |
| 
 | |
| 	/* Having a null inode here (because mapping->host was set to zero by
 | |
| 	the VFS or MM) should not happen but we had reports of on oops (due to
 | |
| 	it being zero) during stress testcases so we need to check for it */
 | |
| 
 | |
| 	if(cifs_inode == NULL) {
 | |
| 		cERROR(1,("Null inode passed to cifs_writeable_file"));
 | |
| 		dump_stack();
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	read_lock(&GlobalSMBSeslock);
 | |
| 	list_for_each_entry(open_file, &cifs_inode->openFileList, flist) {
 | |
| 		if (open_file->closePend)
 | |
| 			continue;
 | |
| 		if (open_file->pfile &&
 | |
| 		    ((open_file->pfile->f_flags & O_RDWR) ||
 | |
| 		     (open_file->pfile->f_flags & O_WRONLY))) {
 | |
| 			atomic_inc(&open_file->wrtPending);
 | |
| 			read_unlock(&GlobalSMBSeslock);
 | |
| 			if((open_file->invalidHandle) && 
 | |
| 			   (!open_file->closePend) /* BB fixme -since the second clause can not be true remove it BB */) {
 | |
| 				rc = cifs_reopen_file(&cifs_inode->vfs_inode, 
 | |
| 						      open_file->pfile, FALSE);
 | |
| 				/* if it fails, try another handle - might be */
 | |
| 				/* dangerous to hold up writepages with retry */
 | |
| 				if(rc) {
 | |
| 					cFYI(1,("failed on reopen file in wp"));
 | |
| 					read_lock(&GlobalSMBSeslock);
 | |
| 					/* can not use this handle, no write
 | |
| 					pending on this one after all */
 | |
| 					atomic_dec
 | |
| 					     (&open_file->wrtPending);
 | |
| 					continue;
 | |
| 				}
 | |
| 			}
 | |
| 			return open_file;
 | |
| 		}
 | |
| 	}
 | |
| 	read_unlock(&GlobalSMBSeslock);
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| static int cifs_partialpagewrite(struct page *page, unsigned from, unsigned to)
 | |
| {
 | |
| 	struct address_space *mapping = page->mapping;
 | |
| 	loff_t offset = (loff_t)page->index << PAGE_CACHE_SHIFT;
 | |
| 	char *write_data;
 | |
| 	int rc = -EFAULT;
 | |
| 	int bytes_written = 0;
 | |
| 	struct cifs_sb_info *cifs_sb;
 | |
| 	struct cifsTconInfo *pTcon;
 | |
| 	struct inode *inode;
 | |
| 	struct cifsFileInfo *open_file;
 | |
| 
 | |
| 	if (!mapping || !mapping->host)
 | |
| 		return -EFAULT;
 | |
| 
 | |
| 	inode = page->mapping->host;
 | |
| 	cifs_sb = CIFS_SB(inode->i_sb);
 | |
| 	pTcon = cifs_sb->tcon;
 | |
| 
 | |
| 	offset += (loff_t)from;
 | |
| 	write_data = kmap(page);
 | |
| 	write_data += from;
 | |
| 
 | |
| 	if ((to > PAGE_CACHE_SIZE) || (from > to)) {
 | |
| 		kunmap(page);
 | |
| 		return -EIO;
 | |
| 	}
 | |
| 
 | |
| 	/* racing with truncate? */
 | |
| 	if (offset > mapping->host->i_size) {
 | |
| 		kunmap(page);
 | |
| 		return 0; /* don't care */
 | |
| 	}
 | |
| 
 | |
| 	/* check to make sure that we are not extending the file */
 | |
| 	if (mapping->host->i_size - offset < (loff_t)to)
 | |
| 		to = (unsigned)(mapping->host->i_size - offset); 
 | |
| 
 | |
| 	open_file = find_writable_file(CIFS_I(mapping->host));
 | |
| 	if (open_file) {
 | |
| 		bytes_written = cifs_write(open_file->pfile, write_data,
 | |
| 					   to-from, &offset);
 | |
| 		atomic_dec(&open_file->wrtPending);
 | |
| 		/* Does mm or vfs already set times? */
 | |
| 		inode->i_atime = inode->i_mtime = current_fs_time(inode->i_sb);
 | |
| 		if ((bytes_written > 0) && (offset)) {
 | |
| 			rc = 0;
 | |
| 		} else if (bytes_written < 0) {
 | |
| 			if (rc != -EBADF)
 | |
| 				rc = bytes_written;
 | |
| 		}
 | |
| 	} else {
 | |
| 		cFYI(1, ("No writeable filehandles for inode"));
 | |
| 		rc = -EIO;
 | |
| 	}
 | |
| 
 | |
| 	kunmap(page);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| static int cifs_writepages(struct address_space *mapping,
 | |
| 			   struct writeback_control *wbc)
 | |
| {
 | |
| 	struct backing_dev_info *bdi = mapping->backing_dev_info;
 | |
| 	unsigned int bytes_to_write;
 | |
| 	unsigned int bytes_written;
 | |
| 	struct cifs_sb_info *cifs_sb;
 | |
| 	int done = 0;
 | |
| 	pgoff_t end;
 | |
| 	pgoff_t index;
 | |
|  	int range_whole = 0;
 | |
| 	struct kvec iov[32];
 | |
| 	int len;
 | |
| 	int n_iov = 0;
 | |
| 	pgoff_t next;
 | |
| 	int nr_pages;
 | |
| 	__u64 offset = 0;
 | |
| 	struct cifsFileInfo *open_file;
 | |
| 	struct page *page;
 | |
| 	struct pagevec pvec;
 | |
| 	int rc = 0;
 | |
| 	int scanned = 0;
 | |
| 	int xid;
 | |
| 
 | |
| 	cifs_sb = CIFS_SB(mapping->host->i_sb);
 | |
| 	
 | |
| 	/*
 | |
| 	 * If wsize is smaller that the page cache size, default to writing
 | |
| 	 * one page at a time via cifs_writepage
 | |
| 	 */
 | |
| 	if (cifs_sb->wsize < PAGE_CACHE_SIZE)
 | |
| 		return generic_writepages(mapping, wbc);
 | |
| 
 | |
| 	if((cifs_sb->tcon->ses) && (cifs_sb->tcon->ses->server))
 | |
| 		if(cifs_sb->tcon->ses->server->secMode &
 | |
|                           (SECMODE_SIGN_REQUIRED | SECMODE_SIGN_ENABLED))
 | |
| 			if(!experimEnabled)
 | |
| 				return generic_writepages(mapping, wbc);
 | |
| 
 | |
| 	/*
 | |
| 	 * BB: Is this meaningful for a non-block-device file system?
 | |
| 	 * If it is, we should test it again after we do I/O
 | |
| 	 */
 | |
| 	if (wbc->nonblocking && bdi_write_congested(bdi)) {
 | |
| 		wbc->encountered_congestion = 1;
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	xid = GetXid();
 | |
| 
 | |
| 	pagevec_init(&pvec, 0);
 | |
| 	if (wbc->range_cyclic) {
 | |
| 		index = mapping->writeback_index; /* Start from prev offset */
 | |
| 		end = -1;
 | |
| 	} else {
 | |
| 		index = wbc->range_start >> PAGE_CACHE_SHIFT;
 | |
| 		end = wbc->range_end >> PAGE_CACHE_SHIFT;
 | |
| 		if (wbc->range_start == 0 && wbc->range_end == LLONG_MAX)
 | |
| 			range_whole = 1;
 | |
| 		scanned = 1;
 | |
| 	}
 | |
| retry:
 | |
| 	while (!done && (index <= end) &&
 | |
| 	       (nr_pages = pagevec_lookup_tag(&pvec, mapping, &index,
 | |
| 			PAGECACHE_TAG_DIRTY,
 | |
| 			min(end - index, (pgoff_t)PAGEVEC_SIZE - 1) + 1))) {
 | |
| 		int first;
 | |
| 		unsigned int i;
 | |
| 
 | |
| 		first = -1;
 | |
| 		next = 0;
 | |
| 		n_iov = 0;
 | |
| 		bytes_to_write = 0;
 | |
| 
 | |
| 		for (i = 0; i < nr_pages; i++) {
 | |
| 			page = pvec.pages[i];
 | |
| 			/*
 | |
| 			 * At this point we hold neither mapping->tree_lock nor
 | |
| 			 * lock on the page itself: the page may be truncated or
 | |
| 			 * invalidated (changing page->mapping to NULL), or even
 | |
| 			 * swizzled back from swapper_space to tmpfs file
 | |
| 			 * mapping
 | |
| 			 */
 | |
| 
 | |
| 			if (first < 0)
 | |
| 				lock_page(page);
 | |
| 			else if (TestSetPageLocked(page))
 | |
| 				break;
 | |
| 
 | |
| 			if (unlikely(page->mapping != mapping)) {
 | |
| 				unlock_page(page);
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			if (!wbc->range_cyclic && page->index > end) {
 | |
| 				done = 1;
 | |
| 				unlock_page(page);
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			if (next && (page->index != next)) {
 | |
| 				/* Not next consecutive page */
 | |
| 				unlock_page(page);
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			if (wbc->sync_mode != WB_SYNC_NONE)
 | |
| 				wait_on_page_writeback(page);
 | |
| 
 | |
| 			if (PageWriteback(page) ||
 | |
| 					!test_clear_page_dirty(page)) {
 | |
| 				unlock_page(page);
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			if (page_offset(page) >= mapping->host->i_size) {
 | |
| 				done = 1;
 | |
| 				unlock_page(page);
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			/*
 | |
| 			 * BB can we get rid of this?  pages are held by pvec
 | |
| 			 */
 | |
| 			page_cache_get(page);
 | |
| 
 | |
| 			len = min(mapping->host->i_size - page_offset(page),
 | |
| 				  (loff_t)PAGE_CACHE_SIZE);
 | |
| 
 | |
| 			/* reserve iov[0] for the smb header */
 | |
| 			n_iov++;
 | |
| 			iov[n_iov].iov_base = kmap(page);
 | |
| 			iov[n_iov].iov_len = len;
 | |
| 			bytes_to_write += len;
 | |
| 
 | |
| 			if (first < 0) {
 | |
| 				first = i;
 | |
| 				offset = page_offset(page);
 | |
| 			}
 | |
| 			next = page->index + 1;
 | |
| 			if (bytes_to_write + PAGE_CACHE_SIZE > cifs_sb->wsize)
 | |
| 				break;
 | |
| 		}
 | |
| 		if (n_iov) {
 | |
| 			/* Search for a writable handle every time we call
 | |
| 			 * CIFSSMBWrite2.  We can't rely on the last handle
 | |
| 			 * we used to still be valid
 | |
| 			 */
 | |
| 			open_file = find_writable_file(CIFS_I(mapping->host));
 | |
| 			if (!open_file) {
 | |
| 				cERROR(1, ("No writable handles for inode"));
 | |
| 				rc = -EBADF;
 | |
| 			} else {
 | |
| 				rc = CIFSSMBWrite2(xid, cifs_sb->tcon,
 | |
| 						   open_file->netfid,
 | |
| 						   bytes_to_write, offset,
 | |
| 						   &bytes_written, iov, n_iov,
 | |
| 						   1);
 | |
| 				atomic_dec(&open_file->wrtPending);
 | |
| 				if (rc || bytes_written < bytes_to_write) {
 | |
| 					cERROR(1,("Write2 ret %d, written = %d",
 | |
| 						  rc, bytes_written));
 | |
| 					/* BB what if continued retry is
 | |
| 					   requested via mount flags? */
 | |
| 					set_bit(AS_EIO, &mapping->flags);
 | |
| 				} else {
 | |
| 					cifs_stats_bytes_written(cifs_sb->tcon,
 | |
| 								 bytes_written);
 | |
| 				}
 | |
| 			}
 | |
| 			for (i = 0; i < n_iov; i++) {
 | |
| 				page = pvec.pages[first + i];
 | |
| 				/* Should we also set page error on
 | |
| 				success rc but too little data written? */
 | |
| 				/* BB investigate retry logic on temporary
 | |
| 				server crash cases and how recovery works
 | |
| 				when page marked as error */ 
 | |
| 				if(rc)
 | |
| 					SetPageError(page);
 | |
| 				kunmap(page);
 | |
| 				unlock_page(page);
 | |
| 				page_cache_release(page);
 | |
| 			}
 | |
| 			if ((wbc->nr_to_write -= n_iov) <= 0)
 | |
| 				done = 1;
 | |
| 			index = next;
 | |
| 		}
 | |
| 		pagevec_release(&pvec);
 | |
| 	}
 | |
| 	if (!scanned && !done) {
 | |
| 		/*
 | |
| 		 * We hit the last page and there is more work to be done: wrap
 | |
| 		 * back to the start of the file
 | |
| 		 */
 | |
| 		scanned = 1;
 | |
| 		index = 0;
 | |
| 		goto retry;
 | |
| 	}
 | |
| 	if (wbc->range_cyclic || (range_whole && wbc->nr_to_write > 0))
 | |
| 		mapping->writeback_index = index;
 | |
| 
 | |
| 	FreeXid(xid);
 | |
| 
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| static int cifs_writepage(struct page* page, struct writeback_control *wbc)
 | |
| {
 | |
| 	int rc = -EFAULT;
 | |
| 	int xid;
 | |
| 
 | |
| 	xid = GetXid();
 | |
| /* BB add check for wbc flags */
 | |
| 	page_cache_get(page);
 | |
|         if (!PageUptodate(page)) {
 | |
| 		cFYI(1, ("ppw - page not up to date"));
 | |
| 	}
 | |
| 	
 | |
| 	rc = cifs_partialpagewrite(page, 0, PAGE_CACHE_SIZE);
 | |
| 	SetPageUptodate(page); /* BB add check for error and Clearuptodate? */
 | |
| 	unlock_page(page);
 | |
| 	page_cache_release(page);	
 | |
| 	FreeXid(xid);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| static int cifs_commit_write(struct file *file, struct page *page,
 | |
| 	unsigned offset, unsigned to)
 | |
| {
 | |
| 	int xid;
 | |
| 	int rc = 0;
 | |
| 	struct inode *inode = page->mapping->host;
 | |
| 	loff_t position = ((loff_t)page->index << PAGE_CACHE_SHIFT) + to;
 | |
| 	char *page_data;
 | |
| 
 | |
| 	xid = GetXid();
 | |
| 	cFYI(1, ("commit write for page %p up to position %lld for %d", 
 | |
| 		 page, position, to));
 | |
| 	if (position > inode->i_size) {
 | |
| 		i_size_write(inode, position);
 | |
| 		/* if (file->private_data == NULL) {
 | |
| 			rc = -EBADF;
 | |
| 		} else {
 | |
| 			open_file = (struct cifsFileInfo *)file->private_data;
 | |
| 			cifs_sb = CIFS_SB(inode->i_sb);
 | |
| 			rc = -EAGAIN;
 | |
| 			while (rc == -EAGAIN) {
 | |
| 				if ((open_file->invalidHandle) && 
 | |
| 				    (!open_file->closePend)) {
 | |
| 					rc = cifs_reopen_file(
 | |
| 						file->f_dentry->d_inode, file);
 | |
| 					if (rc != 0)
 | |
| 						break;
 | |
| 				}
 | |
| 				if (!open_file->closePend) {
 | |
| 					rc = CIFSSMBSetFileSize(xid,
 | |
| 						cifs_sb->tcon, position,
 | |
| 						open_file->netfid,
 | |
| 						open_file->pid, FALSE);
 | |
| 				} else {
 | |
| 					rc = -EBADF;
 | |
| 					break;
 | |
| 				}
 | |
| 			}
 | |
| 			cFYI(1, (" SetEOF (commit write) rc = %d", rc));
 | |
| 		} */
 | |
| 	}
 | |
| 	if (!PageUptodate(page)) {
 | |
| 		position =  ((loff_t)page->index << PAGE_CACHE_SHIFT) + offset;
 | |
| 		/* can not rely on (or let) writepage write this data */
 | |
| 		if (to < offset) {
 | |
| 			cFYI(1, ("Illegal offsets, can not copy from %d to %d",
 | |
| 				offset, to));
 | |
| 			FreeXid(xid);
 | |
| 			return rc;
 | |
| 		}
 | |
| 		/* this is probably better than directly calling
 | |
| 		   partialpage_write since in this function the file handle is
 | |
| 		   known which we might as well	leverage */
 | |
| 		/* BB check if anything else missing out of ppw
 | |
| 		   such as updating last write time */
 | |
| 		page_data = kmap(page);
 | |
| 		rc = cifs_write(file, page_data + offset, to-offset,
 | |
| 				&position);
 | |
| 		if (rc > 0)
 | |
| 			rc = 0;
 | |
| 		/* else if (rc < 0) should we set writebehind rc? */
 | |
| 		kunmap(page);
 | |
| 	} else {	
 | |
| 		set_page_dirty(page);
 | |
| 	}
 | |
| 
 | |
| 	FreeXid(xid);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| int cifs_fsync(struct file *file, struct dentry *dentry, int datasync)
 | |
| {
 | |
| 	int xid;
 | |
| 	int rc = 0;
 | |
| 	struct inode *inode = file->f_dentry->d_inode;
 | |
| 
 | |
| 	xid = GetXid();
 | |
| 
 | |
| 	cFYI(1, ("Sync file - name: %s datasync: 0x%x", 
 | |
| 		dentry->d_name.name, datasync));
 | |
| 	
 | |
| 	rc = filemap_fdatawrite(inode->i_mapping);
 | |
| 	if (rc == 0)
 | |
| 		CIFS_I(inode)->write_behind_rc = 0;
 | |
| 	FreeXid(xid);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| /* static void cifs_sync_page(struct page *page)
 | |
| {
 | |
| 	struct address_space *mapping;
 | |
| 	struct inode *inode;
 | |
| 	unsigned long index = page->index;
 | |
| 	unsigned int rpages = 0;
 | |
| 	int rc = 0;
 | |
| 
 | |
| 	cFYI(1, ("sync page %p",page));
 | |
| 	mapping = page->mapping;
 | |
| 	if (!mapping)
 | |
| 		return 0;
 | |
| 	inode = mapping->host;
 | |
| 	if (!inode)
 | |
| 		return; */
 | |
| 
 | |
| /*	fill in rpages then 
 | |
| 	result = cifs_pagein_inode(inode, index, rpages); */ /* BB finish */
 | |
| 
 | |
| /*	cFYI(1, ("rpages is %d for sync page of Index %ld", rpages, index));
 | |
| 
 | |
| #if 0
 | |
| 	if (rc < 0)
 | |
| 		return rc;
 | |
| 	return 0;
 | |
| #endif
 | |
| } */
 | |
| 
 | |
| /*
 | |
|  * As file closes, flush all cached write data for this inode checking
 | |
|  * for write behind errors.
 | |
|  */
 | |
| int cifs_flush(struct file *file, fl_owner_t id)
 | |
| {
 | |
| 	struct inode * inode = file->f_dentry->d_inode;
 | |
| 	int rc = 0;
 | |
| 
 | |
| 	/* Rather than do the steps manually:
 | |
| 	   lock the inode for writing
 | |
| 	   loop through pages looking for write behind data (dirty pages)
 | |
| 	   coalesce into contiguous 16K (or smaller) chunks to write to server
 | |
| 	   send to server (prefer in parallel)
 | |
| 	   deal with writebehind errors
 | |
| 	   unlock inode for writing
 | |
| 	   filemapfdatawrite appears easier for the time being */
 | |
| 
 | |
| 	rc = filemap_fdatawrite(inode->i_mapping);
 | |
| 	if (!rc) /* reset wb rc if we were able to write out dirty pages */
 | |
| 		CIFS_I(inode)->write_behind_rc = 0;
 | |
| 		
 | |
| 	cFYI(1, ("Flush inode %p file %p rc %d",inode,file,rc));
 | |
| 
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| ssize_t cifs_user_read(struct file *file, char __user *read_data,
 | |
| 	size_t read_size, loff_t *poffset)
 | |
| {
 | |
| 	int rc = -EACCES;
 | |
| 	unsigned int bytes_read = 0;
 | |
| 	unsigned int total_read = 0;
 | |
| 	unsigned int current_read_size;
 | |
| 	struct cifs_sb_info *cifs_sb;
 | |
| 	struct cifsTconInfo *pTcon;
 | |
| 	int xid;
 | |
| 	struct cifsFileInfo *open_file;
 | |
| 	char *smb_read_data;
 | |
| 	char __user *current_offset;
 | |
| 	struct smb_com_read_rsp *pSMBr;
 | |
| 
 | |
| 	xid = GetXid();
 | |
| 	cifs_sb = CIFS_SB(file->f_dentry->d_sb);
 | |
| 	pTcon = cifs_sb->tcon;
 | |
| 
 | |
| 	if (file->private_data == NULL) {
 | |
| 		FreeXid(xid);
 | |
| 		return -EBADF;
 | |
| 	}
 | |
| 	open_file = (struct cifsFileInfo *)file->private_data;
 | |
| 
 | |
| 	if ((file->f_flags & O_ACCMODE) == O_WRONLY) {
 | |
| 		cFYI(1, ("attempting read on write only file instance"));
 | |
| 	}
 | |
| 	for (total_read = 0, current_offset = read_data;
 | |
| 	     read_size > total_read;
 | |
| 	     total_read += bytes_read, current_offset += bytes_read) {
 | |
| 		current_read_size = min_t(const int, read_size - total_read, 
 | |
| 					  cifs_sb->rsize);
 | |
| 		rc = -EAGAIN;
 | |
| 		smb_read_data = NULL;
 | |
| 		while (rc == -EAGAIN) {
 | |
| 			int buf_type = CIFS_NO_BUFFER;
 | |
| 			if ((open_file->invalidHandle) && 
 | |
| 			    (!open_file->closePend)) {
 | |
| 				rc = cifs_reopen_file(file->f_dentry->d_inode,
 | |
| 					file, TRUE);
 | |
| 				if (rc != 0)
 | |
| 					break;
 | |
| 			}
 | |
| 			rc = CIFSSMBRead(xid, pTcon,
 | |
| 					 open_file->netfid,
 | |
| 					 current_read_size, *poffset,
 | |
| 					 &bytes_read, &smb_read_data,
 | |
| 					 &buf_type);
 | |
| 			pSMBr = (struct smb_com_read_rsp *)smb_read_data;
 | |
| 			if (smb_read_data) {
 | |
| 				if (copy_to_user(current_offset,
 | |
| 						smb_read_data +
 | |
| 						4 /* RFC1001 length field */ +
 | |
| 						le16_to_cpu(pSMBr->DataOffset),
 | |
| 						bytes_read)) {
 | |
| 					rc = -EFAULT;
 | |
| 				}
 | |
| 
 | |
| 				if(buf_type == CIFS_SMALL_BUFFER)
 | |
| 					cifs_small_buf_release(smb_read_data);
 | |
| 				else if(buf_type == CIFS_LARGE_BUFFER)
 | |
| 					cifs_buf_release(smb_read_data);
 | |
| 				smb_read_data = NULL;
 | |
| 			}
 | |
| 		}
 | |
| 		if (rc || (bytes_read == 0)) {
 | |
| 			if (total_read) {
 | |
| 				break;
 | |
| 			} else {
 | |
| 				FreeXid(xid);
 | |
| 				return rc;
 | |
| 			}
 | |
| 		} else {
 | |
| 			cifs_stats_bytes_read(pTcon, bytes_read);
 | |
| 			*poffset += bytes_read;
 | |
| 		}
 | |
| 	}
 | |
| 	FreeXid(xid);
 | |
| 	return total_read;
 | |
| }
 | |
| 
 | |
| 
 | |
| static ssize_t cifs_read(struct file *file, char *read_data, size_t read_size,
 | |
| 	loff_t *poffset)
 | |
| {
 | |
| 	int rc = -EACCES;
 | |
| 	unsigned int bytes_read = 0;
 | |
| 	unsigned int total_read;
 | |
| 	unsigned int current_read_size;
 | |
| 	struct cifs_sb_info *cifs_sb;
 | |
| 	struct cifsTconInfo *pTcon;
 | |
| 	int xid;
 | |
| 	char *current_offset;
 | |
| 	struct cifsFileInfo *open_file;
 | |
| 	int buf_type = CIFS_NO_BUFFER;
 | |
| 
 | |
| 	xid = GetXid();
 | |
| 	cifs_sb = CIFS_SB(file->f_dentry->d_sb);
 | |
| 	pTcon = cifs_sb->tcon;
 | |
| 
 | |
| 	if (file->private_data == NULL) {
 | |
| 		FreeXid(xid);
 | |
| 		return -EBADF;
 | |
| 	}
 | |
| 	open_file = (struct cifsFileInfo *)file->private_data;
 | |
| 
 | |
| 	if ((file->f_flags & O_ACCMODE) == O_WRONLY)
 | |
| 		cFYI(1, ("attempting read on write only file instance"));
 | |
| 
 | |
| 	for (total_read = 0, current_offset = read_data; 
 | |
| 	     read_size > total_read;
 | |
| 	     total_read += bytes_read, current_offset += bytes_read) {
 | |
| 		current_read_size = min_t(const int, read_size - total_read,
 | |
| 					  cifs_sb->rsize);
 | |
| 		/* For windows me and 9x we do not want to request more
 | |
| 		than it negotiated since it will refuse the read then */
 | |
| 		if((pTcon->ses) && 
 | |
| 			!(pTcon->ses->capabilities & CAP_LARGE_FILES)) {
 | |
| 			current_read_size = min_t(const int, current_read_size,
 | |
| 					pTcon->ses->server->maxBuf - 128);
 | |
| 		}
 | |
| 		rc = -EAGAIN;
 | |
| 		while (rc == -EAGAIN) {
 | |
| 			if ((open_file->invalidHandle) && 
 | |
| 			    (!open_file->closePend)) {
 | |
| 				rc = cifs_reopen_file(file->f_dentry->d_inode,
 | |
| 					file, TRUE);
 | |
| 				if (rc != 0)
 | |
| 					break;
 | |
| 			}
 | |
| 			rc = CIFSSMBRead(xid, pTcon,
 | |
| 					 open_file->netfid,
 | |
| 					 current_read_size, *poffset,
 | |
| 					 &bytes_read, ¤t_offset,
 | |
| 					 &buf_type);
 | |
| 		}
 | |
| 		if (rc || (bytes_read == 0)) {
 | |
| 			if (total_read) {
 | |
| 				break;
 | |
| 			} else {
 | |
| 				FreeXid(xid);
 | |
| 				return rc;
 | |
| 			}
 | |
| 		} else {
 | |
| 			cifs_stats_bytes_read(pTcon, total_read);
 | |
| 			*poffset += bytes_read;
 | |
| 		}
 | |
| 	}
 | |
| 	FreeXid(xid);
 | |
| 	return total_read;
 | |
| }
 | |
| 
 | |
| int cifs_file_mmap(struct file *file, struct vm_area_struct *vma)
 | |
| {
 | |
| 	struct dentry *dentry = file->f_dentry;
 | |
| 	int rc, xid;
 | |
| 
 | |
| 	xid = GetXid();
 | |
| 	rc = cifs_revalidate(dentry);
 | |
| 	if (rc) {
 | |
| 		cFYI(1, ("Validation prior to mmap failed, error=%d", rc));
 | |
| 		FreeXid(xid);
 | |
| 		return rc;
 | |
| 	}
 | |
| 	rc = generic_file_mmap(file, vma);
 | |
| 	FreeXid(xid);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void cifs_copy_cache_pages(struct address_space *mapping, 
 | |
| 	struct list_head *pages, int bytes_read, char *data,
 | |
| 	struct pagevec *plru_pvec)
 | |
| {
 | |
| 	struct page *page;
 | |
| 	char *target;
 | |
| 
 | |
| 	while (bytes_read > 0) {
 | |
| 		if (list_empty(pages))
 | |
| 			break;
 | |
| 
 | |
| 		page = list_entry(pages->prev, struct page, lru);
 | |
| 		list_del(&page->lru);
 | |
| 
 | |
| 		if (add_to_page_cache(page, mapping, page->index,
 | |
| 				      GFP_KERNEL)) {
 | |
| 			page_cache_release(page);
 | |
| 			cFYI(1, ("Add page cache failed"));
 | |
| 			data += PAGE_CACHE_SIZE;
 | |
| 			bytes_read -= PAGE_CACHE_SIZE;
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		target = kmap_atomic(page,KM_USER0);
 | |
| 
 | |
| 		if (PAGE_CACHE_SIZE > bytes_read) {
 | |
| 			memcpy(target, data, bytes_read);
 | |
| 			/* zero the tail end of this partial page */
 | |
| 			memset(target + bytes_read, 0, 
 | |
| 			       PAGE_CACHE_SIZE - bytes_read);
 | |
| 			bytes_read = 0;
 | |
| 		} else {
 | |
| 			memcpy(target, data, PAGE_CACHE_SIZE);
 | |
| 			bytes_read -= PAGE_CACHE_SIZE;
 | |
| 		}
 | |
| 		kunmap_atomic(target, KM_USER0);
 | |
| 
 | |
| 		flush_dcache_page(page);
 | |
| 		SetPageUptodate(page);
 | |
| 		unlock_page(page);
 | |
| 		if (!pagevec_add(plru_pvec, page))
 | |
| 			__pagevec_lru_add(plru_pvec);
 | |
| 		data += PAGE_CACHE_SIZE;
 | |
| 	}
 | |
| 	return;
 | |
| }
 | |
| 
 | |
| static int cifs_readpages(struct file *file, struct address_space *mapping,
 | |
| 	struct list_head *page_list, unsigned num_pages)
 | |
| {
 | |
| 	int rc = -EACCES;
 | |
| 	int xid;
 | |
| 	loff_t offset;
 | |
| 	struct page *page;
 | |
| 	struct cifs_sb_info *cifs_sb;
 | |
| 	struct cifsTconInfo *pTcon;
 | |
| 	int bytes_read = 0;
 | |
| 	unsigned int read_size,i;
 | |
| 	char *smb_read_data = NULL;
 | |
| 	struct smb_com_read_rsp *pSMBr;
 | |
| 	struct pagevec lru_pvec;
 | |
| 	struct cifsFileInfo *open_file;
 | |
| 	int buf_type = CIFS_NO_BUFFER;
 | |
| 
 | |
| 	xid = GetXid();
 | |
| 	if (file->private_data == NULL) {
 | |
| 		FreeXid(xid);
 | |
| 		return -EBADF;
 | |
| 	}
 | |
| 	open_file = (struct cifsFileInfo *)file->private_data;
 | |
| 	cifs_sb = CIFS_SB(file->f_dentry->d_sb);
 | |
| 	pTcon = cifs_sb->tcon;
 | |
| 
 | |
| 	pagevec_init(&lru_pvec, 0);
 | |
| 
 | |
| 	for (i = 0; i < num_pages; ) {
 | |
| 		unsigned contig_pages;
 | |
| 		struct page *tmp_page;
 | |
| 		unsigned long expected_index;
 | |
| 
 | |
| 		if (list_empty(page_list))
 | |
| 			break;
 | |
| 
 | |
| 		page = list_entry(page_list->prev, struct page, lru);
 | |
| 		offset = (loff_t)page->index << PAGE_CACHE_SHIFT;
 | |
| 
 | |
| 		/* count adjacent pages that we will read into */
 | |
| 		contig_pages = 0;
 | |
| 		expected_index = 
 | |
| 			list_entry(page_list->prev, struct page, lru)->index;
 | |
| 		list_for_each_entry_reverse(tmp_page,page_list,lru) {
 | |
| 			if (tmp_page->index == expected_index) {
 | |
| 				contig_pages++;
 | |
| 				expected_index++;
 | |
| 			} else
 | |
| 				break; 
 | |
| 		}
 | |
| 		if (contig_pages + i >  num_pages)
 | |
| 			contig_pages = num_pages - i;
 | |
| 
 | |
| 		/* for reads over a certain size could initiate async
 | |
| 		   read ahead */
 | |
| 
 | |
| 		read_size = contig_pages * PAGE_CACHE_SIZE;
 | |
| 		/* Read size needs to be in multiples of one page */
 | |
| 		read_size = min_t(const unsigned int, read_size,
 | |
| 				  cifs_sb->rsize & PAGE_CACHE_MASK);
 | |
| 
 | |
| 		rc = -EAGAIN;
 | |
| 		while (rc == -EAGAIN) {
 | |
| 			if ((open_file->invalidHandle) && 
 | |
| 			    (!open_file->closePend)) {
 | |
| 				rc = cifs_reopen_file(file->f_dentry->d_inode,
 | |
| 					file, TRUE);
 | |
| 				if (rc != 0)
 | |
| 					break;
 | |
| 			}
 | |
| 
 | |
| 			rc = CIFSSMBRead(xid, pTcon,
 | |
| 					 open_file->netfid,
 | |
| 					 read_size, offset,
 | |
| 					 &bytes_read, &smb_read_data,
 | |
| 					 &buf_type);
 | |
| 			/* BB more RC checks ? */
 | |
| 			if (rc== -EAGAIN) {
 | |
| 				if (smb_read_data) {
 | |
| 					if(buf_type == CIFS_SMALL_BUFFER)
 | |
| 						cifs_small_buf_release(smb_read_data);
 | |
| 					else if(buf_type == CIFS_LARGE_BUFFER)
 | |
| 						cifs_buf_release(smb_read_data);
 | |
| 					smb_read_data = NULL;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		if ((rc < 0) || (smb_read_data == NULL)) {
 | |
| 			cFYI(1, ("Read error in readpages: %d", rc));
 | |
| 			/* clean up remaing pages off list */
 | |
| 			while (!list_empty(page_list) && (i < num_pages)) {
 | |
| 				page = list_entry(page_list->prev, struct page,
 | |
| 						  lru);
 | |
| 				list_del(&page->lru);
 | |
| 				page_cache_release(page);
 | |
| 			}
 | |
| 			break;
 | |
| 		} else if (bytes_read > 0) {
 | |
| 			pSMBr = (struct smb_com_read_rsp *)smb_read_data;
 | |
| 			cifs_copy_cache_pages(mapping, page_list, bytes_read,
 | |
| 				smb_read_data + 4 /* RFC1001 hdr */ +
 | |
| 				le16_to_cpu(pSMBr->DataOffset), &lru_pvec);
 | |
| 
 | |
| 			i +=  bytes_read >> PAGE_CACHE_SHIFT;
 | |
| 			cifs_stats_bytes_read(pTcon, bytes_read);
 | |
| 			if ((int)(bytes_read & PAGE_CACHE_MASK) != bytes_read) {
 | |
| 				i++; /* account for partial page */
 | |
| 
 | |
| 				/* server copy of file can have smaller size 
 | |
| 				   than client */
 | |
| 				/* BB do we need to verify this common case ? 
 | |
| 				   this case is ok - if we are at server EOF 
 | |
| 				   we will hit it on next read */
 | |
| 
 | |
| 			/* while (!list_empty(page_list) && (i < num_pages)) {
 | |
| 					page = list_entry(page_list->prev, 
 | |
| 							  struct page, list);
 | |
| 					list_del(&page->list);
 | |
| 					page_cache_release(page);
 | |
| 				}
 | |
| 				break; */
 | |
| 			}
 | |
| 		} else {
 | |
| 			cFYI(1, ("No bytes read (%d) at offset %lld . "
 | |
| 				 "Cleaning remaining pages from readahead list",
 | |
| 				 bytes_read, offset));
 | |
| 			/* BB turn off caching and do new lookup on 
 | |
| 			   file size at server? */
 | |
| 			while (!list_empty(page_list) && (i < num_pages)) {
 | |
| 				page = list_entry(page_list->prev, struct page,
 | |
| 						  lru);
 | |
| 				list_del(&page->lru);
 | |
| 
 | |
| 				/* BB removeme - replace with zero of page? */
 | |
| 				page_cache_release(page);
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 		if (smb_read_data) {
 | |
| 			if(buf_type == CIFS_SMALL_BUFFER)
 | |
| 				cifs_small_buf_release(smb_read_data);
 | |
| 			else if(buf_type == CIFS_LARGE_BUFFER)
 | |
| 				cifs_buf_release(smb_read_data);
 | |
| 			smb_read_data = NULL;
 | |
| 		}
 | |
| 		bytes_read = 0;
 | |
| 	}
 | |
| 
 | |
| 	pagevec_lru_add(&lru_pvec);
 | |
| 
 | |
| /* need to free smb_read_data buf before exit */
 | |
| 	if (smb_read_data) {
 | |
| 		if(buf_type == CIFS_SMALL_BUFFER)
 | |
| 			cifs_small_buf_release(smb_read_data);
 | |
| 		else if(buf_type == CIFS_LARGE_BUFFER)
 | |
| 			cifs_buf_release(smb_read_data);
 | |
| 		smb_read_data = NULL;
 | |
| 	} 
 | |
| 
 | |
| 	FreeXid(xid);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| static int cifs_readpage_worker(struct file *file, struct page *page,
 | |
| 	loff_t *poffset)
 | |
| {
 | |
| 	char *read_data;
 | |
| 	int rc;
 | |
| 
 | |
| 	page_cache_get(page);
 | |
| 	read_data = kmap(page);
 | |
| 	/* for reads over a certain size could initiate async read ahead */
 | |
|                                                                                                                            
 | |
| 	rc = cifs_read(file, read_data, PAGE_CACHE_SIZE, poffset);
 | |
|                                                                                                                            
 | |
| 	if (rc < 0)
 | |
| 		goto io_error;
 | |
| 	else
 | |
| 		cFYI(1, ("Bytes read %d",rc));
 | |
|                                                                                                                            
 | |
| 	file->f_dentry->d_inode->i_atime =
 | |
| 		current_fs_time(file->f_dentry->d_inode->i_sb);
 | |
|                                                                                                                            
 | |
| 	if (PAGE_CACHE_SIZE > rc)
 | |
| 		memset(read_data + rc, 0, PAGE_CACHE_SIZE - rc);
 | |
| 
 | |
| 	flush_dcache_page(page);
 | |
| 	SetPageUptodate(page);
 | |
| 	rc = 0;
 | |
|                                                                                                                            
 | |
| io_error:
 | |
|         kunmap(page);
 | |
| 	page_cache_release(page);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| static int cifs_readpage(struct file *file, struct page *page)
 | |
| {
 | |
| 	loff_t offset = (loff_t)page->index << PAGE_CACHE_SHIFT;
 | |
| 	int rc = -EACCES;
 | |
| 	int xid;
 | |
| 
 | |
| 	xid = GetXid();
 | |
| 
 | |
| 	if (file->private_data == NULL) {
 | |
| 		FreeXid(xid);
 | |
| 		return -EBADF;
 | |
| 	}
 | |
| 
 | |
| 	cFYI(1, ("readpage %p at offset %d 0x%x\n", 
 | |
| 		 page, (int)offset, (int)offset));
 | |
| 
 | |
| 	rc = cifs_readpage_worker(file, page, &offset);
 | |
| 
 | |
| 	unlock_page(page);
 | |
| 
 | |
| 	FreeXid(xid);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| /* We do not want to update the file size from server for inodes
 | |
|    open for write - to avoid races with writepage extending
 | |
|    the file - in the future we could consider allowing
 | |
|    refreshing the inode only on increases in the file size 
 | |
|    but this is tricky to do without racing with writebehind
 | |
|    page caching in the current Linux kernel design */
 | |
| int is_size_safe_to_change(struct cifsInodeInfo *cifsInode)
 | |
| {
 | |
| 	struct cifsFileInfo *open_file = NULL;
 | |
| 
 | |
| 	if (cifsInode)
 | |
| 		open_file =  find_writable_file(cifsInode);
 | |
|  
 | |
| 	if(open_file) {
 | |
| 		struct cifs_sb_info *cifs_sb;
 | |
| 
 | |
| 		/* there is not actually a write pending so let
 | |
| 		this handle go free and allow it to
 | |
| 		be closable if needed */
 | |
| 		atomic_dec(&open_file->wrtPending);
 | |
| 
 | |
| 		cifs_sb = CIFS_SB(cifsInode->vfs_inode.i_sb);
 | |
| 		if ( cifs_sb->mnt_cifs_flags & CIFS_MOUNT_DIRECT_IO ) {
 | |
| 			/* since no page cache to corrupt on directio 
 | |
| 			we can change size safely */
 | |
| 			return 1;
 | |
| 		}
 | |
| 
 | |
| 		return 0;
 | |
| 	} else
 | |
| 		return 1;
 | |
| }
 | |
| 
 | |
| static int cifs_prepare_write(struct file *file, struct page *page,
 | |
| 	unsigned from, unsigned to)
 | |
| {
 | |
| 	int rc = 0;
 | |
|         loff_t offset = (loff_t)page->index << PAGE_CACHE_SHIFT;
 | |
| 	cFYI(1, ("prepare write for page %p from %d to %d",page,from,to));
 | |
| 	if (!PageUptodate(page)) {
 | |
| 	/*	if (to - from != PAGE_CACHE_SIZE) {
 | |
| 			void *kaddr = kmap_atomic(page, KM_USER0);
 | |
| 			memset(kaddr, 0, from);
 | |
| 			memset(kaddr + to, 0, PAGE_CACHE_SIZE - to);
 | |
| 			flush_dcache_page(page);
 | |
| 			kunmap_atomic(kaddr, KM_USER0);
 | |
| 		} */
 | |
| 		/* If we are writing a full page it will be up to date,
 | |
| 		   no need to read from the server */
 | |
| 		if ((to == PAGE_CACHE_SIZE) && (from == 0))
 | |
| 			SetPageUptodate(page);
 | |
| 
 | |
| 		/* might as well read a page, it is fast enough */
 | |
| 		if ((file->f_flags & O_ACCMODE) != O_WRONLY) {
 | |
| 			rc = cifs_readpage_worker(file, page, &offset);
 | |
| 		} else {
 | |
| 		/* should we try using another file handle if there is one -
 | |
| 		   how would we lock it to prevent close of that handle
 | |
| 		   racing with this read?
 | |
| 		   In any case this will be written out by commit_write */
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* BB should we pass any errors back? 
 | |
| 	   e.g. if we do not have read access to the file */
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| const struct address_space_operations cifs_addr_ops = {
 | |
| 	.readpage = cifs_readpage,
 | |
| 	.readpages = cifs_readpages,
 | |
| 	.writepage = cifs_writepage,
 | |
| 	.writepages = cifs_writepages,
 | |
| 	.prepare_write = cifs_prepare_write,
 | |
| 	.commit_write = cifs_commit_write,
 | |
| 	.set_page_dirty = __set_page_dirty_nobuffers,
 | |
| 	/* .sync_page = cifs_sync_page, */
 | |
| 	/* .direct_IO = */
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * cifs_readpages requires the server to support a buffer large enough to
 | |
|  * contain the header plus one complete page of data.  Otherwise, we need
 | |
|  * to leave cifs_readpages out of the address space operations.
 | |
|  */
 | |
| const struct address_space_operations cifs_addr_ops_smallbuf = {
 | |
| 	.readpage = cifs_readpage,
 | |
| 	.writepage = cifs_writepage,
 | |
| 	.writepages = cifs_writepages,
 | |
| 	.prepare_write = cifs_prepare_write,
 | |
| 	.commit_write = cifs_commit_write,
 | |
| 	.set_page_dirty = __set_page_dirty_nobuffers,
 | |
| 	/* .sync_page = cifs_sync_page, */
 | |
| 	/* .direct_IO = */
 | |
| };
 |