/*
 * $Id: media_harmony.c,v 1.1 2007/11/06 10:07:22 stuart_hc Exp $
 *
 * 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>
 *
 * 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.
 */


/*
 * Media Harmony is a Samba VFS module that allows multiple AVID
 * clients to share media. Each client sees their own copy of the
 * AVID msmMMOB.mdb and msmFMID.pmr files and Creating directories.
 *
 * Add this module to the vfs objects option in your Samba share
 * configuration.
 * eg.
 *
 *   [avid_win]
 *	path = /video
 *	vfs objects = media_harmony
 *	...
 *
 * 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.
 *
 *
 * Andrew Klaassen, 2012-03-14
 * To prevent Avid clients from interrupting each other (via Avid's habit
 * of launching a database refresh whenever it notices an mtime update
 * on media directories, i.e. whenever one editor adds new material to a
 * shared share), I've added code that causes stat information for anything
 * directly under "Avid MediaFile/MXF" to be taken from
 * dirname_clientaddr_clientuser if it exists.  These files ~aren't~
 * hidden, unlike the client-suffixed database files.
 *
 * For example, stat information for
 *	Avid MediaFiles/MXF/1
 * will come from
 *	Avid MediaFiles/MXF/1_192.168.1.10_dave
 * for dave working on 192.168.1.10, but will come from
 *	Avid MediaFile/MXF/1_192.168.1.11_susan
 * for susan working on 192.168.1.11.  If those alternate
 * directories don't exist, the user will get the actual directory's stat
 * info.  When an editor wants to force a database refresh, they update
 * the mtime on "their" file.  This will cause Avid
 * on that client to see an updated mtime for "Avid MediaFiles/MXF/1",
 * which will trigger an Avid database refresh just for that editor.
 *
 *
 * 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. For example: it is possibly to open client specific
 * files such as msmMMOB.mdb_192.168.1.10_userx even though is doesn't
 * show up in a directory listing.
 *
 */


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

#define MH_INFO_DEBUG 10
#define MH_ERR_DEBUG 0

static const char* MDB_FILENAME = "msmMMOB.mdb";
static const size_t MDB_FILENAME_LEN = 11;
static const char* PMR_FILENAME = "msmFMID.pmr";
static const size_t PMR_FILENAME_LEN = 11;
static const char* CREATING_DIRNAME = "Creating";
static const size_t CREATING_DIRNAME_LEN = 8;
static const char* AVID_MEDIAFILES_DIRNAME = "Avid MediaFiles";
static const size_t AVID_MEDIAFILES_DIRNAME_LEN = 15;
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 const char* AVID_MXF_DIRNAME = "Avid MediaFiles/MXF";
static const size_t AVID_MXF_DIRNAME_LEN = 19;

static int vfs_mh_debug_level = DBGC_VFS;

/* supplements the directory list stream */
typedef struct mh_dirinfo_struct
{
	DIR* dirstream;
	char *dirpath;
	char *clientPath;
	bool isInMediaFiles;
	char *clientMDBFilename;
	char *clientPMRFilename;
	char *clientCreatingDirname;
} mh_dirinfo_struct;


/* Add "_<ip address>_<user name>" 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;
	char *raddr = NULL;

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

	raddr = tsocket_address_inet_addr_string(
			handle->conn->sconn->remote_address, talloc_tos());
	if (raddr == NULL)
	{
		errno = ENOMEM;
		status = -1;
		goto err;
	}

	/* talloc_asprintf_append uses talloc_realloc, which
	 * frees original 'path' memory so we don't have to.
	 */
	*path = talloc_asprintf_append(*path, "_%s_%s",
		raddr,
		handle->conn->session_info->unix_info->sanitized_username);
	if (*path == NULL)
	{
		DEBUG(MH_ERR_DEBUG, ("alloc_append_client_suffix "
					"out of memory\n"));
		errno = ENOMEM;
		status = -1;
		goto err;
	}
	DEBUG(MH_INFO_DEBUG, ("Leaving with *path '%s'\n", *path));
