]> git.ipfire.org Git - thirdparty/bacula.git/commitdiff
Add first support for tags in the Director
authorEric Bollengier <eric@baculasystems.com>
Fri, 28 Aug 2020 08:46:44 +0000 (10:46 +0200)
committerEric Bollengier <eric@baculasystems.com>
Thu, 24 Mar 2022 08:02:57 +0000 (09:02 +0100)
14 files changed:
bacula/src/cats/bdb.h
bacula/src/cats/cats.h
bacula/src/cats/protos.h
bacula/src/cats/sql.c
bacula/src/cats/sql_create.c
bacula/src/cats/sql_delete.c
bacula/src/cats/sql_list.c
bacula/src/dird/dird_conf.c
bacula/src/dird/dird_conf.h
bacula/src/dird/job.c
bacula/src/dird/protos.h
bacula/src/dird/ua_cmds.c
bacula/src/dird/ua_output.c
bacula/src/dird/ua_purge.c

index a9d55295c3eb250f712f2739e440af8ec67d1bc1..c3193f1d6a42c84b64a9ba62de2f36f83d86778d 100644 (file)
@@ -176,6 +176,7 @@ public:
    int bdb_purge_media_record(JCR *jcr, MEDIA_DBR *mr);
    int bdb_delete_snapshot_record(JCR *jcr, SNAPSHOT_DBR *sr);
    int bdb_delete_client_record(JCR *jcr, CLIENT_DBR *cr);
+   int bdb_delete_tag_record(JCR *jcr, TAG_DBR *tr);
 
    /* sql_find.c */
    bool bdb_find_last_job_end_time(JCR *jcr, JOB_DBR *jr, POOLMEM **etime, char *job);
@@ -186,6 +187,7 @@ public:
    bool bdb_find_failed_job_since(JCR *jcr, JOB_DBR *jr, POOLMEM *stime, int &JobLevel);
    
    /* sql_create.c */
+   bool bdb_create_tag_record(JCR *jcr, TAG_DBR *tr);
    bool bdb_create_log_record(JCR *jcr, JobId_t jobid, utime_t mtime, char *msg);
    int bdb_create_events_record(JCR *jcr, EVENTS_DBR *rec);
    int bdb_create_path_record(JCR *jcr, ATTR_DBR *ar);
