/*
   Unix SMB/CIFS implementation.
   Main metadata server / Spotlight routines

   Copyright (C) Ralph Boehme 2012-2014

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "includes.h"
#include "librpc/gen_ndr/auth.h"
#include "dbwrap/dbwrap.h"
#include "lib/util/dlinklist.h"
#include "lib/util/util_tdb.h"
#include "lib/util/time_basic.h"
#include "lib/dbwrap/dbwrap_rbt.h"
#include "libcli/security/dom_sid.h"
#include "mdssvc.h"
#include "sparql_parser.h"

#undef DBGC_CLASS
#define DBGC_CLASS DBGC_RPC_SRV

#define SLQ_DEBUG(lvl, _slq, state) do { if (CHECK_DEBUGLVL(lvl)) {	\
	const struct sl_query *__slq = _slq;				\
	struct timeval_buf start_buf;					\
	const char *start;						\
	struct timeval_buf last_used_buf;				\
	const char *last_used;						\
	struct timeval_buf expire_buf;					\
	const char *expire;						\
	start = timeval_str_buf(&__slq->start_time, false,		\
				true, &start_buf);			\
	last_used = timeval_str_buf(&__slq->last_used, false,		\
				    true, &last_used_buf);		\
	expire = timeval_str_buf(&__slq->expire_time, false,		\
				 true, &expire_buf);			\
	DEBUG(lvl,("%s slq[0x%jx,0x%jx], start: %s, last_used: %s, "	\
		   "expires: %s, query: '%s'\n", state,			\
		   (uintmax_t)__slq->ctx1, (uintmax_t)__slq->ctx2,	\
		   start, last_used, expire, __slq->query_string));	\
}} while(0)

struct slrpc_cmd {
	const char *name;
	bool (*function)(struct mds_ctx *mds_ctx,
			 const DALLOC_CTX *query,
			 DALLOC_CTX *reply);
};

struct slq_destroy_state {
	struct tevent_context *ev;
	struct sl_query *slq;
};

/*
 * If these functions return an error, they hit something like a non
 * recoverable talloc error. Most errors are dealt with by returning
 * an errror code in the Spotlight RPC reply.
 */
static bool slrpc_fetch_properties(struct mds_ctx *mds_ctx,
				   const DALLOC_CTX *query, DALLOC_CTX *reply);
static bool slrpc_open_query(struct mds_ctx *mds_ctx,
			     const DALLOC_CTX *query, DALLOC_CTX *reply);
static bool slrpc_fetch_query_results(struct mds_ctx *mds_ctx,
				      const DALLOC_CTX *query, DALLOC_CTX *reply);
static bool slrpc_store_attributes(struct mds_ctx *mds_ctx,
				   const DALLOC_CTX *query, DALLOC_CTX *reply);
static bool slrpc_fetch_attributenames(struct mds_ctx *mds_ctx,
				       const DALLOC_CTX *query, DALLOC_CTX *reply);
static bool slrpc_fetch_attributes(struct mds_ctx *mds_ctx,
				   const DALLOC_CTX *query, DALLOC_CTX *reply);
static bool slrpc_close_query(struct mds_ctx *mds_ctx,
			      const DALLOC_CTX *query, DALLOC_CTX *reply);

static struct tevent_req *slq_destroy_send(TALLOC_CTX *mem_ctx,
					   struct tevent_context *ev,
					   struct sl_query **slq)
{
	struct tevent_req *req;
	struct slq_destroy_state *state;

	req = tevent_req_create(mem_ctx, &state,
				struct slq_destroy_state);
	if (req == NULL) {
		return NULL;
	}
	state->slq = talloc_move(state, slq);
	tevent_req_done(req);

	return tevent_req_post(req, ev);
}

static void slq_destroy_recv(struct tevent_req *req)
{
	tevent_req_received(req);
}

/************************************************
 * Misc utility functions
 ************************************************/

static char *tab_level(TALLOC_CTX *mem_ctx, int level)
{
	int i;
	char *string = talloc_array(mem_ctx, char, level + 1);

	for (i = 0; i < level; i++) {
		string[i] = '\t';
	}

	string[i] = '\0';
	return string;
}

char *mds_dalloc_dump(DALLOC_CTX *dd, int nestinglevel)
{
	const char *type;
	int n, result;
	uint64_t i;
	sl_bool_t bl;
	sl_time_t t;
	struct tm *tm;
	char datestring[256];
	sl_cnids_t cnids;
	char *logstring, *nested_logstring;
	char *tab_string1, *tab_string2;
	void *p;
	bool ok;
	char *utf8string;
	size_t utf8len;

	tab_string1 = tab_level(dd, nestinglevel);
	if (tab_string1 == NULL) {
		return NULL;
	}
	tab_string2 = tab_level(dd, nestinglevel + 1);
	if (tab_string2 == NULL) {
		return NULL;
	}

	logstring = talloc_asprintf(dd,
				    "%s%s(#%lu): {\n",
				    tab_string1,
				    talloc_get_name(dd),
				    dalloc_size(dd));
	if (logstring == NULL) {
		return NULL;
	}

	for (n = 0; n < dalloc_size(dd); n++) {
		type = dalloc_get_name(dd, n);
		if (type == NULL) {
			return NULL;
		}
		p = dalloc_get_object(dd, n);
		if (p == NULL) {
			return NULL;
		}
		if (strcmp(type, "DALLOC_CTX") == 0
		    || strcmp(type, "sl_array_t") == 0
		    || strcmp(type, "sl_filemeta_t") == 0
		    || strcmp(type, "sl_dict_t") == 0) {
			nested_logstring = mds_dalloc_dump(p, nestinglevel + 1);
			if (nested_logstring == NULL) {
				return NULL;
			}
			logstring = talloc_strdup_append(logstring,
							 nested_logstring);
		} else if (strcmp(type, "uint64_t") == 0) {
			memcpy(&i, p, sizeof(uint64_t));
			logstring = talloc_asprintf_append(
				logstring,
				"%suint64_t: 0x%04jx\n",
				tab_string2, (uintmax_t)i);
		} else if (strcmp(type, "char *") == 0) {
			logstring = talloc_asprintf_append(
				logstring,
				"%sstring: %s\n",
				tab_string2,
				(char *)p);
		} else if (strcmp(type, "smb_ucs2_t *") == 0) {
			ok = convert_string_talloc(talloc_tos(),
						   CH_UTF16LE,
						   CH_UTF8,
						   p,
						   talloc_get_size(p),
						   &utf8string,
						   &utf8len);
			if (!ok) {
				return NULL;
			}
			logstring = talloc_asprintf_append(
				logstring,
				"%sUTF16-string: %s\n",
				tab_string2,
				utf8string);
			TALLOC_FREE(utf8string);
		} else if (strcmp(type, "sl_bool_t") == 0) {
			memcpy(&bl, p, sizeof(sl_bool_t));
			logstring = talloc_asprintf_append(
				logstring,
				"%sbool: %s\n",
				tab_string2,
				bl ? "true" : "false");
		} else if (strcmp(type, "sl_nil_t") == 0) {
			logstring = talloc_asprintf_append(
				logstring,
				"%snil\n",
				tab_string2);
		} else if (strcmp(type, "sl_time_t") == 0) {
			memcpy(&t, p, sizeof(sl_time_t));
			tm = localtime(&t.tv_sec);
			if (tm == NULL) {
				return NULL;
			}
			result = strftime(datestring,
					 sizeof(datestring),
					 "%Y-%m-%d %H:%M:%S", tm);
			if (result == 0) {
				return NULL;
			}
			logstring = talloc_asprintf_append(
				logstring,
				"%ssl_time_t: %s.%06lu\n",
				tab_string2,
				datestring,
				(unsigned long)t.tv_usec);
		} else if (strcmp(type, "sl_cnids_t") == 0) {
			memcpy(&cnids, p, sizeof(sl_cnids_t));
			logstring = talloc_asprintf_append(
				logstring,
				"%sCNIDs: unkn1: 0x%" PRIx16 ", unkn2: 0x%" PRIx32 "\n",
				tab_string2,
				cnids.ca_unkn1,
				cnids.ca_context);
			if (logstring == NULL) {
				return NULL;
			}
			if (cnids.ca_cnids) {
				nested_logstring = mds_dalloc_dump(
					cnids.ca_cnids,
					nestinglevel + 2);
				if (!nested_logstring) {
					return NULL;
				}
				logstring = talloc_strdup_append(logstring,
								 nested_logstring);
			}
		} else {
			logstring = talloc_asprintf_append(
				logstring,
				"%stype: %s\n",
				tab_string2,
				type);
		}
		if (logstring == NULL) {
			return NULL;
		}
	}
	logstring = talloc_asprintf_append(logstring,
					   "%s}\n",
					   tab_string1);
	if (logstring == NULL) {
		return NULL;
	}
	return logstring;
}

