/* 
   Unix SMB/CIFS implementation.
   client file operations
   Copyright (C) Andrew Tridgell 1994-1998
   Copyright (C) Jeremy Allison 2001-2002
   Copyright (C) James Myers 2003
   
   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 "system/filesys.h"
#include "libcli/raw/libcliraw.h"
#include "libcli/libcli.h"
#include "system/dir.h"

/****************************************************************************
 Hard/Symlink a file (UNIX extensions).
****************************************************************************/

static NTSTATUS smbcli_link_internal(struct smbcli_tree *tree, 
				  const char *fname_src, 
				  const char *fname_dst, bool hard_link)
{
	union smb_setfileinfo parms;
	NTSTATUS status;

	if (hard_link) {
		parms.generic.level = RAW_SFILEINFO_UNIX_HLINK;
		parms.unix_hlink.in.file.path = fname_src;
		parms.unix_hlink.in.link_dest = fname_dst;
	} else {
		parms.generic.level = RAW_SFILEINFO_UNIX_LINK;
		parms.unix_link.in.file.path = fname_src;
		parms.unix_link.in.link_dest = fname_dst;
	}
	
	status = smb_raw_setpathinfo(tree, &parms);

	return status;
}

/****************************************************************************
 Symlink a file (UNIX extensions).
****************************************************************************/
NTSTATUS smbcli_unix_symlink(struct smbcli_tree *tree, const char *fname_src, 
			  const char *fname_dst)
{
	return smbcli_link_internal(tree, fname_src, fname_dst, false);
}

/****************************************************************************
 Hard a file (UNIX extensions).
****************************************************************************/
NTSTATUS smbcli_unix_hardlink(struct smbcli_tree *tree, const char *fname_src, 
			   const char *fname_dst)
{
	return smbcli_link_internal(tree, fname_src, fname_dst, true);
}


/****************************************************************************
 Chmod or chown a file internal (UNIX extensions).
****************************************************************************/
static NTSTATUS smbcli_unix_chmod_chown_internal(struct smbcli_tree *tree, 
					      const char *fname, 
					      uint32_t mode, uint32_t uid, 
					      uint32_t gid)
{
	union smb_setfileinfo parms;
	NTSTATUS status;

	parms.generic.level = SMB_SFILEINFO_UNIX_BASIC;
	parms.unix_basic.in.file.path = fname;
	parms.unix_basic.in.uid = uid;
	parms.unix_basic.in.gid = gid;
	parms.unix_basic.in.mode = mode;
	
	status = smb_raw_setpathinfo(tree, &parms);

	return status;
}

/****************************************************************************
 chmod a file (UNIX extensions).
****************************************************************************/

NTSTATUS smbcli_unix_chmod(struct smbcli_tree *tree, const char *fname, mode_t mode)
{
	return smbcli_unix_chmod_chown_internal(tree, fname, 
					     unix_perms_to_wire(mode), 
					     SMB_UID_NO_CHANGE, 
					     SMB_GID_NO_CHANGE);
}

/****************************************************************************
 chown a file (UNIX extensions).
****************************************************************************/
NTSTATUS smbcli_unix_chown(struct smbcli_tree *tree, const char *fname, uid_t uid, 
			gid_t gid)
{
	return smbcli_unix_chmod_chown_internal(tree, fname, SMB_MODE_NO_CHANGE, 
					     (uint32_t)uid, (uint32_t)gid);
}


/****************************************************************************
 Rename a file.
****************************************************************************/
NTSTATUS smbcli_rename(struct smbcli_tree *tree, const char *fname_src, 
		    const char *fname_dst)
{
	union smb_rename parms;

	parms.generic.level = RAW_RENAME_RENAME;
	parms.rename.in.attrib = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_DIRECTORY;
	parms.rename.in.pattern1 = fname_src;
	parms.rename.in.pattern2 = fname_dst;

	return smb_raw_rename(tree, &parms);
}


