/*
 * Samba VFS module supporting multiple AVID clients sharing media.
 *
 * Copyright (C) 2005  Philip de Nier <philipn@users.sourceforge.net>
 * Copyright (C) 2012  Andrew Klaassen <clawsoon@yahoo.com>
 * Copyright (C) 2013  Milos Lukacek
 * Copyright (C) 2013  Ralph Boehme <slow@samba.org>
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/*
 * Unityed Media is a Samba VFS module that allows multiple AVID
 * clients to share media.
 *
 * Add this module to the vfs objects option in your Samba share
 * configuration.
 * eg.
 *
 *   [avid_win]
 *	path = /video
 *	vfs objects = unityed_media
 *	...
 *
 * It is recommended that you separate out Samba shares for Mac
 * and Windows clients, and add the following options to the shares
 * for Windows clients	(NOTE: replace @ with *):
 *
 *	veto files = /.DS_Store/._@/.Trash@/.Spotlight@/.hidden/.hotfiles@/.vol/
 *	delete veto files = yes
 *
 * This prevents hidden files from Mac clients interfering with Windows
 * clients. If you find any more problem hidden files then add them to
 * the list.
 *
 * Notes:
 * This module is designed to work with AVID editing applications that
 * look in the Avid MediaFiles or OMFI MediaFiles directory for media.
 * It is not designed to work as expected in all circumstances for
 * general use.
 */


#include "includes.h"
#include "system/filesys.h"
#include "smbd/smbd.h"
#include "../smbd/globals.h"
#include "auth.h"
#include "../lib/tsocket/tsocket.h"
#include <libgen.h>

#define UM_PARAM_TYPE_NAME "unityed_media"

static const char *AVID_MXF_DIRNAME = "Avid MediaFiles/MXF";
static const size_t AVID_MXF_DIRNAME_LEN = 19;
static const char *OMFI_MEDIAFILES_DIRNAME = "OMFI MediaFiles";
static const size_t OMFI_MEDIAFILES_DIRNAME_LEN = 15;
static const char *APPLE_DOUBLE_PREFIX = "._";
static const size_t APPLE_DOUBLE_PREFIX_LEN = 2;
static int vfs_um_debug_level = DBGC_VFS;

enum um_clientid {UM_CLIENTID_NAME, UM_CLIENTID_IP, UM_CLIENTID_HOSTNAME};

struct um_config_data {
	enum um_clientid clientid;
};

static const struct enum_list um_clientid[] = {
	{UM_CLIENTID_NAME, "user"},
	{UM_CLIENTID_IP, "ip"},
	{UM_CLIENTID_HOSTNAME, "hostname"},
	{-1, NULL}
};

/* supplements the directory list stream */
typedef struct um_dirinfo_struct {
	DIR* dirstream;
	char *dirpath;
	char *clientPath;
	bool isInMediaFiles;
	char *clientSubDirname;
} um_dirinfo_struct;

/**
 * Returns true and first group of digits in path, false and 0 otherwise
 **/
static bool get_digit_group(const char *path, uintmax_t *digit)
{
	const char *p = path;
	char *endp = NULL;
	codepoint_t cp;
	size_t size;

	DEBUG(10, ("get_digit_group entering with path '%s'\n",
		   path));

	/*
	 * Delibiretly initialize to 0 because callers use this result
	 * even though the string doesn't contain any number and we
	 * returned false
	 */
	*digit = 0;

	while (*p) {
		cp = next_codepoint(p, &size);
		if (cp == -1) {
			return false;
		}
		if ((size == 1) && (isdigit(cp))) {
			*digit = (uintmax_t)strtoul(p, &endp, 10);
			DEBUG(10, ("num_suffix = '%ju'\n",
				   *digit));
			return true;
		}
		p += size;
	}

	return false;
}

/* Add "_<remote_name>.<number>" suffix to path or filename.
 *
 * Success: return 0
 * Failure: set errno, path NULL, return -1
 */

static int alloc_append_client_suffix(vfs_handle_struct *handle,
				      char **path)
{
	int status = 0;
	uintmax_t number;
	const char *clientid;
	struct um_config_data *config;

	DEBUG(10, ("Entering with path '%s'\n", *path));

	SMB_VFS_HANDLE_GET_DATA(handle, config,
				struct um_config_data,
				return -1);

	(void)get_digit_group(*path, &number);

	switch (config->clientid) {

	case UM_CLIENTID_IP:
		clientid = tsocket_address_inet_addr_string(
			handle->conn->sconn->remote_address, talloc_tos());
		if (clientid == NULL) {
			errno = ENOMEM;
			status = -1;
			goto err;
		}
		break;

	case UM_CLIENTID_HOSTNAME:
		clientid = get_remote_machine_name();
		break;

	case UM_CLIENTID_NAME:
	default:
		clientid = get_current_username();
		break;
	}

	*path = talloc_asprintf_append(*path, "_%s.%ju",
				       clientid, number);
	if (*path == NULL) {
		DEBUG(1, ("alloc_append_client_suffix "
				     "out of memory\n"));
		errno = ENOMEM;
		status = -1;
		goto err;
	}
	DEBUG(10, ("Leaving with *path '%s'\n", *path));
err:
	return status;
}

/* Returns true if the file or directory begins with the appledouble
 * prefix.
 */
static bool is_apple_double(const char* fname)
{
	bool ret = false;

	DEBUG(10, ("Entering with fname '%s'\n", fname));

	if (strnequal(APPLE_DOUBLE_PREFIX, fname, APPLE_DOUBLE_PREFIX_LEN)) {
		ret = true;
	}
	DEBUG(10, ("Leaving with ret '%s'\n",
			      ret == true ? "true" : "false"));
	return ret;
}

static bool starts_with_media_dir(const char* media_dirname,
				  size_t media_dirname_len,
				  const char *path)
{
	bool ret = false;
	const char *path_start = path;

	DEBUG(10, ("Entering with media_dirname '%s' "
			      "path '%s'\n", media_dirname, path));

	/* Sometimes Samba gives us "./OMFI MediaFiles". */
	if (strnequal(path, "./", 2)) {
		path_start += 2;
	}

	if (strnequal(media_dirname, path_start, media_dirname_len)
	    &&
	    ((path_start[media_dirname_len] == '\0') ||
	     (path_start[media_dirname_len] == '/'))) {
		ret = true;
	}

	DEBUG(10, ("Leaving with ret '%s'\n",
			      ret == true ? "true" : "false"));
	return ret;
}