static char *tracker_to_unix_path(TALLOC_CTX *mem_ctx, const char *uri)
{
	GFile *f;
	char *path;
	char *talloc_path;

	f = g_file_new_for_uri(uri);
	if (f == NULL) {
		return NULL;
	}

	path = g_file_get_path(f);
	g_object_unref(f);

	if (path == NULL) {
		return NULL;
	}

	talloc_path = talloc_strdup(mem_ctx, path);
	g_free(path);
	if (talloc_path == NULL) {
		return NULL;
	}

	return talloc_path;
}

/**
 * Add requested metadata for a query result element
 *
 * This could be rewritten to something more sophisticated like
 * querying metadata from Tracker.
 *
 * If path or sp is NULL, simply add nil values for all attributes.
 **/
static bool add_filemeta(sl_array_t *reqinfo,
			 sl_array_t *fm_array,
			 const char *path,
			 const struct stat_ex *sp)
{
	sl_array_t *meta;
	sl_nil_t nil;
	int i, metacount, result;
	uint64_t uint64var;
	sl_time_t sl_time;
	char *p;
	const char *attribute;

	metacount = dalloc_size(reqinfo);
	if (metacount == 0 || path == NULL || sp == NULL) {
		result = dalloc_add_copy(fm_array, &nil, sl_nil_t);
		if (result != 0) {
			return false;
		}
		return true;
	}

	meta = dalloc_zero(fm_array, sl_array_t);
	if (meta == NULL) {
		return false;
	}

	for (i = 0; i < metacount; i++) {
		attribute = dalloc_get_object(reqinfo, i);
		if (attribute == NULL) {
			return false;
		}
		if (strcmp(attribute, "kMDItemDisplayName") == 0
		    || strcmp(attribute, "kMDItemFSName") == 0) {
			p = strrchr(path, '/');
			if (p) {
				result = dalloc_stradd(meta, p + 1);
				if (result != 0) {
					return false;
				}
			}
		} else if (strcmp(attribute, "kMDItemPath") == 0) {
			result = dalloc_stradd(meta, path);
			if (result != 0) {
				return false;
			}
		} else if (strcmp(attribute, "kMDItemFSSize") == 0) {
			uint64var = sp->st_ex_size;
			result = dalloc_add_copy(meta, &uint64var, uint64_t);
			if (result != 0) {
				return false;
			}
		} else if (strcmp(attribute, "kMDItemFSOwnerUserID") == 0) {
			uint64var = sp->st_ex_uid;
			result = dalloc_add_copy(meta, &uint64var, uint64_t);
			if (result != 0) {
				return false;
			}
		} else if (strcmp(attribute, "kMDItemFSOwnerGroupID") == 0) {
			uint64var = sp->st_ex_gid;
			result = dalloc_add_copy(meta, &uint64var, uint64_t);
			if (result != 0) {
				return false;
			}
		} else if (strcmp(attribute, "kMDItemFSContentChangeDate") == 0) {
			sl_time.tv_sec = sp->st_ex_mtime.tv_sec;
			result = dalloc_add_copy(meta, &sl_time, sl_time_t);
			if (result != 0) {
				return false;
			}
		} else {
			result = dalloc_add_copy(meta, &nil, sl_nil_t);
			if (result != 0) {
				return false;
			}
		}
	}

	result = dalloc_add(fm_array, meta, sl_array_t);
	if (result != 0) {
		return false;
	}
	return true;
}

static int cnid_comp_fn(const void *p1, const void *p2)
{
	const uint64_t *cnid1 = p1, *cnid2 = p2;
	if (*cnid1 == *cnid2) {
		return 0;
	}
	if (*cnid1 < *cnid2) {
		return -1;
	}
	return 1;
}

/**
 * Create a sorted copy of a CNID array
 **/
static bool sort_cnids(struct sl_query *slq, const DALLOC_CTX *d)
{
	uint64_t *cnids = NULL;
	int i;
	const void *p;

	cnids = talloc_array(slq, uint64_t, dalloc_size(d));
	if (cnids == NULL) {
		return false;
	}

	for (i = 0; i < dalloc_size(d); i++) {
		p = dalloc_get_object(d, i);
		if (p == NULL) {
			return NULL;
		}
		memcpy(&cnids[i], p, sizeof(uint64_t));
	}
	qsort(cnids, dalloc_size(d), sizeof(uint64_t), cnid_comp_fn);

	slq->cnids = cnids;
	slq->cnids_num = dalloc_size(d);

	return true;
}

/**
 * Allocate result handle used in the async Tracker cursor result
 * handler for storing results
 **/
static bool create_result_handle(struct sl_query *slq)
{
	sl_nil_t nil = 0;
	struct sl_rslts *query_results;
	int result;

	if (slq->query_results) {
		DEBUG(1, ("unexpected existing result handle\n"));
		return false;
	}

	query_results = talloc_zero(slq, struct sl_rslts);
	if (query_results == NULL) {
		return false;
	}

	/* CNIDs */
	query_results->cnids = talloc_zero(query_results, sl_cnids_t);
	if (query_results->cnids == NULL) {
		return false;
	}
	query_results->cnids->ca_cnids = dalloc_new(query_results->cnids);
	if (query_results->cnids->ca_cnids == NULL) {
		return false;
	}

	query_results->cnids->ca_unkn1 = 0xadd;
	if (slq->ctx2 > UINT32_MAX) {
		DEBUG(1,("64bit ctx2 id too large: 0x%jx", (uintmax_t)slq->ctx2));
		return false;
	}
	query_results->cnids->ca_context = (uint32_t)slq->ctx2;

	/* FileMeta */
	query_results->fm_array = dalloc_zero(query_results, sl_array_t);
	if (query_results->fm_array == NULL) {
		return false;
	}

	/* For some reason the list of results always starts with a nil entry */
	result = dalloc_add_copy(query_results->fm_array, &nil, sl_nil_t);
	if (result != 0) {
		return false;
	}

	slq->query_results = query_results;
	return true;
}

static bool add_results(sl_array_t *array, struct sl_query *slq)
{
	sl_filemeta_t *fm;
	uint64_t status = 0;
	int result;
	bool ok;

	/* FileMeta */
	fm = dalloc_zero(array, sl_filemeta_t);
	if (fm == NULL) {
		return false;
	}

	result = dalloc_add_copy(array, &status, uint64_t);
	if (result != 0) {
		return false;
	}
	result = dalloc_add(array, slq->query_results->cnids, sl_cnids_t);
	if (result != 0) {
		return false;
	}
	if (slq->query_results->num_results > 0) {
		result = dalloc_add(fm, slq->query_results->fm_array, sl_array_t);
		if (result != 0) {
			return false;
		}
	}
	result = dalloc_add(array, fm, sl_filemeta_t);
	if (result != 0) {
		return false;
	}

	/* This ensure the results get clean up after been sent to the client */
	talloc_move(array, &slq->query_results);

	ok = create_result_handle(slq);
	if (!ok) {
		DEBUG(1, ("couldn't add result handle\n"));
		slq->state = SLQ_STATE_ERROR;
		return false;
	}

	return true;
}

