cifs: add "mfsymlinks" mount option
[pandora-kernel.git] / fs / cifs / connect.c
index ec0ea4a..435b912 100644 (file)
@@ -100,11 +100,13 @@ struct smb_vol {
        bool noautotune:1;
        bool nostrictsync:1; /* do not force expensive SMBflush on every sync */
        bool fsc:1;     /* enable fscache */
+       bool mfsymlinks:1; /* use Minshall+French Symlinks */
        unsigned int rsize;
        unsigned int wsize;
        bool sockopt_tcp_nodelay:1;
        unsigned short int port;
        char *prepath;
+       struct sockaddr_storage srcaddr; /* allow binding to a local IP */
        struct nls_table *local_nls;
 };
 
@@ -400,7 +402,9 @@ incomplete_rcv:
                        cFYI(1, "call to reconnect done");
                        csocket = server->ssocket;
                        continue;
-               } else if ((length == -ERESTARTSYS) || (length == -EAGAIN)) {
+               } else if (length == -ERESTARTSYS ||
+                          length == -EAGAIN ||
+                          length == -EINTR) {
                        msleep(1); /* minimum sleep to prevent looping
                                allowing socket to clear and app threads to set
                                tcpStatus CifsNeedReconnect if server hung */
@@ -414,18 +418,6 @@ incomplete_rcv:
                        } else
                                continue;
                } else if (length <= 0) {
-                       if (server->tcpStatus == CifsNew) {
-                               cFYI(1, "tcp session abend after SMBnegprot");
-                               /* some servers kill the TCP session rather than
-                                  returning an SMB negprot error, in which
-                                  case reconnecting here is not going to help,
-                                  and so simply return error to mount */
-                               break;
-                       }
-                       if (!try_to_freeze() && (length == -EINTR)) {
-                               cFYI(1, "cifsd thread killed");
-                               break;
-                       }
                        cFYI(1, "Reconnect after unexpected peek error %d",
                                length);
                        cifs_reconnect(server);
@@ -466,27 +458,19 @@ incomplete_rcv:
                           an error on SMB negprot response */
                        cFYI(1, "Negative RFC1002 Session Response Error 0x%x)",
                                pdu_length);
-                       if (server->tcpStatus == CifsNew) {
-                               /* if nack on negprot (rather than
-                               ret of smb negprot error) reconnecting
-                               not going to help, ret error to mount */
-                               break;
-                       } else {
-                               /* give server a second to
-                               clean up before reconnect attempt */
-                               msleep(1000);
-                               /* always try 445 first on reconnect
-                               since we get NACK on some if we ever
-                               connected to port 139 (the NACK is
-                               since we do not begin with RFC1001
-                               session initialize frame) */
-                               server->addr.sockAddr.sin_port =
-                                       htons(CIFS_PORT);
-                               cifs_reconnect(server);
-                               csocket = server->ssocket;
-                               wake_up(&server->response_q);
-                               continue;
-                       }
+                       /* give server a second to clean up  */
+                       msleep(1000);
+                       /* always try 445 first on reconnect since we get NACK
+                        * on some if we ever connected to port 139 (the NACK
+                        * is since we do not begin with RFC1001 session
+                        * initialize frame)
+                        */
+                       cifs_set_port((struct sockaddr *)
+                                       &server->addr.sockAddr, CIFS_PORT);
+                       cifs_reconnect(server);
+                       csocket = server->ssocket;
+                       wake_up(&server->response_q);
+                       continue;
                } else if (temp != (char) 0) {
                        cERROR(1, "Unknown RFC 1002 frame");
                        cifs_dump_mem(" Received Data: ", (char *)smb_buffer,
@@ -522,8 +506,7 @@ incomplete_rcv:
                     total_read += length) {
                        length = kernel_recvmsg(csocket, &smb_msg, &iov, 1,
                                                pdu_length - total_read, 0);
-                       if ((server->tcpStatus == CifsExiting) ||
-                           (length == -EINTR)) {
+                       if (server->tcpStatus == CifsExiting) {
                                /* then will exit */
                                reconnect = 2;
                                break;
@@ -534,8 +517,9 @@ incomplete_rcv:
                                /* Now we will reread sock */
                                reconnect = 1;
                                break;
-                       } else if ((length == -ERESTARTSYS) ||
-                                  (length == -EAGAIN)) {
+                       } else if (length == -ERESTARTSYS ||
+                                  length == -EAGAIN ||
+                                  length == -EINTR) {
                                msleep(1); /* minimum sleep to prevent looping,
                                              allowing socket to clear and app
                                              threads to set tcpStatus
@@ -1064,6 +1048,22 @@ cifs_parse_mount_options(char *options, const char *devname,
                                                    "long\n");
                                return 1;
                        }
+               } else if (strnicmp(data, "srcaddr", 7) == 0) {
+                       vol->srcaddr.ss_family = AF_UNSPEC;
+
+                       if (!value || !*value) {
+                               printk(KERN_WARNING "CIFS: srcaddr value"
+                                      " not specified.\n");
+                               return 1;       /* needs_arg; */
+                       }
+                       i = cifs_convert_address((struct sockaddr *)&vol->srcaddr,
+                                                value, strlen(value));
+                       if (i < 0) {
+                               printk(KERN_WARNING "CIFS:  Could not parse"
+                                      " srcaddr: %s\n",
+                                      value);
+                               return 1;
+                       }
                } else if (strnicmp(data, "prefixpath", 10) == 0) {
                        if (!value || !*value) {
                                printk(KERN_WARNING
@@ -1343,6 +1343,8 @@ cifs_parse_mount_options(char *options, const char *devname,
                                "/proc/fs/cifs/LookupCacheEnabled to 0\n");
                } else if (strnicmp(data, "fsc", 3) == 0) {
                        vol->fsc = true;
+               } else if (strnicmp(data, "mfsymlinks", 10) == 0) {
+                       vol->mfsymlinks = true;
                } else
                        printk(KERN_WARNING "CIFS: Unknown mount option %s\n",
                                                data);
@@ -1392,8 +1394,36 @@ cifs_parse_mount_options(char *options, const char *devname,
        return 0;
 }
 
+/** Returns true if srcaddr isn't specified and rhs isn't
+ * specified, or if srcaddr is specified and
+ * matches the IP address of the rhs argument.
+ */
+static bool
+srcip_matches(struct sockaddr *srcaddr, struct sockaddr *rhs)
+{
+       switch (srcaddr->sa_family) {
+       case AF_UNSPEC:
+               return (rhs->sa_family == AF_UNSPEC);
+       case AF_INET: {
+               struct sockaddr_in *saddr4 = (struct sockaddr_in *)srcaddr;
+               struct sockaddr_in *vaddr4 = (struct sockaddr_in *)rhs;
+               return (saddr4->sin_addr.s_addr == vaddr4->sin_addr.s_addr);
+       }
+       case AF_INET6: {
+               struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)srcaddr;
+               struct sockaddr_in6 *vaddr6 = (struct sockaddr_in6 *)&rhs;
+               return ipv6_addr_equal(&saddr6->sin6_addr, &vaddr6->sin6_addr);
+       }
+       default:
+               WARN_ON(1);
+               return false; /* don't expect to be here */
+       }
+}
+
+
 static bool
-match_address(struct TCP_Server_Info *server, struct sockaddr *addr)
+match_address(struct TCP_Server_Info *server, struct sockaddr *addr,
+             struct sockaddr *srcaddr)
 {
        struct sockaddr_in *addr4 = (struct sockaddr_in *)addr;
        struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
@@ -1420,6 +1450,9 @@ match_address(struct TCP_Server_Info *server, struct sockaddr *addr)
                break;
        }
 
+       if (!srcip_matches(srcaddr, (struct sockaddr *)&server->srcaddr))
+               return false;
+
        return true;
 }
 
@@ -1487,7 +1520,8 @@ cifs_find_tcp_session(struct sockaddr *addr, struct smb_vol *vol)
                if (server->tcpStatus == CifsNew)
                        continue;
 
-               if (!match_address(server, addr))
+               if (!match_address(server, addr,
+                                  (struct sockaddr *)&vol->srcaddr))
                        continue;
 
                if (!match_security(server, vol))
@@ -1602,6 +1636,8 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
         * no need to spinlock this init of tcpStatus or srv_count
         */
        tcp_ses->tcpStatus = CifsNew;
+       memcpy(&tcp_ses->srcaddr, &volume_info->srcaddr,
+              sizeof(tcp_ses->srcaddr));
        ++tcp_ses->srv_count;
 
        if (addr.ss_family == AF_INET6) {
@@ -1708,7 +1744,6 @@ cifs_put_smb_ses(struct cifsSesInfo *ses)
                CIFSSMBLogoff(xid, ses);
                _FreeXid(xid);
        }
-       cifs_crypto_shash_release(server);
        sesInfoFree(ses);
        cifs_put_tcp_session(server);
 }
@@ -1725,9 +1760,6 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
        if (ses) {
                cFYI(1, "Existing smb sess found (status=%d)", ses->status);
 
-               /* existing SMB ses has a server reference already */
-               cifs_put_tcp_session(server);
-
                mutex_lock(&ses->session_mutex);
                rc = cifs_negotiate_protocol(xid, ses);
                if (rc) {
@@ -1750,6 +1782,9 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
                        }
                }
                mutex_unlock(&ses->session_mutex);
+
+               /* existing SMB ses has a server reference already */
+               cifs_put_tcp_session(server);
                FreeXid(xid);
                return ses;
        }