/*
 * Returns true if the file or directory referenced by the path is ONE
 * LEVEL below the AVID_MXF_DIRNAME or OMFI_MEDIAFILES_DIRNAME
 * directory
 */
static bool is_in_media_dir(const char *path)
{
	int transition_count = 0;
	const char *path_start = path;
	const char *p;
	const char *media_dirname;
	size_t media_dirname_len;

	DEBUG(10, ("Entering with path'%s' ", path));

	/* Sometimes Samba gives us "./OMFI MediaFiles". */
	if (strnequal(path, "./", 2)) {
		path_start += 2;
	}

	if (strnequal(path_start, AVID_MXF_DIRNAME, AVID_MXF_DIRNAME_LEN)) {
		media_dirname = AVID_MXF_DIRNAME;
		media_dirname_len = AVID_MXF_DIRNAME_LEN;
	} else if (strnequal(path_start,
			     OMFI_MEDIAFILES_DIRNAME,
			     OMFI_MEDIAFILES_DIRNAME_LEN)) {
		media_dirname = OMFI_MEDIAFILES_DIRNAME;
		media_dirname_len = OMFI_MEDIAFILES_DIRNAME_LEN;
	} else {
		return false;
	}

	if (path_start[media_dirname_len] == '\0') {
		goto out;
	}

	p = path_start + media_dirname_len + 1;

	while (true) {
		if (*p == '\0' || *p == '/') {
			if (strnequal(p - 3, "/..", 3)) {
				transition_count--;
			} else if ((p[-1] != '/') || !strnequal(p - 2, "/.", 2)) {
				transition_count++;
			}
		}
		if (*p == '\0') {
			break;
		}
		p++;
	}

out:
	DEBUG(10, ("Going out with transition_count '%i'\n",
			      transition_count));
	if (((transition_count == 1) && (media_dirname == AVID_MXF_DIRNAME))
	    ||
	    ((transition_count == 0) && (media_dirname == OMFI_MEDIAFILES_DIRNAME))) {
		return true;
	}
	else return false;
}

/*
 * Returns true if the file or directory referenced by the path is
 * below the AVID_MEDIAFILES_DIRNAME or OMFI_MEDIAFILES_DIRNAME
 * directory The AVID_MEDIAFILES_DIRNAME and OMFI_MEDIAFILES_DIRNAME
 * are assumed to be in the root directory, which is generally a safe
 * assumption in the fixed-path world of Avid.
 */
static bool is_in_media_files(const char *path)
{
	bool ret = false;

	DEBUG(10, ("Entering with path '%s'\n", path));

	if (starts_with_media_dir(AVID_MXF_DIRNAME,
				  AVID_MXF_DIRNAME_LEN, path) ||
	    starts_with_media_dir(OMFI_MEDIAFILES_DIRNAME,
				  OMFI_MEDIAFILES_DIRNAME_LEN, path)) {
		ret = true;
	}
	DEBUG(10, ("Leaving with ret '%s'\n",
			      ret == true ? "true" : "false"));
	return ret;
}


/* Add client suffix to "pure-number" path.
 *
 * Caller must free newPath.
 *
 * Success: return 0
 * Failure: set errno, newPath NULL, return -1
 */
static int alloc_get_client_path(vfs_handle_struct *handle,
				 TALLOC_CTX *ctx,
				 const char *path_in,
				 char **path_out)
{
	int status = 0;
	char *p;
	char *digits;
	size_t digits_len;
	uintmax_t number;

	*path_out = talloc_strdup(ctx, path_in);
        if (*path_out == NULL) {
		DEBUG(1, ("alloc_get_client_path ENOMEM\n"));
		return -1;
	}

	(void)get_digit_group(*path_out, &number);

	digits = talloc_asprintf(NULL, "%ju", number);
        if (digits == NULL) {
		DEBUG(1, ("alloc_get_client_path ENOMEM\n"));
		return -1;
	}
	digits_len = strlen(digits);

	p = strstr_m(path_in, digits);
	if ((p)
	    &&
	    ((p[digits_len] == '\0') || (p[digits_len] == '/'))
	    &&
	    (((p - path_in > 0) && (p[-1] == '/'))
	     ||
	     (((p - path_in) > APPLE_DOUBLE_PREFIX_LEN)
	      &&
	      is_apple_double(p - APPLE_DOUBLE_PREFIX_LEN)
	      &&
	      (p[-(APPLE_DOUBLE_PREFIX_LEN + 1)] == '/'))))
	{
		(*path_out)[p - path_in + digits_len] = '\0';

		status = alloc_append_client_suffix(handle, path_out);
		if (status != 0) {
			goto out;
		}

                *path_out = talloc_strdup_append(*path_out, p + digits_len);
		if (*path_out == NULL) {
			DEBUG(1, ("alloc_get_client_path ENOMEM\n"));
			status = -1;
			goto out;
		}
	}
out:
	/* path_out must be freed in caller. */
	DEBUG(10, ("Result:'%s'\n", *path_out));
	return status;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int alloc_get_client_smb_fname(struct vfs_handle_struct *handle,
				      TALLOC_CTX *ctx,
				      const struct smb_filename *smb_fname,
				      struct smb_filename **client_fname)
{
	int status ;

	DEBUG(10, ("Entering with smb_fname->base_name '%s'\n",
		   smb_fname->base_name));

	*client_fname = cp_smb_filename(ctx, smb_fname);
	if (*client_fname == NULL) {
		DEBUG(1, ("cp_smb_filename returned NULL\n"));
		return -1;
	}
	status = alloc_get_client_path(handle, ctx,
				       smb_fname->base_name,
				       &(*client_fname)->base_name);
	if (status != 0) {
		return -1;
	}

	DEBUG(10, ("Leaving with (*client_fname)->base_name "
		   "'%s'\n", (*client_fname)->base_name));

	return 0;
}


/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int alloc_set_client_dirinfo_path(struct vfs_handle_struct *handle,
					 TALLOC_CTX *ctx,
					 char **path,
					 const char *suffix_number)
{
	int status;

	DEBUG(10, ("Entering with suffix_number '%s'\n",
		   suffix_number));

	*path = talloc_strdup(ctx, suffix_number);
	if (*path == NULL) {
		DEBUG(1, ("alloc_set_client_dirinfo_path ENOMEM\n"));
		return -1;
	}
	status = alloc_append_client_suffix(handle, path);
	if (status != 0) {
		return -1;
	}

	DEBUG(10, ("Leaving with *path '%s'\n", *path));

	return 0;
}

static int alloc_set_client_dirinfo(vfs_handle_struct *handle,
				    const char *fname,
				    struct um_dirinfo_struct **di_result)
{
	int status = 0;
	char *digits;
	uintmax_t number;
	struct um_dirinfo_struct *dip;

	DEBUG(10, ("Entering with fname '%s'\n", fname));

	*di_result = talloc(NULL, struct um_dirinfo_struct);
	if (*di_result == NULL) {
		goto err;
	}
	dip = *di_result;

	dip->dirpath = talloc_strdup(dip, fname);
	if (dip->dirpath == NULL) {
		goto err;
	}

	if (!is_in_media_files(fname)) {
		dip->isInMediaFiles = false;
		dip->clientPath = NULL;
		dip->clientSubDirname = NULL;
		goto out;
	}

	dip->isInMediaFiles = true;

	(void)get_digit_group(fname, &number);
	digits = talloc_asprintf(talloc_tos(), "%ju", number);
	if (digits == NULL) {
		goto err;
	}

	status = alloc_set_client_dirinfo_path(handle, dip,
					       &dip->clientSubDirname,
					       digits);
	if (status != 0) {
		goto err;
	}

	status = alloc_get_client_path(handle, dip, fname,
				       &dip->clientPath);
	if (status != 0 || dip->clientPath == NULL) {
		goto err;
	}

out:
	DEBUG(10, ("Leaving with (*dirInfo)->dirpath '%s', "
			      "(*dirInfo)->clientPath '%s'\n",
			      dip->dirpath, dip->clientPath));
	return status;

err:
	DEBUG(1, ("Failing with fname '%s'\n", fname));
	TALLOC_FREE(*di_result);
	status = -1;
	errno = ENOMEM;
	return status;
}

/**********************************************************************
 * VFS functions
 **********************************************************************/

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int um_statvfs(struct vfs_handle_struct *handle,
		      const char *path,
		      struct vfs_statvfs_struct *statbuf)
{
	int status;
	char *clientPath = NULL;

	DEBUG(10, ("Entering with path '%s'\n", path));

	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_STATVFS(handle, path, statbuf);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       path, &clientPath);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_STATVFS(handle, clientPath, statbuf);
err:
	TALLOC_FREE(clientPath);
	DEBUG(10, ("Leaving with path '%s'\n", path));
	return status;
}

