From: Eric Bollengier Date: Fri, 19 Nov 2021 21:22:40 +0000 (+0100) Subject: Add bconsole .search command X-Git-Tag: Beta-15.0.0~754 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7cf6f37e5fcbf47b3d410b1c8012b15eb619bf86;p=thirdparty%2Fbacula.git Add bconsole .search command --- diff --git a/bacula/src/cats/bdb.h b/bacula/src/cats/bdb.h index 12719decb..1ca76369f 100644 --- a/bacula/src/cats/bdb.h +++ b/bacula/src/cats/bdb.h @@ -258,6 +258,15 @@ public: bool bdb_get_client_pool(JCR *jcr, alist *results); /* sql_list.c */ + bool bdb_search_media_records(JCR *jcr, MEDIA_DBR *mdbr, + DB_RESULT_HANDLER *handler, void *ctx); + bool bdb_search_client_records(JCR *jcr, CLIENT_DBR *rec, + DB_RESULT_HANDLER *callback, void *ctx); + bool bdb_search_job_records(JCR *jcr, JOB_DBR *rec, + DB_RESULT_HANDLER *callback, void *ctx); + bool bdb_search_tag_records(JCR *jcr, TAG_DBR *rec, + DB_RESULT_HANDLER *callback, void *ctx); + void bdb_list_pool_records(JCR *jcr, POOL_DBR *pr, DB_LIST_HANDLER sendit, void *ctx, e_list_type type); alist *bdb_list_job_records(JCR *jcr, JOB_DBR *jr, DB_LIST_HANDLER sendit, void *ctx, e_list_type type); void bdb_list_jobs_for_file(JCR *jcr, const char *client, const char *fname, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type); diff --git a/bacula/src/cats/cats.h b/bacula/src/cats/cats.h index 2881e226a..4201c2929 100644 --- a/bacula/src/cats/cats.h +++ b/bacula/src/cats/cats.h @@ -468,6 +468,7 @@ public: char sid[30]; /* edited StorageId */ bool set_first_written; bool set_label_date; + int limit; /* Limit the number of records returned with search */ }; #define MAX_UNAME_LENGTH 256 @@ -485,6 +486,8 @@ struct CLIENT_DBR { char Name[MAX_NAME_LENGTH]; /* Client name */ char Uname[MAX_UNAME_LENGTH]; /* Uname for client */ char Plugins[MAX_PLUGIN_LENGTH]; /* Plugin list for this client */ + + int limit; /* used by search */ }; /* Counter record as in database */ @@ -610,10 +613,13 @@ public: JobId_t JobId; /* JobId */ bool all; + int limit; /* Used in search */ - TAG_DBR() {}; + TAG_DBR() { + zero(); + }; void zero() { - JobId = 0; + limit = JobId = 0; all = false; *Object = *Client = *Job = *Pool = *Volume = *Comment = *Name = '\0'; }; diff --git a/bacula/src/cats/protos.h b/bacula/src/cats/protos.h index a598447f7..344a3c83b 100644 --- a/bacula/src/cats/protos.h +++ b/bacula/src/cats/protos.h @@ -266,10 +266,19 @@ void bdb_free_restoreobject_record(JCR *jcr, ROBJECT_DBR *rr); mdb->bdb_get_snapshot_record(jcr, sr) #define db_get_job_statistics(jcr, mdb, jr) \ mdb->bdb_get_job_statistics(jcr, jr) + /* sql_list.c */ #define db_list_jobs_for_file(jcr, mdb, cli, fname, result_handler, ctx, type) \ mdb->bdb_list_jobs_for_file(jcr, cli, fname, result_handler, ctx, type) -#define db_list_pool_records(jcr, mdb, pr, sendit, ctx, type) \ +#define db_search_tag_records(jcr, mdb, tag, result_handler, ctx) \ + mdb->bdb_search_tag_records(jcr, tag, result_handler, ctx) +#define db_search_media_records(jcr, mdb, rec, result_handler, ctx) \ + mdb->bdb_search_media_records(jcr, rec, result_handler, ctx) +#define db_search_job_records(jcr, mdb, rec, result_handler, ctx) \ + mdb->bdb_search_job_records(jcr, rec, result_handler, ctx) +#define db_search_client_records(jcr, mdb, rec, result_handler, ctx) \ + mdb->bdb_search_client_records(jcr, rec, result_handler, ctx) +#define db_list_pool_records(jcr, mdb, pr, sendit, ctx, type) \ mdb->bdb_list_pool_records(jcr, pr, sendit, ctx, type) #define db_list_job_records(jcr, mdb, jr, sendit, ctx, type) \ mdb->bdb_list_job_records(jcr, jr, sendit, ctx, type) diff --git a/bacula/src/cats/sql_list.c b/bacula/src/cats/sql_list.c index 0c9a7d4d7..dcf61eba6 100644 --- a/bacula/src/cats/sql_list.c +++ b/bacula/src/cats/sql_list.c @@ -36,6 +36,127 @@ * ----------------------------------------------------------------------- */ +/* We search resources for a specific tag, we just return the resource name + * itself and the result can be stored in a list for example + */ +bool BDB::bdb_search_tag_records(JCR *jcr, TAG_DBR *tag, DB_RESULT_HANDLER *result_handler, void *ctx) +{ + POOL_MEM tmp, where; + char esc[MAX_ESCAPE_NAME_LENGTH]; + char esc_name[MAX_ESCAPE_NAME_LENGTH]; + uint64_t aclbits, aclbits_extra; + + const char *name; + const char *id; + const char *table; + + tag->gen_sql(jcr, this, &table, &name, &id, esc, esc_name, &aclbits, &aclbits_extra); + + bdb_lock(); + pm_strcpy(where, get_acls(aclbits, true)); /* get_acls() uses a specific object buffer */ + const char *whereand = get_acls(aclbits, false); + const char *join = get_acl_join_filter(aclbits_extra); + + if (table) { + if (tag->all) { + if (*esc_name) { + /* Display all resource for a tag */ + Mmsg(tmp, "SELECT %s AS %s FROM Tag%s AS T JOIN %s USING (%s) %s WHERE T.Tag = '%s' %s", + name, table, table, table, id, join, esc_name, whereand); + } + } + if (tag->limit > 0) { + char ed1[50]; + pm_strcat(cmd, " LIMIT "); + pm_strcat(cmd, edit_uint64(tag->limit, ed1)); + } + if (!bdb_sql_query(tmp.c_str(), result_handler, ctx)) { + bdb_unlock(); + return false; + } + } + bdb_unlock(); + return true; +} + +/* + * Search Job record(s) that match JOB_DBR, the result can be stored in a alist for example + * + */ +bool BDB::bdb_search_job_records(JCR *jcr, JOB_DBR *jr, + DB_RESULT_HANDLER *handler, + void *ctx) +{ + char esc[MAX_ESCAPE_NAME_LENGTH]; + const char *join = ""; + const char *where_tmp = ""; + + if (jr->Job[0] == 0) { + return false; + } + + bdb_lock(); + bdb_escape_string(jcr, esc, jr->Job, strlen(jr->Job)); + + /* The ACL can limit on two extra tables, Client and FileSet */ + where_tmp = get_acls(DB_ACL_BIT(DB_ACL_CLIENT) | + DB_ACL_BIT(DB_ACL_FILESET), + 0); + + if (*where_tmp) { + join = get_acl_join_filter(DB_ACL_BIT(DB_ACL_CLIENT) | + DB_ACL_BIT(DB_ACL_FILESET)); + } + + Mmsg(cmd, + "SELECT Job " + "FROM Job " + " %s WHERE Job.Job ILIKE '%%%s%%' %s", join, esc, where_tmp); + + if (jr->limit > 0) { + char ed1[50]; + pm_strcat(cmd, " LIMIT "); + pm_strcat(cmd, edit_uint64(jr->limit, ed1)); + } + + if (!bdb_sql_query(cmd, handler, ctx)) { + bdb_unlock(); + return false; + } + bdb_unlock(); + return true; +} + +/* Search for a client, return only the name */ +bool BDB::bdb_search_client_records(JCR *jcr, CLIENT_DBR *rec, DB_RESULT_HANDLER *callback, void *ctx) +{ + char esc[MAX_ESCAPE_NAME_LENGTH]; + const char *where_tmp = ""; + + bdb_lock(); + bdb_escape_string(jcr, esc, rec->Name, strlen(rec->Name)); + + /* We can apply some ACLs for the Client table */ + where_tmp = get_acls(DB_ACL_BIT(DB_ACL_CLIENT), 0); + + Mmsg(cmd, "SELECT Name " + "FROM Client WHERE Name ILIKE '%%%s%%' %s", + esc, where_tmp); + + if (rec->limit > 0) { + char ed1[50]; + pm_strcat(cmd, " LIMIT "); + pm_strcat(cmd, edit_uint64(rec->limit, ed1)); + } + + if (!bdb_sql_query(cmd, callback, ctx)) { + bdb_unlock(); + return false; + } + bdb_unlock(); + return true; +} + /* * Submit general SQL query */ @@ -128,7 +249,6 @@ void BDB::bdb_list_client_records(JCR *jcr, DB_LIST_HANDLER *sendit, void *ctx, bdb_unlock(); } - /* * List plugin objects types */ @@ -302,6 +422,44 @@ void BDB::bdb_list_restore_objects(JCR *jcr, ROBJECT_DBR *rr, DB_LIST_HANDLER *s bdb_unlock(); } +/* + * If VolumeName is non-zero, list the record for that Volume + */ +bool BDB::bdb_search_media_records(JCR *jcr, MEDIA_DBR *mdbr, + DB_RESULT_HANDLER *handler, void *ctx) +{ + char ed1[50]; + char esc[MAX_ESCAPE_NAME_LENGTH]; + + if (mdbr->VolumeName[0] == 0) { + return false; + } + + bdb_lock(); + bdb_escape_string(jcr, esc, mdbr->VolumeName, strlen(mdbr->VolumeName)); + const char *where = get_acl(DB_ACL_POOL, false); + const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_POOL)) : ""; + + if (mdbr->limit == 0) { + mdbr->limit = 50; + } + + Mmsg(cmd, "SELECT VolumeName FROM Media %s WHERE Media.VolumeName ILIKE '%%%s%%' %s LIMIT %u", + join, + esc, + where, + mdbr->limit); + + if (!bdb_sql_query(cmd, handler, ctx)) { + bdb_unlock(); + return false; + } + + sql_free_result(); + bdb_unlock(); + return true; +} + /* * If VolumeName is non-zero, list the record for that Volume * otherwise, list the Volumes in the Pool specified by PoolId @@ -320,6 +478,7 @@ void BDB::bdb_list_media_records(JCR *jcr, MEDIA_DBR *mdbr, if (type == VERT_LIST || type == JSON_LIST) { if (mdbr->VolumeName[0] != 0) { + Mmsg(cmd, "SELECT MediaId,VolumeName,Slot,PoolId," "MediaType,MediaTypeId,FirstWritten,LastWritten,LabelDate,VolJobs," "VolFiles,VolBlocks,VolParts,VolCloudParts,Media.CacheRetention,VolMounts,VolBytes," diff --git a/bacula/src/dird/ua_dotcmds.c b/bacula/src/dird/ua_dotcmds.c index 012b63971..a333f30fc 100644 --- a/bacula/src/dird/ua_dotcmds.c +++ b/bacula/src/dird/ua_dotcmds.c @@ -95,6 +95,7 @@ static bool dot_quit_cmd(UAContext *ua, const char *cmd); static bool dot_help_cmd(UAContext *ua, const char *cmd); static bool dot_add_events(UAContext *ua, const char *cmd); static int one_handler(void *ctx, int num_field, char **row); +static bool dot_search(UAContext *ua, const char *cmd); struct dcmd_struct { const char *key; bool (*func)(UAContext *ua, const char *cmd); const char *help;const bool use_in_rs;}; static struct dcmd_struct commands[] = { /* help */ /* can be used in runscript */ @@ -118,6 +119,7 @@ static struct dcmd_struct commands[] = { /* help */ /* can be used in runscript { NT_(".pools"), poolscmd, NULL, true}, { NT_(".quit"), dot_quit_cmd, NULL, false}, { NT_(".putfile"), putfile_cmd, NULL, false}, /* use @putfile */ + { NT_(".search"), dot_search, NULL, false}, { NT_(".schedule"), schedulescmd, NULL, false}, { NT_(".sql"), sql_cmd, NULL, false}, { NT_(".status"), dot_status_cmd, NULL, false}, @@ -2737,3 +2739,187 @@ static bool dot_add_events(UAContext *ua, const char *cmd) bail_out: return true; } + +enum { + CAT_VOLUME = (1<<0), + CAT_JOB = (1<<1), + CAT_CLIENT = (1<<2), +}; + +#define CAT_ALL (CAT_VOLUME | CAT_JOB | CAT_CLIENT) + +/* Search over our different tables + * + * { + * "error": 0, + * "errmsg", "", + * "type": "search", + * "data": { + * "volume": ["vol1", "vol2"], + * "client": ["volA"], + * "job": [] + * } + * } + * + */ +static bool dot_search(UAContext *ua, const char *cmd) +{ + uint32_t cat = 0; + uint64_t limit=0; + char *text = NULL; + const char *errmsg = NULL; + alist res(10, owned_by_alist); + OutputWriter out(ua->api_opts); + alist *r = &res; + + for(int i=1; i < ua->argc ; i++) { + if (strcasecmp(ua->argk[i], NT_("limit")) == 0 && is_a_number(ua->argv[i])) { + limit = str_to_uint64(ua->argv[i]); + + } else if (strcasecmp(ua->argk[i], NT_("category")) == 0) { + if (strcasecmp(NPRTB(ua->argv[i]), "all") == 0) { + cat = CAT_ALL; + + } else if (strcasecmp(NPRTB(ua->argv[i]), "client") == 0) { + cat |= CAT_CLIENT; + + } else if (strcasecmp(NPRTB(ua->argv[i]), "job") == 0) { + cat |= CAT_JOB; + + } else if (strcasecmp(NPRTB(ua->argv[i]), "volume") == 0) { + cat |= CAT_VOLUME; + + } else { + errmsg = "Incorrect category"; + goto bail_out; + } + + } else if (strcasecmp(ua->argk[i], NT_("text")) == 0) { + text = ua->argv[i]; + + } else { + errmsg = "Incorrect parameter"; + goto bail_out; + } + } + if (!text || strlen(text) <= 3) { + errmsg = "Missing text to search"; + goto bail_out; + } + + /* TODO: check for nasty characters in text */ + if (!cat) { + cat = CAT_ALL; + } + if (!limit) { + limit = 10; + } + if (!open_new_client_db(ua)) { + errmsg = "Unable to open the catalog"; + goto bail_out; + } + + /* We initialize the output */ + out.get_output(OT_START_OBJ, + OT_INT, "error", 0, + OT_STRING, "errmsg", "", + OT_STRING, "type", "search", + OT_END); + out.start_object("data"); + + if (text[0] == '#') { + /* search for a tag */ + TAG_DBR tr; + r->destroy(); + if (cat & CAT_CLIENT) { + tr.zero(); + tr.all = true; + bstrncpy(tr.Name, text, sizeof(tr.Name)); + strcpy(tr.Client, "1"); // To enable the Client search + if (!db_search_tag_records(ua->jcr, ua->db, &tr, db_string_list_handler, &r)) { + errmsg = "Unable to query client tag"; + goto bail_out; + } + } + out.get_output(OT_ALIST_STR, "client", r, OT_END); + + r->destroy(); + if (cat & CAT_JOB) { + tr.zero(); + tr.all = true; + bstrncpy(tr.Name, text, sizeof(tr.Name)); + strcpy(tr.Job, "1"); // To enable the Job search + if (!db_search_tag_records(ua->jcr, ua->db, &tr, db_string_list_handler, &r)) { + errmsg = "Unable to query Job tag"; + goto bail_out; + } + } + out.get_output(OT_ALIST_STR, "job", r, OT_END); + + r->destroy(); + if (cat & CAT_VOLUME) { + tr.zero(); + tr.all = true; + bstrncpy(tr.Name, text, sizeof(tr.Name)); + strcpy(tr.Volume, "1"); // To enable the Volume search + if (!db_search_tag_records(ua->jcr, ua->db, &tr, db_string_list_handler, &r)) { + errmsg = "Unable to query Volume tag"; + goto bail_out; + } + } + out.get_output(OT_ALIST_STR, "volume", r, OT_END); + + } else { + r->destroy(); + if (cat & CAT_VOLUME) { + /* Search in volumes */ + MEDIA_DBR mdbr; + bstrncpy(mdbr.VolumeName, text, sizeof(mdbr.VolumeName)); + mdbr.limit = limit; + if (!db_search_media_records(ua->jcr, ua->db, &mdbr, db_string_list_handler, &r)) { + errmsg = "Unable to query Volume"; + goto bail_out; + } + } + out.get_output(OT_ALIST_STR, "volume", r, OT_END); + + /* Search in clients */ + r->destroy(); + if (cat & CAT_CLIENT) { + CLIENT_DBR cr; + memset(&cr, 0, sizeof(cr)); + cr.limit = limit; + bstrncpy(cr.Name, text, sizeof(cr.Name)); + if (!db_search_client_records(ua->jcr, ua->db, &cr, db_string_list_handler, &r)) { + errmsg = "Unable to query Client"; + goto bail_out; + } + } + out.get_output(OT_ALIST_STR, "client", &res, OT_END); + + /* Search in Job */ + r->destroy(); + if (cat & CAT_JOB) { + JOB_DBR jr; + memset(&jr, 0, sizeof(jr)); + jr.limit = limit; + bstrncpy(jr.Job, text, sizeof(jr.Job)); + if (!db_search_job_records(ua->jcr, ua->db, &jr, db_string_list_handler, &r)) { + errmsg = "Unable to query Job"; + goto bail_out; + } + } + out.get_output(OT_ALIST_STR, "job", &res, OT_END); + } + out.end_object(); + ua->send_msg(out.get_output(OT_END_OBJ, OT_END)); + return true; + +bail_out: + ua->send_msg(out.get_output(OT_CLEAR, + OT_START_OBJ, + OT_INT, "error", 1, + OT_STRING, "errmsg", errmsg, + OT_END_OBJ, OT_END)); + return true; +}