]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
cifs patches for 2.6.27
authorGreg Kroah-Hartman <gregkh@suse.de>
Wed, 3 Dec 2008 17:51:11 +0000 (09:51 -0800)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 3 Dec 2008 17:51:11 +0000 (09:51 -0800)
12 files changed:
queue-2.6.27/cifs-clean-up-server-protocol-handling.patch [new file with mode: 0644]
queue-2.6.27/cifs-disable-sharing-session-and-tcon-and-add-new-tcp-sharing-code.patch [new file with mode: 0644]
queue-2.6.27/cifs-fix-build-break.patch [new file with mode: 0644]
queue-2.6.27/cifs-fix-check-for-dead-tcon-in-smb_init.patch [new file with mode: 0644]
queue-2.6.27/cifs-fix-check-for-tcon-seal-setting-and-fix-oops-on-failed-mount-from-earlier-patch.patch [new file with mode: 0644]
queue-2.6.27/cifs-fix-cifs-reconnection-flags.patch [new file with mode: 0644]
queue-2.6.27/cifs-minor-cleanup-to-cifs_mount.patch [new file with mode: 0644]
queue-2.6.27/cifs-prevent-cifs_writepages-from-skipping-unwritten-pages.patch [new file with mode: 0644]
queue-2.6.27/cifs-reinstate-sharing-of-smb-sessions-sans-races.patch [new file with mode: 0644]
queue-2.6.27/cifs-reinstate-sharing-of-tree-connections.patch [new file with mode: 0644]
queue-2.6.27/cifs-remove-unused-list-add-new-cifs-sock-list-to-prepare-for-mount-umount-fix.patch [new file with mode: 0644]
queue-2.6.27/series

