pvfs_rename: implement RAW_RENAME_NTTRANS as noop as w2k3
[samba.git] / source4 / ntvfs / posix / pvfs_rename.c
index 81f6ef1bf72933eed085b21b83595d4233d7bf5d..5c2a627084c6c98bdeddfc9d8e51623d1139a1a1 100644 (file)
@@ -7,7 +7,7 @@
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
+   the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #include "includes.h"
 #include "vfs_posix.h"
-#include "librpc/gen_ndr/ndr_security.h"
+#include "librpc/gen_ndr/security.h"
+#include "param/param.h"
+
+
+/*
+  do a file rename, and send any notify triggers
+*/
+NTSTATUS pvfs_do_rename(struct pvfs_state *pvfs,
+                       struct odb_lock *lck,
+                       const struct pvfs_filename *name1,
+                       const char *name2)
+{
+       const char *r1, *r2;
+       uint32_t mask;
+       NTSTATUS status;
+
+       if (rename(name1->full_name, name2) == -1) {
+               return pvfs_map_errno(pvfs, errno);
+       }
+
+       status = odb_rename(lck, name2);
+       NT_STATUS_NOT_OK_RETURN(status);
+
+       if (name1->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) {
+               mask = FILE_NOTIFY_CHANGE_DIR_NAME;
+       } else {
+               mask = FILE_NOTIFY_CHANGE_FILE_NAME;
+       }
+       /* 
+          renames to the same directory cause a OLD_NAME->NEW_NAME notify.
+          renames to a different directory are considered a remove/add 
+       */
+       r1 = strrchr_m(name1->full_name, '/');
+       r2 = strrchr_m(name2, '/');
+
+       if ((r1-name1->full_name) != (r2-name2) ||
+           strncmp(name1->full_name, name2, r1-name1->full_name) != 0) {
+               notify_trigger(pvfs->notify_context, 
+                              NOTIFY_ACTION_REMOVED, 
+                              mask,
+                              name1->full_name);
+               notify_trigger(pvfs->notify_context, 
+                              NOTIFY_ACTION_ADDED, 
+                              mask,
+                              name2);
+       } else {
+               notify_trigger(pvfs->notify_context, 
+                              NOTIFY_ACTION_OLD_NAME, 
+                              mask,
+                              name1->full_name);
+               notify_trigger(pvfs->notify_context, 
+                              NOTIFY_ACTION_NEW_NAME, 
+                              mask,
+                              name2);
+       }
+
+       /* this is a strange one. w2k3 gives an additional event for CHANGE_ATTRIBUTES
+          and CHANGE_CREATION on the new file when renaming files, but not 
+          directories */
+       if ((name1->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) == 0) {
+               notify_trigger(pvfs->notify_context, 
+                              NOTIFY_ACTION_MODIFIED, 
+                              FILE_NOTIFY_CHANGE_ATTRIBUTES|FILE_NOTIFY_CHANGE_CREATION,
+                              name2);
+       }
+       
+       return NT_STATUS_OK;
+}
+
 
 /*
   resolve a wildcard rename pattern. This works on one component of the name
 */
 static const char *pvfs_resolve_wildcard_component(TALLOC_CTX *mem_ctx, 
+                                                  struct smb_iconv_convenience *iconv_convenience,
                                                   const char *fname, 
                                                   const char *pattern)
 {
@@ -47,16 +115,16 @@ static const char *pvfs_resolve_wildcard_component(TALLOC_CTX *mem_ctx,
        while (*p2) {
                codepoint_t c1, c2;
                size_t c_size1, c_size2;
-               c1 = next_codepoint(p1, &c_size1);
-               c2 = next_codepoint(p2, &c_size2);
+               c1 = next_codepoint(iconv_convenience, p1, &c_size1);
+               c2 = next_codepoint(iconv_convenience, p2, &c_size2);
                if (c2 == '?') {
-                       d += push_codepoint(d, c1);
+                       d += push_codepoint(iconv_convenience, d, c1);
                } else if (c2 == '*') {
                        memcpy(d, p1, strlen(p1));
                        d += strlen(p1);
                        break;
                } else {
-                       d += push_codepoint(d, c2);
+                       d += push_codepoint(iconv_convenience, d, c2);
                }
 
                p1 += c_size1;
@@ -72,6 +140,7 @@ static const char *pvfs_resolve_wildcard_component(TALLOC_CTX *mem_ctx,
   resolve a wildcard rename pattern.
 */
 static const char *pvfs_resolve_wildcard(TALLOC_CTX *mem_ctx, 
+                                        struct smb_iconv_convenience *iconv_convenience,
                                         const char *fname, 
                                         const char *pattern)
 {
@@ -104,8 +173,8 @@ static const char *pvfs_resolve_wildcard(TALLOC_CTX *mem_ctx,
                return NULL;
        }
 
-       base1 = pvfs_resolve_wildcard_component(mem_ctx, base1, base2);
-       ext1 = pvfs_resolve_wildcard_component(mem_ctx, ext1, ext2);
+       base1 = pvfs_resolve_wildcard_component(mem_ctx, iconv_convenience, base1, base2);
+       ext1 = pvfs_resolve_wildcard_component(mem_ctx, iconv_convenience, ext1, ext2);
        if (base1 == NULL || ext1 == NULL) {
                return NULL;
        }
@@ -117,6 +186,84 @@ static const char *pvfs_resolve_wildcard(TALLOC_CTX *mem_ctx,
        return talloc_asprintf(mem_ctx, "%s.%s", base1, ext1);
 }
 
+/*
+  retry an rename after a sharing violation
+*/
+static void pvfs_retry_rename(struct pvfs_odb_retry *r,
+                             struct ntvfs_module_context *ntvfs,
+                             struct ntvfs_request *req,
+                             void *_io,
+                             void *private_data,
+                             enum pvfs_wait_notice reason)
+{
+       union smb_rename *io = talloc_get_type(_io, union smb_rename);
+       NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
+
+       talloc_free(r);
+
+       switch (reason) {
+       case PVFS_WAIT_CANCEL:
+/*TODO*/
+               status = NT_STATUS_CANCELLED;
+               break;
+       case PVFS_WAIT_TIMEOUT:
+               /* if it timed out, then give the failure
+                  immediately */
+/*TODO*/
+               status = NT_STATUS_SHARING_VIOLATION;
+               break;
+       case PVFS_WAIT_EVENT:
+
+               /* try the open again, which could trigger another retry setup
+                  if it wants to, so we have to unmark the async flag so we
+                  will know if it does a second async reply */
+               req->async_states->state &= ~NTVFS_ASYNC_STATE_ASYNC;
+
+               status = pvfs_rename(ntvfs, req, io);
+               if (req->async_states->state & NTVFS_ASYNC_STATE_ASYNC) {
+                       /* the 2nd try also replied async, so we don't send
+                          the reply yet */
+                       return;
+               }
+
+               /* re-mark it async, just in case someone up the chain does
+                  paranoid checking */
+               req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC;
+               break;
+       }
+
+       /* send the reply up the chain */
+       req->async_states->status = status;
+       req->async_states->send_fn(req);
+}
+
+/*
+  setup for a rename retry after a sharing violation
+  or a non granted oplock
+*/
+static NTSTATUS pvfs_rename_setup_retry(struct ntvfs_module_context *ntvfs,
+                                       struct ntvfs_request *req,
+                                       union smb_rename *io,
+                                       struct odb_lock *lck,
+                                       NTSTATUS status)
+{
+       struct pvfs_state *pvfs = ntvfs->private_data;
+       struct timeval end_time;
+
+       if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+               end_time = timeval_add(&req->statistics.request_time,
+                                      0, pvfs->sharing_violation_delay);
+       } else if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+               end_time = timeval_add(&req->statistics.request_time,
+                                      pvfs->oplock_break_timeout, 0);
+       } else {
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       return pvfs_odb_retry_setup(ntvfs, req, lck, end_time, io, NULL,
+                                   pvfs_retry_rename);
+}
+
 /*
   rename one file from a wildcard set
 */
@@ -129,11 +276,11 @@ static NTSTATUS pvfs_rename_one(struct pvfs_state *pvfs,
 {
        struct pvfs_filename *name1, *name2;
        TALLOC_CTX *mem_ctx = talloc_new(req);
+       struct odb_lock *lck = NULL;
        NTSTATUS status;
-       struct odb_lock *lck, *lck2;
 
        /* resolve the wildcard pattern for this name */
-       fname2 = pvfs_resolve_wildcard(mem_ctx, fname1, fname2);
+       fname2 = pvfs_resolve_wildcard(mem_ctx, lp_iconv_convenience(pvfs->ntvfs->ctx->lp_ctx), fname1, fname2);
        if (fname2 == NULL) {
                return NT_STATUS_NO_MEMORY;
        }
@@ -153,6 +300,7 @@ static NTSTATUS pvfs_rename_one(struct pvfs_state *pvfs,
 
        status = pvfs_can_rename(pvfs, req, name1, &lck);
        if (!NT_STATUS_IS_OK(status)) {
+               talloc_free(lck);
                goto failed;
        }
 
@@ -160,7 +308,7 @@ static NTSTATUS pvfs_rename_one(struct pvfs_state *pvfs,
        status = pvfs_resolve_partial(pvfs, mem_ctx, 
                                      dir_path, fname2, &name2);
        if (NT_STATUS_IS_OK(status)) {
-               status = pvfs_can_delete(pvfs, req, name2, &lck2);
+               status = pvfs_can_delete(pvfs, req, name2, NULL);
                if (!NT_STATUS_IS_OK(status)) {
                        goto failed;
                }
@@ -173,12 +321,7 @@ static NTSTATUS pvfs_rename_one(struct pvfs_state *pvfs,
                return NT_STATUS_NO_MEMORY;
        }
 
-       if (rename(name1->full_name, fname2) == -1) {
-               talloc_free(mem_ctx);
-               return pvfs_map_errno(pvfs, errno);
-       }
-
-       status = odb_rename(lck, fname2);
+       status = pvfs_do_rename(pvfs, lck, name1, fname2);
 
 failed:
        talloc_free(mem_ctx);
@@ -197,7 +340,7 @@ static NTSTATUS pvfs_rename_wildcard(struct pvfs_state *pvfs,
 {
        struct pvfs_dir *dir;
        NTSTATUS status;
-       uint_t ofs = 0;
+       off_t ofs = 0;
        const char *fname, *fname2, *dir_path;
        uint16_t attrib = ren->rename.in.attrib;
        int total_renamed = 0;
@@ -249,7 +392,7 @@ static NTSTATUS pvfs_rename_mv(struct ntvfs_module_context *ntvfs,
        struct pvfs_state *pvfs = ntvfs->private_data;
        NTSTATUS status;
        struct pvfs_filename *name1, *name2;
-       struct odb_lock *lck;
+       struct odb_lock *lck = NULL;
 
        /* resolve the cifs name to a posix name */
        status = pvfs_resolve_name(pvfs, req, ren->rename.in.pattern1, 
@@ -291,15 +434,26 @@ static NTSTATUS pvfs_rename_mv(struct ntvfs_module_context *ntvfs,
        }
 
        status = pvfs_can_rename(pvfs, req, name1, &lck);
+       /*
+        * on a sharing violation we need to retry when the file is closed by
+        * the other user, or after 1 second
+        * on a non granted oplock we need to retry when the file is closed by
+        * the other user, or after 30 seconds
+        */
+       if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) ||
+            NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) &&
+           (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
+               return pvfs_rename_setup_retry(pvfs->ntvfs, req, ren, lck, status);
+       }
+
        if (!NT_STATUS_IS_OK(status)) {
                return status;
        }
 
-       if (rename(name1->full_name, name2->full_name) == -1) {
-               return pvfs_map_errno(pvfs, errno);
+       status = pvfs_do_rename(pvfs, lck, name1, name2->full_name);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
        }
-
-       status = odb_rename(lck, name2->full_name);
        
        return NT_STATUS_OK;
 }
@@ -314,7 +468,7 @@ static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs,
        struct pvfs_state *pvfs = ntvfs->private_data;
        NTSTATUS status;
        struct pvfs_filename *name1, *name2;
-       struct odb_lock *lck;
+       struct odb_lock *lck = NULL;
 
        switch (ren->ntrename.in.flags) {
        case RENAME_FLAG_RENAME:
@@ -361,6 +515,17 @@ static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs,
        }
 
        status = pvfs_can_rename(pvfs, req, name1, &lck);
+       /*
+        * on a sharing violation we need to retry when the file is closed by
+        * the other user, or after 1 second
+        * on a non granted oplock we need to retry when the file is closed by
+        * the other user, or after 30 seconds
+        */
+       if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) ||
+            NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) &&
+           (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
+               return pvfs_rename_setup_retry(pvfs->ntvfs, req, ren, lck, status);
+       }
        if (!NT_STATUS_IS_OK(status)) {
                return status;
        }
@@ -368,19 +533,14 @@ static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs,
        switch (ren->ntrename.in.flags) {
        case RENAME_FLAG_RENAME:
                status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
-               if (!NT_STATUS_IS_OK(status)) {
-                       return status;
-               }
-               if (rename(name1->full_name, name2->full_name) == -1) {
-                       return pvfs_map_errno(pvfs, errno);
-               }
+               NT_STATUS_NOT_OK_RETURN(status);
+               status = pvfs_do_rename(pvfs, lck, name1, name2->full_name);
+               NT_STATUS_NOT_OK_RETURN(status);
                break;
 
        case RENAME_FLAG_HARD_LINK:
                status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
-               if (!NT_STATUS_IS_OK(status)) {
-                       return status;
-               }
+               NT_STATUS_NOT_OK_RETURN(status);
                if (link(name1->full_name, name2->full_name) == -1) {
                        return pvfs_map_errno(pvfs, errno);
                }
@@ -388,9 +548,7 @@ static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs,
 
        case RENAME_FLAG_COPY:
                status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
-               if (!NT_STATUS_IS_OK(status)) {
-                       return status;
-               }
+               NT_STATUS_NOT_OK_RETURN(status);
                return pvfs_copy_file(pvfs, name1, name2);
 
        case RENAME_FLAG_MOVE_CLUSTER_INFORMATION:
@@ -410,6 +568,9 @@ static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs,
 NTSTATUS pvfs_rename(struct ntvfs_module_context *ntvfs,
                     struct ntvfs_request *req, union smb_rename *ren)
 {
+       struct pvfs_state *pvfs = ntvfs->private_data;
+       struct pvfs_file *f;
+
        switch (ren->generic.level) {
        case RAW_RENAME_RENAME:
                return pvfs_rename_mv(ntvfs, req, ren);
@@ -417,6 +578,15 @@ NTSTATUS pvfs_rename(struct ntvfs_module_context *ntvfs,
        case RAW_RENAME_NTRENAME:
                return pvfs_rename_nt(ntvfs, req, ren);
 
+       case RAW_RENAME_NTTRANS:
+               f = pvfs_find_fd(pvfs, req, ren->nttrans.in.file.ntvfs);
+               if (!f) {
+                       return NT_STATUS_INVALID_HANDLE;
+               }
+
+               /* wk23 ignores the request */
+               return NT_STATUS_OK;
+
        default:
                break;
        }