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 bddadc5..df3edd0 100644 (file)
@@ -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:
index 4438945..452c30e 100644 (file)
@@ -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 }
 };