Keep track of the sparse status of an open file handle. Allows bypass of
[samba.git] / source3 / smbd / fileio.c
index 64cea7f7ced116b87b4f1ccac2361484429f8643..da40013bc5b0242623bddda56b7bb56b48f85b9d 100644 (file)
@@ -20,6 +20,8 @@
 */
 
 #include "includes.h"
+#include "printing.h"
+#include "smbd/globals.h"
 
 static bool setup_write_cache(files_struct *, SMB_OFF_T);
 
@@ -56,6 +58,7 @@ ssize_t read_file(files_struct *fsp,char *data,SMB_OFF_T pos,size_t n)
 
        /* you can't read from print files */
        if (fsp->print_file) {
+               errno = EBADF;
                return -1;
        }
 
@@ -101,7 +104,7 @@ tryagain:
        }
 
        DEBUG(10,("read_file (%s): pos = %.0f, size = %lu, returned %lu\n",
-               fsp->fsp_name, (double)pos, (unsigned long)n, (long)ret ));
+                 fsp_str_dbg(fsp), (double)pos, (unsigned long)n, (long)ret));
 
        fsp->fh->pos += ret;
        fsp->fh->position_information = fsp->fh->pos;
@@ -109,9 +112,6 @@ tryagain:
        return(ret);
 }
 
-/* how many write cache buffers have been allocated */
-static unsigned int allocated_write_caches;
-
 /****************************************************************************
  *Really* write to a file.
 ****************************************************************************/
@@ -128,7 +128,8 @@ static ssize_t real_write_file(struct smb_request *req,
                 ret = vfs_write_data(req, fsp, data, n);
         } else {
                fsp->fh->pos = pos;
-               if (pos && lp_strict_allocate(SNUM(fsp->conn))) {
+               if (pos && lp_strict_allocate(SNUM(fsp->conn) &&
+                               !fsp->is_sparse)) {
                        if (vfs_fill_sparse(fsp, pos) == -1) {
                                return -1;
                        }
@@ -137,7 +138,7 @@ static ssize_t real_write_file(struct smb_request *req,
        }
 
        DEBUG(10,("real_write_file (%s): pos = %.0f, size = %lu, returned %ld\n",
-               fsp->fsp_name, (double)pos, (unsigned long)n, (long)ret ));
+                 fsp_str_dbg(fsp), (double)pos, (unsigned long)n, (long)ret));
 
        if (ret != -1) {
                fsp->fh->pos += ret;
@@ -165,36 +166,65 @@ static int wcp_file_size_change(files_struct *fsp)
        wcp->file_size = wcp->offset + wcp->data_size;
        ret = SMB_VFS_FTRUNCATE(fsp, wcp->file_size);
        if (ret == -1) {
-               DEBUG(0,("wcp_file_size_change (%s): ftruncate of size %.0f error %s\n",
-                       fsp->fsp_name, (double)wcp->file_size, strerror(errno) ));
+               DEBUG(0,("wcp_file_size_change (%s): ftruncate of size %.0f "
+                        "error %s\n", fsp_str_dbg(fsp),
+                        (double)wcp->file_size, strerror(errno)));
        }
        return ret;
 }
 
-static void update_write_time_handler(struct event_context *ctx,
+void update_write_time_handler(struct event_context *ctx,
                                      struct timed_event *te,
-                                     const struct timeval *now,
+                                     struct timeval now,
                                      void *private_data)
 {
-       files_struct *fsp = (files_struct *)private_data;
+       files_struct *fsp = (files_struct *)private_data;
+
+       DEBUG(5, ("Update write time on %s\n", fsp_str_dbg(fsp)));
 
-       /* Remove the timed event handler. */
-       TALLOC_FREE(fsp->update_write_time_event);
-       DEBUG(5, ("Update write time on %s\n", fsp->fsp_name));
+       /* change the write time in the open file db. */
+       (void)set_write_time(fsp->file_id, timespec_current());
 
-       /* change the write time if not already changed by someoneelse */
-       set_write_time_fsp(fsp, timespec_current(), false);
+       /* And notify. */
+        notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED,
+                     FILE_NOTIFY_CHANGE_LAST_WRITE, fsp->fsp_name->base_name);
+
+       /* Remove the timed event handler. */
+       TALLOC_FREE(fsp->update_write_time_event);
 }
 
+/*********************************************************
+ Schedule a write time update for WRITE_TIME_UPDATE_USEC_DELAY
+ in the future.
+*********************************************************/
+
 void trigger_write_time_update(struct files_struct *fsp)
 {
        int delay;
 
+       if (fsp->posix_open) {
+               /* Don't use delayed writes on POSIX files. */
+               return;
+       }
+
        if (fsp->write_time_forced) {
+               /* No point - "sticky" write times
+                * in effect.
+                */
                return;
        }
 
+       /* We need to remember someone did a write
+        * and update to current time on close. */
+
+       fsp->update_write_time_on_close = true;
+
        if (fsp->update_write_time_triggered) {
+               /*
+                * We only update the write time after 2 seconds
+                * on the first normal write. After that
+                * no other writes affect this until close.
+                */
                return;
        }
        fsp->update_write_time_triggered = true;
@@ -203,15 +233,51 @@ void trigger_write_time_update(struct files_struct *fsp)
                            "smbd", "writetimeupdatedelay",
                            WRITE_TIME_UPDATE_USEC_DELAY);
 
+       DEBUG(5, ("Update write time %d usec later on %s\n",
+                 delay, fsp_str_dbg(fsp)));
+
        /* trigger the update 2 seconds later */
-       fsp->update_write_time_on_close = true;
        fsp->update_write_time_event =
                event_add_timed(smbd_event_context(), NULL,
                                timeval_current_ofs(0, delay),
-                               "update_write_time_handler",
                                update_write_time_handler, fsp);
 }
 
