[GLUE] Rsync SAMBA_3_2_0 SVN r25598 in order to create the v3-2-test branch.
[samba.git] / source / libsmb / clireadwrite.c
index 8012c02a8fc0f3d13d6f5bb6286e8f927edd5e12..ed80dfaf1a7f848b71622a507de610740d09567d 100644 (file)
@@ -1,12 +1,11 @@
 /* 
-   Unix SMB/Netbios implementation.
-   Version 3.0
+   Unix SMB/CIFS implementation.
    client file read/write routines
    Copyright (C) Andrew Tridgell 1994-1998
    
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
+   the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#define NO_SYSLOG
-
 #include "includes.h"
 
 /****************************************************************************
-issue a single SMBread and don't wait for a reply
+Issue a single SMBread and don't wait for a reply.
 ****************************************************************************/
-static void cli_issue_read(struct cli_state *cli, int fnum, off_t offset, 
+
+static BOOL cli_issue_read(struct cli_state *cli, int fnum, off_t offset, 
                           size_t size, int i)
 {
+       BOOL bigoffset = False;
+
        memset(cli->outbuf,'\0',smb_size);
        memset(cli->inbuf,'\0',smb_size);
 
-       set_message(cli->outbuf,10,0,True);
+       if ((SMB_BIG_UINT)offset >> 32) 
+               bigoffset = True;
+
+       set_message(cli->outbuf,bigoffset ? 12 : 10,0,True);
                
-       CVAL(cli->outbuf,smb_com) = SMBreadX;
+       SCVAL(cli->outbuf,smb_com,SMBreadX);
        SSVAL(cli->outbuf,smb_tid,cli->cnum);
        cli_setup_packet(cli);
 
-       CVAL(cli->outbuf,smb_vwv0) = 0xFF;
+       SCVAL(cli->outbuf,smb_vwv0,0xFF);
        SSVAL(cli->outbuf,smb_vwv2,fnum);
        SIVAL(cli->outbuf,smb_vwv3,offset);
        SSVAL(cli->outbuf,smb_vwv5,size);
        SSVAL(cli->outbuf,smb_vwv6,size);
+       SSVAL(cli->outbuf,smb_vwv7,(size >> 16));
        SSVAL(cli->outbuf,smb_mid,cli->mid + i);
 
-       cli_send_smb(cli);
+       if (bigoffset) {
+               SIVAL(cli->outbuf,smb_vwv10,(((SMB_BIG_UINT)offset)>>32) & 0xffffffff);
+       }
+
+       return cli_send_smb(cli);
 }
 
 /****************************************************************************
-  read from a file
+  Read size bytes at offset offset using SMBreadX.
 ****************************************************************************/
-size_t cli_read(struct cli_state *cli, int fnum, char *buf, off_t offset, size_t size)
+
+ssize_t cli_read(struct cli_state *cli, int fnum, char *buf, off_t offset, size_t size)
 {
        char *p;
-       int total = -1;
-       int issued=0;
-       int received=0;
-/*
- * There is a problem in this code when mpx is more than one.
- * for some reason files can get corrupted when being read.
- * Until we understand this fully I am serializing reads (one
- * read/one reply) for now. JRA.
- */
-#if 0
-       int mpx = MAX(cli->max_mux-1, 1); 
-#else
-       int mpx = 1;
-#endif
-       int block = (cli->max_xmit - (smb_size+32)) & ~1023;
-       int mid;
-       int blocks = (size + (block-1)) / block;
+       size_t size2;
+       size_t readsize;
+       ssize_t total = 0;
+       /* We can only do direct reads if not signing. */
+       BOOL direct_reads = !client_is_signing_on(cli);
+
+       if (size == 0) 
+               return 0;
+
+       /*
+        * Set readsize to the maximum size we can handle in one readX,
+        * rounded down to a multiple of 1024.
+        */
+
+       if (client_is_signing_on(cli) == False && (cli->posix_capabilities & CIFS_UNIX_LARGE_READ_CAP)) {
+               readsize = CLI_SAMBA_MAX_POSIX_LARGE_READX_SIZE;
+       } else if (cli->capabilities & CAP_LARGE_READX) {
+               if (cli->is_samba) {
+                       readsize = CLI_SAMBA_MAX_LARGE_READX_SIZE;
+               } else {
+                       readsize = CLI_WINDOWS_MAX_LARGE_READX_SIZE;
+               }
+       } else {
+               readsize = (cli->max_xmit - (smb_size+32)) & ~1023;
+       }
 
-       if (size == 0) return 0;
+       while (total < size) {
+               readsize = MIN(readsize, size-total);
 
-       while (received < blocks) {
-               int size2;
+               /* Issue a read and receive a reply */
 
-               while (issued - received < mpx && issued < blocks) {
-                       int size1 = MIN(block, size-issued*block);
-                       cli_issue_read(cli, fnum, offset+issued*block, size1, issued);
-                       issued++;
-               }
+               if (!cli_issue_read(cli, fnum, offset, readsize, 0))
+                       return -1;
 
-               if (!cli_receive_smb(cli)) {
-                       return total;
+               if (direct_reads) {
+                       if (!cli_receive_smb_readX_header(cli))
+                               return -1;
+               } else {
+                       if (!cli_receive_smb(cli))
+                               return -1;
                }
 
-               received++;
-               mid = SVAL(cli->inbuf, smb_mid) - cli->mid;
-               size2 = SVAL(cli->inbuf, smb_vwv5);
+               /* Check for error.  Make sure to check for DOS and NT
+                   errors. */
 
-               if (CVAL(cli->inbuf,smb_rcls) != 0) {
-                       blocks = MIN(blocks, mid-1);
-                       continue;
-               }
+                if (cli_is_error(cli)) {
+                       BOOL recoverable_error = False;
+                        NTSTATUS status = NT_STATUS_OK;
+                        uint8 eclass = 0;
+                       uint32 ecode = 0;
 
-               if (size2 <= 0) {
-                       blocks = MIN(blocks, mid-1);
-                       /* this distinguishes EOF from an error */
-                       total = MAX(total, 0);
-                       continue;
+                        if (cli_is_nt_error(cli))
+                                status = cli_nt_error(cli);
+                        else
+                                cli_dos_error(cli, &eclass, &ecode);
+
+                       /*
+                        * ERRDOS ERRmoredata or STATUS_MORE_ENRTIES is a
+                        * recoverable error, plus we have valid data in the
+                        * packet so don't error out here.
+                        */
+
+                        if ((eclass == ERRDOS && ecode == ERRmoredata) ||
+                            NT_STATUS_V(status) == NT_STATUS_V(STATUS_MORE_ENTRIES))
+                               recoverable_error = True;
+
+                       if (!recoverable_error)
+                                return -1;
                }
 
-               if (size2 > block) {
-                       DEBUG(0,("server returned more than we wanted!\n"));
+               size2 = SVAL(cli->inbuf, smb_vwv5);
+               size2 |= (((unsigned int)(SVAL(cli->inbuf, smb_vwv7))) << 16);
+
+               if (size2 > readsize) {
+                       DEBUG(5,("server returned more than we wanted!\n"));
                        return -1;
-               }
-               if (mid >= issued) {
-                       DEBUG(0,("invalid mid from server!\n"));
+               } else if (size2 < 0) {
+                       DEBUG(5,("read return < 0!\n"));
                        return -1;
                }
-               p = smb_base(cli->inbuf) + SVAL(cli->inbuf,smb_vwv6);
 
-               memcpy(buf+mid*block, p, size2);
+               if (!direct_reads) {
+                       /* Copy data into buffer */
+                       p = smb_base(cli->inbuf) + SVAL(cli->inbuf,smb_vwv6);
+                       memcpy(buf + total, p, size2);
+               } else {
+                       /* Ensure the remaining data matches the return size. */
+                       ssize_t toread = smb_len_large(cli->inbuf) - SVAL(cli->inbuf,smb_vwv6);
+
+                       /* Ensure the size is correct. */
+                       if (toread != size2) {
+                               DEBUG(5,("direct read logic fail toread (%d) != size2 (%u)\n",
+                                       (int)toread, (unsigned int)size2 ));
+                               return -1;
+                       }
+
+                       /* Read data directly into buffer */
+                       toread = cli_receive_smb_data(cli,buf+total,size2);
+                       if (toread != size2) {
+                               DEBUG(5,("direct read read failure toread (%d) != size2 (%u)\n",
+                                       (int)toread, (unsigned int)size2 ));
+                               return -1;
+                       }
+               }
+
+               total += size2;
+               offset += size2;
+
+               /*
+                * If the server returned less than we asked for we're at EOF.
+                */
 
-               total = MAX(total, mid*block + size2);
+               if (size2 < readsize)
+                       break;
        }
 
-       while (received < issued) {
-               cli_receive_smb(cli);
-               received++;
+       return total;
+}
+
+#if 0  /* relies on client_receive_smb(), now a static in libsmb/clientgen.c */
+
+/* This call is INCOMPATIBLE with SMB signing.  If you remove the #if 0
+   you must fix ensure you don't attempt to sign the packets - data
+   *will* be currupted */
+
+/****************************************************************************
+Issue a single SMBreadraw and don't wait for a reply.
+****************************************************************************/
+
+static BOOL cli_issue_readraw(struct cli_state *cli, int fnum, off_t offset, 
+                          size_t size, int i)
+{
+
+       if (!cli->sign_info.use_smb_signing) {
+               DEBUG(0, ("Cannot use readraw and SMB Signing\n"));
+               return False;
        }
        
-       return total;
+       memset(cli->outbuf,'\0',smb_size);
+       memset(cli->inbuf,'\0',smb_size);
+
+       set_message(cli->outbuf,10,0,True);
+               
+       SCVAL(cli->outbuf,smb_com,SMBreadbraw);
+       SSVAL(cli->outbuf,smb_tid,cli->cnum);
+       cli_setup_packet(cli);
+
+       SSVAL(cli->outbuf,smb_vwv0,fnum);
+       SIVAL(cli->outbuf,smb_vwv1,offset);
+       SSVAL(cli->outbuf,smb_vwv2,size);
+       SSVAL(cli->outbuf,smb_vwv3,size);
+       SSVAL(cli->outbuf,smb_mid,cli->mid + i);
+
+       return cli_send_smb(cli);
 }
 
+/****************************************************************************
+ Tester for the readraw call.
+****************************************************************************/
+
+ssize_t cli_readraw(struct cli_state *cli, int fnum, char *buf, off_t offset, size_t size)
+{
+       char *p;
+       int size2;
+       size_t readsize;
+       ssize_t total = 0;
+
+       if (size == 0) 
+               return 0;
+
+       /*
+        * Set readsize to the maximum size we can handle in one readraw.
+        */
+
+       readsize = 0xFFFF;
+
+       while (total < size) {
+               readsize = MIN(readsize, size-total);
+
+               /* Issue a read and receive a reply */
+
+               if (!cli_issue_readraw(cli, fnum, offset, readsize, 0))
+                       return -1;
+
+               if (!client_receive_smb(cli->fd, cli->inbuf, cli->timeout))
+                       return -1;
+
+               size2 = smb_len(cli->inbuf);
 
+               if (size2 > readsize) {
+                       DEBUG(5,("server returned more than we wanted!\n"));
+                       return -1;
+               } else if (size2 < 0) {
+                       DEBUG(5,("read return < 0!\n"));
+                       return -1;
+               }
+
+               /* Copy data into buffer */
+
+               if (size2) {
+                       p = cli->inbuf + 4;
+                       memcpy(buf + total, p, size2);
+               }
+
+               total += size2;
+               offset += size2;
+
+               /*
+                * If the server returned less than we asked for we're at EOF.
+                */
+
+               if (size2 < readsize)
+                       break;
+       }
+
+       return total;
+}
+#endif
 /****************************************************************************
 issue a single SMBwrite and don't wait for a reply
 ****************************************************************************/
-static void cli_issue_write(struct cli_state *cli, int fnum, off_t offset, uint16 mode, char *buf,
+
+static BOOL cli_issue_write(struct cli_state *cli, int fnum, off_t offset, 
+                           uint16 mode, const char *buf,
                            size_t size, int i)
 {
        char *p;
+       BOOL large_writex = False;
+
+       if (size > cli->bufsize) {
+               cli->outbuf = (char *)SMB_REALLOC(cli->outbuf, size + 1024);
+               if (!cli->outbuf) {
+                       return False;
+               }
+               cli->inbuf = (char *)SMB_REALLOC(cli->inbuf, size + 1024);
+               if (cli->inbuf == NULL) {
+                       SAFE_FREE(cli->outbuf);
+                       return False;
+               }
+               cli->bufsize = size + 1024;
+       }
 
        memset(cli->outbuf,'\0',smb_size);
        memset(cli->inbuf,'\0',smb_size);
 
-       set_message(cli->outbuf,12,size,True);
+       if (((SMB_BIG_UINT)offset >> 32) || (size > 0xFFFF)) {
+               large_writex = True;
+       }
+
+       if (large_writex)
+               set_message(cli->outbuf,14,0,True);
+       else
+               set_message(cli->outbuf,12,0,True);
        
-       CVAL(cli->outbuf,smb_com) = SMBwriteX;
+       SCVAL(cli->outbuf,smb_com,SMBwriteX);
        SSVAL(cli->outbuf,smb_tid,cli->cnum);
        cli_setup_packet(cli);
        
-       CVAL(cli->outbuf,smb_vwv0) = 0xFF;
+       SCVAL(cli->outbuf,smb_vwv0,0xFF);
        SSVAL(cli->outbuf,smb_vwv2,fnum);
 
        SIVAL(cli->outbuf,smb_vwv3,offset);
-       SIVAL(cli->outbuf,smb_vwv5,IS_BITS_SET_ALL(mode, 0x0008) ? 0xFFFFFFFF : 0);
+       SIVAL(cli->outbuf,smb_vwv5,0);
        SSVAL(cli->outbuf,smb_vwv7,mode);
 
-       SSVAL(cli->outbuf,smb_vwv8,IS_BITS_SET_ALL(mode, 0x0008) ? size : 0);
+       SSVAL(cli->outbuf,smb_vwv8,(mode & 0x0008) ? size : 0);
+       /*
+        * According to CIFS-TR-1p00, this following field should only
+        * be set if CAP_LARGE_WRITEX is set. We should check this
+        * locally. However, this check might already have been
+        * done by our callers.
+        */
+       SSVAL(cli->outbuf,smb_vwv9,((size>>16)&1));
        SSVAL(cli->outbuf,smb_vwv10,size);
        SSVAL(cli->outbuf,smb_vwv11,
              smb_buf(cli->outbuf) - smb_base(cli->outbuf));
+
+       if (large_writex) {
+               SIVAL(cli->outbuf,smb_vwv12,(((SMB_BIG_UINT)offset)>>32) & 0xffffffff);
+       }
        
        p = smb_base(cli->outbuf) + SVAL(cli->outbuf,smb_vwv11);
        memcpy(p, buf, size);
+       cli_setup_bcc(cli, p+size);
 
        SSVAL(cli->outbuf,smb_mid,cli->mid + i);
        
        show_msg(cli->outbuf);
-       cli_send_smb(cli);
+       return cli_send_smb(cli);
 }
 
 /****************************************************************************
@@ -172,58 +360,60 @@ static void cli_issue_write(struct cli_state *cli, int fnum, off_t offset, uint1
               0x0004 use raw named pipe protocol
               0x0008 start of message mode named pipe protocol
 ****************************************************************************/
+
 ssize_t cli_write(struct cli_state *cli,
-                 int fnum, uint16 write_mode,
-                 char *buf, off_t offset, size_t size)
+                int fnum, uint16 write_mode,
+                const char *buf, off_t offset, size_t size)
 {
-       int bwritten = 0;
-       int issued = 0;
-       int received = 0;
-       int mpx = MAX(cli->max_mux-1, 1);
-       int block = (cli->max_xmit - (smb_size+32)) & ~1023;
+       ssize_t bwritten = 0;
+       unsigned int issued = 0;
+       unsigned int received = 0;
+       int mpx = 1;
+       int block = cli->max_xmit - (smb_size+32);
        int blocks = (size + (block-1)) / block;
 
+       if(cli->max_mux > 1) {
+               mpx = cli->max_mux-1;
+       } else {
+               mpx = 1;
+       }
+
        while (received < blocks) {
 
-               while ((issued - received < mpx) && (issued < blocks))
-               {
-                       int bsent = issued * block;
-                       int size1 = MIN(block, size - bsent);
+               while ((issued - received < mpx) && (issued < blocks)) {
+                       ssize_t bsent = issued * block;
+                       ssize_t size1 = MIN(block, size - bsent);
 
-                       cli_issue_write(cli, fnum, offset + bsent,
+                       if (!cli_issue_write(cli, fnum, offset + bsent,
                                        write_mode,
                                        buf + bsent,
-                                       size1, issued);
+                                       size1, issued))
+                               return -1;
                        issued++;
                }
 
                if (!cli_receive_smb(cli))
-               {
                        return bwritten;
-               }
 
                received++;
 
-               if (CVAL(cli->inbuf,smb_rcls) != 0)
-               {
+               if (cli_is_error(cli))
                        break;
-               }
 
                bwritten += SVAL(cli->inbuf, smb_vwv2);
+               bwritten += (((int)(SVAL(cli->inbuf, smb_vwv4)))<<16);
        }
 
        while (received < issued && cli_receive_smb(cli))
-       {
                received++;
-       }
        
        return bwritten;
 }
 
-
 /****************************************************************************
   write to a file using a SMBwrite and not bypassing 0 byte writes
 ****************************************************************************/
+
 ssize_t cli_smbwrite(struct cli_state *cli,
                     int fnum, char *buf, off_t offset, size_t size1)
 {
@@ -236,9 +426,9 @@ ssize_t cli_smbwrite(struct cli_state *cli,
                memset(cli->outbuf,'\0',smb_size);
                memset(cli->inbuf,'\0',smb_size);
 
-               set_message(cli->outbuf,5, 3 + size,True);
+               set_message(cli->outbuf,5, 0,True);
 
-               CVAL(cli->outbuf,smb_com) = SMBwrite;
+               SCVAL(cli->outbuf,smb_com,SMBwrite);
                SSVAL(cli->outbuf,smb_tid,cli->cnum);
                cli_setup_packet(cli);
                
@@ -249,25 +439,29 @@ ssize_t cli_smbwrite(struct cli_state *cli,
                
                p = smb_buf(cli->outbuf);
                *p++ = 1;
-               SSVAL(p, 0, size);
-               memcpy(p+2, buf, size);
+               SSVAL(p, 0, size); p += 2;
+               memcpy(p, buf + total, size); p += size;
+
+               cli_setup_bcc(cli, p);
                
-               cli_send_smb(cli);
-               if (!cli_receive_smb(cli)) {
+               if (!cli_send_smb(cli))
+                       return -1;
+
+               if (!cli_receive_smb(cli))
                        return -1;
-               }
                
-               if (CVAL(cli->inbuf,smb_rcls) != 0) {
+               if (cli_is_error(cli))
                        return -1;
-               }
 
                size = SVAL(cli->inbuf,smb_vwv0);
-               if (size == 0) break;
+               if (size == 0)
+                       break;
 
                size1 -= size;
                total += size;
+               offset += size;
+
        } while (size1);
 
        return total;
 }
-