summaryrefslogtreecommitdiffstats
path: root/fs/cifs
diff options
context:
space:
mode:
Diffstat (limited to 'fs/cifs')
-rw-r--r--fs/cifs/CHANGES9
-rw-r--r--fs/cifs/README19
-rw-r--r--fs/cifs/cifsfs.c42
-rw-r--r--fs/cifs/cifsfs.h2
-rw-r--r--fs/cifs/cifsglob.h2
-rw-r--r--fs/cifs/cifssmb.c2
-rw-r--r--fs/cifs/connect.c94
-rw-r--r--fs/cifs/inode.c211
-rw-r--r--fs/cifs/readdir.c3
9 files changed, 259 insertions, 125 deletions
diff --git a/fs/cifs/CHANGES b/fs/cifs/CHANGES
index 06e521a945c3..8f528ea24c48 100644
--- a/fs/cifs/CHANGES
+++ b/fs/cifs/CHANGES
@@ -1,3 +1,11 @@
+Version 1.55
+------------
+Various fixes to make delete of open files behavior more predictable
+(when delete of an open file fails we mark the file as "delete-on-close"
+in a way that more servers accept, but only if we can first rename the
+file to a temporary name). Add experimental support for more safely
+handling fcntl(F_SETLEASE).
+
Version 1.54
------------
Fix premature write failure on congested networks (we would give up
@@ -13,6 +21,7 @@ on dns_upcall (resolving DFS referralls). Fix plain text password
authentication (requires setting SecurityFlags to 0x30030 to enable
lanman and plain text though). Fix writes to be at correct offset when
file is open with O_APPEND and file is on a directio (forcediretio) mount.
+Fix bug in rewinding readdir directory searches. Add nodfs mount option.
Version 1.53
------------
diff --git a/fs/cifs/README b/fs/cifs/README
index bd2343d4c6a6..a439dc1739b3 100644
--- a/fs/cifs/README
+++ b/fs/cifs/README
@@ -463,6 +463,9 @@ A partial list of the supported mount options follows:
with cifs style mandatory byte range locks (and most
cifs servers do not yet support requesting advisory
byte range locks).
+ nodfs Disable DFS (global name space support) even if the
+ server claims to support it. This can help work around
+ a problem with parsing of DFS paths with Samba 3.0.24 server.
remount remount the share (often used to change from ro to rw mounts
or vice versa)
cifsacl Report mode bits (e.g. on stat) based on the Windows ACL for
@@ -488,6 +491,19 @@ A partial list of the supported mount options follows:
Note that this differs from the sign mount option in that it
causes encryption of data sent over this mounted share but other
shares mounted to the same server are unaffected.
+ locallease This option is rarely needed. Fcntl F_SETLEASE is
+ used by some applications such as Samba and NFSv4 server to
+ check to see whether a file is cacheable. CIFS has no way
+ to explicitly request a lease, but can check whether a file
+ is cacheable (oplocked). Unfortunately, even if a file
+ is not oplocked, it could still be cacheable (ie cifs client
+ could grant fcntl leases if no other local processes are using
+ the file) for cases for example such as when the server does not
+ support oplocks and the user is sure that the only updates to
+ the file will be from this client. Specifying this mount option
+ will allow the cifs client to check for leases (only) locally
+ for files which are not oplocked instead of denying leases
+ in that case. (EXPERIMENTAL)
sec Security mode. Allowed values are:
none attempt to connection as a null user (no name)
krb5 Use Kerberos version 5 authentication
@@ -638,6 +654,9 @@ requires enabling CONFIG_CIFS_EXPERIMENTAL
cifsacl support needed to retrieve approximated mode bits based on
the contents on the CIFS ACL.
+ lease support: cifs will check the oplock state before calling into
+ the vfs to see if we can grant a lease on a file.
+
DNOTIFY fcntl: needed for support of directory change
notification and perhaps later for file leases)
diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
index 84cc011a16e4..ac5915d61dca 100644
--- a/fs/cifs/cifsfs.c
+++ b/fs/cifs/cifsfs.c
@@ -312,6 +312,7 @@ cifs_alloc_inode(struct super_block *sb)
file data or metadata */
cifs_inode->clientCanCacheRead = false;
cifs_inode->clientCanCacheAll = false;
+ cifs_inode->delete_pending = false;
cifs_inode->vfs_inode.i_blkbits = 14; /* 2**14 = CIFS_MAX_MSGSIZE */
/* Can not set i_flags here - they get immediately overwritten
@@ -620,6 +621,37 @@ static loff_t cifs_llseek(struct file *file, loff_t offset, int origin)
return generic_file_llseek_unlocked(file, offset, origin);
}
+#ifdef CONFIG_CIFS_EXPERIMENTAL
+static int cifs_setlease(struct file *file, long arg, struct file_lock **lease)
+{
+ /* note that this is called by vfs setlease with the BKL held
+ although I doubt that BKL is needed here in cifs */
+ struct inode *inode = file->f_path.dentry->d_inode;
+
+ if (!(S_ISREG(inode->i_mode)))
+ return -EINVAL;
+
+ /* check if file is oplocked */
+ if (((arg == F_RDLCK) &&
+ (CIFS_I(inode)->clientCanCacheRead)) ||
+ ((arg == F_WRLCK) &&
+ (CIFS_I(inode)->clientCanCacheAll)))
+ return generic_setlease(file, arg, lease);
+ else if (CIFS_SB(inode->i_sb)->tcon->local_lease &&
+ !CIFS_I(inode)->clientCanCacheRead)
+ /* If the server claims to support oplock on this
+ file, then we still need to check oplock even
+ if the local_lease mount option is set, but there
+ are servers which do not support oplock for which
+ this mount option may be useful if the user
+ knows that the file won't be changed on the server
+ by anyone else */
+ return generic_setlease(file, arg, lease);
+ else
+ return -EAGAIN;
+}
+#endif
+
struct file_system_type cifs_fs_type = {
.owner = THIS_MODULE,
.name = "cifs",
@@ -698,6 +730,7 @@ const struct file_operations cifs_file_ops = {
#ifdef CONFIG_CIFS_EXPERIMENTAL
.dir_notify = cifs_dir_notify,
+ .setlease = cifs_setlease,
#endif /* CONFIG_CIFS_EXPERIMENTAL */
};
@@ -718,6 +751,7 @@ const struct file_operations cifs_file_direct_ops = {
.llseek = cifs_llseek,
#ifdef CONFIG_CIFS_EXPERIMENTAL
.dir_notify = cifs_dir_notify,
+ .setlease = cifs_setlease,
#endif /* CONFIG_CIFS_EXPERIMENTAL */
};
const struct file_operations cifs_file_nobrl_ops = {
@@ -738,6 +772,7 @@ const struct file_operations cifs_file_nobrl_ops = {
#ifdef CONFIG_CIFS_EXPERIMENTAL
.dir_notify = cifs_dir_notify,
+ .setlease = cifs_setlease,
#endif /* CONFIG_CIFS_EXPERIMENTAL */
};
@@ -757,6 +792,7 @@ const struct file_operations cifs_file_direct_nobrl_ops = {
.llseek = cifs_llseek,
#ifdef CONFIG_CIFS_EXPERIMENTAL
.dir_notify = cifs_dir_notify,
+ .setlease = cifs_setlease,
#endif /* CONFIG_CIFS_EXPERIMENTAL */
};
@@ -949,6 +985,12 @@ static int cifs_oplock_thread(void *dummyarg)
the call */
/* mutex_lock(&inode->i_mutex);*/
if (S_ISREG(inode->i_mode)) {
+#ifdef CONFIG_CIFS_EXPERIMENTAL
+ if (CIFS_I(inode)->clientCanCacheAll == 0)
+ break_lease(inode, FMODE_READ);
+ else if (CIFS_I(inode)->clientCanCacheRead == 0)
+ break_lease(inode, FMODE_WRITE);
+#endif
rc = filemap_fdatawrite(inode->i_mapping);
if (CIFS_I(inode)->clientCanCacheRead == 0) {
waitrc = filemap_fdatawait(
diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h
index f7b4a5cd837b..074de0b5064d 100644
--- a/fs/cifs/cifsfs.h
+++ b/fs/cifs/cifsfs.h
@@ -101,5 +101,5 @@ extern long cifs_ioctl(struct file *filep, unsigned int cmd, unsigned long arg);
extern const struct export_operations cifs_export_ops;
#endif /* EXPERIMENTAL */
-#define CIFS_VERSION "1.54"
+#define CIFS_VERSION "1.55"
#endif /* _CIFSFS_H */
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index 0d22479d99b7..c791e5b5a914 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -285,6 +285,7 @@ struct cifsTconInfo {
bool seal:1; /* transport encryption for this mounted share */
bool unix_ext:1; /* if false disable Linux extensions to CIFS protocol
for this mount even if server would support */
+ bool local_lease:1; /* check leases (only) on local system not remote */
/* BB add field for back pointer to sb struct(s)? */
};
@@ -353,6 +354,7 @@ struct cifsInodeInfo {
bool clientCanCacheRead:1; /* read oplock */
bool clientCanCacheAll:1; /* read and writebehind oplock */
bool oplockPending:1;
+ bool delete_pending:1; /* DELETE_ON_CLOSE is set */
struct inode vfs_inode;
};
diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c
index 6f4ffe15d68d..843a85fb8b9a 100644
--- a/fs/cifs/cifssmb.c
+++ b/fs/cifs/cifssmb.c
@@ -1309,6 +1309,7 @@ OldOpenRetry:
cpu_to_le64(le32_to_cpu(pSMBr->EndOfFile));
pfile_info->EndOfFile = pfile_info->AllocationSize;
pfile_info->NumberOfLinks = cpu_to_le32(1);
+ pfile_info->DeletePending = 0;
}
}
@@ -1410,6 +1411,7 @@ openRetry:
pfile_info->AllocationSize = pSMBr->AllocationSize;
pfile_info->EndOfFile = pSMBr->EndOfFile;
pfile_info->NumberOfLinks = cpu_to_le32(1);
+ pfile_info->DeletePending = 0;
}
}
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 4c13bcdb92a5..71b7661e2260 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -90,6 +90,8 @@ struct smb_vol {
bool nocase:1; /* request case insensitive filenames */
bool nobrl:1; /* disable sending byte range locks to srv */
bool seal:1; /* request transport encryption on share */
+ bool nodfs:1; /* Do not request DFS, even if available */
+ bool local_lease:1; /* check leases only on local system, not remote */
unsigned int rsize;
unsigned int wsize;
unsigned int sockopt;
@@ -124,7 +126,7 @@ cifs_reconnect(struct TCP_Server_Info *server)
struct mid_q_entry *mid_entry;
spin_lock(&GlobalMid_Lock);
- if (kthread_should_stop()) {
+ if (server->tcpStatus == CifsExiting) {
/* the demux thread will exit normally
next time through the loop */
spin_unlock(&GlobalMid_Lock);
@@ -184,7 +186,8 @@ cifs_reconnect(struct TCP_Server_Info *server)
spin_unlock(&GlobalMid_Lock);
up(&server->tcpSem);
- while ((!kthread_should_stop()) && (server->tcpStatus != CifsGood)) {
+ while ((server->tcpStatus != CifsExiting) &&
+ (server->tcpStatus != CifsGood)) {
try_to_freeze();
if (server->protocolType == IPV6) {
rc = ipv6_connect(&server->addr.sockAddr6,
@@ -201,7 +204,7 @@ cifs_reconnect(struct TCP_Server_Info *server)
} else {
atomic_inc(&tcpSesReconnectCount);
spin_lock(&GlobalMid_Lock);
- if (!kthread_should_stop())
+ if (server->tcpStatus != CifsExiting)
server->tcpStatus = CifsGood;
server->sequence_number = 0;
spin_unlock(&GlobalMid_Lock);
@@ -356,7 +359,7 @@ cifs_demultiplex_thread(struct TCP_Server_Info *server)
GFP_KERNEL);
set_freezable();
- while (!kthread_should_stop()) {
+ while (server->tcpStatus != CifsExiting) {
if (try_to_freeze())
continue;
if (bigbuf == NULL) {
@@ -397,7 +400,7 @@ incomplete_rcv:
kernel_recvmsg(csocket, &smb_msg,
&iov, 1, pdu_length, 0 /* BB other flags? */);
- if (kthread_should_stop()) {
+ if (server->tcpStatus == CifsExiting) {
break;
} else if (server->tcpStatus == CifsNeedReconnect) {
cFYI(1, ("Reconnect after server stopped responding"));
@@ -522,7 +525,7 @@ incomplete_rcv:
total_read += length) {
length = kernel_recvmsg(csocket, &smb_msg, &iov, 1,
pdu_length - total_read, 0);
- if (kthread_should_stop() ||
+ if ((server->tcpStatus == CifsExiting) ||
(length == -EINTR)) {
/* then will exit */
reconnect = 2;
@@ -651,14 +654,6 @@ multi_t2_fnd:
spin_unlock(&GlobalMid_Lock);
wake_up_all(&server->response_q);
- /* don't exit until kthread_stop is called */
- set_current_state(TASK_UNINTERRUPTIBLE);
- while (!kthread_should_stop()) {
- schedule();
- set_current_state(TASK_UNINTERRUPTIBLE);
- }
- set_current_state(TASK_RUNNING);
-
/* check if we have blocked requests that need to free */
/* Note that cifs_max_pending is normally 50, but
can be set at module install time to as little as two */
@@ -755,6 +750,7 @@ multi_t2_fnd:
write_unlock(&GlobalSMBSeslock);
kfree(server->hostname);
+ task_to_wake = xchg(&server->tsk, NULL);
kfree(server);
length = atomic_dec_return(&tcpSesAllocCount);
@@ -762,6 +758,16 @@ multi_t2_fnd:
mempool_resize(cifs_req_poolp, length + cifs_min_rcv,
GFP_KERNEL);
+ /* if server->tsk was NULL then wait for a signal before exiting */
+ if (!task_to_wake) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ while (!signal_pending(current)) {
+ schedule();
+ set_current_state(TASK_INTERRUPTIBLE);
+ }
+ set_current_state(TASK_RUNNING);
+ }
+
return 0;
}
@@ -1218,6 +1224,8 @@ cifs_parse_mount_options(char *options, const char *devname,
vol->sfu_emul = 1;
} else if (strnicmp(data, "nosfu", 5) == 0) {
vol->sfu_emul = 0;
+ } else if (strnicmp(data, "nodfs", 5) == 0) {
+ vol->nodfs = 1;
} else if (strnicmp(data, "posixpaths", 10) == 0) {
vol->posix_paths = 1;
} else if (strnicmp(data, "noposixpaths", 12) == 0) {
@@ -1268,6 +1276,10 @@ cifs_parse_mount_options(char *options, const char *devname,
vol->no_psx_acl = 0;
} else if (strnicmp(data, "noacl", 5) == 0) {
vol->no_psx_acl = 1;
+#ifdef CONFIG_CIFS_EXPERIMENTAL
+ } else if (strnicmp(data, "locallease", 6) == 0) {
+ vol->local_lease = 1;
+#endif
} else if (strnicmp(data, "sign", 4) == 0) {
vol->secFlg |= CIFSSEC_MUST_SIGN;
} else if (strnicmp(data, "seal", 4) == 0) {
@@ -1845,6 +1857,16 @@ convert_delimiter(char *path, char delim)
}
}
+static void
+kill_cifsd(struct TCP_Server_Info *server)
+{
+ struct task_struct *task;
+
+ task = xchg(&server->tsk, NULL);
+ if (task)
+ force_sig(SIGKILL, task);
+}
+
int
cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
char *mount_data, const char *devname)
@@ -2166,6 +2188,7 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
for the retry flag is used */
tcon->retry = volume_info.retry;
tcon->nocase = volume_info.nocase;
+ tcon->local_lease = volume_info.local_lease;
if (tcon->seal != volume_info.seal)
cERROR(1, ("transport encryption setting "
"conflicts with existing tid"));
@@ -2197,6 +2220,12 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
volume_info.UNC,
tcon, cifs_sb->local_nls);
cFYI(1, ("CIFS Tcon rc = %d", rc));
+ if (volume_info.nodfs) {
+ tcon->Flags &=
+ ~SMB_SHARE_IS_IN_DFS;
+ cFYI(1, ("DFS disabled (%d)",
+ tcon->Flags));
+ }
}
if (!rc) {
atomic_inc(&pSesInfo->inUse);
@@ -2225,14 +2254,7 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
spin_lock(&GlobalMid_Lock);
srvTcp->tcpStatus = CifsExiting;
spin_unlock(&GlobalMid_Lock);
- if (srvTcp->tsk) {
- /* If we could verify that kthread_stop would
- always wake up processes blocked in
- tcp in recv_mesg then we could remove the
- send_sig call */
- force_sig(SIGKILL, srvTcp->tsk);
- kthread_stop(srvTcp->tsk);
- }
+ kill_cifsd(srvTcp);
}
/* If find_unc succeeded then rc == 0 so we can not end */
if (tcon) /* up accidently freeing someone elses tcon struct */
@@ -2245,19 +2267,15 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
temp_rc = CIFSSMBLogoff(xid, pSesInfo);
/* if the socketUseCount is now zero */
if ((temp_rc == -ESHUTDOWN) &&
- (pSesInfo->server) &&
- (pSesInfo->server->tsk)) {
- force_sig(SIGKILL,
- pSesInfo->server->tsk);
- kthread_stop(pSesInfo->server->tsk);
- }
+ (pSesInfo->server))
+ kill_cifsd(pSesInfo->server);
} else {
cFYI(1, ("No session or bad tcon"));
- if ((pSesInfo->server) &&
- (pSesInfo->server->tsk)) {
- force_sig(SIGKILL,
- pSesInfo->server->tsk);
- kthread_stop(pSesInfo->server->tsk);
+ if (pSesInfo->server) {
+ spin_lock(&GlobalMid_Lock);
+ srvTcp->tcpStatus = CifsExiting;
+ spin_unlock(&GlobalMid_Lock);
+ kill_cifsd(pSesInfo->server);
}
}
sesInfoFree(pSesInfo);
@@ -3544,7 +3562,6 @@ cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb)
int rc = 0;
int xid;
struct cifsSesInfo *ses = NULL;
- struct task_struct *cifsd_task;
char *tmp;
xid = GetXid();
@@ -3560,7 +3577,6 @@ cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb)
tconInfoFree(cifs_sb->tcon);
if ((ses) && (ses->server)) {
/* save off task so we do not refer to ses later */
- cifsd_task = ses->server->tsk;
cFYI(1, ("About to do SMBLogoff "));
rc = CIFSSMBLogoff(xid, ses);
if (rc == -EBUSY) {
@@ -3568,10 +3584,8 @@ cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb)
return 0;
} else if (rc == -ESHUTDOWN) {
cFYI(1, ("Waking up socket by sending signal"));
- if (cifsd_task) {
- force_sig(SIGKILL, cifsd_task);
- kthread_stop(cifsd_task);
- }
+ if (ses->server)
+ kill_cifsd(ses->server);
rc = 0;
} /* else - we have an smb session
left on this socket do not kill cifsd */
@@ -3701,7 +3715,9 @@ int cifs_setup_session(unsigned int xid, struct cifsSesInfo *pSesInfo,
cERROR(1, ("Send error in SessSetup = %d", rc));
} else {
cFYI(1, ("CIFS Session Established successfully"));
+ spin_lock(&GlobalMid_Lock);
pSesInfo->status = CifsGood;
+ spin_unlock(&GlobalMid_Lock);
}
ss_err_exit:
diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c
index a8c833345fc9..d54fa8aeaea9 100644
--- a/fs/cifs/inode.c
+++ b/fs/cifs/inode.c
@@ -506,6 +506,7 @@ int cifs_get_inode_info(struct inode **pinode,
inode = *pinode;
cifsInfo = CIFS_I(inode);
cifsInfo->cifsAttrs = attr;
+ cifsInfo->delete_pending = pfindData->DeletePending ? true : false;
cFYI(1, ("Old time %ld", cifsInfo->time));
cifsInfo->time = jiffies;
cFYI(1, ("New time %ld", cifsInfo->time));
@@ -772,63 +773,106 @@ out:
* anything else.
*/
static int
-cifs_rename_pending_delete(char *full_path, struct inode *inode, int xid)
+cifs_rename_pending_delete(char *full_path, struct dentry *dentry, int xid)
{
int oplock = 0;
int rc;
__u16 netfid;
+ struct inode *inode = dentry->d_inode;
struct cifsInodeInfo *cifsInode = CIFS_I(inode);
struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
struct cifsTconInfo *tcon = cifs_sb->tcon;
- __u32 dosattr;
- FILE_BASIC_INFO *info_buf;
+ __u32 dosattr, origattr;
+ FILE_BASIC_INFO *info_buf = NULL;
rc = CIFSSMBOpen(xid, tcon, full_path, FILE_OPEN,
- DELETE|FILE_WRITE_ATTRIBUTES,
- CREATE_NOT_DIR|CREATE_DELETE_ON_CLOSE,
+ DELETE|FILE_WRITE_ATTRIBUTES, CREATE_NOT_DIR,
&netfid, &oplock, NULL, cifs_sb->local_nls,
cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
if (rc != 0)
goto out;
- /* set ATTR_HIDDEN and clear ATTR_READONLY */
- cifsInode = CIFS_I(inode);
- dosattr = cifsInode->cifsAttrs & ~ATTR_READONLY;
+ origattr = cifsInode->cifsAttrs;
+ if (origattr == 0)
+ origattr |= ATTR_NORMAL;
+
+ dosattr = origattr & ~ATTR_READONLY;
if (dosattr == 0)
dosattr |= ATTR_NORMAL;
dosattr |= ATTR_HIDDEN;
- info_buf = kzalloc(sizeof(*info_buf), GFP_KERNEL);
- if (info_buf == NULL) {
- rc = -ENOMEM;
- goto out_close;
+ /* set ATTR_HIDDEN and clear ATTR_READONLY, but only if needed */
+ if (dosattr != origattr) {
+ info_buf = kzalloc(sizeof(*info_buf), GFP_KERNEL);
+ if (info_buf == NULL) {
+ rc = -ENOMEM;
+ goto out_close;
+ }
+ info_buf->Attributes = cpu_to_le32(dosattr);
+ rc = CIFSSMBSetFileInfo(xid, tcon, info_buf, netfid,
+ current->tgid);
+ /* although we would like to mark the file hidden
+ if that fails we will still try to rename it */
+ if (rc != 0)
+ cifsInode->cifsAttrs = dosattr;
+ else
+ dosattr = origattr; /* since not able to change them */
}
- info_buf->Attributes = cpu_to_le32(dosattr);
- rc = CIFSSMBSetFileInfo(xid, tcon, info_buf, netfid, current->tgid);
- kfree(info_buf);
- if (rc != 0)
- goto out_close;
- cifsInode->cifsAttrs = dosattr;
- /* silly-rename the file */
- CIFSSMBRenameOpenFile(xid, tcon, netfid, NULL, cifs_sb->local_nls,
+ /* rename the file */
+ rc = CIFSSMBRenameOpenFile(xid, tcon, netfid, NULL, cifs_sb->local_nls,
cifs_sb->mnt_cifs_flags &
CIFS_MOUNT_MAP_SPECIAL_CHR);
+ if (rc != 0) {
+ rc = -ETXTBSY;
+ goto undo_setattr;
+ }
- /* set DELETE_ON_CLOSE */
- rc = CIFSSMBSetFileDisposition(xid, tcon, true, netfid, current->tgid);
-
- /*
- * some samba versions return -ENOENT when we try to set the file
- * disposition here. Likely a samba bug, but work around it for now
- */
- if (rc == -ENOENT)
- rc = 0;
+ /* try to set DELETE_ON_CLOSE */
+ if (!cifsInode->delete_pending) {
+ rc = CIFSSMBSetFileDisposition(xid, tcon, true, netfid,
+ current->tgid);
+ /*
+ * some samba versions return -ENOENT when we try to set the
+ * file disposition here. Likely a samba bug, but work around
+ * it for now. This means that some cifsXXX files may hang
+ * around after they shouldn't.
+ *
+ * BB: remove this hack after more servers have the fix
+ */
+ if (rc == -ENOENT)
+ rc = 0;
+ else if (rc != 0) {
+ rc = -ETXTBSY;
+ goto undo_rename;
+ }
+ cifsInode->delete_pending = true;
+ }
out_close:
CIFSSMBClose(xid, tcon, netfid);
out:
+ kfree(info_buf);
return rc;
+
+ /*
+ * reset everything back to the original state. Don't bother
+ * dealing with errors here since we can't do anything about
+ * them anyway.
+ */
+undo_rename:
+ CIFSSMBRenameOpenFile(xid, tcon, netfid, dentry->d_name.name,
+ cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
+ CIFS_MOUNT_MAP_SPECIAL_CHR);
+undo_setattr:
+ if (dosattr != origattr) {
+ info_buf->Attributes = cpu_to_le32(origattr);
+ if (!CIFSSMBSetFileInfo(xid, tcon, info_buf, netfid,
+ current->tgid))
+ cifsInode->cifsAttrs = origattr;
+ }
+
+ goto out_close;
}
int cifs_unlink(struct inode *dir, struct dentry *dentry)
@@ -878,7 +922,7 @@ psx_del_no_retry:
} else if (rc == -ENOENT) {
d_drop(dentry);
} else if (rc == -ETXTBSY) {
- rc = cifs_rename_pending_delete(full_path, inode, xid);
+ rc = cifs_rename_pending_delete(full_path, dentry, xid);
if (rc == 0)
drop_nlink(inode);
} else if (rc == -EACCES && dosattr == 0) {
@@ -1241,22 +1285,21 @@ cifs_do_rename(int xid, struct dentry *from_dentry, const char *fromPath,
return rc;
}
-int cifs_rename(struct inode *source_inode, struct dentry *source_direntry,
- struct inode *target_inode, struct dentry *target_direntry)
+int cifs_rename(struct inode *source_dir, struct dentry *source_dentry,
+ struct inode *target_dir, struct dentry *target_dentry)
{
char *fromName = NULL;
char *toName = NULL;
struct cifs_sb_info *cifs_sb_source;
struct cifs_sb_info *cifs_sb_target;
- struct cifsTconInfo *pTcon;
+ struct cifsTconInfo *tcon;
FILE_UNIX_BASIC_INFO *info_buf_source = NULL;
FILE_UNIX_BASIC_INFO *info_buf_target;
- int xid;
- int rc;
+ int xid, rc, tmprc;
- cifs_sb_target = CIFS_SB(target_inode->i_sb);
- cifs_sb_source = CIFS_SB(source_inode->i_sb);
- pTcon = cifs_sb_source->tcon;
+ cifs_sb_target = CIFS_SB(target_dir->i_sb);
+ cifs_sb_source = CIFS_SB(source_dir->i_sb);
+ tcon = cifs_sb_source->tcon;
xid = GetXid();
@@ -1264,7 +1307,7 @@ int cifs_rename(struct inode *source_inode, struct dentry *source_direntry,
* BB: this might be allowed if same server, but different share.
* Consider adding support for this
*/
- if (pTcon != cifs_sb_target->tcon) {
+ if (tcon != cifs_sb_target->tcon) {
rc = -EXDEV;
goto cifs_rename_exit;
}
@@ -1273,65 +1316,65 @@ int cifs_rename(struct inode *source_inode, struct dentry *source_direntry,
* we already have the rename sem so we do not need to
* grab it again here to protect the path integrity
*/
- fromName = build_path_from_dentry(source_direntry);
+ fromName = build_path_from_dentry(source_dentry);
if (fromName == NULL) {
rc = -ENOMEM;
goto cifs_rename_exit;
}
- toName = build_path_from_dentry(target_direntry);
+ toName = build_path_from_dentry(target_dentry);
if (toName == NULL) {
rc = -ENOMEM;
goto cifs_rename_exit;
}
- rc = cifs_do_rename(xid, source_direntry, fromName,
- target_direntry, toName);
+ rc = cifs_do_rename(xid, source_dentry, fromName,
+ target_dentry, toName);
- if (rc == -EEXIST) {
- if (pTcon->unix_ext) {
- /*
- * Are src and dst hardlinks of same inode? We can
- * only tell with unix extensions enabled
- */
- info_buf_source =
- kmalloc(2 * sizeof(FILE_UNIX_BASIC_INFO),
- GFP_KERNEL);
- if (info_buf_source == NULL)
- goto unlink_target;
-
- info_buf_target = info_buf_source + 1;
- rc = CIFSSMBUnixQPathInfo(xid, pTcon, fromName,
- info_buf_source,
- cifs_sb_source->local_nls,
- cifs_sb_source->mnt_cifs_flags &
- CIFS_MOUNT_MAP_SPECIAL_CHR);
- if (rc != 0)
- goto unlink_target;
-
- rc = CIFSSMBUnixQPathInfo(xid, pTcon,
- toName, info_buf_target,
- cifs_sb_target->local_nls,
- /* remap based on source sb */
- cifs_sb_source->mnt_cifs_flags &
- CIFS_MOUNT_MAP_SPECIAL_CHR);
+ if (rc == -EEXIST && tcon->unix_ext) {
+ /*
+ * Are src and dst hardlinks of same inode? We can
+ * only tell with unix extensions enabled
+ */
+ info_buf_source =
+ kmalloc(2 * sizeof(FILE_UNIX_BASIC_INFO),
+ GFP_KERNEL);
+ if (info_buf_source == NULL) {
+ rc = -ENOMEM;
+ goto cifs_rename_exit;
+ }
- if (rc == 0 && (info_buf_source->UniqueId ==
- info_buf_target->UniqueId))
- /* same file, POSIX says that this is a noop */
- goto cifs_rename_exit;
- } /* else ... BB we could add the same check for Windows by
+ info_buf_target = info_buf_source + 1;
+ tmprc = CIFSSMBUnixQPathInfo(xid, tcon, fromName,
+ info_buf_source,
+ cifs_sb_source->local_nls,
+ cifs_sb_source->mnt_cifs_flags &
+ CIFS_MOUNT_MAP_SPECIAL_CHR);
+ if (tmprc != 0)
+ goto unlink_target;
+
+ tmprc = CIFSSMBUnixQPathInfo(xid, tcon,
+ toName, info_buf_target,
+ cifs_sb_target->local_nls,
+ /* remap based on source sb */
+ cifs_sb_source->mnt_cifs_flags &
+ CIFS_MOUNT_MAP_SPECIAL_CHR);
+
+ if (tmprc == 0 && (info_buf_source->UniqueId ==
+ info_buf_target->UniqueId))
+ /* same file, POSIX says that this is a noop */
+ goto cifs_rename_exit;
+ } /* else ... BB we could add the same check for Windows by
checking the UniqueId via FILE_INTERNAL_INFO */
+
unlink_target:
- /*
- * we either can not tell the files are hardlinked (as with
- * Windows servers) or files are not hardlinked. Delete the
- * target manually before renaming to follow POSIX rather than
- * Windows semantics
- */
- cifs_unlink(target_inode, target_direntry);
- rc = cifs_do_rename(xid, source_direntry, fromName,
- target_direntry, toName);
+ if ((rc == -EACCES) || (rc == -EEXIST)) {
+ tmprc = cifs_unlink(target_dir, target_dentry);
+ if (tmprc)
+ goto cifs_rename_exit;
+
+ rc = cifs_do_rename(xid, source_dentry, fromName,
+ target_dentry, toName);
}
cifs_rename_exit:
diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c
index 765adf12d54f..58d57299f2a0 100644
--- a/fs/cifs/readdir.c
+++ b/fs/cifs/readdir.c
@@ -762,14 +762,15 @@ static int find_cifs_entry(const int xid, struct cifsTconInfo *pTcon,
rc));
return rc;
}
+ cifs_save_resume_key(cifsFile->srch_inf.last_entry, cifsFile);
}
while ((index_to_find >= cifsFile->srch_inf.index_of_last_entry) &&
(rc == 0) && !cifsFile->srch_inf.endOfSearch) {
cFYI(1, ("calling findnext2"));
- cifs_save_resume_key(cifsFile->srch_inf.last_entry, cifsFile);
rc = CIFSFindNext(xid, pTcon, cifsFile->netfid,
&cifsFile->srch_inf);
+ cifs_save_resume_key(cifsFile->srch_inf.last_entry, cifsFile);
if (rc)
return -ENOENT;
}