diff options
Diffstat (limited to 'source3/rpc_server/mdssvc/mdssvc_tracker.c')
-rw-r--r-- | source3/rpc_server/mdssvc/mdssvc_tracker.c | 499 |
1 files changed, 499 insertions, 0 deletions
diff --git a/source3/rpc_server/mdssvc/mdssvc_tracker.c b/source3/rpc_server/mdssvc/mdssvc_tracker.c new file mode 100644 index 0000000..54f391e --- /dev/null +++ b/source3/rpc_server/mdssvc/mdssvc_tracker.c @@ -0,0 +1,499 @@ +/* + Unix SMB/CIFS implementation. + Main metadata server / Spotlight routines / Tracker backend + + Copyright (C) Ralph Boehme 2019 + + 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 "lib/util/time_basic.h" +#include "mdssvc.h" +#include "mdssvc_tracker.h" +#include "lib/tevent_glib_glue.h" +#include "rpc_server/mdssvc/sparql_parser.tab.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_RPC_SRV + +static struct mdssvc_tracker_ctx *mdssvc_tracker_ctx; + +/************************************************ + * Tracker async callbacks + ************************************************/ + +static void tracker_con_cb(GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + struct mds_tracker_ctx *ctx = NULL; + TrackerSparqlConnection *tracker_con = NULL; + GError *error = NULL; + + tracker_con = tracker_sparql_connection_get_finish(res, &error); + if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + /* + * If the async request was cancelled, user_data will already be + * talloc_free'd, so we must be carefully checking for + * G_IO_ERROR_CANCELLED before using user_data. + */ + DBG_ERR("Tracker connection cancelled\n"); + g_error_free(error); + return; + } + /* + * Ok, we're not cancelled, we can now safely use user_data. + */ + ctx = talloc_get_type_abort(user_data, struct mds_tracker_ctx); + ctx->async_pending = false; + /* + * Check error again, above we only checked for G_IO_ERROR_CANCELLED. + */ + if (error) { + DBG_ERR("Could not connect to Tracker: %s\n", error->message); + g_error_free(error); + return; + } + + ctx->tracker_con = tracker_con; + + DBG_DEBUG("connected to Tracker\n"); +} + +static void tracker_cursor_cb(GObject *object, + GAsyncResult *res, + gpointer user_data); + +static void tracker_query_cb(GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + struct sl_tracker_query *tq = NULL; + struct sl_query *slq = NULL; + TrackerSparqlConnection *conn = NULL; + TrackerSparqlCursor *cursor = NULL; + GError *error = NULL; + + conn = TRACKER_SPARQL_CONNECTION(object); + + cursor = tracker_sparql_connection_query_finish(conn, res, &error); + /* + * If the async request was cancelled, user_data will already be + * talloc_free'd, so we must be carefully checking for + * G_IO_ERROR_CANCELLED before using user_data. + */ + if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + DBG_ERR("Tracker query cancelled\n"); + if (cursor != NULL) { + g_object_unref(cursor); + } + g_error_free(error); + return; + } + /* + * Ok, we're not cancelled, we can now safely use user_data. + */ + tq = talloc_get_type_abort(user_data, struct sl_tracker_query); + tq->async_pending = false; + slq = tq->slq; + /* + * Check error again, above we only checked for G_IO_ERROR_CANCELLED. + */ + if (error) { + DBG_ERR("Tracker query error: %s\n", error->message); + g_error_free(error); + slq->state = SLQ_STATE_ERROR; + return; + } + + tq->cursor = cursor; + slq->state = SLQ_STATE_RESULTS; + + tracker_sparql_cursor_next_async(tq->cursor, + tq->gcancellable, + tracker_cursor_cb, + tq); + tq->async_pending = true; +} + +static char *tracker_to_unix_path(TALLOC_CTX *mem_ctx, const char *uri) +{ + GFile *f = NULL; + char *path = NULL; + char *talloc_path = NULL; + + 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; +} + +static void tracker_cursor_cb(GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + TrackerSparqlCursor *cursor = NULL; + struct sl_tracker_query *tq = NULL; + struct sl_query *slq = NULL; + const gchar *uri = NULL; + GError *error = NULL; + char *path = NULL; + gboolean more_results; + bool ok; + + cursor = TRACKER_SPARQL_CURSOR(object); + more_results = tracker_sparql_cursor_next_finish(cursor, + res, + &error); + /* + * If the async request was cancelled, user_data will already be + * talloc_free'd, so we must be carefully checking for + * G_IO_ERROR_CANCELLED before using user_data. + */ + if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_error_free(error); + g_object_unref(cursor); + return; + } + /* + * Ok, we're not cancelled, we can now safely use user_data. + */ + tq = talloc_get_type_abort(user_data, struct sl_tracker_query); + tq->async_pending = false; + slq = tq->slq; + /* + * Check error again, above we only checked for G_IO_ERROR_CANCELLED. + */ + if (error) { + DBG_ERR("Tracker cursor: %s\n", error->message); + g_error_free(error); + slq->state = SLQ_STATE_ERROR; + return; + } + + SLQ_DEBUG(10, slq, "results"); + + if (!more_results) { + slq->state = SLQ_STATE_DONE; + + g_object_unref(tq->cursor); + tq->cursor = NULL; + + g_object_unref(tq->gcancellable); + tq->gcancellable = NULL; + return; + } + + uri = tracker_sparql_cursor_get_string(tq->cursor, 0, NULL); + if (uri == NULL) { + DBG_ERR("error fetching Tracker URI\n"); + slq->state = SLQ_STATE_ERROR; + return; + } + + path = tracker_to_unix_path(slq->query_results, uri); + if (path == NULL) { + DBG_ERR("error converting Tracker URI to path: %s\n", uri); + slq->state = SLQ_STATE_ERROR; + return; + } + + ok = mds_add_result(slq, path); + if (!ok) { + DBG_ERR("error adding result for path: %s\n", uri); + slq->state = SLQ_STATE_ERROR; + return; + } + + if (slq->query_results->num_results >= MAX_SL_RESULTS) { + slq->state = SLQ_STATE_FULL; + SLQ_DEBUG(10, slq, "full"); + return; + } + + slq->state = SLQ_STATE_RESULTS; + SLQ_DEBUG(10, slq, "cursor next"); + + tracker_sparql_cursor_next_async(tq->cursor, + tq->gcancellable, + tracker_cursor_cb, + tq); + tq->async_pending = true; +} + +/* + * This gets called once, even if the backend is not configured by the user + */ +static bool mdssvc_tracker_init(struct mdssvc_ctx *mdssvc_ctx) +{ + if (mdssvc_tracker_ctx != NULL) { + return true; + } + +#if (GLIB_MAJOR_VERSION < 3) && (GLIB_MINOR_VERSION < 36) + g_type_init(); +#endif + + mdssvc_tracker_ctx = talloc_zero(mdssvc_ctx, struct mdssvc_tracker_ctx); + if (mdssvc_tracker_ctx == NULL) { + return false; + } + mdssvc_tracker_ctx->mdssvc_ctx = mdssvc_ctx; + + return true; +} + +/* + * This gets called per mdscmd_open / tcon. This runs initialisation code that + * should only run if the tracker backend is actually used. + */ +static bool mdssvc_tracker_prepare(void) +{ + if (mdssvc_tracker_ctx->gmain_ctx != NULL) { + /* + * Assuming everything is setup if gmain_ctx is. + */ + return true; + } + + mdssvc_tracker_ctx->gmain_ctx = g_main_context_new(); + if (mdssvc_tracker_ctx->gmain_ctx == NULL) { + DBG_ERR("error from g_main_context_new\n"); + return false; + } + + mdssvc_tracker_ctx->glue = samba_tevent_glib_glue_create( + mdssvc_tracker_ctx, + mdssvc_tracker_ctx->mdssvc_ctx->ev_ctx, + mdssvc_tracker_ctx->gmain_ctx); + if (mdssvc_tracker_ctx->glue == NULL) { + DBG_ERR("samba_tevent_glib_glue_create failed\n"); + g_object_unref(mdssvc_tracker_ctx->gmain_ctx); + mdssvc_tracker_ctx->gmain_ctx = NULL; + return false; + } + + return true; +} + +static bool mdssvc_tracker_shutdown(struct mdssvc_ctx *mdssvc_ctx) +{ + if (mdssvc_tracker_ctx == NULL) { + return true; + } + + if (mdssvc_tracker_ctx->gmain_ctx == NULL) { + return true; + } + + samba_tevent_glib_glue_quit(mdssvc_tracker_ctx->glue); + TALLOC_FREE(mdssvc_tracker_ctx->glue); + + g_object_unref(mdssvc_tracker_ctx->gmain_ctx); + mdssvc_tracker_ctx->gmain_ctx = NULL; + return true; +} + +static int mds_tracker_ctx_destructor(struct mds_tracker_ctx *ctx) +{ + /* + * Don't g_object_unref() the connection if there's an async request + * pending, it's used in the async callback and will be unreferenced + * there. + */ + if (ctx->async_pending) { + g_cancellable_cancel(ctx->gcancellable); + ctx->gcancellable = NULL; + return 0; + } + + if (ctx->tracker_con == NULL) { + return 0; + } + g_object_unref(ctx->tracker_con); + ctx->tracker_con = NULL; + + return 0; +} + +static bool mds_tracker_connect(struct mds_ctx *mds_ctx) +{ + struct mds_tracker_ctx *ctx = NULL; + bool ok; + + ok = mdssvc_tracker_prepare(); + if (!ok) { + return false; + } + + ctx = talloc_zero(mds_ctx, struct mds_tracker_ctx); + if (ctx == NULL) { + return false; + } + talloc_set_destructor(ctx, mds_tracker_ctx_destructor); + + ctx->mds_ctx = mds_ctx; + + ctx->gcancellable = g_cancellable_new(); + if (ctx->gcancellable == NULL) { + DBG_ERR("error from g_cancellable_new\n"); + TALLOC_FREE(ctx); + return false; + } + + tracker_sparql_connection_get_async(ctx->gcancellable, + tracker_con_cb, + ctx); + ctx->async_pending = true; + + mds_ctx->backend_private = ctx; + + return true; +} + +static int tq_destructor(struct sl_tracker_query *tq) +{ + /* + * Don't g_object_unref() the cursor if there's an async request + * pending, it's used in the async callback and will be unreferenced + * there. + */ + if (tq->async_pending) { + g_cancellable_cancel(tq->gcancellable); + tq->gcancellable = NULL; + return 0; + } + + if (tq->cursor == NULL) { + return 0; + } + g_object_unref(tq->cursor); + tq->cursor = NULL; + return 0; +} + +static bool mds_tracker_search_start(struct sl_query *slq) +{ + struct mds_tracker_ctx *tmds_ctx = talloc_get_type_abort( + slq->mds_ctx->backend_private, struct mds_tracker_ctx); + struct sl_tracker_query *tq = NULL; + char *escaped_scope = NULL; + bool ok; + + if (tmds_ctx->tracker_con == NULL) { + DBG_ERR("no connection to Tracker\n"); + return false; + } + + tq = talloc_zero(slq, struct sl_tracker_query); + if (tq == NULL) { + return false; + } + tq->slq = slq; + talloc_set_destructor(tq, tq_destructor); + + tq->gcancellable = g_cancellable_new(); + if (tq->gcancellable == NULL) { + DBG_ERR("g_cancellable_new() failed\n"); + goto error; + } + + escaped_scope = g_uri_escape_string( + slq->path_scope, + G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, + TRUE); + if (escaped_scope == NULL) { + goto error; + } + + tq->path_scope = talloc_strdup(tq, escaped_scope); + g_free(escaped_scope); + escaped_scope = NULL; + if (tq->path_scope == NULL) { + goto error; + } + + slq->backend_private = tq; + + 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 + */ + goto error; + } + + DBG_DEBUG("SPARQL query: \"%s\"\n", tq->sparql_query); + + tracker_sparql_connection_query_async(tmds_ctx->tracker_con, + tq->sparql_query, + tq->gcancellable, + tracker_query_cb, + tq); + tq->async_pending = true; + + slq->state = SLQ_STATE_RUNNING; + return true; +error: + g_object_unref(tq->gcancellable); + TALLOC_FREE(tq); + slq->backend_private = NULL; + return false; +} + +static bool mds_tracker_search_cont(struct sl_query *slq) +{ + struct sl_tracker_query *tq = talloc_get_type_abort( + slq->backend_private, struct sl_tracker_query); + + tracker_sparql_cursor_next_async(tq->cursor, + tq->gcancellable, + tracker_cursor_cb, + tq); + tq->async_pending = true; + + return true; +} + +struct mdssvc_backend mdsscv_backend_tracker = { + .init = mdssvc_tracker_init, + .shutdown = mdssvc_tracker_shutdown, + .connect = mds_tracker_connect, + .search_start = mds_tracker_search_start, + .search_cont = mds_tracker_search_cont, +}; |