mirror of
https://github.com/samba-team/samba.git
synced 2025-08-30 17:49:30 +03:00
The gist of this is: 1. A write or other modifications as per MS-FSA "2.1.4.17 Algorithm for Noting that a File Has Been Modified" immediately update the file times, there's no delayed update. 2. Setting a timestamp on a handle only disables further timestamp updates via that handle. Modifications via other handles must still update the timestamps. This can not be implemented by storing the sticky writetime in the share_mode_data record for performance reasons, as from 2 it follows that the record would have to be checked and updated for every write on all handles. Instead, writes on handles with sticky writetime in effect, record the current timestamp before the modification and reset the filesystem timestamp to the recorded value after doing the modification. For reference, these are the relevant pieces from MS-FSA: 2.1.1.6 Abstract Date Model: Per Open - UserSetModificationTime: A Boolean that is TRUE if a user has explicitly set File.LastModificationTime through this Open. - UserSetChangeTime: A Boolean that is TRUE if a user has explicitly set File.LastChangeTime through this Open. - UserSetAccessTime: A Boolean that is TRUE if a user has explicitly set File.LastAccessTime through this Open. 2.1.4.17 Algorithm for Noting That a File Has Been Modified The inputs for this algorithm are as follows: - Open: The Open through which the file was modified. The pseudocode for the algorithm is as follows: The object store SHOULD<43>: - If Open.UserSetModificationTime is FALSE, set Open.File.LastModificationTime to the current system time. - If Open.UserSetChangeTime is FALSE, set Open.File.LastChangeTime to the current system time. - If Open.UserSetAccessTime is FALSE, set Open.File.LastAccessTime to the current system time. - Set Open.File.FileAttributes.FILE_ATTRIBUTE_ARCHIVE to TRUE. This then gets used for eg writes: 2.1.5.4 Server Requests a Write ... - The object store MUST note that the file has been modified as specified in section 2.1.4.17 with Open equal to Open. ... Note the following differences between Windows behaviour and current MS-FSA, these are documentation bugs that will be fixed in later releases as discussed here: https://lists.samba.org/archive/cifs-protocol/2025-March/004453.html * When processing FileEndOfFileInformation timestamps must be updated if new size equals current size * When processing FileAllocationInformation timestamps must be updated if new size equals current size * When processing FileAllocationInformation, If (the new allocation size is less than the existing allocation) Set the modification time (which will also updates the change time) Else Update the LastChangeTime BUG: https://bugzilla.samba.org/show_bug.cgi?id=13594 Signed-off-by: Ralph Boehme <slow@samba.org> Reviewed-by: Jeremy Allison <jra@samba.org> Autobuild-User(master): Ralph Böhme <slow@samba.org> Autobuild-Date(master): Thu Mar 27 06:37:31 UTC 2025 on atb-devel-224
253 lines
6.7 KiB
C
253 lines
6.7 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
Core SMB2 server
|
|
|
|
Copyright (C) Stefan Metzmacher 2009
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
#include "smbd/smbd.h"
|
|
#include "smbd/globals.h"
|
|
#include "../libcli/smb/smb_common.h"
|
|
#include "../lib/util/tevent_ntstatus.h"
|
|
#include "libcli/security/security.h"
|
|
|
|
#undef DBGC_CLASS
|
|
#define DBGC_CLASS DBGC_SMB2
|
|
|
|
static struct tevent_req *smbd_smb2_flush_send(TALLOC_CTX *mem_ctx,
|
|
struct tevent_context *ev,
|
|
struct smbd_smb2_request *smb2req,
|
|
struct files_struct *fsp);
|
|
static NTSTATUS smbd_smb2_flush_recv(struct tevent_req *req);
|
|
|
|
static void smbd_smb2_request_flush_done(struct tevent_req *subreq);
|
|
NTSTATUS smbd_smb2_request_process_flush(struct smbd_smb2_request *req)
|
|
{
|
|
NTSTATUS status;
|
|
const uint8_t *inbody;
|
|
uint64_t in_file_id_persistent;
|
|
uint64_t in_file_id_volatile;
|
|
struct files_struct *in_fsp;
|
|
struct tevent_req *subreq;
|
|
|
|
status = smbd_smb2_request_verify_sizes(req, 0x18);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
return smbd_smb2_request_error(req, status);
|
|
}
|
|
inbody = SMBD_SMB2_IN_BODY_PTR(req);
|
|
|
|
in_file_id_persistent = BVAL(inbody, 0x08);
|
|
in_file_id_volatile = BVAL(inbody, 0x10);
|
|
|
|
in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
|
|
if (in_fsp == NULL) {
|
|
return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
|
|
}
|
|
|
|
subreq = smbd_smb2_flush_send(req, req->sconn->ev_ctx,
|
|
req, in_fsp);
|
|
if (subreq == NULL) {
|
|
return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
|
|
}
|
|
tevent_req_set_callback(subreq, smbd_smb2_request_flush_done, req);
|
|
|
|
return smbd_smb2_request_pending_queue(req, subreq, 500);
|
|
}
|
|
|
|
static void smbd_smb2_request_flush_done(struct tevent_req *subreq)
|
|
{
|
|
struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
|
|
struct smbd_smb2_request);
|
|
DATA_BLOB outbody;
|
|
NTSTATUS status;
|
|
NTSTATUS error; /* transport error */
|
|
|
|
status = smbd_smb2_flush_recv(subreq);
|
|
TALLOC_FREE(subreq);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
error = smbd_smb2_request_error(req, status);
|
|
if (!NT_STATUS_IS_OK(error)) {
|
|
smbd_server_connection_terminate(req->xconn,
|
|
nt_errstr(error));
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
outbody = smbd_smb2_generate_outbody(req, 0x04);
|
|
if (outbody.data == NULL) {
|
|
error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
|
|
if (!NT_STATUS_IS_OK(error)) {
|
|
smbd_server_connection_terminate(req->xconn,
|
|
nt_errstr(error));
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
SSVAL(outbody.data, 0x00, 0x04); /* struct size */
|
|
SSVAL(outbody.data, 0x02, 0); /* reserved */
|
|
|
|
error = smbd_smb2_request_done(req, outbody, NULL);
|
|
if (!NT_STATUS_IS_OK(error)) {
|
|
smbd_server_connection_terminate(req->xconn,
|
|
nt_errstr(error));
|
|
return;
|
|
}
|
|
}
|
|
|
|
struct smbd_smb2_flush_state {
|
|
struct smbd_smb2_request *smb2req;
|
|
struct files_struct *fsp;
|
|
};
|
|
|
|
static void smbd_smb2_flush_done(struct tevent_req *subreq);
|
|
|
|
static struct tevent_req *smbd_smb2_flush_send(TALLOC_CTX *mem_ctx,
|
|
struct tevent_context *ev,
|
|
struct smbd_smb2_request *smb2req,
|
|
struct files_struct *fsp)
|
|
{
|
|
struct tevent_req *req;
|
|
struct tevent_req *subreq;
|
|
struct smbd_smb2_flush_state *state;
|
|
struct smb_request *smbreq;
|
|
bool is_compound = false;
|
|
bool is_last_in_compound = false;
|
|
NTSTATUS status;
|
|
|
|
req = tevent_req_create(mem_ctx, &state,
|
|
struct smbd_smb2_flush_state);
|
|
if (req == NULL) {
|
|
return NULL;
|
|
}
|
|
state->smb2req = smb2req;
|
|
state->fsp = fsp;
|
|
|
|
DEBUG(10,("smbd_smb2_flush: %s - %s\n",
|
|
fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
|
|
|
|
smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
|
|
if (tevent_req_nomem(smbreq, req)) {
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
|
|
if (IS_IPC(smbreq->conn)) {
|
|
tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
|
|
status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
bool allow_dir_flush = false;
|
|
uint32_t flush_access = FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY;
|
|
|
|
if (!fsp->fsp_flags.is_directory) {
|
|
tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
|
|
/*
|
|
* Directories are not writable in the conventional
|
|
* sense, but if opened with *either*
|
|
* FILE_ADD_FILE or FILE_ADD_SUBDIRECTORY
|
|
* they can be flushed.
|
|
*/
|
|
|
|
status = check_any_access_fsp(fsp, flush_access);
|
|
if (NT_STATUS_IS_OK(status)) {
|
|
allow_dir_flush = true;
|
|
}
|
|
|
|
if (allow_dir_flush == false) {
|
|
tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
}
|
|
|
|
if (fsp_get_io_fd(fsp) == -1) {
|
|
tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE);
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
|
|
if (!lp_strict_sync(SNUM(smbreq->conn))) {
|
|
/*
|
|
* No strict sync. Don't really do
|
|
* anything here.
|
|
*/
|
|
tevent_req_done(req);
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
|
|
subreq = SMB_VFS_FSYNC_SEND(state, ev, fsp);
|
|
if (tevent_req_nomem(subreq, req)) {
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
|
|
tevent_req_set_callback(subreq, smbd_smb2_flush_done, req);
|
|
|
|
is_compound = smbd_smb2_is_compound(smb2req);
|
|
is_last_in_compound = smbd_smb2_is_last_in_compound(smb2req);
|
|
|
|
if (is_compound && !is_last_in_compound) {
|
|
/*
|
|
* Can't go async if we're not the
|
|
* last request in a compound request.
|
|
* Cause this request to complete synchronously.
|
|
*/
|
|
smb2_request_set_async_internal(state->smb2req, true);
|
|
}
|
|
|
|
/* Ensure any close request knows about this outstanding IO. */
|
|
if (!aio_add_req_to_fsp(fsp, req)) {
|
|
tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
|
|
return req;
|
|
|
|
}
|
|
|
|
static void smbd_smb2_flush_done(struct tevent_req *subreq)
|
|
{
|
|
struct tevent_req *req = tevent_req_callback_data(
|
|
subreq, struct tevent_req);
|
|
int ret;
|
|
struct vfs_aio_state vfs_aio_state;
|
|
|
|
ret = SMB_VFS_FSYNC_RECV(subreq, &vfs_aio_state);
|
|
TALLOC_FREE(subreq);
|
|
if (ret == -1) {
|
|
tevent_req_nterror(req, map_nt_error_from_unix(vfs_aio_state.error));
|
|
return;
|
|
}
|
|
tevent_req_done(req);
|
|
}
|
|
|
|
static NTSTATUS smbd_smb2_flush_recv(struct tevent_req *req)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
if (tevent_req_is_nterror(req, &status)) {
|
|
tevent_req_received(req);
|
|
return status;
|
|
}
|
|
|
|
tevent_req_received(req);
|
|
return NT_STATUS_OK;
|
|
}
|