[RFC] Advice on SMB client python bindings?

Stefan Metzmacher metze at samba.org
Mon Dec 3 18:41:34 UTC 2018


Hi Tim,
> OK, just to re-cap, would you be happy with the following?
> - I add an optional async=True|False parameter to
> libsmb_samba_internal.Conn(). Default to async=False. This changes the
> py_cli_state_init() behaviour (mostly around self->oplock_waiter).
> - Where possible/simple, we use the async libsmb APIs so they work in
> either case.
> - Where that's not so simple, we just use the synchronous API, i.e. more
> work would be needed in future if we ever wanted to support that API in
> the async=True case.
> 
> A bunch of the libsmb APIs (cli_unlink(), cli_rmdir(), cli_mkdir(),
> cli_chkpath(), cli_query_security_descriptor()) seem to do the
> SMBv1/SMBv2 check in the synchronous API rather than the asynchronous
> API. And it looks to me like trying to add SMBv2 support to the async
> API would not be trivial.
> 
> The attached patch-set (#3 & #4) is an example of what I mean.

I think we should keep the async versions.

Can you start with the attached patches?
This is is enough in order to implement my dcerpc over ncacn_np
testcases.

Thanks!
metze
-------------- next part --------------
From 657c2a1d81bdd1fb3580421e31e6b897cdca2ea2 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Mon, 3 Dec 2018 14:37:05 +0100
Subject: [PATCH 1/7] s3:pylibsmb: pass self to py_tevent_req_wait_exc()

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 source3/libsmb/pylibsmb.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/source3/libsmb/pylibsmb.c b/source3/libsmb/pylibsmb.c
index 311820c7d667..f36c6ea9da83 100644
--- a/source3/libsmb/pylibsmb.c
+++ b/source3/libsmb/pylibsmb.c
@@ -364,7 +364,7 @@ static int py_tevent_req_wait(struct tevent_context *ev,
 
 #endif
 
-static bool py_tevent_req_wait_exc(struct tevent_context *ev,
+static bool py_tevent_req_wait_exc(struct py_cli_state *self,
 				   struct tevent_req *req)
 {
 	int ret;
@@ -373,7 +373,7 @@ static bool py_tevent_req_wait_exc(struct tevent_context *ev,
 		PyErr_NoMemory();
 		return false;
 	}
-	ret = py_tevent_req_wait(ev, req);
+	ret = py_tevent_req_wait(self->ev, req);
 	if (ret != 0) {
 		TALLOC_FREE(req);
 		errno = ret;
@@ -453,7 +453,7 @@ static int py_cli_state_init(struct py_cli_state *self, PyObject *args,
 	req = cli_full_connection_creds_send(
 		NULL, self->ev, "myname", host, NULL, 0, share, "?????",
 		cli_creds, flags, SMB_SIGNING_DEFAULT);
-	if (!py_tevent_req_wait_exc(self->ev, req)) {
+	if (!py_tevent_req_wait_exc(self, req)) {
 		return -1;
 	}
 	status = cli_full_connection_creds_recv(req, &self->cli);
@@ -612,7 +612,7 @@ static PyObject *py_cli_create(struct py_cli_state *self, PyObject *args,
 				DesiredAccess, FileAttributes, ShareAccess,
 				CreateDisposition, CreateOptions,
 				SecurityFlags);
-	if (!py_tevent_req_wait_exc(self->ev, req)) {
+	if (!py_tevent_req_wait_exc(self, req)) {
 		return NULL;
 	}
 	status = cli_ntcreate_recv(req, &fnum, NULL);
@@ -636,7 +636,7 @@ static PyObject *py_cli_close(struct py_cli_state *self, PyObject *args)
 	}
 
 	req = cli_close_send(NULL, self->ev, self->cli, fnum);
-	if (!py_tevent_req_wait_exc(self->ev, req)) {
+	if (!py_tevent_req_wait_exc(self, req)) {
 		return NULL;
 	}
 	status = cli_close_recv(req);
@@ -672,7 +672,7 @@ static PyObject *py_cli_write(struct py_cli_state *self, PyObject *args,
 
 	req = cli_write_andx_send(NULL, self->ev, self->cli, fnum, mode,
 				  (uint8_t *)buf, offset, buflen);
-	if (!py_tevent_req_wait_exc(self->ev, req)) {
+	if (!py_tevent_req_wait_exc(self, req)) {
 		return NULL;
 	}
 	status = cli_write_andx_recv(req, &written);
@@ -708,7 +708,7 @@ static PyObject *py_cli_read(struct py_cli_state *self, PyObject *args,
 
 	req = cli_read_andx_send(NULL, self->ev, self->cli, fnum,
 				 offset, size);
-	if (!py_tevent_req_wait_exc(self->ev, req)) {
+	if (!py_tevent_req_wait_exc(self, req)) {
 		return NULL;
 	}
 	status = cli_read_andx_recv(req, &buflen, &buf);
@@ -740,7 +740,7 @@ static PyObject *py_cli_ftruncate(struct py_cli_state *self, PyObject *args,
 	}
 
 	req = cli_ftruncate_send(NULL, self->ev, self->cli, fnum, size);
-	if (!py_tevent_req_wait_exc(self->ev, req)) {
+	if (!py_tevent_req_wait_exc(self, req)) {
 		return NULL;
 	}
 	status = cli_ftruncate_recv(req);
@@ -771,7 +771,7 @@ static PyObject *py_cli_delete_on_close(struct py_cli_state *self,
 
 	req = cli_nt_delete_on_close_send(NULL, self->ev, self->cli, fnum,
 					  flag);
-	if (!py_tevent_req_wait_exc(self->ev, req)) {
+	if (!py_tevent_req_wait_exc(self, req)) {
 		return NULL;
 	}
 	status = cli_nt_delete_on_close_recv(req);
@@ -812,7 +812,7 @@ static PyObject *py_cli_list(struct py_cli_state *self,
 
 	req = cli_list_send(NULL, self->ev, self->cli, mask, attribute,
 			    info_level);
-	if (!py_tevent_req_wait_exc(self->ev, req)) {
+	if (!py_tevent_req_wait_exc(self, req)) {
 		return NULL;
 	}
 	status = cli_list_recv(req, NULL, &finfos, &num_finfos);
-- 
2.17.1


From d5bd6514c1b9c4d4d733407acc7fe57a0402ecbf Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Mon, 3 Dec 2018 15:02:06 +0100
Subject: [PATCH 2/7] s3:pylibsmb: only use poll_mt backend if
 multi_threaded=True is specified

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 python/samba/tests/libsmb_samba_internal.py |  3 +-
 source3/libsmb/pylibsmb.c                   | 52 ++++++++++++++++-----
 2 files changed, 43 insertions(+), 12 deletions(-)

diff --git a/python/samba/tests/libsmb_samba_internal.py b/python/samba/tests/libsmb_samba_internal.py
index c88095c8bc2f..db99c0bfb2bc 100644
--- a/python/samba/tests/libsmb_samba_internal.py
+++ b/python/samba/tests/libsmb_samba_internal.py
@@ -59,7 +59,8 @@ class LibsmbTestCase(samba.tests.TestCase):
         creds.set_username(os.getenv("USERNAME"))
         creds.set_password(os.getenv("PASSWORD"))
 
-        c = libsmb_samba_internal.Conn(os.getenv("SERVER_IP"), "tmp", creds)
+        c = libsmb_samba_internal.Conn(os.getenv("SERVER_IP"), "tmp",
+                                       creds, multi_threaded=True)
 
         mythreads = []
 
diff --git a/source3/libsmb/pylibsmb.c b/source3/libsmb/pylibsmb.c
index f36c6ea9da83..b07952c98d1b 100644
--- a/source3/libsmb/pylibsmb.c
+++ b/source3/libsmb/pylibsmb.c
@@ -81,6 +81,8 @@ struct py_cli_state {
 	PyObject_HEAD
 	struct cli_state *cli;
 	struct tevent_context *ev;
+	int (*req_wait_fn)(struct tevent_context *ev,
+			   struct tevent_req *req);
 	struct py_cli_thread *thread_state;
 
 	struct tevent_req *oplock_waiter;
@@ -194,7 +196,10 @@ static int py_cli_thread_destructor(struct py_cli_thread *t)
 	return 0;
 }
 
-static bool py_cli_state_setup_ev(struct py_cli_state *self)
+static int py_tevent_cond_req_wait(struct tevent_context *ev,
+				   struct tevent_req *req);
+
+static bool py_cli_state_setup_mt_ev(struct py_cli_state *self)
 {
 	struct py_cli_thread *t = NULL;
 	int ret;
@@ -206,6 +211,8 @@ static bool py_cli_state_setup_ev(struct py_cli_state *self)
 	samba_tevent_set_debug(self->ev, "pylibsmb_tevent_mt");
 	tevent_set_trace_callback(self->ev, py_cli_state_trace_callback, self);
 
+	self->req_wait_fn = py_tevent_cond_req_wait;
+
 	self->thread_state = talloc_zero(NULL, struct py_cli_thread);
 	if (self->thread_state == NULL) {
 		goto fail;
@@ -303,8 +310,8 @@ fail:
 	return result;
 }
 
-static int py_tevent_req_wait(struct tevent_context *ev,
-			      struct tevent_req *req)
+static int py_tevent_cond_req_wait(struct tevent_context *ev,
+				   struct tevent_req *req)
 {
 	struct py_tevent_cond cond;
 	tevent_req_set_callback(req, py_tevent_signalme, &cond);
@@ -334,7 +341,10 @@ static void py_tevent_signalme(struct tevent_req *req)
 	py_tevent_cond_signal(cond);
 }
 
-#else
+#endif
+
+static int py_tevent_req_wait(struct tevent_context *ev,
+			      struct tevent_req *req);
 
 static bool py_cli_state_setup_ev(struct py_cli_state *self)
 {
@@ -345,6 +355,8 @@ static bool py_cli_state_setup_ev(struct py_cli_state *self)
 
 	samba_tevent_set_debug(self->ev, "pylibsmb_tevent");
 
+	self->req_wait_fn = py_tevent_req_wait;
+
 	return true;
 }
 
@@ -362,8 +374,6 @@ static int py_tevent_req_wait(struct tevent_context *ev,
 	return 0;
 }
 
-#endif
-
 static bool py_tevent_req_wait_exc(struct py_cli_state *self,
 				   struct tevent_req *req)
 {
@@ -373,7 +383,7 @@ static bool py_tevent_req_wait_exc(struct py_cli_state *self,
 		PyErr_NoMemory();
 		return false;
 	}
-	ret = py_tevent_req_wait(self->ev, req);
+	ret = self->req_wait_fn(self->ev, req);
 	if (ret != 0) {
 		TALLOC_FREE(req);
 		errno = ret;
@@ -410,6 +420,8 @@ static int py_cli_state_init(struct py_cli_state *self, PyObject *args,
 	char *host, *share;
 	PyObject *creds = NULL;
 	struct cli_credentials *cli_creds;
+	PyObject *py_multi_threaded = Py_False;
+	bool multi_threaded = false;
 	struct tevent_req *req;
 	bool ret;
 	/*
@@ -421,7 +433,7 @@ static int py_cli_state_init(struct py_cli_state *self, PyObject *args,
 	int flags = CLI_FULL_CONNECTION_FORCE_SMB1;
 
 	static const char *kwlist[] = {
-		"host", "share", "credentials", NULL
+		"host", "share", "credentials", "multi_threaded", NULL
 	};
 
 	PyTypeObject *py_type_Credentials = get_pytype(
@@ -431,8 +443,10 @@ static int py_cli_state_init(struct py_cli_state *self, PyObject *args,
 	}
 
 	ret = ParseTupleAndKeywords(
-		args, kwds, "ss|O!", kwlist,
-		&host, &share, py_type_Credentials, &creds);
+		args, kwds, "ss|O!O", kwlist,
+		&host, &share,
+		py_type_Credentials, &creds,
+		&py_multi_threaded);
 
 	Py_DECREF(py_type_Credentials);
 
@@ -440,8 +454,24 @@ static int py_cli_state_init(struct py_cli_state *self, PyObject *args,
 		return -1;
 	}
 
-	if (!py_cli_state_setup_ev(self)) {
+	multi_threaded = PyObject_IsTrue(py_multi_threaded);
+
+	if (multi_threaded) {
+#ifdef HAVE_PTHREAD
+		ret = py_cli_state_setup_mt_ev(self);
+		if (!ret) {
+			return -1;
+		}
+#else
+		PyErr_SetString(PyExc_RuntimeError,
+				"No PTHREAD support available");
 		return -1;
+#endif
+	} else {
+		ret = py_cli_state_setup_ev(self);
+		if (!ret) {
+			return -1;
+		}
 	}
 
 	if (creds == NULL) {
-- 
2.17.1


From 513eeef7d6e376dc353c2e21c716720c4e64a747 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Mon, 3 Dec 2018 15:42:50 +0100
Subject: [PATCH 3/7] s3:pylibsmb: add sign=True to require signing

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 source3/libsmb/pylibsmb.c | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/source3/libsmb/pylibsmb.c b/source3/libsmb/pylibsmb.c
index b07952c98d1b..6c98c7066dad 100644
--- a/source3/libsmb/pylibsmb.c
+++ b/source3/libsmb/pylibsmb.c
@@ -422,6 +422,9 @@ static int py_cli_state_init(struct py_cli_state *self, PyObject *args,
 	struct cli_credentials *cli_creds;
 	PyObject *py_multi_threaded = Py_False;
 	bool multi_threaded = false;
+	PyObject *py_sign = Py_False;
+	bool sign = false;
+	int signing_state = SMB_SIGNING_DEFAULT;
 	struct tevent_req *req;
 	bool ret;
 	/*
@@ -433,7 +436,8 @@ static int py_cli_state_init(struct py_cli_state *self, PyObject *args,
 	int flags = CLI_FULL_CONNECTION_FORCE_SMB1;
 
 	static const char *kwlist[] = {
-		"host", "share", "credentials", "multi_threaded", NULL
+		"host", "share", "credentials",
+		"multi_threaded", "sign", NULL
 	};
 
 	PyTypeObject *py_type_Credentials = get_pytype(
@@ -443,10 +447,11 @@ static int py_cli_state_init(struct py_cli_state *self, PyObject *args,
 	}
 
 	ret = ParseTupleAndKeywords(
-		args, kwds, "ss|O!O", kwlist,
+		args, kwds, "ss|O!OO", kwlist,
 		&host, &share,
 		py_type_Credentials, &creds,
-		&py_multi_threaded);
+		&py_multi_threaded,
+		&py_sign);
 
 	Py_DECREF(py_type_Credentials);
 
@@ -455,6 +460,11 @@ static int py_cli_state_init(struct py_cli_state *self, PyObject *args,
 	}
 
 	multi_threaded = PyObject_IsTrue(py_multi_threaded);
+	sign = PyObject_IsTrue(py_sign);
+
+	if (sign) {
+		signing_state = SMB_SIGNING_REQUIRED;
+	}
 
 	if (multi_threaded) {
 #ifdef HAVE_PTHREAD
@@ -482,7 +492,7 @@ static int py_cli_state_init(struct py_cli_state *self, PyObject *args,
 
 	req = cli_full_connection_creds_send(
 		NULL, self->ev, "myname", host, NULL, 0, share, "?????",
-		cli_creds, flags, SMB_SIGNING_DEFAULT);
+		cli_creds, flags, signing_state);
 	if (!py_tevent_req_wait_exc(self, req)) {
 		return -1;
 	}
-- 
2.17.1


From b92dd8f74a0293ec7e6982f29fba44a1d05fad69 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Mon, 3 Dec 2018 15:44:43 +0100
Subject: [PATCH 4/7] python/tests: also initialize the s3 loadparm subsystem

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 python/samba/tests/__init__.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py
index 9b30d2efa1ed..6ae8fbd9c80e 100644
--- a/python/samba/tests/__init__.py
+++ b/python/samba/tests/__init__.py
@@ -24,6 +24,7 @@ import warnings
 import ldb
 import samba
 from samba import param
+from samba.samba3 import param as s3param
 from samba import credentials
 from samba.credentials import Credentials
 from samba import gensec
@@ -302,8 +303,10 @@ class TestCaseInTempDir(TestCase):
 
 def env_loadparm():
     lp = param.LoadParm()
+    lp3 = s3param.get_context()
     try:
         lp.load(os.environ["SMB_CONF_PATH"])
+        lp3.load(lp.configfile)
     except KeyError:
         raise KeyError("SMB_CONF_PATH not set")
     return lp
-- 
2.17.1


From e185f8c7f0934dfe437882e0b9a345487584fcb2 Mon Sep 17 00:00:00 2001
From: Tim Beale <timbeale at catalyst.net.nz>
Date: Mon, 3 Dec 2018 11:01:14 +1300
Subject: [PATCH 5/7] tests: Add SMB chkpath test case

chkpath was only tested incidentally (and that assertion was wrong). Add
a proper test to prove it works correctly. We can then clean-up the
incorrect assertion in the next patch.

Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
---
 python/samba/tests/smb.py | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/python/samba/tests/smb.py b/python/samba/tests/smb.py
index e0e60e371027..de2ecfc18fe8 100644
--- a/python/samba/tests/smb.py
+++ b/python/samba/tests/smb.py
@@ -65,6 +65,26 @@ class SMBTests(samba.tests.TestCase):
         self.conn.unlink(test_file)
         self.assertFalse(self.conn.chkpath(test_file))
 
+    def test_chkpath(self):
+        """Tests .chkpath determines whether or not a directory exists"""
+
+        self.assertTrue(self.conn.chkpath(test_dir))
+
+        # should return False for a non-existent directory
+        bad_dir = os.path.join(test_dir, 'dont_exist').replace('/', '\\')
+        self.assertFalse(self.conn.chkpath(bad_dir))
+
+        # should return False for files (because they're not directories)
+        self.conn.savefile(test_file, binary_contents)
+        self.assertFalse(self.conn.chkpath(test_file))
+
+        # check correct result after creating and then deleting a new dir
+        new_dir = os.path.join(test_dir, 'test-new').replace('/', '\\')
+        self.conn.mkdir(new_dir)
+        self.assertTrue(self.conn.chkpath(new_dir))
+        self.conn.rmdir(new_dir)
+        self.assertFalse(self.conn.chkpath(new_dir))
+
     def test_save_load_text(self):
 
         self.conn.savefile(test_file, test_contents.encode('utf8'))
-- 
2.17.1


From 69abda53842d37531bec22c2cea3d790d228ccb8 Mon Sep 17 00:00:00 2001
From: Tim Beale <timbeale at catalyst.net.nz>
Date: Mon, 3 Dec 2018 11:15:14 +1300
Subject: [PATCH 6/7] tests: Fix SMB unlink test case assertion

The current assertion would never detect if the unlink API is broken.
The chkpath() API is only useful for checking if directories exist, so
it will always return False for a regular file (regardless of whether
the file actually exists or not).

Rework the test case so we assert that the file exists by trying to read
its contents (which will throw an error if the file doesn't exist).

Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
---
 python/samba/tests/smb.py | 21 ++++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/python/samba/tests/smb.py b/python/samba/tests/smb.py
index de2ecfc18fe8..6ee5eaabb880 100644
--- a/python/samba/tests/smb.py
+++ b/python/samba/tests/smb.py
@@ -20,6 +20,8 @@ import os
 import random
 import sys
 from samba import smb
+from samba import NTSTATUSError
+from samba.ntstatus import NT_STATUS_OBJECT_NAME_NOT_FOUND
 
 PY3 = sys.version_info[0] == 3
 addom = 'addom.samba.example.com/'
@@ -57,13 +59,30 @@ class SMBTests(samba.tests.TestCase):
         self.assertIn('Policies', ls,
                       msg='"Policies" directory not found in sysvol')
 
+    def file_exists(self, filepath):
+        """Returns whether a regular file exists (by trying to open it)"""
+        try:
+            self.conn.loadfile(filepath)
+            exists = True;
+        except NTSTATUSError as err:
+            if err.args[0] == NT_STATUS_OBJECT_NAME_NOT_FOUND:
+                exists = False
+            else:
+                raise err
+        return exists
+
     def test_unlink(self):
         """
         The smb.unlink API should delete file
         """
+        # create the test file
+        self.assertFalse(self.file_exists(test_file))
         self.conn.savefile(test_file, binary_contents)
+        self.assertTrue(self.file_exists(test_file))
+
+        # delete it and check that it's gone
         self.conn.unlink(test_file)
-        self.assertFalse(self.conn.chkpath(test_file))
+        self.assertFalse(self.file_exists(test_file))
 
     def test_chkpath(self):
         """Tests .chkpath determines whether or not a directory exists"""
-- 
2.17.1


From 012007557234b3d71e257c64a91aac7cb9602b20 Mon Sep 17 00:00:00 2001
From: Tim Beale <timbeale at catalyst.net.nz>
Date: Mon, 3 Dec 2018 10:50:19 +1300
Subject: [PATCH 7/7] s3/pylibsmb: Add .unlink API

Add a basic .unlink() API to the source3 bindings. This is based on the
source4 python bindings, but uses the source3 client library APIs.

Update the source4 test to use the source3 API. We will gradually
convert it over, and then delete the source4 python bindings.

Pair-Programmed-With: Stefan Metzmacher <metze at samba.org>

Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 python/samba/tests/smb.py |  7 ++++++-
 source3/libsmb/pylibsmb.c | 26 ++++++++++++++++++++++++++
 2 files changed, 32 insertions(+), 1 deletion(-)

diff --git a/python/samba/tests/smb.py b/python/samba/tests/smb.py
index 6ee5eaabb880..91b487dfad13 100644
--- a/python/samba/tests/smb.py
+++ b/python/samba/tests/smb.py
@@ -22,6 +22,7 @@ import sys
 from samba import smb
 from samba import NTSTATUSError
 from samba.ntstatus import NT_STATUS_OBJECT_NAME_NOT_FOUND
+from samba.samba3 import libsmb_samba_internal
 
 PY3 = sys.version_info[0] == 3
 addom = 'addom.samba.example.com/'
@@ -43,6 +44,10 @@ class SMBTests(samba.tests.TestCase):
                             "sysvol",
                             lp=self.get_loadparm(),
                             creds=creds)
+
+        # temporarily create a 2nd SMB connection for migrating the py-bindings
+        self.smb_conn = libsmb_samba_internal.Conn(self.server, "sysvol", creds)
+
         self.conn.mkdir(test_dir)
 
     def tearDown(self):
@@ -81,7 +86,7 @@ class SMBTests(samba.tests.TestCase):
         self.assertTrue(self.file_exists(test_file))
 
         # delete it and check that it's gone
-        self.conn.unlink(test_file)
+        self.smb_conn.unlink(test_file)
         self.assertFalse(self.file_exists(test_file))
 
     def test_chkpath(self):
diff --git a/source3/libsmb/pylibsmb.c b/source3/libsmb/pylibsmb.c
index 6c98c7066dad..c86a8a8322c2 100644
--- a/source3/libsmb/pylibsmb.c
+++ b/source3/libsmb/pylibsmb.c
@@ -892,6 +892,29 @@ static PyObject *py_cli_list(struct py_cli_state *self,
 	return result;
 }
 
+static PyObject *py_smb_unlink(struct py_cli_state *self, PyObject *args)
+{
+	NTSTATUS status;
+	const char *filename = NULL;
+	struct tevent_req *req = NULL;
+
+	if (!PyArg_ParseTuple(args, "s:unlink", &filename)) {
+		return NULL;
+	}
+
+	req = cli_unlink_send(NULL, self->ev, self->cli, filename,
+			      FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+	if (!py_tevent_req_wait_exc(self, req)) {
+		return NULL;
+	}
+	status = cli_unlink_recv(req);
+	TALLOC_FREE(req);
+
+	PyErr_NTSTATUS_NOT_OK_RAISE(status);
+
+	Py_RETURN_NONE;
+}
+
 static PyMethodDef py_cli_state_methods[] = {
 	{ "create", (PyCFunction)py_cli_create, METH_VARARGS|METH_KEYWORDS,
 	  "Open a file" },
@@ -912,6 +935,9 @@ static PyMethodDef py_cli_state_methods[] = {
 	  "List a directory" },
 	{ "get_oplock_break", (PyCFunction)py_cli_get_oplock_break,
 	  METH_VARARGS, "Wait for an oplock break" },
+	{ "unlink", (PyCFunction)py_smb_unlink,
+	  METH_VARARGS,
+	  "unlink(path) -> None\n\n \t\tDelete a file." },
 	{ NULL, NULL, 0, NULL }
 };
 
-- 
2.17.1

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: OpenPGP digital signature
URL: <http://lists.samba.org/pipermail/samba-technical/attachments/20181203/fc408506/signature.sig>


More information about the samba-technical mailing list