/* Success: return a um_dirinfo_struct cast as a DIR
 * Failure: set errno, return NULL
 */
static DIR *um_opendir(vfs_handle_struct *handle,
		       const char *fname,
		       const char *mask,
		       uint32_t attr)
{
	struct um_dirinfo_struct *dirInfo;

	DEBUG(10, ("Entering with fname '%s'\n", fname));

	if (alloc_set_client_dirinfo(handle, fname, &dirInfo)) {
		goto err;
	}

	if (!dirInfo->isInMediaFiles) {
		dirInfo->dirstream = SMB_VFS_NEXT_OPENDIR(
			handle, fname, mask, attr);
	} else {
		dirInfo->dirstream = SMB_VFS_NEXT_OPENDIR(
			handle, dirInfo->clientPath, mask, attr);
	}

	if (dirInfo->dirstream == NULL) {
		goto err;
	}

	DEBUG(10, ("Leaving with dirInfo->dirpath '%s', "
			      "dirInfo->clientPath '%s'\n",
			      dirInfo->dirpath,
			      dirInfo->clientPath));
	return (DIR*)dirInfo;

err:
	DEBUG(1, ("Failing with fname '%s'\n", fname));
	TALLOC_FREE(dirInfo);
	return NULL;
}

static DIR *um_fdopendir(vfs_handle_struct *handle,
			 files_struct *fsp,
			 const char *mask,
			 uint32_t attr)
{
	struct um_dirinfo_struct *dirInfo = NULL;
	DIR *dirstream;

	DEBUG(10, ("Entering with fsp->fsp_name->base_name '%s'\n",
		   fsp->fsp_name->base_name));

	dirstream = SMB_VFS_NEXT_FDOPENDIR(handle, fsp, mask, attr);
	if (!dirstream) {
		goto err;
	}

	if (alloc_set_client_dirinfo(handle,
				     fsp->fsp_name->base_name,
				     &dirInfo)) {
		goto err;
	}

	dirInfo->dirstream = dirstream;

	if (!dirInfo->isInMediaFiles) {
		/*
		 * FIXME: this is the original code, something must be
		 * missing here, but what? -slow
		 */
		goto out;
	}

out:
	DEBUG(10, ("Leaving with dirInfo->dirpath '%s', "
		   "dirInfo->clientPath '%s', "
		   "fsp->fsp_name->st.st_ex_mtime %s",
		   dirInfo->dirpath,
		   dirInfo->clientPath,
		   ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec))));
	return (DIR *) dirInfo;

err:
	DEBUG(1, ("Failing with fsp->fsp_name->base_name '%s'\n",
		  fsp->fsp_name->base_name));
	TALLOC_FREE(dirInfo);
	return NULL;
}

/*
 * skip own suffixed directory
 * replace own suffixed directory with non suffixed.
 *
 * Success: return dirent
 * End of data: return NULL
 * Failure: set errno, return NULL
 */