static const struct slrpc_cmd *slrpc_cmd_by_name(const char *rpccmd)
{
	size_t i;
	static const struct slrpc_cmd cmds[] = {
		{ "fetchPropertiesForContext:", slrpc_fetch_properties},
		{ "openQueryWithParams:forContext:", slrpc_open_query},
		{ "fetchQueryResultsForContext:", slrpc_fetch_query_results},
		{ "storeAttributes:forOIDArray:context:", slrpc_store_attributes},
		{ "fetchAttributeNamesForOIDArray:context:", slrpc_fetch_attributenames},
		{ "fetchAttributes:forOIDArray:context:", slrpc_fetch_attributes},
		{ "fetchAllAttributes:forOIDArray:context:", slrpc_fetch_attributes},
		{ "closeQueryForContext:", slrpc_close_query},
	};

	for (i = 0; i < ARRAY_SIZE(cmds); i++) {
		int cmp;

		cmp = strcmp(cmds[i].name, rpccmd);
		if (cmp == 0) {
			return &cmds[i];
		}
	}

	return NULL;
}

/**
 * Search the list of active queries given their context ids
 **/
static struct sl_query *slq_for_ctx(struct mds_ctx *mds_ctx,
				    uint64_t ctx1, uint64_t ctx2)
{
	struct sl_query *q;

	for (q = mds_ctx->query_list; q; q = q->next) {
		if ((q->ctx1 == ctx1) && (q->ctx2 == ctx2)) {
			return q;
		}
	}

	return NULL;
}

static int slq_destructor_cb(struct sl_query *slq)
{
	SLQ_DEBUG(10, slq, "destroying");

	/* Free all entries before freeing the slq handle! */
	TALLOC_FREE(slq->entries_ctx);
	TALLOC_FREE(slq->te);

	if (slq->mds_ctx != NULL) {
		DLIST_REMOVE(slq->mds_ctx->query_list, slq);
		slq->mds_ctx = NULL;
	}

	if (slq->tracker_cursor != NULL) {
		g_object_unref(slq->tracker_cursor);
		slq->tracker_cursor = NULL;
	}

	if (slq->gcancellable != NULL) {
		g_cancellable_cancel(slq->gcancellable);
		g_object_unref(slq->gcancellable);
		slq->gcancellable = NULL;
	}

	return 0;
}

/**
 * Remove talloc_refcounted entry from mapping db
 *
 * Multiple queries (via the slq handle) may reference a
 * sl_inode_path_map entry, when the last reference goes away as the
 * queries are closed and this gets called to remove the entry from
 * the db.
 **/
static int ino_path_map_destr_cb(struct sl_inode_path_map *entry)
{
	NTSTATUS status;
	TDB_DATA key;

	key = make_tdb_data((uint8_t *)&entry->ino, sizeof(entry->ino));

	status = dbwrap_delete(entry->mds_ctx->ino_path_map, key);
	if (!NT_STATUS_IS_OK(status)) {
		DEBUG(1, ("Failed to delete record: %s\n", nt_errstr(status)));
		return -1;
	}

	DEBUG(10,("deleted: %s\n", entry->path));
	return 0;
}

/**
 * Add result to inode->path mapping dbwrap rbt db
 *
 * This is necessary as a CNID db substitute, ie we need a way to
 * simulate unique, constant numerical identifiers for paths with an
 * API that supports mapping from id to path.
 *
 * Entries are talloc'ed of the query, using talloc_reference() if
 * multiple queries returned the same result. That way we can cleanup
 * entries by calling talloc_free() on the query slq handles.
 **/

static bool inode_map_add(struct sl_query *slq, uint64_t ino, const char *path)
{
	NTSTATUS status;
	struct sl_inode_path_map *entry;
	TDB_DATA key, value;
	void *p;

	key = make_tdb_data((uint8_t *)&ino, sizeof(ino));
	status = dbwrap_fetch(slq->mds_ctx->ino_path_map, slq, key, &value);

	if (NT_STATUS_IS_OK(status)) {
		/*
		 * We have one db, so when different parallel queries
		 * return the same file, we have to refcount entries
		 * in the db.
		 */

		if (value.dsize != sizeof(void *)) {
			DEBUG(1, ("invalide dsize\n"));
			return false;
		}
		memcpy(&p, value.dptr, sizeof(p));
		entry = talloc_get_type_abort(p, struct sl_inode_path_map);

		DEBUG(10, ("map: %s\n", entry->path));

		entry = talloc_reference(slq->entries_ctx, entry);
		if (entry == NULL) {
			DEBUG(1, ("talloc_reference failed\n"));
			return false;
		}
		return true;
	}

	if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
		DEBUG(1, ("dbwrap_fetch failed %s\n", nt_errstr(status)));
		return false;
	}

	entry = talloc_zero(slq->entries_ctx, struct sl_inode_path_map);
	if (entry == NULL) {
		DEBUG(1, ("talloc failed\n"));
		return false;
	}

	entry->ino = ino;
	entry->mds_ctx = slq->mds_ctx;
	entry->path = talloc_strdup(entry, path);
	if (entry->path == NULL) {
		DEBUG(1, ("talloc failed\n"));
		TALLOC_FREE(entry);
		return false;
	}

	status = dbwrap_store(slq->mds_ctx->ino_path_map, key,
			      make_tdb_data((void *)&entry, sizeof(void *)), 0);
	if (!NT_STATUS_IS_OK(status)) {
		DEBUG(1, ("Failed to store record: %s\n", nt_errstr(status)));
		TALLOC_FREE(entry);
		return false;
	}

	talloc_set_destructor(entry, ino_path_map_destr_cb);

	return true;
}

/************************************************
 * Tracker async callbacks
 ************************************************/

static void tracker_con_cb(GObject *object,
			   GAsyncResult *res,
			   gpointer user_data)
{
	struct mds_ctx *mds_ctx = talloc_get_type_abort(user_data, struct mds_ctx);
	GError *error = NULL;

	mds_ctx->tracker_con = tracker_sparql_connection_get_finish(res,
								    &error);
	if (error) {
		DEBUG(1, ("Could not connect to Tracker: %s\n",
			  error->message));
		g_error_free(error);
	}

	DEBUG(10, ("connected to Tracker\n"));
	g_main_loop_quit(mds_ctx->gmainloop);
}

static void tracker_cursor_cb_destroy_done(struct tevent_req *subreq);