@@ -1759,6 +1794,8 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
        if (ses == NULL)
                goto get_ses_fail;
 
+       ses->tilen = 0;
+       ses->tiblob = NULL;
        /* new SMB session uses our server ref */
        ses->server = server;
        if (server->addr.sockAddr6.sin6_family == AF_INET6)
@@ -1788,23 +1825,13 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
        ses->linux_uid = volume_info->linux_uid;
        ses->overrideSecFlg = volume_info->secFlg;
 
-       rc = cifs_crypto_shash_allocate(server);
-       if (rc) {
-               cERROR(1, "could not setup hash structures rc %d", rc);
-               goto get_ses_fail;
-       }
-       server->tilen = 0;
-       server->tiblob = NULL;
-
        mutex_lock(&ses->session_mutex);
        rc = cifs_negotiate_protocol(xid, ses);
        if (!rc)
                rc = cifs_setup_session(xid, ses, volume_info->local_nls);
        mutex_unlock(&ses->session_mutex);
-       if (rc) {
-               cifs_crypto_shash_release(ses->server);
+       if (rc)
                goto get_ses_fail;
-       }
 
        /* success, put it on the list */
        write_lock(&cifs_tcp_ses_lock);
@@ -2026,6 +2053,33 @@ static void rfc1002mangle(char *target, char *source, unsigned int length)
 
 }
 