static struct dirent *um_readdir(vfs_handle_struct *handle,
				 DIR *dirp,
				 SMB_STRUCT_STAT *sbuf)
{
	um_dirinfo_struct* dirInfo = (um_dirinfo_struct*)dirp;
	struct dirent *d = NULL;
	int skip;

	DEBUG(10, ("dirInfo->dirpath '%s', "
		   "dirInfo->clientPath '%s', "
		   "dirInfo->isInMediaFiles '%s', "
		   "dirInfo->clientSubDirname '%s'\n",
		   dirInfo->dirpath,
		   dirInfo->clientPath,
		   dirInfo->isInMediaFiles ? "true" : "false",
		   dirInfo->clientSubDirname));

	if (!dirInfo->isInMediaFiles) {
		return SMB_VFS_NEXT_READDIR(handle, dirInfo->dirstream, sbuf);
	}

	do {
		const char* dname;
		bool isAppleDouble;
		char *digits;
		size_t digits_len;
		uintmax_t number;

		skip = false;
		d = SMB_VFS_NEXT_READDIR(handle, dirInfo->dirstream, sbuf);

		if (d == NULL) {
			break;
		}

		/* ignore apple double prefix for logic below */
		if (is_apple_double(d->d_name)) {
			dname = &d->d_name[APPLE_DOUBLE_PREFIX_LEN];
			isAppleDouble = true;
		} else {
			dname = d->d_name;
			isAppleDouble = false;
		}

		DEBUG(10, ("dname = '%s'\n", dname));

		(void)get_digit_group(dname, &number);
		digits = talloc_asprintf(talloc_tos(), "%ju", number);
		if (digits == NULL) {
			DEBUG(1, ("out of memory"));
			goto err;
		}
		digits_len = strlen(digits);

		if (alloc_set_client_dirinfo_path(handle,
						  dirInfo,
						  &((dirInfo)->clientSubDirname),
						  digits)) {
			goto err;
		}

		/*
		 * If set to "true", vfs shows digits-only
		 * non-suffixed subdirectories.  Normally, such
		 * subdirectories can exists only in non-media
		 * directories, so we set it to "false".  Otherwise,
		 * if we have such subdirectories (probably created
		 * over not "unityed" connection), it can be little
		 * bit confusing.
		 */
		if (strequal(dname, digits)) {
			skip = false;
		} else if (strequal(dname, dirInfo->clientSubDirname)) {
			/*
			 * Remove suffix of this client's suffixed
			 * subdirectories
			 */
			if (isAppleDouble) {
				d->d_name[digits_len + APPLE_DOUBLE_PREFIX_LEN] = '\0';
			} else {
				d->d_name[digits_len] = '\0';
			}
		} else if (strnequal(digits, dname, digits_len)) {
			/*
			 * Set to false to see another clients subdirectories
			 */
			skip = false;
		}
	} while (skip);

	DEBUG(10, ("Leaving um_readdir\n"));
	return d;
err:
	TALLOC_FREE(dirInfo);
	return NULL;
}

static void um_seekdir(vfs_handle_struct *handle,
		       DIR *dirp,
		       long offset)
{
	DEBUG(10, ("Entering and leaving um_seekdir\n"));
	SMB_VFS_NEXT_SEEKDIR(handle,
			     ((um_dirinfo_struct*)dirp)->dirstream, offset);
}

static long um_telldir(vfs_handle_struct *handle,
		       DIR *dirp)
{
	DEBUG(10, ("Entering and leaving um_telldir\n"));
	return SMB_VFS_NEXT_TELLDIR(handle,
				    ((um_dirinfo_struct*)dirp)->dirstream);
}

static void um_rewinddir(vfs_handle_struct *handle,
			 DIR *dirp)
{
	DEBUG(10, ("Entering and leaving um_rewinddir\n"));
	SMB_VFS_NEXT_REWINDDIR(handle,
			       ((um_dirinfo_struct*)dirp)->dirstream);
}

static int um_mkdir(vfs_handle_struct *handle,
		    const char *path,
		    mode_t mode)
{
	int status;
	char *clientPath;
	TALLOC_CTX *ctx;


	DEBUG(10, ("Entering with path '%s'\n", path));

	if (!is_in_media_files(path) || !is_in_media_dir(path)) {
		return SMB_VFS_NEXT_MKDIR(handle, path, mode);
	}

	clientPath = NULL;
	ctx = talloc_tos();

	if ((status = alloc_get_client_path(handle, ctx,
					    path,
					    &clientPath))) {
		goto err;
	}

	status = SMB_VFS_NEXT_MKDIR(handle, clientPath, mode);
err:
	TALLOC_FREE(clientPath);
	DEBUG(10, ("Leaving with path '%s'\n", path));
	return status;
}

static int um_rmdir(vfs_handle_struct *handle,
		    const char *path)
{
	int status;
	char *clientPath;
	TALLOC_CTX *ctx;


	DEBUG(10, ("Entering with path '%s'\n", path));

	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_RMDIR(handle, path);
	}

	clientPath = NULL;
	ctx = talloc_tos();

	if ((status = alloc_get_client_path(handle, ctx,
					    path,
					    &clientPath))) {
		goto err;
	}

	status = SMB_VFS_NEXT_RMDIR(handle, clientPath);
err:
	TALLOC_FREE(clientPath);
	DEBUG(10, ("Leaving with path '%s'\n", path));
	return status;
}

static int um_closedir(vfs_handle_struct *handle,
		       DIR *dirp)
{
	DIR *realdirp = ((um_dirinfo_struct*)dirp)->dirstream;

	TALLOC_FREE(dirp);

	return SMB_VFS_NEXT_CLOSEDIR(handle, realdirp);
}

static void um_init_search_op(vfs_handle_struct *handle,
			      DIR *dirp)
{
	DEBUG(10, ("Entering and leaving um_init_search_op\n"));

	SMB_VFS_NEXT_INIT_SEARCH_OP(handle,
				    ((um_dirinfo_struct*)dirp)->dirstream);
}

static int um_open(vfs_handle_struct *handle,
		   struct smb_filename *smb_fname,
		   files_struct *fsp,
		   int flags,
		   mode_t mode)
{
	int ret;
	struct smb_filename *client_fname = NULL;

	DEBUG(10, ("Entering with smb_fname->base_name '%s'\n",
			      smb_fname->base_name));

	if (!is_in_media_files(smb_fname->base_name)) {
		return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
	}

	if (alloc_get_client_smb_fname(handle, talloc_tos(),
				       smb_fname,
				       &client_fname)) {
		ret = -1;
		goto err;
	}

	/*
	 * FIXME:
	 * What about fsp->fsp_name?  We also have to get correct stat
	 * info into fsp and smb_fname for DB files, don't we?
	 */

	DEBUG(10, ("Leaving with smb_fname->base_name '%s' "
		   "smb_fname->st.st_ex_mtime %s"
		   "fsp->fsp_name->st.st_ex_mtime %s",
			      smb_fname->base_name,
			      ctime(&(smb_fname->st.st_ex_mtime.tv_sec)),
			      ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec))));

	ret = SMB_VFS_NEXT_OPEN(handle, client_fname, fsp, flags, mode);
err:
	TALLOC_FREE(client_fname);
	DEBUG(10, ("Leaving with smb_fname->base_name '%s'\n",
			      smb_fname->base_name));
	return ret;
}