static void tracker_cursor_cb(GObject *object,
			      GAsyncResult *res,
			      gpointer user_data)
{
	GError *error = NULL;
	struct sl_query *slq = talloc_get_type_abort(user_data, struct sl_query);
	gboolean more_results;
	const gchar *uri;
	char *path;
	int result;
	struct stat_ex sb;
	uint64_t ino64;
	bool ok;
	struct tevent_req *req;

	SLQ_DEBUG(10, slq, "tracker_cursor_cb");

	more_results = tracker_sparql_cursor_next_finish(slq->tracker_cursor,
							 res,
							 &error);

	if (slq->state == SLQ_STATE_DONE) {
		/*
		 * The query was closed in slrpc_close_query(), so we
		 * don't care for results or errors from
		 * tracker_sparql_cursor_next_finish(), we just go
		 * ahead and schedule deallocation of the slq handle.
		 *
		 * We have to shedule the deallocation via tevent,
		 * because we have to unref the cursor glib object and
		 * we can't do it here, because it's still used after
		 * we return.
		 */
		SLQ_DEBUG(10, slq, "closed");
		g_main_loop_quit(slq->mds_ctx->gmainloop);

		req = slq_destroy_send(slq, server_event_context(), &slq);
		if (req == NULL) {
			slq->state = SLQ_STATE_ERROR;
			return;
		}
		tevent_req_set_callback(req, tracker_cursor_cb_destroy_done, NULL);
		return;
	}

	if (error) {
		DEBUG(1, ("Tracker cursor: %s\n", error->message));
		g_error_free(error);
		slq->state = SLQ_STATE_ERROR;
		g_main_loop_quit(slq->mds_ctx->gmainloop);
		return;
	}

	if (!more_results) {
		slq->state = SLQ_STATE_DONE;
		g_main_loop_quit(slq->mds_ctx->gmainloop);
		return;
	}

	uri = tracker_sparql_cursor_get_string(slq->tracker_cursor, 0, NULL);
	if (uri == NULL) {
		DEBUG(1, ("error fetching Tracker URI\n"));
		slq->state = SLQ_STATE_ERROR;
		g_main_loop_quit(slq->mds_ctx->gmainloop);
		return;
	}
	path = tracker_to_unix_path(slq->query_results, uri);
	if (path == NULL) {
		DEBUG(1, ("error converting Tracker URI to path: %s\n", uri));
		slq->state = SLQ_STATE_ERROR;
		g_main_loop_quit(slq->mds_ctx->gmainloop);
		return;
	}

	if (geteuid() != slq->mds_ctx->uid) {
		DEBUG(0, ("uid mismatch: %d/%d\n", geteuid(), slq->mds_ctx->uid));
		smb_panic("uid mismatch");
	}

	result = sys_stat(path, &sb, false);
	if (result != 0) {
		goto done;
	}
	result = access(path, R_OK);
	if (result != 0) {
		goto done;
	}

	ino64 = sb.st_ex_ino;
	if (slq->cnids) {
		/*
		 * Check whether the found element is in the requested
		 * set of IDs. Note that we're faking CNIDs by using
		 * filesystem inode numbers here
		 */
		ok = bsearch(&ino64, slq->cnids, slq->cnids_num,
			     sizeof(uint64_t), cnid_comp_fn);
		if (!ok) {
			goto done;
		}
	}

	/*
	 * Add inode number and filemeta to result set, this is what
	 * we return as part of the result set of a query
	 */
	result = dalloc_add_copy(slq->query_results->cnids->ca_cnids,
				 &ino64, uint64_t);
	if (result != 0) {
		DEBUG(1, ("dalloc error\n"));
		slq->state = SLQ_STATE_ERROR;
		g_main_loop_quit(slq->mds_ctx->gmainloop);
		return;
	}
	ok = add_filemeta(slq->reqinfo, slq->query_results->fm_array,
			  path, &sb);
	if (!ok) {
		DEBUG(1, ("add_filemeta error\n"));
		slq->state = SLQ_STATE_ERROR;
		g_main_loop_quit(slq->mds_ctx->gmainloop);
		return;
	}

	ok = inode_map_add(slq, ino64, path);
	if (!ok) {
		DEBUG(1, ("inode_map_add error\n"));
		slq->state = SLQ_STATE_ERROR;
		g_main_loop_quit(slq->mds_ctx->gmainloop);
		return;
	}

	slq->query_results->num_results++;

done:
	if (slq->query_results->num_results >= MAX_SL_RESULTS) {
		slq->state = SLQ_STATE_FULL;
		SLQ_DEBUG(10, slq, "full");
		g_main_loop_quit(slq->mds_ctx->gmainloop);
		return;
	}

	slq->state = SLQ_STATE_RESULTS;
	SLQ_DEBUG(10, slq, "cursor next");
	tracker_sparql_cursor_next_async(slq->tracker_cursor,
					 slq->gcancellable,
					 tracker_cursor_cb,
					 slq);
}

static void tracker_cursor_cb_destroy_done(struct tevent_req *req)
{
	slq_destroy_recv(req);
	TALLOC_FREE(req);

	DEBUG(10, ("%s\n", __func__));
}

static void tracker_query_cb(GObject *object,
			     GAsyncResult *res,
			     gpointer user_data)
{
	GError *error = NULL;
	struct sl_query *slq = talloc_get_type_abort(user_data, struct sl_query);

	SLQ_DEBUG(10, slq, "tracker_query_cb");

	slq->tracker_cursor = tracker_sparql_connection_query_finish(
		TRACKER_SPARQL_CONNECTION(object),
		res,
		&error);
	if (error) {
		slq->state = SLQ_STATE_ERROR;
		DEBUG(1, ("Tracker query error: %s\n", error->message));
		g_error_free(error);
		g_main_loop_quit(slq->mds_ctx->gmainloop);
		return;
	}

	if (slq->state == SLQ_STATE_DONE) {
		SLQ_DEBUG(10, slq, "done");
		g_main_loop_quit(slq->mds_ctx->gmainloop);
		talloc_free(slq);
		return;
	}

	slq->state = SLQ_STATE_RESULTS;

	tracker_sparql_cursor_next_async(slq->tracker_cursor,
					 slq->gcancellable,
					 tracker_cursor_cb,
					 slq);
}

/***********************************************************
 * Spotlight RPC functions
 ***********************************************************/

static bool slrpc_fetch_properties(struct mds_ctx *mds_ctx,
				   const DALLOC_CTX *query, DALLOC_CTX *reply)
{
	sl_dict_t *dict;
	sl_array_t *array;
	char *s;
	uint64_t u;
	sl_bool_t b;
	sl_uuid_t uuid;
	int result;

	dict = dalloc_zero(reply, sl_dict_t);
	if (dict == NULL) {
		return false;
	}

	/* kMDSStoreHasPersistentUUID = false */
	result = dalloc_stradd(dict, "kMDSStoreHasPersistentUUID");
	if (result != 0) {
		return false;
	}
	b = false;
	result = dalloc_add_copy(dict, &b, sl_bool_t);
	if (result != 0) {
		return false;
	}

	/* kMDSStoreIsBackup = false */
	result = dalloc_stradd(dict, "kMDSStoreIsBackup");
	if (result != 0) {
		return false;
	}
	b = false;
	result = dalloc_add_copy(dict, &b, sl_bool_t);
	if (result != 0) {
		return false;
	}

	/* kMDSStoreUUID = uuid */
	result = dalloc_stradd(dict, "kMDSStoreUUID");
	if (result != 0) {
		return false;
	}
	memcpy(uuid.sl_uuid, "fakeuuidfakeuuid", sizeof(uuid.sl_uuid));
	result = dalloc_add_copy(dict, &uuid, sl_uuid_t);
	if (result != 0) {
		return false;
	}

	/* kMDSStoreSupportsVolFS = true */
	result = dalloc_stradd(dict, "kMDSStoreSupportsVolFS");
	if (result != 0) {
		return false;
	}
	b = true;
	result = dalloc_add_copy(dict, &b, sl_bool_t);
	if (result != 0) {
		return false;
	}

	/* kMDSVolumeUUID = uuid */
	result = dalloc_stradd(dict, "kMDSVolumeUUID");
	if (result != 0) {
		return false;
	}
	memcpy(uuid.sl_uuid, "fakeuuidfakeuuid", sizeof(uuid.sl_uuid));
	result = dalloc_add_copy(dict, &uuid, sl_uuid_t);
	if (result != 0) {
		return false;
	}

	/* kMDSDiskStoreSpindleNumber = 1 (fake) */
	result = dalloc_stradd(dict, "kMDSDiskStoreSpindleNumber");
	if (result != 0) {
		return false;
	}
	u = 1;
	result = dalloc_add_copy(dict, &u, uint64_t);
	if (result != 0) {
		return false;
	}

	/* kMDSDiskStorePolicy = 3 (whatever that means, taken from OS X) */
	result = dalloc_stradd(dict, "kMDSDiskStorePolicy");
	if (result != 0) {
		return false;
	}
	u = 3;
	result = dalloc_add_copy(dict, &u, uint64_t);
	if (result != 0) {
		return false;
	}

	/* kMDSStoreMetaScopes array */
	array = dalloc_zero(dict, sl_array_t);
	if (array == NULL) {
		return NULL;
	}
	result = dalloc_stradd(array, "kMDQueryScopeComputer");
	if (result != 0) {
		return false;
	}
	result = dalloc_stradd(array, "kMDQueryScopeAllIndexed");
	if (result != 0) {
		return false;
	}
	result = dalloc_stradd(array, "kMDQueryScopeComputerIndexed");
	if (result != 0) {
		return false;
	}
	result = dalloc_add(dict, array, sl_array_t);
	if (result != 0) {
		return false;
	}

	/* kMDSStoreDevice = 0x1000003 (whatever that means, taken from OS X) */
	result = dalloc_stradd(dict, "kMDSStoreDevice");
	if (result != 0) {
		return false;
	}
	u = 0x1000003;
	result = dalloc_add_copy(dict, &u, uint64_t);
	if (result != 0) {
		return false;
	}

	/* kMDSStoreSupportsTCC = true (whatever that means, taken from OS X) */
	result = dalloc_stradd(dict, "kMDSStoreSupportsTCC");
	if (result != 0) {
		return false;
	}
	b = true;
	result = dalloc_add_copy(dict, &b, sl_bool_t);
	if (result != 0) {
		return false;
	}

	/* kMDSStorePathScopes = ["/"] (whatever that means, taken from OS X) */
	result = dalloc_stradd(dict, "kMDSStorePathScopes");
	if (result != 0) {
		return false;
	}
	array = dalloc_zero(dict, sl_array_t);
	if (array == NULL) {
		return false;
	}
	s = talloc_strdup(dict, "/");
	if (s == NULL) {
		return false;
	}
	talloc_set_name(s, "smb_ucs2_t *");
	result = dalloc_add(array, s, smb_ucs2_t *);
	if (result != 0) {
		return false;
	}
	result = dalloc_add(dict, array, sl_array_t);
	if (result != 0) {
		return false;
	}

	result = dalloc_add(reply, dict, sl_dict_t);
	if (result != 0) {
		return false;
	}

	return true;
}