@@ -263,6 +265,7 @@ public:
    void bdb_list_client_records(JCR *jcr, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
    void bdb_list_copies_records(JCR *jcr, uint32_t limit, char *jobids, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
    void bdb_list_events_records(JCR *jcr, EVENTS_DBR *rec, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
+   void bdb_list_tag_records(JCR *jcr, TAG_DBR *rec, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
    void bdb_list_base_files_for_job(JCR *jcr, JobId_t jobid, DB_LIST_HANDLER *sendit, void *ctx);
    void bdb_list_restore_objects(JCR *jcr, ROBJECT_DBR *rr, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
    void bdb_list_plugin_objects(JCR *jcr, OBJECT_DBR *obj_r, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
index 8548bad99a9da7fbd21b869acaf6e75728f6f560..60d82feeb9a455dc7db98e15d0c67789e56b5688 100644 (file)
@@ -548,6 +548,39 @@ public:
    uint64_t Size;                   /* Snapshot Size */
 };
 
+class TAG_DBR
+{
+public:
+   char    Client[MAX_NAME_LENGTH];  /* Client name */
+   char    Job[MAX_NAME_LENGTH];     /* Job name */
+   char    Pool[MAX_NAME_LENGTH];    /* Pool name */
+   char    Volume[MAX_NAME_LENGTH];  /* Volume name */
+   char    Comment[MAX_NAME_LENGTH]; /* Comment */
+   char    Name[MAX_NAME_LENGTH];    /* Name */
+   char    Object[MAX_NAME_LENGTH];  /* Object name */
+   JobId_t JobId;                    /* JobId */
+
+   bool    all;
+
+   TAG_DBR() {};
+   void zero() {
+      JobId = 0;
+      all = false;
+      *Object = *Client = *Job = *Pool = *Volume = *Comment = *Name = '\0';
+   };
+   virtual ~TAG_DBR(){};
+
+   /* Scan a TAG_DBR object to help with SQL queries */
+   void gen_sql(JCR *jcr, BDB *db,
+                const char **table, /* Table name (Client, Job, Media...) */
+                const char **name,  /* Name of the record (Name, VolumeName, JobId) */
+                const char **id,    /* Id of the record (ClientId, JobId, MediaId) */
+                char *esc,          /* Escaped name of the resource */
+                char *esc_name,     /* Escaped name of the tag */
+                uint64_t *aclbits,  /* ACL used */
+                uint64_t *aclbits_extra); /* Extra ACL used */
+};
+
 /* Call back context for getting a 32/64 bit value from the database */
 class db_int64_ctx {
 public:
index 53fdbc0b6efcab512e3bc83a2093e06bd839183c..f4e9e9b150f23a4d36b8a447c3f9e10c3eb15bef 100644 (file)
@@ -115,7 +115,9 @@ void bdb_free_restoreobject_record(JCR *jcr, ROBJECT_DBR *rr);
            mdb->bdb_create_log_record(jcr, jobid, mtime, msg)
 #define db_create_events_record(jcr, mdb, rec) \
            mdb->bdb_create_events_record(jcr, rec)
-#define db_create_path_record(jcr, mdb, ar) \
+#define db_create_tag_record(jcr, mdb, rec) \
+           mdb->bdb_create_tag_record(jcr, rec)
+#define db_create_path_record(jcr, mdb, ar)     \
            mdb->bdb_create_path_record(jcr, ar)
 #define db_create_file_attributes_record(jcr, mdb, ar) \
            mdb->bdb_create_file_attributes_record(jcr, ar)
@@ -172,6 +174,8 @@ void bdb_free_restoreobject_record(JCR *jcr, ROBJECT_DBR *rr);
            mdb->bdb_delete_snapshot_record(jcr, sr)
 #define db_delete_client_record(jcr, mdb, cr)         \
            mdb->bdb_delete_client_record(jcr, cr)
+#define db_delete_tag_record(jcr, mdb, tr)         \
+           mdb->bdb_delete_tag_record(jcr, tr)
 
 
 /* sql_find.c */
@@ -290,6 +294,8 @@ void bdb_free_restoreobject_record(JCR *jcr, ROBJECT_DBR *rr);
            mdb->bdb_list_snapshot_records(jcr, snapdbr, sendit, ua, llist)
 #define db_list_files(jcr, mdb, filedbr, sendit, ua) \
            mdb->bdb_list_files(jcr, filedbr, sendit, ua);
+#define db_list_tag_records(jcr, mdb, tagdbr, sendit, ua, llist)     \
+           mdb->bdb_list_tag_records(jcr, tagdbr, sendit, ua, llist);
 
 /* sql_update.c */
 #define db_update_job_start_record(jcr, mdb, jr) \
index 426aefc7bb7c38fe6f37b31f4ad5801fd47db2f8..0d7b5532e25a003f893bd09c41173fbb04d06c87 100644 (file)
@@ -1060,6 +1060,114 @@ void bdb_debug_print(JCR *jcr, FILE *fp)
    mdb->print_lock_info(fp); 
 } 
 
+
+/* Scan a TAG_DBR object to help with SQL queries */
+void TAG_DBR::gen_sql(JCR *jcr, BDB *db,
+                      const char **table_, /* Table name (Client, Job, Media...) */
+                      const char **name_,  /* Name of the record (Name, VolumeName, JobId) */
+                      const char **id_,    /* Id of the record (ClientId, MediaId, JobId) */
+                      char *esc,           /* Escaped name of the resource */
+                      char *esc_name,      /* Escaped name of the tag */
+                      uint64_t *aclbits_,  /* ACL used */
+                      uint64_t *aclbits_extra_ /* Extra ACLs (not already included in the join list) */)
+{
+   db->bdb_lock();
+   *esc = 0;
+   *esc_name = 0;
+   const char *name = NT_("Name");
+   const char *table = NULL;
+   const char *id = NULL;
+   uint64_t aclbits=0;          /* Used in the WHERE */
+   uint64_t aclbits_extra=0;    /* Used in the JOIN */
+
+   if (*Client) {
+      table = "Client";
+      id = "ClientId";
+      db->bdb_escape_string(jcr, esc, Client, strlen(Client));
+      aclbits |= DB_ACL_BIT(DB_ACL_CLIENT);
+
+   } else if (*Job) {
+      table = "Job";
+      name = "Name";
+      id = "JobId";
+      int len = strlen(Job);    /*                        01234567890123456789012 */
+      if (len > 23) {           /* Can be a full job name .2020-08-28_15.34.32_03 */
+         int idx=len-23;
+         if (Job[idx]   == '.' &&
+             B_ISDIGIT(Job[idx+1]) &&
+             B_ISDIGIT(Job[idx+2]) &&
+             B_ISDIGIT(Job[idx+3]) &&
+             B_ISDIGIT(Job[idx+4]) &&
+             Job[idx+5] == '-'     &&
+             B_ISDIGIT(Job[idx+6]) &&
+             B_ISDIGIT(Job[idx+7]) &&
+             Job[idx+8] == '-'     &&
+             B_ISDIGIT(Job[idx+9]) &&
+             B_ISDIGIT(Job[idx+10]) &&
+             Job[idx+11] == '_'     &&
+             B_ISDIGIT(Job[idx+12]) &&
+             B_ISDIGIT(Job[idx+13]) &&
+             Job[idx+14] == '.'     &&
+             B_ISDIGIT(Job[idx+15]) &&
+             B_ISDIGIT(Job[idx+16]) &&
+             Job[idx+17] == '.'     &&
+             B_ISDIGIT(Job[idx+18]) &&
+             B_ISDIGIT(Job[idx+19]) &&
+             Job[idx+20] == '_'     &&
+             B_ISDIGIT(Job[idx+21]) &&
+             B_ISDIGIT(Job[idx+22]) &&
+             B_ISDIGIT(Job[idx+23]) == 0)
+         {
+            name = "Job";
+         }
+      }
+      db->bdb_escape_string(jcr, esc, Job, strlen(Job));
+      aclbits |= DB_ACL_BIT(DB_ACL_JOB);
+
+   } else if (*Volume) {
+      table = "Media";
+      name = "VolumeName";
+      id = "MediaId";
+      db->bdb_escape_string(jcr, esc, Volume, strlen(Volume));
+      aclbits_extra |= DB_ACL_BIT(DB_ACL_POOL);
+      aclbits |= DB_ACL_BIT(DB_ACL_POOL);
+
+   } else if (*Pool) {
+      table = "Pool";
+      id = "PoolId";
+      db->bdb_escape_string(jcr, esc, Pool, strlen(Pool));
+      aclbits_extra |= DB_ACL_BIT(DB_ACL_POOL);
+      aclbits |= DB_ACL_BIT(DB_ACL_POOL);
+
+   } else if (*Object) {
+      table = "Object";
+      name = "ObjectName";
+      id = "ObjectId";
+      db->bdb_escape_string(jcr, esc, Object, strlen(Object));
+      aclbits |= DB_ACL_BIT(DB_ACL_JOB);
+      aclbits_extra |= DB_ACL_BIT(DB_ACL_JOB);
+   }
+
+   if (*Name) {
+      db->bdb_escape_string(jcr, esc_name, Name, strlen(Name));
+   }
+   db->bdb_unlock();
+
+   if (JobId > 0) {
+      table = "Job";
+      name = "JobId";
+      id = "JobId";
+      edit_uint64(JobId, esc);
+      aclbits |= DB_ACL_BIT(DB_ACL_JOB);
+   }
+
+   *table_ = table;
+   *name_ = name;
+   *id_ = id;
+   *aclbits_ = aclbits;
+   *aclbits_extra_ = aclbits_extra;
+}
+
 #ifdef COMMUNITY
 bool BDB::bdb_check_settings(JCR *jcr, int64_t *starttime, int val, int64_t val2) 
 {
index 4169ee1675b5e58964aab6575613df83df2469a2..b8a1a3bfd74090ac64f3d0eed4a02dd31071ab00 100644 (file)
@@ -1440,4 +1440,33 @@ bool BDB::bdb_create_log_record(JCR *jcr, JobId_t jobid, utime_t mtime, char *ms
    return ret; 
 } 
 
+bool BDB::bdb_create_tag_record(JCR *jcr, TAG_DBR *tag)
+{
+   uint64_t aclbits, aclbits_extra;
+   char esc[MAX_ESCAPE_NAME_LENGTH];
+   char esc_name[MAX_ESCAPE_NAME_LENGTH];
+   const char *name;
+   const char *table;
+   const char *id;
+   bool ret=false;
+
+   tag->gen_sql(jcr, this, &table, &name, &id, esc, esc_name, &aclbits, &aclbits_extra);
+
+   bdb_lock();
+   /* TODO: Need a special kind of ACL */
+   const char *whereand = get_acls(aclbits, false);
+   const char *join = get_acl_join_filter(aclbits_extra);
+
+   if (*esc_name && *esc) {             /* We have a tag name */
+      Mmsg(cmd, "INSERT INTO Tag%s (Tag, %s) VALUES ('%s', (SELECT %s FROM %s %s WHERE %s = '%s' %s))",
+           table, id, esc_name, id, table, join, (tag->JobId>0)?id:name, esc, whereand);
+      ret = bdb_sql_query(cmd, NULL, (void *)NULL);
+
+   } else {
+      Dmsg2(DT_SQL|50, "Tag invalid esc_name='%s' esc='%s'\n", esc_name, esc);
+   }
+   bdb_unlock();
+   return ret;
+}
+
 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL */
index e97ecfe13e6bfcafb54d8064a285a35135e2c5d4..eaa4e8ca0f21e677bdbc5d3a52583cd6d817890d 100644 (file)
@@ -186,6 +186,9 @@ int BDB::bdb_delete_media_record(JCR *jcr, MEDIA_DBR *mr)
 
    Mmsg(cmd, "DELETE FROM Media WHERE MediaId=%lu", mr->MediaId);
    bdb_sql_query(cmd, NULL, (void *)NULL);
+
+   Mmsg(cmd, "DELETE FROM TagMedia WHERE MediaId=%lu", mr->MediaId);
+   bdb_sql_query(cmd, NULL, (void *)NULL);
    bdb_unlock();
    return 1;
 }
@@ -243,8 +246,47 @@ int BDB::bdb_delete_client_record(JCR *jcr, CLIENT_DBR *cr)
 
    Mmsg(cmd, "DELETE FROM Client WHERE ClientId=%d", cr->ClientId);
    bdb_sql_query(cmd, NULL, (void *)NULL);
+
+   Mmsg(cmd, "DELETE FROM TagClient WHERE ClientId=%d", cr->ClientId);
+   bdb_sql_query(cmd, NULL, (void *)NULL);
    bdb_unlock();
    return 1;
 }
 
+/* Delete Tag record */
+int BDB::bdb_delete_tag_record(JCR *jcr, TAG_DBR *tag)
+{
+   char esc[MAX_ESCAPE_NAME_LENGTH];
+   char esc_name[MAX_ESCAPE_NAME_LENGTH];
+   uint64_t aclbits, aclbits_extra;
+   const char *name;
+   const char *table;
+   const char *id;
+   bool ret = false;
+
+   tag->gen_sql(jcr, this, &table, &name, &id, esc, esc_name, &aclbits, &aclbits_extra);
+
+   bdb_lock();
+   /* TODO: Need a special kind of ACL */
+   const char *join = get_acl_join_filter(aclbits_extra);
+   const char *whereand = get_acls(aclbits, false);
+
+   if (*esc_name) {             /* We have a tag name */
+      if (tag->all) {
+         Mmsg(cmd, "DELETE FROM Tag%s WHERE Tag = '%s'", table, esc_name);
+
+      } else {
+         Mmsg(cmd, "DELETE FROM Tag%s WHERE Tag = '%s' AND %s IN (SELECT W.%sId FROM %s AS W %s WHERE W.%s = '%s' %s)",
+                                   table,      esc_name,   id,          table,    table,   join,    name, esc,  whereand);
+      }
+   } else {
+      Mmsg(cmd, "DELETE FROM Tag%s WHERE %sId IN (SELECT W.%s FROM %s AS W  %s   WHERE W.%s = '%s' %s)",
+                              table,     table,            id,  table, join,     name, esc, whereand);
+   }
+   Dmsg1(DT_SQL|50, "q=%s\n", cmd);
+   ret = bdb_sql_query(cmd, NULL, (void *)NULL);
+   bdb_unlock();
+   return ret;
+}
+
 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL */
index e2825d729c845835c8f791a62d2bd10cac51d86d..2f73a28bf2f78286066403e7c70c46bf20deb487 100644 (file)
@@ -255,8 +255,8 @@ void BDB::bdb_list_media_records(JCR *jcr, MEDIA_DBR *mdbr,
 
    bdb_lock();
    bdb_escape_string(jcr, esc, mdbr->VolumeName, strlen(mdbr->VolumeName));
-   const char *join = get_acl_join_filter(DB_ACL_BIT(DB_ACL_POOL));
    const char *where = get_acl(DB_ACL_POOL, false);
+   const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_POOL)) : "";
 
    if (type == VERT_LIST) {
       if (mdbr->VolumeName[0] != 0) {
@@ -338,13 +338,14 @@ void BDB::bdb_list_jobmedia_records(JCR *jcr, uint32_t JobId,
 
    bdb_lock();
    /* Get some extra SQL parameters if needed */
-   const char *join = get_acl_join_filter(DB_ACL_BIT(DB_ACL_JOB)     |
-                                          DB_ACL_BIT(DB_ACL_FILESET) |
-                                          DB_ACL_BIT(DB_ACL_CLIENT));
    const char *where = get_acls(DB_ACL_BIT(DB_ACL_JOB)     |
                                 DB_ACL_BIT(DB_ACL_FILESET) |
                                 DB_ACL_BIT(DB_ACL_CLIENT), (JobId == 0));
 
+   const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_JOB)     |
+                                                   DB_ACL_BIT(DB_ACL_FILESET) |
+                                                   DB_ACL_BIT(DB_ACL_CLIENT)) : "";
+
    if (type == VERT_LIST) {
       if (JobId > 0) {                   /* do by JobId */
          Mmsg(cmd, "SELECT JobMediaId,JobId,Media.MediaId,Media.VolumeName,"
@@ -446,8 +447,8 @@ void BDB::bdb_list_copies_records(JCR *jcr, uint32_t limit, char *JobIds,
    }
 
    bdb_lock();
-   const char *join = get_acl_join_filter(DB_ACL_BIT(DB_ACL_CLIENT));
    const char *where = get_acls(DB_ACL_BIT(DB_ACL_JOB) | DB_ACL_BIT(DB_ACL_CLIENT), false);
+   const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_CLIENT)) : "";
 
    Mmsg(cmd,
    "SELECT DISTINCT Job.PriorJobId AS JobId, Job.Job, "
@@ -567,13 +568,14 @@ void BDB::bdb_list_joblog_records(JCR *jcr, uint32_t JobId,
    }
    bdb_lock();
 
-   const char *join = get_acl_join_filter(DB_ACL_BIT(DB_ACL_JOB)     |
-                                          DB_ACL_BIT(DB_ACL_FILESET) |
-                                          DB_ACL_BIT(DB_ACL_CLIENT));
    const char *where = get_acls(DB_ACL_BIT(DB_ACL_JOB)     |
                                 DB_ACL_BIT(DB_ACL_FILESET) |
                                 DB_ACL_BIT(DB_ACL_CLIENT), false);
 
+   const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_JOB)     |
+                                                   DB_ACL_BIT(DB_ACL_FILESET) |
+                                                   DB_ACL_BIT(DB_ACL_CLIENT)) : "";
+
    if (type == VERT_LIST) {
       Mmsg(cmd, "SELECT Time,LogText FROM Log %s "
            "WHERE Log.JobId=%s %s ORDER BY LogId ASC",
@@ -618,6 +620,7 @@ alist *BDB::bdb_list_job_records(JCR *jcr, JOB_DBR *jr, DB_LIST_HANDLER *sendit,
    POOLMEM *tmp    = get_pool_memory(PM_MESSAGE);
    const char *order = "ASC";
    const char *join = "";
+   const char *where_tmp = "";
    *where = 0;
 
    bdb_lock();
@@ -677,13 +680,16 @@ alist *BDB::bdb_list_job_records(JCR *jcr, JOB_DBR *jr, DB_LIST_HANDLER *sendit,
       append_filter(where, tmp);
    }
 
-   pm_strcat(where, get_acls(DB_ACL_BIT(DB_ACL_CLIENT)  |
-                             DB_ACL_BIT(DB_ACL_JOB)     |
-                             DB_ACL_BIT(DB_ACL_FILESET),
-                             where[0] == 0));
+   where_tmp = get_acls(DB_ACL_BIT(DB_ACL_CLIENT)  |
+                        DB_ACL_BIT(DB_ACL_JOB)     |
+                        DB_ACL_BIT(DB_ACL_FILESET),
+                        where[0] == 0);
+   pm_strcat(where, where_tmp);
 
-   join = get_acl_join_filter(DB_ACL_BIT(DB_ACL_CLIENT)  |
-                              DB_ACL_BIT(DB_ACL_FILESET));
+   if (*where_tmp) {
+      join = get_acl_join_filter(DB_ACL_BIT(DB_ACL_CLIENT)  |
+                                 DB_ACL_BIT(DB_ACL_FILESET));
+   }
 
    switch (type) {
    case VERT_LIST:
@@ -743,8 +749,8 @@ alist *BDB::bdb_list_job_records(JCR *jcr, JOB_DBR *jr, DB_LIST_HANDLER *sendit,
 void BDB::bdb_list_job_totals(JCR *jcr, JOB_DBR *jr, DB_LIST_HANDLER *sendit, void *ctx)
 {
    bdb_lock();
-   const char *join = get_acl_join_filter(DB_ACL_BIT(DB_ACL_CLIENT));
    const char *where = get_acls(DB_ACL_BIT(DB_ACL_CLIENT) | DB_ACL_BIT(DB_ACL_JOB), true);
+   const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_CLIENT)) : "";
 
    /* List by Job */
    Mmsg(cmd, "SELECT  count(*) AS Jobs,sum(JobFiles) "
@@ -799,14 +805,14 @@ void BDB::bdb_list_files_for_job(JCR *jcr, JobId_t jobid, int deleted, DB_LIST_H
 
    bdb_lock();
    /* Get optional filters for the SQL query */
-   const char *join = get_acl_join_filter(DB_ACL_BIT(DB_ACL_JOB) |
-                                          DB_ACL_BIT(DB_ACL_CLIENT) |
-                                          DB_ACL_BIT(DB_ACL_FILESET));
-
    const char *where = get_acls(DB_ACL_BIT(DB_ACL_JOB) |
                                 DB_ACL_BIT(DB_ACL_CLIENT) |
                                 DB_ACL_BIT(DB_ACL_FILESET), true);
 
+   const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_JOB) |
+                                                   DB_ACL_BIT(DB_ACL_CLIENT) |
+                                                   DB_ACL_BIT(DB_ACL_FILESET)) : "";
+
    /*
     * MySQL is different with no || operator
     */
@@ -999,5 +1005,52 @@ bail_out:
    bdb_unlock();
 }
 
+void BDB::bdb_list_tag_records(JCR *jcr, TAG_DBR *tag, DB_LIST_HANDLER *result_handler, void *ctx, e_list_type type)
+{
+   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, %s AS %s FROM Tag%s AS T JOIN %s USING (%s) %s WHERE T.Tag = '%s' %s",
+                 id, name, table, table, table, id, join, esc_name, whereand);
+         } else {
+            /* Display all tags for a resource type */
+            Mmsg(tmp, "SELECT DISTINCT T.Tag, %s AS %s, %s AS %s FROM Tag%s AS T JOIN %s USING (%s) %s %s",
+                 id, id, name, table, table, table, id, join, where.c_str());
+         }
+
+      } else {
+         if (*esc_name) {
+            Mmsg(tmp, "SELECT T.Tag, %s as %s, %s AS %s FROM Tag%s AS T JOIN %s USING (%s) %s WHERE %s = '%s' AND T.Tag = '%s' %s",
+                 id, id, name, table, table, table, id, join, name, esc, esc_name, whereand);
+
+         } else {
+            /* Display all tags for a client */
+            Mmsg(tmp, "SELECT Tag, %s as %s, %s as %s FROM Tag%s AS T JOIN %s USING (%s) %s WHERE %s = '%s' %s",
+                 id, id, (tag->JobId>0)?"Name":name, table, table, table, id, join, name, esc, whereand);
+         }
+      }
+      Dmsg1(DT_SQL|50, "q=%s\n", tmp.c_str());
+      bdb_list_sql_query(jcr, tmp.c_str(), result_handler, ctx, 0, type);
+   }
+   bdb_unlock();
+}
+
 
 #endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL */
index cd726db7f4c5baad7f492da80e50d09cba476494..98a2be3fdff70b27fd60d97e9f0935f1b2ecd4bd 100644 (file)
@@ -669,6 +669,7 @@ RES_ITEM job_items[] = {
    {"DeleteConsolidatedJobs",  store_bool, ITEM(res_job.DeleteConsolidatedJobs), 0, ITEM_DEFAULT, false},
    {"PluginOptions", store_str, ITEM(res_job.PluginOptions), 0, 0, 0},
    {"Base", store_alist_res, ITEM(res_job.base),  R_JOB, 0, 0},
+   {"Tag",         store_alist_str, ITEM(res_job.tag), 0, 0, 0},
    {NULL, NULL, {0}, 0, 0, 0}
 };
 
@@ -1854,6 +1855,9 @@ void free_resource(RES *rres, int type)
          free_runscripts(res->res_job.RunScripts);
          delete res->res_job.RunScripts;
       }
+      if (res->res_job.tag) {
+         delete res->res_job.tag;
+      }
       break;
    case R_MSGS:
       if (res->res_msgs.mail_cmd) {
@@ -2072,7 +2076,7 @@ bool save_resource(CONFIG *config, int type, RES_ITEM *items, int pass)
          res->res_job.jobdefs    = res_all.res_job.jobdefs;
          res->res_job.run_cmds   = res_all.res_job.run_cmds;
          res->res_job.RunScripts = res_all.res_job.RunScripts;
-
+         res->res_job.tag        = res_all.res_job.tag;
          /* TODO: JobDefs where/regexwhere doesn't work well (but this
           * is not very useful)
           * We have to set_bit(index, res_all.hdr.item_present);
index fde5a0d3e439ec9abea221dcb319dc0c19b32ab5..8d0700c7400c23ced2b1ad196620728edb9a5cc3 100644 (file)
@@ -514,6 +514,7 @@ public:
    bool PurgeMigrateJob;              /* Purges source job on completion */
    bool DeleteConsolidatedJobs;       /* Delete or not consolidated Virtual Full jobs */
 
+   alist *tag;                        /* tags defined for this Job */
    alist *base;                       /* Base jobs */
    int64_t max_bandwidth;             /* Speed limit on this job */
 
index c3457b9489e445dea5d1f9392d369ce0caa8b6c3..e67a32091c3a0a637ca032c267b69e8c21d8a87a 100644 (file)
@@ -170,6 +170,15 @@ bool setup_job(JCR *jcr)
       goto bail_out;
    }
    jcr->JobId = jcr->jr.JobId;
+   if (jcr->job->tag) {
+      char *tag;
+      TAG_DBR Tag;
+      Tag.JobId = jcr->JobId;
+      foreach_alist(tag, jcr->job->tag) {
+         bstrncpy(Tag.Name, tag, sizeof(Tag.Name)-1);
+         db_create_tag_record(jcr, jcr->db, &Tag);
+      }
+   }
    Dmsg4(100, "Created job record JobId=%d Name=%s Type=%c Level=%c\n",
        jcr->JobId, jcr->Job, jcr->jr.JobType, jcr->jr.JobLevel);
 
index fad8bebaf3d7327e22ca09b14858290edcca57a8..7fdbabce606218660c08c319b2c55a14ca65da32 100644 (file)
@@ -215,6 +215,7 @@ bool acl_access_client_ok(UAContext *ua, const char *name, int32_t jobtype);
 bool acl_access_console_ok(UAContext *ua, const char *name);
 
 /* ua_cmds.c */
+int tag_cmd(UAContext *ua, const char *cmd);
 bool get_uid_gid_from_acl(UAContext *ua, alist **uid, alist **gid, alist **dir);
 bool do_a_command(UAContext *ua);
 bool do_a_dot_command(UAContext *ua);
index 0eae2112e7d228cb2e2a842868187682c220a0dc..7d59e88fd0bebb68c1efd48acf456f38b7da29b3 100644 (file)
@@ -130,7 +130,7 @@ static struct cmdstruct commands[] = {                                      /* C
 
  { NT_("label"),      label_cmd,     _("Label a tape"), NT_("storage=<storage> volume=<vol> pool=<pool> slot=<slot> drive=<nb> barcodes"), false},
  { NT_("list"),       list_cmd,      _("List objects from catalog"),
-   NT_("jobs [client=<cli>] [jobid=<nn>] [ujobid=<name>] [job=<name>] [joberrors] [jobstatus=<s>] [level=<l>] [jobtype=<t>] [limit=<n>] [order=<asc|desc>]|\n"
+   NT_("jobs [client=<cli>] [jobid=<nn>] [ujobid=<name>] [job=<name>] [tag=<name>] [joberrors] [jobstatus=<s>] [level=<l>] [jobtype=<t>] [limit=<n>] [order=<asc|desc>]|\n"
        "\tjobtotals | pools | volume | media <pool=pool-name> | files [type=<deleted|all>] jobid=<nn> | copies jobid=<nn> |\n"
        "\tjoblog jobid=<nn> | pluginrestoreconf jobid=<nn> restoreobjectid=<nn> | snapshot | \n"
        "\tfilemedia jobid=<nn> fileindex=<mm> | clients\n"
@@ -138,7 +138,7 @@ static struct cmdstruct commands[] = {                                      /* C
       ), false},
 
  { NT_("llist"),      llist_cmd,     _("Full or long list like list command"),
-   NT_("jobs [client=<cli>] [jobid=<nn>] [ujobid=<name>] [job=<name>] [joberrors] [jobstatus=<s>] [level=<l>] [jobtype=<t>] [order=<asc/desc>] [limit=<n>]|\n"
+   NT_("jobs [client=<cli>] [jobid=<nn>] [ujobid=<name> [tag=<name>] [job=<name>] [joberrors] [jobstatus=<s>] [level=<l>] [jobtype=<t>] [order=<asc/desc>] [limit=<n>]|\n"
        "\tjobtotals | pools | volume | media <pool=pool-name> | files jobid=<nn> | copies jobid=<nn> |\n"
        "\tjoblog jobid=<nn> | pluginrestoreconf jobid=<nn> restoreobjectid=<nn> | snapshot |\n"
        "\tfilemedia jobid=<nn> fileindex=<mm> | clients\n"), false},
@@ -205,6 +205,7 @@ static struct cmdstruct commands[] = {                                      /* C
  { NT_("sqlquery"),   sqlquery_cmd,  _("Use SQL to query catalog"), NT_(""),          false},
  { NT_("statistics"),    collect_cmd,   _("Display daemon statistics data"), NT_("[ simple | full | json ] all | <metric>\n"
        "\t[ client=<client-name> | storage=<storage-name> ]"), true},
+ { NT_("tag"),        tag_cmd,       _("Manage tags"), NT_("[add|list|delete] [name=<str>] [job=<job>|jobid=<jobid>|client=<cli>|volume=<vol>]"), false},
  { NT_("time"),       time_cmd,      _("Print current time"),       NT_(""),          true},
  { NT_("trace"),      trace_cmd,     _("Turn on/off trace to file"), NT_("on | off"), true},
  { NT_("truncate"),   truncate_cmd,  _("Truncate one or more Volumes"), NT_("volume=<vol> [mediatype=<type> pool=<pool> allpools storage=<st> drive=<num>]"),  true},
@@ -1558,6 +1559,207 @@ bail_out:
    return 1;
 }
 
+class tag_mngt: public TAG_DBR
+{
+public:
+   int         action;          /* 1: create, 2: delete, 3: list, 0: nothing */
+   int         target;          /* 1: client, 2: job, 3: volume */
+
+   tag_mngt() {
+      zero();
+      action = 0;
+      target = 0;
+   };
+
+   int scan_command(UAContext *ua) {
+      for(int i = 0 ; i < ua->argc ; i++) {
+         if (strcasecmp(ua->argk[i], NT_("client")) == 0) {
+            if (is_name_valid(ua->argv[i], NULL)) {
+               bstrncpy(Client, ua->argv[i], sizeof(Client));
+
+            } else {
+               all = 1;
+               bstrncpy(Client, "*", sizeof(Client));
+            }
+            target = 1;
+
+         } else if (strcasecmp(ua->argk[i], NT_("job")) == 0 ||
+                    strcasecmp(ua->argk[i], NT_("jobs")) == 0) /* Compatible with list jobs tag= */
+         {
+            if (is_name_valid(ua->argv[i], NULL)) {
+               bstrncpy(Job, ua->argv[i], sizeof(Job));
+
+            } else {
+               all = 1;
+               bstrncpy(Job, "*", sizeof(Job));
+            }
+            target = 2;
+#if 0
+         } else if (strcasecmp(ua->argk[i], NT_("pool")) == 0) {
+            if (is_name_valid(ua->argv[i], NULL)) {
+               bstrncpy(Pool, ua->argv[i], sizeof(Pool));
+
+            } else {
+               all = 1;
+               bstrncpy(Pool, "*", sizeof(Pool));
+            }
+#endif
+
+         } else if (strcasecmp(ua->argk[i], NT_("volume")) == 0) {
+            if (is_name_valid(ua->argv[i], NULL)) {
+               bstrncpy(Volume, ua->argv[i], sizeof(Volume));
+
+            } else {
+               all = 1;
+               bstrncpy(Volume, "*", sizeof(Volume));
+            }
+            target = 3;
+
+         } else if (strcasecmp(ua->argk[i], NT_("jobid")) == 0 && is_a_number(ua->argv[i])) {
+            JobId = str_to_uint64(ua->argv[i]);
+            target = 2;
+
+         } else if (strcasecmp(ua->argk[i], NT_("name")) == 0 && is_name_valid(ua->argv[i], NULL, "#")) {
+            bstrncpy(Name, ua->argv[i], sizeof(Name));
+
+         } else if (strcasecmp(ua->argk[i], NT_("tag")) == 0 && is_name_valid(ua->argv[i], NULL, "#")) {
+            bstrncpy(Name, ua->argv[i], sizeof(Name));
+
+         } else if (strcasecmp(ua->argk[i], NT_("add")) == 0) {
+            action = 1;
+
+         } else if (strcasecmp(ua->argk[i], NT_("delete")) == 0) {
+            action = 2;
+
+         } else if (strcasecmp(ua->argk[i], NT_("list")) == 0) {
+            action = 3;
+         }
+      }
+      return 1;
+   };
+};
+
+/*
+ * tag management
+ */
+static int delete_tag(UAContext *ua)
+{
+   return 1;
+}
+
+/*
+ * tag management
+ */
+int tag_cmd(UAContext *ua, const char *cmd)
+{
+   tag_mngt t;
+   t.scan_command(ua);
+
+   if (!open_client_db(ua)) {
+      return 1;
+   }
+   if (t.action == 0) {
+      /*
+       * We didn't find an appropriate keyword above, so
+       * prompt the user.
+       */
+      start_prompt(ua, _("Available Tag operations:\n"));
+      add_prompt(ua, _("Add"));
+      add_prompt(ua, _("Delete"));
+      add_prompt(ua, _("List"));
+
+      t.action = 1 + do_prompt(ua, "", _("Select Tag operation"), NULL, 0);
+
+      if (t.action == 0) {
+         return 1;
+      }
+   }
+   if (t.target == 0) {
+      start_prompt(ua, _("Available Tag target:\n"));
+      add_prompt(ua, _("Client")); /* 1 */
+      add_prompt(ua, _("Job"));    /* 2 */
+      add_prompt(ua, _("Volume")); /* 3 */
+
+      t.target = 1 + do_prompt(ua, "", _("Select Tag target"), NULL, 0);
+
+      switch(t.target) {
+      case 1:
+      {
+         CLIENT *cl = get_client_resource(ua, JT_BACKUP_RESTORE);
+         if (cl) {
+            bstrncpy(t.Client, cl->name(), sizeof(t.Client)-1);
+         } else {
+            return 1;
+         }
+         break;
+      }
+      case 2:
+      {
+         JOB_DBR jr;
+         bmemset(&jr, 0, sizeof(jr));
+         t.JobId = get_job_dbr(ua, &jr);
+
+         if (t.JobId == 0) {
+            return 1;
+         }
+         break;
+      }
+      case 3:
+      {
+         MEDIA_DBR mr;
+         if (!select_media_dbr(ua, &mr)) {
+            return 1;
+         }
+         bstrncpy(t.Volume, mr.VolumeName, sizeof(t.Volume)-1);
+         break;
+      }
+      default:
+         return 1;
+      }
+   }
+
+   if (t.target == 0) {
+      return 1;
+   }
+
+   switch(t.action) {
+   case 1: /* create */
+      if (*t.Name == 0) {
+         if (!get_cmd(ua, _("Enter the Tag value: "), false)) {
+            return false;
+         }
+         if (strlen(ua->cmd) > sizeof(t.Name)-1) {
+            ua->error_msg(_("Invalid tag\n"));
+            return false;
+         }
+         bstrncpy(t.Name, ua->cmd, sizeof(t.Name)-1);
+      }
+      if (db_create_tag_record(ua->jcr, ua->db, &t)) {
+         ua->send_msg(_("1000 Tag added\n"));
+      } else {
+         ua->error_msg(_("1009 Unable to add specified Tag.\n"));
+      }
+      break;
+
+   case 2: /* delete */
+      if (db_delete_tag_record(ua->jcr, ua->db, &t)) {
+         ua->send_msg(_("1000 Tag deleted\n"));
+      } else {
+         ua->error_msg(_("1009 Unable to delete specified Tag\n"));
+      }
+      break;
+
+   case 3: /* list */
+      db_list_tag_records(ua->jcr, ua->db, &t, prtit, ua, HORZ_LIST);
+      break;
+
+   default:
+      return 0;
+   }
+   
+   return 1;
+}
+
 /*
  * print time
  */
@@ -1598,6 +1800,7 @@ static int delete_cmd(UAContext *ua, const char *cmd)
       NT_("jobid"),
       NT_("snapshot"),
       NT_("client"),
+      NT_("tag"),
       NULL};
 
    /* Deleting large jobs can take time! */
@@ -1625,6 +1828,12 @@ static int delete_cmd(UAContext *ua, const char *cmd)
    case 4:
       delete_client(ua);
       return 1;
+   case 5:
+      /* We don't want to manage tag deletion via the delete command, an
+       * accident can come very quickly (we have client, volume, etc... in the
+       * same) command line */
+
+      /* failback wanted */
    default:
       break;
    }
