/* 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 . */ #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, };