s3:pylibsmb: Add .deltree() API to SMB py bindings
authorTim Beale <timbeale@catalyst.net.nz>
Wed, 12 Dec 2018 00:45:46 +0000 (13:45 +1300)
committerTim Beale <timbeale@samba.org>
Mon, 7 Jan 2019 00:23:08 +0000 (01:23 +0100)
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 <timbeale@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
python/samba/tests/smb.py
source3/libsmb/pylibsmb.c

index bddadc5d165f269b3e92c5dede36cc5138411cef..df3edd0400469acd6493c0d974605aa8b467cd8e 100644 (file)
@@ -58,7 +58,7 @@ class SMBTests(samba.tests.TestCase):
     def tearDown(self):
         super(SMBTests, self).tearDown()
         try:
     def tearDown(self):
         super(SMBTests, self).tearDown()
         try:
-            self.conn.deltree(test_dir)
+            self.smb_conn.deltree(test_dir)
         except:
             pass
 
         except:
             pass
 
@@ -96,6 +96,7 @@ class SMBTests(samba.tests.TestCase):
         dirpaths = []
         empty_dirs = []
         cur_dir = test_dir
         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)
         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)
 
         # 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.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.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:
 
         # now check that all the dirs/files are no longer there
         for subdir in dirpaths + empty_dirs:
index 44389454d6f2200fa2cc3d75d0a3362c0af27d1d..452c30e949051a2172a55a70a500a7b20e56c259 100644 (file)
@@ -1380,6 +1380,98 @@ static PyObject *py_smb_chkpath(struct py_cli_state *self, PyObject *args)
        return PyBool_FromLong(dir_exists);
 }
 
        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" },
 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." },
        { "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 }
 };
 
        { NULL, NULL, 0, NULL }
 };