err:
	TALLOC_FREE(raddr);
	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(MH_INFO_DEBUG, ("Entering with fname '%s'\n", fname));

	if (strncmp(APPLE_DOUBLE_PREFIX, fname, APPLE_DOUBLE_PREFIX_LEN)
			== 0)
	{
		ret = True;
	}
	DEBUG(MH_INFO_DEBUG, ("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;

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

	/* Sometimes Samba gives us "./OMFI MediaFiles". */
	if (strncmp(path, "./", 2) == 0)
	{
		path_start = &path[2];
	}
	else {
		path_start = path;
	}

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

	DEBUG(MH_INFO_DEBUG, ("Leaving with ret '%s'\n",
				ret == True ? "True" : "False"));
	return ret;
}

/*
 * 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(MH_INFO_DEBUG, ("Entering with path '%s'\n", path));

	if (
		starts_with_media_dir(AVID_MEDIAFILES_DIRNAME,
				AVID_MEDIAFILES_DIRNAME_LEN, path)
		||
		starts_with_media_dir(OMFI_MEDIAFILES_DIRNAME,
				OMFI_MEDIAFILES_DIRNAME_LEN, path)
	)
	{
		ret = True;
	}
	DEBUG(MH_INFO_DEBUG, ("Leaving with ret '%s'\n",
				ret == True ? "True" : "False"));
	return ret;
}

/*
 * Returns depth of path under media directory.  Deals with the
 * occasional ..../. and ..../.. paths that get passed to stat.
 *
 * Assumes is_in_media_files has already been called and has returned
 * true for the path; if it hasn't, this function will likely crash
 * and burn.
 *
 * Not foolproof; something like "Avid MediaFiles/MXF/../foo/1"
 * would fool it.  Haven't seen paths like that getting to the
 * stat function yet, so ignoring that possibility for now.
 */
static int depth_from_media_dir(const char* media_dirname,
		size_t media_dirname_len, const char* path)
{
	int transition_count = 0;
	const char *path_start;
	const char *pathPtr;

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

	/* Sometimes Samba gives us "./OMFI MediaFiles". */
	if (strncmp(path, "./", 2) == 0)
	{
		path_start = &path[2];
	}
	else {
		path_start = path;
	}

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

	pathPtr = &path_start[media_dirname_len + 1];

	while(1)
	{
		if (*pathPtr == '\0' || *pathPtr == '/')
		{
			if (
				*(pathPtr - 1) == '.'
					&&
				*(pathPtr - 2) == '.'
					&&
				*(pathPtr - 3) == '/'
			)
			{
				transition_count--;
			}
			else if (
				!
				(
					*(pathPtr - 1) == '/'
					||
					(
						*(pathPtr - 1) == '.'
							&&
						*(pathPtr - 2) == '/'
					)
				)
			)
			{
				transition_count++;
			}
		}
		if (*pathPtr == '\0')
		{
			break;
		}
		pathPtr++;
	}

	DEBUG(MH_INFO_DEBUG, ("Leaving with transition_count '%i'\n",
				transition_count));
out:
	return transition_count;
}

/* Identifies MDB and PMR files at end of path. */
static bool is_avid_database(
		char *path,
		size_t path_len,
		const char *avid_db_filename,
		const size_t avid_db_filename_len)
{
	bool ret = False;

	DEBUG(MH_INFO_DEBUG, ("Entering with path '%s', "
			      "avid_db_filename '%s', "
			      "path_len '%i', "
			      "avid_db_filename_len '%i'\n",
			      path, avid_db_filename,
			      (int)path_len, (int)avid_db_filename_len));

	if (
		path_len > avid_db_filename_len
			&&
		strcmp(&path[path_len - avid_db_filename_len],
				avid_db_filename) == 0
			&&
		(
			path[path_len - avid_db_filename_len - 1] == '/'
			||
			(path_len > avid_db_filename_len
				+ APPLE_DOUBLE_PREFIX_LEN
				&&
			path[path_len - avid_db_filename_len
				- APPLE_DOUBLE_PREFIX_LEN - 1] == '/'
				&&
			is_apple_double(&path[path_len
				- avid_db_filename_len
				- APPLE_DOUBLE_PREFIX_LEN]))
		)
	)
	{
		ret = True;
	}
	DEBUG(MH_INFO_DEBUG, ("Leaving with ret '%s'\n",
				ret == True ? "True" : "False"));
	return ret;
}


/* Add client suffix to paths to MDB_FILENAME, PMR_FILENAME and
 * CREATING_SUBDIRNAME.
 *
 * 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,
		char **newPath)
{
	/* replace /CREATING_DIRNAME/ or /._CREATING_DIRNAME/
	 * directory in path - potentially in middle of path
	 * - with suffixed name.
	 */
	int status = 0;
	char* pathPtr;
	size_t intermPathLen;

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

	*newPath = talloc_strdup(ctx, path);
	if (*newPath == NULL)
	{
		DEBUG(MH_ERR_DEBUG, ("alloc_get_client_path ENOMEM #1\n"));
		errno = ENOMEM;
		status = -1;
		goto out;
	}
	DEBUG(MH_INFO_DEBUG, ("newPath #1 %s\n", *newPath));
	if (
		(pathPtr = strstr(path, CREATING_DIRNAME)) != NULL
			&&
		(
			*(pathPtr + CREATING_DIRNAME_LEN) == '\0'
			||
			*(pathPtr + CREATING_DIRNAME_LEN) == '/'
		)
			&&
		(
			(pathPtr - path > 0
				&&
			 *(pathPtr - 1) == '/')
			||
			(pathPtr - path > APPLE_DOUBLE_PREFIX_LEN
				&&
			*(pathPtr - APPLE_DOUBLE_PREFIX_LEN - 1) == '/'
				&&
			 is_apple_double(pathPtr - APPLE_DOUBLE_PREFIX_LEN))
		)
	)
	{
		/* Insert client suffix into path. */
		(*newPath)[pathPtr - path + CREATING_DIRNAME_LEN] = '\0';
		DEBUG(MH_INFO_DEBUG, ("newPath #2 %s\n", *newPath));

		if ((status = alloc_append_client_suffix(handle, newPath)))
		{
			goto out;
		}

		DEBUG(MH_INFO_DEBUG, ("newPath #3 %s\n", *newPath));
		*newPath = talloc_strdup_append(*newPath,
				pathPtr + CREATING_DIRNAME_LEN);
		if (*newPath == NULL)
		{
			DEBUG(MH_ERR_DEBUG, ("alloc_get_client_path "
						"ENOMEM #2\n"));
			errno = ENOMEM;
			status = -1;
			goto out;
		}
		DEBUG(MH_INFO_DEBUG, ("newPath #4 %s\n", *newPath));
	}

	/* replace /MDB_FILENAME or /PMR_FILENAME or /._MDB_FILENAME
	 * or /._PMR_FILENAME at newPath end with suffixed name.
	 */
	intermPathLen = strlen(*newPath);
	if (
		is_avid_database(*newPath, intermPathLen,
			MDB_FILENAME, MDB_FILENAME_LEN)
		||
		is_avid_database(*newPath, intermPathLen,
			PMR_FILENAME, PMR_FILENAME_LEN)
	)
	{
		DEBUG(MH_INFO_DEBUG, ("newPath #5 %s\n", *newPath));
		if ((status = alloc_append_client_suffix(handle, newPath)))
		{
			goto out;
		}
		DEBUG(MH_INFO_DEBUG, ("newPath #6 %s\n", *newPath));
	}
out:
	/* newPath must be freed in caller. */
	DEBUG(MH_INFO_DEBUG, ("Leaving with *newPath '%s'\n", *newPath));
	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 **clientFname)
{
	int status = 0;

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

	*clientFname = cp_smb_filename(ctx, smb_fname);
	if ((*clientFname) == NULL) {
		DEBUG(MH_ERR_DEBUG, ("alloc_get_client_smb_fname "
					"NTERR\n"));
		errno = ENOMEM;
		status = -1;
		goto err;
	}
	if ((status = alloc_get_client_path(handle, ctx,
				smb_fname->base_name,
				&(*clientFname)->base_name)))
	{
		goto err;
	}
	DEBUG(MH_INFO_DEBUG, ("Leaving with (*clientFname)->base_name "
				"'%s'\n", (*clientFname)->base_name));
err:
	return status;
}


