s3:pylibsmb: Add .loadfile() API to SMB py bindings
authorTim Beale <timbeale@catalyst.net.nz>
Wed, 5 Dec 2018 02:08:09 +0000 (15:08 +1300)
committerTim Beale <timbeale@samba.org>
Mon, 7 Jan 2019 00:23:08 +0000 (01:23 +0100)
Add a .loadfile API to read a file's contents. This provides a
convenient way to read a file and is consistent with the existing
source4 API, which is used by things like the GPO python code and the
ntacls backup.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=13676

Signed-off-by: Tim Beale <timbeale@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
python/samba/tests/smb.py
source3/libsmb/pylibsmb.c

index df590ff..bddadc5 100644 (file)
@@ -150,7 +150,7 @@ class SMBTests(samba.tests.TestCase):
     def file_exists(self, filepath):
         """Returns whether a regular file exists (by trying to open it)"""
         try:
-            self.conn.loadfile(filepath)
+            self.smb_conn.loadfile(filepath)
             exists = True;
         except NTSTATUSError as err:
             if (err.args[0] == NT_STATUS_OBJECT_NAME_NOT_FOUND or
@@ -197,14 +197,14 @@ class SMBTests(samba.tests.TestCase):
 
         self.smb_conn.savefile(test_file, test_contents.encode('utf8'))
 
-        contents = self.conn.loadfile(test_file)
+        contents = self.smb_conn.loadfile(test_file)
         self.assertEquals(contents.decode('utf8'), test_contents,
                           msg='contents of test file did not match what was written')
 
         # check we can overwrite the file with new contents
         new_contents = 'wxyz' * 128
         self.smb_conn.savefile(test_file, new_contents.encode('utf8'))
-        contents = self.conn.loadfile(test_file)
+        contents = self.smb_conn.loadfile(test_file)
         self.assertEquals(contents.decode('utf8'), new_contents,
                           msg='contents of test file did not match what was written')
 
@@ -213,7 +213,7 @@ class SMBTests(samba.tests.TestCase):
     def test_save_load_string_bytes(self):
         self.smb_conn.savefile(test_file, test_literal_bytes_embed_nulls)
 
-        contents = self.conn.loadfile(test_file)
+        contents = self.smb_conn.loadfile(test_file)
         self.assertEquals(contents, test_literal_bytes_embed_nulls,
                           msg='contents of test file did not match what was written')
 
@@ -222,7 +222,7 @@ class SMBTests(samba.tests.TestCase):
         if PY3:
             self.smb_conn.savefile(test_file, utf_contents.encode('utf8'))
 
-            contents = self.conn.loadfile(test_file)
+            contents = self.smb_conn.loadfile(test_file)
             self.assertEquals(contents.decode('utf8'), utf_contents,
                               msg='contents of test file did not match what was written')
 
@@ -231,7 +231,7 @@ class SMBTests(samba.tests.TestCase):
     def test_save_binary_contents(self):
         self.smb_conn.savefile(test_file, binary_contents)
 
-        contents = self.conn.loadfile(test_file)
+        contents = self.smb_conn.loadfile(test_file)
         self.assertEquals(contents, binary_contents,
                           msg='contents of test file did not match what was written')
 
index 2a1ae29..2190b7c 100644 (file)
@@ -28,6 +28,7 @@
 #include "source4/libcli/util/pyerrors.h"
 #include "auth/credentials/pycredentials.h"
 #include "trans2.h"
+#include "libsmb/clirap.h"
 
 static PyTypeObject *get_pytype(const char *module, const char *type)
 {
@@ -863,6 +864,133 @@ static PyObject *py_cli_write(struct py_cli_state *self, PyObject *args,
        return Py_BuildValue("K", (unsigned long long)written);
 }
 
+/*
+ * Returns the size of the given file
+ */
+static NTSTATUS py_smb_filesize(struct py_cli_state *self, uint16_t fnum,
+                               off_t *size)
+{
+       NTSTATUS status;
+
+       if (self->is_smb1) {
+               uint8_t *rdata = NULL;
+               struct tevent_req *req = NULL;
+
+               req = cli_qfileinfo_send(NULL, self->ev, self->cli, fnum,
+                                        SMB_QUERY_FILE_ALL_INFO, 68,
+                                        CLI_BUFFER_SIZE);
+               if (!py_tevent_req_wait_exc(self, req)) {
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+               status = cli_qfileinfo_recv(req, NULL, NULL, &rdata, NULL);
+               if (NT_STATUS_IS_OK(status)) {
+                       *size = IVAL2_TO_SMB_BIG_UINT(rdata, 48);
+               }
+               TALLOC_FREE(req);
+               TALLOC_FREE(rdata);
+       } else {
+               status = cli_qfileinfo_basic(self->cli, fnum, NULL, size,
+                                            NULL, NULL, NULL, NULL, NULL);
+       }
+       return status;
+}
+
+static NTSTATUS pull_helper(char *buf, size_t n, void *priv)
+{
+       char **dest_buf = (char **)priv;
+       memcpy(*dest_buf, buf, n);
+       *dest_buf += n;
+       return NT_STATUS_OK;
+}
+
+/*
+ * Loads the specified file's contents and returns it
+ */
+static PyObject *py_smb_loadfile(struct py_cli_state *self, PyObject *args,
+                                PyObject *kwargs)
+{
+       NTSTATUS status;
+       const char *filename = NULL;
+       struct tevent_req *req = NULL;
+       uint16_t fnum;
+       off_t size;
+       char *buf = NULL;
+       off_t nread = 0;
+       PyObject *result = NULL;
+
+       if (!PyArg_ParseTuple(args, "s:loadfile", &filename)) {
+               return NULL;
+       }
+
+       /* get a read file handle */
+       req = cli_ntcreate_send(NULL, self->ev, self->cli, filename, 0,
+                               FILE_READ_DATA, FILE_ATTRIBUTE_NORMAL,
+                               FILE_SHARE_READ, FILE_OPEN, 0,
+                               SMB2_IMPERSONATION_IMPERSONATION, 0);
+       if (!py_tevent_req_wait_exc(self, req)) {
+               return NULL;
+       }
+       status = cli_ntcreate_recv(req, &fnum, NULL);
+       TALLOC_FREE(req);
+       PyErr_NTSTATUS_IS_ERR_RAISE(status);
+
+       /* get a buffer to hold the file contents */
+       status = py_smb_filesize(self, fnum, &size);
+       PyErr_NTSTATUS_IS_ERR_RAISE(status);
+
+       result = PyBytes_FromStringAndSize(NULL, size);
+       if (result == NULL) {
+               return NULL;
+       }
+
+       /* read the file contents */
+       buf = PyBytes_AS_STRING(result);
+       req = cli_pull_send(NULL, self->ev, self->cli, fnum, 0, size,
+                           size, pull_helper, &buf);
+       if (!py_tevent_req_wait_exc(self, req)) {
+               Py_XDECREF(result);
+               return NULL;
+       }
+       status = cli_pull_recv(req, &nread);
+       TALLOC_FREE(req);
+       if (!NT_STATUS_IS_OK(status)) {
+               Py_XDECREF(result);
+               PyErr_SetNTSTATUS(status);
+               return NULL;
+       }
+
+       /* close the file handle */
+       req = cli_close_send(NULL, self->ev, self->cli, fnum);
+       if (!py_tevent_req_wait_exc(self, req)) {
+               Py_XDECREF(result);
+               return NULL;
+       }
+       status = cli_close_recv(req);
+       TALLOC_FREE(req);
+       if (!NT_STATUS_IS_OK(status)) {
+               Py_XDECREF(result);
+               PyErr_SetNTSTATUS(status);
+               return NULL;
+       }
+
+       /* sanity-check we read the expected number of bytes */
+       if (nread > size) {
+               Py_XDECREF(result);
+               PyErr_Format(PyExc_IOError,
+                            "read invalid - got %zu requested %zu",
+                            nread, size);
+               return NULL;
+       }
+
+       if (nread < size) {
+               if (_PyBytes_Resize(&result, nread) < 0) {
+                       return NULL;
+               }
+       }
+
+       return result;
+}
+
 static PyObject *py_cli_read(struct py_cli_state *self, PyObject *args,
                             PyObject *kwds)
 {
@@ -1303,6 +1431,9 @@ static PyMethodDef py_cli_state_methods[] = {
        { "savefile", (PyCFunction)py_smb_savefile, METH_VARARGS,
          "savefile(path, str) -> None\n\n"
          "\t\tWrite " PY_DESC_PY3_BYTES " str to file." },
+       { "loadfile", (PyCFunction)py_smb_loadfile, METH_VARARGS,
+         "loadfile(path) -> file contents as a " PY_DESC_PY3_BYTES
+         "\n\n\t\tRead contents of a file." },
        { NULL, NULL, 0, NULL }
 };