Add function fd_open_atomic() which uses O_CREAT|O_EXCL to return a guaranteed indica...
authorJeremy Allison <jra@samba.org>
Mon, 9 Jul 2012 23:59:49 +0000 (16:59 -0700)
committerJeremy Allison <jra@samba.org>
Tue, 10 Jul 2012 16:39:29 +0000 (09:39 -0700)
source3/smbd/open.c

index 473fd9769ca2b0f4f30eb6c949f10ab56cd0b1cd..6f1af4b4c4c6cf2d7b1c4ee24d3011acbd4289f0 100644 (file)
@@ -533,6 +533,107 @@ NTSTATUS change_dir_owner_to_parent(connection_struct *conn,
        return status;
 }
 
+/****************************************************************************
+ Open a file - returning a guaranteed ATOMIC indication of if the
+ file was created or not.
+****************************************************************************/
+
+static NTSTATUS fd_open_atomic(struct connection_struct *conn,
+                       files_struct *fsp,
+                       int flags,
+                       mode_t mode,
+                       bool *file_created)
+{
+       NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+       bool file_existed = VALID_STAT(fsp->fsp_name->st);
+
+       *file_created = false;
+
+       if (!(flags & O_CREAT)) {
+               /*
+                * We're not creating the file, just pass through.
+                */
+               return fd_open(conn, fsp, flags, mode);
+       }
+
+       if (flags & O_EXCL) {
+               /*
+                * Fail if already exists, just pass through.
+                */
+               status = fd_open(conn, fsp, flags, mode);
+               if (NT_STATUS_IS_OK(status)) {
+                       /*
+                        * Here we've opened with O_CREAT|O_EXCL
+                        * and got success. We *know* we created
+                        * this file.
+                        */
+                       *file_created = true;
+               }
+               return status;
+       }
+
+       /*
+        * Now it gets tricky. We have O_CREAT, but not O_EXCL.
+        * To know absolutely if we created the file or not,
+        * we can never call O_CREAT without O_EXCL. So if
+        * we think the file existed, try without O_CREAT|O_EXCL.
+        * If we think the file didn't exist, try with
+        * O_CREAT|O_EXCL. Keep bouncing between these two
+        * requests until either the file is created, or
+        * opened. Either way, we keep going until we get
+        * a returnable result (error, or open/create).
+        */
+
+       while(1) {
+               int curr_flags = flags;
+
+               if (file_existed) {
+                       /* Just try open, do not create. */
+                       curr_flags &= ~(O_CREAT);
+                       status = fd_open(conn, fsp, curr_flags, mode);
+                       if (NT_STATUS_EQUAL(status,
+                                       NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+                               /*
+                                * Someone deleted it in the meantime.
+                                * Retry with O_EXCL.
+                                */
+                               file_existed = false;
+                               DEBUG(10,("fd_open_atomic: file %s existed. "
+                                       "Retry.\n",
+                                       smb_fname_str_dbg(fsp->fsp_name)));
+                                       continue;
+                       }
+               } else {
+                       /* Try create exclusively, fail if it exists. */
+                       curr_flags |= O_EXCL;
+                       status = fd_open(conn, fsp, curr_flags, mode);
+                       if (NT_STATUS_EQUAL(status,
+                                       NT_STATUS_OBJECT_NAME_COLLISION)) {
+                               /*
+                                * Someone created it in the meantime.
+                                * Retry without O_CREAT.
+                                */
+                               file_existed = true;
+                               DEBUG(10,("fd_open_atomic: file %s "
+                                       "did not exist. Retry.\n",
+                                       smb_fname_str_dbg(fsp->fsp_name)));
+                               continue;
+                       }
+                       if (NT_STATUS_IS_OK(status)) {
+                               /*
+                                * Here we've opened with O_CREAT|O_EXCL
+                                * and got success. We *know* we created
+                                * this file.
+                                */
+                               *file_created = true;
+                       }
+               }
+               /* Create is done, or failed. */
+               break;
+       }
+       return status;
+}
+
 /****************************************************************************
  Open a file.
 ****************************************************************************/
@@ -544,7 +645,8 @@ static NTSTATUS open_file(files_struct *fsp,
                          int flags,
                          mode_t unx_mode,
                          uint32 access_mask, /* client requested access mask. */
-                         uint32 open_access_mask) /* what we're actually using in the open. */
+                         uint32 open_access_mask, /* what we're actually using in the open. */
+                         bool *p_file_created)
 {
        struct smb_filename *smb_fname = fsp->fsp_name;
        NTSTATUS status = NT_STATUS_OK;
@@ -670,7 +772,8 @@ static NTSTATUS open_file(files_struct *fsp,
                }
 
                /* Actually do the open */
-               status = fd_open(conn, fsp, local_flags, unx_mode);
+               status = fd_open_atomic(conn, fsp, local_flags,
+                               unx_mode, p_file_created);
                if (!NT_STATUS_IS_OK(status)) {
                        DEBUG(3,("Error opening file %s (%s) (local_flags=%d) "
                                 "(flags=%d)\n", smb_fname_str_dbg(smb_fname),
@@ -690,7 +793,7 @@ static NTSTATUS open_file(files_struct *fsp,
                        return status;
                }
 
-               if ((local_flags & O_CREAT) && !file_existed) {
+               if (*p_file_created) {
                        /* We created this file. */
 
                        bool need_re_stat = false;
@@ -2236,7 +2339,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 
        fsp_open = open_file(fsp, conn, req, parent_dir,
                             flags|flags2, unx_mode, access_mask,
-                            open_access_mask);
+                            open_access_mask, &new_file_created);
 
        if (!NT_STATUS_IS_OK(fsp_open)) {
                if (NT_STATUS_EQUAL(fsp_open, NT_STATUS_RETRY)) {