r21108: Send sys_notify_watch through the VFS, FAM is next
[ira/wip.git] / source3 / smbd / close.c
index c37c2ceab2de15176e2d33ac53449c7ba3424375..714e0615de690e61be68883ee074902d2b6afd85 100644 (file)
@@ -1,8 +1,9 @@
 /* 
-   Unix SMB/Netbios implementation.
-   Version 1.9.
+   Unix SMB/CIFS implementation.
    file closing
    Copyright (C) Andrew Tridgell 1992-1998
+   Copyright (C) Jeremy Allison 1992-2004.
+   Copyright (C) Volker Lendecke 2005
    
    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
 
 #include "includes.h"
 
-extern int DEBUGLEVEL;
-
-extern int32 global_oplocks_open;
-
+extern struct current_user current_user;
 
 /****************************************************************************
-run a file if it is a magic script
+ Run a file if it is a magic script.
 ****************************************************************************/
+
 static void check_magic(files_struct *fsp,connection_struct *conn)
 {
-  if (!*lp_magicscript(SNUM(conn)))
-    return;
-
-  DEBUG(5,("checking magic for %s\n",fsp->fsp_name));
-
-  {
-    char *p;
-    if (!(p = strrchr(fsp->fsp_name,'/')))
-      p = fsp->fsp_name;
-    else
-      p++;
-
-    if (!strequal(lp_magicscript(SNUM(conn)),p))
-      return;
-  }
-
-  {
-    int ret;
-    pstring magic_output;
-    pstring fname;
-    pstrcpy(fname,fsp->fsp_name);
-
-    if (*lp_magicoutput(SNUM(conn)))
-      pstrcpy(magic_output,lp_magicoutput(SNUM(conn)));
-    else
-      slprintf(magic_output,sizeof(fname)-1, "%s.out",fname);
-
-    chmod(fname,0755);
-    ret = smbrun(fname,magic_output,False);
-    DEBUG(3,("Invoking magic command %s gave %d\n",fname,ret));
-    unlink(fname);
-  }
+       if (!*lp_magicscript(SNUM(conn)))
+               return;
+
+       DEBUG(5,("checking magic for %s\n",fsp->fsp_name));
+
+       {
+               char *p;
+               if (!(p = strrchr_m(fsp->fsp_name,'/')))
+                       p = fsp->fsp_name;
+               else
+                       p++;
+
+               if (!strequal(lp_magicscript(SNUM(conn)),p))
+                       return;
+       }
+
+       {
+               int ret;
+               pstring magic_output;
+               pstring fname;
+               SMB_STRUCT_STAT st;
+               int tmp_fd, outfd;
+
+               pstrcpy(fname,fsp->fsp_name);
+               if (*lp_magicoutput(SNUM(conn)))
+                       pstrcpy(magic_output,lp_magicoutput(SNUM(conn)));
+               else
+                       slprintf(magic_output,sizeof(fname)-1, "%s.out",fname);
+
+               chmod(fname,0755);
+               ret = smbrun(fname,&tmp_fd);
+               DEBUG(3,("Invoking magic command %s gave %d\n",fname,ret));
+               unlink(fname);
+               if (ret != 0 || tmp_fd == -1) {
+                       if (tmp_fd != -1)
+                               close(tmp_fd);
+                       return;
+               }
+               outfd = open(magic_output, O_CREAT|O_EXCL|O_RDWR, 0600);
+               if (outfd == -1) {
+                       close(tmp_fd);
+                       return;
+               }
+
+               if (sys_fstat(tmp_fd,&st) == -1) {
+                       close(tmp_fd);
+                       close(outfd);
+                       return;
+               }
+
+               transfer_file(tmp_fd,outfd,(SMB_OFF_T)st.st_size);
+               close(tmp_fd);
+               close(outfd);
+       }
 }
 
 /****************************************************************************
   Common code to close a file or a directory.
 ****************************************************************************/