static NTSTATUS um_create_file(vfs_handle_struct *handle,
			       struct smb_request *req,
			       uint16_t root_dir_fid,
			       struct smb_filename *smb_fname,
			       uint32_t access_mask,
			       uint32_t share_access,
			       uint32_t create_disposition,
			       uint32_t create_options,
			       uint32_t file_attributes,
			       uint32_t oplock_request,
			       struct smb2_lease *lease,
			       uint64_t allocation_size,
			       uint32_t private_flags,
			       struct security_descriptor *sd,
			       struct ea_list *ea_list,
			       files_struct **result_fsp,
			       int *pinfo,
			       const struct smb2_create_blobs *in_context_blobs,
			       struct smb2_create_blobs *out_context_blobs)
{
	NTSTATUS status;
	struct smb_filename *client_fname = NULL;

	DEBUG(10, ("Entering with smb_fname->base_name '%s'\n",
		   smb_fname->base_name));

	if (!is_in_media_files(smb_fname->base_name)) {
		return SMB_VFS_NEXT_CREATE_FILE(
			handle,
			req,
			root_dir_fid,
			smb_fname,
			access_mask,
			share_access,
			create_disposition,
			create_options,
			file_attributes,
			oplock_request,
			lease,
			allocation_size,
			private_flags,
			sd,
			ea_list,
			result_fsp,
			pinfo,
			in_context_blobs,
			out_context_blobs);
	}

	if (alloc_get_client_smb_fname(handle, talloc_tos(),
				       smb_fname,
				       &client_fname)) {
		status = map_nt_error_from_unix(errno);
		goto err;
	}

	/*
	 * FIXME:
	 * This only creates files, so we don't have to worry about
	 * our fake directory stat'ing here.  But we still need to
	 * route stat calls for DB files properly, right?
	 */
	status = SMB_VFS_NEXT_CREATE_FILE(
		handle,
		req,
		root_dir_fid,
		client_fname,
		access_mask,
		share_access,
		create_disposition,
		create_options,
		file_attributes,
		oplock_request,
		lease,
		allocation_size,
		private_flags,
		sd,
		ea_list,
		result_fsp,
		pinfo,
		in_context_blobs,
		out_context_blobs);
err:
	TALLOC_FREE(client_fname);
	DEBUG(10, ("Leaving with smb_fname->base_name '%s'"
		   "smb_fname->st.st_ex_mtime %s"
		   " fsp->fsp_name->st.st_ex_mtime %s",
		   smb_fname->base_name,
		   ctime(&(smb_fname->st.st_ex_mtime.tv_sec)),
		   (*result_fsp) && VALID_STAT((*result_fsp)->fsp_name->st) ?
		   ctime(&((*result_fsp)->fsp_name->st.st_ex_mtime.tv_sec)) :
		   "No fsp time\n"));
	return status;
}

static int um_rename(vfs_handle_struct *handle,
		     const struct smb_filename *smb_fname_src,
		     const struct smb_filename *smb_fname_dst)
{
	int status;
	struct smb_filename *src_client_fname = NULL;
	struct smb_filename *dst_client_fname = NULL;

	DEBUG(10, ("Entering with "
		   "smb_fname_src->base_name '%s', "
		   "smb_fname_dst->base_name '%s'\n",
		   smb_fname_src->base_name,
		   smb_fname_dst->base_name));

	if (!is_in_media_files(smb_fname_src->base_name)
	    &&
	    !is_in_media_files(smb_fname_dst->base_name)) {
		return SMB_VFS_NEXT_RENAME(handle, smb_fname_src,
					   smb_fname_dst);
	}

	status = alloc_get_client_smb_fname(handle, talloc_tos(),
					    smb_fname_src,
					    &src_client_fname);
	if (status != 0) {
		goto err;
	}

	status = alloc_get_client_smb_fname(handle, talloc_tos(),
					    smb_fname_dst,
					    &dst_client_fname);

	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_RENAME(handle, src_client_fname,
				     dst_client_fname);
err:
	TALLOC_FREE(dst_client_fname);
	TALLOC_FREE(src_client_fname);
	DEBUG(10, ("Leaving with smb_fname_src->base_name '%s',"
		   " smb_fname_dst->base_name '%s'\n",
		   smb_fname_src->base_name,
		   smb_fname_dst->base_name));
	return status;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int um_stat(vfs_handle_struct *handle,
		   struct smb_filename *smb_fname)
{
	int status = 0;
	struct smb_filename *client_fname = NULL;

	DEBUG(10, ("Entering with smb_fname->base_name '%s'\n",
		   smb_fname->base_name));

	if (!is_in_media_files(smb_fname->base_name)) {
		return SMB_VFS_NEXT_STAT(handle, smb_fname);
	}

	status = alloc_get_client_smb_fname(handle, talloc_tos(),
					    smb_fname,
					    &client_fname);
	if (status != 0) {
		goto err;
	}
	DEBUG(10, ("Stat'ing client_fname->base_name '%s'\n",
		   client_fname->base_name));

	status = SMB_VFS_NEXT_STAT(handle, client_fname);
	if (status != 0) {
		goto err;
	}

	/*
	 * Unlike functions with const smb_filename, we have to modify
	 * smb_fname itself to pass our info back up.
	 */
	DEBUG(10, ("Setting smb_fname '%s' stat from client_fname '%s'\n",
		   smb_fname->base_name, client_fname->base_name));
	smb_fname->st = client_fname->st;

err:
	TALLOC_FREE(client_fname);
	DEBUG(10, ("Leaving with smb_fname->st.st_ex_mtime %s",
		   ctime(&(smb_fname->st.st_ex_mtime.tv_sec))));
	return status;
}

static int um_lstat(vfs_handle_struct *handle,
		    struct smb_filename *smb_fname)
{
	int status = 0;
	struct smb_filename *client_fname = NULL;

	DEBUG(10, ("Entering with smb_fname->base_name '%s'\n",
		   smb_fname->base_name));

	if (!is_in_media_files(smb_fname->base_name)) {
		return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
	}

	client_fname = NULL;

	status = alloc_get_client_smb_fname(handle, talloc_tos(),
					    smb_fname,
					    &client_fname);
	if (status != 0) {
		goto err;
	}
	status = SMB_VFS_NEXT_LSTAT(handle, client_fname);
	if (status != 0) {
		goto err;
	}

	smb_fname->st = client_fname->st;

err:
	TALLOC_FREE(client_fname);
	DEBUG(10, ("Leaving with smb_fname->st.st_ex_mtime %s",
		   ctime(&(smb_fname->st.st_ex_mtime.tv_sec))));
	return status;
}

static int um_fstat(vfs_handle_struct *handle,
		    files_struct *fsp, SMB_STRUCT_STAT *sbuf)
{
	int status = 0;

	DEBUG(10, ("Entering with fsp->fsp_name->base_name "
		   "'%s'\n", fsp_str_dbg(fsp)));

	status = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
	if (status != 0) {
		goto out;
	}

	if ((fsp->fsp_name == NULL) ||
	    !is_in_media_files(fsp->fsp_name->base_name)) {
		goto out;
	}

	status = um_stat(handle, fsp->fsp_name);
	if (status != 0) {
		goto out;
	}

	*sbuf = fsp->fsp_name->st;

out:
	DEBUG(10, ("Leaving with fsp->fsp_name->st.st_ex_mtime %s\n",
		   fsp->fsp_name != NULL ?
		   ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec)) : "0"));
	return status;
}