+static int
+bind_socket(struct TCP_Server_Info *server)
+{
+       int rc = 0;
+       if (server->srcaddr.ss_family != AF_UNSPEC) {
+               /* Bind to the specified local IP address */
+               struct socket *socket = server->ssocket;
+               rc = socket->ops->bind(socket,
+                                      (struct sockaddr *) &server->srcaddr,
+                                      sizeof(server->srcaddr));
+               if (rc < 0) {
+                       struct sockaddr_in *saddr4;
+                       struct sockaddr_in6 *saddr6;
+                       saddr4 = (struct sockaddr_in *)&server->srcaddr;
+                       saddr6 = (struct sockaddr_in6 *)&server->srcaddr;
+                       if (saddr6->sin6_family == AF_INET6)
+                               cERROR(1, "cifs: "
+                                      "Failed to bind to: %pI6c, error: %d\n",
+                                      &saddr6->sin6_addr, rc);
+                       else
+                               cERROR(1, "cifs: "
+                                      "Failed to bind to: %pI4, error: %d\n",
+                                      &saddr4->sin_addr.s_addr, rc);
+               }
+       }
+       return rc;
+}
 
 static int
 ipv4_connect(struct TCP_Server_Info *server)
@@ -2051,6 +2105,10 @@ ipv4_connect(struct TCP_Server_Info *server)
                cifs_reclassify_socket4(socket);
        }
 
+       rc = bind_socket(server);
+       if (rc < 0)
+               return rc;
+
        /* user overrode default port */
        if (server->addr.sockAddr.sin_port) {
                rc = socket->ops->connect(socket, (struct sockaddr *)
@@ -2213,6 +2271,10 @@ ipv6_connect(struct TCP_Server_Info *server)
                cifs_reclassify_socket6(socket);
        }
 
+       rc = bind_socket(server);
+       if (rc < 0)
+               return rc;
+
        /* user overrode default port */
        if (server->addr.sockAddr6.sin6_port) {
                rc = socket->ops->connect(socket,
@@ -2495,6 +2557,14 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info,
                cFYI(1, "mounting share using direct i/o");
                cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DIRECT_IO;
        }
+       if (pvolume_info->mfsymlinks) {
+               if (pvolume_info->sfu_emul) {
+                       cERROR(1,  "mount option mfsymlinks ignored if sfu "
+                                  "mount option is used");
+               } else {
+                       cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_MF_SYMLINKS;
+               }
+       }
 
        if ((pvolume_info->cifs_acl) && (pvolume_info->dynperm))
                cERROR(1, "mount option dynperm ignored if cifsacl "