/* 
   Unix SMB/CIFS implementation.
   DMAPI Support routines

   Copyright (C) James Peach 2006

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "includes.h"

#undef DBGC_CLASS
#define DBGC_CLASS DBGC_DMAPI

#ifndef USE_DMAPI

int dmapi_init_session(void) { return -1; }
uint32 dmapi_file_flags(const char * const path) { return 0; }
BOOL dmapi_have_session(void) { return False; }

#else /* USE_DMAPI */

#ifdef HAVE_XFS_DMAPI_H
#include <xfs/dmapi.h>
#elif defined(HAVE_SYS_DMI_H)
#include <sys/dmi.h>
#elif defined(HAVE_SYS_JFSDMAPI_H)
#include <sys/jfsdmapi.h>
#elif defined(HAVE_SYS_DMAPI_H)
#include <sys/dmapi.h>
#elif defined(HAVE_DMAPI_H)
#include <dmapi.h>
#endif

#define DMAPI_SESSION_NAME "samba"
#define DMAPI_TRACE 10

static dm_sessid_t dmapi_session = DM_NO_SESSION;

/* Initialise the DMAPI interface. Make sure that we only end up initialising
 * once per process to avoid resource leaks across different DMAPI
 * implementations.
 */
static int init_dmapi_service(void)
{
	static pid_t lastpid;

	pid_t mypid;

	mypid = sys_getpid();
	if (mypid != lastpid) {
		char *version;

		lastpid = mypid;
		if (dm_init_service(&version) < 0) {
			return -1;
		}

		DEBUG(0, ("Initializing DMAPI: %s\n", version));
	}

	return 0;
}

BOOL dmapi_have_session(void)
{
	return dmapi_session != DM_NO_SESSION;
}

static dm_sessid_t *realloc_session_list(dm_sessid_t * sessions, int count)
{
	dm_sessid_t *nsessions;

	nsessions = TALLOC_REALLOC_ARRAY(NULL, sessions, dm_sessid_t, count);
	if (nsessions == NULL) {
		TALLOC_FREE(sessions);
		return NULL;
	}

	return nsessions;
}

/* Initialise DMAPI session. The session is persistant kernel state, so it
 * might already exist, in which case we merely want to reconnect to it. This
 * function should be called as root.
 */
int dmapi_init_session(void)
{
	char	buf[DM_SESSION_INFO_LEN];
	size_t	buflen;

	uint	    nsessions = 10;
	dm_sessid_t *sessions = NULL;

	int i, err;

	/* If we aren't root, something in the following will fail due to lack
	 * of privileges. Aborting seems a little extreme.
	 */
	SMB_WARN(getuid() == 0, "dmapi_init_session must be called as root");

	dmapi_session = DM_NO_SESSION;
	if (init_dmapi_service() < 0) {
		return -1;
	}

retry:

	if ((sessions = realloc_session_list(sessions, nsessions)) == NULL) {
		return -1;
	}

	err = dm_getall_sessions(nsessions, sessions, &nsessions);
	if (err < 0) {
		if (errno == E2BIG) {
			nsessions *= 2;
			goto retry;
		}

		DEBUGADD(DMAPI_TRACE,
			("failed to retrieve DMAPI sessions: %s\n",
			strerror(errno)));
		TALLOC_FREE(sessions);
		return -1;
	}

	for (i = 0; i < nsessions; ++i) {
		err = dm_query_session(sessions[i], sizeof(buf), buf, &buflen);
		buf[sizeof(buf) - 1] = '\0';
		if (err == 0 && strcmp(DMAPI_SESSION_NAME, buf) == 0) {
			dmapi_session = sessions[i];
			DEBUGADD(DMAPI_TRACE,
				("attached to existing DMAPI session "
				 "named '%s'\n", buf));
			break;
		}
	}

	TALLOC_FREE(sessions);

	/* No session already defined. */
	if (dmapi_session == DM_NO_SESSION) {
		err = dm_create_session(DM_NO_SESSION,
					CONST_DISCARD(char *,
						      DMAPI_SESSION_NAME),
					&dmapi_session);
		if (err < 0) {
			DEBUGADD(DMAPI_TRACE,
				("failed to create new DMAPI session: %s\n",
				strerror(errno)));
			dmapi_session = DM_NO_SESSION;
			return -1;
		}

		DEBUGADD(DMAPI_TRACE,
			("created new DMAPI session named '%s'\n",
			DMAPI_SESSION_NAME));
	}

	/* Note that we never end the DMAPI session. This enables child
	 * processes to continue to use the session after we exit. It also lets
	 * you run a second Samba server on different ports without any
	 * conflict.
	 */

	return 0;
}