-static void close_filestruct(files_struct *fsp)
+
+static int close_filestruct(files_struct *fsp)
 {   
        connection_struct *conn = fsp->conn;
+       int ret = 0;
     
-       fsp->open = False;
-       fsp->is_directory = False; 
-    
+       if (fsp->fh->fd != -1) {
+               if(flush_write_cache(fsp, CLOSE_FLUSH) == -1)
+                       ret = -1;
+
+               delete_write_cache(fsp);
+       }
+
        conn->num_files_open--;
-       if(fsp->wbmpx_ptr) {  
-               free((char *)fsp->wbmpx_ptr);
-               fsp->wbmpx_ptr = NULL; 
-       }  
-     
-#if WITH_MMAP
-       if(fsp->mmap_ptr) {
-               munmap(fsp->mmap_ptr,fsp->mmap_size);
-               fsp->mmap_ptr = NULL;
-       }  
-#endif 
+       SAFE_FREE(fsp->wbmpx_ptr);
+
+       return ret;
 }    
 
 /****************************************************************************
- Close a file - possibly invalidating the read prediction.
+ If any deferred opens are waiting on this close, notify them.
+****************************************************************************/
 
- If normal_close is 1 then this came from a normal SMBclose (or equivalent)
- operation otherwise it came as the result of some other operation such as
- the closing of the connection. In the latter case printing and
- magic scripts are not run.
+static void notify_deferred_opens(struct share_mode_lock *lck)
+{
+       int i;
+       for (i=0; i<lck->num_share_modes; i++) {
+               struct share_mode_entry *e = &lck->share_modes[i];
+               if (!is_deferred_open_entry(e)) {
+                       continue;
+               }
+               if (procid_is_me(&e->pid)) {
+                       /*
+                        * We need to notify ourself to retry the open.  Do
+                        * this by finding the queued SMB record, moving it to
+                        * the head of the queue and changing the wait time to
+                        * zero.
+                        */
+                       schedule_deferred_open_smb_message(e->op_mid);
+               } else {
+                       char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE];
+
+                       share_mode_entry_to_message(msg, e);
+
+                       message_send_pid(e->pid, MSG_SMB_OPEN_RETRY,
+                                        msg, MSG_SMB_SHARE_MODE_ENTRY_SIZE, True);
+               }
+       }
+}
+
+/****************************************************************************
+ Deal with removing a share mode on last close.
 ****************************************************************************/