static int um_unlink(vfs_handle_struct *handle,
		     const struct smb_filename *smb_fname)
{
	int status;
	struct smb_filename *client_fname = NULL;

	DEBUG(10, ("Entering um_unlink\n"));

	if (!is_in_media_files(smb_fname->base_name)) {
		return SMB_VFS_NEXT_UNLINK(handle, smb_fname);
	}

	status = alloc_get_client_smb_fname(handle, talloc_tos(),
					    smb_fname,
					    &client_fname);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_UNLINK(handle, client_fname);

err:
	TALLOC_FREE(client_fname);
	return status;
}

static int um_chmod(vfs_handle_struct *handle,
		    const char *path,
		    mode_t mode)
{
	int status;
	char *client_path = NULL;

	DEBUG(10, ("Entering um_chmod\n"));

	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_CHMOD(handle, path, mode);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       path, &client_path);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_CHMOD(handle, client_path, mode);

err:
	TALLOC_FREE(client_path);
	return status;
}

static int um_chown(vfs_handle_struct *handle,
		    const char *path,
		    uid_t uid,
		    gid_t gid)
{
	int status;
	char *client_path = NULL;

	DEBUG(10, ("Entering um_chown\n"));

	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_CHOWN(handle, path, uid, gid);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       path, &client_path);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_CHOWN(handle, client_path, uid, gid);

err:
	TALLOC_FREE(client_path);
	return status;
}

static int um_lchown(vfs_handle_struct *handle,
		     const char *path,
		     uid_t uid,
		     gid_t gid)
{
	int status;
	char *client_path = NULL;

	DEBUG(10, ("Entering um_lchown\n"));
	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_LCHOWN(handle, path, uid, gid);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       path, &client_path);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_LCHOWN(handle, client_path, uid, gid);

err:
	TALLOC_FREE(client_path);
	return status;
}

static int um_chdir(vfs_handle_struct *handle,
		    const char *path)
{
	int status;
	char *client_path = NULL;

	DEBUG(10, ("Entering um_chdir\n"));

	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_CHDIR(handle, path);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       path, &client_path);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_CHDIR(handle, client_path);

err:
	TALLOC_FREE(client_path);
	return status;
}

static int um_ntimes(vfs_handle_struct *handle,
		     const struct smb_filename *smb_fname,
		     struct smb_file_time *ft)
{
	int status;
	struct smb_filename *client_fname = NULL;

	DEBUG(10, ("Entering um_ntimes\n"));

	if (!is_in_media_files(smb_fname->base_name)) {
		return SMB_VFS_NEXT_NTIMES(handle, smb_fname, ft);
	}

	status = alloc_get_client_smb_fname(handle, talloc_tos(),
					    smb_fname, &client_fname);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_NTIMES(handle, client_fname, ft);

err:
	TALLOC_FREE(client_fname);
	return status;
}

static int um_symlink(vfs_handle_struct *handle,
		      const char *oldpath,
		      const char *newpath)
{
	int status;
	char *old_client_path = NULL;
	char *new_client_path = NULL;

	DEBUG(10, ("Entering um_symlink\n"));

	if (!is_in_media_files(oldpath) && !is_in_media_files(newpath)) {
		return SMB_VFS_NEXT_SYMLINK(handle, oldpath, newpath);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       oldpath, &old_client_path);
	if (status != 0) {
		goto err;
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       newpath, &new_client_path);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_SYMLINK(handle,
				      old_client_path,
				      new_client_path);

err:
	TALLOC_FREE(new_client_path);
	TALLOC_FREE(old_client_path);
	return status;
}

static int um_readlink(vfs_handle_struct *handle,
		       const char *path,
		       char *buf,
		       size_t bufsiz)
{
	int status;
	char *client_path = NULL;

	DEBUG(10, ("Entering um_readlink\n"));

	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_READLINK(handle, path, buf, bufsiz);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       path, &client_path);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_READLINK(handle, client_path, buf, bufsiz);

err:
	TALLOC_FREE(client_path);
	return status;
}

static int um_link(vfs_handle_struct *handle,
		   const char *oldpath,
		   const char *newpath)
{
	int status;
	char *old_client_path = NULL;
	char *new_client_path = NULL;

	DEBUG(10, ("Entering um_link\n"));
	if (!is_in_media_files(oldpath) && !is_in_media_files(newpath)) {
		return SMB_VFS_NEXT_LINK(handle, oldpath, newpath);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       oldpath, &old_client_path);
	if (status != 0) {
		goto err;
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       newpath, &new_client_path);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_LINK(handle, old_client_path, new_client_path);

err:
	TALLOC_FREE(new_client_path);
	TALLOC_FREE(old_client_path);
	return status;
}

static int um_mknod(vfs_handle_struct *handle,
		    const char *pathname,
		    mode_t mode,
		    SMB_DEV_T dev)
{
	int status;
	char *client_path = NULL;

	DEBUG(10, ("Entering um_mknod\n"));
	if (!is_in_media_files(pathname)) {
		return SMB_VFS_NEXT_MKNOD(handle, pathname, mode, dev);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       pathname, &client_path);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_MKNOD(handle, client_path, mode, dev);

err:
	TALLOC_FREE(client_path);
	return status;
}