@@ -1649,6 +1858,9 @@ static int delete_cmd(UAContext *ua, const char *cmd)
    case 4:
       delete_client(ua);
       return 1;
+   case 5:
+      delete_tag(ua);
+      return 1;
    default:
       ua->warning_msg(_("Nothing done.\n"));
       break;
index 8a017f19efe5dfb12abf83c56b04077ff0638b76..24dd5817250cbc01debc0d9b25b4489104950f6a 100644 (file)
@@ -826,6 +826,9 @@ static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist)
          }
          db_list_events_records(ua->jcr,ua->db, &event, prtit, ua, llist);
 
+      } else if (strcasecmp(ua->argk[i], NT_("tag")) == 0) {
+         return tag_cmd(ua, cmd);
+
       } else if (strcasecmp(ua->argk[i], NT_("limit")) == 0
                  || strcasecmp(ua->argk[i], NT_("days")) == 0
                  || strcasecmp(ua->argk[i], NT_("joberrors")) == 0
@@ -838,6 +841,7 @@ static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist)
                  || strcasecmp(ua->argk[i], NT_("long")) == 0
                  || strcasecmp(ua->argk[i], NT_("start")) == 0
                  || strcasecmp(ua->argk[i], NT_("end")) == 0
+                 || strcasecmp(ua->argk[i], NT_("name")) == 0
          ) {
          /* Ignore it */
       } else if (strcasecmp(ua->argk[i], NT_("snapshot")) == 0 ||
index 75ac8e56c7046c38c7db9df4c7d09ab384e088d7..4d700f47f4f30d1895fd252057f667bb0e58bdb5 100644 (file)
@@ -290,6 +290,10 @@ void purge_files_from_jobs(UAContext *ua, char *jobs)
 {
    POOL_MEM query(PM_MESSAGE);
 
+   Mmsg(query, "DELETE FROM TagJob WHERE JobId IN (%s)", jobs);
+   db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+   Dmsg1(050, "Delete TagJob sql=%s\n", query.c_str());
+
    Mmsg(query, "DELETE FROM File WHERE JobId IN (%s)", jobs);
    db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
    Dmsg1(050, "Delete File sql=%s\n", query.c_str());