-void close_file(files_struct *fsp, BOOL normal_close)
+
+static NTSTATUS close_remove_share_mode(files_struct *fsp,
+                                       enum file_close_type close_type)
 {
-       SMB_DEV_T dev = fsp->fd_ptr->dev;
-       SMB_INO_T inode = fsp->fd_ptr->inode;
-       int token;
-    BOOL last_reference = False;
-    BOOL delete_on_close = fsp->fd_ptr->delete_on_close;
        connection_struct *conn = fsp->conn;
+       BOOL delete_file = False;
+       struct share_mode_lock *lck;
+       SMB_STRUCT_STAT sbuf;
+       NTSTATUS status = NT_STATUS_OK;
 
-       remove_pending_lock_requests_by_fid(fsp);
-
-       close_filestruct(fsp);
+       /*
+        * Lock the share entries, and determine if we should delete
+        * on close. If so delete whilst the lock is still in effect.
+        * This prevents race conditions with the file being created. JRA.
+        */
 
-#if USE_READ_PREDICTION
-       invalidate_read_prediction(fsp->fd_ptr->fd);
-#endif
+       lck = get_share_mode_lock(NULL, fsp->dev, fsp->inode, NULL, NULL);
 
-       if (lp_share_modes(SNUM(conn))) {
-               lock_share_entry(conn, dev, inode, &token);
-               del_share_mode(token, fsp);
+       if (lck == NULL) {
+               DEBUG(0, ("close_remove_share_mode: Could not get share mode "
+                         "lock for file %s\n", fsp->fsp_name));
+               return NT_STATUS_INVALID_PARAMETER;
        }
 
-       if(fd_attempt_close(fsp) == 0)
-               last_reference = True;
+       if (!del_share_mode(lck, fsp)) {
+               DEBUG(0, ("close_remove_share_mode: Could not delete share "
+                         "entry for file %s\n", fsp->fsp_name));
+       }
 
-    fsp->fd_ptr = NULL;
+       if (fsp->initial_delete_on_close && (lck->delete_token == NULL)) {
+               BOOL became_user = False;
 
-       if (lp_share_modes(SNUM(conn)))
-               unlock_share_entry(conn, dev, inode, token);
+               /* Initial delete on close was set and no one else
+                * wrote a real delete on close. */
 
-       /* NT uses smbclose to start a print - weird */
-       if (normal_close && fsp->print_file)
-               print_file(conn, SNUM(conn), fsp);
+               if (current_user.vuid != fsp->vuid) {
+                       become_user(conn, fsp->vuid);
+                       became_user = True;
+               }
+               set_delete_on_close_lck(lck, True, &current_user.ut);
+               if (became_user) {
+                       unbecome_user();
+               }
+       }
 
-       /* check for magic scripts */
-       if (normal_close) {
-               check_magic(fsp,conn);
+       delete_file = lck->delete_on_close;
+
+       if (delete_file) {
+               int i;
+               /* See if others still have the file open. If this is the
+                * case, then don't delete */
+               for (i=0; i<lck->num_share_modes; i++) {
+                       if (is_valid_share_mode_entry(&lck->share_modes[i])) {
+                               delete_file = False;
+                               break;
+                       }
+               }
        }
 
+       /* Notify any deferred opens waiting on this close. */
+       notify_deferred_opens(lck);
+       reply_to_oplock_break_requests(fsp);
+
        /*
         * NT can set delete_on_close of the last open
         * reference to a file.
         */
 
-    if (normal_close && last_reference && delete_on_close) {
-        DEBUG(5,("close_file: file %s. Delete on close was set - deleting file.\n",
-           fsp->fsp_name));
-               if(fsp->conn->vfs_ops.unlink(dos_to_unix(fsp->fsp_name, False)) != 0) {
-          /*
-           * This call can potentially fail as another smbd may have
-           * had the file open with delete on close set and deleted
-           * it when its last reference to this file went away. Hence
-           * we log this but not at debug level zero.
-           */
+       if (!(close_type == NORMAL_CLOSE || close_type == SHUTDOWN_CLOSE)
+           || !delete_file
+           || (lck->delete_token == NULL)) {
+               TALLOC_FREE(lck);
+               return NT_STATUS_OK;
+       }
 
-          DEBUG(5,("close_file: file %s. Delete on close was set and unlink failed \
-with error %s\n", fsp->fsp_name, strerror(errno) ));
-        }
-    }
+       /*
+        * Ok, we have to delete the file
+        */
+
+       DEBUG(5,("close_remove_share_mode: file %s. Delete on close was set "
+                "- deleting file.\n", fsp->fsp_name));
+
+       /* Become the user who requested the delete. */
+
+       if (!push_sec_ctx()) {
+               smb_panic("close_remove_share_mode: file %s. failed to push "
+                         "sec_ctx.\n");
+       }
+
+       set_sec_ctx(lck->delete_token->uid,
+                   lck->delete_token->gid,
+                   lck->delete_token->ngroups,
+                   lck->delete_token->groups,
+                   NULL);
+
+       /* We can only delete the file if the name we have is still valid and
+          hasn't been renamed. */
+       
+       if(SMB_VFS_STAT(conn,fsp->fsp_name,&sbuf) != 0) {
+               DEBUG(5,("close_remove_share_mode: file %s. Delete on close "
+                        "was set and stat failed with error %s\n",
+                        fsp->fsp_name, strerror(errno) ));
+               /*
+                * Don't save the errno here, we ignore this error
+                */
+               goto done;
+       }
+
+       if(sbuf.st_dev != fsp->dev || sbuf.st_ino != fsp->inode) {
+               DEBUG(5,("close_remove_share_mode: file %s. Delete on close "
+                        "was set and dev and/or inode does not match\n",
+                        fsp->fsp_name ));
+               DEBUG(5,("close_remove_share_mode: file %s. stored dev = %x, "
+                        "inode = %.0f stat dev = %x, inode = %.0f\n",
+                        fsp->fsp_name,
+                        (unsigned int)fsp->dev, (double)fsp->inode,
+                        (unsigned int)sbuf.st_dev, (double)sbuf.st_ino ));
+               /*
+                * Don't save the errno here, we ignore this error
+                */
+               goto done;
+       }
+
+       if (SMB_VFS_UNLINK(conn,fsp->fsp_name) != 0) {
+               /*
+                * This call can potentially fail as another smbd may
+                * have had the file open with delete on close set and
+                * deleted it when its last reference to this file
+                * went away. Hence we log this but not at debug level
+                * zero.
+                */
+
+               DEBUG(5,("close_remove_share_mode: file %s. Delete on close "
+                        "was set and unlink failed with error %s\n",
+                        fsp->fsp_name, strerror(errno) ));
+
+               status = map_nt_error_from_unix(errno);
+               goto done;
+       }
+
+       status = NT_STATUS_FILE_DELETED;
 
-       if(fsp->granted_oplock == True)
-               global_oplocks_open--;
+ done:
+       /* unbecome user. */
+       pop_sec_ctx();
+       
+       TALLOC_FREE(lck);
+       return status;
+}
+
+/****************************************************************************
+ Close a file.
 
-       fsp->sent_oplock_break = False;
+ close_type can be NORMAL_CLOSE=0,SHUTDOWN_CLOSE,ERROR_CLOSE.
+ printing and magic scripts are only run on normal close.
+ delete on close is done on normal and shutdown close.
+****************************************************************************/
+
+static int close_normal_file(files_struct *fsp, enum file_close_type close_type)
+{
+       connection_struct *conn = fsp->conn;
+       int saved_errno = 0;
+       int err = 0;
+       int err1 = 0;
+
+       if (fsp->aio_write_behind) {
+               /*
+                * If we're finishing write behind on a close we can get a write
+                * error here, we must remember this.
+                */
+               int ret = wait_for_aio_completion(fsp);
+               if (ret) {
+                       saved_errno = ret;
+                       err1 = -1;
+               }
+       } else {
+               cancel_aio_by_fsp(fsp);
+       }
+       /*
+        * If we're flushing on a close we can get a write
+        * error here, we must remember this.
+        */
+
+       if (close_filestruct(fsp) == -1) {
+               saved_errno = errno;
+               err1 = -1;
+       }
+
+       if (fsp->print_file) {
+               print_fsp_end(fsp, close_type);
+               file_free(fsp);
+               return 0;
+       }
 
-       DEBUG(2,("%s closed file %s (numopen=%d)\n",
-                conn->user,fsp->fsp_name,
-                conn->num_files_open));
+       /* If this is an old DOS or FCB open and we have multiple opens on
+          the same handle we only have one share mode. Ensure we only remove
+          the share mode on the last close. */
 
-       if (fsp->fsp_name) {
-               string_free(&fsp->fsp_name);
+       if (fsp->fh->ref_count == 1) {
+               /* Should we return on error here... ? */
+               close_remove_share_mode(fsp, close_type);
        }
 
+       if(fsp->oplock_type) {
+               release_file_oplock(fsp);
+       }
+
+       locking_close_file(fsp);
+
+       err = fd_close(conn, fsp);
+
+       /* Only save errno if fd_close failed and we don't already
+          have an errno saved from a flush call. */
+       if ((err1 != -1) && (err == -1)) {
+               saved_errno = errno;
+       }
+
+       /* check for magic scripts */
+       if (close_type == NORMAL_CLOSE) {
+               check_magic(fsp,conn);
+       }
+
+       /*
+        * Ensure pending modtime is set after close.
+        */
+
+       if(fsp->pending_modtime && fsp->pending_modtime_owner) {
+               set_filetime(conn, fsp->fsp_name, fsp->pending_modtime);
+       } else if (fsp->last_write_time) {
+               set_filetime(conn, fsp->fsp_name, fsp->last_write_time);
+       }
+
+       DEBUG(2,("%s closed file %s (numopen=%d) %s\n",
+               conn->user,fsp->fsp_name,
+               conn->num_files_open,
+               (err == -1 || err1 == -1) ? strerror(saved_errno) : ""));
+
        file_free(fsp);
+
+       if (err == -1 || err1 == -1) {
+               errno = saved_errno;
+               return saved_errno;
+       } else {
+               return 0;
+       }
 }
 
 /****************************************************************************
  Close a directory opened by an NT SMB call. 
 ****************************************************************************/
   
-void close_directory(files_struct *fsp)
+static int close_directory(files_struct *fsp, enum file_close_type close_type)
 {
-       remove_pending_change_notify_requests_by_fid(fsp);
+       struct share_mode_lock *lck = 0;
+       BOOL delete_dir = False;
+
+       /*
+        * NT can set delete_on_close of the last open
+        * reference to a directory also.
+        */
+
+       lck = get_share_mode_lock(NULL, fsp->dev, fsp->inode, NULL, NULL);
+
+       if (lck == NULL) {
+               DEBUG(0, ("close_directory: Could not get share mode lock for %s\n", fsp->fsp_name));
+               return EINVAL;
+       }
+
+       if (!del_share_mode(lck, fsp)) {
+               DEBUG(0, ("close_directory: Could not delete share entry for %s\n", fsp->fsp_name));
+       }
+
+       if (fsp->initial_delete_on_close) {
+               BOOL became_user = False;
+
+               /* Initial delete on close was set - for
+                * directories we don't care if anyone else
+                * wrote a real delete on close. */
+
+               if (current_user.vuid != fsp->vuid) {
+                       become_user(fsp->conn, fsp->vuid);
+                       became_user = True;
+               }
+               send_stat_cache_delete_message(fsp->fsp_name);
+               set_delete_on_close_lck(lck, True, &current_user.ut);
+               if (became_user) {
+                       unbecome_user();
+               }
+       }
+
+       delete_dir = lck->delete_on_close;
+
+       if (delete_dir) {
+               int i;
+               /* See if others still have the dir open. If this is the
+                * case, then don't delete */
+               for (i=0; i<lck->num_share_modes; i++) {
+                       if (is_valid_share_mode_entry(&lck->share_modes[i])) {
+                               delete_dir = False;
+                               break;
+                       }
+               }
+       }
+
+       if ((close_type == NORMAL_CLOSE || close_type == SHUTDOWN_CLOSE) &&
+                               delete_dir &&
+                               lck->delete_token) {
+               BOOL ok;
+       
+               /* Become the user who requested the delete. */
+
+               if (!push_sec_ctx()) {
+                       smb_panic("close_directory: failed to push sec_ctx.\n");
+               }
+
+               set_sec_ctx(lck->delete_token->uid,
+                               lck->delete_token->gid,
+                               lck->delete_token->ngroups,
+                               lck->delete_token->groups,
+                               NULL);
+
+               TALLOC_FREE(lck);
+
+               ok = rmdir_internals(fsp->conn, fsp->fsp_name);
+
+               DEBUG(5,("close_directory: %s. Delete on close was set - deleting directory %s.\n",
+                       fsp->fsp_name, ok ? "succeeded" : "failed" ));
+
+               /* unbecome user. */
+               pop_sec_ctx();
+
+               /*
+                * Ensure we remove any change notify requests that would
+                * now fail as the directory has been deleted.
+                */
+
+               if(ok) {
+                       remove_pending_change_notify_requests_by_fid(fsp, NT_STATUS_DELETE_PENDING);
+               }
+       } else {
+               TALLOC_FREE(lck);
+               remove_pending_change_notify_requests_by_fid(
+                       fsp, NT_STATUS_OK);
+       }
 
        /*
         * Do the code common to files and directories.
         */
        close_filestruct(fsp);
-       
-       if (fsp->fsp_name)
-               string_free(&fsp->fsp_name);
-       
        file_free(fsp);
+       return 0;
+}
+
+/****************************************************************************
+ Close a 'stat file' opened internally.
+****************************************************************************/
+  
+static int close_stat(files_struct *fsp)
+{
+       /*
+        * Do the code common to files and directories.
+        */
+       close_filestruct(fsp);
+       file_free(fsp);
+       return 0;
 }
 
+/****************************************************************************
+ Close a files_struct.
+****************************************************************************/
+  
+int close_file(files_struct *fsp, enum file_close_type close_type)
+{
+       if(fsp->is_directory)
+               return close_directory(fsp, close_type);
+       else if (fsp->is_stat)
+               return close_stat(fsp);
+       else if (fsp->fake_file_handle != NULL)
+               return close_fake_file(fsp);
+       else
+               return close_normal_file(fsp, close_type);
+}