static char *um_realpath(vfs_handle_struct *handle,
			 const char *path)
{
	char *buf = NULL;
	char *client_path = NULL;
	int status;

	DEBUG(10, ("Entering um_realpath\n"));

	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_REALPATH(handle, path);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       path, &client_path);
	if (status != 0) {
		goto err;
	}

	buf = SMB_VFS_NEXT_REALPATH(handle, client_path);

err:
	TALLOC_FREE(client_path);
	return buf;
}

static int um_chflags(vfs_handle_struct *handle,
		      const char *path,
		      unsigned int flags)
{
	int status;
	char *client_path = NULL;

	DEBUG(10, ("Entering um_chflags\n"));

	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_CHFLAGS(handle, path, flags);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       path, &client_path);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_CHFLAGS(handle, client_path, flags);
err:
	TALLOC_FREE(client_path);
	return status;
}

static NTSTATUS um_streaminfo(struct vfs_handle_struct *handle,
			      struct files_struct *fsp,
			      const char *fname,
			      TALLOC_CTX *ctx,
			      unsigned int *num_streams,
			      struct stream_struct **streams)
{
	NTSTATUS status;
	char *client_path = NULL;
	int ret;

	DEBUG(10, ("Entering um_streaminfo\n"));

	if (!is_in_media_files(fname)) {
		return SMB_VFS_NEXT_STREAMINFO(handle, fsp, fname,
					       ctx, num_streams, streams);
	}

	ret = alloc_get_client_path(handle, talloc_tos(),
				    fname, &client_path);
	if (ret != 0) {
		status = map_nt_error_from_unix(errno);
		goto err;
	}

	/*
	 * This only works on files, so we don't have to worry about
	 * our fake directory stat'ing here.  But what does this
	 * function do, exactly?  Does it need extra modifications for
	 * the Avid stuff?
	 */
	status = SMB_VFS_NEXT_STREAMINFO(handle, fsp, client_path,
					 ctx, num_streams, streams);
err:
	TALLOC_FREE(client_path);
	return status;
}

/*
 * Ignoring get_real_filename function because the default doesn't do
 * anything.
 */

static NTSTATUS um_get_nt_acl(vfs_handle_struct *handle,
			      const char *name,
			      uint32_t security_info,
			      TALLOC_CTX *mem_ctx,
			      struct security_descriptor **ppdesc)
{
	NTSTATUS status;
	char *client_path = NULL;
	int ret;

	DEBUG(10, ("Entering um_get_nt_acl\n"));

	if (!is_in_media_files(name)) {
		return SMB_VFS_NEXT_GET_NT_ACL(handle, name,
					       security_info,
					       mem_ctx, ppdesc);
	}

	ret = alloc_get_client_path(handle, talloc_tos(),
				    name, &client_path);
	if (ret != 0) {
		status = map_nt_error_from_unix(errno);
		goto err;
	}

	status = SMB_VFS_NEXT_GET_NT_ACL(handle, client_path,
					 security_info,
					 mem_ctx, ppdesc);
err:
	TALLOC_FREE(client_path);
	return status;
}

static int um_chmod_acl(vfs_handle_struct *handle,
			const char *path,
			mode_t mode)
{
	int status;
	char *client_path = NULL;

	DEBUG(10, ("Entering um_chmod_acl\n"));

	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_CHMOD_ACL(handle, path, mode);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       path, &client_path);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_CHMOD_ACL(handle, client_path, mode);

err:
	TALLOC_FREE(client_path);
	return status;
}

static SMB_ACL_T um_sys_acl_get_file(vfs_handle_struct *handle,
				     const char *path_p,
				     SMB_ACL_TYPE_T type,
				     TALLOC_CTX *mem_ctx)
{
	SMB_ACL_T ret;
	char *client_path = NULL;
	int status;

	DEBUG(10, ("Entering um_sys_acl_get_file\n"));

	if (!is_in_media_files(path_p)) {
		return SMB_VFS_NEXT_SYS_ACL_GET_FILE(handle, path_p,
						     type, mem_ctx);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       path_p, &client_path);
	if (status != 0) {
		ret = NULL;
		goto err;
	}

	ret = SMB_VFS_NEXT_SYS_ACL_GET_FILE(handle, client_path, type, mem_ctx);

err:
	TALLOC_FREE(client_path);
	return ret;
}

static int um_sys_acl_set_file(vfs_handle_struct *handle,
			       const char *name,
			       SMB_ACL_TYPE_T acltype,
			       SMB_ACL_T theacl)
{
	int status;
	char *client_path = NULL;

	DEBUG(10, ("Entering um_sys_acl_set_file\n"));

	if (!is_in_media_files(name)) {
		return SMB_VFS_NEXT_SYS_ACL_SET_FILE(handle, name,
						     acltype, theacl);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       name, &client_path);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_SYS_ACL_SET_FILE(handle, client_path,
					       acltype, theacl);

err:
	TALLOC_FREE(client_path);
	return status;
}

static int um_sys_acl_delete_def_file(vfs_handle_struct *handle,
				      const char *path)
{
	int status;
	char *client_path = NULL;

	DEBUG(10, ("Entering um_sys_acl_delete_def_file\n"));

	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_SYS_ACL_DELETE_DEF_FILE(handle, path);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
					    path, &client_path);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_SYS_ACL_DELETE_DEF_FILE(handle, client_path);

err:
	TALLOC_FREE(client_path);
	return status;
}

static ssize_t um_getxattr(struct vfs_handle_struct *handle,
			   const char *path,
			   const char *name,
			   void *value,
			   size_t size)
{
	ssize_t ret;
	char *client_path = NULL;
	int status;

	DEBUG(10, ("Entering um_getxattr\n"));
	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_GETXATTR(handle, path, name, value, size);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       path, &client_path);
	if (status != 0) {
		ret = -1;
		goto err;
	}

	ret = SMB_VFS_NEXT_GETXATTR(handle, client_path, name, value, size);
err:
	TALLOC_FREE(client_path);
	return ret;
}

static ssize_t um_listxattr(struct vfs_handle_struct *handle,
			    const char *path,
			    char *list,
			    size_t size)
{
	ssize_t ret;
	char *client_path = NULL;
	int status;

	DEBUG(10, ("Entering um_listxattr\n"));

	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_LISTXATTR(handle, path, list, size);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       path, &client_path);
	if (status != 0) {
		ret = -1;
		goto err;
	}

	ret = SMB_VFS_NEXT_LISTXATTR(handle, client_path, list, size);