/*
 * 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 *avid_db_filename)
{
	int status = 0;

	DEBUG(MH_INFO_DEBUG, ("Entering with avid_db_filename '%s'\n",
			      avid_db_filename));

	if ((*path = talloc_strdup(ctx, avid_db_filename)) == NULL)
	{
		DEBUG(MH_ERR_DEBUG, ("alloc_set_client_dirinfo_path "
					"ENOMEM\n"));
		errno = ENOMEM;
		status = -1;
		goto err;
	}
	if ((status = alloc_append_client_suffix(handle, path)))
	{
		goto err;
	}
	DEBUG(MH_INFO_DEBUG, ("Leaving with *path '%s'\n", *path));
err:
	return status;
}

/*
 * Replace mtime on clientFname with mtime from client-suffixed
 * equivalent, if it exists.
 *
 * Success: return 0
 * Failure: set errno, return -1
 */
static int set_fake_mtime(vfs_handle_struct *handle,
		TALLOC_CTX *ctx,
		struct smb_filename **clientFname,
		int (*statFn)(const char *, SMB_STRUCT_STAT *, bool))
{
	int status = 0;
	char *statPath;
	SMB_STRUCT_STAT fakeStat;
	int copy_len;

	DEBUG(MH_INFO_DEBUG, ("Entering with (*clientFname)->base_name "
			      "'%s', (*clientFname)->st.st_ex_mtime %s",
			      (*clientFname)->base_name,
			      ctime(&((*clientFname)->st.st_ex_mtime.tv_sec))));

	if (
		depth_from_media_dir(AVID_MXF_DIRNAME,
				AVID_MXF_DIRNAME_LEN,
				(*clientFname)->base_name)
			!= 1
			&&
		depth_from_media_dir(OMFI_MEDIAFILES_DIRNAME,
				OMFI_MEDIAFILES_DIRNAME_LEN,
				(*clientFname)->base_name)
			!= 0
	)
	{
		goto out;
	}

	copy_len = strlen((*clientFname)->base_name);

	/* Hack to deal with occasional "Avid MediaFiles/MXF/1/." paths.
	 * We know we're under a media dir, so paths are at least 2 chars
	 * long.
	 */
	if ((*clientFname)->base_name[copy_len - 1] == '.' &&
			(*clientFname)->base_name[copy_len - 2] == '/')
	{
		copy_len -= 2;
	}

	if (((statPath = talloc_strndup(ctx,
			(*clientFname)->base_name, copy_len)) == NULL))
	{
		errno = ENOMEM;
		status = -1;
		goto err;
	}
	if ((status = alloc_append_client_suffix(handle, &statPath)))
	{
		goto err;
	}

	DEBUG(MH_INFO_DEBUG, ("Fake stat'ing '%s'\n", statPath));
	if (statFn(statPath, &fakeStat,
			lp_fake_directory_create_times(SNUM(handle->conn))))
	{
		/* This can fail for legitimate reasons - i.e. the
		 * fakeStat directory doesn't exist, which is okay
		 * - so we don't set status.  But if it does fail,
		 * we need to skip over the mtime assignment.
		 */
		goto err;
	}

	DEBUG(MH_INFO_DEBUG, ("Setting fake mtime from '%s'\n", statPath));
	(*clientFname)->st.st_ex_mtime = fakeStat.st_ex_mtime;
err:
	TALLOC_FREE(statPath);
out:
	DEBUG(MH_INFO_DEBUG, ("Leaving with (*clientFname)->base_name "
			"'%s', (*clientFname)->st.st_ex_mtime %s",
			(*clientFname)->base_name,
			ctime(&((*clientFname)->st.st_ex_mtime.tv_sec))));
	return status;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int mh_statvfs(struct vfs_handle_struct *handle,
		const struct smb_filename *smb_fname,
		struct vfs_statvfs_struct *statbuf)
{
	int status;
	struct smb_filename *clientFname = NULL;

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

	if (!is_in_media_files(smb_fname->base_name))
	{
		status = SMB_VFS_NEXT_STATVFS(handle, smb_fname, statbuf);
		goto out;
	}

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

	status = SMB_VFS_NEXT_STATVFS(handle, clientFname, statbuf);
err:
	TALLOC_FREE(clientFname);
out:
	DEBUG(MH_INFO_DEBUG, ("Leaving with path '%s'\n",
			smb_fname->base_name));
	return status;
}

static int alloc_set_client_dirinfo(vfs_handle_struct *handle,
		const char *fname,
		struct mh_dirinfo_struct **dirInfo)
{
	int status = 0;
	char *clientPath;
	TALLOC_CTX *ctx;

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

	*dirInfo = talloc(NULL, struct mh_dirinfo_struct);
	if (*dirInfo == NULL)
	{
		goto err;
	}

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

	if (!is_in_media_files(fname))
	{
		(*dirInfo)->clientPath = NULL;
		(*dirInfo)->clientMDBFilename = NULL;
		(*dirInfo)->clientPMRFilename = NULL;
		(*dirInfo)->clientCreatingDirname = NULL;
		(*dirInfo)->isInMediaFiles = False;
		goto out;
	}

	(*dirInfo)->isInMediaFiles = True;

	if (alloc_set_client_dirinfo_path(handle,
				*dirInfo,
				&((*dirInfo)->clientMDBFilename),
				MDB_FILENAME))
	{
		goto err;
	}

	if (alloc_set_client_dirinfo_path(handle,
				*dirInfo,
				&((*dirInfo)->clientPMRFilename),
				PMR_FILENAME))
	{
		goto err;
	}

	if (alloc_set_client_dirinfo_path(handle,
				*dirInfo,
				&((*dirInfo)->clientCreatingDirname),
				CREATING_DIRNAME))
	{
		goto err;
	}

	clientPath = NULL;
	ctx = talloc_tos();

	if (alloc_get_client_path(handle, ctx,
				fname,
				&clientPath))
	{
		goto err;
	}

	(*dirInfo)->clientPath = talloc_strdup(*dirInfo, clientPath);
	if ((*dirInfo)->clientPath == NULL)
	{
		goto err;
	}

	TALLOC_FREE(clientPath);

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

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

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

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

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

	if (!dirInfo->isInMediaFiles)
	{
		dirInfo->dirstream = SMB_VFS_NEXT_OPENDIR(handle,
			smb_fname, mask, attr);
	} else {
		struct smb_filename *smb_fname_clientpath =
				synthetic_smb_fname(talloc_tos(),
					dirInfo->clientPath,
					NULL,
					NULL,
					smb_fname->flags);
		if (smb_fname_clientpath == NULL) {
			goto err;
		}

		dirInfo->dirstream = SMB_VFS_NEXT_OPENDIR(handle,
			smb_fname_clientpath, mask, attr);
		TALLOC_FREE(smb_fname_clientpath);
	}

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

	/* Success is freed in closedir. */
	DEBUG(MH_INFO_DEBUG, ("Leaving with dirInfo->dirpath '%s', "
				"dirInfo->clientPath '%s'\n",
				dirInfo->dirpath,
				dirInfo->clientPath));
	return (DIR*)dirInfo;
err:
	/* Failure is freed here. */
	DEBUG(MH_ERR_DEBUG, ("Failing with fname '%s'\n",
		smb_fname->base_name));
	TALLOC_FREE(dirInfo);
	return NULL;
}

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

	DEBUG(MH_INFO_DEBUG, ("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) {
		goto out;
	}

	if (set_fake_mtime(handle, fsp, &(fsp->fsp_name), sys_stat))
	{
		goto err;
	}

out:
	DEBUG(MH_INFO_DEBUG, ("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))));
	/* Success is freed in closedir. */
	return (DIR *) dirInfo;
err:
	/* Failure is freed here. */
	DEBUG(MH_ERR_DEBUG, ("Failing with fsp->fsp_name->base_name '%s'\n",
			fsp->fsp_name->base_name));
	TALLOC_FREE(dirInfo);
	return NULL;
}

/*
 * skip MDB_FILENAME and PMR_FILENAME filenames and CREATING_DIRNAME
 * directory, skip other client's suffixed MDB_FILENAME and PMR_FILENAME
 * filenames and CREATING_DIRNAME directory, replace this client's
 * suffixed MDB_FILENAME and PMR_FILENAME filenames and CREATING_DIRNAME
 * directory with non suffixed.
 *
 * Success: return dirent
 * End of data: return NULL
 * Failure: set errno, return NULL
 */
static struct dirent *mh_readdir(vfs_handle_struct *handle,
		DIR *dirp,
		SMB_STRUCT_STAT *sbuf)
{
	mh_dirinfo_struct* dirInfo = (mh_dirinfo_struct*)dirp;
	struct dirent *d = NULL;
	int skip;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_readdir\n"));

	DEBUG(MH_INFO_DEBUG, ("dirInfo->dirpath '%s', "
			      "dirInfo->clientPath '%s', "
			      "dirInfo->isInMediaFiles '%s', "
			      "dirInfo->clientMDBFilename '%s', "
			      "dirInfo->clientPMRFilename '%s', "
			      "dirInfo->clientCreatingDirname '%s'\n",
			      dirInfo->dirpath,
			      dirInfo->clientPath,
			      dirInfo->isInMediaFiles ? "True" : "False",
			      dirInfo->clientMDBFilename,
			      dirInfo->clientPMRFilename,
			      dirInfo->clientCreatingDirname));

	if (! dirInfo->isInMediaFiles)
	{
		d = SMB_VFS_NEXT_READDIR(handle, dirInfo->dirstream, sbuf);
		goto out;
	}

	do
	{
		const char* dname;
		bool isAppleDouble;

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

		/* skip Avid-special files with no client suffix */
		if (
			strcmp(dname, MDB_FILENAME) == 0
			||
			strcmp(dname, PMR_FILENAME) == 0
			||
			strcmp(dname, CREATING_DIRNAME) == 0
		)
		{
			skip = True;
		}
		/* chop client suffix off this client's suffixed files */
		else if (strcmp(dname, dirInfo->clientMDBFilename) == 0)
		{
			if (isAppleDouble)
			{
				d->d_name[MDB_FILENAME_LEN
					+ APPLE_DOUBLE_PREFIX_LEN] = '\0';
			}
			else
			{
				d->d_name[MDB_FILENAME_LEN] = '\0';
			}
		}
		else if (strcmp(dname, dirInfo->clientPMRFilename) == 0)
		{
			if (isAppleDouble)
			{
				d->d_name[PMR_FILENAME_LEN
					+ APPLE_DOUBLE_PREFIX_LEN] = '\0';
			}
			else
			{
				d->d_name[PMR_FILENAME_LEN] = '\0';
			}
		}
		else if (strcmp(dname, dirInfo->clientCreatingDirname)
				== 0)
		{
			if (isAppleDouble)
			{
				d->d_name[CREATING_DIRNAME_LEN
					+ APPLE_DOUBLE_PREFIX_LEN] = '\0';
			}
			else
			{
				d->d_name[CREATING_DIRNAME_LEN] = '\0';
			}
		}
		/*
		 * Anything that starts as an Avid-special file
		 * that's made it this far should be skipped.  This
		 * is different from the original behaviour, which
		 * only skipped other client's suffixed files.
		 */
		else if (
			strncmp(MDB_FILENAME, dname,
				MDB_FILENAME_LEN) == 0
			||
			strncmp(PMR_FILENAME, dname,
				PMR_FILENAME_LEN) == 0
			||
			strncmp(CREATING_DIRNAME, dname,
				CREATING_DIRNAME_LEN) == 0
		)
		{
			skip = True;
		}
	}
	while (skip);

out:
	DEBUG(MH_INFO_DEBUG, ("Leaving mh_readdir\n"));
	return d;
}

/*
 * Success: no success result defined.
 * Failure: no failure result defined.
 */
static void mh_seekdir(vfs_handle_struct *handle,
		DIR *dirp,
		long offset)
{
	DEBUG(MH_INFO_DEBUG, ("Entering and leaving mh_seekdir\n"));
	SMB_VFS_NEXT_SEEKDIR(handle,
			((mh_dirinfo_struct*)dirp)->dirstream, offset);
}

/*
 * Success: return long
 * Failure: no failure result defined.
 */
static long mh_telldir(vfs_handle_struct *handle,
		DIR *dirp)
{
	DEBUG(MH_INFO_DEBUG, ("Entering and leaving mh_telldir\n"));
	return SMB_VFS_NEXT_TELLDIR(handle,
			((mh_dirinfo_struct*)dirp)->dirstream);
}

/*
 * Success: no success result defined.
 * Failure: no failure result defined.
 */
static void mh_rewinddir(vfs_handle_struct *handle,
		DIR *dirp)
{
	DEBUG(MH_INFO_DEBUG, ("Entering and leaving mh_rewinddir\n"));
	SMB_VFS_NEXT_REWINDDIR(handle,
			((mh_dirinfo_struct*)dirp)->dirstream);
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int mh_mkdir(vfs_handle_struct *handle,
		const struct smb_filename *smb_fname,
		mode_t mode)
{
	int status;
	struct smb_filename *clientFname = NULL;
	const char *path = smb_fname->base_name;

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

	if (!is_in_media_files(path))
	{
		status = SMB_VFS_NEXT_MKDIR(handle, smb_fname, mode);
		goto out;
	}

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

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

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int mh_rmdir(vfs_handle_struct *handle,
		const struct smb_filename *smb_fname)
{
	int status;
	struct smb_filename *clientFname = NULL;
	const char *path = smb_fname->base_name;

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

	if (!is_in_media_files(path))
	{
		status = SMB_VFS_NEXT_RMDIR(handle, smb_fname);
		goto out;
	}

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

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

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int mh_closedir(vfs_handle_struct *handle,
		DIR *dirp)
{
	DIR *realdirp = ((mh_dirinfo_struct*)dirp)->dirstream;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_closedir\n"));
	// Will this talloc_free destroy realdirp?
	TALLOC_FREE(dirp);

	DEBUG(MH_INFO_DEBUG, ("Leaving mh_closedir\n"));
	return SMB_VFS_NEXT_CLOSEDIR(handle, realdirp);
}

/*
 * Success: return non-negative file descriptor
 * Failure: set errno, return -1
 */
static int mh_open(vfs_handle_struct *handle,
		struct smb_filename *smb_fname,
		files_struct *fsp,
		int flags,
		mode_t mode)
{
	int ret;
	struct smb_filename *clientFname;
	TALLOC_CTX *ctx;


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

	if (!is_in_media_files(smb_fname->base_name))
	{
		ret = SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags,
				mode);
		goto out;
	}

	clientFname = NULL;
	ctx = talloc_tos();

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

	// 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(MH_INFO_DEBUG, ("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, clientFname, fsp, flags, mode);
err:
	TALLOC_FREE(clientFname);
out:
	DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname->base_name '%s'\n",
				smb_fname->base_name));
	return ret;
}

/*
 * Success: return non-negative file descriptor
 * Failure: set errno, return -1
 */
static NTSTATUS mh_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 *clientFname;
	TALLOC_CTX *ctx;


	DEBUG(MH_INFO_DEBUG, ("Entering with smb_fname->base_name '%s'\n",
				smb_fname->base_name));
	if (!is_in_media_files(smb_fname->base_name))
	{
		status = 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);
		goto out;
	}

	clientFname = NULL;
	ctx = talloc_tos();

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

	/* 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,
		clientFname,
		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(clientFname);
out:
	DEBUG(MH_INFO_DEBUG, ("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;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int mh_rename(vfs_handle_struct *handle,
		const struct smb_filename *smb_fname_src,
		const struct smb_filename *smb_fname_dst)
{
	int status;
	struct smb_filename *srcClientFname;
	struct smb_filename *dstClientFname;
	TALLOC_CTX *ctx;


	DEBUG(MH_INFO_DEBUG, ("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))
	{
		status = SMB_VFS_NEXT_RENAME(handle, smb_fname_src,
				smb_fname_dst);
		goto out;
	}

	srcClientFname = NULL;
	dstClientFname = NULL;
	ctx = talloc_tos();

	if ((status = alloc_get_client_smb_fname(handle, ctx,
				smb_fname_src,
				&srcClientFname)))
	{
		goto err;
	}

	if ((status = alloc_get_client_smb_fname(handle, ctx,
				smb_fname_dst,
				&dstClientFname)))
	{
		goto err;
	}

	status = SMB_VFS_NEXT_RENAME(handle, srcClientFname,
				dstClientFname);
err:
	TALLOC_FREE(dstClientFname);
	TALLOC_FREE(srcClientFname);
out:
	DEBUG(MH_INFO_DEBUG, ("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 mh_stat(vfs_handle_struct *handle,
		struct smb_filename *smb_fname)
{
	int status = 0;
	struct smb_filename *clientFname;
	TALLOC_CTX *ctx;


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

	if (!is_in_media_files(smb_fname->base_name))
	{
		status = SMB_VFS_NEXT_STAT(handle, smb_fname);
		goto out;
	}

	clientFname = NULL;
	ctx = talloc_tos();

	if ((status = alloc_get_client_smb_fname(handle, ctx,
				smb_fname,
				&clientFname)))
	{
		goto err;
	}
	DEBUG(MH_INFO_DEBUG, ("Stat'ing clientFname->base_name '%s'\n",
				clientFname->base_name));
	if ((status = SMB_VFS_NEXT_STAT(handle, clientFname)))
	{
		goto err;
	}
	if ((status = set_fake_mtime(handle, ctx, &clientFname, sys_stat)))
	{
		goto err;
	}

	/* Unlike functions with const smb_filename, we have to
	 * modify smb_fname itself to pass our info back up.
	 */
	DEBUG(MH_INFO_DEBUG, ("Setting smb_fname '%s' stat "
				"from clientFname '%s'\n",
				smb_fname->base_name,
				clientFname->base_name));
	smb_fname->st = clientFname->st;
err:
	TALLOC_FREE(clientFname);
out:
	DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname->st.st_ex_mtime %s",
			ctime(&(smb_fname->st.st_ex_mtime.tv_sec))));
	return status;
}

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

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

	if (!is_in_media_files(smb_fname->base_name))
	{
		status = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
		goto out;
	}

	clientFname = NULL;
	ctx = talloc_tos();

	if ((status = alloc_get_client_smb_fname(handle, ctx,
				smb_fname,
				&clientFname)))
	{
		goto err;
	}
	if ((status = SMB_VFS_NEXT_LSTAT(handle, clientFname)))
	{
		goto err;
	}

	if ((status = set_fake_mtime(handle, ctx, &clientFname, sys_lstat)))
	{
		goto err;
	}
	/* Unlike functions with const smb_filename, we have to
	 * modify smb_fname itself to pass our info back up.
	 */
	smb_fname->st = clientFname->st;
