s4-streams: fixed handling of stream rename and overwrite
[ira/wip.git] / source4 / ntvfs / posix / pvfs_rename.c
index aae17f327c47a45e3b8ba65f26b433e04279f945..d963357cbabd2ad4d0cd18537d6ae153c2746689 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/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)
 {
@@ -35,7 +103,7 @@ static const char *pvfs_resolve_wildcard_component(TALLOC_CTX *mem_ctx,
        char *dest, *d;
 
        /* the length is bounded by the length of the two strings combined */
-       dest = talloc(mem_ctx, strlen(fname) + strlen(pattern) + 1);
+       dest = talloc_array(mem_ctx, char, strlen(fname) + strlen(pattern) + 1);
        if (dest == NULL) {
                return NULL;
        }
@@ -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_convenience(iconv_convenience, p1, &c_size1);
+               c2 = next_codepoint_convenience(iconv_convenience, p2, &c_size2);
                if (c2 == '?') {
-                       d += push_codepoint(d, c1);
+                       d += push_codepoint_convenience(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_convenience(iconv_convenience, d, c2);
                }
 
                p1 += c_size1;
@@ -65,6 +133,8 @@ static const char *pvfs_resolve_wildcard_component(TALLOC_CTX *mem_ctx,
 
        *d = 0;
 
+       talloc_set_name_const(dest, dest);
+
        return dest;
 }
 
@@ -72,6 +142,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 +175,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,71 +188,151 @@ 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 = talloc_get_type(ntvfs->private_data,
+                                 struct pvfs_state);
+       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
 */
 static NTSTATUS pvfs_rename_one(struct pvfs_state *pvfs, 
-                               struct smbsrv_request *req, 
+                               struct ntvfs_request *req, 
                                const char *dir_path,
                                const char *fname1,
                                const char *fname2,
                                uint16_t attrib)
 {
        struct pvfs_filename *name1, *name2;
-       TALLOC_CTX *mem_ctx = talloc(req, 0);
+       TALLOC_CTX *mem_ctx = talloc_new(req);
+       struct odb_lock *lck = NULL;
        NTSTATUS status;
 
        /* 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;
        }
 
        /* get a pvfs_filename source object */
        status = pvfs_resolve_partial(pvfs, mem_ctx, 
-                                     dir_path, fname1, &name1);
+                                     dir_path, fname1,
+                                     PVFS_RESOLVE_NO_OPENDB,
+                                     &name1);
        if (!NT_STATUS_IS_OK(status)) {
-               talloc_free(mem_ctx);
-               return status;
+               goto failed;
        }
 
        /* make sure its matches the given attributes */
        status = pvfs_match_attrib(pvfs, name1, attrib, 0);
        if (!NT_STATUS_IS_OK(status)) {
-               talloc_free(mem_ctx);
-               return status;
+               goto failed;
        }
 
-       status = pvfs_can_rename(pvfs, name1);
+       status = pvfs_can_rename(pvfs, req, name1, &lck);
        if (!NT_STATUS_IS_OK(status)) {
-               talloc_free(mem_ctx);
-               return status;
+               talloc_free(lck);
+               goto failed;
        }
 
        /* get a pvfs_filename dest object */
        status = pvfs_resolve_partial(pvfs, mem_ctx, 
-                                     dir_path, fname2, &name2);
+                                     dir_path, fname2,
+                                     PVFS_RESOLVE_NO_OPENDB,
+                                     &name2);
        if (NT_STATUS_IS_OK(status)) {
-               status = pvfs_can_delete(pvfs, name2);
+               status = pvfs_can_delete(pvfs, req, name2, NULL);
                if (!NT_STATUS_IS_OK(status)) {
-                       talloc_free(mem_ctx);
-                       return status;
+                       goto failed;
                }
        }
 
+       status = NT_STATUS_OK;
+
        fname2 = talloc_asprintf(mem_ctx, "%s/%s", dir_path, fname2);
        if (fname2 == NULL) {
                return NT_STATUS_NO_MEMORY;
        }
 
-       if (rename(name1->full_name, fname2) == -1) {
-               talloc_free(mem_ctx);
-               return pvfs_map_errno(pvfs, errno);
-       }
+       status = pvfs_do_rename(pvfs, lck, name1, fname2);
 
+failed:
        talloc_free(mem_ctx);
-
-       return NT_STATUS_OK;
+       return status;
 }
 
 
@@ -189,14 +340,14 @@ static NTSTATUS pvfs_rename_one(struct pvfs_state *pvfs,
   rename a set of files with wildcards
 */
 static NTSTATUS pvfs_rename_wildcard(struct pvfs_state *pvfs, 
-                                    struct smbsrv_request *req, 
+                                    struct ntvfs_request *req, 
                                     union smb_rename *ren, 
                                     struct pvfs_filename *name1, 
                                     struct pvfs_filename *name2)
 {
        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;
@@ -243,19 +394,23 @@ static NTSTATUS pvfs_rename_wildcard(struct pvfs_state *pvfs,
   rename a set of files - SMBmv interface
 */
 static NTSTATUS pvfs_rename_mv(struct ntvfs_module_context *ntvfs,
-                              struct smbsrv_request *req, union smb_rename *ren)
+                              struct ntvfs_request *req, union smb_rename *ren)
 {
-       struct pvfs_state *pvfs = ntvfs->private_data;
+       struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+                                 struct pvfs_state);
        NTSTATUS status;
        struct pvfs_filename *name1, *name2;
+       struct odb_lock *lck = NULL;
 
        /* resolve the cifs name to a posix name */
-       status = pvfs_resolve_name(pvfs, req, ren->rename.in.pattern1, 0, &name1);
+       status = pvfs_resolve_name(pvfs, req, ren->rename.in.pattern1, 
+                                  PVFS_RESOLVE_WILDCARD, &name1);
        if (!NT_STATUS_IS_OK(status)) {
                return status;
        }
 
-       status = pvfs_resolve_name(pvfs, req, ren->rename.in.pattern2, 0, &name2);
+       status = pvfs_resolve_name(pvfs, req, ren->rename.in.pattern2, 
+                                  PVFS_RESOLVE_WILDCARD, &name2);
        if (!NT_STATUS_IS_OK(status)) {
                return status;
        }
@@ -281,28 +436,103 @@ static NTSTATUS pvfs_rename_mv(struct ntvfs_module_context *ntvfs,
                return status;
        }
 
-       status = pvfs_can_rename(pvfs, name1);
+       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);
+       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;
+       }
+
+       status = pvfs_do_rename(pvfs, lck, name1, name2->full_name);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
        }
        
        return NT_STATUS_OK;
 }
 
 