static void slq_close_timer(struct tevent_context *ev,
			    struct tevent_timer *te,
			    struct timeval current_time,
			    void *private_data)
{
	struct sl_query *slq = talloc_get_type_abort(
		private_data, struct sl_query);
	struct mds_ctx *mds_ctx = slq->mds_ctx;

	SLQ_DEBUG(10, slq, "expired");

	TALLOC_FREE(slq);

	if (CHECK_DEBUGLVL(10)) {
		for (slq = mds_ctx->query_list; slq != NULL; slq = slq->next) {
			SLQ_DEBUG(10, slq, "pending");
		}
	}
}

/**
 * Begin a search query
 **/
static bool slrpc_open_query(struct mds_ctx *mds_ctx,
			     const DALLOC_CTX *query, DALLOC_CTX *reply)
{
	bool ok;
	uint64_t sl_result;
	uint64_t *uint64p;
	DALLOC_CTX *reqinfo;
	sl_array_t *array, *path_scope;
	sl_cnids_t *cnids;
	struct sl_query *slq = NULL;
	int result;
	char *querystring;

	array = dalloc_zero(reply, sl_array_t);
	if (array == NULL) {
		return false;
	}

	if (mds_ctx->tracker_con == NULL) {
		DEBUG(1, ("no connection to Tracker\n"));
		goto error;
	}

	/* Allocate and initialize query object */
	slq = talloc_zero(mds_ctx, struct sl_query);
	if (slq == NULL) {
		return false;
	}
	slq->entries_ctx = talloc_named_const(slq, 0, "struct sl_query.entries_ctx");
	if (slq->entries_ctx == NULL) {
		TALLOC_FREE(slq);
		return false;
	}
	talloc_set_destructor(slq, slq_destructor_cb);
	slq->state = SLQ_STATE_NEW;
	slq->mds_ctx = mds_ctx;

	slq->last_used = timeval_current();
	slq->start_time = slq->last_used;
	slq->expire_time = timeval_add(&slq->last_used, MAX_SL_RUNTIME, 0);
	slq->te = tevent_add_timer(server_event_context(), slq,
				   slq->expire_time, slq_close_timer, slq);
	if (slq->te == NULL) {
		DEBUG(1, ("tevent_add_timer failed\n"));
		goto error;
	}

	slq->gcancellable = g_cancellable_new();
	if (slq->gcancellable == NULL) {
		DEBUG(1,("error from g_cancellable_new\n"));
		goto error;
	}

	querystring = dalloc_value_for_key(query, "DALLOC_CTX", 0,
					   "DALLOC_CTX", 1,
					   "kMDQueryString");
	if (querystring == NULL) {
		DEBUG(1, ("missing kMDQueryString\n"));
		goto error;
	}
	slq->query_string = talloc_strdup(slq, querystring);
	if (slq->query_string == NULL) {
		DEBUG(1, ("out of memory\n"));
		goto error;
	}

	/*
	 * FIXME: convert spotlight query charset from decomposed UTF8
	 * to host charset precomposed UTF8.
	 */

	uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
			     "uint64_t", 1);
	if (uint64p == NULL) {
		goto error;
	}
	slq->ctx1 = *uint64p;
	uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
			     "uint64_t", 2);
	if (uint64p == NULL) {
		goto error;
	}
	slq->ctx2 = *uint64p;

	path_scope = dalloc_value_for_key(query, "DALLOC_CTX", 0,
					  "DALLOC_CTX", 1, "kMDScopeArray");
	if (path_scope == NULL) {
		goto error;
	}

	slq->path_scope = dalloc_get(path_scope, "char *", 0);
	if (slq->path_scope == NULL) {
		goto error;
	}

	slq->path_scope = talloc_strdup(slq, slq->path_scope);
	if (slq->path_scope == NULL) {
		goto error;
	}


	reqinfo = dalloc_value_for_key(query, "DALLOC_CTX", 0,
				       "DALLOC_CTX", 1, "kMDAttributeArray");
	if (reqinfo == NULL) {
		goto error;
	}

	slq->reqinfo = talloc_steal(slq, reqinfo);
	DEBUG(10, ("requested attributes: %s", mds_dalloc_dump(reqinfo, 0)));

	cnids = dalloc_value_for_key(query, "DALLOC_CTX", 0,
				     "DALLOC_CTX", 1, "kMDQueryItemArray");
	if (cnids) {
		ok = sort_cnids(slq, cnids->ca_cnids);
		if (!ok) {
			goto error;
		}
	}

	ok = create_result_handle(slq);
	if (!ok) {
		DEBUG(1, ("create_result_handle error\n"));
		slq->state = SLQ_STATE_ERROR;
		goto error;
	}

	SLQ_DEBUG(10, slq, "new");

	DLIST_ADD(mds_ctx->query_list, slq);

	ok = map_spotlight_to_sparql_query(slq);
	if (!ok) {
		/*
		 * Two cases:
		 *
		 * 1) the query string is "false", the parser returns
		 * an error for that. We're supposed to return -1
		 * here.
		 *
		 * 2) the parsing really failed, in that case we're
		 * probably supposed to return -1 too, this needs
		 * verification though
		 */
		SLQ_DEBUG(10, slq, "map failed");
		goto error;
	}

	DEBUG(10, ("SPARQL query: \"%s\"\n", slq->sparql_query));

	g_main_context_push_thread_default(mds_ctx->gcontext);
	tracker_sparql_connection_query_async(mds_ctx->tracker_con,
					      slq->sparql_query,
					      slq->gcancellable,
					      tracker_query_cb,
					      slq);
	g_main_context_pop_thread_default(mds_ctx->gcontext);
	slq->state = SLQ_STATE_RUNNING;

	sl_result = 0;
	result = dalloc_add_copy(array, &sl_result, uint64_t);
	if (result != 0) {
		goto error;
	}
	result = dalloc_add(reply, array, sl_array_t);
	if (result != 0) {
		goto error;
	}
	return true;

