From: Eric Bollengier Date: Mon, 12 Dec 2022 20:15:00 +0000 (+0100) Subject: Add Content preview to FileSet catalog record. X-Git-Tag: Beta-15.0.0~320 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e24ac95dac69abe62d316e7241261f71f4859bfb;p=thirdparty%2Fbacula.git Add Content preview to FileSet catalog record. The FileSet catalog record is now created during the Director startup, it was done during the Job creation. The Content field will represent the FileSet overview. If the FileSet has files and directories, it will contain "files" If the FileSet has plugin entries, each plugin will be added to the Content field. Ex: Content="files,mysql,postgresql" --- diff --git a/bacula/src/cats/cats.h b/bacula/src/cats/cats.h index 84c1650be..832c13b27 100644 --- a/bacula/src/cats/cats.h +++ b/bacula/src/cats/cats.h @@ -536,6 +536,7 @@ struct FILESET_DBR { char FileSet[MAX_NAME_LENGTH]; /* FileSet name */ char MD5[50]; /* MD5 signature of include/exclude */ time_t CreateTime; /* date created */ + char Content[MAX_PLUGIN_LENGTH]; /* List of plugins in a fileset record */ /* * This is where we return CreateTime */ diff --git a/bacula/src/cats/make_mysql_tables.in b/bacula/src/cats/make_mysql_tables.in index 5b29d08c5..31e19a065 100644 --- a/bacula/src/cats/make_mysql_tables.in +++ b/bacula/src/cats/make_mysql_tables.in @@ -396,6 +396,7 @@ CREATE TABLE FileSet ( FileSet TINYBLOB NOT NULL, MD5 TINYBLOB, CreateTime DATETIME, + Content BLOB DEFAULT '', PRIMARY KEY(FileSetId) ); diff --git a/bacula/src/cats/make_postgresql_tables.in b/bacula/src/cats/make_postgresql_tables.in index 21d185502..ba1db4622 100644 --- a/bacula/src/cats/make_postgresql_tables.in +++ b/bacula/src/cats/make_postgresql_tables.in @@ -329,6 +329,7 @@ CREATE TABLE fileset fileset text not null, md5 text not null, createtime timestamp without time zone not null, + content text default '', primary key (filesetid) ); diff --git a/bacula/src/cats/make_sqlite3_tables.in b/bacula/src/cats/make_sqlite3_tables.in index da019118f..cf09c0203 100644 --- a/bacula/src/cats/make_sqlite3_tables.in +++ b/bacula/src/cats/make_sqlite3_tables.in @@ -342,6 +342,7 @@ CREATE TABLE FileSet ( FileSet VARCHAR(128) NOT NULL, MD5 VARCHAR(25) NOT NULL, CreateTime DATETIME DEFAULT 0, + Content TEXT DEFAULT '', PRIMARY KEY(FileSetId) ); diff --git a/bacula/src/cats/sql_create.c b/bacula/src/cats/sql_create.c index 4b7b8e0ea..15cefb5d7 100644 --- a/bacula/src/cats/sql_create.c +++ b/bacula/src/cats/sql_create.c @@ -733,13 +733,15 @@ bool BDB::bdb_create_fileset_record(JCR *jcr, FILESET_DBR *fsr) struct tm tm; char esc_fs[MAX_ESCAPE_NAME_LENGTH]; char esc_md5[MAX_ESCAPE_NAME_LENGTH]; + char esc_content[MAX_ESCAPE_PLUGIN_LENGTH]; /* TODO: Escape FileSet and MD5 */ bdb_lock(); fsr->created = false; bdb_escape_string(jcr, esc_fs, fsr->FileSet, strlen(fsr->FileSet)); bdb_escape_string(jcr, esc_md5, fsr->MD5, strlen(fsr->MD5)); - Mmsg(cmd, "SELECT FileSetId,CreateTime FROM FileSet WHERE " + bdb_escape_string(jcr, esc_content, fsr->Content, strlen(fsr->Content)); + Mmsg(cmd, "SELECT FileSetId,CreateTime,Content FROM FileSet WHERE " "FileSet='%s' AND MD5='%s'", esc_fs, esc_md5); fsr->FileSetId = 0; @@ -762,6 +764,17 @@ bool BDB::bdb_create_fileset_record(JCR *jcr, FILESET_DBR *fsr) } else { bstrncpy(fsr->cCreateTime, row[1], sizeof(fsr->cCreateTime)); } + if ((row[2] == NULL || strcmp(row[2], "") == 0) && esc_content[0]) { + Mmsg(cmd, "UPDATE FileSet SET Content='%s' WHERE FileSetId=%ld", + esc_content, fsr->FileSetId); + if (!UpdateDB(jcr, cmd, false /* should match*/)) { + /* If we cannot update, this is not the end of the world, but we + * can display a message + */ + Dmsg2(50, "Unable to update FileSet content field for %ld ERR=%s\n", + fsr->FileSetId, sql_strerror()); + } + } sql_free_result(); bdb_unlock(); return true; @@ -776,8 +789,8 @@ bool BDB::bdb_create_fileset_record(JCR *jcr, FILESET_DBR *fsr) strftime(fsr->cCreateTime, sizeof(fsr->cCreateTime), "%Y-%m-%d %H:%M:%S", &tm); /* Must create it */ - Mmsg(cmd, "INSERT INTO FileSet (FileSet,MD5,CreateTime) " -"VALUES ('%s','%s','%s')", esc_fs, esc_md5, fsr->cCreateTime); + Mmsg(cmd, "INSERT INTO FileSet (FileSet,MD5,CreateTime,Content) " + "VALUES ('%s','%s','%s','%s')", esc_fs, esc_md5, fsr->cCreateTime, esc_content); if ((fsr->FileSetId = sql_insert_autokey_record(cmd, NT_("FileSet"))) == 0) { Mmsg2(&errmsg, _("Create DB FileSet record %s failed. ERR=%s\n"), diff --git a/bacula/src/cats/sql_get.c b/bacula/src/cats/sql_get.c index c7632431f..d2c4caaa9 100644 --- a/bacula/src/cats/sql_get.c +++ b/bacula/src/cats/sql_get.c @@ -1087,13 +1087,13 @@ int BDB::bdb_get_fileset_record(JCR *jcr, FILESET_DBR *fsr) bdb_lock(); if (fsr->FileSetId != 0) { /* find by id */ Mmsg(cmd, - "SELECT FileSetId,FileSet,MD5,CreateTime FROM FileSet " + "SELECT FileSetId,FileSet,MD5,CreateTime,Content FROM FileSet " "WHERE FileSetId=%s", edit_int64(fsr->FileSetId, ed1)); } else { /* find by name */ bdb_escape_string(jcr, esc, fsr->FileSet, strlen(fsr->FileSet)); Mmsg(cmd, - "SELECT FileSetId,FileSet,MD5,CreateTime FROM FileSet " + "SELECT FileSetId,FileSet,MD5,CreateTime,Content FROM FileSet " "WHERE FileSet='%s' ORDER BY CreateTime DESC LIMIT 1", esc); } @@ -1111,6 +1111,7 @@ int BDB::bdb_get_fileset_record(JCR *jcr, FILESET_DBR *fsr) bstrncpy(fsr->FileSet, row[1]!=NULL?row[1]:"", sizeof(fsr->FileSet)); bstrncpy(fsr->MD5, row[2]!=NULL?row[2]:"", sizeof(fsr->MD5)); bstrncpy(fsr->cCreateTime, row[3]!=NULL?row[3]:"", sizeof(fsr->cCreateTime)); + bstrncpy(fsr->Content, row[4]!=NULL?row[4]:"", sizeof(fsr->Content)); stat = fsr->FileSetId; } sql_free_result(); diff --git a/bacula/src/cats/update_mysql_tables.in b/bacula/src/cats/update_mysql_tables.in index 0ab9f7d4c..9c085b31f 100644 --- a/bacula/src/cats/update_mysql_tables.in +++ b/bacula/src/cats/update_mysql_tables.in @@ -718,6 +718,9 @@ ALTER TABLE Object MODIFY COLUMN ObjectCategory BLOB; MODIFY COLUMN ObjectUUID BLOB; +ALTER TABLE FileSet + ADD COLUMN Content BLOB DEFAULT ''; + ALTER TABLE Job ADD COLUMN RealStartTime DATETIME, ADD COLUMN isVirtualFull TINYINT default 0, diff --git a/bacula/src/cats/update_postgresql_tables.in b/bacula/src/cats/update_postgresql_tables.in index 320fbf5ec..10b880a7a 100644 --- a/bacula/src/cats/update_postgresql_tables.in +++ b/bacula/src/cats/update_postgresql_tables.in @@ -762,6 +762,8 @@ ALTER TABLE Media ADD COLUMN Protected smallint default 0; ALTER TABLE Media ADD COLUMN UseProtect smallint default 0; ALTER TABLE Media ADD COLUMN VolEncrypted smallint default 0; +ALTER TABLE FileSet ADD COLUMN Content text default ''; + INSERT INTO Events (EventsCode, EventsType, EventsTime, EventsDaemon, EventsSource, EventsRef, EventsText) VALUES ('DU0001', 'catalog_update', NOW(), '*SHELL*', 'update_bacula_tables', 'pid$$', 'Catalog schema was updated to 1026'); UPDATE Version SET VersionId=1026; diff --git a/bacula/src/dird/dird.c b/bacula/src/dird/dird.c index 6cd5318d2..79543fe68 100644 --- a/bacula/src/dird/dird.c +++ b/bacula/src/dird/dird.c @@ -1757,6 +1757,37 @@ static bool check_catalog(cat_op mode) counter->CurrentValue = counter->MinValue; /* default value */ } } + /* Loop over all fileset, defining/updating them in each database */ + FILESET *fs; + foreach_res(fs, R_FILESET) { + FILESET_DBR fsr; + + bmemset(&fsr, 0, sizeof(FILESET_DBR)); + bstrncpy(fsr.FileSet, fs->hdr.name, sizeof(fsr.FileSet)); + if (fs->have_MD5) { + struct MD5Context md5c; + unsigned char digest[MD5HashSize]; + memcpy(&md5c, &fs->md5c, sizeof(md5c)); + MD5Final(digest, &md5c); + /* + * Keep the flag (last arg) set to false otherwise old FileSets will + * get new MD5 sums and the user will get Full backups on everything + */ + bin_to_base64(fsr.MD5, sizeof(fsr.MD5), (char *)digest, MD5HashSize, false); + bstrncpy(fs->MD5, fsr.MD5, sizeof(fs->MD5)); + } + /* Previously, the content of the fileset was not generated, so, if + * we have a fileset like that, we can update the record + */ + if (!db_get_fileset_record(NULL, db, &fsr) || fsr.Content[0] == 0) { + fileset_get_content(&fsr, fs); + if (!db_create_fileset_record(NULL, db, &fsr)) { + Jmsg(NULL, M_FATAL, 0, + _("Could not create FileSet \"%s\" record. ERR=%s\n"), + fs->hdr.name, configfile); + } + } + } /* cleanup old job records */ if (mode == UPDATE_AND_FIX) { alist events(100, not_owned_by_alist); diff --git a/bacula/src/dird/job.c b/bacula/src/dird/job.c index a880717fa..d0b408054 100644 --- a/bacula/src/dird/job.c +++ b/bacula/src/dird/job.c @@ -1389,13 +1389,96 @@ bool get_or_create_client_record(JCR *jcr) return true; } +struct name_item +{ + rblink link; + char val[1]; +}; + +static int my_strcmp(void *a, void *b) +{ + struct name_item *a1 = (struct name_item*)a; + struct name_item *b1 = (struct name_item*)b; + return strcmp(a1->val, b1->val); +} + +/* Get the content overview of a FileSet */ +void fileset_get_content(FILESET_DBR *fdbr, FILESET *fileset) +{ + POOL_MEM tmp; + struct name_item *el = NULL; + bool files = false, first=true; + rblist c(el, &el->link); + regex_t r1; + regmatch_t pmatch[6]; + /* We are looking for the plugin name, so basically Plugin = "mysql: options" + * Some plugins like Exchange have the interesting part inside the second part + */ + regcomp(&r1, "^ *([a-zA-Z0-9-]+) *(: */@([A-Za-z]+))?", REG_EXTENDED); + for (int i=0; inum_includes; i++) { + INCEXE *ie = fileset->include_items[i]; + + if (ie->name_list.size() > 0) { + files = true; + } + for (int j=0; j < ie->plugin_list.size(); j++) { + char *item = (char *)ie->plugin_list.get(j); + if (regexec(&r1, item, 5, pmatch, 0) == 0 && + pmatch[1].rm_so >= 0 && + pmatch[1].rm_eo > 0) + { + int l = pmatch[1].rm_eo - pmatch[1].rm_so + 1; + tmp.check_size(l+1); + bstrncpy(tmp.c_str(), + item + pmatch[1].rm_so, + l); + + /* Special case for VSS plugins, the exact module is found in the + * second part + */ + if (strcasecmp(tmp.c_str(), "vss") == 0 && + pmatch[3].rm_so >= 0 && + pmatch[3].rm_eo > 0) + { + // vss - SYSTEMSTATE \0 + l = l + 1 + pmatch[3].rm_eo - pmatch[3].rm_so; + tmp.check_size(l+1); + bstrncat(tmp.c_str(), "-", l); + bstrncat(tmp.c_str(), item + pmatch[3].rm_so, l); + } + + struct name_item *el2; + el = (struct name_item *) malloc(sizeof(struct name_item) + l + 1); + bstrncpy(el->val, tmp.c_str(), l); + el2 = (struct name_item *)c.insert(el, my_strcmp); + if (el2 != el) { + free(el); // not inserted + } + } + } + } + pm_strcpy(tmp, ""); + if (files) { + pm_strcpy(tmp, "files"); + first=false; + } + foreach_rblist(el, &c) { + if (!first) { + pm_strcat(tmp, ","); + } + pm_strcat(tmp, el->val); + first = false; + } + bstrncpy(fdbr->Content, tmp.c_str(), sizeof(fdbr->Content)); + regfree(&r1); +} + /* * Get or Create FileSet record */ bool get_or_create_fileset_record(JCR *jcr) { FILESET_DBR fsr; - bmemset(&fsr, 0, sizeof(FILESET_DBR)); bstrncpy(fsr.FileSet, jcr->fileset->hdr.name, sizeof(fsr.FileSet)); if (jcr->fileset->have_MD5) { @@ -1414,7 +1497,10 @@ bool get_or_create_fileset_record(JCR *jcr) Jmsg(jcr, M_WARNING, 0, _("FileSet MD5 digest not found.\n")); } if (!jcr->fileset->ignore_fs_changes || - !db_get_fileset_record(jcr, jcr->db, &fsr)) { + !db_get_fileset_record(jcr, jcr->db, &fsr)) + { + /* Now we create the fileset at reload/startup, it should not happen */ + fileset_get_content(&fsr, jcr->fileset); if (!db_create_fileset_record(jcr, jcr->db, &fsr)) { Jmsg(jcr, M_ERROR, 0, _("Could not create FileSet \"%s\" record. ERR=%s\n"), fsr.FileSet, db_strerror(jcr->db)); diff --git a/bacula/src/dird/protos.h b/bacula/src/dird/protos.h index 046686fec..4342653fe 100644 --- a/bacula/src/dird/protos.h +++ b/bacula/src/dird/protos.h @@ -128,6 +128,7 @@ extern void set_jcr_defaults(JCR *jcr, JOB *job); extern void create_unique_job_name(JCR *jcr, const char *base_name); extern void update_job_end_record(JCR *jcr); extern bool get_or_create_client_record(JCR *jcr); +extern void fileset_get_content(FILESET_DBR *fdbr, FILESET *fileset); extern bool get_or_create_fileset_record(JCR *jcr); extern DBId_t get_or_create_pool_record(JCR *jcr, char *pool_name); extern void apply_pool_overrides(JCR *jcr); diff --git a/bacula/updatedb/update_mysql_tables_1025_to_1026.in b/bacula/updatedb/update_mysql_tables_1025_to_1026.in index cba15eddc..995741368 100644 --- a/bacula/updatedb/update_mysql_tables_1025_to_1026.in +++ b/bacula/updatedb/update_mysql_tables_1025_to_1026.in @@ -90,6 +90,9 @@ ALTER TABLE Object MODIFY COLUMN ObjectCategory BLOB; MODIFY COLUMN ObjectUUID BLOB; +ALTER TABLE FileSet + ADD COLUMN Content BLOB DEFAULT ''; + ALTER TABLE Job ADD COLUMN RealStartTime DATETIME, ADD COLUMN isVirtualFull TINYINT default 0, diff --git a/bacula/updatedb/update_postgresql_tables_1025_to_1026.in b/bacula/updatedb/update_postgresql_tables_1025_to_1026.in index 4fb563b8f..5f9e27cb9 100644 --- a/bacula/updatedb/update_postgresql_tables_1025_to_1026.in +++ b/bacula/updatedb/update_postgresql_tables_1025_to_1026.in @@ -69,6 +69,8 @@ ALTER TABLE Media ADD COLUMN Protected smallint default 0; ALTER TABLE Media ADD COLUMN UseProtect smallint default 0; ALTER TABLE Media ADD COLUMN VolEncrypted smallint default 0; +ALTER TABLE FileSet ADD COLUMN Content text default ''; + INSERT INTO Events (EventsCode, EventsType, EventsTime, EventsDaemon, EventsSource, EventsRef, EventsText) VALUES ('DU0001', 'catalog_update', NOW(), '*SHELL*', 'update_bacula_tables', 'pid$$', 'Catalog schema was updated to 1026'); UPDATE Version SET VersionId=1026;