+/*
+  rename a stream
+*/
+static NTSTATUS pvfs_rename_stream(struct ntvfs_module_context *ntvfs,
+                                  struct ntvfs_request *req, union smb_rename *ren,
+                                  struct pvfs_filename *name1)
+{
+       struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+                                 struct pvfs_state);
+       NTSTATUS status;
+       struct odb_lock *lck = NULL;
+
+       if (name1->has_wildcard) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       if (ren->ntrename.in.new_name[0] != ':') {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       if (!name1->exists) {
+               return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+       }
+
+       if (ren->ntrename.in.flags != RENAME_FLAG_RENAME) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       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;
+       }
+
+       status = pvfs_access_check_simple(pvfs, req, name1, SEC_FILE_WRITE_ATTRIBUTE);
+       NT_STATUS_NOT_OK_RETURN(status);
+
+       status = pvfs_stream_rename(pvfs, name1, -1, 
+                                   ren->ntrename.in.new_name+1, 
+                                   true);
+       NT_STATUS_NOT_OK_RETURN(status);
+       
+       return NT_STATUS_OK;
+}
+
 /*
   rename a set of files - ntrename interface
 */
 static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs,
-                              struct smbsrv_request *req, union smb_rename *ren)
+                              struct ntvfs_request *req, union smb_rename *ren)
 {
-       struct pvfs_state *pvfs = ntvfs->private_data;
+       struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+                                 struct pvfs_state);
        NTSTATUS status;
        struct pvfs_filename *name1, *name2;
+       struct odb_lock *lck = NULL;
 
        switch (ren->ntrename.in.flags) {
        case RENAME_FLAG_RENAME:
@@ -315,12 +545,19 @@ static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs,
        }
 
        /* resolve the cifs name to a posix name */
-       status = pvfs_resolve_name(pvfs, req, ren->ntrename.in.old_name, 0, &name1);
+       status = pvfs_resolve_name(pvfs, req, ren->ntrename.in.old_name, 
+                                  PVFS_RESOLVE_WILDCARD | PVFS_RESOLVE_STREAMS, &name1);
        if (!NT_STATUS_IS_OK(status)) {
                return status;
        }
 
-       status = pvfs_resolve_name(pvfs, req, ren->ntrename.in.new_name, 0, &name2);
+       if (name1->stream_name) {
+               /* stream renames need to be handled separately */
+               return pvfs_rename_stream(ntvfs, req, ren, name1);
+       }
+
+       status = pvfs_resolve_name(pvfs, req, ren->ntrename.in.new_name, 
+                                  PVFS_RESOLVE_WILDCARD, &name2);
        if (!NT_STATUS_IS_OK(status)) {
                return status;
        }
@@ -346,25 +583,41 @@ static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs,
                return status;
        }
 
-       status = pvfs_can_rename(pvfs, name1);
+       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;
        }
 
        switch (ren->ntrename.in.flags) {
        case RENAME_FLAG_RENAME:
-               if (rename(name1->full_name, name2->full_name) == -1) {
-                       return pvfs_map_errno(pvfs, errno);
-               }
+               status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
+               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);
+               NT_STATUS_NOT_OK_RETURN(status);
                if (link(name1->full_name, name2->full_name) == -1) {
                        return pvfs_map_errno(pvfs, errno);
                }
                break;
 
        case RENAME_FLAG_COPY:
+               status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
+               NT_STATUS_NOT_OK_RETURN(status);
                return pvfs_copy_file(pvfs, name1, name2);
 
        case RENAME_FLAG_MOVE_CLUSTER_INFORMATION:
@@ -382,8 +635,12 @@ static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs,
   rename a set of files - ntrename interface
 */
 NTSTATUS pvfs_rename(struct ntvfs_module_context *ntvfs,
-                    struct smbsrv_request *req, union smb_rename *ren)
+                    struct ntvfs_request *req, union smb_rename *ren)
 {
+       struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+                                 struct pvfs_state);
+       struct pvfs_file *f;
+
        switch (ren->generic.level) {
        case RAW_RENAME_RENAME:
                return pvfs_rename_mv(ntvfs, req, ren);
@@ -391,6 +648,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;
        }