diff --git a/queue-2.6.27/cifs-clean-up-server-protocol-handling.patch b/queue-2.6.27/cifs-clean-up-server-protocol-handling.patch
new file mode 100644 (file)
index 0000000..7b996e3
--- /dev/null
@@ -0,0 +1,205 @@
+From sjayaraman@suse.de  Wed Dec  3 09:30:05 2008
+From: Steve French <sfrench@us.ibm.com>
+Date: Fri, 21 Nov 2008 14:23:07 +0530
+Subject: cifs: clean up server protocol handling
+To: stable@kernel.org
+Cc: Steve French <smfrench@gmail.com>, Jeff Layton <jlayton@redhat.com>
+Message-ID: <492676F3.7080004@suse.de>
+
+From: Steve French <sfrench@us.ibm.com>
+
+commit 3ec332ef7a38c2327e18d087d4120a8e3bd3dc6e upstream.
+
+We're currently declaring both a sockaddr_in and sockaddr6_in on the
+stack, but we really only need storage for one of them. Declare a
+sockaddr struct and cast it to the proper type. Also, eliminate the
+protocolType field in the TCP_Server_Info struct. It's redundant since
+we have a sa_family field in the sockaddr anyway.
+
+We may need to revisit this if SCTP is ever implemented, but for now
+this will simplify the code.
+
+CIFS over IPv6 also has a number of problems currently. This fixes all
+of them that I found. Eventually, it would be nice to move more of the
+code to be protocol independent, but this is a start.
+
+Signed-off-by: Jeff Layton <jlayton@redhat.com>
+Signed-off-by: Steve French <sfrench@us.ibm.com>
+Cc: Suresh Jayaraman <sjayaraman@suse.de>
+Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
+
+
+---
+ fs/cifs/cifs_spnego.c |    3 +-
+ fs/cifs/cifsglob.h    |    3 --
+ fs/cifs/connect.c     |   57 ++++++++++++++++++++++++++------------------------
+ 3 files changed, 33 insertions(+), 30 deletions(-)
+
+--- a/fs/cifs/cifsglob.h
++++ b/fs/cifs/cifsglob.h
+@@ -85,8 +85,7 @@ enum securityEnum {
+ };
+ enum protocolEnum {
+-      IPV4 = 0,
+-      IPV6,
++      TCP = 0,
+       SCTP
+       /* Netbios frames protocol not supported at this time */
+ };
+--- a/fs/cifs/cifs_spnego.c
++++ b/fs/cifs/cifs_spnego.c
+@@ -70,7 +70,8 @@ struct key_type cifs_spnego_key_type = {
+                               strlen("ver=0xFF") */
+ #define MAX_MECH_STR_LEN 13 /* length of longest security mechanism name, eg
+                              in future could have strlen(";sec=ntlmsspi") */
+-#define MAX_IPV6_ADDR_LEN 42 /* eg FEDC:BA98:7654:3210:FEDC:BA98:7654:3210/60 */
++/* max possible addr len eg FEDC:BA98:7654:3210:FEDC:BA98:7654:3210/128 */
++#define MAX_IPV6_ADDR_LEN     43
+ /* get a key struct with a SPNEGO security blob, suitable for session setup */
+ struct key *
+ cifs_get_spnego_key(struct cifsSesInfo *sesInfo)
+--- a/fs/cifs/connect.c
++++ b/fs/cifs/connect.c
+@@ -190,7 +190,7 @@ cifs_reconnect(struct TCP_Server_Info *s
+       while ((!kthread_should_stop()) && (server->tcpStatus != CifsGood)) {
+               try_to_freeze();
+-              if (server->protocolType == IPV6) {
++              if (server->addr.sockAddr6.sin6_family == AF_INET6) {
+                       rc = ipv6_connect(&server->addr.sockAddr6,
+                                         &server->ssocket, server->noautotune);
+               } else {
+@@ -1960,10 +1960,10 @@ cifs_mount(struct super_block *sb, struc
+ {
+       int rc = 0;
+       int xid;
+-      int address_type = AF_INET;
+       struct socket *csocket = NULL;
+-      struct sockaddr_in sin_server;
+-      struct sockaddr_in6 sin_server6;
++      struct sockaddr addr;
++      struct sockaddr_in *sin_server = (struct sockaddr_in *) &addr;
++      struct sockaddr_in6 *sin_server6 = (struct sockaddr_in6 *) &addr;
+       struct smb_vol volume_info;
+       struct cifsSesInfo *pSesInfo = NULL;
+       struct cifsSesInfo *existingCifsSes = NULL;
+@@ -1974,6 +1974,7 @@ cifs_mount(struct super_block *sb, struc
+ /* cFYI(1, ("Entering cifs_mount. Xid: %d with: %s", xid, mount_data)); */
++      memset(&addr, 0, sizeof(struct sockaddr));
+       memset(&volume_info, 0, sizeof(struct smb_vol));
+       if (cifs_parse_mount_options(mount_data, devname, &volume_info)) {
+               rc = -EINVAL;
+@@ -1996,16 +1997,16 @@ cifs_mount(struct super_block *sb, struc
+       if (volume_info.UNCip && volume_info.UNC) {
+               rc = cifs_inet_pton(AF_INET, volume_info.UNCip,
+-                                  &sin_server.sin_addr.s_addr);
++                                  &sin_server->sin_addr.s_addr);
+               if (rc <= 0) {
+                       /* not ipv4 address, try ipv6 */
+                       rc = cifs_inet_pton(AF_INET6, volume_info.UNCip,
+-                                          &sin_server6.sin6_addr.in6_u);
++                                          &sin_server6->sin6_addr.in6_u);
+                       if (rc > 0)
+-                              address_type = AF_INET6;
++                              addr.sa_family = AF_INET6;
+               } else {
+-                      address_type = AF_INET;
++                      addr.sa_family = AF_INET;
+               }
+               if (rc <= 0) {
+@@ -2045,39 +2046,38 @@ cifs_mount(struct super_block *sb, struc
+               }
+       }
+-      if (address_type == AF_INET)
+-              existingCifsSes = cifs_find_tcp_session(&sin_server.sin_addr,
++      if (addr.sa_family == AF_INET)
++              existingCifsSes = cifs_find_tcp_session(&sin_server->sin_addr,
+                       NULL /* no ipv6 addr */,
+                       volume_info.username, &srvTcp);
+-      else if (address_type == AF_INET6) {
++      else if (addr.sa_family == AF_INET6) {
+               cFYI(1, ("looking for ipv6 address"));
+               existingCifsSes = cifs_find_tcp_session(NULL /* no ipv4 addr */,
+-                      &sin_server6.sin6_addr,
++                      &sin_server6->sin6_addr,
+                       volume_info.username, &srvTcp);
+       } else {
+               rc = -EINVAL;
+               goto out;
+       }
+-      if (!srvTcp) {  /* create socket */
+-              if (volume_info.port)
+-                      sin_server.sin_port = htons(volume_info.port);
+-              else
+-                      sin_server.sin_port = 0;
+-              if (address_type == AF_INET6) {
++      if (!srvTcp) {
++              if (addr.sa_family == AF_INET6) {
+                       cFYI(1, ("attempting ipv6 connect"));
+                       /* BB should we allow ipv6 on port 139? */
+                       /* other OS never observed in Wild doing 139 with v6 */
+-                      rc = ipv6_connect(&sin_server6, &csocket,
++                      sin_server6->sin6_port = htons(volume_info.port);
++                      rc = ipv6_connect(sin_server6, &csocket,
+                                       volume_info.noblocksnd);
+-              } else
+-                      rc = ipv4_connect(&sin_server, &csocket,
++              } else {
++                      sin_server->sin_port = htons(volume_info.port);
++                      rc = ipv4_connect(sin_server, &csocket,
+                                       volume_info.source_rfc1001_name,
+                                       volume_info.target_rfc1001_name,
+                                       volume_info.noblocksnd,
+                                       volume_info.noautotune);
++              }
+               if (rc < 0) {
+-                      cERROR(1, ("Error connecting to IPv4 socket. "
++                      cERROR(1, ("Error connecting to socket. "
+                                  "Aborting operation"));
+                       if (csocket != NULL)
+                               sock_release(csocket);
+@@ -2092,12 +2092,15 @@ cifs_mount(struct super_block *sb, struc
+               } else {
+                       srvTcp->noblocksnd = volume_info.noblocksnd;
+                       srvTcp->noautotune = volume_info.noautotune;
+-                      memcpy(&srvTcp->addr.sockAddr, &sin_server,
+-                              sizeof(struct sockaddr_in));
++                      if (addr.sa_family == AF_INET6)
++                              memcpy(&srvTcp->addr.sockAddr6, sin_server6,
++                                      sizeof(struct sockaddr_in6));
++                      else
++                              memcpy(&srvTcp->addr.sockAddr, sin_server,
++                                      sizeof(struct sockaddr_in));
+                       atomic_set(&srvTcp->inFlight, 0);
+                       /* BB Add code for ipv6 case too */
+                       srvTcp->ssocket = csocket;
+-                      srvTcp->protocolType = IPV4;
+                       srvTcp->hostname = extract_hostname(volume_info.UNC);
+                       if (IS_ERR(srvTcp->hostname)) {
+                               rc = PTR_ERR(srvTcp->hostname);
+@@ -2149,7 +2152,7 @@ cifs_mount(struct super_block *sb, struc
+               else {
+                       pSesInfo->server = srvTcp;
+                       sprintf(pSesInfo->serverName, "%u.%u.%u.%u",
+-                              NIPQUAD(sin_server.sin_addr.s_addr));
++                              NIPQUAD(sin_server->sin_addr.s_addr));
+               }
+               if (!rc) {
+@@ -2187,7 +2190,7 @@ cifs_mount(struct super_block *sb, struc
+       if (!rc) {
+               setup_cifs_sb(&volume_info, cifs_sb);
+               tcon =
+-                  find_unc(sin_server.sin_addr.s_addr, volume_info.UNC,
++                  find_unc(sin_server->sin_addr.s_addr, volume_info.UNC,
+                            volume_info.username);
+               if (tcon) {
+                       cFYI(1, ("Found match on UNC path"));
diff --git a/queue-2.6.27/cifs-disable-sharing-session-and-tcon-and-add-new-tcp-sharing-code.patch b/queue-2.6.27/cifs-disable-sharing-session-and-tcon-and-add-new-tcp-sharing-code.patch
new file mode 100644 (file)
index 0000000..5a63b6b
--- /dev/null
@@ -0,0 +1,444 @@
+From sjayaraman@suse.de  Wed Dec  3 09:30:44 2008
+From: Jeff Layton <jlayton@redhat.com>
+Date: Fri, 21 Nov 2008 14:23:17 +0530
+Subject: cifs: disable sharing session and tcon and add new TCP sharing code
+To: stable@kernel.org
+Cc: Steve French <smfrench@gmail.com>, Jeff Layton <jlayton@redhat.com>
+Message-ID: <492676FD.7060706@suse.de>
+
+From: Jeff Layton <jlayton@redhat.com>
+
+commit e7ddee9037e7dd43de1ad08b51727e552aedd836 upstream.
+
+The code that allows these structs to be shared is extremely racy.
+Disable the sharing of SMB and tcon structs for now until we can
+come up with a way to do this that's race free.
+
+We want to continue to share TCP sessions, however since they are
+required for multiuser mounts. For that, implement a new (hopefully
+race-free) scheme. Add a new global list of TCP sessions, and take
+care to get a reference to it whenever we're dealing with one.
+
+Signed-off-by: Jeff Layton <jlayton@redhat.com>
+Signed-off-by: Steve French <sfrench@us.ibm.com>
+Cc: Suresh Jayaraman <sjayaraman@suse.de>
+Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
+
+---
+ fs/cifs/cifs_debug.c |    2 
+ fs/cifs/cifsfs.c     |    3 
+ fs/cifs/cifsglob.h   |   17 ++--
+ fs/cifs/cifsproto.h  |    1 
+ fs/cifs/cifssmb.c    |   18 ++--
+ fs/cifs/connect.c    |  206 +++++++++++++++++----------------------------------
+ 6 files changed, 95 insertions(+), 152 deletions(-)
+
+--- a/fs/cifs/cifs_debug.c
++++ b/fs/cifs/cifs_debug.c
+@@ -144,7 +144,7 @@ static int cifs_debug_data_proc_show(str
+                       seq_printf(m, "TCP status: %d\n\tLocal Users To "
+                                   "Server: %d SecMode: 0x%x Req On Wire: %d",
+                               ses->server->tcpStatus,
+-                              atomic_read(&ses->server->socketUseCount),
++                              ses->server->srv_count,
+                               ses->server->secMode,
+                               atomic_read(&ses->server->inFlight));
+--- a/fs/cifs/cifsfs.c
++++ b/fs/cifs/cifsfs.c
+@@ -1013,7 +1013,7 @@ init_cifs(void)
+ {
+       int rc = 0;
+       cifs_proc_init();
+-      INIT_LIST_HEAD(&global_cifs_sock_list);
++      INIT_LIST_HEAD(&cifs_tcp_ses_list);
+       INIT_LIST_HEAD(&GlobalSMBSessionList); /* BB to be removed by jl */
+       INIT_LIST_HEAD(&GlobalTreeConnectionList); /* BB to be removed by jl */
+       INIT_LIST_HEAD(&GlobalOplock_Q);
+@@ -1043,6 +1043,7 @@ init_cifs(void)
+       GlobalMaxActiveXid = 0;
+       memset(Local_System_Name, 0, 15);
+       rwlock_init(&GlobalSMBSeslock);
++      rwlock_init(&cifs_tcp_ses_lock);
+       spin_lock_init(&GlobalMid_Lock);
+       if (cifs_max_pending < 2) {
+--- a/fs/cifs/cifsglob.h
++++ b/fs/cifs/cifsglob.h
+@@ -123,6 +123,7 @@ struct cifs_cred {
+ struct TCP_Server_Info {
+       struct list_head tcp_ses_list;
+       struct list_head smb_ses_list;
++      int srv_count; /* reference counter */
+       /* 15 character server name + 0x20 16th byte indicating type = srv */
+       char server_RFC1001_name[SERVER_NAME_LEN_WITH_NULL];
+       char unicode_server_Name[SERVER_NAME_LEN_WITH_NULL * 2];
+@@ -144,7 +145,6 @@ struct TCP_Server_Info {
+       bool svlocal:1;                 /* local server or remote */
+       bool noblocksnd;                /* use blocking sendmsg */
+       bool noautotune;                /* do not autotune send buf sizes */
+-      atomic_t socketUseCount; /* number of open cifs sessions on socket */
+       atomic_t inFlight;  /* number of requests on the wire to server */
+ #ifdef CONFIG_CIFS_STATS2
+       atomic_t inSend; /* requests trying to send */
+@@ -589,13 +589,18 @@ require use of the stronger protocol */
+ #define GLOBAL_EXTERN extern
+ #endif
+-
+-/* the list of TCP_Server_Info structures, ie each of the sockets
++/*
++ * the list of TCP_Server_Info structures, ie each of the sockets
+  * connecting our client to a distinct server (ip address), is
+- * chained together by global_cifs_sock_list. The list of all our SMB
++ * chained together by cifs_tcp_ses_list. The list of all our SMB
+  * sessions (and from that the tree connections) can be found
+- * by iterating over global_cifs_sock_list */
+-GLOBAL_EXTERN struct list_head global_cifs_sock_list;
++ * by iterating over cifs_tcp_ses_list
++ */
++GLOBAL_EXTERN struct list_head                cifs_tcp_ses_list;
++
++/* protects cifs_tcp_ses_list and srv_count for each tcp session */
++GLOBAL_EXTERN rwlock_t                cifs_tcp_ses_lock;
++
+ GLOBAL_EXTERN struct list_head GlobalSMBSessionList; /* BB to be removed by jl*/
+ GLOBAL_EXTERN struct list_head GlobalTreeConnectionList; /* BB to be removed */
+ GLOBAL_EXTERN rwlock_t GlobalSMBSeslock;  /* protects list inserts on 3 above */
+--- a/fs/cifs/cifsproto.h
++++ b/fs/cifs/cifsproto.h
+@@ -102,6 +102,7 @@ extern void acl_to_uid_mode(struct inode
+                           const __u16 *pfid);
+ extern int mode_to_acl(struct inode *inode, const char *path, __u64);
++extern void cifs_put_tcp_session(struct TCP_Server_Info *server);
+ extern int cifs_mount(struct super_block *, struct cifs_sb_info *, char *,
+                       const char *);
+ extern int cifs_umount(struct super_block *, struct cifs_sb_info *);
+--- a/fs/cifs/cifssmb.c
++++ b/fs/cifs/cifssmb.c
+@@ -664,8 +664,9 @@ CIFSSMBNegotiate(unsigned int xid, struc
+                       rc = -EIO;
+                       goto neg_err_exit;
+               }
+-
+-              if (server->socketUseCount.counter > 1) {
++              read_lock(&cifs_tcp_ses_lock);
++              if (server->srv_count > 1) {
++                      read_unlock(&cifs_tcp_ses_lock);
+                       if (memcmp(server->server_GUID,
+                                  pSMBr->u.extended_response.
+                                  GUID, 16) != 0) {
+@@ -674,9 +675,11 @@ CIFSSMBNegotiate(unsigned int xid, struc
+                                       pSMBr->u.extended_response.GUID,
+                                       16);
+                       }
+-              } else
++              } else {
++                      read_unlock(&cifs_tcp_ses_lock);
+                       memcpy(server->server_GUID,
+                              pSMBr->u.extended_response.GUID, 16);
++              }
+               if (count == 16) {
+                       server->secType = RawNTLMSSP;
+@@ -830,12 +833,9 @@ CIFSSMBLogoff(const int xid, struct cifs
+       pSMB->AndXCommand = 0xFF;
+       rc = SendReceiveNoRsp(xid, ses, (struct smb_hdr *) pSMB, 0);
+ session_already_dead:
+-      atomic_dec(&ses->server->socketUseCount);
+-      if (atomic_read(&ses->server->socketUseCount) == 0) {
+-              spin_lock(&GlobalMid_Lock);
+-              ses->server->tcpStatus = CifsExiting;
+-              spin_unlock(&GlobalMid_Lock);
+-              rc = -ESHUTDOWN;
++      if (ses->server) {
++              cifs_put_tcp_session(ses->server);
++              rc = 0;
+       }
+       up(&ses->sesSem);
+--- a/fs/cifs/connect.c
++++ b/fs/cifs/connect.c
+@@ -657,6 +657,11 @@ multi_t2_fnd:
+               }
+       } /* end while !EXITING */
++      /* take it off the list, if it's not already */
++      write_lock(&cifs_tcp_ses_lock);
++      list_del_init(&server->tcp_ses_list);
++      write_unlock(&cifs_tcp_ses_lock);
++
+       spin_lock(&GlobalMid_Lock);
+       server->tcpStatus = CifsExiting;
+       spin_unlock(&GlobalMid_Lock);
+@@ -1346,92 +1351,66 @@ cifs_parse_mount_options(char *options, 
+       return 0;
+ }
+-static struct cifsSesInfo *
+-cifs_find_tcp_session(struct in_addr *target_ip_addr,
+-                    struct in6_addr *target_ip6_addr,
+-                    char *userName, struct TCP_Server_Info **psrvTcp)
++static struct TCP_Server_Info *
++cifs_find_tcp_session(struct sockaddr *addr)
+ {
+       struct list_head *tmp;
+-      struct cifsSesInfo *ses;
+-
+-      *psrvTcp = NULL;
+-
+-      read_lock(&GlobalSMBSeslock);
+-      list_for_each(tmp, &GlobalSMBSessionList) {
+-              ses = list_entry(tmp, struct cifsSesInfo, cifsSessionList);
+-              if (!ses->server)
++      struct TCP_Server_Info *server;
++      struct sockaddr_in *addr4 = (struct sockaddr_in *) addr;
++      struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) addr;
++
++      write_lock(&cifs_tcp_ses_lock);
++      list_for_each(tmp, &cifs_tcp_ses_list) {
++              server = list_entry(tmp, struct TCP_Server_Info,
++                                  tcp_ses_list);
++
++              /*
++               * the demux thread can exit on its own while still in CifsNew
++               * so don't accept any sockets in that state. Since the
++               * tcpStatus never changes back to CifsNew it's safe to check
++               * for this without a lock.
++               */
++              if (server->tcpStatus == CifsNew)
+                       continue;
+-              if (target_ip_addr &&
+-                  ses->server->addr.sockAddr.sin_addr.s_addr != target_ip_addr->s_addr)
+-                              continue;
+-              else if (target_ip6_addr &&
+-                       memcmp(&ses->server->addr.sockAddr6.sin6_addr,
+-                              target_ip6_addr, sizeof(*target_ip6_addr)))
+-                              continue;
+-              /* BB lock server and tcp session; increment use count here?? */
+-
+-              /* found a match on the TCP session */
+-              *psrvTcp = ses->server;
++              if (addr->sa_family == AF_INET &&
++                  (addr4->sin_addr.s_addr !=
++                   server->addr.sockAddr.sin_addr.s_addr))
++                      continue;
++              else if (addr->sa_family == AF_INET6 &&
++                       memcmp(&server->addr.sockAddr6.sin6_addr,
++                              &addr6->sin6_addr, sizeof(addr6->sin6_addr)))
++                      continue;
+-              /* BB check if reconnection needed */
+-              if (strncmp(ses->userName, userName, MAX_USERNAME_SIZE) == 0) {
+-                      read_unlock(&GlobalSMBSeslock);
+-                      /* Found exact match on both TCP and
+-                         SMB sessions */
+-                      return ses;
+-              }
+-              /* else tcp and smb sessions need reconnection */
++              ++server->srv_count;
++              write_unlock(&cifs_tcp_ses_lock);
++              return server;
+       }
+-      read_unlock(&GlobalSMBSeslock);
+-
++      write_unlock(&cifs_tcp_ses_lock);
+       return NULL;
+ }
+-static struct cifsTconInfo *
+-find_unc(__be32 new_target_ip_addr, char *uncName, char *userName)
++void
++cifs_put_tcp_session(struct TCP_Server_Info *server)
+ {
+-      struct list_head *tmp;
+-      struct cifsTconInfo *tcon;
+-      __be32 old_ip;
+-
+-      read_lock(&GlobalSMBSeslock);
+-
+-      list_for_each(tmp, &GlobalTreeConnectionList) {
+-              cFYI(1, ("Next tcon"));
+-              tcon = list_entry(tmp, struct cifsTconInfo, cifsConnectionList);
+-              if (!tcon->ses || !tcon->ses->server)
+-                      continue;
+-
+-              old_ip = tcon->ses->server->addr.sockAddr.sin_addr.s_addr;
+-              cFYI(1, ("old ip addr: %x == new ip %x ?",
+-                      old_ip, new_target_ip_addr));
++      struct task_struct *task;
+-              if (old_ip != new_target_ip_addr)
+-                      continue;
+-
+-              /* BB lock tcon, server, tcp session and increment use count? */
+-              /* found a match on the TCP session */
+-              /* BB check if reconnection needed */
+-              cFYI(1, ("IP match, old UNC: %s new: %s",
+-                      tcon->treeName, uncName));
+-
+-              if (strncmp(tcon->treeName, uncName, MAX_TREE_SIZE))
+-                      continue;
+-
+-              cFYI(1, ("and old usr: %s new: %s",
+-                      tcon->treeName, uncName));
++      write_lock(&cifs_tcp_ses_lock);
++      if (--server->srv_count > 0) {
++              write_unlock(&cifs_tcp_ses_lock);
++              return;
++      }
+-              if (strncmp(tcon->ses->userName, userName, MAX_USERNAME_SIZE))
+-                      continue;
++      list_del_init(&server->tcp_ses_list);
++      write_unlock(&cifs_tcp_ses_lock);
+-              /* matched smb session (user name) */
+-              read_unlock(&GlobalSMBSeslock);
+-              return tcon;
+-      }
++      spin_lock(&GlobalMid_Lock);
++      server->tcpStatus = CifsExiting;
++      spin_unlock(&GlobalMid_Lock);
+-      read_unlock(&GlobalSMBSeslock);
+-      return NULL;
++      task = xchg(&server->tsk, NULL);
++      if (task)
++              force_sig(SIGKILL, task);
+ }
+ int
+@@ -2046,21 +2025,10 @@ cifs_mount(struct super_block *sb, struc
+               }
+       }
+-      if (addr.sa_family == AF_INET)
+-              existingCifsSes = cifs_find_tcp_session(&sin_server->sin_addr,
+-                      NULL /* no ipv6 addr */,
+-                      volume_info.username, &srvTcp);
+-      else if (addr.sa_family == AF_INET6) {
+-              cFYI(1, ("looking for ipv6 address"));
+-              existingCifsSes = cifs_find_tcp_session(NULL /* no ipv4 addr */,
+-                      &sin_server6->sin6_addr,
+-                      volume_info.username, &srvTcp);
+-      } else {
+-              rc = -EINVAL;
+-              goto out;
+-      }
+-
+-      if (!srvTcp) {
++      srvTcp = cifs_find_tcp_session(&addr);
++      if (srvTcp) {
++              cFYI(1, ("Existing tcp session with server found"));
++      } else {        /* create socket */
+               if (addr.sa_family == AF_INET6) {
+                       cFYI(1, ("attempting ipv6 connect"));
+                       /* BB should we allow ipv6 on port 139? */
+@@ -2130,6 +2098,12 @@ cifs_mount(struct super_block *sb, struc
+                       memcpy(srvTcp->server_RFC1001_name,
+                               volume_info.target_rfc1001_name, 16);
+                       srvTcp->sequence_number = 0;
++                      INIT_LIST_HEAD(&srvTcp->tcp_ses_list);
++                      ++srvTcp->srv_count;
++                      write_lock(&cifs_tcp_ses_lock);
++                      list_add(&srvTcp->tcp_ses_list,
++                               &cifs_tcp_ses_list);
++                      write_unlock(&cifs_tcp_ses_lock);
+               }
+       }
+@@ -2181,17 +2155,12 @@ cifs_mount(struct super_block *sb, struc
+                       rc = cifs_setup_session(xid, pSesInfo,
+                                               cifs_sb->local_nls);
+                       up(&pSesInfo->sesSem);
+-                      if (!rc)
+-                              atomic_inc(&srvTcp->socketUseCount);
+               }
+       }
+       /* search for existing tcon to this server share */
+       if (!rc) {
+               setup_cifs_sb(&volume_info, cifs_sb);
+-              tcon =
+-                  find_unc(sin_server->sin_addr.s_addr, volume_info.UNC,
+-                           volume_info.username);
+               if (tcon) {
+                       cFYI(1, ("Found match on UNC path"));
+                       if (tcon->seal != volume_info.seal)
+@@ -2248,47 +2217,22 @@ cifs_mount(struct super_block *sb, struc
+ /* on error free sesinfo and tcon struct if needed */
+ mount_fail_check:
+       if (rc) {
+-              /* if session setup failed, use count is zero but
+-              we still need to free cifsd thread */
+-              if (atomic_read(&srvTcp->socketUseCount) == 0) {
+-                      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);
+-                      }
+-              }
+                /* If find_unc succeeded then rc == 0 so we can not end */
+-              if (tcon)  /* up accidently freeing someone elses tcon struct */
++              /* up accidently freeing someone elses tcon struct */
++              if (tcon)
+                       tconInfoFree(tcon);
++
+               if (existingCifsSes == NULL) {
+                       if (pSesInfo) {
+                               if ((pSesInfo->server) &&
+-                                  (pSesInfo->status == CifsGood)) {
+-                                      int temp_rc;
+-                                      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);
+-                                      }
+-                              } else {
++                                  (pSesInfo->status == CifsGood))
++                                      CIFSSMBLogoff(xid, pSesInfo);
++                              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)
++                                              cifs_put_tcp_session(
++                                                      pSesInfo->server);
+                               sesInfoFree(pSesInfo);
+                               /* pSesInfo = NULL; */
+                       }
+@@ -3596,15 +3540,7 @@ cifs_umount(struct super_block *sb, stru
+                       if (rc == -EBUSY) {
+                               FreeXid(xid);
+                               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);
+-                              }
+-                              rc = 0;
+-                      } /* else - we have an smb session
+-                              left on this socket do not kill cifsd */
++                      }
+               } else
+                       cFYI(1, ("No session or bad tcon"));
+       }
diff --git a/queue-2.6.27/cifs-fix-build-break.patch b/queue-2.6.27/cifs-fix-build-break.patch
new file mode 100644 (file)
index 0000000..5474f78
--- /dev/null
@@ -0,0 +1,34 @@
+From sjayaraman@suse.de  Wed Dec  3 09:41:59 2008
+From: Steve French <sfrench@us.ibm.com>
+Date: Fri, 21 Nov 2008 14:24:05 +0530
+Subject: cifs: Fix build break
+To: stable@kernel.org
+Cc: Steve French <smfrench@gmail.com>
+Message-ID: <4926772D.60304@suse.de>
+
+From: Steve French <sfrench@us.ibm.com>
+
+commit c2b3382cd4d6c6adef1347e81f20e16c93a39feb upstream
+
+Signed-off-by: Steve French <sfrench@us.ibm.com>
+Cc: Suresh Jayaraman <sjayaraman@suse.de>
+Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
+
+---
+ fs/cifs/cifs_debug.c |    4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/fs/cifs/cifs_debug.c
++++ b/fs/cifs/cifs_debug.c
+@@ -249,9 +249,9 @@ static ssize_t cifs_stats_proc_write(str
+               list_for_each(tmp1, &cifs_tcp_ses_list) {
+                       server = list_entry(tmp1, struct TCP_Server_Info,
+                                           tcp_ses_list);
+-                      list_for_each(tmp2, &server->smb_session_list) {
++                      list_for_each(tmp2, &server->smb_ses_list) {
+                               ses = list_entry(tmp2, struct cifsSesInfo,
+-                                               smb_session_list);
++                                               smb_ses_list);
+                               list_for_each(tmp3, &ses->tcon_list) {
+                                       tcon = list_entry(tmp3,
+                                                         struct cifsTconInfo,
diff --git a/queue-2.6.27/cifs-fix-check-for-dead-tcon-in-smb_init.patch b/queue-2.6.27/cifs-fix-check-for-dead-tcon-in-smb_init.patch
new file mode 100644 (file)
index 0000000..9f638c7
--- /dev/null
@@ -0,0 +1,36 @@
+From sjayaraman@suse.de  Wed Dec  3 09:44:31 2008
+From: Steve French <sfrench@us.ibm.com>
+Date: Mon, 24 Nov 2008 10:31:05 +0530
+Subject: cifs: fix check for dead tcon in smb_init
+To: stable@kernel.org
+Cc: Steve French <smfrench@gmail.com>, Jeff Layton <jlayton@redhat.com>
+Message-ID: <492A3511.2040109@suse.de>
+
+From: Steve French <sfrench@us.ibm.com>
+
+commit bfb59820ee46616a7bdb4af6b8f7e109646de6ec upstream
+
+This was recently changed to check for need_reconnect, but should
+actually be a check for a tidStatus of CifsExiting.
+
+Signed-off-by: Jeff Layton <jlayton@redhat.com>
+Signed-off-by: Steve French <sfrench@us.ibm.com>
+Cc: Suresh Jayaraman <sjayaraman@suse.de>
+Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
+
+
+---
+ fs/cifs/cifssmb.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/fs/cifs/cifssmb.c
++++ b/fs/cifs/cifssmb.c
+@@ -295,7 +295,7 @@ smb_init(int smb_command, int wct, struc
+          check for tcp and smb session status done differently
+          for those three - in the calling routine */
+       if (tcon) {
+-              if (tcon->need_reconnect) {
++              if (tcon->tidStatus == CifsExiting) {
+                       /* only tree disconnect, open, and write,
+                         (and ulogoff which does not have tcon)
+                         are allowed as we start force umount */
diff --git a/queue-2.6.27/cifs-fix-check-for-tcon-seal-setting-and-fix-oops-on-failed-mount-from-earlier-patch.patch b/queue-2.6.27/cifs-fix-check-for-tcon-seal-setting-and-fix-oops-on-failed-mount-from-earlier-patch.patch
new file mode 100644 (file)
index 0000000..78abfe4
--- /dev/null
@@ -0,0 +1,58 @@
+From sjayaraman@suse.de  Wed Dec  3 09:42:51 2008
+From: Steve French <sfrench@us.ibm.com>
+Date: Fri, 21 Nov 2008 14:24:12 +0530
+Subject: cifs: Fix check for tcon seal setting and fix oops on failed mount from earlier patch
+To: stable@kernel.org
+Cc: Steve French <smfrench@gmail.com>, Jeff Layton <jlayton@redhat.com>
+Message-ID: <49267734.7050508@suse.de>
+
+From: Steve French <sfrench@us.ibm.com>
+
+commit ab3f992983062440b4f37c666dac66d987902d91 upstream
+
+set tcon->ses earlier
+
+If the inital tree connect fails, we'll end up calling cifs_put_smb_ses
+with a NULL pointer. Fix it by setting the tcon->ses earlier.
+
+Acked-by: Jeff Layton <jlayton@redhat.com>
+Signed-off-by: Steve French <sfrench@us.ibm.com>
+Cc: Suresh Jayaraman <sjayaraman@suse.de>
+Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
+
+
+---
+ fs/cifs/connect.c |    7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+--- a/fs/cifs/connect.c
++++ b/fs/cifs/connect.c
+@@ -2256,16 +2256,18 @@ cifs_mount(struct super_block *sb, struc
+                       cFYI(1, ("Found match on UNC path"));
+                       /* existing tcon already has a reference */
+                       cifs_put_smb_ses(pSesInfo);
++                      if (tcon->seal != volume_info.seal)
++                              cERROR(1, ("transport encryption setting "
++                                         "conflicts with existing tid"));
+               } else {
+                       tcon = tconInfoAlloc();
+                       if (tcon == NULL) {
+                               rc = -ENOMEM;
+                               goto mount_fail_check;
+                       }
++                      tcon->ses = pSesInfo;
+                       /* check for null share name ie connect to dfs root */
+-
+-                      /* BB check if works for exactly length 3 strings */
+                       if ((strchr(volume_info.UNC + 3, '\\') == NULL)
+                           && (strchr(volume_info.UNC + 3, '/') == NULL)) {
+                               /* rc = connect_to_dfs_path(...) */
+@@ -2283,7 +2285,6 @@ cifs_mount(struct super_block *sb, struc
+                       if (rc)
+                               goto mount_fail_check;
+                       tcon->seal = volume_info.seal;
+-                      tcon->ses = pSesInfo;
+                       write_lock(&cifs_tcp_ses_lock);
+                       list_add(&tcon->tcon_list, &pSesInfo->tcon_list);
+                       write_unlock(&cifs_tcp_ses_lock);
diff --git a/queue-2.6.27/cifs-fix-cifs-reconnection-flags.patch b/queue-2.6.27/cifs-fix-cifs-reconnection-flags.patch
new file mode 100644 (file)
index 0000000..9a67c0a
--- /dev/null
@@ -0,0 +1,520 @@
+From sjayaraman@suse.de  Wed Dec  3 09:28:09 2008
+From: Steve French <sfrench@us.ibm.com>
+Date: Fri, 21 Nov 2008 14:22:45 +0530
+Subject: cifs: Fix cifs reconnection flags
+To: stable@kernel.org
+Cc: Steve French <smfrench@gmail.com>, Shirish S Pargaonkar <shirishp@us.ibm.com>, Jeff Layton <jlayton@redhat.com>
+Message-ID: <492676DD.5070504@suse.de>
+
+From: Steve French <sfrench@us.ibm.com>
+
+commit 3b7952109361c684caf0c50474da8662ecc81019 upstream
+
+[CIFS] Fix cifs reconnection flags
+
+In preparation for Jeff's big umount/mount fixes to remove the possibility of
+various races in cifs mount and linked list handling of sessions, sockets and
+tree connections, this patch cleans up some repetitive code in cifs_mount,
+and addresses a problem with ses->status and tcon->tidStatus in which we
+were overloading the "need_reconnect" state with other status in that
+field.  So the "need_reconnect" flag has been broken out from those
+two state fields (need reconnect was not mutually exclusive from some of the
+other possible tid and ses states).  In addition, a few exit cases in
+cifs_mount were cleaned up, and a problem with a tcon flag (for lease support)
+was not being set consistently for the 2nd mount of the same share
+
+CC: Jeff Layton <jlayton@redhat.com>
+CC: Shirish Pargaonkar <shirishp@us.ibm.com>
+Signed-off-by: Steve French <sfrench@us.ibm.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
+
+---
+ fs/cifs/cifsfs.c   |    2 
+ fs/cifs/cifsglob.h |    5 +
+ fs/cifs/cifssmb.c  |   40 ++++----
+ fs/cifs/connect.c  |  252 ++++++++++++++++++++++++++---------------------------
+ fs/cifs/file.c     |    2 
+ 5 files changed, 155 insertions(+), 146 deletions(-)
+
+--- a/fs/cifs/cifsfs.c
++++ b/fs/cifs/cifsfs.c
+@@ -967,7 +967,7 @@ static int cifs_oplock_thread(void *dumm
+                               not bother sending an oplock release if session
+                               to server still is disconnected since oplock
+                               already released by the server in that case */
+-                      if (pTcon->tidStatus != CifsNeedReconnect) {
++                      if (!pTcon->need_reconnect) {
+                               rc = CIFSSMBLock(0, pTcon, netfid,
+                                               0 /* len */ , 0 /* offset */, 0,
+                                               0, LOCKING_ANDX_OPLOCK_RELEASE,
+--- a/fs/cifs/cifsglob.h
++++ b/fs/cifs/cifsglob.h
+@@ -122,6 +122,8 @@ struct cifs_cred {
+  */
+ struct TCP_Server_Info {
++      struct list_head tcp_ses_list;
++      struct list_head smb_ses_list;
+       /* 15 character server name + 0x20 16th byte indicating type = srv */
+       char server_RFC1001_name[SERVER_NAME_LEN_WITH_NULL];
+       char unicode_server_Name[SERVER_NAME_LEN_WITH_NULL * 2];
+@@ -195,6 +197,7 @@ struct cifsUidInfo {
+  */
+ struct cifsSesInfo {
+       struct list_head cifsSessionList;
++      struct list_head tcon_list;
+       struct semaphore sesSem;
+ #if 0
+       struct cifsUidInfo *uidInfo;    /* pointer to user info */
+@@ -216,6 +219,7 @@ struct cifsSesInfo {
+       char userName[MAX_USERNAME_SIZE + 1];
+       char *domainName;
+       char *password;
++      bool need_reconnect:1; /* connection reset, uid now invalid */
+ };
+ /* no more than one of the following three session flags may be set */
+ #define CIFS_SES_NT4 1
+@@ -287,6 +291,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 need_reconnect:1; /* connection reset, tid now invalid */
+       /* BB add field for back pointer to sb struct(s)? */
+ };
+--- a/fs/cifs/cifssmb.c
++++ b/fs/cifs/cifssmb.c
+@@ -190,10 +190,10 @@ small_smb_init(int smb_command, int wct,
+               /* need to prevent multiple threads trying to
+               simultaneously reconnect the same SMB session */
+                       down(&tcon->ses->sesSem);
+-                      if (tcon->ses->status == CifsNeedReconnect)
++                      if (tcon->ses->need_reconnect)
+                               rc = cifs_setup_session(0, tcon->ses,
+                                                       nls_codepage);
+-                      if (!rc && (tcon->tidStatus == CifsNeedReconnect)) {
++                      if (!rc && (tcon->need_reconnect)) {
+                               mark_open_files_invalid(tcon);
+                               rc = CIFSTCon(0, tcon->ses, tcon->treeName,
+                                             tcon, nls_codepage);
+@@ -295,7 +295,7 @@ smb_init(int smb_command, int wct, struc
+          check for tcp and smb session status done differently
+          for those three - in the calling routine */
+       if (tcon) {
+-              if (tcon->tidStatus == CifsExiting) {
++              if (tcon->need_reconnect) {
+                       /* only tree disconnect, open, and write,
+                         (and ulogoff which does not have tcon)
+                         are allowed as we start force umount */
+@@ -337,10 +337,10 @@ smb_init(int smb_command, int wct, struc
+               /* need to prevent multiple threads trying to
+               simultaneously reconnect the same SMB session */
+                       down(&tcon->ses->sesSem);
+-                      if (tcon->ses->status == CifsNeedReconnect)
++                      if (tcon->ses->need_reconnect)
+                               rc = cifs_setup_session(0, tcon->ses,
+                                                       nls_codepage);
+-                      if (!rc && (tcon->tidStatus == CifsNeedReconnect)) {
++                      if (!rc && (tcon->need_reconnect)) {
+                               mark_open_files_invalid(tcon);
+                               rc = CIFSTCon(0, tcon->ses, tcon->treeName,
+                                             tcon, nls_codepage);
+@@ -759,7 +759,7 @@ CIFSSMBTDis(const int xid, struct cifsTc
+       /* No need to return error on this operation if tid invalidated and
+       closed on server already e.g. due to tcp session crashing */
+-      if (tcon->tidStatus == CifsNeedReconnect) {
++      if (tcon->need_reconnect) {
+               up(&tcon->tconSem);
+               return 0;
+       }
+@@ -806,32 +806,36 @@ CIFSSMBLogoff(const int xid, struct cifs
+               up(&ses->sesSem);
+               return -EBUSY;
+       }
++
++      if (ses->server == NULL)
++              return -EIO;
++
++      if (ses->need_reconnect)
++              goto session_already_dead; /* no need to send SMBlogoff if uid
++                                            already closed due to reconnect */
+       rc = small_smb_init(SMB_COM_LOGOFF_ANDX, 2, NULL, (void **)&pSMB);
+       if (rc) {
+               up(&ses->sesSem);
+               return rc;
+       }
+-      if (ses->server) {
+-              pSMB->hdr.Mid = GetNextMid(ses->server);
++      pSMB->hdr.Mid = GetNextMid(ses->server);
+-              if (ses->server->secMode &
++      if (ses->server->secMode &
+                  (SECMODE_SIGN_REQUIRED | SECMODE_SIGN_ENABLED))
+                       pSMB->hdr.Flags2 |= SMBFLG2_SECURITY_SIGNATURE;
+-      }
+       pSMB->hdr.Uid = ses->Suid;
+       pSMB->AndXCommand = 0xFF;
+       rc = SendReceiveNoRsp(xid, ses, (struct smb_hdr *) pSMB, 0);
+-      if (ses->server) {
+-              atomic_dec(&ses->server->socketUseCount);
+-              if (atomic_read(&ses->server->socketUseCount) == 0) {
+-                      spin_lock(&GlobalMid_Lock);
+-                      ses->server->tcpStatus = CifsExiting;
+-                      spin_unlock(&GlobalMid_Lock);
+-                      rc = -ESHUTDOWN;
+-              }
++session_already_dead:
++      atomic_dec(&ses->server->socketUseCount);
++      if (atomic_read(&ses->server->socketUseCount) == 0) {
++              spin_lock(&GlobalMid_Lock);
++              ses->server->tcpStatus = CifsExiting;
++              spin_unlock(&GlobalMid_Lock);
++              rc = -ESHUTDOWN;
+       }
+       up(&ses->sesSem);
+--- a/fs/cifs/connect.c
++++ b/fs/cifs/connect.c
+@@ -147,7 +147,7 @@ cifs_reconnect(struct TCP_Server_Info *s
+               ses = list_entry(tmp, struct cifsSesInfo, cifsSessionList);
+               if (ses->server) {
+                       if (ses->server == server) {
+-                              ses->status = CifsNeedReconnect;
++                              ses->need_reconnect = true;
+                               ses->ipc_tid = 0;
+                       }
+               }
+@@ -156,7 +156,7 @@ cifs_reconnect(struct TCP_Server_Info *s
+       list_for_each(tmp, &GlobalTreeConnectionList) {
+               tcon = list_entry(tmp, struct cifsTconInfo, cifsConnectionList);
+               if ((tcon->ses) && (tcon->ses->server == server))
+-                      tcon->tidStatus = CifsNeedReconnect;
++                      tcon->need_reconnect = true;
+       }
+       read_unlock(&GlobalSMBSeslock);
+       /* do not want to be sending data on a socket we are freeing */
+@@ -1868,6 +1868,92 @@ convert_delimiter(char *path, char delim
+       }
+ }
++static void setup_cifs_sb(struct smb_vol *pvolume_info,
++                        struct cifs_sb_info *cifs_sb)
++{
++      if (pvolume_info->rsize > CIFSMaxBufSize) {
++              cERROR(1, ("rsize %d too large, using MaxBufSize",
++                      pvolume_info->rsize));
++              cifs_sb->rsize = CIFSMaxBufSize;
++      } else if ((pvolume_info->rsize) &&
++                      (pvolume_info->rsize <= CIFSMaxBufSize))
++              cifs_sb->rsize = pvolume_info->rsize;
++      else /* default */
++              cifs_sb->rsize = CIFSMaxBufSize;
++
++      if (pvolume_info->wsize > PAGEVEC_SIZE * PAGE_CACHE_SIZE) {
++              cERROR(1, ("wsize %d too large, using 4096 instead",
++                        pvolume_info->wsize));
++              cifs_sb->wsize = 4096;
++      } else if (pvolume_info->wsize)
++              cifs_sb->wsize = pvolume_info->wsize;
++      else
++              cifs_sb->wsize = min_t(const int,
++                                      PAGEVEC_SIZE * PAGE_CACHE_SIZE,
++                                      127*1024);
++              /* old default of CIFSMaxBufSize was too small now
++                 that SMB Write2 can send multiple pages in kvec.
++                 RFC1001 does not describe what happens when frame
++                 bigger than 128K is sent so use that as max in
++                 conjunction with 52K kvec constraint on arch with 4K
++                 page size  */
++
++      if (cifs_sb->rsize < 2048) {
++              cifs_sb->rsize = 2048;
++              /* Windows ME may prefer this */
++              cFYI(1, ("readsize set to minimum: 2048"));
++      }
++      /* calculate prepath */
++      cifs_sb->prepath = pvolume_info->prepath;
++      if (cifs_sb->prepath) {
++              cifs_sb->prepathlen = strlen(cifs_sb->prepath);
++              /* we can not convert the / to \ in the path
++              separators in the prefixpath yet because we do not
++              know (until reset_cifs_unix_caps is called later)
++              whether POSIX PATH CAP is available. We normalize
++              the / to \ after reset_cifs_unix_caps is called */
++              pvolume_info->prepath = NULL;
++      } else
++              cifs_sb->prepathlen = 0;
++      cifs_sb->mnt_uid = pvolume_info->linux_uid;
++      cifs_sb->mnt_gid = pvolume_info->linux_gid;
++      cifs_sb->mnt_file_mode = pvolume_info->file_mode;
++      cifs_sb->mnt_dir_mode = pvolume_info->dir_mode;
++      cFYI(1, ("file mode: 0x%x  dir mode: 0x%x",
++              cifs_sb->mnt_file_mode, cifs_sb->mnt_dir_mode));
++
++      if (pvolume_info->noperm)
++              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NO_PERM;
++      if (pvolume_info->setuids)
++              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_SET_UID;
++      if (pvolume_info->server_ino)
++              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_SERVER_INUM;
++      if (pvolume_info->remap)
++              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_MAP_SPECIAL_CHR;
++      if (pvolume_info->no_xattr)
++              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NO_XATTR;
++      if (pvolume_info->sfu_emul)
++              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_UNX_EMUL;
++      if (pvolume_info->nobrl)
++              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NO_BRL;
++      if (pvolume_info->cifs_acl)
++              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_CIFS_ACL;
++      if (pvolume_info->override_uid)
++              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_OVERR_UID;
++      if (pvolume_info->override_gid)
++              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_OVERR_GID;
++      if (pvolume_info->dynperm)
++              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DYNPERM;
++      if (pvolume_info->direct_io) {
++              cFYI(1, ("mounting share using direct i/o"));
++              cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DIRECT_IO;
++      }
++
++      if ((pvolume_info->cifs_acl) && (pvolume_info->dynperm))
++              cERROR(1, ("mount option dynperm ignored if cifsacl "
++                         "mount option supported"));
++}
++
+ int
+ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
+          char *mount_data, const char *devname)
+@@ -1973,9 +2059,7 @@ cifs_mount(struct super_block *sb, struc
+               goto out;
+       }
+-      if (srvTcp) {
+-              cFYI(1, ("Existing tcp session with server found"));
+-      } else {        /* create socket */
++      if (!srvTcp) {  /* create socket */
+               if (volume_info.port)
+                       sin_server.sin_port = htons(volume_info.port);
+               else
+@@ -2051,7 +2135,7 @@ cifs_mount(struct super_block *sb, struc
+               cFYI(1, ("Existing smb sess found (status=%d)",
+                       pSesInfo->status));
+               down(&pSesInfo->sesSem);
+-              if (pSesInfo->status == CifsNeedReconnect) {
++              if (pSesInfo->need_reconnect) {
+                       cFYI(1, ("Session needs reconnect"));
+                       rc = cifs_setup_session(xid, pSesInfo,
+                                               cifs_sb->local_nls);
+@@ -2101,139 +2185,52 @@ cifs_mount(struct super_block *sb, struc
+       /* search for existing tcon to this server share */
+       if (!rc) {
+-              if (volume_info.rsize > CIFSMaxBufSize) {
+-                      cERROR(1, ("rsize %d too large, using MaxBufSize",
+-                              volume_info.rsize));
+-                      cifs_sb->rsize = CIFSMaxBufSize;
+-              } else if ((volume_info.rsize) &&
+-                              (volume_info.rsize <= CIFSMaxBufSize))
+-                      cifs_sb->rsize = volume_info.rsize;
+-              else /* default */
+-                      cifs_sb->rsize = CIFSMaxBufSize;
+-
+-              if (volume_info.wsize > PAGEVEC_SIZE * PAGE_CACHE_SIZE) {
+-                      cERROR(1, ("wsize %d too large, using 4096 instead",
+-                                volume_info.wsize));
+-                      cifs_sb->wsize = 4096;
+-              } else if (volume_info.wsize)
+-                      cifs_sb->wsize = volume_info.wsize;
+-              else
+-                      cifs_sb->wsize =
+-                              min_t(const int, PAGEVEC_SIZE * PAGE_CACHE_SIZE,
+-                                      127*1024);
+-                      /* old default of CIFSMaxBufSize was too small now
+-                         that SMB Write2 can send multiple pages in kvec.
+-                         RFC1001 does not describe what happens when frame
+-                         bigger than 128K is sent so use that as max in
+-                         conjunction with 52K kvec constraint on arch with 4K
+-                         page size  */
+-
+-              if (cifs_sb->rsize < 2048) {
+-                      cifs_sb->rsize = 2048;
+-                      /* Windows ME may prefer this */
+-                      cFYI(1, ("readsize set to minimum: 2048"));
+-              }
+-              /* calculate prepath */
+-              cifs_sb->prepath = volume_info.prepath;
+-              if (cifs_sb->prepath) {
+-                      cifs_sb->prepathlen = strlen(cifs_sb->prepath);
+-                      /* we can not convert the / to \ in the path
+-                      separators in the prefixpath yet because we do not
+-                      know (until reset_cifs_unix_caps is called later)
+-                      whether POSIX PATH CAP is available. We normalize
+-                      the / to \ after reset_cifs_unix_caps is called */
+-                      volume_info.prepath = NULL;
+-              } else
+-                      cifs_sb->prepathlen = 0;
+-              cifs_sb->mnt_uid = volume_info.linux_uid;
+-              cifs_sb->mnt_gid = volume_info.linux_gid;
+-              cifs_sb->mnt_file_mode = volume_info.file_mode;
+-              cifs_sb->mnt_dir_mode = volume_info.dir_mode;
+-              cFYI(1, ("file mode: 0x%x  dir mode: 0x%x",
+-                      cifs_sb->mnt_file_mode, cifs_sb->mnt_dir_mode));
+-
+-              if (volume_info.noperm)
+-                      cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NO_PERM;
+-              if (volume_info.setuids)
+-                      cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_SET_UID;
+-              if (volume_info.server_ino)
+-                      cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_SERVER_INUM;
+-              if (volume_info.remap)
+-                      cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_MAP_SPECIAL_CHR;
+-              if (volume_info.no_xattr)
+-                      cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NO_XATTR;
+-              if (volume_info.sfu_emul)
+-                      cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_UNX_EMUL;
+-              if (volume_info.nobrl)
+-                      cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NO_BRL;
+-              if (volume_info.cifs_acl)
+-                      cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_CIFS_ACL;
+-              if (volume_info.override_uid)
+-                      cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_OVERR_UID;
+-              if (volume_info.override_gid)
+-                      cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_OVERR_GID;
+-              if (volume_info.dynperm)
+-                      cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DYNPERM;
+-              if (volume_info.direct_io) {
+-                      cFYI(1, ("mounting share using direct i/o"));
+-                      cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DIRECT_IO;
+-              }
+-
+-              if ((volume_info.cifs_acl) && (volume_info.dynperm))
+-                      cERROR(1, ("mount option dynperm ignored if cifsacl "
+-                                 "mount option supported"));
+-
++              setup_cifs_sb(&volume_info, cifs_sb);
+               tcon =
+                   find_unc(sin_server.sin_addr.s_addr, volume_info.UNC,
+                            volume_info.username);
+               if (tcon) {
+                       cFYI(1, ("Found match on UNC path"));
+-                      /* we can have only one retry value for a connection
+-                         to a share so for resources mounted more than once
+-                         to the same server share the last value passed in
+-                         for the retry flag is used */
+-                      tcon->retry = volume_info.retry;
+-                      tcon->nocase = volume_info.nocase;
+                       if (tcon->seal != volume_info.seal)
+                               cERROR(1, ("transport encryption setting "
+                                          "conflicts with existing tid"));
+               } else {
+                       tcon = tconInfoAlloc();
+-                      if (tcon == NULL)
++                      if (tcon == NULL) {
+                               rc = -ENOMEM;
+-                      else {
+-                              /* check for null share name ie connecting to
+-                               * dfs root */
+-
+-                              /* BB check if this works for exactly length
+-                               * three strings */
+-                              if ((strchr(volume_info.UNC + 3, '\\') == NULL)
+-                                  && (strchr(volume_info.UNC + 3, '/') ==
+-                                      NULL)) {
+-/*                                    rc = connect_to_dfs_path(xid, pSesInfo,
+-                                              "", cifs_sb->local_nls,
+-                                              cifs_sb->mnt_cifs_flags &
+-                                                CIFS_MOUNT_MAP_SPECIAL_CHR);*/
+-                                      cFYI(1, ("DFS root not supported"));
+-                                      rc = -ENODEV;
+-                                      goto out;
+-                              } else {
+-                                      /* BB Do we need to wrap sesSem around
+-                                       * this TCon call and Unix SetFS as
+-                                       * we do on SessSetup and reconnect? */
+-                                      rc = CIFSTCon(xid, pSesInfo,
+-                                              volume_info.UNC,
+-                                              tcon, cifs_sb->local_nls);
+-                                      cFYI(1, ("CIFS Tcon rc = %d", rc));
+-                              }
+-                              if (!rc) {
+-                                      atomic_inc(&pSesInfo->inUse);
+-                                      tcon->retry = volume_info.retry;
+-                                      tcon->nocase = volume_info.nocase;
+-                                      tcon->seal = volume_info.seal;
+-                              }
++                              goto mount_fail_check;
+                       }
++
++                      /* check for null share name ie connect to dfs root */
++
++                      /* BB check if works for exactly length 3 strings */
++                      if ((strchr(volume_info.UNC + 3, '\\') == NULL)
++                          && (strchr(volume_info.UNC + 3, '/') == NULL)) {
++                              /* rc = connect_to_dfs_path(...) */
++                              cFYI(1, ("DFS root not supported"));
++                              rc = -ENODEV;
++                              goto mount_fail_check;
++                      } else {
++                              /* BB Do we need to wrap sesSem around
++                               * this TCon call and Unix SetFS as
++                               * we do on SessSetup and reconnect? */
++                              rc = CIFSTCon(xid, pSesInfo, volume_info.UNC,
++                                            tcon, cifs_sb->local_nls);
++                              cFYI(1, ("CIFS Tcon rc = %d", rc));
++                      }
++                      if (!rc) {
++                              atomic_inc(&pSesInfo->inUse);
++                              tcon->seal = volume_info.seal;
++                      } else
++                              goto mount_fail_check;
+               }
++
++              /* we can have only one retry value for a connection
++                 to a share so for resources mounted more than once
++                 to the same server share the last value passed in
++                 for the retry flag is used */
++              tcon->retry = volume_info.retry;
++              tcon->nocase = volume_info.nocase;
+       }
+       if (pSesInfo) {
+               if (pSesInfo->capabilities & CAP_LARGE_FILES) {
+@@ -2246,6 +2243,7 @@ cifs_mount(struct super_block *sb, struc
+       sb->s_time_gran = 100;
+ /* on error free sesinfo and tcon struct if needed */
++mount_fail_check:
+       if (rc) {
+               /* if session setup failed, use count is zero but
+               we still need to free cifsd thread */
+@@ -3499,6 +3497,7 @@ CIFSTCon(unsigned int xid, struct cifsSe
+       /* above now done in SendReceive */
+       if ((rc == 0) && (tcon != NULL)) {
+               tcon->tidStatus = CifsGood;
++              tcon->need_reconnect = false;
+               tcon->tid = smb_buffer_response->Tid;
+               bcc_ptr = pByteArea(smb_buffer_response);
+               length = strnlen(bcc_ptr, BCC(smb_buffer_response) - 2);
+@@ -3730,6 +3729,7 @@ int cifs_setup_session(unsigned int xid,
+       } else {
+               cFYI(1, ("CIFS Session Established successfully"));
+                       pSesInfo->status = CifsGood;
++                      pSesInfo->need_reconnect = false;
+       }
+ ss_err_exit:
+--- a/fs/cifs/file.c
++++ b/fs/cifs/file.c
+@@ -493,7 +493,7 @@ int cifs_close(struct inode *inode, stru
+               if (pTcon) {
+                       /* no sense reconnecting to close a file that is
+                          already closed */
+-                      if (pTcon->tidStatus != CifsNeedReconnect) {
++                      if (!pTcon->need_reconnect) {
+                               timeout = 2;
+                               while ((atomic_read(&pSMBFile->wrtPending) != 0)
+                                       && (timeout <= 2048)) {
diff --git a/queue-2.6.27/cifs-minor-cleanup-to-cifs_mount.patch b/queue-2.6.27/cifs-minor-cleanup-to-cifs_mount.patch
new file mode 100644 (file)
index 0000000..f6193d7
--- /dev/null
@@ -0,0 +1,127 @@
+From sjayaraman@suse.de  Wed Dec  3 09:40:26 2008
+From: Steve French <sfrench@us.ibm.com>
+Date: Fri, 21 Nov 2008 14:23:44 +0530
+Subject: cifs: minor cleanup to cifs_mount
+To: stable@kernel.org
+Cc: Steve French <smfrench@gmail.com>
+Message-ID: <49267718.9010206@suse.de>
+
+From: Steve French <sfrench@us.ibm.com>
+
+commit d82c2df54e2f7e447476350848d8eccc8d2fe46a upstream
+
+Signed-off-by: Steve French <sfrench@us.ibm.com>
+Cc: Suresh Jayaraman <sjayaraman@suse.de>
+Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
+
+
+---
+ fs/cifs/connect.c |   74 ++++++++++++++++++++++++------------------------------
+ 1 file changed, 34 insertions(+), 40 deletions(-)
+
+--- a/fs/cifs/connect.c
++++ b/fs/cifs/connect.c
+@@ -1357,7 +1357,6 @@ cifs_find_tcp_session(struct sockaddr *a
+       list_for_each(tmp, &cifs_tcp_ses_list) {
+               server = list_entry(tmp, struct TCP_Server_Info,
+                                   tcp_ses_list);
+-
+               /*
+                * the demux thread can exit on its own while still in CifsNew
+                * so don't accept any sockets in that state. Since the
+@@ -1378,6 +1377,7 @@ cifs_find_tcp_session(struct sockaddr *a
+               ++server->srv_count;
+               write_unlock(&cifs_tcp_ses_lock);
++              cFYI(1, ("Existing tcp session with server found"));
+               return server;
+       }
+       write_unlock(&cifs_tcp_ses_lock);
+@@ -2063,9 +2063,7 @@ cifs_mount(struct super_block *sb, struc
+       }
+       srvTcp = cifs_find_tcp_session(&addr);
+-      if (srvTcp) {
+-              cFYI(1, ("Existing tcp session with server found"));
+-      } else {        /* create socket */
++      if (!srvTcp) { /* create socket */
+               if (addr.sa_family == AF_INET6) {
+                       cFYI(1, ("attempting ipv6 connect"));
+                       /* BB should we allow ipv6 on port 139? */
+@@ -2272,44 +2270,40 @@ mount_fail_check:
+                       cifs_put_smb_ses(pSesInfo);
+               else
+                       cifs_put_tcp_session(srvTcp);
+-      } else {
+-              atomic_inc(&tcon->useCount);
+-              cifs_sb->tcon = tcon;
+-              tcon->ses = pSesInfo;
+-
+-              /* do not care if following two calls succeed - informational */
+-              if (!tcon->ipc) {
+-                      CIFSSMBQFSDeviceInfo(xid, tcon);
+-                      CIFSSMBQFSAttributeInfo(xid, tcon);
+-              }
++              goto out;
++      }
++      atomic_inc(&tcon->useCount);
++      cifs_sb->tcon = tcon;
++      tcon->ses = pSesInfo;
++
++      /* do not care if following two calls succeed - informational */
++      if (!tcon->ipc) {
++              CIFSSMBQFSDeviceInfo(xid, tcon);
++              CIFSSMBQFSAttributeInfo(xid, tcon);
++      }
+-              /* tell server which Unix caps we support */
+-              if (tcon->ses->capabilities & CAP_UNIX)
+-                      /* reset of caps checks mount to see if unix extensions
+-                         disabled for just this mount */
+-                      reset_cifs_unix_caps(xid, tcon, sb, &volume_info);
+-              else
+-                      tcon->unix_ext = 0; /* server does not support them */
++      /* tell server which Unix caps we support */
++      if (tcon->ses->capabilities & CAP_UNIX)
++              /* reset of caps checks mount to see if unix extensions
++                 disabled for just this mount */
++              reset_cifs_unix_caps(xid, tcon, sb, &volume_info);
++      else
++              tcon->unix_ext = 0; /* server does not support them */
+-              /* convert forward to back slashes in prepath here if needed */
+-              if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) == 0)
+-                      convert_delimiter(cifs_sb->prepath,
+-                                        CIFS_DIR_SEP(cifs_sb));
+-
+-              if ((tcon->unix_ext == 0) && (cifs_sb->rsize > (1024 * 127))) {
+-                      cifs_sb->rsize = 1024 * 127;
+-                      cFYI(DBG2,
+-                              ("no very large read support, rsize now 127K"));
+-              }
+-              if (!(tcon->ses->capabilities & CAP_LARGE_WRITE_X))
+-                      cifs_sb->wsize = min(cifs_sb->wsize,
+-                                           (tcon->ses->server->maxBuf -
+-                                            MAX_CIFS_HDR_SIZE));
+-              if (!(tcon->ses->capabilities & CAP_LARGE_READ_X))
+-                      cifs_sb->rsize = min(cifs_sb->rsize,
+-                                           (tcon->ses->server->maxBuf -
+-                                            MAX_CIFS_HDR_SIZE));
+-      }
++      /* convert forward to back slashes in prepath here if needed */
++      if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) == 0)
++              convert_delimiter(cifs_sb->prepath, CIFS_DIR_SEP(cifs_sb));
++
++      if ((tcon->unix_ext == 0) && (cifs_sb->rsize > (1024 * 127))) {
++              cifs_sb->rsize = 1024 * 127;
++              cFYI(DBG2, ("no very large read support, rsize now 127K"));
++      }
++      if (!(tcon->ses->capabilities & CAP_LARGE_WRITE_X))
++              cifs_sb->wsize = min(cifs_sb->wsize,
++                             (tcon->ses->server->maxBuf - MAX_CIFS_HDR_SIZE));
++      if (!(tcon->ses->capabilities & CAP_LARGE_READ_X))
++              cifs_sb->rsize = min(cifs_sb->rsize,
++                             (tcon->ses->server->maxBuf - MAX_CIFS_HDR_SIZE));
+       /* volume_info.password is freed above when existing session found
+       (in which case it is not needed anymore) but when new sesion is created
diff --git a/queue-2.6.27/cifs-prevent-cifs_writepages-from-skipping-unwritten-pages.patch b/queue-2.6.27/cifs-prevent-cifs_writepages-from-skipping-unwritten-pages.patch
new file mode 100644 (file)
index 0000000..943c8a8
--- /dev/null
@@ -0,0 +1,57 @@
+From sjayaraman@suse.de  Wed Dec  3 09:43:30 2008
+From: Dave Kleikamp <shaggy@linux.vnet.ibm.com>
+Date: Fri, 21 Nov 2008 14:24:20 +0530
+Subject: cifs: prevent cifs_writepages() from skipping unwritten pages
+To: stable@kernel.org
+Cc: Steve French <smfrench@gmail.com>, Dave Kleikamp <shaggy@linux.vnet.ibm.com>, Shirish S Pargaonkar <shirishp@us.ibm.com>, Jeff Layton <jlayton@redhat.com>
+Message-ID: <4926773C.6010409@suse.de>
+
+From: Dave Kleikamp <shaggy@linux.vnet.ibm.com>
+
+commit b066a48c9532243894f93a06ca5a0ee2cc21a8dc upstream
+
+prevent cifs_writepages() from skipping unwritten pages
+
+Fixes a data corruption under heavy stress in which pages could be left
+dirty after all open instances of a inode have been closed.
+
+In order to write contiguous pages whenever possible, cifs_writepages()
+asks pagevec_lookup_tag() for more pages than it may write at one time.
+Normally, it then resets index just past the last page written before calling
+pagevec_lookup_tag() again.
+
+If cifs_writepages() can't write the first page returned, it wasn't resetting
+index, and the next call to pagevec_lookup_tag() resulted in skipping all of
+the pages it previously returned, even though cifs_writepages() did nothing
+with them.  This can result in data loss when the file descriptor is about
+to be closed.
+
+This patch ensures that index gets set back to the next returned page so
+that none get skipped.
+
+Signed-off-by: Dave Kleikamp <shaggy@linux.vnet.ibm.com>
+Acked-by: Jeff Layton <jlayton@redhat.com>
+Cc: Shirish S Pargaonkar <shirishp@us.ibm.com>
+Signed-off-by: Steve French <sfrench@us.ibm.com>
+Cc: Suresh Jayaraman <sjayaraman@suse.de>
+Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
+
+
+---
+ fs/cifs/file.c |    5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+--- a/fs/cifs/file.c
++++ b/fs/cifs/file.c
+@@ -1396,7 +1396,10 @@ retry:
+                       if ((wbc->nr_to_write -= n_iov) <= 0)
+                               done = 1;
+                       index = next;
+-              }
++              } else
++                      /* Need to re-find the pages we skipped */
++                      index = pvec.pages[0]->index + 1;
++
+               pagevec_release(&pvec);
+       }
+       if (!scanned && !done) {
diff --git a/queue-2.6.27/cifs-reinstate-sharing-of-smb-sessions-sans-races.patch b/queue-2.6.27/cifs-reinstate-sharing-of-smb-sessions-sans-races.patch
new file mode 100644 (file)
index 0000000..15379d1
--- /dev/null
@@ -0,0 +1,657 @@
+From sjayaraman@suse.de  Wed Dec  3 09:31:32 2008
+From: Jeff Layton <jlayton@redhat.com>
+Date: Fri, 21 Nov 2008 14:23:30 +0530
+Subject: cifs: reinstate sharing of SMB sessions sans races
+To: stable@kernel.org
+Cc: Steve French <smfrench@gmail.com>, Jeff Layton <jlayton@redhat.com>
+Message-ID: <4926770A.7090107@suse.de>
+
+From: Jeff Layton <jlayton@redhat.com>
+
+commit 14fbf50d695207754daeb96270b3027a3821121f upstream
+
+We do this by abandoning the global list of SMB sessions and instead
+moving to a per-server list. This entails adding a new list head to the
+TCP_Server_Info struct. The refcounting for the cifsSesInfo is moved to
+a non-atomic variable. We have to protect it by a lock anyway, so there's
+no benefit to making it an atomic. The list and refcount are protected
+by the global cifs_tcp_ses_lock.
+
+The patch also adds a new routines to find and put SMB sessions and
+that properly take and put references under the lock.
+
+Signed-off-by: Jeff Layton <jlayton@redhat.com>
+Signed-off-by: Steve French <sfrench@us.ibm.com>
+Cc: Suresh Jayaraman <sjayaraman@suse.de>
+Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
+
+---
+ fs/cifs/cifs_debug.c |   53 ++++++------
+ fs/cifs/cifsfs.c     |   17 +--
+ fs/cifs/cifsglob.h   |    6 -
+ fs/cifs/cifsproto.h  |    1 
+ fs/cifs/cifssmb.c    |   22 +---
+ fs/cifs/connect.c    |  225 ++++++++++++++++++++++++++++-----------------------
+ fs/cifs/misc.c       |   16 +--
+ 7 files changed, 174 insertions(+), 166 deletions(-)
+
+--- a/fs/cifs/cifs_debug.c
++++ b/fs/cifs/cifs_debug.c
+@@ -107,9 +107,9 @@ void cifs_dump_mids(struct TCP_Server_In
+ #ifdef CONFIG_PROC_FS
+ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
+ {
+-      struct list_head *tmp;
+-      struct list_head *tmp1;
++      struct list_head *tmp, *tmp2, *tmp3;
+       struct mid_q_entry *mid_entry;
++      struct TCP_Server_Info *server;
+       struct cifsSesInfo *ses;
+       struct cifsTconInfo *tcon;
+       int i;
+@@ -122,43 +122,45 @@ static int cifs_debug_data_proc_show(str
+       seq_printf(m, "Servers:");
+       i = 0;
+-      read_lock(&GlobalSMBSeslock);
+-      list_for_each(tmp, &GlobalSMBSessionList) {
++      read_lock(&cifs_tcp_ses_lock);
++      list_for_each(tmp, &cifs_tcp_ses_list) {
++              server = list_entry(tmp, struct TCP_Server_Info,
++                                  tcp_ses_list);
+               i++;
+-              ses = list_entry(tmp, struct cifsSesInfo, cifsSessionList);
+-              if ((ses->serverDomain == NULL) || (ses->serverOS == NULL) ||
+-                 (ses->serverNOS == NULL)) {
+-                      seq_printf(m, "\nentry for %s not fully "
+-                                      "displayed\n\t", ses->serverName);
+-              } else {
+-                      seq_printf(m,
++              list_for_each(tmp2, &server->smb_ses_list) {
++                      ses = list_entry(tmp2, struct cifsSesInfo,
++                                       smb_ses_list);
++                      if ((ses->serverDomain == NULL) ||
++                              (ses->serverOS == NULL) ||
++                              (ses->serverNOS == NULL)) {
++                              seq_printf(m, "\nentry for %s not fully "
++                                         "displayed\n\t", ses->serverName);
++                      } else {
++                              seq_printf(m,
+                                   "\n%d) Name: %s  Domain: %s Mounts: %d OS:"
+                                   " %s  \n\tNOS: %s\tCapability: 0x%x\n\tSMB"
+                                   " session status: %d\t",
+                               i, ses->serverName, ses->serverDomain,
+-                              atomic_read(&ses->inUse),
+-                              ses->serverOS, ses->serverNOS,
++                              ses->ses_count, ses->serverOS, ses->serverNOS,
+                               ses->capabilities, ses->status);
+-              }
+-              if (ses->server) {
++                      }
+                       seq_printf(m, "TCP status: %d\n\tLocal Users To "
+-                                  "Server: %d SecMode: 0x%x Req On Wire: %d",
+-                              ses->server->tcpStatus,
+-                              ses->server->srv_count,
+-                              ses->server->secMode,
+-                              atomic_read(&ses->server->inFlight));
++                                 "Server: %d SecMode: 0x%x Req On Wire: %d",
++                                 server->tcpStatus, server->srv_count,
++                                 server->secMode,
++                                 atomic_read(&server->inFlight));
+ #ifdef CONFIG_CIFS_STATS2
+                       seq_printf(m, " In Send: %d In MaxReq Wait: %d",
+-                              atomic_read(&ses->server->inSend),
+-                              atomic_read(&ses->server->num_waiters));
++                              atomic_read(&server->inSend),
++                              atomic_read(&server->num_waiters));
+ #endif
+                       seq_puts(m, "\nMIDs:\n");
+                       spin_lock(&GlobalMid_Lock);
+-                      list_for_each(tmp1, &ses->server->pending_mid_q) {
+-                              mid_entry = list_entry(tmp1, struct
++                      list_for_each(tmp3, &server->pending_mid_q) {
++                              mid_entry = list_entry(tmp3, struct
+                                       mid_q_entry,
+                                       qhead);
+                               seq_printf(m, "State: %d com: %d pid:"
+@@ -171,9 +173,8 @@ static int cifs_debug_data_proc_show(str
+                       }
+                       spin_unlock(&GlobalMid_Lock);
+               }
+-
+       }
+-      read_unlock(&GlobalSMBSeslock);
++      read_unlock(&cifs_tcp_ses_lock);
+       seq_putc(m, '\n');
+       seq_puts(m, "Shares:");
+--- a/fs/cifs/cifsfs.c
++++ b/fs/cifs/cifsfs.c
+@@ -985,24 +985,24 @@ static int cifs_oplock_thread(void *dumm
+ static int cifs_dnotify_thread(void *dummyarg)
+ {
+       struct list_head *tmp;
+-      struct cifsSesInfo *ses;
++      struct TCP_Server_Info *server;
+       do {
+               if (try_to_freeze())
+                       continue;
+               set_current_state(TASK_INTERRUPTIBLE);
+               schedule_timeout(15*HZ);
+-              read_lock(&GlobalSMBSeslock);
+               /* check if any stuck requests that need
+                  to be woken up and wakeq so the
+                  thread can wake up and error out */
+-              list_for_each(tmp, &GlobalSMBSessionList) {
+-                      ses = list_entry(tmp, struct cifsSesInfo,
+-                              cifsSessionList);
+-                      if (ses->server && atomic_read(&ses->server->inFlight))
+-                              wake_up_all(&ses->server->response_q);
++              read_lock(&cifs_tcp_ses_lock);
++              list_for_each(tmp, &cifs_tcp_ses_list) {
++                      server = list_entry(tmp, struct TCP_Server_Info,
++                                       tcp_ses_list);
++                      if (atomic_read(&server->inFlight))
++                              wake_up_all(&server->response_q);
+               }
+-              read_unlock(&GlobalSMBSeslock);
++              read_unlock(&cifs_tcp_ses_lock);
+       } while (!kthread_should_stop());
+       return 0;
+@@ -1014,7 +1014,6 @@ init_cifs(void)
+       int rc = 0;
+       cifs_proc_init();
+       INIT_LIST_HEAD(&cifs_tcp_ses_list);
+-      INIT_LIST_HEAD(&GlobalSMBSessionList); /* BB to be removed by jl */
+       INIT_LIST_HEAD(&GlobalTreeConnectionList); /* BB to be removed by jl */
+       INIT_LIST_HEAD(&GlobalOplock_Q);
+ #ifdef CONFIG_CIFS_EXPERIMENTAL
+--- a/fs/cifs/cifsglob.h
++++ b/fs/cifs/cifsglob.h
+@@ -195,14 +195,14 @@ struct cifsUidInfo {
+  * Session structure.  One of these for each uid session with a particular host
+  */
+ struct cifsSesInfo {
+-      struct list_head cifsSessionList;
++      struct list_head smb_ses_list;
+       struct list_head tcon_list;
+       struct semaphore sesSem;
+ #if 0
+       struct cifsUidInfo *uidInfo;    /* pointer to user info */
+ #endif
+       struct TCP_Server_Info *server; /* pointer to server info */
+-      atomic_t inUse; /* # of mounts (tree connections) on this ses */
++      int ses_count;          /* reference counter */
+       enum statusEnum status;
+       unsigned overrideSecFlg;  /* if non-zero override global sec flags */
+       __u16 ipc_tid;          /* special tid for connection to IPC share */
+@@ -600,8 +600,6 @@ GLOBAL_EXTERN struct list_head             cifs_tcp
+ /* protects cifs_tcp_ses_list and srv_count for each tcp session */
+ GLOBAL_EXTERN rwlock_t                cifs_tcp_ses_lock;
+-
+-GLOBAL_EXTERN struct list_head GlobalSMBSessionList; /* BB to be removed by jl*/
+ GLOBAL_EXTERN struct list_head GlobalTreeConnectionList; /* BB to be removed */
+ GLOBAL_EXTERN rwlock_t GlobalSMBSeslock;  /* protects list inserts on 3 above */
+--- a/fs/cifs/cifsproto.h
++++ b/fs/cifs/cifsproto.h
+@@ -102,7 +102,6 @@ extern void acl_to_uid_mode(struct inode
+                           const __u16 *pfid);
+ extern int mode_to_acl(struct inode *inode, const char *path, __u64);
+-extern void cifs_put_tcp_session(struct TCP_Server_Info *server);
+ extern int cifs_mount(struct super_block *, struct cifs_sb_info *, char *,
+                       const char *);
+ extern int cifs_umount(struct super_block *, struct cifs_sb_info *);
+--- a/fs/cifs/cifssmb.c
++++ b/fs/cifs/cifssmb.c
+@@ -799,20 +799,16 @@ CIFSSMBLogoff(const int xid, struct cifs
+       int rc = 0;
+       cFYI(1, ("In SMBLogoff for session disconnect"));
+-      if (ses)
+-              down(&ses->sesSem);
+-      else
+-              return -EIO;
+-
+-      atomic_dec(&ses->inUse);
+-      if (atomic_read(&ses->inUse) > 0) {
+-              up(&ses->sesSem);
+-              return -EBUSY;
+-      }
+-      if (ses->server == NULL)
++      /*
++       * BB: do we need to check validity of ses and server? They should
++       * always be valid since we have an active reference. If not, that
++       * should probably be a BUG()
++       */
++      if (!ses || !ses->server)
+               return -EIO;
++      down(&ses->sesSem);
+       if (ses->need_reconnect)
+               goto session_already_dead; /* no need to send SMBlogoff if uid
+                                             already closed due to reconnect */
+@@ -833,10 +829,6 @@ CIFSSMBLogoff(const int xid, struct cifs
+       pSMB->AndXCommand = 0xFF;
+       rc = SendReceiveNoRsp(xid, ses, (struct smb_hdr *) pSMB, 0);
+ session_already_dead:
+-      if (ses->server) {
+-              cifs_put_tcp_session(ses->server);
+-              rc = 0;
+-      }
+       up(&ses->sesSem);
+       /* if session dead then we do not need to do ulogoff,
+--- a/fs/cifs/connect.c
++++ b/fs/cifs/connect.c
+@@ -142,23 +142,18 @@ cifs_reconnect(struct TCP_Server_Info *s
+       /* before reconnecting the tcp session, mark the smb session (uid)
+               and the tid bad so they are not used until reconnected */
+-      read_lock(&GlobalSMBSeslock);
+-      list_for_each(tmp, &GlobalSMBSessionList) {
+-              ses = list_entry(tmp, struct cifsSesInfo, cifsSessionList);
+-              if (ses->server) {
+-                      if (ses->server == server) {
+-                              ses->need_reconnect = true;
+-                              ses->ipc_tid = 0;
+-                      }
+-              }
+-              /* else tcp and smb sessions need reconnection */
++      read_lock(&cifs_tcp_ses_lock);
++      list_for_each(tmp, &server->smb_ses_list) {
++              ses = list_entry(tmp, struct cifsSesInfo, smb_ses_list);
++              ses->need_reconnect = true;
++              ses->ipc_tid = 0;
+       }
++      read_unlock(&cifs_tcp_ses_lock);
+       list_for_each(tmp, &GlobalTreeConnectionList) {
+               tcon = list_entry(tmp, struct cifsTconInfo, cifsConnectionList);
+               if ((tcon->ses) && (tcon->ses->server == server))
+                       tcon->need_reconnect = true;
+       }
+-      read_unlock(&GlobalSMBSeslock);
+       /* do not want to be sending data on a socket we are freeing */
+       down(&server->tcpSem);
+       if (server->ssocket) {
+@@ -702,29 +697,29 @@ multi_t2_fnd:
+       if (smallbuf) /* no sense logging a debug message if NULL */
+               cifs_small_buf_release(smallbuf);
+-      read_lock(&GlobalSMBSeslock);
++      /*
++       * BB: we shouldn't have to do any of this. It shouldn't be
++       * possible to exit from the thread with active SMB sessions
++       */
++      read_lock(&cifs_tcp_ses_lock);
+       if (list_empty(&server->pending_mid_q)) {
+               /* loop through server session structures attached to this and
+                   mark them dead */
+-              list_for_each(tmp, &GlobalSMBSessionList) {
+-                      ses =
+-                          list_entry(tmp, struct cifsSesInfo,
+-                                     cifsSessionList);
+-                      if (ses->server == server) {
+-                              ses->status = CifsExiting;
+-                              ses->server = NULL;
+-                      }
++              list_for_each(tmp, &server->smb_ses_list) {
++                      ses = list_entry(tmp, struct cifsSesInfo,
++                                       smb_ses_list);
++                      ses->status = CifsExiting;
++                      ses->server = NULL;
+               }
+-              read_unlock(&GlobalSMBSeslock);
++              read_unlock(&cifs_tcp_ses_lock);
+       } else {
+               /* although we can not zero the server struct pointer yet,
+               since there are active requests which may depnd on them,
+               mark the corresponding SMB sessions as exiting too */
+-              list_for_each(tmp, &GlobalSMBSessionList) {
++              list_for_each(tmp, &server->smb_ses_list) {
+                       ses = list_entry(tmp, struct cifsSesInfo,
+-                                       cifsSessionList);
+-                      if (ses->server == server)
+-                              ses->status = CifsExiting;
++                                       smb_ses_list);
++                      ses->status = CifsExiting;
+               }
+               spin_lock(&GlobalMid_Lock);
+@@ -739,7 +734,7 @@ multi_t2_fnd:
+                       }
+               }
+               spin_unlock(&GlobalMid_Lock);
+-              read_unlock(&GlobalSMBSeslock);
++              read_unlock(&cifs_tcp_ses_lock);
+               /* 1/8th of sec is more than enough time for them to exit */
+               msleep(125);
+       }
+@@ -761,14 +756,13 @@ multi_t2_fnd:
+       if there are any pointing to this (e.g
+       if a crazy root user tried to kill cifsd
+       kernel thread explicitly this might happen) */
+-      write_lock(&GlobalSMBSeslock);
+-      list_for_each(tmp, &GlobalSMBSessionList) {
+-              ses = list_entry(tmp, struct cifsSesInfo,
+-                              cifsSessionList);
+-              if (ses->server == server)
+-                      ses->server = NULL;
++      /* BB: This shouldn't be necessary, see above */
++      read_lock(&cifs_tcp_ses_lock);
++      list_for_each(tmp, &server->smb_ses_list) {
++              ses = list_entry(tmp, struct cifsSesInfo, smb_ses_list);
++              ses->server = NULL;
+       }
+-      write_unlock(&GlobalSMBSeslock);
++      read_unlock(&cifs_tcp_ses_lock);
+       kfree(server->hostname);
+       kfree(server);
+@@ -1390,7 +1384,7 @@ cifs_find_tcp_session(struct sockaddr *a
+       return NULL;
+ }
+-void
++static void
+ cifs_put_tcp_session(struct TCP_Server_Info *server)
+ {
+       struct task_struct *task;
+@@ -1413,6 +1407,50 @@ cifs_put_tcp_session(struct TCP_Server_I
+               force_sig(SIGKILL, task);
+ }
++static struct cifsSesInfo *
++cifs_find_smb_ses(struct TCP_Server_Info *server, char *username)
++{
++      struct list_head *tmp;
++      struct cifsSesInfo *ses;
++
++      write_lock(&cifs_tcp_ses_lock);
++      list_for_each(tmp, &server->smb_ses_list) {
++              ses = list_entry(tmp, struct cifsSesInfo, smb_ses_list);
++              if (strncmp(ses->userName, username, MAX_USERNAME_SIZE))
++                      continue;
++
++              ++ses->ses_count;
++              write_unlock(&cifs_tcp_ses_lock);
++              return ses;
++      }
++      write_unlock(&cifs_tcp_ses_lock);
++      return NULL;
++}
++
++static void
++cifs_put_smb_ses(struct cifsSesInfo *ses)
++{
++      int xid;
++      struct TCP_Server_Info *server = ses->server;
++
++      write_lock(&cifs_tcp_ses_lock);
++      if (--ses->ses_count > 0) {
++              write_unlock(&cifs_tcp_ses_lock);
++              return;
++      }
++
++      list_del_init(&ses->smb_ses_list);
++      write_unlock(&cifs_tcp_ses_lock);
++
++      if (ses->status == CifsGood) {
++              xid = GetXid();
++              CIFSSMBLogoff(xid, ses);
++              _FreeXid(xid);
++      }
++      sesInfoFree(ses);
++      cifs_put_tcp_session(server);
++}
++
+ int
+ get_dfs_path(int xid, struct cifsSesInfo *pSesInfo, const char *old_path,
+            const struct nls_table *nls_codepage, unsigned int *pnum_referrals,
+@@ -1945,7 +1983,6 @@ cifs_mount(struct super_block *sb, struc
+       struct sockaddr_in6 *sin_server6 = (struct sockaddr_in6 *) &addr;
+       struct smb_vol volume_info;
+       struct cifsSesInfo *pSesInfo = NULL;
+-      struct cifsSesInfo *existingCifsSes = NULL;
+       struct cifsTconInfo *tcon = NULL;
+       struct TCP_Server_Info *srvTcp = NULL;
+@@ -2099,6 +2136,7 @@ cifs_mount(struct super_block *sb, struc
+                               volume_info.target_rfc1001_name, 16);
+                       srvTcp->sequence_number = 0;
+                       INIT_LIST_HEAD(&srvTcp->tcp_ses_list);
++                      INIT_LIST_HEAD(&srvTcp->smb_ses_list);
+                       ++srvTcp->srv_count;
+                       write_lock(&cifs_tcp_ses_lock);
+                       list_add(&srvTcp->tcp_ses_list,
+@@ -2107,10 +2145,16 @@ cifs_mount(struct super_block *sb, struc
+               }
+       }
+-      if (existingCifsSes) {
+-              pSesInfo = existingCifsSes;
++      pSesInfo = cifs_find_smb_ses(srvTcp, volume_info.username);
++      if (pSesInfo) {
+               cFYI(1, ("Existing smb sess found (status=%d)",
+                       pSesInfo->status));
++              /*
++               * The existing SMB session already has a reference to srvTcp,
++               * so we can put back the extra one we got before
++               */
++              cifs_put_tcp_session(srvTcp);
++
+               down(&pSesInfo->sesSem);
+               if (pSesInfo->need_reconnect) {
+                       cFYI(1, ("Session needs reconnect"));
+@@ -2121,41 +2165,44 @@ cifs_mount(struct super_block *sb, struc
+       } else if (!rc) {
+               cFYI(1, ("Existing smb sess not found"));
+               pSesInfo = sesInfoAlloc();
+-              if (pSesInfo == NULL)
++              if (pSesInfo == NULL) {
+                       rc = -ENOMEM;
+-              else {
+-                      pSesInfo->server = srvTcp;
+-                      sprintf(pSesInfo->serverName, "%u.%u.%u.%u",
+-                              NIPQUAD(sin_server->sin_addr.s_addr));
++                      goto mount_fail_check;
+               }
+-              if (!rc) {
+-                      /* volume_info.password freed at unmount */
+-                      if (volume_info.password) {
+-                              pSesInfo->password = volume_info.password;
+-                              /* set to NULL to prevent freeing on exit */
+-                              volume_info.password = NULL;
+-                      }
+-                      if (volume_info.username)
+-                              strncpy(pSesInfo->userName,
+-                                      volume_info.username,
+-                                      MAX_USERNAME_SIZE);
+-                      if (volume_info.domainname) {
+-                              int len = strlen(volume_info.domainname);
+-                              pSesInfo->domainName =
+-                                      kmalloc(len + 1, GFP_KERNEL);
+-                              if (pSesInfo->domainName)
+-                                      strcpy(pSesInfo->domainName,
+-                                              volume_info.domainname);
+-                      }
+-                      pSesInfo->linux_uid = volume_info.linux_uid;
+-                      pSesInfo->overrideSecFlg = volume_info.secFlg;
+-                      down(&pSesInfo->sesSem);
+-                      /* BB FIXME need to pass vol->secFlgs BB */
+-                      rc = cifs_setup_session(xid, pSesInfo,
+-                                              cifs_sb->local_nls);
+-                      up(&pSesInfo->sesSem);
++              /* new SMB session uses our srvTcp ref */
++              pSesInfo->server = srvTcp;
++              sprintf(pSesInfo->serverName, "%u.%u.%u.%u",
++                      NIPQUAD(sin_server->sin_addr.s_addr));
++
++              write_lock(&cifs_tcp_ses_lock);
++              list_add(&pSesInfo->smb_ses_list, &srvTcp->smb_ses_list);
++              write_unlock(&cifs_tcp_ses_lock);
++
++              /* volume_info.password freed at unmount */
++              if (volume_info.password) {
++                      pSesInfo->password = volume_info.password;
++                      /* set to NULL to prevent freeing on exit */
++                      volume_info.password = NULL;
++              }
++              if (volume_info.username)
++                      strncpy(pSesInfo->userName, volume_info.username,
++                              MAX_USERNAME_SIZE);
++              if (volume_info.domainname) {
++                      int len = strlen(volume_info.domainname);
++                      pSesInfo->domainName = kmalloc(len + 1, GFP_KERNEL);
++                      if (pSesInfo->domainName)
++                              strcpy(pSesInfo->domainName,
++                                      volume_info.domainname);
+               }
++              pSesInfo->linux_uid = volume_info.linux_uid;
++              pSesInfo->overrideSecFlg = volume_info.secFlg;
++              down(&pSesInfo->sesSem);
++
++              /* BB FIXME need to pass vol->secFlgs BB */
++              rc = cifs_setup_session(xid, pSesInfo,
++                                      cifs_sb->local_nls);
++              up(&pSesInfo->sesSem);
+       }
+       /* search for existing tcon to this server share */
+@@ -2190,11 +2237,9 @@ cifs_mount(struct super_block *sb, struc
+                                             tcon, cifs_sb->local_nls);
+                               cFYI(1, ("CIFS Tcon rc = %d", rc));
+                       }
+-                      if (!rc) {
+-                              atomic_inc(&pSesInfo->inUse);
+-                              tcon->seal = volume_info.seal;
+-                      } else
++                      if (rc)
+                               goto mount_fail_check;
++                      tcon->seal = volume_info.seal;
+               }
+               /* we can have only one retry value for a connection
+@@ -2214,7 +2259,7 @@ cifs_mount(struct super_block *sb, struc
+       /* BB FIXME fix time_gran to be larger for LANMAN sessions */
+       sb->s_time_gran = 100;
+-/* on error free sesinfo and tcon struct if needed */
++      /* on error free sesinfo and tcon struct if needed */
+ mount_fail_check:
+       if (rc) {
+                /* If find_unc succeeded then rc == 0 so we can not end */
+@@ -2222,21 +2267,11 @@ mount_fail_check:
+               if (tcon)
+                       tconInfoFree(tcon);
+-              if (existingCifsSes == NULL) {
+-                      if (pSesInfo) {
+-                              if ((pSesInfo->server) &&
+-                                  (pSesInfo->status == CifsGood))
+-                                      CIFSSMBLogoff(xid, pSesInfo);
+-                              else {
+-                                      cFYI(1, ("No session or bad tcon"));
+-                              }
+-                                      if (pSesInfo->server)
+-                                              cifs_put_tcp_session(
+-                                                      pSesInfo->server);
+-                              sesInfoFree(pSesInfo);
+-                              /* pSesInfo = NULL; */
+-                      }
+-              }
++              /* should also end up putting our tcp session ref if needed */
++              if (pSesInfo)
++                      cifs_put_smb_ses(pSesInfo);
++              else
++                      cifs_put_tcp_session(srvTcp);
+       } else {
+               atomic_inc(&tcon->useCount);
+               cifs_sb->tcon = tcon;
+@@ -3532,17 +3567,7 @@ cifs_umount(struct super_block *sb, stru
+               }
+               DeleteTconOplockQEntries(cifs_sb->tcon);
+               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) {
+-                              FreeXid(xid);
+-                              return 0;
+-                      }
+-              } else
+-                      cFYI(1, ("No session or bad tcon"));
++              cifs_put_smb_ses(ses);
+       }
+       cifs_sb->tcon = NULL;
+@@ -3550,8 +3575,6 @@ cifs_umount(struct super_block *sb, stru
+       cifs_sb->prepathlen = 0;
+       cifs_sb->prepath = NULL;
+       kfree(tmp);
+-      if (ses)
+-              sesInfoFree(ses);
+       FreeXid(xid);
+       return rc;
+--- a/fs/cifs/misc.c
++++ b/fs/cifs/misc.c
+@@ -75,12 +75,11 @@ sesInfoAlloc(void)
+       ret_buf = kzalloc(sizeof(struct cifsSesInfo), GFP_KERNEL);
+       if (ret_buf) {
+-              write_lock(&GlobalSMBSeslock);
+               atomic_inc(&sesInfoAllocCount);
+               ret_buf->status = CifsNew;
+-              list_add(&ret_buf->cifsSessionList, &GlobalSMBSessionList);
++              ++ret_buf->ses_count;
++              INIT_LIST_HEAD(&ret_buf->smb_ses_list);
+               init_MUTEX(&ret_buf->sesSem);
+-              write_unlock(&GlobalSMBSeslock);
+       }
+       return ret_buf;
+ }
+@@ -93,10 +92,7 @@ sesInfoFree(struct cifsSesInfo *buf_to_f
+               return;
+       }
+-      write_lock(&GlobalSMBSeslock);
+       atomic_dec(&sesInfoAllocCount);
+-      list_del(&buf_to_free->cifsSessionList);
+-      write_unlock(&GlobalSMBSeslock);
+       kfree(buf_to_free->serverOS);
+       kfree(buf_to_free->serverDomain);
+       kfree(buf_to_free->serverNOS);
+@@ -354,9 +350,9 @@ header_assemble(struct smb_hdr *buffer, 
+                               if (current->fsuid != treeCon->ses->linux_uid) {
+                                       cFYI(1, ("Multiuser mode and UID "
+                                                "did not match tcon uid"));
+-                                      read_lock(&GlobalSMBSeslock);
+-                                      list_for_each(temp_item, &GlobalSMBSessionList) {
+-                                              ses = list_entry(temp_item, struct cifsSesInfo, cifsSessionList);
++                                      read_lock(&cifs_tcp_ses_lock);
++                                      list_for_each(temp_item, &treeCon->ses->server->smb_ses_list) {
++                                              ses = list_entry(temp_item, struct cifsSesInfo, smb_ses_list);
+                                               if (ses->linux_uid == current->fsuid) {
+                                                       if (ses->server == treeCon->ses->server) {
+                                                               cFYI(1, ("found matching uid substitute right smb_uid"));
+@@ -368,7 +364,7 @@ header_assemble(struct smb_hdr *buffer, 
+                                                       }
+                                               }
+                                       }
+-                                      read_unlock(&GlobalSMBSeslock);
++                                      read_unlock(&cifs_tcp_ses_lock);
+                               }
+                       }
+               }
diff --git a/queue-2.6.27/cifs-reinstate-sharing-of-tree-connections.patch b/queue-2.6.27/cifs-reinstate-sharing-of-tree-connections.patch
new file mode 100644 (file)
index 0000000..4f8ddd1
--- /dev/null
@@ -0,0 +1,765 @@
+From sjayaraman@suse.de  Wed Dec  3 09:41:15 2008
+From: Jeff Layton <jlayton@redhat.com>
+Date: Fri, 21 Nov 2008 14:23:55 +0530
+Subject: cifs: reinstate sharing of tree connections
+To: stable@kernel.org
+Cc: Steve French <smfrench@gmail.com>, Jeff Layton <jlayton@redhat.com>
+Message-ID: <49267723.7060701@suse.de>
+
+From: Jeff Layton <jlayton@redhat.com>
+
+commit f1987b44f642e96176adc88b7ce23a1d74806f89 upstream
+
+Use a similar approach to the SMB session sharing. Add a list of tcons
+attached to each SMB session. Move the refcount to non-atomic. Protect
+all of the above with the cifs_tcp_ses_lock. Add functions to
+properly find and put references to the tcons.
+
+Signed-off-by: Jeff Layton <jlayton@redhat.com>
+Signed-off-by: Steve French <sfrench@us.ibm.com>
+Cc: Suresh Jayaraman <sjayaraman@suse.de>
+Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
+
+---
+ fs/cifs/cifs_debug.c |  236 +++++++++++++++++++++++++++------------------------
+ fs/cifs/cifsfs.c     |    8 -
+ fs/cifs/cifsglob.h   |   13 +-
+ fs/cifs/cifssmb.c    |   43 ++-------
+ fs/cifs/connect.c    |   94 +++++++++++++-------
+ fs/cifs/misc.c       |   74 +++++++--------
+ 6 files changed, 249 insertions(+), 219 deletions(-)
+
+--- a/fs/cifs/cifs_debug.c
++++ b/fs/cifs/cifs_debug.c
+@@ -107,12 +107,13 @@ void cifs_dump_mids(struct TCP_Server_In
+ #ifdef CONFIG_PROC_FS
+ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
+ {
+-      struct list_head *tmp, *tmp2, *tmp3;
++      struct list_head *tmp1, *tmp2, *tmp3;
+       struct mid_q_entry *mid_entry;
+       struct TCP_Server_Info *server;
+       struct cifsSesInfo *ses;
+       struct cifsTconInfo *tcon;
+-      int i;
++      int i, j;
++      __u32 dev_type;
+       seq_puts(m,
+                   "Display Internal CIFS Data Structures for Debugging\n"
+@@ -123,8 +124,8 @@ static int cifs_debug_data_proc_show(str
+       i = 0;
+       read_lock(&cifs_tcp_ses_lock);
+-      list_for_each(tmp, &cifs_tcp_ses_list) {
+-              server = list_entry(tmp, struct TCP_Server_Info,
++      list_for_each(tmp1, &cifs_tcp_ses_list) {
++              server = list_entry(tmp1, struct TCP_Server_Info,
+                                   tcp_ses_list);
+               i++;
+               list_for_each(tmp2, &server->smb_ses_list) {
+@@ -133,12 +134,12 @@ static int cifs_debug_data_proc_show(str
+                       if ((ses->serverDomain == NULL) ||
+                               (ses->serverOS == NULL) ||
+                               (ses->serverNOS == NULL)) {
+-                              seq_printf(m, "\nentry for %s not fully "
+-                                         "displayed\n\t", ses->serverName);
++                              seq_printf(m, "\n%d) entry for %s not fully "
++                                         "displayed\n\t", i, ses->serverName);
+                       } else {
+                               seq_printf(m,
+-                                  "\n%d) Name: %s  Domain: %s Mounts: %d OS:"
+-                                  " %s  \n\tNOS: %s\tCapability: 0x%x\n\tSMB"
++                                  "\n%d) Name: %s  Domain: %s Uses: %d OS:"
++                                  " %s\n\tNOS: %s\tCapability: 0x%x\n\tSMB"
+                                   " session status: %d\t",
+                               i, ses->serverName, ses->serverDomain,
+                               ses->ses_count, ses->serverOS, ses->serverNOS,
+@@ -156,14 +157,44 @@ static int cifs_debug_data_proc_show(str
+                               atomic_read(&server->num_waiters));
+ #endif
+-                      seq_puts(m, "\nMIDs:\n");
++                      seq_puts(m, "\n\tShares:");
++                      j = 0;
++                      list_for_each(tmp3, &ses->tcon_list) {
++                              tcon = list_entry(tmp3, struct cifsTconInfo,
++                                                tcon_list);
++                              ++j;
++                              dev_type = le32_to_cpu(tcon->fsDevInfo.DeviceType);
++                              seq_printf(m, "\n\t%d) %s Mounts: %d ", j,
++                                         tcon->treeName, tcon->tc_count);
++                              if (tcon->nativeFileSystem) {
++                                      seq_printf(m, "Type: %s ",
++                                                 tcon->nativeFileSystem);
++                              }
++                              seq_printf(m, "DevInfo: 0x%x Attributes: 0x%x"
++                                      "\nPathComponentMax: %d Status: 0x%d",
++                                      le32_to_cpu(tcon->fsDevInfo.DeviceCharacteristics),
++                                      le32_to_cpu(tcon->fsAttrInfo.Attributes),
++                                      le32_to_cpu(tcon->fsAttrInfo.MaxPathNameComponentLength),
++                                      tcon->tidStatus);
++                              if (dev_type == FILE_DEVICE_DISK)
++                                      seq_puts(m, " type: DISK ");
++                              else if (dev_type == FILE_DEVICE_CD_ROM)
++                                      seq_puts(m, " type: CDROM ");
++                              else
++                                      seq_printf(m, " type: %d ", dev_type);
++
++                              if (tcon->need_reconnect)
++                                      seq_puts(m, "\tDISCONNECTED ");
++                              seq_putc(m, '\n');
++                      }
++
++                      seq_puts(m, "\n\tMIDs:\n");
+                       spin_lock(&GlobalMid_Lock);
+                       list_for_each(tmp3, &server->pending_mid_q) {
+-                              mid_entry = list_entry(tmp3, struct
+-                                      mid_q_entry,
++                              mid_entry = list_entry(tmp3, struct mid_q_entry,
+                                       qhead);
+-                              seq_printf(m, "State: %d com: %d pid:"
++                              seq_printf(m, "\tState: %d com: %d pid:"
+                                               " %d tsk: %p mid %d\n",
+                                               mid_entry->midState,
+                                               (int)mid_entry->command,
+@@ -177,41 +208,6 @@ static int cifs_debug_data_proc_show(str
+       read_unlock(&cifs_tcp_ses_lock);
+       seq_putc(m, '\n');
+-      seq_puts(m, "Shares:");
+-
+-      i = 0;
+-      read_lock(&GlobalSMBSeslock);
+-      list_for_each(tmp, &GlobalTreeConnectionList) {
+-              __u32 dev_type;
+-              i++;
+-              tcon = list_entry(tmp, struct cifsTconInfo, cifsConnectionList);
+-              dev_type = le32_to_cpu(tcon->fsDevInfo.DeviceType);
+-              seq_printf(m, "\n%d) %s Uses: %d ", i,
+-                               tcon->treeName, atomic_read(&tcon->useCount));
+-              if (tcon->nativeFileSystem) {
+-                      seq_printf(m, "Type: %s ",
+-                                       tcon->nativeFileSystem);
+-              }
+-              seq_printf(m, "DevInfo: 0x%x Attributes: 0x%x"
+-                               "\nPathComponentMax: %d Status: %d",
+-                          le32_to_cpu(tcon->fsDevInfo.DeviceCharacteristics),
+-                          le32_to_cpu(tcon->fsAttrInfo.Attributes),
+-                          le32_to_cpu(tcon->fsAttrInfo.MaxPathNameComponentLength),
+-                          tcon->tidStatus);
+-              if (dev_type == FILE_DEVICE_DISK)
+-                      seq_puts(m, " type: DISK ");
+-              else if (dev_type == FILE_DEVICE_CD_ROM)
+-                      seq_puts(m, " type: CDROM ");
+-              else
+-                      seq_printf(m, " type: %d ", dev_type);
+-
+-              if (tcon->need_reconnect)
+-                      seq_puts(m, "\tDISCONNECTED ");
+-      }
+-      read_unlock(&GlobalSMBSeslock);
+-
+-      seq_putc(m, '\n');
+-
+       /* BB add code to dump additional info such as TCP session info now */
+       return 0;
+ }
+@@ -235,7 +231,9 @@ static ssize_t cifs_stats_proc_write(str
+ {
+       char c;
+       int rc;
+-      struct list_head *tmp;
++      struct list_head *tmp1, *tmp2, *tmp3;
++      struct TCP_Server_Info *server;
++      struct cifsSesInfo *ses;
+       struct cifsTconInfo *tcon;
+       rc = get_user(c, buffer);
+@@ -243,33 +241,42 @@ static ssize_t cifs_stats_proc_write(str
+               return rc;
+       if (c == '1' || c == 'y' || c == 'Y' || c == '0') {
+-              read_lock(&GlobalSMBSeslock);
+ #ifdef CONFIG_CIFS_STATS2
+               atomic_set(&totBufAllocCount, 0);
+               atomic_set(&totSmBufAllocCount, 0);
+ #endif /* CONFIG_CIFS_STATS2 */
+-              list_for_each(tmp, &GlobalTreeConnectionList) {
+-                      tcon = list_entry(tmp, struct cifsTconInfo,
+-                                      cifsConnectionList);
+-                      atomic_set(&tcon->num_smbs_sent, 0);
+-                      atomic_set(&tcon->num_writes, 0);
+-                      atomic_set(&tcon->num_reads, 0);
+-                      atomic_set(&tcon->num_oplock_brks, 0);
+-                      atomic_set(&tcon->num_opens, 0);
+-                      atomic_set(&tcon->num_closes, 0);
+-                      atomic_set(&tcon->num_deletes, 0);
+-                      atomic_set(&tcon->num_mkdirs, 0);
+-                      atomic_set(&tcon->num_rmdirs, 0);
+-                      atomic_set(&tcon->num_renames, 0);
+-                      atomic_set(&tcon->num_t2renames, 0);
+-                      atomic_set(&tcon->num_ffirst, 0);
+-                      atomic_set(&tcon->num_fnext, 0);
+-                      atomic_set(&tcon->num_fclose, 0);
+-                      atomic_set(&tcon->num_hardlinks, 0);
+-                      atomic_set(&tcon->num_symlinks, 0);
+-                      atomic_set(&tcon->num_locks, 0);
++              read_lock(&cifs_tcp_ses_lock);
++              list_for_each(tmp1, &cifs_tcp_ses_list) {
++                      server = list_entry(tmp1, struct TCP_Server_Info,
++                                          tcp_ses_list);
++                      list_for_each(tmp2, &server->smb_session_list) {
++                              ses = list_entry(tmp2, struct cifsSesInfo,
++                                               smb_session_list);
++                              list_for_each(tmp3, &ses->tcon_list) {
++                                      tcon = list_entry(tmp3,
++                                                        struct cifsTconInfo,
++                                                        tcon_list);
++                                      atomic_set(&tcon->num_smbs_sent, 0);
++                                      atomic_set(&tcon->num_writes, 0);
++                                      atomic_set(&tcon->num_reads, 0);
++                                      atomic_set(&tcon->num_oplock_brks, 0);
++                                      atomic_set(&tcon->num_opens, 0);
++                                      atomic_set(&tcon->num_closes, 0);
++                                      atomic_set(&tcon->num_deletes, 0);
++                                      atomic_set(&tcon->num_mkdirs, 0);
++                                      atomic_set(&tcon->num_rmdirs, 0);
++                                      atomic_set(&tcon->num_renames, 0);
++                                      atomic_set(&tcon->num_t2renames, 0);
++                                      atomic_set(&tcon->num_ffirst, 0);
++                                      atomic_set(&tcon->num_fnext, 0);
++                                      atomic_set(&tcon->num_fclose, 0);
++                                      atomic_set(&tcon->num_hardlinks, 0);
++                                      atomic_set(&tcon->num_symlinks, 0);
++                                      atomic_set(&tcon->num_locks, 0);
++                              }
++                      }
+               }
+-              read_unlock(&GlobalSMBSeslock);
++              read_unlock(&cifs_tcp_ses_lock);
+       }
+       return count;
+@@ -278,7 +285,9 @@ static ssize_t cifs_stats_proc_write(str
+ static int cifs_stats_proc_show(struct seq_file *m, void *v)
+ {
+       int i;
+-      struct list_head *tmp;
++      struct list_head *tmp1, *tmp2, *tmp3;
++      struct TCP_Server_Info *server;
++      struct cifsSesInfo *ses;
+       struct cifsTconInfo *tcon;
+       seq_printf(m,
+@@ -307,44 +316,55 @@ static int cifs_stats_proc_show(struct s
+               GlobalCurrentXid, GlobalMaxActiveXid);
+       i = 0;
+-      read_lock(&GlobalSMBSeslock);
+-      list_for_each(tmp, &GlobalTreeConnectionList) {
+-              i++;
+-              tcon = list_entry(tmp, struct cifsTconInfo, cifsConnectionList);
+-              seq_printf(m, "\n%d) %s", i, tcon->treeName);
+-              if (tcon->need_reconnect)
+-                      seq_puts(m, "\tDISCONNECTED ");
+-              seq_printf(m, "\nSMBs: %d Oplock Breaks: %d",
+-                      atomic_read(&tcon->num_smbs_sent),
+-                      atomic_read(&tcon->num_oplock_brks));
+-              seq_printf(m, "\nReads:  %d Bytes: %lld",
+-                      atomic_read(&tcon->num_reads),
+-                      (long long)(tcon->bytes_read));
+-              seq_printf(m, "\nWrites: %d Bytes: %lld",
+-                      atomic_read(&tcon->num_writes),
+-                      (long long)(tcon->bytes_written));
+-              seq_printf(m,
+-                      "\nLocks: %d HardLinks: %d Symlinks: %d",
+-                      atomic_read(&tcon->num_locks),
+-                      atomic_read(&tcon->num_hardlinks),
+-                      atomic_read(&tcon->num_symlinks));
+-
+-              seq_printf(m, "\nOpens: %d Closes: %d Deletes: %d",
+-                      atomic_read(&tcon->num_opens),
+-                      atomic_read(&tcon->num_closes),
+-                      atomic_read(&tcon->num_deletes));
+-              seq_printf(m, "\nMkdirs: %d Rmdirs: %d",
+-                      atomic_read(&tcon->num_mkdirs),
+-                      atomic_read(&tcon->num_rmdirs));
+-              seq_printf(m, "\nRenames: %d T2 Renames %d",
+-                      atomic_read(&tcon->num_renames),
+-                      atomic_read(&tcon->num_t2renames));
+-              seq_printf(m, "\nFindFirst: %d FNext %d FClose %d",
+-                      atomic_read(&tcon->num_ffirst),
+-                      atomic_read(&tcon->num_fnext),
+-                      atomic_read(&tcon->num_fclose));
++      read_lock(&cifs_tcp_ses_lock);
++      list_for_each(tmp1, &cifs_tcp_ses_list) {
++              server = list_entry(tmp1, struct TCP_Server_Info,
++                                  tcp_ses_list);
++              list_for_each(tmp2, &server->smb_ses_list) {
++                      ses = list_entry(tmp2, struct cifsSesInfo,
++                                       smb_ses_list);
++                      list_for_each(tmp3, &ses->tcon_list) {
++                              tcon = list_entry(tmp3,
++                                                struct cifsTconInfo,
++                                                tcon_list);
++                              i++;
++                              seq_printf(m, "\n%d) %s", i, tcon->treeName);
++                              if (tcon->need_reconnect)
++                                      seq_puts(m, "\tDISCONNECTED ");
++                              seq_printf(m, "\nSMBs: %d Oplock Breaks: %d",
++                                      atomic_read(&tcon->num_smbs_sent),
++                                      atomic_read(&tcon->num_oplock_brks));
++                              seq_printf(m, "\nReads:  %d Bytes: %lld",
++                                      atomic_read(&tcon->num_reads),
++                                      (long long)(tcon->bytes_read));
++                              seq_printf(m, "\nWrites: %d Bytes: %lld",
++                                      atomic_read(&tcon->num_writes),
++                                      (long long)(tcon->bytes_written));
++                              seq_printf(m, "\nLocks: %d HardLinks: %d "
++                                            "Symlinks: %d",
++                                      atomic_read(&tcon->num_locks),
++                                      atomic_read(&tcon->num_hardlinks),
++                                      atomic_read(&tcon->num_symlinks));
++                              seq_printf(m, "\nOpens: %d Closes: %d"
++                                            "Deletes: %d",
++                                      atomic_read(&tcon->num_opens),
++                                      atomic_read(&tcon->num_closes),
++                                      atomic_read(&tcon->num_deletes));
++                              seq_printf(m, "\nMkdirs: %d Rmdirs: %d",
++                                      atomic_read(&tcon->num_mkdirs),
++                                      atomic_read(&tcon->num_rmdirs));
++                              seq_printf(m, "\nRenames: %d T2 Renames %d",
++                                      atomic_read(&tcon->num_renames),
++                                      atomic_read(&tcon->num_t2renames));
++                              seq_printf(m, "\nFindFirst: %d FNext %d "
++                                            "FClose %d",
++                                      atomic_read(&tcon->num_ffirst),
++                                      atomic_read(&tcon->num_fnext),
++                                      atomic_read(&tcon->num_fclose));
++                      }
++              }
+       }
+-      read_unlock(&GlobalSMBSeslock);
++      read_unlock(&cifs_tcp_ses_lock);
+       seq_putc(m, '\n');
+       return 0;
+--- a/fs/cifs/cifsfs.c
++++ b/fs/cifs/cifsfs.c
+@@ -510,10 +510,11 @@ static void cifs_umount_begin(struct sup
+       tcon = cifs_sb->tcon;
+       if (tcon == NULL)
+               return;
+-      down(&tcon->tconSem);
+-      if (atomic_read(&tcon->useCount) == 1)
++
++      read_lock(&cifs_tcp_ses_lock);
++      if (tcon->tc_count == 1)
+               tcon->tidStatus = CifsExiting;
+-      up(&tcon->tconSem);
++      read_unlock(&cifs_tcp_ses_lock);
+       /* cancel_brl_requests(tcon); */ /* BB mark all brl mids as exiting */
+       /* cancel_notify_requests(tcon); */
+@@ -1014,7 +1015,6 @@ init_cifs(void)
+       int rc = 0;
+       cifs_proc_init();
+       INIT_LIST_HEAD(&cifs_tcp_ses_list);
+-      INIT_LIST_HEAD(&GlobalTreeConnectionList); /* BB to be removed by jl */
+       INIT_LIST_HEAD(&GlobalOplock_Q);
+ #ifdef CONFIG_CIFS_EXPERIMENTAL
+       INIT_LIST_HEAD(&GlobalDnotifyReqList);
+--- a/fs/cifs/cifsglob.h
++++ b/fs/cifs/cifsglob.h
+@@ -233,16 +233,15 @@ struct cifsSesInfo {
+  * session
+  */
+ struct cifsTconInfo {
+-      struct list_head cifsConnectionList;
++      struct list_head tcon_list;
++      int tc_count;
+       struct list_head openFileList;
+-      struct semaphore tconSem;
+       struct cifsSesInfo *ses;        /* pointer to session associated with */
+       char treeName[MAX_TREE_SIZE + 1]; /* UNC name of resource in ASCII */
+       char *nativeFileSystem;
+       __u16 tid;              /* The 2 byte tree id */
+       __u16 Flags;            /* optional support bits */
+       enum statusEnum tidStatus;
+-      atomic_t useCount;      /* how many explicit/implicit mounts to share */
+ #ifdef CONFIG_CIFS_STATS
+       atomic_t num_smbs_sent;
+       atomic_t num_writes;
+@@ -598,9 +597,13 @@ require use of the stronger protocol */
+  */
+ GLOBAL_EXTERN struct list_head                cifs_tcp_ses_list;
+-/* protects cifs_tcp_ses_list and srv_count for each tcp session */
++/*
++ * This lock protects the cifs_tcp_ses_list, the list of smb sessions per
++ * tcp session, and the list of tcon's per smb session. It also protects
++ * the reference counters for the server, smb session, and tcon. Finally,
++ * changes to the tcon->tidStatus should be done while holding this lock.
++ */
+ GLOBAL_EXTERN rwlock_t                cifs_tcp_ses_lock;
+-GLOBAL_EXTERN struct list_head GlobalTreeConnectionList; /* BB to be removed */
+ GLOBAL_EXTERN rwlock_t GlobalSMBSeslock;  /* protects list inserts on 3 above */
+ GLOBAL_EXTERN struct list_head GlobalOplock_Q;
+--- a/fs/cifs/cifssmb.c
++++ b/fs/cifs/cifssmb.c
+@@ -742,50 +742,31 @@ CIFSSMBTDis(const int xid, struct cifsTc
+       int rc = 0;
+       cFYI(1, ("In tree disconnect"));
+-      /*
+-       *  If last user of the connection and
+-       *  connection alive - disconnect it
+-       *  If this is the last connection on the server session disconnect it
+-       *  (and inside session disconnect we should check if tcp socket needs
+-       *  to be freed and kernel thread woken up).
+-       */
+-      if (tcon)
+-              down(&tcon->tconSem);
+-      else
+-              return -EIO;
+-      atomic_dec(&tcon->useCount);
+-      if (atomic_read(&tcon->useCount) > 0) {
+-              up(&tcon->tconSem);
+-              return -EBUSY;
+-      }
++      /* BB: do we need to check this? These should never be NULL. */
++      if ((tcon->ses == NULL) || (tcon->ses->server == NULL))
++              return -EIO;
+-      /* No need to return error on this operation if tid invalidated and
+-      closed on server already e.g. due to tcp session crashing */
+-      if (tcon->need_reconnect) {
+-              up(&tcon->tconSem);
++      /*
++       * No need to return error on this operation if tid invalidated and
++       * closed on server already e.g. due to tcp session crashing. Also,
++       * the tcon is no longer on the list, so no need to take lock before
++       * checking this.
++       */
++      if (tcon->need_reconnect)
+               return 0;
+-      }
+-      if ((tcon->ses == NULL) || (tcon->ses->server == NULL)) {
+-              up(&tcon->tconSem);
+-              return -EIO;
+-      }
+       rc = small_smb_init(SMB_COM_TREE_DISCONNECT, 0, tcon,
+                           (void **)&smb_buffer);
+-      if (rc) {
+-              up(&tcon->tconSem);
++      if (rc)
+               return rc;
+-      }
+       rc = SendReceiveNoRsp(xid, tcon->ses, smb_buffer, 0);
+       if (rc)
+               cFYI(1, ("Tree disconnect failed %d", rc));
+-      up(&tcon->tconSem);
+-
+       /* No need to return error on this operation if tid invalidated and
+-      closed on server already e.g. due to tcp session crashing */
++         closed on server already e.g. due to tcp session crashing */
+       if (rc == -EAGAIN)
+               rc = 0;
+--- a/fs/cifs/connect.c
++++ b/fs/cifs/connect.c
+@@ -122,7 +122,7 @@ static int
+ cifs_reconnect(struct TCP_Server_Info *server)
+ {
+       int rc = 0;
+-      struct list_head *tmp;
++      struct list_head *tmp, *tmp2;
+       struct cifsSesInfo *ses;
+       struct cifsTconInfo *tcon;
+       struct mid_q_entry *mid_entry;
+@@ -147,13 +147,12 @@ cifs_reconnect(struct TCP_Server_Info *s
+               ses = list_entry(tmp, struct cifsSesInfo, smb_ses_list);
+               ses->need_reconnect = true;
+               ses->ipc_tid = 0;
+-      }
+-      read_unlock(&cifs_tcp_ses_lock);
+-      list_for_each(tmp, &GlobalTreeConnectionList) {
+-              tcon = list_entry(tmp, struct cifsTconInfo, cifsConnectionList);
+-              if ((tcon->ses) && (tcon->ses->server == server))
++              list_for_each(tmp2, &ses->tcon_list) {
++                      tcon = list_entry(tmp2, struct cifsTconInfo, tcon_list);
+                       tcon->need_reconnect = true;
++              }
+       }
++      read_unlock(&cifs_tcp_ses_lock);
+       /* do not want to be sending data on a socket we are freeing */
+       down(&server->tcpSem);
+       if (server->ssocket) {
+@@ -1451,6 +1450,52 @@ cifs_put_smb_ses(struct cifsSesInfo *ses
+       cifs_put_tcp_session(server);
+ }
++static struct cifsTconInfo *
++cifs_find_tcon(struct cifsSesInfo *ses, const char *unc)
++{
++      struct list_head *tmp;
++      struct cifsTconInfo *tcon;
++
++      write_lock(&cifs_tcp_ses_lock);
++      list_for_each(tmp, &ses->tcon_list) {
++              tcon = list_entry(tmp, struct cifsTconInfo, tcon_list);
++              if (tcon->tidStatus == CifsExiting)
++                      continue;
++              if (strncmp(tcon->treeName, unc, MAX_TREE_SIZE))
++                      continue;
++
++              ++tcon->tc_count;
++              write_unlock(&cifs_tcp_ses_lock);
++              return tcon;
++      }
++      write_unlock(&cifs_tcp_ses_lock);
++      return NULL;
++}
++
++static void
++cifs_put_tcon(struct cifsTconInfo *tcon)
++{
++      int xid;
++      struct cifsSesInfo *ses = tcon->ses;
++
++      write_lock(&cifs_tcp_ses_lock);
++      if (--tcon->tc_count > 0) {
++              write_unlock(&cifs_tcp_ses_lock);
++              return;
++      }
++
++      list_del_init(&tcon->tcon_list);
++      write_unlock(&cifs_tcp_ses_lock);
++
++      xid = GetXid();
++      CIFSSMBTDis(xid, tcon);
++      _FreeXid(xid);
++
++      DeleteTconOplockQEntries(tcon);
++      tconInfoFree(tcon);
++      cifs_put_smb_ses(ses);
++}
++
+ int
+ get_dfs_path(int xid, struct cifsSesInfo *pSesInfo, const char *old_path,
+            const struct nls_table *nls_codepage, unsigned int *pnum_referrals,
+@@ -2206,11 +2251,11 @@ cifs_mount(struct super_block *sb, struc
+       /* search for existing tcon to this server share */
+       if (!rc) {
+               setup_cifs_sb(&volume_info, cifs_sb);
++              tcon = cifs_find_tcon(pSesInfo, volume_info.UNC);
+               if (tcon) {
+                       cFYI(1, ("Found match on UNC path"));
+-                      if (tcon->seal != volume_info.seal)
+-                              cERROR(1, ("transport encryption setting "
+-                                         "conflicts with existing tid"));
++                      /* existing tcon already has a reference */
++                      cifs_put_smb_ses(pSesInfo);
+               } else {
+                       tcon = tconInfoAlloc();
+                       if (tcon == NULL) {
+@@ -2238,6 +2283,10 @@ cifs_mount(struct super_block *sb, struc
+                       if (rc)
+                               goto mount_fail_check;
+                       tcon->seal = volume_info.seal;
++                      tcon->ses = pSesInfo;
++                      write_lock(&cifs_tcp_ses_lock);
++                      list_add(&tcon->tcon_list, &pSesInfo->tcon_list);
++                      write_unlock(&cifs_tcp_ses_lock);
+               }
+               /* we can have only one retry value for a connection
+@@ -2263,18 +2312,14 @@ mount_fail_check:
+                /* If find_unc succeeded then rc == 0 so we can not end */
+               /* up accidently freeing someone elses tcon struct */
+               if (tcon)
+-                      tconInfoFree(tcon);
+-
+-              /* should also end up putting our tcp session ref if needed */
+-              if (pSesInfo)
++                      cifs_put_tcon(tcon);
++              else if (pSesInfo)
+                       cifs_put_smb_ses(pSesInfo);
+               else
+                       cifs_put_tcp_session(srvTcp);
+               goto out;
+       }
+-      atomic_inc(&tcon->useCount);
+       cifs_sb->tcon = tcon;
+-      tcon->ses = pSesInfo;
+       /* do not care if following two calls succeed - informational */
+       if (!tcon->ipc) {
+@@ -3545,24 +3590,10 @@ int
+ 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();
+-
+-      if (cifs_sb->tcon) {
+-              ses = cifs_sb->tcon->ses; /* save ptr to ses before delete tcon!*/
+-              rc = CIFSSMBTDis(xid, cifs_sb->tcon);
+-              if (rc == -EBUSY) {
+-                      FreeXid(xid);
+-                      return 0;
+-              }
+-              DeleteTconOplockQEntries(cifs_sb->tcon);
+-              tconInfoFree(cifs_sb->tcon);
+-              cifs_put_smb_ses(ses);
+-      }
++      if (cifs_sb->tcon)
++              cifs_put_tcon(cifs_sb->tcon);
+       cifs_sb->tcon = NULL;
+       tmp = cifs_sb->prepath;
+@@ -3570,7 +3601,6 @@ cifs_umount(struct super_block *sb, stru
+       cifs_sb->prepath = NULL;
+       kfree(tmp);
+-      FreeXid(xid);
+       return rc;
+ }
+--- a/fs/cifs/misc.c
++++ b/fs/cifs/misc.c
+@@ -79,6 +79,7 @@ sesInfoAlloc(void)
+               ret_buf->status = CifsNew;
+               ++ret_buf->ses_count;
+               INIT_LIST_HEAD(&ret_buf->smb_ses_list);
++              INIT_LIST_HEAD(&ret_buf->tcon_list);
+               init_MUTEX(&ret_buf->sesSem);
+       }
+       return ret_buf;
+@@ -107,17 +108,14 @@ tconInfoAlloc(void)
+       struct cifsTconInfo *ret_buf;
+       ret_buf = kzalloc(sizeof(struct cifsTconInfo), GFP_KERNEL);
+       if (ret_buf) {
+-              write_lock(&GlobalSMBSeslock);
+               atomic_inc(&tconInfoAllocCount);
+-              list_add(&ret_buf->cifsConnectionList,
+-                       &GlobalTreeConnectionList);
+               ret_buf->tidStatus = CifsNew;
++              ++ret_buf->tc_count;
+               INIT_LIST_HEAD(&ret_buf->openFileList);
+-              init_MUTEX(&ret_buf->tconSem);
++              INIT_LIST_HEAD(&ret_buf->tcon_list);
+ #ifdef CONFIG_CIFS_STATS
+               spin_lock_init(&ret_buf->stat_lock);
+ #endif
+-              write_unlock(&GlobalSMBSeslock);
+       }
+       return ret_buf;
+ }
+@@ -129,10 +127,7 @@ tconInfoFree(struct cifsTconInfo *buf_to
+               cFYI(1, ("Null buffer passed to tconInfoFree"));
+               return;
+       }
+-      write_lock(&GlobalSMBSeslock);
+       atomic_dec(&tconInfoAllocCount);
+-      list_del(&buf_to_free->cifsConnectionList);
+-      write_unlock(&GlobalSMBSeslock);
+       kfree(buf_to_free->nativeFileSystem);
+       kfree(buf_to_free);
+ }
+@@ -497,9 +492,10 @@ bool
+ is_valid_oplock_break(struct smb_hdr *buf, struct TCP_Server_Info *srv)
+ {
+       struct smb_com_lock_req *pSMB = (struct smb_com_lock_req *)buf;
+-      struct list_head *tmp;
+-      struct list_head *tmp1;
++      struct list_head *tmp, *tmp1, *tmp2;
++      struct cifsSesInfo *ses;
+       struct cifsTconInfo *tcon;
++      struct cifsInodeInfo *pCifsInode;
+       struct cifsFileInfo *netfile;
+       cFYI(1, ("Checking for oplock break or dnotify response"));
+@@ -554,42 +550,42 @@ is_valid_oplock_break(struct smb_hdr *bu
+               return false;
+       /* look up tcon based on tid & uid */
+-      read_lock(&GlobalSMBSeslock);
+-      list_for_each(tmp, &GlobalTreeConnectionList) {
+-              tcon = list_entry(tmp, struct cifsTconInfo, cifsConnectionList);
+-              if ((tcon->tid == buf->Tid) && (srv == tcon->ses->server)) {
++      read_lock(&cifs_tcp_ses_lock);
++      list_for_each(tmp, &srv->smb_ses_list) {
++              ses = list_entry(tmp, struct cifsSesInfo, smb_ses_list);
++              list_for_each(tmp1, &ses->tcon_list) {
++                      tcon = list_entry(tmp1, struct cifsTconInfo, tcon_list);
++                      if (tcon->tid != buf->Tid)
++                              continue;
++
+                       cifs_stats_inc(&tcon->num_oplock_brks);
+-                      list_for_each(tmp1, &tcon->openFileList) {
+-                              netfile = list_entry(tmp1, struct cifsFileInfo,
++                      list_for_each(tmp2, &tcon->openFileList) {
++                              netfile = list_entry(tmp2, struct cifsFileInfo,
+                                                    tlist);
+-                              if (pSMB->Fid == netfile->netfid) {
+-                                      struct cifsInodeInfo *pCifsInode;
+-                                      read_unlock(&GlobalSMBSeslock);
+-                                      cFYI(1,
+-                                          ("file id match, oplock break"));
+-                                      pCifsInode =
+-                                              CIFS_I(netfile->pInode);
+-                                      pCifsInode->clientCanCacheAll = false;
+-                                      if (pSMB->OplockLevel == 0)
+-                                              pCifsInode->clientCanCacheRead
+-                                                      = false;
+-                                      pCifsInode->oplockPending = true;
+-                                      AllocOplockQEntry(netfile->pInode,
+-                                                        netfile->netfid,
+-                                                        tcon);
+-                                      cFYI(1,
+-                                          ("about to wake up oplock thread"));
+-                                      if (oplockThread)
+-                                          wake_up_process(oplockThread);
+-                                      return true;
+-                              }
++                              if (pSMB->Fid != netfile->netfid)
++                                      continue;
++
++                              read_unlock(&cifs_tcp_ses_lock);
++                              cFYI(1, ("file id match, oplock break"));
++                              pCifsInode = CIFS_I(netfile->pInode);
++                              pCifsInode->clientCanCacheAll = false;
++                              if (pSMB->OplockLevel == 0)
++                                      pCifsInode->clientCanCacheRead = false;
++                              pCifsInode->oplockPending = true;
++                              AllocOplockQEntry(netfile->pInode,
++                                                netfile->netfid, tcon);
++                              cFYI(1, ("about to wake up oplock thread"));
++                              if (oplockThread)
++                                      wake_up_process(oplockThread);
++
++                              return true;
+                       }
+-                      read_unlock(&GlobalSMBSeslock);
++                      read_unlock(&cifs_tcp_ses_lock);
+                       cFYI(1, ("No matching file for oplock break"));
+                       return true;
+               }
+       }
+-      read_unlock(&GlobalSMBSeslock);
++      read_unlock(&cifs_tcp_ses_lock);
+       cFYI(1, ("Can not process oplock break for non-existent connection"));
+       return true;
+ }
diff --git a/queue-2.6.27/cifs-remove-unused-list-add-new-cifs-sock-list-to-prepare-for-mount-umount-fix.patch b/queue-2.6.27/cifs-remove-unused-list-add-new-cifs-sock-list-to-prepare-for-mount-umount-fix.patch
new file mode 100644 (file)
index 0000000..57e4edf
--- /dev/null
@@ -0,0 +1,99 @@
+From sjayaraman@suse.de  Wed Dec  3 09:29:08 2008
+From: Steve French <sfrench@us.ibm.com>
+Date: Fri, 21 Nov 2008 14:22:57 +0530
+Subject: cifs: remove unused list, add new cifs sock list to prepare for mount/umount fix
+To: stable@kernel.org
+Cc: Steve French <smfrench@gmail.com>, Jeff Layton <jlayton@redhat.com>, Shirish S Pargaonkar <shirishp@us.ibm.com>
+Message-ID: <492676E9.2080408@suse.de>
+
+From: Steve French <sfrench@us.ibm.com>
+
+commit fb396016647ae9de5b3bd8c4ee4f7b9cc7148bd5 upstream.
+
+Also adds two lines missing from the previous patch (for the need reconnect flag in the
+/proc/fs/cifs/DebugData handling)
+
+The new global_cifs_sock_list is added, and initialized in init_cifs but not used yet.
+Jeff Layton will be adding code in to use that and to remove the GlobalTcon and GlobalSMBSession
+lists.
+
+CC: Jeff Layton <jlayton@redhat.com>
+CC: Shirish Pargaonkar <shirishp@us.ibm.com>
+Signed-off-by: Steve French <sfrench@us.ibm.com>
+Cc: Suresh Jayaraman <sjayaraman@suse.de>
+Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
+
+---
+ fs/cifs/cifs_debug.c |    4 ++--
+ fs/cifs/cifsfs.c     |    6 +++---
+ fs/cifs/cifsglob.h   |   23 ++++++++---------------
+ 3 files changed, 13 insertions(+), 20 deletions(-)
+
+--- a/fs/cifs/cifs_debug.c
++++ b/fs/cifs/cifs_debug.c
+@@ -204,7 +204,7 @@ static int cifs_debug_data_proc_show(str
+               else
+                       seq_printf(m, " type: %d ", dev_type);
+-              if (tcon->tidStatus == CifsNeedReconnect)
++              if (tcon->need_reconnect)
+                       seq_puts(m, "\tDISCONNECTED ");
+       }
+       read_unlock(&GlobalSMBSeslock);
+@@ -311,7 +311,7 @@ static int cifs_stats_proc_show(struct s
+               i++;
+               tcon = list_entry(tmp, struct cifsTconInfo, cifsConnectionList);
+               seq_printf(m, "\n%d) %s", i, tcon->treeName);
+-              if (tcon->tidStatus == CifsNeedReconnect)
++              if (tcon->need_reconnect)
+                       seq_puts(m, "\tDISCONNECTED ");
+               seq_printf(m, "\nSMBs: %d Oplock Breaks: %d",
+                       atomic_read(&tcon->num_smbs_sent),
+--- a/fs/cifs/cifsfs.c
++++ b/fs/cifs/cifsfs.c
+@@ -1013,9 +1013,9 @@ init_cifs(void)
+ {
+       int rc = 0;
+       cifs_proc_init();
+-/*    INIT_LIST_HEAD(&GlobalServerList);*/    /* BB not implemented yet */
+-      INIT_LIST_HEAD(&GlobalSMBSessionList);
+-      INIT_LIST_HEAD(&GlobalTreeConnectionList);
++      INIT_LIST_HEAD(&global_cifs_sock_list);
++      INIT_LIST_HEAD(&GlobalSMBSessionList); /* BB to be removed by jl */
++      INIT_LIST_HEAD(&GlobalTreeConnectionList); /* BB to be removed by jl */
+       INIT_LIST_HEAD(&GlobalOplock_Q);
+ #ifdef CONFIG_CIFS_EXPERIMENTAL
+       INIT_LIST_HEAD(&GlobalDnotifyReqList);
+--- a/fs/cifs/cifsglob.h
++++ b/fs/cifs/cifsglob.h
+@@ -590,22 +590,15 @@ require use of the stronger protocol */
+ #define GLOBAL_EXTERN extern
+ #endif
+-/*
+- * The list of servers that did not respond with NT LM 0.12.
+- * This list helps improve performance and eliminate the messages indicating
+- * that we had a communications error talking to the server in this list.
+- */
+-/* Feature not supported */
+-/* GLOBAL_EXTERN struct servers_not_supported *NotSuppList; */
+-
+-/*
+- * The following is a hash table of all the users we know about.
+- */
+-GLOBAL_EXTERN struct smbUidInfo *GlobalUidList[UID_HASH];
+-/* GLOBAL_EXTERN struct list_head GlobalServerList; BB not implemented yet */
+-GLOBAL_EXTERN struct list_head GlobalSMBSessionList;
+-GLOBAL_EXTERN struct list_head GlobalTreeConnectionList;
++/* the list of TCP_Server_Info structures, ie each of the sockets
++ * connecting our client to a distinct server (ip address), is
++ * chained together by global_cifs_sock_list. The list of all our SMB
++ * sessions (and from that the tree connections) can be found
++ * by iterating over global_cifs_sock_list */
++GLOBAL_EXTERN struct list_head global_cifs_sock_list;
++GLOBAL_EXTERN struct list_head GlobalSMBSessionList; /* BB to be removed by jl*/
++GLOBAL_EXTERN struct list_head GlobalTreeConnectionList; /* BB to be removed */
+ GLOBAL_EXTERN rwlock_t GlobalSMBSeslock;  /* protects list inserts on 3 above */
+ GLOBAL_EXTERN struct list_head GlobalOplock_Q;
index cc856442fa2694d6686f02605b006e848698153b..82b514b199a6e73478eb17f6c1fa5c41fbf5a1e9 100644 (file)
@@ -65,3 +65,14 @@ firewire-fw-sbp2-another-ipod-mini-quirk-entry.patch
 ib-mlx4-fix-mtt-leakage-in-resize-cq.patch
 net-fix-soft-lockups-oom-issues-w-unix-garbage-collector.patch
 libata-improve-phantom-device-detection.patch
+cifs-fix-cifs-reconnection-flags.patch
+cifs-remove-unused-list-add-new-cifs-sock-list-to-prepare-for-mount-umount-fix.patch
+cifs-clean-up-server-protocol-handling.patch
+cifs-disable-sharing-session-and-tcon-and-add-new-tcp-sharing-code.patch
+cifs-reinstate-sharing-of-smb-sessions-sans-races.patch
+cifs-minor-cleanup-to-cifs_mount.patch
+cifs-reinstate-sharing-of-tree-connections.patch
+cifs-fix-build-break.patch
+cifs-fix-check-for-tcon-seal-setting-and-fix-oops-on-failed-mount-from-earlier-patch.patch
+cifs-prevent-cifs_writepages-from-skipping-unwritten-pages.patch
+cifs-fix-check-for-dead-tcon-in-smb_init.patch