summaryrefslogtreecommitdiff
path: root/source3
diff options
context:
space:
mode:
Diffstat (limited to 'source3')
-rw-r--r--source3/libsmb/clireadwrite.c178
1 files changed, 163 insertions, 15 deletions
diff --git a/source3/libsmb/clireadwrite.c b/source3/libsmb/clireadwrite.c
index c618509f01..5aee8f18bd 100644
--- a/source3/libsmb/clireadwrite.c
+++ b/source3/libsmb/clireadwrite.c
@@ -444,27 +444,175 @@ NTSTATUS cli_pull(struct cli_state *cli, uint16_t fnum,
return result;
}
-static NTSTATUS cli_read_sink(char *buf, size_t n, void *priv)
+/****************************************************************************
+Issue a single SMBread and don't wait for a reply.
+****************************************************************************/
+
+static bool cli_issue_read(struct cli_state *cli, int fnum, off_t offset,
+ size_t size, int i)
{
- char **pbuf = (char **)priv;
- memcpy(*pbuf, buf, n);
- *pbuf += n;
- return NT_STATUS_OK;
+ bool bigoffset = False;
+
+ memset(cli->outbuf,'\0',smb_size);
+ memset(cli->inbuf,'\0',smb_size);
+
+ if ((SMB_BIG_UINT)offset >> 32)
+ bigoffset = True;
+
+ cli_set_message(cli->outbuf,bigoffset ? 12 : 10,0,True);
+
+ SCVAL(cli->outbuf,smb_com,SMBreadX);
+ SSVAL(cli->outbuf,smb_tid,cli->cnum);
+ cli_setup_packet(cli);
+
+ 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);
+
+ if (bigoffset) {
+ SIVAL(cli->outbuf,smb_vwv10,(((SMB_BIG_UINT)offset)>>32) & 0xffffffff);
+ }
+
+ return cli_send_smb(cli);
}
-ssize_t cli_read(struct cli_state *cli, int fnum, char *buf,
- off_t offset, size_t size)
+/****************************************************************************
+ Read size bytes at offset offset using SMBreadX.
+****************************************************************************/
+
+ssize_t cli_read(struct cli_state *cli, int fnum, char *buf, off_t offset, size_t size)
{
- NTSTATUS status;
- ssize_t ret;
+ char *p;
+ size_t size2;
+ size_t readsize;
+ ssize_t total = 0;
+ /* We can only do direct reads if not signing or encrypting. */
+ bool direct_reads = !client_is_signing_on(cli) && !cli_encryption_on(cli);
- status = cli_pull(cli, fnum, offset, size, size,
- cli_read_sink, &buf, &ret);
- if (!NT_STATUS_IS_OK(status)) {
- cli_set_error(cli, status);
- return -1;
+ 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_encryption_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;
+ }
+
+ while (total < size) {
+ readsize = MIN(readsize, size-total);
+
+ /* Issue a read and receive a reply */
+
+ if (!cli_issue_read(cli, fnum, offset, readsize, 0))
+ return -1;
+
+ if (direct_reads) {
+ if (!cli_receive_smb_readX_header(cli))
+ return -1;
+ } else {
+ if (!cli_receive_smb(cli))
+ return -1;
+ }
+
+ /* Check for error. Make sure to check for DOS and NT
+ errors. */
+
+ if (cli_is_error(cli)) {
+ bool recoverable_error = False;
+ NTSTATUS status = NT_STATUS_OK;
+ uint8 eclass = 0;
+ uint32 ecode = 0;
+
+ 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;
+ }
+
+ /* size2 is the number of bytes the server returned.
+ * Might be zero. */
+ 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;
+ } else if (size2 < 0) {
+ DEBUG(5,("read return < 0!\n"));
+ return -1;
+ }
+
+ if (size2) {
+ /* smb_vwv6 is the offset in the packet of the returned
+ * data bytes. Only valid if size2 != 0. */
+
+ 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.
+ */
+
+ if (size2 < readsize)
+ break;
}
- return ret;
+
+ return total;
}
#if 0 /* relies on client_receive_smb(), now a static in libsmb/clientgen.c */