[CIFS] Add new nostrictsync cifs mount option to avoid slow SMB flush
[safe/jmp/linux-2.6] / fs / cifs / connect.c
index a36f4bd..18e84a4 100644 (file)
@@ -23,7 +23,6 @@
 #include <linux/string.h>
 #include <linux/list.h>
 #include <linux/wait.h>
-#include <linux/ipv6.h>
 #include <linux/pagemap.h>
 #include <linux/ctype.h>
 #include <linux/utsname.h>
@@ -35,6 +34,7 @@
 #include <linux/freezer.h>
 #include <asm/uaccess.h>
 #include <asm/processor.h>
+#include <net/ipv6.h>
 #include "cifspdu.h"
 #include "cifsglob.h"
 #include "cifsproto.h"
@@ -89,11 +89,13 @@ struct smb_vol {
        bool nullauth:1;   /* attempt to authenticate with null user */
        bool nocase:1;     /* request case insensitive filenames */
        bool nobrl:1;      /* disable sending byte range locks to srv */
+       bool mand_lock:1;  /* send mandatory not posix byte range lock reqs */
        bool seal:1;       /* request transport encryption on share */
        bool nodfs:1;      /* Do not request DFS, even if available */
        bool local_lease:1; /* check leases only on local system, not remote */
        bool noblocksnd:1;
        bool noautotune:1;
+       bool nostrictsync:1; /* do not force expensive SMBflush on every sync */
        unsigned int rsize;
        unsigned int wsize;
        unsigned int sockopt;
@@ -102,19 +104,16 @@ struct smb_vol {
 };
 
 static int ipv4_connect(struct TCP_Server_Info *server);
-static int ipv6_connect(struct sockaddr_in6 *psin_server,
-                       struct socket **csocket, bool noblocksnd);
-
-
-       /*
-        * cifs tcp session reconnection
-        *
-        * mark tcp session as reconnecting so temporarily locked
-        * mark all smb sessions as reconnecting for tcp session
-        * reconnect tcp session
-        * wake up waiters on reconnection? - (not needed currently)
-        */
+static int ipv6_connect(struct TCP_Server_Info *server);
 
+/*
+ * cifs tcp session reconnection
+ *
+ * mark tcp session as reconnecting so temporarily locked
+ * mark all smb sessions as reconnecting for tcp session
+ * reconnect tcp session
+ * wake up waiters on reconnection? - (not needed currently)
+ */
 static int
 cifs_reconnect(struct TCP_Server_Info *server)
 {
@@ -183,8 +182,7 @@ cifs_reconnect(struct TCP_Server_Info *server)
               (server->tcpStatus != CifsGood)) {
                try_to_freeze();
                if (server->addr.sockAddr6.sin6_family == AF_INET6)
-                       rc = ipv6_connect(&server->addr.sockAddr6,
-                                         &server->ssocket, server->noautotune);
+                       rc = ipv6_connect(server);
                else
                        rc = ipv4_connect(server);
                if (rc) {
@@ -826,8 +824,8 @@ cifs_parse_mount_options(char *options, const char *devname,
        /* null target name indicates to use *SMBSERVR default called name
           if we end up sending RFC1001 session initialize */
        vol->target_rfc1001_name[0] = 0;
-       vol->linux_uid = current->uid;  /* current->euid instead? */
-       vol->linux_gid = current->gid;
+       vol->linux_uid = current_uid();  /* use current_euid() instead? */
+       vol->linux_gid = current_gid();
        vol->dir_mode = S_IRWXUGO;
        /* 2767 perms indicate mandatory locking support */
        vol->file_mode = (S_IRWXUGO | S_ISGID) & (~S_IXGRP);
@@ -1250,6 +1248,17 @@ cifs_parse_mount_options(char *options, const char *devname,
                        if (vol->file_mode ==
                                (S_IALLUGO & ~(S_ISUID | S_IXGRP)))
                                vol->file_mode = S_IALLUGO;
+               } else if (strnicmp(data, "forcemandatorylock", 9) == 0) {
+                       /* will take the shorter form "forcemand" as well */
+                       /* This mount option will force use of mandatory
+                         (DOS/Windows style) byte range locks, instead of
+                         using posix advisory byte range locks, even if the
+                         Unix extensions are available and posix locks would
+                         be supported otherwise. If Unix extensions are not
+                         negotiated this has no effect since mandatory locks
+                         would be used (mandatory locks is all that those
+                         those servers support) */
+                       vol->mand_lock = 1;
                } else if (strnicmp(data, "setuids", 7) == 0) {
                        vol->setuids = 1;
                } else if (strnicmp(data, "nosetuids", 9) == 0) {
@@ -1266,6 +1275,10 @@ cifs_parse_mount_options(char *options, const char *devname,
                        vol->intr = 0;
                } else if (strnicmp(data, "intr", 4) == 0) {
                        vol->intr = 1;
+               } else if (strnicmp(data, "nostrictsync", 12) == 0) {
+                       vol->nostrictsync = 1;
+               } else if (strnicmp(data, "strictsync", 10) == 0) {
+                       vol->nostrictsync = 0;
                } else if (strnicmp(data, "serverino", 7) == 0) {
                        vol->server_ino = 1;
                } else if (strnicmp(data, "noserverino", 9) == 0) {
@@ -1346,7 +1359,7 @@ cifs_parse_mount_options(char *options, const char *devname,
 }
 
 static struct TCP_Server_Info *
-cifs_find_tcp_session(struct sockaddr *addr)
+cifs_find_tcp_session(struct sockaddr_storage *addr)
 {
        struct list_head *tmp;
        struct TCP_Server_Info *server;
@@ -1366,13 +1379,13 @@ cifs_find_tcp_session(struct sockaddr *addr)
                if (server->tcpStatus == CifsNew)
                        continue;
 
-               if (addr->sa_family == AF_INET &&
+               if (addr->ss_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)))
+               else if (addr->ss_family == AF_INET6 &&
+                        !ipv6_addr_equal(&server->addr.sockAddr6.sin6_addr,
+                                         &addr6->sin6_addr))
                        continue;
 
                ++server->srv_count;
@@ -1411,12 +1424,12 @@ static struct TCP_Server_Info *
 cifs_get_tcp_session(struct smb_vol *volume_info)
 {
        struct TCP_Server_Info *tcp_ses = NULL;
-       struct sockaddr addr;
+       struct sockaddr_storage addr;
        struct sockaddr_in *sin_server = (struct sockaddr_in *) &addr;
        struct sockaddr_in6 *sin_server6 = (struct sockaddr_in6 *) &addr;
        int rc;
 
-       memset(&addr, 0, sizeof(struct sockaddr));
+       memset(&addr, 0, sizeof(struct sockaddr_storage));
 
        if (volume_info->UNCip && volume_info->UNC) {
                rc = cifs_inet_pton(AF_INET, volume_info->UNCip,
@@ -1427,9 +1440,9 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
                        rc = cifs_inet_pton(AF_INET6, volume_info->UNCip,
                                            &sin_server6->sin6_addr.in6_u);
                        if (rc > 0)
-                               addr.sa_family = AF_INET6;
+                               addr.ss_family = AF_INET6;
                } else {
-                       addr.sa_family = AF_INET;
+                       addr.ss_family = AF_INET;
                }
 
                if (rc <= 0) {
@@ -1494,15 +1507,14 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
        tcp_ses->tcpStatus = CifsNew;
        ++tcp_ses->srv_count;
 
-       if (addr.sa_family == AF_INET6) {
+       if (addr.ss_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 */
                memcpy(&tcp_ses->addr.sockAddr6, sin_server6,
                        sizeof(struct sockaddr_in6));
                sin_server6->sin6_port = htons(volume_info->port);
-               rc = ipv6_connect(sin_server6, &tcp_ses->ssocket,
-                               volume_info->noblocksnd);
+               rc = ipv6_connect(tcp_ses);
        } else {
                memcpy(&tcp_ses->addr.sockAddr, sin_server,
                        sizeof(struct sockaddr_in));
@@ -1795,7 +1807,7 @@ ipv4_connect(struct TCP_Server_Info *server)
         *  user space buffer
         */
        socket->sk->sk_rcvtimeo = 7 * HZ;
-       socket->sk->sk_sndtimeo = 3 * HZ;
+       socket->sk->sk_sndtimeo = 5 * HZ;
 
        /* make the bufsizes depend on wsize/rsize and max requests */
        if (server->noautotune) {
@@ -1853,9 +1865,7 @@ ipv4_connect(struct TCP_Server_Info *server)
                        smb_buf = (struct smb_hdr *)ses_init_buf;
                        /* sizeof RFC1002_SESSION_REQUEST with no scope */
                        smb_buf->smb_buf_length = 0x81000044;
-                       rc = smb_send(socket, smb_buf, 0x44,
-                               (struct sockaddr *) &server->addr.sockAddr,
-                               server->noblocksnd);
+                       rc = smb_send(server, smb_buf, 0x44);
                        kfree(ses_init_buf);
                        msleep(1); /* RFC1001 layer in at least one server
                                      requires very short break before negprot
@@ -1875,79 +1885,81 @@ ipv4_connect(struct TCP_Server_Info *server)
 }
 
 static int
-ipv6_connect(struct sockaddr_in6 *psin_server, struct socket **csocket,
-            bool noblocksnd)
+ipv6_connect(struct TCP_Server_Info *server)
 {
        int rc = 0;
-       int connected = 0;
+       bool connected = false;
        __be16 orig_port = 0;
+       struct socket *socket = server->ssocket;
 
-       if (*csocket == NULL) {
+       if (socket == NULL) {
                rc = sock_create_kern(PF_INET6, SOCK_STREAM,
-                                     IPPROTO_TCP, csocket);
+                                     IPPROTO_TCP, &socket);
                if (rc < 0) {
                        cERROR(1, ("Error %d creating ipv6 socket", rc));
-                       *csocket = NULL;
+                       socket = NULL;
                        return rc;
-               } else {
-               /* BB other socket options to set KEEPALIVE, NODELAY? */
-                        cFYI(1, ("ipv6 Socket created"));
-                       (*csocket)->sk->sk_allocation = GFP_NOFS;
-                       cifs_reclassify_socket6(*csocket);
                }
-       }
 
-       psin_server->sin6_family = AF_INET6;
+               /* BB other socket options to set KEEPALIVE, NODELAY? */
+               cFYI(1, ("ipv6 Socket created"));
+               server->ssocket = socket;
+               socket->sk->sk_allocation = GFP_NOFS;
+               cifs_reclassify_socket6(socket);
+       }
 
-       if (psin_server->sin6_port) { /* user overrode default port */
-               rc = (*csocket)->ops->connect(*csocket,
-                               (struct sockaddr *) psin_server,
+       /* user overrode default port */
+       if (server->addr.sockAddr6.sin6_port) {
+               rc = socket->ops->connect(socket,
+                               (struct sockaddr *) &server->addr.sockAddr6,
                                sizeof(struct sockaddr_in6), 0);
                if (rc >= 0)
-                       connected = 1;
+                       connected = true;
        }
 
        if (!connected) {
                /* save original port so we can retry user specified port
                        later if fall back ports fail this time  */
 
-               orig_port = psin_server->sin6_port;
+               orig_port = server->addr.sockAddr6.sin6_port;
                /* do not retry on the same port we just failed on */
-               if (psin_server->sin6_port != htons(CIFS_PORT)) {
-                       psin_server->sin6_port = htons(CIFS_PORT);
-
-                       rc = (*csocket)->ops->connect(*csocket,
-                                       (struct sockaddr *) psin_server,
+               if (server->addr.sockAddr6.sin6_port != htons(CIFS_PORT)) {
+                       server->addr.sockAddr6.sin6_port = htons(CIFS_PORT);
+                       rc = socket->ops->connect(socket, (struct sockaddr *)
+                                       &server->addr.sockAddr6,
                                        sizeof(struct sockaddr_in6), 0);
                        if (rc >= 0)
-                               connected = 1;
+                               connected = true;
                }
        }
        if (!connected) {
-               psin_server->sin6_port = htons(RFC1001_PORT);
-               rc = (*csocket)->ops->connect(*csocket, (struct sockaddr *)
-                                psin_server, sizeof(struct sockaddr_in6), 0);
+               server->addr.sockAddr6.sin6_port = htons(RFC1001_PORT);
+               rc = socket->ops->connect(socket, (struct sockaddr *)
+                               &server->addr.sockAddr6,
+                               sizeof(struct sockaddr_in6), 0);
                if (rc >= 0)
-                       connected = 1;
+                       connected = true;
        }
 
        /* give up here - unless we want to retry on different
                protocol families some day */
        if (!connected) {
                if (orig_port)
-                       psin_server->sin6_port = orig_port;
+                       server->addr.sockAddr6.sin6_port = orig_port;
                cFYI(1, ("Error %d connecting to server via ipv6", rc));
-               sock_release(*csocket);
-               *csocket = NULL;
+               sock_release(socket);
+               server->ssocket = NULL;
                return rc;
        }
-       /* Eventually check for other socket options to change from
-               the default. sock_setsockopt not used because it expects
-               user space buffer */
-       (*csocket)->sk->sk_rcvtimeo = 7 * HZ;
-       if (!noblocksnd)
-               (*csocket)->sk->sk_sndtimeo = 3 * HZ;
 
+       /*
+        * Eventually check for other socket options to change from
+        * the default. sock_setsockopt not used because it expects
+        * user space buffer
+        */
+       socket->sk->sk_rcvtimeo = 7 * HZ;
+       socket->sk->sk_sndtimeo = 5 * HZ;
+       server->ssocket = socket;
 
        return rc;
 }
@@ -2153,6 +2165,10 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info,
                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->nostrictsync)
+               cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NO_SSYNC;
+       if (pvolume_info->mand_lock)
+               cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NOPOSIXBRL;
        if (pvolume_info->cifs_acl)
                cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_CIFS_ACL;
        if (pvolume_info->override_uid)
@@ -2171,6 +2187,33 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info,
                           "mount option supported"));
 }
 
+static int
+is_path_accessible(int xid, struct cifsTconInfo *tcon,
+                  struct cifs_sb_info *cifs_sb, const char *full_path)
+{
+       int rc;
+       __u64 inode_num;
+       FILE_ALL_INFO *pfile_info;
+
+       rc = CIFSGetSrvInodeNumber(xid, tcon, full_path, &inode_num,
+                                  cifs_sb->local_nls,
+                                  cifs_sb->mnt_cifs_flags &
+                                               CIFS_MOUNT_MAP_SPECIAL_CHR);
+       if (rc != -EOPNOTSUPP)
+               return rc;
+
+       pfile_info = kmalloc(sizeof(FILE_ALL_INFO), GFP_KERNEL);
+       if (pfile_info == NULL)
+               return -ENOMEM;
+
+       rc = CIFSSMBQPathInfo(xid, tcon, full_path, pfile_info,
+                             0 /* not legacy */, cifs_sb->local_nls,
+                             cifs_sb->mnt_cifs_flags &
+                               CIFS_MOUNT_MAP_SPECIAL_CHR);
+       kfree(pfile_info);
+       return rc;
+}
+
 int
 cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
           char *mount_data, const char *devname)
@@ -2181,6 +2224,7 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
        struct cifsSesInfo *pSesInfo = NULL;
        struct cifsTconInfo *tcon = NULL;
        struct TCP_Server_Info *srvTcp = NULL;
+       char   *full_path;
 
        xid = GetXid();
 
@@ -2259,11 +2303,11 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
                /* new SMB session uses our srvTcp ref */
                pSesInfo->server = srvTcp;
                if (srvTcp->addr.sockAddr6.sin6_family == AF_INET6)
-                       sprintf(pSesInfo->serverName, NIP6_FMT,
-                               NIP6(srvTcp->addr.sockAddr6.sin6_addr));
+                       sprintf(pSesInfo->serverName, "%pI6",
+                               &srvTcp->addr.sockAddr6.sin6_addr);
                else
-                       sprintf(pSesInfo->serverName, NIPQUAD_FMT,
-                               NIPQUAD(srvTcp->addr.sockAddr.sin_addr.s_addr));
+                       sprintf(pSesInfo->serverName, "%pI4",
+                               &srvTcp->addr.sockAddr.sin_addr.s_addr);
 
                write_lock(&cifs_tcp_ses_lock);
                list_add(&pSesInfo->smb_ses_list, &srvTcp->smb_ses_list);
@@ -2271,9 +2315,12 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
 
                /* 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;
+                       pSesInfo->password = kstrdup(volume_info->password,
+                                                    GFP_KERNEL);
+                       if (!pSesInfo->password) {
+                               rc = -ENOMEM;
+                               goto mount_fail_check;
+                       }
                }
                if (volume_info->username)
                        strncpy(pSesInfo->userName, volume_info->username,
@@ -2313,7 +2360,16 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
                                rc = -ENOMEM;
                                goto mount_fail_check;
                        }
+
                        tcon->ses = pSesInfo;
+                       if (volume_info->password) {
+                               tcon->password = kstrdup(volume_info->password,
+                                                        GFP_KERNEL);
+                               if (!tcon->password) {
+                                       rc = -ENOMEM;
+                                       goto mount_fail_check;
+                               }
+                       }
 
                        /* check for null share name ie connect to dfs root */
                        if ((strchr(volume_info->UNC + 3, '\\') == NULL)
@@ -2405,6 +2461,23 @@ mount_fail_check:
                cifs_sb->rsize = min(cifs_sb->rsize,
                               (tcon->ses->server->maxBuf - MAX_CIFS_HDR_SIZE));
 
+       if (!rc && cifs_sb->prepathlen) {
+               /* build_path_to_root works only when we have a valid tcon */
+               full_path = cifs_build_path_to_root(cifs_sb);
+               if (full_path == NULL) {
+                       rc = -ENOMEM;
+                       goto mount_fail_check;
+               }
+               rc = is_path_accessible(xid, tcon, cifs_sb, full_path);
+               if (rc) {
+                       cERROR(1, ("Path %s in not accessible: %d",
+                                               full_path, rc));
+                       kfree(full_path);
+                       goto mount_fail_check;
+               }
+               kfree(full_path);
+       }
+
        /* volume_info->password is freed above when existing session found
        (in which case it is not needed anymore) but when new sesion is created
        the password ptr is put in the new session structure (in which case the
@@ -2571,7 +2644,7 @@ CIFSSessSetup(unsigned int xid, struct cifsSesInfo *ses,
                __u16 action = le16_to_cpu(pSMBr->resp.Action);
                __u16 blob_len = le16_to_cpu(pSMBr->resp.SecurityBlobLength);
                if (action & GUEST_LOGIN)
-                       cFYI(1, (" Guest login")); /* BB mark SesInfo struct? */
+                       cFYI(1, ("Guest login")); /* BB mark SesInfo struct? */
                ses->Suid = smb_buffer_response->Uid; /* UID left in wire format
                                                         (little endian) */
                cFYI(1, ("UID = %d ", ses->Suid));
@@ -2717,13 +2790,11 @@ CIFSSessSetup(unsigned int xid, struct cifsSesInfo *ses,
                                              len));
                        }
                } else {
-                       cERROR(1,
-                              (" Security Blob Length extends beyond "
+                       cERROR(1, ("Security Blob Length extends beyond "
                                "end of SMB"));
                }
        } else {
-               cERROR(1,
-                      (" Invalid Word count %d: ",
+               cERROR(1, ("Invalid Word count %d: ",
                        smb_buffer_response->WordCount));
                rc = -EIO;
        }
@@ -2881,7 +2952,7 @@ CIFSNTLMSSPNegotiateSessSetup(unsigned int xid,
                __u16 blob_len = le16_to_cpu(pSMBr->resp.SecurityBlobLength);
 
                if (action & GUEST_LOGIN)
-                       cFYI(1, (" Guest login"));
+                       cFYI(1, ("Guest login"));
        /* Do we want to set anything in SesInfo struct when guest login? */
 
                bcc_ptr = pByteArea(smb_buffer_response);
@@ -2889,8 +2960,7 @@ CIFSNTLMSSPNegotiateSessSetup(unsigned int xid,
 
                SecurityBlob2 = (PCHALLENGE_MESSAGE) bcc_ptr;
                if (SecurityBlob2->MessageType != NtLmChallenge) {
-                       cFYI(1,
-                            ("Unexpected NTLMSSP message type received %d",
+                       cFYI(1, ("Unexpected NTLMSSP message type received %d",
                              SecurityBlob2->MessageType));
                } else if (ses) {
                        ses->Suid = smb_buffer_response->Uid; /* UID left in le format */
@@ -3062,8 +3132,7 @@ CIFSNTLMSSPNegotiateSessSetup(unsigned int xid,
                        cERROR(1, ("No session structure passed in."));
                }
        } else {
-               cERROR(1,
-                      (" Invalid Word count %d:",
+               cERROR(1, ("Invalid Word count %d:",
                        smb_buffer_response->WordCount));
                rc = -EIO;
        }
@@ -3302,7 +3371,7 @@ CIFSNTLMSSPAuthSessSetup(unsigned int xid, struct cifsSesInfo *ses,
                __u16 action = le16_to_cpu(pSMBr->resp.Action);
                __u16 blob_len = le16_to_cpu(pSMBr->resp.SecurityBlobLength);
                if (action & GUEST_LOGIN)
-                       cFYI(1, (" Guest login")); /* BB Should we set anything
+                       cFYI(1, ("Guest login")); /* BB Should we set anything
                                                         in SesInfo struct ? */
 /*             if (SecurityBlob2->MessageType != NtLm??) {
                        cFYI("Unexpected message type on auth response is %d"));
@@ -3525,12 +3594,14 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,
                   NTLMv2 password here) */
 #ifdef CONFIG_CIFS_WEAK_PW_HASH
                if ((extended_security & CIFSSEC_MAY_LANMAN) &&
-                       (ses->server->secType == LANMAN))
-                       calc_lanman_hash(ses, bcc_ptr);
+                   (ses->server->secType == LANMAN))
+                       calc_lanman_hash(tcon->password, ses->server->cryptKey,
+                                        ses->server->secMode &
+                                           SECMODE_PW_ENCRYPT ? true : false,
+                                        bcc_ptr);
                else
 #endif /* CIFS_WEAK_PW_HASH */
-               SMBNTencrypt(ses->password,
-                            ses->server->cryptKey,
+               SMBNTencrypt(tcon->password, ses->server->cryptKey,
                             bcc_ptr);
 
                bcc_ptr += CIFS_SESS_KEY_SIZE;