/*
   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 <http://www.gnu.org/licenses/>.
*/

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

void fsp_flush_write_time_update(struct files_struct *fsp)
{
	/*
	 * Note this won't expect any impersonation!
	 * So don't call any SMB_VFS operations here!
	 */

	DEBUG(5, ("Update write time on %s\n", fsp_str_dbg(fsp)));

	trigger_write_time_update_immediate(fsp);
}

static void update_write_time_handler(struct tevent_context *ctx,
				      struct tevent_timer *te,
				      struct timeval now,
				      void *private_data)
{
	files_struct *fsp = (files_struct *)private_data;
	fsp_flush_write_time_update(fsp);
}

/*********************************************************
 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_flags & FSP_POSIX_FLAGS_OPEN) {
		/* Don't use delayed writes on POSIX files. */
		return;
	}

	if (fsp->fsp_flags.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->fsp_flags.update_write_time_on_close = true;

	if (fsp->fsp_flags.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->fsp_flags.update_write_time_triggered = true;

	delay = lp_parm_int(SNUM(fsp->conn),
			    "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_event =
		tevent_add_timer(fsp->conn->sconn->ev_ctx, NULL,
				 timeval_current_ofs_usec(delay),
				 update_write_time_handler, fsp);
}

void trigger_write_time_update_immediate(struct files_struct *fsp)
{
	struct smb_file_time ft;

	init_smb_file_time(&ft);

	if (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) {
		/* Don't use delayed writes on POSIX files. */
		return;
	}

        if (fsp->fsp_flags.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->fsp_flags.update_write_time_triggered = true;
        fsp->fsp_flags.update_write_time_on_close = false;

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

void mark_file_modified(files_struct *fsp)
{
	int dosmode;

	trigger_write_time_update(fsp);

	if (fsp->fsp_flags.modified) {
		return;
	}

	fsp->fsp_flags.modified = true;

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

	mark_file_modified(fsp);

	/*
	 * If this file is level II oplocked then we need
	 * to grab the shared memory lock and inform all
	 * other files with a level II lock that they need
	 * to flush their read caches. We keep the lock over
	 * the shared memory area whilst doing this.
	 */

	/* 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);

	total_written = real_write_file(req, fsp, data, pos, n);
	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;
}