+void trigger_write_time_update_immediate(struct files_struct *fsp)
+{
+       struct smb_file_time ft;
+
+       if (fsp->posix_open) {
+               /* Don't use delayed writes on POSIX files. */
+               return;
+       }
+
+        if (fsp->write_time_forced) {
+               /*
+                * No point - "sticky" write times
+                * in effect.
+                */
+                return;
+        }
+
+       TALLOC_FREE(fsp->update_write_time_event);
+       DEBUG(5, ("Update write time immediate on %s\n",
+                 fsp_str_dbg(fsp)));
+
+       /* After an immediate update, reset the trigger. */
+       fsp->update_write_time_triggered = true;
+        fsp->update_write_time_on_close = false;
+
+       ZERO_STRUCT(ft);
+       ft.mtime = timespec_current();
+
+       /* Update the time in the open file db. */
+       (void)set_write_time(fsp->file_id, ft.mtime);
+
+       /* Now set on disk - takes care of notify. */
+       (void)smb_set_file_time(fsp->conn, fsp, fsp->fsp_name, &ft, false);
+}
+
 /****************************************************************************
  Write to a file.
 ****************************************************************************/
@@ -227,17 +293,15 @@ ssize_t write_file(struct smb_request *req,
        int write_path = -1;
 
        if (fsp->print_file) {
-               fstring sharename;
-               uint32 jobid;
+               uint32_t t;
+               int ret;
 
-               if (!rap_to_pjobid(fsp->rap_print_jobid, sharename, &jobid)) {
-                       DEBUG(3,("write_file: Unable to map RAP jobid %u to jobid.\n",
-                                               (unsigned int)fsp->rap_print_jobid ));
-                       errno = EBADF;
+               ret = print_spool_write(fsp, data, n, pos, &t);
+               if (ret) {
+                       errno = ret;
                        return -1;
                }
-
-               return print_job_write(SNUM(fsp->conn), jobid, data, pos, n);
+               return t;
        }
 
        if (!fsp->can_write) {
@@ -246,20 +310,18 @@ ssize_t write_file(struct smb_request *req,
        }
 
        if (!fsp->modified) {
-               SMB_STRUCT_STAT st;
                fsp->modified = True;
 
-               if (SMB_VFS_FSTAT(fsp, &st) == 0) {
-                       int dosmode;
+               if (SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st) == 0) {
                        trigger_write_time_update(fsp);
-                       dosmode = dos_mode(fsp->conn,fsp->fsp_name,&st);
-                       if ((lp_store_dos_attributes(SNUM(fsp->conn)) ||
-                                       MAP_ARCHIVE(fsp->conn)) &&
-                                       !IS_DOS_ARCHIVE(dosmode)) {
-                               file_set_dosmode(fsp->conn,fsp->fsp_name,
-                                               dosmode | aARCH,&st,
-                                               NULL,
-                                               false);
+                       if (!fsp->posix_open &&
+                                       (lp_store_dos_attributes(SNUM(fsp->conn)) ||
+                                       MAP_ARCHIVE(fsp->conn))) {
+                               int dosmode = dos_mode(fsp->conn, fsp->fsp_name);
+                               if (!IS_DOS_ARCHIVE(dosmode)) {
+                                       file_set_dosmode(fsp->conn, fsp->fsp_name,
+                                                dosmode | aARCH, NULL, false);
+                               }
                        }
 
                        /*
@@ -268,7 +330,8 @@ ssize_t write_file(struct smb_request *req,
                         */
 
                        if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type) && !wcp) {
-                               setup_write_cache(fsp, st.st_size);
+                               setup_write_cache(fsp,
+                                                fsp->fsp_name->st.st_ex_size);
                                wcp = fsp->wcp;
                        }
                }
@@ -289,7 +352,9 @@ ssize_t write_file(struct smb_request *req,
         * the shared memory area whilst doing this.
         */
 
-       release_level_2_oplocks_on_change(fsp);
+       /* This should actually be improved to span the write. */
+       contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_WRITE);
+       contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_WRITE);
 
 #ifdef WITH_PROFILE
        if (profile_p && profile_p->writecache_total_writes % 500 == 0) {
@@ -331,11 +396,44 @@ nonop=%u allocated=%u active=%u direct=%u perfect=%u readhits=%u\n",
                return total_written;
        }
 
-       DEBUG(9,("write_file (%s)(fd=%d pos=%.0f size=%u) wcp->offset=%.0f wcp->data_size=%u\n",
-               fsp->fsp_name, fsp->fh->fd, (double)pos, (unsigned int)n, (double)wcp->offset, (unsigned int)wcp->data_size));
+       DEBUG(9,("write_file (%s)(fd=%d pos=%.0f size=%u) wcp->offset=%.0f "
+                "wcp->data_size=%u\n", fsp_str_dbg(fsp), fsp->fh->fd,
+                (double)pos, (unsigned int)n, (double)wcp->offset,
+                (unsigned int)wcp->data_size));
 
        fsp->fh->pos = pos + n;
 
+       if ((n == 1) && (data[0] == '\0') && (pos > wcp->file_size)) {
+               int ret;
+
+               /*
+                * This is a 1-byte write of a 0 beyond the EOF and
+                * thus implicitly also beyond the current active
+                * write cache, the typical file-extending (and
+                * allocating, but we're using the write cache here)
+                * write done by Windows. We just have to ftruncate
+                * the file and rely on posix semantics to return
+                * zeros for non-written file data that is within the
+                * file length.
+                *
+                * We can not use wcp_file_size_change here because we
+                * might have an existing write cache, and
+                * wcp_file_size_change assumes a change to just the
+                * end of the current write cache.
+                */
+
+               wcp->file_size = pos + 1;
+               ret = SMB_VFS_FTRUNCATE(fsp, wcp->file_size);
+               if (ret == -1) {
+                       DEBUG(0,("wcp_file_size_change (%s): ftruncate of size %.0f"
+                                "error %s\n", fsp_str_dbg(fsp),
+                                (double)wcp->file_size, strerror(errno)));
+                       return -1;
+               }
+               return 1;
+       }
+
+
        /*
         * If we have active cache and it isn't contiguous then we flush.
         * NOTE: There is a small problem with running out of disk ....
@@ -727,6 +825,26 @@ n = %u, wcp->offset=%.0f, wcp->data_size=%u\n",
                        DO_PROFILE_INC(writecache_init_writes);
                }
 #endif
+
+               if ((wcp->data_size == 0)
+                   && (pos > wcp->file_size)
+                   && (pos + n <= wcp->file_size + wcp->alloc_size)) {
+                       /*
+                        * This is a write completely beyond the
+                        * current EOF, but within reach of the write
+                        * cache. We expect fill-up writes pretty
+                        * soon, so it does not make sense to start
+                        * the write cache at the current
+                        * offset. These fill-up writes would trigger
+                        * separate pwrites or even unnecessary cache
+                        * flushes because they overlap if this is a
+                        * one-byte allocating write.
+                        */
+                       wcp->offset = wcp->file_size;
+                       wcp->data_size = pos - wcp->file_size;
+                       memset(wcp->data, 0, wcp->data_size);
+               }
+
                memcpy(wcp->data+wcp->data_size, data, n);
                if (wcp->data_size == 0) {
                        wcp->offset = pos;
@@ -777,7 +895,8 @@ void delete_write_cache(files_struct *fsp)
        SAFE_FREE(wcp->data);
        SAFE_FREE(fsp->wcp);
 
-       DEBUG(10,("delete_write_cache: File %s deleted write cache\n", fsp->fsp_name ));
+       DEBUG(10,("delete_write_cache: File %s deleted write cache\n",
+                 fsp_str_dbg(fsp)));
 }
 
 /****************************************************************************
@@ -820,7 +939,7 @@ static bool setup_write_cache(files_struct *fsp, SMB_OFF_T file_size)
        allocated_write_caches++;
 
        DEBUG(10,("setup_write_cache: File %s allocated write cache size %lu\n",
-               fsp->fsp_name, (unsigned long)wcp->alloc_size ));
+                 fsp_str_dbg(fsp), (unsigned long)wcp->alloc_size));
 
        return True;
 }
@@ -835,11 +954,14 @@ void set_filelen_write_cache(files_struct *fsp, SMB_OFF_T file_size)
                /* The cache *must* have been flushed before we do this. */
                if (fsp->wcp->data_size != 0) {
                        char *msg;
-                       asprintf(&msg, "set_filelen_write_cache: size change "
+                       if (asprintf(&msg, "set_filelen_write_cache: size change "
                                 "on file %s with write cache size = %lu\n",
-                                fsp->fsp_name,
-                                (unsigned long)fsp->wcp->data_size);
-                       smb_panic(msg);
+                                fsp->fsp_name->base_name,
+                                (unsigned long)fsp->wcp->data_size) != -1) {
+                               smb_panic(msg);
+                       } else {
+                               smb_panic("set_filelen_write_cache");
+                       }
                }
                fsp->wcp->file_size = file_size;
        }
@@ -913,11 +1035,15 @@ NTSTATUS sync_file(connection_struct *conn, files_struct *fsp, bool write_throug
  Perform a stat whether a valid fd or not.
 ************************************************************/
 
-int fsp_stat(files_struct *fsp, SMB_STRUCT_STAT *pst)
+int fsp_stat(files_struct *fsp)
 {
        if (fsp->fh->fd == -1) {
-               return SMB_VFS_STAT(fsp->conn, fsp->fsp_name, pst);
+               if (fsp->posix_open) {
+                       return SMB_VFS_LSTAT(fsp->conn, fsp->fsp_name);
+               } else {
+                       return SMB_VFS_STAT(fsp->conn, fsp->fsp_name);
+               }
        } else {
-               return SMB_VFS_FSTAT(fsp, pst);
+               return SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
        }
 }