/****************************************************************************
 Delete a file.
****************************************************************************/
NTSTATUS smbcli_unlink(struct smbcli_tree *tree, const char *fname)
{
	union smb_unlink parms;

	parms.unlink.in.pattern = fname;
	parms.unlink.in.attrib = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_DIRECTORY;

	return smb_raw_unlink(tree, &parms);
}

struct wcard_delete_state {
	struct smbcli_tree *tree;
	NTSTATUS status;
	char *error_name; /* To help debugging. */
};

static void del_fn(struct clilist_file_info *finfo,
			const char *pattern,
			void *priv)
{
	NTSTATUS status;
	union smb_unlink parms;
	char *filename = NULL;
	char *dirname = NULL;
	char *p = NULL;
	struct wcard_delete_state *state = (struct wcard_delete_state *)priv;

	if (ISDOT(finfo->name) || ISDOTDOT(finfo->name)) {
		return;
	}
	dirname = talloc_strdup(state, pattern);
	if (dirname == NULL) {
		TALLOC_FREE(state->error_name);
		state->status = NT_STATUS_NO_MEMORY;
		return;
	}
	p = strrchr_m(dirname, '\\');
	if (p != NULL) {
		/* Remove the terminating '\' */
		*p = '\0';
	}
	if (dirname[0] != '\0') {
		filename = talloc_asprintf(dirname,
					   "%s\\%s",
					   dirname,
					   finfo->name);
	} else {
		filename = talloc_asprintf(dirname,
					   "%s",
					   finfo->name);
	}
	if (filename == NULL) {
		TALLOC_FREE(dirname);
		TALLOC_FREE(state->error_name);
		state->status = NT_STATUS_NO_MEMORY;
		return;
	}
	parms.unlink.in.pattern = filename;
	parms.unlink.in.attrib = FILE_ATTRIBUTE_SYSTEM |
				 FILE_ATTRIBUTE_HIDDEN;
	status = smb_raw_unlink(state->tree, &parms);
	if (NT_STATUS_IS_OK(state->status)) {
		state->status = status;
		if (!NT_STATUS_IS_OK(status)) {
			/*
			 * Save off the name we failed to
			 * delete to help debugging.
			 */
			state->error_name = talloc_move(state, &filename);
		}
	}
	TALLOC_FREE(dirname);
}

