1
0
mirror of https://github.com/samba-team/samba.git synced 2025-08-30 17:49:30 +03:00
Files
samba-mirror/source3/smbd/smb2_flush.c
Ralph Boehme b6c8d5d829 smbd: implement modern write time update logic
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
2025-03-27 06:37:31 +00:00

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;
}