1
0
mirror of https://github.com/samba-team/samba.git synced 2025-02-26 21:57:41 +03:00

s3:pylibsmb: Add .savefile() API to SMB py bindings

This provides a simple API for writing a file's contents and makes the
s3 API consistent with the s4 API.

All the async APIs here support SMBv2 so we don't need to use the sync
APIs at all.

Note that we have the choice here of using either cli_write_send() or
cli_push_send(). I chose the latter, because that's what smbclient uses.
It also appears to handle writing a large file better (i.e. one that
exceeds the max write size of the underlying connection).

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>
This commit is contained in:
Tim Beale 2018-12-11 09:34:42 +13:00 committed by Tim Beale
parent e4d1d53597
commit 0af78faf5c
3 changed files with 97 additions and 10 deletions

View File

@ -113,7 +113,7 @@ class SMBTests(samba.tests.TestCase):
for i in range(1, 4):
contents = "I'm file {0} in dir {1}!".format(i, subdir)
path = self.make_sysvol_path(subdir, "file-{0}.txt".format(i))
self.conn.savefile(path, test_contents.encode('utf8'))
self.smb_conn.savefile(path, test_contents.encode('utf8'))
filepaths.append(path)
# sanity-check these dirs/files exist
@ -166,7 +166,7 @@ class SMBTests(samba.tests.TestCase):
"""
# create the test file
self.assertFalse(self.file_exists(test_file))
self.conn.savefile(test_file, binary_contents)
self.smb_conn.savefile(test_file, binary_contents)
self.assertTrue(self.file_exists(test_file))
# delete it and check that it's gone
@ -183,7 +183,7 @@ class SMBTests(samba.tests.TestCase):
self.assertFalse(self.smb_conn.chkpath(bad_dir))
# should return False for files (because they're not directories)
self.conn.savefile(test_file, binary_contents)
self.smb_conn.savefile(test_file, binary_contents)
self.assertFalse(self.smb_conn.chkpath(test_file))
# check correct result after creating and then deleting a new dir
@ -195,7 +195,7 @@ class SMBTests(samba.tests.TestCase):
def test_save_load_text(self):
self.conn.savefile(test_file, test_contents.encode('utf8'))
self.smb_conn.savefile(test_file, test_contents.encode('utf8'))
contents = self.conn.loadfile(test_file)
self.assertEquals(contents.decode('utf8'), test_contents,
@ -203,7 +203,7 @@ class SMBTests(samba.tests.TestCase):
# check we can overwrite the file with new contents
new_contents = 'wxyz' * 128
self.conn.savefile(test_file, new_contents.encode('utf8'))
self.smb_conn.savefile(test_file, new_contents.encode('utf8'))
contents = self.conn.loadfile(test_file)
self.assertEquals(contents.decode('utf8'), new_contents,
msg='contents of test file did not match what was written')
@ -211,7 +211,7 @@ class SMBTests(samba.tests.TestCase):
# with python2 this will save/load str type (with embedded nulls)
# with python3 this will save/load bytes type
def test_save_load_string_bytes(self):
self.conn.savefile(test_file, test_literal_bytes_embed_nulls)
self.smb_conn.savefile(test_file, test_literal_bytes_embed_nulls)
contents = self.conn.loadfile(test_file)
self.assertEquals(contents, test_literal_bytes_embed_nulls,
@ -220,7 +220,7 @@ class SMBTests(samba.tests.TestCase):
# python3 only this will save/load unicode
def test_save_load_utfcontents(self):
if PY3:
self.conn.savefile(test_file, utf_contents.encode('utf8'))
self.smb_conn.savefile(test_file, utf_contents.encode('utf8'))
contents = self.conn.loadfile(test_file)
self.assertEquals(contents.decode('utf8'), utf_contents,
@ -229,7 +229,7 @@ class SMBTests(samba.tests.TestCase):
# with python2 this will save/load str type
# with python3 this will save/load bytes type
def test_save_binary_contents(self):
self.conn.savefile(test_file, binary_contents)
self.smb_conn.savefile(test_file, binary_contents)
contents = self.conn.loadfile(test_file)
self.assertEquals(contents, binary_contents,

View File

@ -1,2 +0,0 @@
# currently savefile appends rather than overwriting
samba.tests.smb.*samba.tests.smb.SMBTests.test_save_load_text\(ad_dc:local\)

View File

@ -741,6 +741,92 @@ static PyObject *py_cli_close(struct py_cli_state *self, PyObject *args)
Py_RETURN_NONE;
}
struct push_state {
char *data;
off_t nread;
off_t total_data;
};
/*
* cli_push() helper to write a chunk of data to a remote file
*/
static size_t push_data(uint8_t *buf, size_t n, void *priv)
{
struct push_state *state = (struct push_state *)priv;
char *curr_ptr = NULL;
off_t remaining;
size_t copied_bytes;
if (state->nread >= state->total_data) {
return 0;
}
curr_ptr = state->data + state->nread;
remaining = state->total_data - state->nread;
copied_bytes = MIN(remaining, n);
memcpy(buf, curr_ptr, copied_bytes);
state->nread += copied_bytes;
return copied_bytes;
}
/*
* Writes a file with the contents specified
*/
static PyObject *py_smb_savefile(struct py_cli_state *self, PyObject *args,
PyObject *kwargs)
{
uint16_t fnum;
const char *filename = NULL;
char *data = NULL;
Py_ssize_t size = 0;
NTSTATUS status;
struct tevent_req *req = NULL;
struct push_state state;
if (!PyArg_ParseTuple(args, "s"PYARG_BYTES_LEN":savefile", &filename,
&data, &size)) {
return NULL;
}
/* create a new file handle for writing to */
req = cli_ntcreate_send(NULL, self->ev, self->cli, filename, 0,
FILE_WRITE_DATA, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ|FILE_SHARE_WRITE,
FILE_OVERWRITE_IF, FILE_NON_DIRECTORY_FILE,
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);
/* write the new file contents */
state.data = data;
state.nread = 0;
state.total_data = size;
req = cli_push_send(NULL, self->ev, self->cli, fnum, 0, 0, 0,
push_data, &state);
if (!py_tevent_req_wait_exc(self, req)) {
return NULL;
}
status = cli_push_recv(req);
TALLOC_FREE(req);
PyErr_NTSTATUS_IS_ERR_RAISE(status);
/* close the file handle */
req = cli_close_send(NULL, self->ev, self->cli, fnum);
if (!py_tevent_req_wait_exc(self, req)) {
return NULL;
}
status = cli_close_recv(req);
PyErr_NTSTATUS_IS_ERR_RAISE(status);
Py_RETURN_NONE;
}
static PyObject *py_cli_write(struct py_cli_state *self, PyObject *args,
PyObject *kwds)
{
@ -1214,6 +1300,9 @@ static PyMethodDef py_cli_state_methods[] = {
{ "chkpath", (PyCFunction)py_smb_chkpath, METH_VARARGS,
"chkpath(dir_path) -> True or False\n\n"
"\t\tReturn true if directory exists, false otherwise." },
{ "savefile", (PyCFunction)py_smb_savefile, METH_VARARGS,
"savefile(path, str) -> None\n\n"
"\t\tWrite " PY_DESC_PY3_BYTES " str to file." },
{ NULL, NULL, 0, NULL }
};