/* Unix SMB/Netbios implementation. Version 1.9. read/write to a files_struct Copyright (C) Andrew Tridgell 1992-1998 Copyright (C) Jeremy Allison 2000-2002. - write cache. 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 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 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, see . */ #include "includes.h" #include "printing.h" #include "smbd/smbd.h" #include "smbd/globals.h" #include "smbprofile.h" /**************************************************************************** Read from a file. ****************************************************************************/ ssize_t read_file(files_struct *fsp,char *data,off_t pos,size_t n) { off_t new_pos; ssize_t ret = 0; bool ok; /* you can't read from print files */ if (fsp->print_file) { errno = EBADF; return -1; } ok = vfs_valid_pread_range(pos, n); if (!ok) { errno = EINVAL; return -1; } fh_set_pos(fsp->fh, pos); if (n > 0) { ret = SMB_VFS_PREAD(fsp,data,n,pos); if (ret == -1) { return -1; } } DEBUG(10,("read_file (%s): pos = %.0f, size = %lu, returned %lu\n", fsp_str_dbg(fsp), (double)pos, (unsigned long)n, (long)ret)); new_pos = fh_get_pos(fsp->fh) + ret; fh_set_pos(fsp->fh, new_pos); fh_set_position_information(fsp->fh, new_pos); return(ret); } /**************************************************************************** *Really* write to a file. ****************************************************************************/ static ssize_t real_write_file(struct smb_request *req, files_struct *fsp, const char *data, off_t pos, size_t n) { ssize_t ret; bool ok; ok = vfs_valid_pwrite_range(fsp, pos, n); if (!ok) { errno = EINVAL; return -1; } if (n == 0) { return 0; } fh_set_pos(fsp->fh, pos); if (pos && lp_strict_allocate(SNUM(fsp->conn)) && !fsp->fsp_flags.is_sparse) { if (vfs_fill_sparse(fsp, pos) == -1) { return -1; } } ret = vfs_pwrite_data(req, fsp, data, n, pos); DEBUG(10,("real_write_file (%s): pos = %.0f, size = %lu, returned %ld\n", fsp_str_dbg(fsp), (double)pos, (unsigned long)n, (long)ret)); if (ret != -1) { off_t new_pos = fh_get_pos(fsp->fh) + ret; fh_set_pos(fsp->fh, new_pos); /* Yes - this is correct - writes don't update this. JRA. */ /* Found by Samba4 tests. */ #if 0 fsp->position_information = fsp->pos; #endif } return ret; } /********************************************************* Immediately update write time *********************************************************/ void trigger_write_time_update_immediate(struct files_struct *fsp, bool update_mtime, bool update_ctime) { struct smb_file_time ft = smb_file_time_omit(); if (fsp->fsp_flags.posix_open) { return; } if (fsp->fsp_flags.write_time_forced) { /* * No point - "sticky" write times * in effect. */ return; } DEBUG(5, ("Update write time immediate on %s\n", fsp_str_dbg(fsp))); if (update_mtime) { /* * Changing mtime would also update ctime and so implicitly * handle the update_ctime=true case. */ ft.mtime = timespec_current(); } else if (update_ctime && !update_mtime) { /* * The only way to update ctime is by changing *something* in * the inode, atime being the only file metadata I could come up * with we can fiddle with to achieve this. */ ft.atime.tv_nsec = UTIME_NOW; } /* Now set on disk - takes care of notify. */ (void)smb_set_file_time(fsp->conn, fsp, fsp->fsp_name, &ft, false); } /* * If this is a sticky-write time handle, refresh the current mtime * so we can restore it after a modification. */ void prepare_file_modified(files_struct *fsp, struct file_modified_state *state) { int ret; if (!fsp->fsp_flags.write_time_forced) { return; } ZERO_STRUCTP(state); ret = SMB_VFS_FSTAT(fsp, &state->st); if (ret != 0) { DBG_ERR("Prepare [%s] failed: %s\n", fsp_str_dbg(fsp), strerror(errno)); return; } state->valid = true; return; } void mark_file_modified(files_struct *fsp, bool modified, struct file_modified_state *modified_state) { int dosmode; NTSTATUS status; if (fsp->fsp_flags.write_time_forced && modified_state->valid) { struct smb_file_time ft = smb_file_time_omit(); ft.mtime = modified_state->st.st_ex_mtime; /* * Pave over the "cached" stat info mtime in the fsp, * vfs_default checks this and if the existing time matches what * we're trying to set, it skips setting the time. file_ntimes() * will fill the value with what we've set. */ fsp->fsp_name->st.st_ex_mtime = (struct timespec){}; status = smb_set_file_time(fsp->conn, fsp, fsp->fsp_name, &ft, false); if (!NT_STATUS_IS_OK(status)) { DBG_ERR("smb_set_file_time [%s] failed: %s\n", fsp_str_dbg(fsp), nt_errstr(status)); } } if (fsp->fsp_flags.modified) { return; } /* * The modified fsp_flag triggers a directory lease breaks when closing * the handle and this must only happen after writing to a * file. Modifying a file by other means that affect the file state * causing directory lease breaks is handled in the corresponding * functions explicitly by calling notify_fname() with * NOTIFY_ACTION_DIRLEASE_BREAK. */ fsp->fsp_flags.modified = modified; if (!(lp_store_dos_attributes(SNUM(fsp->conn)) || MAP_ARCHIVE(fsp->conn))) { return; } dosmode = fdos_mode(fsp); if (dosmode & FILE_ATTRIBUTE_ARCHIVE) { return; } file_set_dosmode(fsp->conn, fsp->fsp_name, dosmode | FILE_ATTRIBUTE_ARCHIVE, NULL, false); } /**************************************************************************** Write to a file. ****************************************************************************/ ssize_t write_file(struct smb_request *req, files_struct *fsp, const char *data, off_t pos, size_t n) { struct file_modified_state state; ssize_t total_written = 0; if (fsp->print_file) { uint32_t t; int ret; ret = print_spool_write(fsp, data, n, pos, &t); if (ret) { errno = ret; return -1; } return t; } if (!fsp->fsp_flags.can_write) { errno = EPERM; return -1; } /* 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); prepare_file_modified(fsp, &state); total_written = real_write_file(req, fsp, data, pos, n); if (total_written != -1) { mark_file_modified(fsp, true, &state); } return total_written; } /******************************************************************* sync a file ********************************************************************/ NTSTATUS sync_file(connection_struct *conn, files_struct *fsp, bool write_through) { if (fsp_get_io_fd(fsp) == -1) return NT_STATUS_INVALID_HANDLE; if (lp_strict_sync(SNUM(conn)) && (lp_sync_always(SNUM(conn)) || write_through)) { int ret; ret = smb_vfs_fsync_sync(fsp); if (ret == -1) { return map_nt_error_from_unix(errno); } } return NT_STATUS_OK; }