]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
Add bconsole .search command
authorEric Bollengier <eric@baculasystems.com>
Fri, 19 Nov 2021 21:22:40 +0000 (22:22 +0100)
committerEric Bollengier <eric@baculasystems.com>
Thu, 14 Sep 2023 11:56:56 +0000 (13:56 +0200)
bacula/src/cats/bdb.h
bacula/src/cats/cats.h
bacula/src/cats/protos.h
bacula/src/cats/sql_list.c
bacula/src/dird/ua_dotcmds.c

index 12719decbacf2aab571a016c0fb46cf7f1654c7a..1ca76369fd76ece0643a6b341230a35fd49744c6 100644 (file)
@@ -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);
index 2881e226adb15c3a96090deedb9739f9a648399a..4201c2929fc8f23e1bcf57472ab4ab2f937a6ea0 100644 (file)
@@ -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';
    };
index a598447f7562d7f6f6cb38483d56422d842172ab..344a3c83bad79b5d47c30fca195d40813f26e20c 100644 (file)
@@ -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)
index 0c9a7d4d7bb72815f270b7242cd379e03b9c4366..dcf61eba64796a249533ed95aeff5fee87e4276a 100644 (file)
  * -----------------------------------------------------------------------
  */
 
+/* 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,"
index 012b6397165fcd066d3c07c2c67d3f5c6d5b34ad..a333f30fce35143b80f8528d070e91f62da78451 100644 (file)
@@ -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;
+}