err:
	TALLOC_FREE(clientFname);
out:
	DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname->st.st_ex_mtime %s",
			ctime(&(smb_fname->st.st_ex_mtime.tv_sec))));
	return status;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int mh_fstat(vfs_handle_struct *handle,
		files_struct *fsp, SMB_STRUCT_STAT *sbuf)
{
	int status = 0;

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

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

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

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

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

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

	DEBUG(MH_INFO_DEBUG, ("Entering mh_unlink\n"));
	if (!is_in_media_files(smb_fname->base_name))
	{
		status = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
		goto out;
	}

	clientFname = NULL;
	ctx = talloc_tos();

	if ((status = alloc_get_client_smb_fname(handle, ctx,
				smb_fname,
				&clientFname)))
	{
		goto err;
	}

	status = SMB_VFS_NEXT_UNLINK(handle, clientFname);
err:
	TALLOC_FREE(clientFname);
out:
	return status;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int mh_chmod(vfs_handle_struct *handle,
		const struct smb_filename *smb_fname,
		mode_t mode)
{
	int status;
	struct smb_filename *clientFname = NULL;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_chmod\n"));
	if (!is_in_media_files(smb_fname->base_name))
	{
		status = SMB_VFS_NEXT_CHMOD(handle, smb_fname, mode);
		goto out;
	}

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

	status = SMB_VFS_NEXT_CHMOD(handle, clientFname, mode);
err:
	TALLOC_FREE(clientFname);
out:
	return status;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int mh_chown(vfs_handle_struct *handle,
		const struct smb_filename *smb_fname,
		uid_t uid,
		gid_t gid)
{
	int status;
	struct smb_filename *clientFname = NULL;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_chown\n"));
	if (!is_in_media_files(smb_fname->base_name))
	{
		status = SMB_VFS_NEXT_CHOWN(handle, smb_fname, uid, gid);
		goto out;
	}

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

	status = SMB_VFS_NEXT_CHOWN(handle, clientFname, uid, gid);
err:
	TALLOC_FREE(clientFname);
out:
	return status;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int mh_lchown(vfs_handle_struct *handle,
		const struct smb_filename *smb_fname,
		uid_t uid,
		gid_t gid)
{
	int status;
	struct smb_filename *clientFname = NULL;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_lchown\n"));
	if (!is_in_media_files(smb_fname->base_name))
	{
		status = SMB_VFS_NEXT_LCHOWN(handle, smb_fname, uid, gid);
		goto out;
	}

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

	status = SMB_VFS_NEXT_LCHOWN(handle, clientFname, uid, gid);
err:
	TALLOC_FREE(clientFname);
out:
	return status;
}

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

	DEBUG(MH_INFO_DEBUG, ("Entering mh_chdir\n"));
	if (!is_in_media_files(smb_fname->base_name)) {
		status = SMB_VFS_NEXT_CHDIR(handle, smb_fname);
		goto out;
	}

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

	status = SMB_VFS_NEXT_CHDIR(handle, clientFname);
err:
	TALLOC_FREE(clientFname);
out:
	return status;
}

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


	DEBUG(MH_INFO_DEBUG, ("Entering mh_ntimes\n"));
	if (!is_in_media_files(smb_fname->base_name))
	{
		status = SMB_VFS_NEXT_NTIMES(handle, smb_fname, ft);
		goto out;
	}

	clientFname = NULL;
	ctx = talloc_tos();

	if ((status = alloc_get_client_smb_fname(handle, ctx,
				smb_fname,
				&clientFname)))
	{
		goto err;
	}

	status = SMB_VFS_NEXT_NTIMES(handle, clientFname, ft);
err:
	TALLOC_FREE(clientFname);
out:
	return status;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 */

static int mh_symlink(vfs_handle_struct *handle,
		const char *link_contents,
		const struct smb_filename *new_smb_fname)
{
	int status = -1;
	char *client_link_contents = NULL;
	struct smb_filename *newclientFname = NULL;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_symlink\n"));
	if (!is_in_media_files(link_contents) &&
			!is_in_media_files(new_smb_fname->base_name)) {
		status = SMB_VFS_NEXT_SYMLINK(handle,
				link_contents,
				new_smb_fname);
		goto out;
	}

	if ((status = alloc_get_client_path(handle, talloc_tos(),
				link_contents,
				&client_link_contents))) {
		goto err;
	}
	if ((status = alloc_get_client_smb_fname(handle, talloc_tos(),
				new_smb_fname,
				&newclientFname))) {
		goto err;
	}

	status = SMB_VFS_NEXT_SYMLINK(handle,
				client_link_contents,
				newclientFname);
err:
	TALLOC_FREE(client_link_contents);
	TALLOC_FREE(newclientFname);
out:
	return status;
}

/*
 * Success: return byte count
 * Failure: set errno, return -1
 */
static int mh_readlink(vfs_handle_struct *handle,
		const struct smb_filename *smb_fname,
		char *buf,
		size_t bufsiz)
{
	int status;
	struct smb_filename *clientFname = NULL;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_readlink\n"));
	if (!is_in_media_files(smb_fname->base_name)) {
		status = SMB_VFS_NEXT_READLINK(handle, smb_fname, buf, bufsiz);
		goto out;
	}

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

	status = SMB_VFS_NEXT_READLINK(handle, clientFname, buf, bufsiz);
err:
	TALLOC_FREE(clientFname);
out:
	return status;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int mh_link(vfs_handle_struct *handle,
		const struct smb_filename *old_smb_fname,
		const struct smb_filename *new_smb_fname)
{
	int status;
	struct smb_filename *oldclientFname = NULL;
	struct smb_filename *newclientFname = NULL;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_link\n"));
	if (!is_in_media_files(old_smb_fname->base_name) &&
			!is_in_media_files(new_smb_fname->base_name)) {
		status = SMB_VFS_NEXT_LINK(handle,
				old_smb_fname,
				new_smb_fname);
		goto out;
	}

	if ((status = alloc_get_client_smb_fname(handle, talloc_tos(),
				old_smb_fname,
				&oldclientFname))) {
		goto err;
	}
	if ((status = alloc_get_client_smb_fname(handle, talloc_tos(),
				new_smb_fname,
				&newclientFname))) {
		goto err;
	}

	status = SMB_VFS_NEXT_LINK(handle, oldclientFname, newclientFname);
err:
	TALLOC_FREE(newclientFname);
	TALLOC_FREE(oldclientFname);
out:
	return status;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int mh_mknod(vfs_handle_struct *handle,
		const struct smb_filename *smb_fname,
		mode_t mode,
		SMB_DEV_T dev)
{
	int status;
	struct smb_filename *clientFname = NULL;
	TALLOC_CTX *ctx;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_mknod\n"));
	if (!is_in_media_files(smb_fname->base_name)) {
		status = SMB_VFS_NEXT_MKNOD(handle, smb_fname, mode, dev);
		goto out;
	}

	ctx = talloc_tos();

	if ((status = alloc_get_client_smb_fname(handle, ctx,
				smb_fname,
				&clientFname))) {
		goto err;
	}

	status = SMB_VFS_NEXT_MKNOD(handle, clientFname, mode, dev);
err:
	TALLOC_FREE(clientFname);
out:
	return status;
}

/*
 * Success: return path pointer
 * Failure: set errno, return NULL pointer
 */
static struct smb_filename *mh_realpath(vfs_handle_struct *handle,
				TALLOC_CTX *ctx,
				const struct smb_filename *smb_fname)
{
	struct smb_filename *result_fname = NULL;
	struct smb_filename *clientFname = NULL;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_realpath\n"));
	if (!is_in_media_files(smb_fname->base_name)) {
		return SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname);
	}

	if (alloc_get_client_smb_fname(handle, ctx,
				smb_fname,
				&clientFname) != 0) {
		goto err;
	}

	result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, clientFname);
err:
	TALLOC_FREE(clientFname);
	return result_fname;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 */
static int mh_chflags(vfs_handle_struct *handle,
		const struct smb_filename *smb_fname,
		unsigned int flags)
{
	int status;
	struct smb_filename *clientFname = NULL;
	TALLOC_CTX *ctx;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_chflags\n"));
	if (!is_in_media_files(smb_fname->base_name)) {
		status = SMB_VFS_NEXT_CHFLAGS(handle, smb_fname, flags);
		goto out;
	}

	ctx = talloc_tos();

	if ((status = alloc_get_client_smb_fname(handle, ctx,
				smb_fname,
				&clientFname))) {
		goto err;
	}

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

/*
 * Success: return NT_STATUS_OK
 * Failure: return NT status error
 */
static NTSTATUS mh_streaminfo(struct vfs_handle_struct *handle,
		struct files_struct *fsp,
		const struct smb_filename *smb_fname,
		TALLOC_CTX *ctx,
		unsigned int *num_streams,
		struct stream_struct **streams)
{
	NTSTATUS status;
	int ret;
	struct smb_filename *clientFname = NULL;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_streaminfo\n"));
	if (!is_in_media_files(smb_fname->base_name)) {
		status = SMB_VFS_NEXT_STREAMINFO(handle,
				fsp,
				smb_fname,
				ctx,
				num_streams,
				streams);
		goto out;
	}

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

	/* This only works on files, so we don't have to worry about
	 * our fake directory stat'ing here.
	 */
	status = SMB_VFS_NEXT_STREAMINFO(handle, fsp, clientFname,
				ctx, num_streams, streams);
err:
	TALLOC_FREE(clientFname);
out:
	return status;
}

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

/*
 * Success: return NT_STATUS_OK
 * Failure: return NT status error
 * In this case, "name" is a path.
 */
static NTSTATUS mh_get_nt_acl(vfs_handle_struct *handle,
			      const struct smb_filename *smb_fname,
			      uint32_t security_info,
			      TALLOC_CTX *mem_ctx,
			      struct security_descriptor **ppdesc)
{
	NTSTATUS status;
	char *clientPath;
	struct smb_filename *client_smb_fname = NULL;
	TALLOC_CTX *ctx;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_get_nt_acl\n"));
	if (!is_in_media_files(smb_fname->base_name))
	{
		status = SMB_VFS_NEXT_GET_NT_ACL(handle, smb_fname,
						 security_info,
						 mem_ctx, ppdesc);
		goto out;
	}

	clientPath = NULL;
	ctx = talloc_tos();

	if (alloc_get_client_path(handle, ctx,
				smb_fname->base_name,
				&clientPath))
	{
		status = map_nt_error_from_unix(errno);
		goto err;
	}

	client_smb_fname = synthetic_smb_fname(talloc_tos(),
					clientPath,
					NULL,
					NULL,
					smb_fname->flags);
	if (client_smb_fname == NULL) {
		TALLOC_FREE(clientPath);
		return NT_STATUS_NO_MEMORY;
	}

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

/*
 * Success: return acl pointer
 * Failure: set errno, return NULL
 */
static SMB_ACL_T mh_sys_acl_get_file(vfs_handle_struct *handle,
				const struct smb_filename *smb_fname,
				SMB_ACL_TYPE_T type,
				TALLOC_CTX *mem_ctx)
{
	SMB_ACL_T ret;
	int status;
	struct smb_filename *clientFname = NULL;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_sys_acl_get_file\n"));
	if (!is_in_media_files(smb_fname->base_name)) {
		ret = SMB_VFS_NEXT_SYS_ACL_GET_FILE(handle, smb_fname,
				type, mem_ctx);
		goto out;
	}

	status = alloc_get_client_smb_fname(handle,
				talloc_tos(),
				smb_fname,
				&clientFname);
	if (status != 0) {
		ret = (SMB_ACL_T)NULL;
		goto err;
	}

	ret = SMB_VFS_NEXT_SYS_ACL_GET_FILE(handle, clientFname, type, mem_ctx);
err:
	TALLOC_FREE(clientFname);
out:
	return ret;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 * In this case, "name" is a path.
 */
static int mh_sys_acl_set_file(vfs_handle_struct *handle,
		const struct smb_filename *smb_fname,
		SMB_ACL_TYPE_T acltype,
		SMB_ACL_T theacl)
{
	int status;
	struct smb_filename *clientFname = NULL;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_sys_acl_set_file\n"));
	if (!is_in_media_files(smb_fname->base_name)) {
		status = SMB_VFS_NEXT_SYS_ACL_SET_FILE(handle, smb_fname,
				acltype, theacl);
		goto out;
	}

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

	status = SMB_VFS_NEXT_SYS_ACL_SET_FILE(handle, clientFname,
			acltype, theacl);
err:
	TALLOC_FREE(clientFname);
out:
	return status;
}

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

	DEBUG(MH_INFO_DEBUG, ("Entering mh_sys_acl_delete_def_file\n"));
	if (!is_in_media_files(smb_fname->base_name)) {
		status = SMB_VFS_NEXT_SYS_ACL_DELETE_DEF_FILE(handle,
				smb_fname);
		goto out;
	}

	status = alloc_get_client_smb_fname(handle,
				talloc_tos(),
				smb_fname,
				&clientFname);
	if (status != 0) {
		goto err;
	}
	status = SMB_VFS_NEXT_SYS_ACL_DELETE_DEF_FILE(handle, clientFname);
err:
	TALLOC_FREE(clientFname);
out:
	return status;
}

/*
 * Success: return positive number
 * Failure: set errno, return -1
 * In this case, "name" is an attr name.
 */
static ssize_t mh_getxattr(struct vfs_handle_struct *handle,
		const struct smb_filename *smb_fname,
		const char *name,
		void *value,
		size_t size)
{
	int status;
	struct smb_filename *clientFname = NULL;
	ssize_t ret;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_getxattr\n"));
	if (!is_in_media_files(smb_fname->base_name)) {
		ret = SMB_VFS_NEXT_GETXATTR(handle, smb_fname,
					name, value, size);
		goto out;
	}

	status = alloc_get_client_smb_fname(handle,
				talloc_tos(),
				smb_fname,
				&clientFname);
	if (status != 0) {
		ret = -1;
		goto err;
	}
	ret = SMB_VFS_NEXT_GETXATTR(handle, clientFname, name, value, size);
err:
	TALLOC_FREE(clientFname);
out:
	return ret;
}

/*
 * Success: return positive number
 * Failure: set errno, return -1
 */
static ssize_t mh_listxattr(struct vfs_handle_struct *handle,
		const struct smb_filename *smb_fname,
		char *list,
		size_t size)
{
	ssize_t ret;
	struct smb_filename *clientFname = NULL;
	int status;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_listxattr\n"));
	if (!is_in_media_files(smb_fname->base_name)) {
		ret = SMB_VFS_NEXT_LISTXATTR(handle, smb_fname, list, size);
		goto out;
	}

	status = alloc_get_client_smb_fname(handle,
				talloc_tos(),
				smb_fname,
				&clientFname);
	if (status != 0) {
		ret = -1;
		goto err;
	}

	ret = SMB_VFS_NEXT_LISTXATTR(handle, clientFname, list, size);
err:
	TALLOC_FREE(clientFname);
out:
	return ret;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 * In this case, "name" is an attr name.
 */
static int mh_removexattr(struct vfs_handle_struct *handle,
		const struct smb_filename *smb_fname,
		const char *name)
{
	int status;
	struct smb_filename *clientFname = NULL;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_removexattr\n"));
	if (!is_in_media_files(smb_fname->base_name)) {
		status = SMB_VFS_NEXT_REMOVEXATTR(handle, smb_fname, name);
		goto out;
	}

	status = alloc_get_client_smb_fname(handle,
				talloc_tos(),
				smb_fname,
				&clientFname);
	if (status != 0) {
		goto err;
	}
	status = SMB_VFS_NEXT_REMOVEXATTR(handle, clientFname, name);
err:
	TALLOC_FREE(clientFname);
out:
	return status;
}

/*
 * Success: return 0
 * Failure: set errno, return -1
 * In this case, "name" is an attr name.
 */
static int mh_setxattr(struct vfs_handle_struct *handle,
		const struct smb_filename *smb_fname,
		const char *name,
		const void *value,
		size_t size,
		int flags)
{
	int status;
	struct smb_filename *clientFname = NULL;

	DEBUG(MH_INFO_DEBUG, ("Entering mh_setxattr\n"));
	if (!is_in_media_files(smb_fname->base_name)) {
		status = SMB_VFS_NEXT_SETXATTR(handle, smb_fname, name, value,
				size, flags);
		goto out;
	}

	status = alloc_get_client_smb_fname(handle,
				talloc_tos(),
				smb_fname,
				&clientFname);
	if (status != 0) {
		goto err;
	}
	status = SMB_VFS_NEXT_SETXATTR(handle, clientFname, name, value,
			size, flags);
err:
	TALLOC_FREE(clientFname);
out:
	return status;
}

/* VFS operations structure */

static struct vfs_fn_pointers vfs_mh_fns = {
	/* Disk operations */

	.statvfs_fn = mh_statvfs,

	/* Directory operations */

	.opendir_fn = mh_opendir,
	.fdopendir_fn = mh_fdopendir,
	.readdir_fn = mh_readdir,
	.seekdir_fn = mh_seekdir,
	.telldir_fn = mh_telldir,
	.rewind_dir_fn = mh_rewinddir,
	.mkdir_fn = mh_mkdir,
	.rmdir_fn = mh_rmdir,
	.closedir_fn = mh_closedir,

	/* File operations */

	.open_fn = mh_open,
	.create_file_fn = mh_create_file,
	.rename_fn = mh_rename,
	.stat_fn = mh_stat,
	.lstat_fn = mh_lstat,
	.fstat_fn = mh_fstat,
	.unlink_fn = mh_unlink,
	.chmod_fn = mh_chmod,
	.chown_fn = mh_chown,
	.lchown_fn = mh_lchown,
	.chdir_fn = mh_chdir,
	.ntimes_fn = mh_ntimes,
	.symlink_fn = mh_symlink,
	.readlink_fn = mh_readlink,
	.link_fn = mh_link,
	.mknod_fn = mh_mknod,
	.realpath_fn = mh_realpath,
	.chflags_fn = mh_chflags,
	.streaminfo_fn = mh_streaminfo,

	/* NT ACL operations. */

	.get_nt_acl_fn = mh_get_nt_acl,

	/* POSIX ACL operations. */

	.sys_acl_get_file_fn = mh_sys_acl_get_file,
	.sys_acl_set_file_fn = mh_sys_acl_set_file,
	.sys_acl_delete_def_file_fn = mh_sys_acl_delete_def_file,

	/* EA operations. */
	.getxattr_fn = mh_getxattr,
	.listxattr_fn = mh_listxattr,
	.removexattr_fn = mh_removexattr,
	.setxattr_fn = mh_setxattr,

	/* aio operations */
};

static_decl_vfs;
NTSTATUS vfs_media_harmony_init(TALLOC_CTX *ctx)
{
	NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
				"media_harmony", &vfs_mh_fns);
	if (!NT_STATUS_IS_OK(ret))
	{
		goto out;
	}

	vfs_mh_debug_level = debug_add_class("media_harmony");

	if (vfs_mh_debug_level == -1) {
		vfs_mh_debug_level = DBGC_VFS;
		DEBUG(1, ("media_harmony_init: Couldn't register custom "
				"debugging class.\n"));
	} else {
		DEBUG(3, ("media_harmony_init: Debug class number of "
				"'media_harmony': %d\n",
				vfs_mh_debug_level));
	}

out:
	return ret;
}