error:
	sl_result = UINT64_MAX;
	TALLOC_FREE(slq);
	result = dalloc_add_copy(array, &sl_result, uint64_t);
	if (result != 0) {
		return false;
	}
	result = dalloc_add(reply, array, sl_array_t);
	if (result != 0) {
		return false;
	}
	return true;
}

/**
 * Fetch results of a query
 **/
static bool slrpc_fetch_query_results(struct mds_ctx *mds_ctx,
				      const DALLOC_CTX *query,
				      DALLOC_CTX *reply)
{
	bool ok;
	struct sl_query *slq = NULL;
	uint64_t *uint64p, ctx1, ctx2;
	uint64_t status;
	sl_array_t *array;
	int result;

	array = dalloc_zero(reply, sl_array_t);
	if (array == NULL) {
		return false;
	}

	/* Get query for context */
	uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
			     "uint64_t", 1);
	if (uint64p == NULL) {
		goto error;
	}
	ctx1 = *uint64p;

	uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
			     "uint64_t", 2);
	if (uint64p == NULL) {
		goto error;
	}
	ctx2 = *uint64p;

	slq = slq_for_ctx(mds_ctx, ctx1, ctx2);
	if (slq == NULL) {
		DEBUG(1, ("bad context: [0x%jx,0x%jx]\n",
			  (uintmax_t)ctx1, (uintmax_t)ctx2));
		goto error;
	}

	TALLOC_FREE(slq->te);
	slq->last_used = timeval_current();
	slq->expire_time = timeval_add(&slq->last_used, MAX_SL_RUNTIME, 0);
	slq->te = tevent_add_timer(server_event_context(), slq,
				   slq->expire_time, slq_close_timer, slq);
	if (slq->te == NULL) {
		DEBUG(1, ("tevent_add_timer failed\n"));
		goto error;
	}

	SLQ_DEBUG(10, slq, "fetch");

	switch (slq->state) {
	case SLQ_STATE_RUNNING:
	case SLQ_STATE_RESULTS:
	case SLQ_STATE_FULL:
	case SLQ_STATE_DONE:
		ok = add_results(array, slq);
		if (!ok) {
			DEBUG(1, ("error adding results\n"));
			goto error;
		}
		if (slq->state == SLQ_STATE_FULL) {
			slq->state = SLQ_STATE_RESULTS;
			g_main_context_push_thread_default(mds_ctx->gcontext);
			tracker_sparql_cursor_next_async(
				slq->tracker_cursor,
				slq->gcancellable,
				tracker_cursor_cb,
				slq);
			g_main_context_pop_thread_default(mds_ctx->gcontext);
		}
		break;

	case SLQ_STATE_ERROR:
		DEBUG(1, ("query in error state\n"));
		goto error;

	default:
		DEBUG(1, ("unexpected query state %d\n", slq->state));
		goto error;
	}

	result = dalloc_add(reply, array, sl_array_t);
	if (result != 0) {
		goto error;
	}
	return true;

error:
	status = UINT64_MAX;
	TALLOC_FREE(slq);
	result = dalloc_add_copy(array, &status, uint64_t);
	if (result != 0) {
		return false;
	}
	result = dalloc_add(reply, array, sl_array_t);
	if (result != 0) {
		return false;
	}
	return true;
}

/**
 * Store metadata attributes for a CNID
 **/
static bool slrpc_store_attributes(struct mds_ctx *mds_ctx,
				   const DALLOC_CTX *query, DALLOC_CTX *reply)
{
	uint64_t sl_result;
	sl_array_t *array;
	int result;

	array = dalloc_zero(reply, sl_array_t);
	if (array == NULL) {
		return false;
	}

	/*
	 * FIXME: not implemented. Used by the client for eg setting
	 * the modification date of the shared directory which clients
	 * poll indicating changes on the share and cause the client
	 * to refresh view.
	 */

	sl_result = 0;
	result = dalloc_add_copy(array, &sl_result, uint64_t);
	if (result != 0) {
		return false;
	}
	result = dalloc_add(reply, array, sl_array_t);
	if (result != 0) {
		return false;
	}

	return true;
}

/**
 * Fetch supported metadata attributes for a CNID
 **/
static bool slrpc_fetch_attributenames(struct mds_ctx *mds_ctx,
				       const DALLOC_CTX *query,
				       DALLOC_CTX *reply)
{
	uint64_t id;
	sl_cnids_t *cnids;
	sl_array_t *array;
	uint64_t sl_result;
	sl_cnids_t *replycnids;
	sl_array_t *mdattrs;
	sl_filemeta_t *fmeta;
	int result;
	void *p;

	cnids = dalloc_get(query, "DALLOC_CTX", 0, "sl_cnids_t", 1);
	if (cnids == NULL) {
		return false;
	}

	p = dalloc_get_object(cnids->ca_cnids, 0);
	if (p == NULL) {
		return NULL;
	}
	memcpy(&id, p, sizeof(uint64_t));

	/* Result array */
	array = dalloc_zero(reply, sl_array_t);
	if (array == NULL) {
		return false;
	}

	result = dalloc_add(reply, array, sl_array_t);
	if (result != 0) {
		return false;
	}

	/* Return result value 0 */
	sl_result = 0;
	result = dalloc_add_copy(array, &sl_result, uint64_t);
	if (result != 0) {
		return false;
	}

	/* Return CNID array */
	replycnids = talloc_zero(reply, sl_cnids_t);
	if (replycnids == NULL) {
		return false;
	}

	replycnids->ca_cnids = dalloc_new(cnids);
	if (replycnids->ca_cnids == NULL) {
		return false;
	}

	replycnids->ca_unkn1 = 0xfec;
	replycnids->ca_context = cnids->ca_context;
	result = dalloc_add_copy(replycnids->ca_cnids, &id, uint64_t);
	if (result != 0) {
		return false;
	}
	result = dalloc_add(array, replycnids, sl_cnids_t);
	if (result != 0) {
		return false;
	}

	/*
	 * FIXME: this should return the real attributes from all
	 * known metadata sources (Tracker and filesystem)
	 */
	mdattrs = dalloc_zero(reply, sl_array_t);
	if (mdattrs == NULL) {
		return false;
	}

	result = dalloc_stradd(mdattrs, "kMDItemFSName");
	if (result != 0) {
		return false;
	}
	result = dalloc_stradd(mdattrs, "kMDItemDisplayName");
	if (result != 0) {
		return false;
	}
	result = dalloc_stradd(mdattrs, "kMDItemFSSize");
	if (result != 0) {
		return false;
	}
	result = dalloc_stradd(mdattrs, "kMDItemFSOwnerUserID");
	if (result != 0) {
		return false;
	}
	result = dalloc_stradd(mdattrs, "kMDItemFSOwnerGroupID");
	if (result != 0) {
		return false;
	}
	result = dalloc_stradd(mdattrs, "kMDItemFSContentChangeDate");
	if (result != 0) {
		return false;
	}

	fmeta = dalloc_zero(reply, sl_filemeta_t);
	if (fmeta == NULL) {
		return false;
	}
	result = dalloc_add(fmeta, mdattrs, sl_array_t);
	if (result != 0) {
		return false;
	}
	result = dalloc_add(array, fmeta, sl_filemeta_t);
	if (result != 0) {
		return false;
	}

	return true;
}

