From 9a3e640bbaa45f2b6cd2e9a2ff514fdfa26759d0 Mon Sep 17 00:00:00 2001 From: Tim Beale Date: Wed, 12 Dec 2018 13:45:46 +1300 Subject: [PATCH] s3:pylibsmb: Add .deltree() API to SMB py bindings This basically re-uses the underlying functionality of existing APIs in order to support a .deltree() API, i.e. - we use the .list() functionality (i.e. do_listing()) to traverse every item in the given directory. - we then use either .unlink() (i.e. unlink_file()) or .rmdir() (i.e. remove_dir()) to delete the individual item. - sub-directories are handled recursively, by repeating the process. Note that the .deltree() API is currently only really used for testing (and deleting GPO files). So the recursion is never going to be excessive. BUG: https://bugzilla.samba.org/show_bug.cgi?id=13676 Signed-off-by: Tim Beale Reviewed-by: Andrew Bartlett --- python/samba/tests/smb.py | 9 ++-- source3/libsmb/pylibsmb.c | 95 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 4 deletions(-) diff --git a/python/samba/tests/smb.py b/python/samba/tests/smb.py index bddadc5d165f..df3edd040046 100644 --- a/python/samba/tests/smb.py +++ b/python/samba/tests/smb.py @@ -58,7 +58,7 @@ class SMBTests(samba.tests.TestCase): def tearDown(self): super(SMBTests, self).tearDown() try: - self.conn.deltree(test_dir) + self.smb_conn.deltree(test_dir) except: pass @@ -96,6 +96,7 @@ class SMBTests(samba.tests.TestCase): dirpaths = [] empty_dirs = [] cur_dir = test_dir + for subdir in ["subdir-X", "subdir-Y", "subdir-Z"]: path = self.make_sysvol_path(cur_dir, subdir) self.smb_conn.mkdir(path) @@ -126,18 +127,18 @@ class SMBTests(samba.tests.TestCase): # try using deltree to remove a single empty directory path = empty_dirs.pop(0) - self.conn.deltree(path) + self.smb_conn.deltree(path) self.assertFalse(self.smb_conn.chkpath(path), "Failed to delete {0}".format(path)) # try using deltree to remove a single file path = filepaths.pop(0) - self.conn.deltree(path) + self.smb_conn.deltree(path) self.assertFalse(self.file_exists(path), "Failed to delete {0}".format(path)) # delete the top-level dir - self.conn.deltree(test_dir) + self.smb_conn.deltree(test_dir) # now check that all the dirs/files are no longer there for subdir in dirpaths + empty_dirs: diff --git a/source3/libsmb/pylibsmb.c b/source3/libsmb/pylibsmb.c index 44389454d6f2..452c30e94905 100644 --- a/source3/libsmb/pylibsmb.c +++ b/source3/libsmb/pylibsmb.c @@ -1380,6 +1380,98 @@ static PyObject *py_smb_chkpath(struct py_cli_state *self, PyObject *args) return PyBool_FromLong(dir_exists); } +struct deltree_state { + struct py_cli_state *self; + const char *full_dirpath; +}; + +static NTSTATUS delete_dir_tree(struct py_cli_state *self, + const char *dirpath); + +/* + * Deletes a single item in the directory tree. This could be either a file + * or a directory. This function gets invoked as a callback for every item in + * the given directory's listings. + */ +static NTSTATUS delete_tree_callback(const char *mntpoint, + struct file_info *finfo, + const char *mask, void *priv) +{ + char *filepath = NULL; + struct deltree_state *state = priv; + NTSTATUS status; + + /* skip '.' or '..' directory listings */ + if (ISDOT(finfo->name) || ISDOTDOT(finfo->name)) { + return NT_STATUS_OK; + } + + /* get the absolute filepath */ + filepath = talloc_asprintf(NULL, "%s\\%s", state->full_dirpath, + finfo->name); + if (filepath == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (finfo->mode & FILE_ATTRIBUTE_DIRECTORY) { + + /* recursively delete the sub-directory and its contents */ + status = delete_dir_tree(state->self, filepath); + } else { + status = unlink_file(state->self, filepath); + } + + TALLOC_FREE(filepath); + return status; +} + +/* + * Removes a directory and all its contents + */ +static NTSTATUS delete_dir_tree(struct py_cli_state *self, + const char *filepath) +{ + NTSTATUS status; + const char *mask = "*"; + struct deltree_state state = { 0 }; + + /* go through the directory's contents, deleting each item */ + state.self = self; + state.full_dirpath = filepath; + status = do_listing(self, filepath, mask, LIST_ATTRIBUTE_MASK, + delete_tree_callback, &state); + + /* remove the directory itself */ + if (NT_STATUS_IS_OK(status)) { + status = remove_dir(self, filepath); + } + return status; +} + +static PyObject *py_smb_deltree(struct py_cli_state *self, PyObject *args) +{ + NTSTATUS status; + const char *filepath = NULL; + bool dir_exists; + + if (!PyArg_ParseTuple(args, "s:deltree", &filepath)) { + return NULL; + } + + /* check whether we're removing a directory or a file */ + dir_exists = check_dir_path(self, filepath); + + if (dir_exists) { + status = delete_dir_tree(self, filepath); + } else { + status = unlink_file(self, filepath); + } + + PyErr_NTSTATUS_IS_ERR_RAISE(status); + + Py_RETURN_NONE; +} + static PyMethodDef py_cli_state_methods[] = { { "settimeout", (PyCFunction)py_cli_settimeout, METH_VARARGS, "settimeout(new_timeout_msecs) => return old_timeout_msecs" }, @@ -1426,6 +1518,9 @@ static PyMethodDef py_cli_state_methods[] = { { "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." }, + { "deltree", (PyCFunction)py_smb_deltree, METH_VARARGS, + "deltree(path) -> None\n\n" + "\t\tDelete a directory and all its contents." }, { NULL, NULL, 0, NULL } }; -- 2.34.1