/****************************************************************************
 Delete a file, possibly with a wildcard pattern.
****************************************************************************/
NTSTATUS smbcli_unlink_wcard(struct smbcli_tree *tree, const char *pattern)
{
	NTSTATUS status;
	int ret;
	struct wcard_delete_state *state = NULL;

	if (strchr(pattern, '*') == NULL) {
		/* No wildcard, just call smbcli_unlink(). */
		return smbcli_unlink(tree, pattern);
	}
	state = talloc_zero(tree, struct wcard_delete_state);
	if (state == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	state->tree = tree;
	ret = smbcli_list(tree,
			  pattern,
			  FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN,
			  del_fn,
			  state);
	status = state->status;
	TALLOC_FREE(state);
	if (ret < 0) {
		return NT_STATUS_UNSUCCESSFUL;
	}
	return status;
}

/****************************************************************************
 Create a directory.
****************************************************************************/
NTSTATUS smbcli_mkdir(struct smbcli_tree *tree, const char *dname)
{
	union smb_mkdir parms;

	parms.mkdir.level = RAW_MKDIR_MKDIR;
	parms.mkdir.in.path = dname;

	return smb_raw_mkdir(tree, &parms);
}


/****************************************************************************
 Remove a directory.
****************************************************************************/
NTSTATUS smbcli_rmdir(struct smbcli_tree *tree, const char *dname)
{
	struct smb_rmdir parms;

	parms.in.path = dname;

	return smb_raw_rmdir(tree, &parms);
}


/****************************************************************************
 Set or clear the delete on close flag.
****************************************************************************/
NTSTATUS smbcli_nt_delete_on_close(struct smbcli_tree *tree, int fnum, 
				   bool flag)
{
	union smb_setfileinfo parms;
	NTSTATUS status;

	parms.disposition_info.level = RAW_SFILEINFO_DISPOSITION_INFO;
	parms.disposition_info.in.file.fnum = fnum;
	parms.disposition_info.in.delete_on_close = flag;
	
	status = smb_raw_setfileinfo(tree, &parms);

	return status;
}


/****************************************************************************
 Create/open a file - exposing the full horror of the NT API :-).
 Used in CIFS-on-CIFS NTVFS.
****************************************************************************/
int smbcli_nt_create_full(struct smbcli_tree *tree, const char *fname,
		       uint32_t CreatFlags, uint32_t DesiredAccess,
		       uint32_t FileAttributes, uint32_t ShareAccess,
		       uint32_t CreateDisposition, uint32_t CreateOptions,
		       uint8_t SecurityFlags)
{
	union smb_open open_parms;
	TALLOC_CTX *mem_ctx;
	NTSTATUS status;

	mem_ctx = talloc_init("raw_open");
	if (!mem_ctx) return -1;

	open_parms.ntcreatex.level = RAW_OPEN_NTCREATEX;
	open_parms.ntcreatex.in.flags = CreatFlags;
	open_parms.ntcreatex.in.root_fid.fnum = 0;
	open_parms.ntcreatex.in.access_mask = DesiredAccess;
	open_parms.ntcreatex.in.file_attr = FileAttributes;
	open_parms.ntcreatex.in.alloc_size = 0;
	open_parms.ntcreatex.in.share_access = ShareAccess;
	open_parms.ntcreatex.in.open_disposition = CreateDisposition;
	open_parms.ntcreatex.in.create_options = CreateOptions;
	open_parms.ntcreatex.in.impersonation = 0;
	open_parms.ntcreatex.in.security_flags = SecurityFlags;
	open_parms.ntcreatex.in.fname = fname;

	status = smb_raw_open(tree, mem_ctx, &open_parms);
	talloc_free(mem_ctx);

	if (NT_STATUS_IS_OK(status)) {
		return open_parms.ntcreatex.out.file.fnum;
	}
	
	return -1;
}


/****************************************************************************
 Open a file (using SMBopenx)
 WARNING: if you open with O_WRONLY then getattrE won't work!
****************************************************************************/
int smbcli_open(struct smbcli_tree *tree, const char *fname, int flags, 
	     int share_mode)
{
	union smb_open open_parms;
	unsigned int openfn=0;
	unsigned int accessmode=0;
	TALLOC_CTX *mem_ctx;
	NTSTATUS status;

	mem_ctx = talloc_init("raw_open");
	if (!mem_ctx) return -1;

	if (flags & O_CREAT) {
		openfn |= OPENX_OPEN_FUNC_CREATE;
	}
	if (!(flags & O_EXCL)) {
		if (flags & O_TRUNC) {
			openfn |= OPENX_OPEN_FUNC_TRUNC;
		} else {
			openfn |= OPENX_OPEN_FUNC_OPEN;
		}
	}

	accessmode = (share_mode<<OPENX_MODE_DENY_SHIFT);

	if ((flags & O_ACCMODE) == O_RDWR) {
		accessmode |= OPENX_MODE_ACCESS_RDWR;
	} else if ((flags & O_ACCMODE) == O_WRONLY) {
		accessmode |= OPENX_MODE_ACCESS_WRITE;
	} 

#if defined(O_SYNC)
	if ((flags & O_SYNC) == O_SYNC) {
		accessmode |= OPENX_MODE_WRITE_THRU;
	}
#endif

	if (share_mode == DENY_FCB) {
		accessmode = OPENX_MODE_ACCESS_FCB | OPENX_MODE_DENY_FCB;
	}

	open_parms.openx.level = RAW_OPEN_OPENX;
	open_parms.openx.in.flags = 0;
	open_parms.openx.in.open_mode = accessmode;
	open_parms.openx.in.search_attrs = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN;
	open_parms.openx.in.file_attrs = 0;
	open_parms.openx.in.write_time = 0;
	open_parms.openx.in.open_func = openfn;
	open_parms.openx.in.size = 0;
	open_parms.openx.in.timeout = 0;
	open_parms.openx.in.fname = fname;

	status = smb_raw_open(tree, mem_ctx, &open_parms);
	talloc_free(mem_ctx);

	if (NT_STATUS_IS_OK(status)) {
		return open_parms.openx.out.file.fnum;
	}

	return -1;
}


/****************************************************************************
 Close a file.
****************************************************************************/
NTSTATUS smbcli_close(struct smbcli_tree *tree, int fnum)
{
	union smb_close close_parms;
	NTSTATUS status;

	close_parms.close.level = RAW_CLOSE_CLOSE;
	close_parms.close.in.file.fnum = fnum;
	close_parms.close.in.write_time = 0;
	status = smb_raw_close(tree, &close_parms);
	return status;
}

/****************************************************************************
 send a lock with a specified locktype 
 this is used for testing LOCKING_ANDX_CANCEL_LOCK
****************************************************************************/
NTSTATUS smbcli_locktype(struct smbcli_tree *tree, int fnum, 
		      uint32_t offset, uint32_t len, int timeout, 
		      uint8_t locktype)
{
	union smb_lock parms;
	struct smb_lock_entry lock[1];
	NTSTATUS status;

	parms.lockx.level = RAW_LOCK_LOCKX;
	parms.lockx.in.file.fnum = fnum;
	parms.lockx.in.mode = locktype;
	parms.lockx.in.timeout = timeout;
	parms.lockx.in.ulock_cnt = 0;
	parms.lockx.in.lock_cnt = 1;
	lock[0].pid = tree->session->pid;
	lock[0].offset = offset;
	lock[0].count = len;
	parms.lockx.in.locks = &lock[0];

	status = smb_raw_lock(tree, &parms);
	
	return status;
}


/****************************************************************************
 Lock a file.
****************************************************************************/
NTSTATUS smbcli_lock(struct smbcli_tree *tree, int fnum, 
		  uint32_t offset, uint32_t len, int timeout, 
		  enum brl_type lock_type)
{
	union smb_lock parms;
	struct smb_lock_entry lock[1];
	NTSTATUS status;

	parms.lockx.level = RAW_LOCK_LOCKX;
	parms.lockx.in.file.fnum = fnum;
	parms.lockx.in.mode = (lock_type == READ_LOCK? 1 : 0);
	parms.lockx.in.timeout = timeout;
	parms.lockx.in.ulock_cnt = 0;
	parms.lockx.in.lock_cnt = 1;
	lock[0].pid = tree->session->pid;
	lock[0].offset = offset;
	lock[0].count = len;
	parms.lockx.in.locks = &lock[0];

	status = smb_raw_lock(tree, &parms);

	return status;
}


/****************************************************************************
 Unlock a file.
****************************************************************************/
NTSTATUS smbcli_unlock(struct smbcli_tree *tree, int fnum, uint32_t offset, uint32_t len)
{
	union smb_lock parms;
	struct smb_lock_entry lock[1];
	NTSTATUS status;

	parms.lockx.level = RAW_LOCK_LOCKX;
	parms.lockx.in.file.fnum = fnum;
	parms.lockx.in.mode = 0;
	parms.lockx.in.timeout = 0;
	parms.lockx.in.ulock_cnt = 1;
	parms.lockx.in.lock_cnt = 0;
	lock[0].pid = tree->session->pid;
	lock[0].offset = offset;
	lock[0].count = len;
	parms.lockx.in.locks = &lock[0];
	
	status = smb_raw_lock(tree, &parms);
	return status;
}
	

/****************************************************************************
 Lock a file with 64 bit offsets.
****************************************************************************/
NTSTATUS smbcli_lock64(struct smbcli_tree *tree, int fnum, 
		    off_t offset, off_t len, int timeout, 
		    enum brl_type lock_type)
{
	union smb_lock parms;
	int ltype;
	struct smb_lock_entry lock[1];
	NTSTATUS status;

	if (!(tree->session->transport->negotiate.capabilities & CAP_LARGE_FILES)) {
		return smbcli_lock(tree, fnum, offset, len, timeout, lock_type);
	}

	parms.lockx.level = RAW_LOCK_LOCKX;
	parms.lockx.in.file.fnum = fnum;
	
	ltype = (lock_type == READ_LOCK? 1 : 0);
	ltype |= LOCKING_ANDX_LARGE_FILES;
	parms.lockx.in.mode = ltype;
	parms.lockx.in.timeout = timeout;
	parms.lockx.in.ulock_cnt = 0;
	parms.lockx.in.lock_cnt = 1;
	lock[0].pid = tree->session->pid;
	lock[0].offset = offset;
	lock[0].count = len;
	parms.lockx.in.locks = &lock[0];

	status = smb_raw_lock(tree, &parms);
	
	return status;
}


/****************************************************************************
 Unlock a file with 64 bit offsets.
****************************************************************************/
NTSTATUS smbcli_unlock64(struct smbcli_tree *tree, int fnum, off_t offset, 
			 off_t len)
{
	union smb_lock parms;
	struct smb_lock_entry lock[1];
	NTSTATUS status;

	if (!(tree->session->transport->negotiate.capabilities & CAP_LARGE_FILES)) {
		return smbcli_unlock(tree, fnum, offset, len);
	}

	parms.lockx.level = RAW_LOCK_LOCKX;
	parms.lockx.in.file.fnum = fnum;
	parms.lockx.in.mode = LOCKING_ANDX_LARGE_FILES;
	parms.lockx.in.timeout = 0;
	parms.lockx.in.ulock_cnt = 1;
	parms.lockx.in.lock_cnt = 0;
	lock[0].pid = tree->session->pid;
	lock[0].offset = offset;
	lock[0].count = len;
	parms.lockx.in.locks = &lock[0];

	status = smb_raw_lock(tree, &parms);

	return status;
}


/****************************************************************************
 Do a SMBgetattrE call.
****************************************************************************/
NTSTATUS smbcli_getattrE(struct smbcli_tree *tree, int fnum,
		      uint16_t *attr, size_t *size,
		      time_t *c_time, time_t *a_time, time_t *m_time)
{		
	union smb_fileinfo parms;
	NTSTATUS status;

	parms.getattre.level = RAW_FILEINFO_GETATTRE;
	parms.getattre.in.file.fnum = fnum;

	status = smb_raw_fileinfo(tree, NULL, &parms);

	if (!NT_STATUS_IS_OK(status))
		return status;

	if (size) {
		*size = parms.getattre.out.size;
	}

	if (attr) {
		*attr = parms.getattre.out.attrib;
	}

	if (c_time) {
		*c_time = parms.getattre.out.create_time;
	}

	if (a_time) {
		*a_time = parms.getattre.out.access_time;
	}

	if (m_time) {
		*m_time = parms.getattre.out.write_time;
	}

	return status;
}

/****************************************************************************
 Do a SMBgetatr call
****************************************************************************/
NTSTATUS smbcli_getatr(struct smbcli_tree *tree, const char *fname, 
		    uint16_t *attr, size_t *size, time_t *t)
{
	union smb_fileinfo parms;
	NTSTATUS status;

	parms.getattr.level = RAW_FILEINFO_GETATTR;
	parms.getattr.in.file.path = fname;

	status = smb_raw_pathinfo(tree, NULL, &parms);
	
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	if (size) {
		*size = parms.getattr.out.size;
	}

	if (t) {
		*t = parms.getattr.out.write_time;
	}

	if (attr) {
		*attr = parms.getattr.out.attrib;
	}

	return status;
}


/****************************************************************************
 Do a SMBsetatr call.
****************************************************************************/
NTSTATUS smbcli_setatr(struct smbcli_tree *tree, const char *fname, uint16_t mode, 
		    time_t t)
{
	union smb_setfileinfo parms;

	parms.setattr.level = RAW_SFILEINFO_SETATTR;
	parms.setattr.in.file.path = fname;
	parms.setattr.in.attrib = mode;
	parms.setattr.in.write_time = t;
	
	return smb_raw_setpathinfo(tree, &parms);
}

/****************************************************************************
 Do a setfileinfo basic_info call.
****************************************************************************/
NTSTATUS smbcli_fsetatr(struct smbcli_tree *tree, int fnum, uint16_t mode, 
			NTTIME create_time, NTTIME access_time, 
			NTTIME write_time, NTTIME change_time)
{
	union smb_setfileinfo parms;

	parms.basic_info.level = RAW_SFILEINFO_BASIC_INFO;
	parms.basic_info.in.file.fnum = fnum;
	parms.basic_info.in.attrib = mode;
	parms.basic_info.in.create_time = create_time;
	parms.basic_info.in.access_time = access_time;
	parms.basic_info.in.write_time = write_time;
	parms.basic_info.in.change_time = change_time;
	
	return smb_raw_setfileinfo(tree, &parms);
}


/****************************************************************************
 truncate a file to a given size
****************************************************************************/
NTSTATUS smbcli_ftruncate(struct smbcli_tree *tree, int fnum, uint64_t size)
{
	union smb_setfileinfo parms;

	parms.end_of_file_info.level = RAW_SFILEINFO_END_OF_FILE_INFO;
	parms.end_of_file_info.in.file.fnum = fnum;
	parms.end_of_file_info.in.size = size;
	
	return smb_raw_setfileinfo(tree, &parms);
}


/****************************************************************************
 Check for existence of a dir.
****************************************************************************/
NTSTATUS smbcli_chkpath(struct smbcli_tree *tree, const char *path)
{
	union smb_chkpath parms;
	char *path2;
	NTSTATUS status;

	path2 = strdup(path);
	trim_string(path2,NULL,"\\");
	if (!*path2) {
		free(path2);
		path2 = strdup("\\");
	}

	parms.chkpath.in.path = path2;

	status = smb_raw_chkpath(tree, &parms);

	free(path2);

	return status;
}


/****************************************************************************
 Query disk space.
****************************************************************************/
NTSTATUS smbcli_dskattr(struct smbcli_tree *tree, uint32_t *bsize, 
			uint64_t *total, uint64_t *avail)
{
	union smb_fsinfo fsinfo_parms;
	TALLOC_CTX *mem_ctx;
	NTSTATUS status;

	mem_ctx = talloc_init("smbcli_dskattr");

	fsinfo_parms.dskattr.level = RAW_QFS_SIZE_INFO;
	status = smb_raw_fsinfo(tree, mem_ctx, &fsinfo_parms);
	if (NT_STATUS_IS_OK(status)) {
		*bsize = fsinfo_parms.size_info.out.bytes_per_sector * fsinfo_parms.size_info.out.sectors_per_unit;
		*total = fsinfo_parms.size_info.out.total_alloc_units;
		*avail = fsinfo_parms.size_info.out.avail_alloc_units;
	}

	talloc_free(mem_ctx);
	
	return status;
}


/****************************************************************************
 Create and open a temporary file.
****************************************************************************/
int smbcli_ctemp(struct smbcli_tree *tree, const char *path, char **tmp_path)
{
	union smb_open open_parms;
	TALLOC_CTX *mem_ctx;
	NTSTATUS status;
	int ret = -1;

	mem_ctx = talloc_init("raw_open");
	if (!mem_ctx) return ret;

	open_parms.openx.level = RAW_OPEN_CTEMP;
	open_parms.ctemp.in.attrib = 0;
	open_parms.ctemp.in.directory = path;
	open_parms.ctemp.in.write_time = 0;

	status = smb_raw_open(tree, mem_ctx, &open_parms);
	if (NT_STATUS_IS_OK(status)) {
		if (tmp_path) {
			*tmp_path = strdup(open_parms.ctemp.out.name);
		}
		ret = open_parms.ctemp.out.file.fnum;
	}
	talloc_free(mem_ctx);
	return ret;
}