/**
 * Fetch metadata attribute values for a CNID
 **/
static bool slrpc_fetch_attributes(struct mds_ctx *mds_ctx,
				   const DALLOC_CTX *query, DALLOC_CTX *reply)
{
	int result;
	bool ok;
	sl_array_t *array;
	sl_cnids_t *cnids;
	sl_cnids_t *replycnids;
	sl_array_t *reqinfo;
	uint64_t ino;
	uint64_t sl_result;
	sl_filemeta_t *fm;
	sl_array_t *fm_array;
	sl_nil_t nil;
	struct stat_ex sb;
	struct sl_inode_path_map *elem = NULL;
	void *p;
	TDB_DATA val = tdb_null;
	NTSTATUS status;

	array = dalloc_zero(reply, sl_array_t);
	if (array == NULL) {
		return false;
	}
	replycnids = talloc_zero(reply, sl_cnids_t);
	if (replycnids == NULL) {
		goto error;
	}
	replycnids->ca_cnids = dalloc_new(replycnids);
	if (replycnids->ca_cnids == NULL) {
		goto error;
	}
	fm = dalloc_zero(array, sl_filemeta_t);
	if (fm == NULL) {
		goto error;
	}
	fm_array = dalloc_zero(fm, sl_array_t);
	if (fm_array == NULL) {
		goto error;
	}
	/* For some reason the list of results always starts with a nil entry */
	result = dalloc_add_copy(fm_array, &nil, sl_nil_t);
	if (result == -1) {
		goto error;
	}

	reqinfo = dalloc_get(query, "DALLOC_CTX", 0, "sl_array_t", 1);
	if (reqinfo == NULL) {
		goto error;
	}

	cnids = dalloc_get(query, "DALLOC_CTX", 0, "sl_cnids_t", 2);
	if (cnids == NULL) {
		goto error;
	}
	p = dalloc_get_object(cnids->ca_cnids, 0);
	if (p == NULL) {
		goto error;
	}
	memcpy(&ino, p, sizeof(uint64_t));

	replycnids->ca_unkn1 = 0xfec;
	replycnids->ca_context = cnids->ca_context;
	result = dalloc_add_copy(replycnids->ca_cnids, &ino, uint64_t);
	if (result != 0) {
		goto error;
	}

	status = dbwrap_fetch(mds_ctx->ino_path_map, reply,
			      make_tdb_data((void*)&ino, sizeof(uint64_t)),
			      &val);
	if (!NT_STATUS_IS_OK(status)) {
		DEBUG(1, ("Failed to fetch inode: %s\n", nt_errstr(status)));
		goto error;
	}
	if (val.dsize != sizeof(p)) {
		DEBUG(1, ("invalid record pointer size: %zd\n", val.dsize));
		TALLOC_FREE(val.dptr);
		goto error;
	}

	memcpy(&p, val.dptr, sizeof(p));
	elem = talloc_get_type_abort(p, struct sl_inode_path_map);

	result = sys_stat(elem->path, &sb, false);
	if (result != 0) {
		goto error;
	}

	ok = add_filemeta(reqinfo, fm_array, elem->path, &sb);
	if (!ok) {
		goto error;
	}

	sl_result = 0;
	result = dalloc_add_copy(array, &sl_result, uint64_t);
	if (result != 0) {
		goto error;
	}
	result = dalloc_add(array, replycnids, sl_cnids_t);
	if (result != 0) {
		goto error;
	}
	result = dalloc_add(fm, fm_array, sl_array_t);
	if (result != 0) {
		goto error;
	}
	result = dalloc_add(array, fm, sl_filemeta_t);
	if (result != 0) {
		goto error;
	}
	result = dalloc_add(reply, array, sl_array_t);
	if (result != 0) {
		goto error;
	}

	return true;

error:
	sl_result = UINT64_MAX;
	result = dalloc_add_copy(array, &sl_result, uint64_t);
	if (result != 0) {
		return false;
	}
	result = dalloc_add(reply, array, sl_array_t);
	if (result != 0) {
		return false;
	}

	return true;
}

/**
 * Close a query
 **/
static bool slrpc_close_query(struct mds_ctx *mds_ctx,
			      const DALLOC_CTX *query, DALLOC_CTX *reply)
{
	struct sl_query *slq = NULL;
	uint64_t *uint64p, ctx1, ctx2;
	sl_array_t *array;
	uint64_t sl_res;
	int result;

	array = dalloc_zero(reply, sl_array_t);
	if (array == NULL) {
		return false;
	}

	/* Context */
	uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
			     "uint64_t", 1);
	if (uint64p == NULL) {
		goto done;
	}
	ctx1 = *uint64p;

	uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
			     "uint64_t", 2);
	if (uint64p == NULL) {
		goto done;
	}
	ctx2 = *uint64p;

	/* Get query for context and free it */
	slq = slq_for_ctx(mds_ctx, ctx1, ctx2);
	if (slq == NULL) {
		DEBUG(1, ("bad context: [0x%jx,0x%jx]\n",
			  (uintmax_t)ctx1, (uintmax_t)ctx2));
		goto done;
	}

	switch (slq->state) {
	case SLQ_STATE_RUNNING:
	case SLQ_STATE_RESULTS:
		DEBUG(10, ("close: requesting query close\n"));
		/*
		 * Mark the query is done so the cursor callback can
		 * act accordingly by stopping to request more results
		 * and sheduling query resource deallocation via
		 * tevent.
		 */
		slq->state = SLQ_STATE_DONE;
		break;

	case SLQ_STATE_FULL:
	case SLQ_STATE_DONE:
		DEBUG(10, ("close: query was done or result queue was full\n"));
		/*
		 * We can directly deallocate the query because there
		 * are no pending Tracker async calls in flight in
		 * these query states.
		 */
		TALLOC_FREE(slq);
		break;

	default:
		DEBUG(1, ("close: unexpected state: %d\n", slq->state));
		break;
	}


done:
	sl_res = 0;
	result = dalloc_add_copy(array, &sl_res, uint64_t);
	if (result != 0) {
		return false;
	}
	result = dalloc_add(reply, array, sl_array_t);
	if (result != 0) {
		return false;
	}
	return true;
}

/**
 * Init callbacks at startup
 **/
bool mds_init(struct messaging_context *msg_ctx)
{
#if (GLIB_MAJOR_VERSION < 3) && (GLIB_MINOR_VERSION < 36)
	g_type_init();
#endif
	return true;
}

bool mds_shutdown(void)
{
	return true;
}

static gboolean gmainloop_timer(gpointer user_data)
{
	struct mds_ctx *ctx = talloc_get_type_abort(user_data, struct mds_ctx);

	DEBUG(10,("%s\n", __func__));
	g_main_loop_quit(ctx->gmainloop);

	return G_SOURCE_CONTINUE;
}

/**
 * Initialise a context per share handle
 **/
struct mds_ctx *mds_init_ctx(TALLOC_CTX *mem_ctx,
			     const struct auth_session_info *session_info,
			     const char *path)
{
	struct mds_ctx *mds_ctx;

	mds_ctx = talloc_zero(mem_ctx, struct mds_ctx);
	if (mds_ctx == NULL) {
		return NULL;
	}
	talloc_set_destructor(mds_ctx, mds_ctx_destructor_cb);

	mds_ctx->spath = talloc_strdup(mds_ctx, path);
	if (mds_ctx->spath == NULL) {
		goto error;
	}

	if (session_info->security_token->num_sids < 1) {
		goto error;
	}
	sid_copy(&mds_ctx->sid, &session_info->security_token->sids[0]);
	mds_ctx->uid = session_info->unix_token->uid;

	mds_ctx->ino_path_map = db_open_rbt(mds_ctx);
	if (mds_ctx->ino_path_map == NULL) {
		DEBUG(1,("open inode map db failed\n"));
		goto error;
	}

	mds_ctx->gcontext = g_main_context_new();
	if (mds_ctx->gcontext == NULL) {
		DEBUG(1,("error from g_main_context_new\n"));
		goto error;
	}

	mds_ctx->gmainloop = g_main_loop_new(mds_ctx->gcontext, false);
	if (mds_ctx->gmainloop == NULL) {
		DEBUG(1,("error from g_main_loop_new\n"));
		goto error;
	}

	g_main_context_push_thread_default(mds_ctx->gcontext);
	tracker_sparql_connection_get_async(mds_ctx->gcancellable,
					    tracker_con_cb, mds_ctx);
	g_main_context_pop_thread_default(mds_ctx->gcontext);

	return mds_ctx;

error:
	TALLOC_FREE(mds_ctx);
	return NULL;
}