err:
	TALLOC_FREE(client_path);
	return ret;
}

static int um_removexattr(struct vfs_handle_struct *handle,
			  const char *path,
			  const char *name)
{
	int status;
	char *client_path = NULL;

	DEBUG(10, ("Entering um_removexattr\n"));

	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_REMOVEXATTR(handle, path, name);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       path, &client_path);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_REMOVEXATTR(handle, client_path, name);

err:
	TALLOC_FREE(client_path);
	return status;
}

static int um_setxattr(struct vfs_handle_struct *handle,
		       const char *path,
		       const char *name,
		       const void *value,
		       size_t size,
		       int flags)
{
	int status;
	char *client_path = NULL;

	DEBUG(10, ("Entering um_setxattr\n"));

	if (!is_in_media_files(path)) {
		return SMB_VFS_NEXT_SETXATTR(handle, path, name, value,
					     size, flags);
	}

	status = alloc_get_client_path(handle, talloc_tos(),
				       path, &client_path);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_SETXATTR(handle, client_path, name, value,
				       size, flags);

err:
	TALLOC_FREE(client_path);
	return status;
}

static bool um_is_offline(struct vfs_handle_struct *handle,
			  const struct smb_filename *fname,
			  SMB_STRUCT_STAT *sbuf)
{
	bool ret;
	struct smb_filename *client_fname = NULL;
	int status;

	DEBUG(10, ("Entering um_is_offline\n"));

	if (!is_in_media_files(fname->base_name)) {
		return SMB_VFS_NEXT_IS_OFFLINE(handle, fname, sbuf);
	}

	status = alloc_get_client_smb_fname(handle, talloc_tos(),
					    fname, &client_fname);
	if (status != 0) {
		ret = false;
		goto err;
	}

	ret = SMB_VFS_NEXT_IS_OFFLINE(handle, client_fname, sbuf);

err:
	TALLOC_FREE(client_fname);
	return ret;
}

static int um_set_offline(struct vfs_handle_struct *handle,
			  const struct smb_filename *fname)
{
	int status;
	struct smb_filename *client_fname = NULL;

	DEBUG(10, ("Entering um_set_offline\n"));

	if (!is_in_media_files(fname->base_name)) {
		return SMB_VFS_NEXT_SET_OFFLINE(handle, fname);
	}

	status = alloc_get_client_smb_fname(handle, talloc_tos(),
					    fname, &client_fname);
	if (status != 0) {
		goto err;
	}

	status = SMB_VFS_NEXT_SET_OFFLINE(handle, client_fname);

err:
	TALLOC_FREE(client_fname);
	return status;
}

static int um_connect(vfs_handle_struct *handle,
			 const char *service,
			 const char *user)
{
	int rc;
	struct um_config_data *config;
	int enumval;

	rc = SMB_VFS_NEXT_CONNECT(handle, service, user);
	if (rc != 0) {
		return rc;
	}

	config = talloc_zero(handle->conn, struct um_config_data);
	if (!config) {
		DEBUG(1, ("talloc_zero() failed\n"));
		errno = ENOMEM;
		return -1;
	}

	enumval = lp_parm_enum(SNUM(handle->conn), UM_PARAM_TYPE_NAME,
			       "clientid", um_clientid, UM_CLIENTID_NAME);
	if (enumval == -1) {
		DEBUG(1, ("value for %s: type unknown\n",
			  UM_PARAM_TYPE_NAME));
		return -1;
	}
	config->clientid = (enum um_clientid)enumval;

	SMB_VFS_HANDLE_SET_DATA(handle, config,
				NULL, struct um_config_data,
				return -1);

	return 0;
}

/* VFS operations structure */

static struct vfs_fn_pointers vfs_um_fns = {
	.connect_fn = um_connect,

	/* Disk operations */

	.statvfs_fn = um_statvfs,

	/* Directory operations */

	.opendir_fn = um_opendir,
	.fdopendir_fn = um_fdopendir,
	.readdir_fn = um_readdir,
	.seekdir_fn = um_seekdir,
	.telldir_fn = um_telldir,
	.rewind_dir_fn = um_rewinddir,
	.mkdir_fn = um_mkdir,
	.rmdir_fn = um_rmdir,
	.closedir_fn = um_closedir,
	.init_search_op_fn = um_init_search_op,

	/* File operations */

	.open_fn = um_open,
	.create_file_fn = um_create_file,
	.rename_fn = um_rename,
	.stat_fn = um_stat,
	.lstat_fn = um_lstat,
	.fstat_fn = um_fstat,
	.unlink_fn = um_unlink,
	.chmod_fn = um_chmod,
	.chown_fn = um_chown,
	.lchown_fn = um_lchown,
	.chdir_fn = um_chdir,
	.ntimes_fn = um_ntimes,
	.symlink_fn = um_symlink,
	.readlink_fn = um_readlink,
	.link_fn = um_link,
	.mknod_fn = um_mknod,
	.realpath_fn = um_realpath,
	.chflags_fn = um_chflags,
	.streaminfo_fn = um_streaminfo,

	/* NT ACL operations. */

	.get_nt_acl_fn = um_get_nt_acl,

	/* POSIX ACL operations. */

	.chmod_acl_fn = um_chmod_acl,

	.sys_acl_get_file_fn = um_sys_acl_get_file,
	.sys_acl_set_file_fn = um_sys_acl_set_file,
	.sys_acl_delete_def_file_fn = um_sys_acl_delete_def_file,

	/* EA operations. */
	.getxattr_fn = um_getxattr,
	.listxattr_fn = um_listxattr,
	.removexattr_fn = um_removexattr,
	.setxattr_fn = um_setxattr,

	/* aio operations */

	/* offline operations */
	.is_offline_fn = um_is_offline,
	.set_offline_fn = um_set_offline
};

NTSTATUS vfs_unityed_media_init(void);
NTSTATUS vfs_unityed_media_init(void)
{
	NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
					"unityed_media", &vfs_um_fns);
	if (!NT_STATUS_IS_OK(ret)) {
		return ret;
	}

	vfs_um_debug_level = debug_add_class("unityed_media");

	if (vfs_um_debug_level == -1) {
		vfs_um_debug_level = DBGC_VFS;
		DEBUG(1, ("unityed_media_init: Couldn't register custom "
			  "debugging class.\n"));
	}

	return ret;
}