/* Reattach to an existing dmapi session. Called from service processes that
 * might not be running as root.
 */
static int reattach_dmapi_session(void)
{
	char	buf[DM_SESSION_INFO_LEN];
	size_t	buflen;

	if (dmapi_session != DM_NO_SESSION ) {
		become_root();

		/* NOTE: On Linux, this call opens /dev/dmapi, costing us a
		 * file descriptor. Ideally, we would close this when we fork.
		 */
		if (init_dmapi_service() < 0) {
			dmapi_session = DM_NO_SESSION;
			unbecome_root();
			return -1;
		}

		if (dm_query_session(dmapi_session, sizeof(buf),
			    buf, &buflen) < 0) {
			/* Session is stale. Disable DMAPI. */
			dmapi_session = DM_NO_SESSION;
			unbecome_root();
			return -1;
		}

		set_effective_capability(DMAPI_ACCESS_CAPABILITY);

		DEBUG(DMAPI_TRACE, ("reattached DMAPI session\n"));
		unbecome_root();
	}

	return 0;
}

uint32 dmapi_file_flags(const char * const path)
{
	static int attached = 0;

	int		err;
	dm_eventset_t   events = {0};
	uint		nevents;

	void	*dm_handle;
	size_t	dm_handle_len;

	uint32	flags = 0;

	/* If a DMAPI session has been initialised, then we need to make sure
	 * we are attached to it and have the correct privileges. This is
	 * necessary to be able to do DMAPI operations across a fork(2). If
	 * it fails, there is no liklihood of that failure being transient.
	 *
	 * Note that this use of the static attached flag relies on the fact
	 * that dmapi_file_flags() is never called prior to forking the
	 * per-client server process.
	 */
	if (dmapi_have_session() && !attached) {
		attached++;
		if (reattach_dmapi_session() < 0) {
			return 0;
		}
	}

	/* AIX has DMAPI but no POSIX capablities support. In this case,
	 * we need to be root to do DMAPI manipulations.
	 */
#ifndef HAVE_POSIX_CAPABILITIES
	become_root();
#endif

	err = dm_path_to_handle(CONST_DISCARD(char *, path),
		&dm_handle, &dm_handle_len);
	if (err < 0) {
		DEBUG(DMAPI_TRACE, ("dm_path_to_handle(%s): %s\n",
			    path, strerror(errno)));

		if (errno != EPERM) {
			goto done;
		}

		/* Linux capabilities are broken in that changing our
		 * user ID will clobber out effective capabilities irrespective
		 * of whether we have set PR_SET_KEEPCAPS. Fortunately, the
		 * capabilities are not removed from our permitted set, so we
		 * can re-acquire them if necessary.
		 */

		set_effective_capability(DMAPI_ACCESS_CAPABILITY);

		err = dm_path_to_handle(CONST_DISCARD(char *, path),
			&dm_handle, &dm_handle_len);
		if (err < 0) {
			DEBUG(DMAPI_TRACE,
			    ("retrying dm_path_to_handle(%s): %s\n",
			    path, strerror(errno)));
			goto done;
		}
	}

	err = dm_get_eventlist(dmapi_session, dm_handle, dm_handle_len,
		DM_NO_TOKEN, DM_EVENT_MAX, &events, &nevents);
	if (err < 0) {
		DEBUG(DMAPI_TRACE, ("dm_get_eventlist(%s): %s\n",
			    path, strerror(errno)));
		dm_handle_free(dm_handle, dm_handle_len);
		goto done;
	}

	/* We figure that the only reason a DMAPI application would be
	 * interested in trapping read events is that part of the file is
	 * offline.
	 */
	DEBUG(DMAPI_TRACE, ("DMAPI event list for %s is %#llx\n",
		    path, events));
	if (DMEV_ISSET(DM_EVENT_READ, events)) {
		flags = FILE_ATTRIBUTE_OFFLINE;
	}

	dm_handle_free(dm_handle, dm_handle_len);

	if (flags & FILE_ATTRIBUTE_OFFLINE) {
		DEBUG(DMAPI_TRACE, ("%s is OFFLINE\n", path));
	}

done:

#ifndef HAVE_POSIX_CAPABILITIES
	unbecome_root();
#endif

	return flags;
}

#endif /* USE_DMAPI */