/**
 * Tear down connections and free all resources
 **/
int mds_ctx_destructor_cb(struct mds_ctx *mds_ctx)
{
	/*
	 * We need to free query_list before ino_path_map
	 */
	while (mds_ctx->query_list != NULL) {
		/*
		 * slq destructor removes element from list.
		 * Don't use TALLOC_FREE()!
		 */
		talloc_free(mds_ctx->query_list);
	}
	TALLOC_FREE(mds_ctx->ino_path_map);

	if (mds_ctx->tracker_con != NULL) {
		g_object_unref(mds_ctx->tracker_con);
	}
	if (mds_ctx->gcancellable != NULL) {
		g_cancellable_cancel(mds_ctx->gcancellable);
		g_object_unref(mds_ctx->gcancellable);
	}
	if (mds_ctx->gmainloop != NULL) {
		g_main_loop_unref(mds_ctx->gmainloop);
	}
	if (mds_ctx->gcontext != NULL) {
		g_main_context_unref(mds_ctx->gcontext);
	}

	ZERO_STRUCTP(mds_ctx);

	return 0;
}

static bool mds_run_gmainloop(struct mds_ctx *mds_ctx, guint timeout)
{
	guint timer_id;
	GSource *timer;

	/*
	 * It seems the event processing of the libtracker-sparql
	 * async subsystem defers callbacks until *all* events are
	 * processes by the async subsystem main processing loop.
	 *
	 * g_main_context_iteration(may_block=FALSE) can't be used,
	 * because a search that produces a few thousand matches
	 * generates as many events that must be processed in either
	 * g_main_context_iteration() or g_main_loop_run() before
	 * callbacks are called.
	 *
	 * Unfortunately g_main_context_iteration() only processes a
	 * small subset of these event (1-30) at a time when run in
	 * mds_dispatch(), which happens once a second while the
	 * client polls for results.
	 *
	 * Carefully using the blocking g_main_loop_run() fixes
	 * this. It processes events until we exit from the loop at
	 * defined exit points. By adding a 1 ms timeout we at least
	 * try to get as close as possible to non-blocking behaviour.
	 */

	if (!g_main_context_pending(mds_ctx->gcontext)) {
		return true;
	}

	g_main_context_push_thread_default(mds_ctx->gcontext);

	timer = g_timeout_source_new(timeout);
	if (timer == NULL) {
		DEBUG(1,("g_timeout_source_new_seconds\n"));
		g_main_context_pop_thread_default(mds_ctx->gcontext);
		return false;
	}

	timer_id = g_source_attach(timer, mds_ctx->gcontext);
	if (timer_id == 0) {
		DEBUG(1,("g_timeout_add failed\n"));
		g_source_destroy(timer);
		g_main_context_pop_thread_default(mds_ctx->gcontext);
		return false;
	}

	g_source_set_callback(timer, gmainloop_timer, mds_ctx, NULL);

	g_main_loop_run(mds_ctx->gmainloop);

	g_source_destroy(timer);

	g_main_context_pop_thread_default(mds_ctx->gcontext);
	return true;
}

/**
 * Dispatch a Spotlight RPC command
 **/
bool mds_dispatch(struct mds_ctx *mds_ctx,
		  struct mdssvc_blob *request_blob,
		  struct mdssvc_blob *response_blob)
{
	bool ok;
	ssize_t len;
	DALLOC_CTX *query = NULL;
	DALLOC_CTX *reply = NULL;
	char *rpccmd;
	const struct slrpc_cmd *slcmd;

	if (CHECK_DEBUGLVL(10)) {
		const struct sl_query *slq;

		for (slq = mds_ctx->query_list; slq != NULL; slq = slq->next) {
			SLQ_DEBUG(10, slq, "pending");
		}
	}

	response_blob->length = 0;

	/*
	 * Process finished glib events.
	 *
	 * FIXME: integrate with tevent instead of piggy packing it
	 * onto the processing of new requests.
	 *
	 * mds_dispatch() is called by the client a few times in a row:
	 *
	 * - first in order to open/start a search query
	 *
	 * - later in order to fetch results asynchronously, typically
	 *   once a second. If no results have been retrieved from the
	 *   search store (Tracker) yet, we return no results.
	 *   The client asks for more results every second as long
	 *   as the "Search Window" in the client gui is open.
	 *
	 * - at some point the query is closed
	 *
	 * This means we try to iterate through the glib event loop
	 * before processing the request in order to get result
	 * from tracker which can be returned to the client.
	 */

	ok = mds_run_gmainloop(mds_ctx, MDS_TRACKER_ASYNC_TIMEOUT_MS);
	if (!ok) {
		goto cleanup;
	}

	DEBUG(10, ("share path: %s\n", mds_ctx->spath));

	query = dalloc_new(mds_ctx);
	if (query == NULL) {
		ok = false;
		goto cleanup;
	}
	reply = dalloc_new(mds_ctx);
	if (reply == NULL) {
		ok = false;
		goto cleanup;
	}

	ok = sl_unpack(query, (char *)request_blob->spotlight_blob,
		       request_blob->length);
	if (!ok) {
		DEBUG(1, ("error unpacking Spotlight RPC blob\n"));
		goto cleanup;
	}

	DEBUG(5, ("%s", mds_dalloc_dump(query, 0)));

	rpccmd = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
			    "char *", 0);
	if (rpccmd == NULL) {
		DEBUG(1, ("missing primary Spotlight RPC command\n"));
		ok = false;
		goto cleanup;
	}

	DEBUG(10, ("Spotlight RPC cmd: %s\n", rpccmd));

	slcmd = slrpc_cmd_by_name(rpccmd);
	if (slcmd == NULL) {
		DEBUG(1, ("unsupported primary Spotlight RPC command %s\n",
			  rpccmd));
		ok = false;
		goto cleanup;
	}

	/*
	 * If these functions return an error, they hit something like
	 * a non recoverable talloc error
	 */
	ok = slcmd->function(mds_ctx, query, reply);
	if (!ok) {
		DEBUG(1, ("error in Spotlight RPC handler\n"));
		goto cleanup;
	}

	DEBUG(5, ("%s", mds_dalloc_dump(reply, 0)));

	len = sl_pack(reply, (char *)response_blob->spotlight_blob,
		      response_blob->size);
	if (len == -1) {
		DEBUG(1, ("error packing Spotlight RPC reply\n"));
		ok = false;
		goto cleanup;
	}

	/*
	 * Run g_main_loop a second time in order to dispatch events
	 * that may have been queued at the libtracker-sparql level.
	 * As we only want to dispatch (write out requests) but not
	 * wait for anything, we use a much shorter timeout here.
	 */
	ok = mds_run_gmainloop(mds_ctx, MDS_TRACKER_ASYNC_TIMEOUT_MS / 10);
	if (!ok) {
		goto cleanup;
	}

	response_blob->length = len;

cleanup:
	talloc_free(query);
	talloc_free(reply);
	return ok;
}
