From a38ac264c79d3dd51d9cbd7b2ce852e7ea85a108 Mon Sep 17 00:00:00 2001 From: Eric Bollengier Date: Fri, 28 Aug 2020 10:46:44 +0200 Subject: [PATCH] Add first support for tags in the Director --- bacula/src/cats/bdb.h | 3 + bacula/src/cats/cats.h | 33 ++++++ bacula/src/cats/protos.h | 8 +- bacula/src/cats/sql.c | 108 ++++++++++++++++++ bacula/src/cats/sql_create.c | 29 +++++ bacula/src/cats/sql_delete.c | 42 +++++++ bacula/src/cats/sql_list.c | 91 ++++++++++++--- bacula/src/dird/dird_conf.c | 6 +- bacula/src/dird/dird_conf.h | 1 + bacula/src/dird/job.c | 9 ++ bacula/src/dird/protos.h | 1 + bacula/src/dird/ua_cmds.c | 216 ++++++++++++++++++++++++++++++++++- bacula/src/dird/ua_output.c | 4 + bacula/src/dird/ua_purge.c | 4 + 14 files changed, 532 insertions(+), 23 deletions(-) diff --git a/bacula/src/cats/bdb.h b/bacula/src/cats/bdb.h index a9d55295c..c3193f1d6 100644 --- a/bacula/src/cats/bdb.h +++ b/bacula/src/cats/bdb.h @@ -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); diff --git a/bacula/src/cats/cats.h b/bacula/src/cats/cats.h index 8548bad99..60d82feeb 100644 --- a/bacula/src/cats/cats.h +++ b/bacula/src/cats/cats.h @@ -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: diff --git a/bacula/src/cats/protos.h b/bacula/src/cats/protos.h index 53fdbc0b6..f4e9e9b15 100644 --- a/bacula/src/cats/protos.h +++ b/bacula/src/cats/protos.h @@ -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) \ diff --git a/bacula/src/cats/sql.c b/bacula/src/cats/sql.c index 426aefc7b..0d7b5532e 100644 --- a/bacula/src/cats/sql.c +++ b/bacula/src/cats/sql.c @@ -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) { diff --git a/bacula/src/cats/sql_create.c b/bacula/src/cats/sql_create.c index 4169ee167..b8a1a3bfd 100644 --- a/bacula/src/cats/sql_create.c +++ b/bacula/src/cats/sql_create.c @@ -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 */ diff --git a/bacula/src/cats/sql_delete.c b/bacula/src/cats/sql_delete.c index e97ecfe13..eaa4e8ca0 100644 --- a/bacula/src/cats/sql_delete.c +++ b/bacula/src/cats/sql_delete.c @@ -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 */ diff --git a/bacula/src/cats/sql_list.c b/bacula/src/cats/sql_list.c index e2825d729..2f73a28bf 100644 --- a/bacula/src/cats/sql_list.c +++ b/bacula/src/cats/sql_list.c @@ -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 */ diff --git a/bacula/src/dird/dird_conf.c b/bacula/src/dird/dird_conf.c index cd726db7f..98a2be3fd 100644 --- a/bacula/src/dird/dird_conf.c +++ b/bacula/src/dird/dird_conf.c @@ -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); diff --git a/bacula/src/dird/dird_conf.h b/bacula/src/dird/dird_conf.h index fde5a0d3e..8d0700c74 100644 --- a/bacula/src/dird/dird_conf.h +++ b/bacula/src/dird/dird_conf.h @@ -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 */ diff --git a/bacula/src/dird/job.c b/bacula/src/dird/job.c index c3457b948..e67a32091 100644 --- a/bacula/src/dird/job.c +++ b/bacula/src/dird/job.c @@ -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); diff --git a/bacula/src/dird/protos.h b/bacula/src/dird/protos.h index fad8bebaf..7fdbabce6 100644 --- a/bacula/src/dird/protos.h +++ b/bacula/src/dird/protos.h @@ -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); diff --git a/bacula/src/dird/ua_cmds.c b/bacula/src/dird/ua_cmds.c index 0eae2112e..7d59e88fd 100644 --- a/bacula/src/dird/ua_cmds.c +++ b/bacula/src/dird/ua_cmds.c @@ -130,7 +130,7 @@ static struct cmdstruct commands[] = { /* C { NT_("label"), label_cmd, _("Label a tape"), NT_("storage= volume= pool= slot= drive= barcodes"), false}, { NT_("list"), list_cmd, _("List objects from catalog"), - NT_("jobs [client=] [jobid=] [ujobid=] [job=] [joberrors] [jobstatus=] [level=] [jobtype=] [limit=] [order=]|\n" + NT_("jobs [client=] [jobid=] [ujobid=] [job=] [tag=] [joberrors] [jobstatus=] [level=] [jobtype=] [limit=] [order=]|\n" "\tjobtotals | pools | volume | media | files [type=] jobid= | copies jobid= |\n" "\tjoblog jobid= | pluginrestoreconf jobid= restoreobjectid= | snapshot | \n" "\tfilemedia jobid= fileindex= | 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=] [jobid=] [ujobid=] [job=] [joberrors] [jobstatus=] [level=] [jobtype=] [order=] [limit=]|\n" + NT_("jobs [client=] [jobid=] [ujobid= [tag=] [job=] [joberrors] [jobstatus=] [level=] [jobtype=] [order=] [limit=]|\n" "\tjobtotals | pools | volume | media | files jobid= | copies jobid= |\n" "\tjoblog jobid= | pluginrestoreconf jobid= restoreobjectid= | snapshot |\n" "\tfilemedia jobid= fileindex= | 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 | \n" "\t[ client= | storage= ]"), true}, + { NT_("tag"), tag_cmd, _("Manage tags"), NT_("[add|list|delete] [name=] [job=|jobid=|client=|volume=]"), 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= [mediatype= pool= allpools storage= drive=]"), 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; diff --git a/bacula/src/dird/ua_output.c b/bacula/src/dird/ua_output.c index 8a017f19e..24dd58172 100644 --- a/bacula/src/dird/ua_output.c +++ b/bacula/src/dird/ua_output.c @@ -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 || diff --git a/bacula/src/dird/ua_purge.c b/bacula/src/dird/ua_purge.c index 75ac8e56c..4d700f47f 100644 --- a/bacula/src/dird/ua_purge.c +++ b/bacula/src/dird/ua_purge.c @@ -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()); -- 2.47.3