#define FT_INVALIDDT 20 /* Drive type not allowed for */
#define FT_REPARSE 21 /* Win NTFS reparse point */
#define FT_PLUGIN 22 /* Plugin generated filename */
+#define FT_DELETED 23 /* Deleted file entry */
/* Definitions for upper part of type word (see above). */
#define AR_DATA_STREAM (1<<16) /* Data stream id present */
int db_get_client_record(JCR *jcr, B_DB *mdb, CLIENT_DBR *cdbr);
int db_get_counter_record(JCR *jcr, B_DB *mdb, COUNTER_DBR *cr);
bool db_get_query_dbids(JCR *jcr, B_DB *mdb, POOL_MEM &query, dbid_list &ids);
+bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids, DB_RESULT_HANDLER *result_handler, void *ctx);
+bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb, JOB_DBR *jr, POOLMEM *jobids);
+int db_get_int_handler(void *ctx, int num_fields, char **row);
/* sql_list.c */
return ok;
}
-
-
/* Get Media Record
*
* Returns: false: on failure
return ok;
}
+/*
+ * Find the last "accurate" backup state (that can take deleted files in account)
+ * 1) Get all files with jobid in list (F subquery)
+ * 2) Take only the last version of each file (Temp subquery) => accurate list is ok
+ * 3) Join the result to file table to get fileindex, jobid and lstat information
+ *
+ * TODO: On postgresql, this is done with
+SELECT DISTINCT ON (PathId, FilenameId) FileIndex, Path, Name, LStat
+ FROM File JOIN Filename USING (FilenameId) JOIN Path USING (PathId) WHERE JobId IN (40341)
+ ORDER BY PathId, FilenameId, JobId DESC
+ */
+bool db_get_file_list(JCR *jcr, B_DB *mdb, char *jobids,
+ DB_RESULT_HANDLER *result_handler, void *ctx)
+{
+ if (!*jobids) {
+ db_lock(mdb);
+ Mmsg(mdb->errmsg, _("ERR=JobIds are empty\n"));
+ db_unlock(mdb);
+ return false;
+ }
+
+ POOL_MEM buf (PM_MESSAGE);
+
+ Mmsg(buf,
+ "SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId, File.LStat "
+ "FROM ( "
+ "SELECT max(FileId) as FileId, PathId, FilenameId "
+ "FROM (SELECT FileId, PathId, FilenameId FROM File WHERE JobId IN (%s)) AS F "
+ "GROUP BY PathId, FilenameId "
+ ") AS Temp "
+ "JOIN Filename ON (Filename.FilenameId = Temp.FilenameId) "
+ "JOIN Path ON (Path.PathId = Temp.PathId) "
+ "JOIN File ON (File.FileId = Temp.FileId) "
+ "WHERE File.FileIndex > 0 ",
+ jobids);
+
+ return db_sql_query(mdb, buf.c_str(), result_handler, ctx);
+}
+
+
+/* Full : do nothing
+ * Differential : get the last full id
+ * Incremental : get the last full + last diff + last incr(s) ids
+ *
+ * TODO: look and merge from ua_restore.c
+ */
+bool db_accurate_get_jobids(JCR *jcr, B_DB *mdb,
+ JOB_DBR *jr, POOLMEM *jobids)
+{
+ char clientid[50], jobid[50], filesetid[50];
+ char date[MAX_TIME_LENGTH];
+
+ POOL_MEM query (PM_FNAME);
+ bstrutime(date, sizeof(date), time(NULL) + 1);
+ jobids[0]='\0';
+
+ /* First, find the last good Full backup for this job/client/fileset */
+ Mmsg(query,
+"CREATE TEMPORARY TABLE btemp3%s AS "
+ "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
+ "FROM Job JOIN FileSet USING (FileSetId) "
+ "WHERE ClientId = %s "
+ "AND Level='F' AND JobStatus='T' AND Type='B' "
+ "AND StartTime<'%s' "
+ "AND FileSet.FileSet=(SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
+ "ORDER BY Job.JobTDate DESC LIMIT 1",
+ edit_uint64(jcr->JobId, jobid),
+ edit_uint64(jr->ClientId, clientid),
+ date,
+ edit_uint64(jr->FileSetId, filesetid));
+
+ if (!db_sql_query(mdb, query.c_str(), NULL, NULL)) {
+ return false;
+ }
+
+ if (jr->JobLevel == L_INCREMENTAL) {
+
+ /* Now, find the last differential backup after the last full */
+ Mmsg(query,
+"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
+ "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
+ "FROM Job JOIN FileSet USING (FileSetId) "
+ "WHERE ClientId = %s "
+ "AND Level='D' AND JobStatus='T' AND Type='B' "
+ "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
+ "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
+ "ORDER BY Job.JobTDate DESC LIMIT 1 ",
+ jobid,
+ clientid,
+ jobid,
+ filesetid);
+
+ db_sql_query(mdb, query.c_str(), NULL, NULL);
+
+ /* We just have to take all incremental after the last Full/Diff */
+ Mmsg(query,
+"INSERT INTO btemp3%s (JobId, StartTime, EndTime, JobTDate, PurgedFiles) "
+ "SELECT JobId, StartTime, EndTime, JobTDate, PurgedFiles "
+ "FROM Job JOIN FileSet USING (FileSetId) "
+ "WHERE ClientId = %s "
+ "AND Level='I' AND JobStatus='T' AND Type='B' "
+ "AND StartTime > (SELECT EndTime FROM btemp3%s ORDER BY EndTime DESC LIMIT 1) "
+ "AND FileSet.FileSet= (SELECT FileSet FROM FileSet WHERE FileSetId = %s) "
+ "ORDER BY Job.JobTDate DESC ",
+ jobid,
+ clientid,
+ jobid,
+ filesetid);
+ db_sql_query(mdb, query.c_str(), NULL, NULL);
+ }
+
+ /* build a jobid list ie: 1,2,3,4 */
+ Mmsg(query, "SELECT JobId FROM btemp3%s", jobid);
+ db_sql_query(mdb, query.c_str(), db_get_int_handler, jobids);
+ Dmsg1(1, "db_accurate_get_jobids=%s\n", jobids);
+
+ Mmsg(query, "DROP TABLE btemp3%s", jobid);
+ db_sql_query(mdb, query.c_str(), NULL, NULL);
+
+ return true;
+}
+
+/*
+ * Use to build a string of int list from a query. "10,20,30"
+ */
+int db_get_int_handler(void *ctx, int num_fields, char **row)
+{
+ POOLMEM *ret = (POOLMEM *)ctx;
+ if (num_fields == 1) {
+ if (ret[0]) {
+ pm_strcat(ret, ",");
+ }
+ pm_strcat(ret, row[0]);
+ }
+ return 0;
+}
#endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_SQLITE || HAVE_POSTGRESQL || HAVE_DBI */
return true;
}
+/*
+ * Foreach files in currrent list, send "/path/fname\0LStat" to FD
+ */
+static int accurate_list_handler(void *ctx, int num_fields, char **row)
+{
+ JCR *jcr = (JCR *)ctx;
+
+ if (job_canceled(jcr)) {
+ return 1;
+ }
+
+ if (row[2] > 0) { /* discard when file_index == 0 */
+ jcr->file_bsock->fsend("%s%s%c%s", row[0], row[1], 0, row[4]);
+ }
+ return 0;
+}
+
+/*
+ * Send current file list to FD
+ * DIR -> FD : accurate files=xxxx
+ * DIR -> FD : /path/to/file\0Lstat
+ * DIR -> FD : /path/to/dir/\0Lstat
+ * ...
+ * DIR -> FD : EOD
+ */
+bool send_accurate_current_files(JCR *jcr)
+{
+ POOL_MEM buf;
+
+ if (jcr->accurate==false || job_canceled(jcr) || jcr->JobLevel==L_FULL) {
+ return true;
+ }
+ POOLMEM *jobids = get_pool_memory(PM_FNAME);
+ db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
+
+ if (*jobids == 0) {
+ free_pool_memory(jobids);
+ Jmsg(jcr, M_FATAL, 0, _("Cannot find previous jobids.\n"));
+ return false;
+ }
+ Jmsg(jcr, M_INFO, 0, _("Sending Accurate information.\n"));
+
+ /* to be able to allocate the right size for htable */
+ POOLMEM *nb = get_pool_memory(PM_FNAME);
+ Mmsg(buf, "SELECT sum(JobFiles) FROM Job WHERE JobId IN (%s)",jobids);
+ db_sql_query(jcr->db, buf.c_str(), db_get_int_handler, nb);
+ jcr->file_bsock->fsend("accurate files=%s\n", nb);
+
+ db_get_file_list(jcr, jcr->db, jobids, accurate_list_handler, (void *)jcr);
+
+ free_pool_memory(jobids);
+ free_pool_memory(nb);
+
+ jcr->file_bsock->signal(BNET_EOD);
+ /* TODO: use response() ? */
+
+ return true;
+}
+
/*
* Do a backup of the specified FileSet
*
Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
}
+ /*
+ * If backup is in accurate mode, we send the list of
+ * all files to FD.
+ */
+ if (!send_accurate_current_files(jcr)) {
+ goto bail_out;
+ }
+
/* Send backup command */
fd->fsend(backupcmd);
if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) {
" Software Compression: %s\n"
" VSS: %s\n"
" Encryption: %s\n"
+" Accurate: %s\n"
" Volume name(s): %s\n"
" Volume Session Id: %d\n"
" Volume Session Time: %d\n"
edit_uint64_with_suffix(jcr->SDJobBytes, ec6),
kbps,
compress,
- jcr->VSS?"yes":"no",
- jcr->Encrypt?"yes":"no",
+ jcr->VSS?_("yes"):_("no"),
+ jcr->Encrypt?_("yes"):_("no"),
+ jcr->accurate?_("yes"):_("no"),
jcr->VolumeName,
jcr->VolSessionId,
jcr->VolSessionTime,
* Update File Attributes in the catalog with data
* sent by the Storage daemon. Note, we receive the whole
* attribute record, but we select out only the stat packet,
- * VolSessionId, VolSessionTime, FileIndex, and file name
- * to store in the catalog.
+ * VolSessionId, VolSessionTime, FileIndex, file type, and
+ * file name to store in the catalog.
*/
void catalog_update(JCR *jcr, BSOCK *bs)
{
uint32_t FileIndex;
uint32_t data_len;
char *p;
+ int filetype;
int len;
char *fname, *attr;
ATTR_DBR *ar = NULL;
p = jcr->attr - bs->msg + p; /* point p into jcr->attr */
skip_nonspaces(&p); /* skip FileIndex */
skip_spaces(&p);
+ filetype = str_to_int32(p); /* TODO: choose between unserialize and str_to_int32 */
skip_nonspaces(&p); /* skip FileType */
skip_spaces(&p);
fname = p;
Dmsg1(400, "dird<stored: attr=%s\n", attr);
ar->attr = attr;
ar->fname = fname;
- ar->FileIndex = FileIndex;
+ if (filetype == FT_DELETED) {
+ ar->FileIndex = 0; /* special value */
+ } else {
+ ar->FileIndex = FileIndex;
+ }
ar->Stream = Stream;
ar->link = NULL;
if (jcr->mig_jcr) {
static char filesetcmd[] = "fileset%s\n"; /* set full fileset */
static char jobcmd[] = "JobId=%s Job=%s SDid=%u SDtime=%u Authorization=%s\n";
/* Note, mtime_only is not used here -- implemented as file option */
-static char levelcmd[] = "level = %s%s mtime_only=%d\n";
+static char levelcmd[] = "level = %s%s%s mtime_only=%d\n";
static char runscript[] = "Run OnSuccess=%u OnFailure=%u AbortOnError=%u When=%u Command=%s\n";
static char runbeforenow[]= "RunBeforeNow\n";
char ed1[50];
stime = str_to_utime(jcr->stime);
- fd->fsend(levelcmd, NT_("since_utime "), edit_uint64(stime, ed1), 0);
+ fd->fsend(levelcmd, "", NT_("since_utime "), edit_uint64(stime, ed1), 0);
while (bget_dirmsg(fd) >= 0) { /* allow him to poll us to sync clocks */
Jmsg(jcr, M_INFO, 0, "%s\n", fd->msg);
}
}
-
/*
* Send level command to FD.
* Used for backup jobs and estimate command.
bool send_level_command(JCR *jcr)
{
BSOCK *fd = jcr->file_bsock;
+ const char *accurate=jcr->job->accurate?"accurate_":"";
+ const char *not_accurate="";
/*
* Send Level command to File daemon
*/
switch (jcr->JobLevel) {
case L_BASE:
- fd->fsend(levelcmd, "base", " ", 0);
+ fd->fsend(levelcmd, not_accurate, "base", " ", 0);
break;
/* L_NONE is the console, sending something off to the FD */
case L_NONE:
case L_FULL:
- fd->fsend(levelcmd, "full", " ", 0);
+ fd->fsend(levelcmd, not_accurate, "full", " ", 0);
break;
case L_DIFFERENTIAL:
- fd->fsend(levelcmd, "differential", " ", 0);
+ fd->fsend(levelcmd, accurate, "differential", " ", 0);
send_since_time(jcr);
break;
case L_INCREMENTAL:
- fd->fsend(levelcmd, "incremental", " ", 0);
+ fd->fsend(levelcmd, accurate, "incremental", " ", 0);
send_since_time(jcr);
break;
case L_SINCE:
static RES_ITEM options_items[] = {
{"compression", store_opts, {0}, 0, 0, 0},
{"signature", store_opts, {0}, 0, 0, 0},
+ {"accurate", store_opts, {0}, 0, 0, 0},
{"verify", store_opts, {0}, 0, 0, 0},
{"onefs", store_opts, {0}, 0, 0, 0},
{"recurse", store_opts, {0}, 0, 0, 0},
INC_KW_DIGEST,
INC_KW_ENCRYPTION,
INC_KW_VERIFY,
+ INC_KW_ACCURATE,
INC_KW_ONEFS,
INC_KW_RECURSE,
INC_KW_SPARSE,
{"signature", INC_KW_DIGEST},
{"encryption", INC_KW_ENCRYPTION},
{"verify", INC_KW_VERIFY},
+ {"accurate", INC_KW_ACCURATE},
{"onefs", INC_KW_ONEFS},
{"recurse", INC_KW_RECURSE},
{"sparse", INC_KW_SPARSE},
bstrncat(opts, lc->str, optlen);
bstrncat(opts, ":", optlen); /* terminate it */
Dmsg3(900, "Catopts=%s option=%s optlen=%d\n", opts, option,optlen);
+ } else if (keyword == INC_KW_ACCURATE) { /* special case */
+ /* ***FIXME**** ensure these are in permitted set */
+ bstrncat(opts, "C", optlen); /* indicate Accurate */
+ bstrncat(opts, lc->str, optlen);
+ bstrncat(opts, ":", optlen); /* terminate it */
+ Dmsg3(900, "Catopts=%s option=%s optlen=%d\n", opts, option,optlen);
} else if (keyword == INC_KW_STRIPPATH) { /* another special case */
if (!is_an_integer(lc->str)) {
scan_err1(lc, _("Expected a strip path positive integer, got:%s:"), lc->str);
* For display purposes, the same JobId, with different volumes may
* appear more than once, however, we only insert it once.
*/
- int items = 0;
p = rx->JobIds;
tree.FileEstimate = 0;
if (get_next_jobid_from_list(&p, &JobId) > 0) {
tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
}
}
- for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
- char ed1[50];
- if (JobId == last_JobId) {
- continue; /* eliminate duplicate JobIds */
- }
- last_JobId = JobId;
- ua->info_msg(_("\nBuilding directory tree for JobId %s ... "),
- edit_int64(JobId, ed1));
- items++;
- /*
- * Find files for this JobId and insert them in the tree
- */
- Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
- if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
- ua->error_msg("%s", db_strerror(ua->db));
- }
+ ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
+ rx->JobIds);
+
+ if (!db_get_file_list(ua->jcr, ua->db, rx->JobIds, insert_tree_handler, (void *)&tree)) {
+ ua->error_msg("%s", db_strerror(ua->db));
}
if (tree.FileCount == 0) {
ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
}
} else {
char ec1[50];
- if (items==1) {
- if (tree.all) {
- ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
- edit_uint64_with_commas(tree.FileCount, ec1));
- }
- else {
- ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
- edit_uint64_with_commas(tree.FileCount, ec1));
- }
- }
- else {
- if (tree.all) {
- ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
- items, edit_uint64_with_commas(tree.FileCount, ec1));
- }
- else {
- ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
- items, edit_uint64_with_commas(tree.FileCount, ec1));
- }
+ if (tree.all) {
+ ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
+ edit_uint64_with_commas(tree.FileCount, ec1));
+ } else {
+ ua->info_msg(_("\n%s files inserted into the tree.\n"),
+ edit_uint64_with_commas(tree.FileCount, ec1));
}
if (find_arg(ua, NT_("done")) < 0) {
#include "bacula.h"
#include "filed.h"
+#include "lib/htable.h"
/* Forward referenced functions */
int save_file(JCR *jcr, FF_PKT *ff_pkt, bool top_level);
static void crypto_session_end(JCR *jcr);
static bool crypto_session_send(JCR *jcr, BSOCK *sd);
+typedef struct CurFile {
+ hlink link;
+ char *fname;
+ char *lstat;
+ bool seen;
+} CurFile;
+
+#define accurate_mark_file_as_seen(elt) ((elt)->seen = 1)
+#define accurate_file_has_been_seen(elt) ((elt)->seen)
+
+/*
+ * This function is called for each file seen in fileset.
+ * We check in file_list hash if fname have been backuped
+ * the last time. After we can compare Lstat field.
+ *
+ */
+/* TODO: tweak verify code to use the same function ?? */
+bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt)
+{
+ char *p;
+ int stat=false;
+ struct stat statc; /* catalog stat */
+ char *Opts_Digest;
+ char *fname;
+ CurFile *elt;
+
+ int32_t LinkFIc;
+
+ if (*ff_pkt->VerifyOpts) { /* use mtime + ctime checks by default */
+ Opts_Digest = ff_pkt->VerifyOpts;
+ } else {
+ Opts_Digest = "cm";
+ }
+
+ if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
+ return true;
+ }
+
+ strip_path(ff_pkt);
+
+ if (S_ISDIR(ff_pkt->statp.st_mode)) {
+ fname = ff_pkt->link;
+ } else {
+ fname = ff_pkt->fname;
+ }
+
+ elt = (CurFile *) jcr->file_list->lookup(fname);
+
+ if (!elt) {
+ Dmsg1(500, "accurate %s = yes (not found)\n", fname);
+ stat=true;
+ goto bail_out;
+ }
+
+ if (accurate_file_has_been_seen(elt)) {
+ Dmsg1(500, "accurate %s = no (already seen)\n", fname);
+ stat=false;
+ goto bail_out;
+ }
+
+ decode_stat(elt->lstat, &statc, &LinkFIc); /* decode catalog stat */
+// *do_Digest = CRYPTO_DIGEST_NONE;
+
+ for (p=Opts_Digest; *p; p++) {
+ char ed1[30], ed2[30];
+ switch (*p) {
+ case 'i': /* compare INODEs */
+ if (statc.st_ino != ff_pkt->statp.st_ino) {
+ Jmsg(jcr, M_SAVED, 0, _("%s st_ino differ. Cat: %s File: %s\n"), fname,
+ edit_uint64((uint64_t)statc.st_ino, ed1),
+ edit_uint64((uint64_t)ff_pkt->statp.st_ino, ed2));
+ stat = true;
+ }
+ break;
+ case 'p': /* permissions bits */
+ if (statc.st_mode != ff_pkt->statp.st_mode) {
+ Jmsg(jcr, M_SAVED, 0, _("%s st_mode differ. Cat: %x File: %x\n"), fname,
+ (uint32_t)statc.st_mode, (uint32_t)ff_pkt->statp.st_mode);
+ stat = true;
+ }
+ break;
+// case 'n': /* number of links */
+// if (statc.st_nlink != ff_pkt->statp.st_nlink) {
+// Jmsg(jcr, M_SAVED, 0, _("%s st_nlink differ. Cat: %d File: %d\n"), fname,
+// (uint32_t)statc.st_nlink, (uint32_t)ff_pkt->statp.st_nlink);
+// stat = true;
+// }
+// break;
+ case 'u': /* user id */
+ if (statc.st_uid != ff_pkt->statp.st_uid) {
+ Jmsg(jcr, M_SAVED, 0, _("%s st_uid differ. Cat: %u File: %u\n"), fname,
+ (uint32_t)statc.st_uid, (uint32_t)ff_pkt->statp.st_uid);
+ stat = true;
+ }
+ break;
+ case 'g': /* group id */
+ if (statc.st_gid != ff_pkt->statp.st_gid) {
+ Jmsg(jcr, M_SAVED, 0, _("%s st_gid differ. Cat: %u File: %u\n"), fname,
+ (uint32_t)statc.st_gid, (uint32_t)ff_pkt->statp.st_gid);
+ stat = true;
+ }
+ break;
+ case 's': /* size */
+ if (statc.st_size != ff_pkt->statp.st_size) {
+ Jmsg(jcr, M_SAVED, 0, _("%s st_size differ. Cat: %s File: %s\n"), fname,
+ edit_uint64((uint64_t)statc.st_size, ed1),
+ edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
+ stat = true;
+ }
+ break;
+// case 'a': /* access time */
+// if (statc.st_atime != ff_pkt->statp.st_atime) {
+// Jmsg(jcr, M_SAVED, 0, _("%s st_atime differs\n"), fname);
+// stat = true;
+// }
+// break;
+ case 'm':
+ if (statc.st_mtime != ff_pkt->statp.st_mtime) {
+ Jmsg(jcr, M_SAVED, 0, _("%s st_mtime differs\n"), fname);
+ stat = true;
+ }
+ break;
+ case 'c': /* ctime */
+ if (statc.st_ctime != ff_pkt->statp.st_ctime) {
+ Jmsg(jcr, M_SAVED, 0, _("%s st_ctime differs\n"), fname);
+ stat = true;
+ }
+ break;
+ case 'd': /* file size decrease */
+ if (statc.st_size > ff_pkt->statp.st_size) {
+ Jmsg(jcr, M_SAVED, 0, _("%s st_size decrease. Cat: %s File: %s\n"), fname,
+ edit_uint64((uint64_t)statc.st_size, ed1),
+ edit_uint64((uint64_t)ff_pkt->statp.st_size, ed2));
+ stat = true;
+ }
+ break;
+ case '5': /* compare MD5 */
+ Dmsg1(500, "set Do_MD5 for %s\n", ff_pkt->fname);
+// *do_Digest = CRYPTO_DIGEST_MD5;
+ break;
+ case '1': /* compare SHA1 */
+// *do_Digest = CRYPTO_DIGEST_SHA1;
+ break;
+ case ':':
+ case 'V':
+ default:
+ break;
+ }
+ }
+ accurate_mark_file_as_seen(elt);
+ Dmsg2(500, "accurate %s = %i\n", fname, stat);
+
+bail_out:
+ unstrip_path(ff_pkt);
+ return stat;
+}
+
+/*
+ * This function doesn't work very well with smartalloc
+ * TODO: use bigbuffer from htable
+ */
+int accurate_cmd(JCR *jcr)
+{
+ BSOCK *dir = jcr->dir_bsock;
+ int len;
+ uint64_t nb;
+ CurFile *elt=NULL;
+
+ if (jcr->accurate==false || job_canceled(jcr) || jcr->JobLevel==L_FULL) {
+ return true;
+ }
+
+ if (sscanf(dir->msg, "accurate files=%ld", &nb) != 1) {
+ dir->fsend(_("2991 Bad accurate command\n"));
+ return false;
+ }
+
+ jcr->file_list = (htable *)malloc(sizeof(htable));
+ jcr->file_list->init(elt, &elt->link, nb);
+
+ /*
+ * buffer = sizeof(CurFile) + dirmsg
+ * dirmsg = fname + lstat
+ */
+ /* get current files */
+ while (dir->recv() >= 0) {
+ len = strlen(dir->msg);
+ if ((len+1) < dir->msglen) {
+// elt = (CurFile *)malloc(sizeof(CurFile));
+// elt->fname = (char *) malloc(dir->msglen+1);
+
+ /* we store CurFile, fname and lstat in the same chunk */
+ elt = (CurFile *)malloc(sizeof(CurFile)+dir->msglen+1);
+ elt->fname = (char *) elt+sizeof(CurFile);
+ memcpy(elt->fname, dir->msg, dir->msglen);
+ elt->fname[dir->msglen]='\0';
+ elt->lstat = elt->fname + len + 1;
+ elt->seen=0;
+ jcr->file_list->insert(elt->fname, elt);
+ Dmsg2(500, "add fname=%s lstat=%s\n", elt->fname, elt->lstat);
+ }
+ }
+
+// jcr->file_list->stats();
+ /* TODO: send a EOM ?
+ dir->fsend("2000 OK accurate\n");
+ */
+ return true;
+}
+
+bool accurate_send_deleted_list(JCR *jcr)
+{
+ CurFile *elt;
+ FF_PKT *ff_pkt;
+
+ int stream = STREAM_UNIX_ATTRIBUTES;
+
+ if (jcr->accurate == false || jcr->JobLevel == L_FULL) {
+ goto bail_out;
+ }
+
+ if (jcr->file_list == NULL) {
+ goto bail_out;
+ }
+
+ ff_pkt = init_find_files();
+ ff_pkt->type = FT_DELETED;
+
+ foreach_htable (elt, jcr->file_list) {
+ if (!accurate_file_has_been_seen(elt)) { /* already seen */
+ Dmsg3(500, "deleted fname=%s lstat=%s seen=%i\n", elt->fname, elt->lstat, elt->seen);
+ ff_pkt->fname = elt->fname;
+ decode_stat(elt->lstat, &ff_pkt->statp, &ff_pkt->LinkFI); /* decode catalog stat */
+ encode_and_send_attributes(jcr, ff_pkt, stream);
+ }
+// Free(elt->fname);
+ }
+ term_find_files(ff_pkt);
+bail_out:
+ /* TODO: clean htable when this function is not reached ? */
+ if (jcr->file_list) {
+ jcr->file_list->destroy();
+ free(jcr->file_list);
+ jcr->file_list = NULL;
+ }
+ return true;
+}
+
/*
* Find all the requested files and send them
* to the Storage daemon.
*/
jcr->compress_buf_size = jcr->buf_size + ((jcr->buf_size+999) / 1000) + 30;
jcr->compress_buf = get_memory(jcr->compress_buf_size);
-
+
#ifdef HAVE_LIBZ
z_stream *pZlibStream = (z_stream*)malloc(sizeof(z_stream));
if (pZlibStream) {
return false;
}
- Dmsg1(300, "set_find_options ff=%p\n", jcr->ff);
set_find_options((FF_PKT *)jcr->ff, jcr->incremental, jcr->mtime);
- Dmsg0(300, "start find files\n");
+ /* in accurate mode, we overwrite the find_one check function */
+ if (jcr->accurate) {
+ set_find_changed_function((FF_PKT *)jcr->ff, accurate_check_file);
+ }
+
start_heartbeat_monitor(jcr);
jcr->acl_text = get_pool_memory(PM_MESSAGE);
set_jcr_job_status(jcr, JS_ErrorTerminated);
}
+ accurate_send_deleted_list(jcr); /* send deleted list to SD */
+
free_pool_memory(jcr->acl_text);
stop_heartbeat_monitor(jcr);
* For a directory, link is the same as fname, but with trailing
* slash. For a linked file, link is the link.
*/
- strip_path(ff_pkt);
+ if (ff_pkt->type != FT_DELETED) { /* already stripped */
+ strip_path(ff_pkt);
+ }
if (ff_pkt->type == FT_LNK || ff_pkt->type == FT_LNKSAVED) {
Dmsg2(300, "Link %s to %s\n", ff_pkt->fname, ff_pkt->link);
stat = sd->fsend("%ld %d %s%c%s%c%s%c%s%c", jcr->JobFiles,
stat = sd->fsend("%ld %d %s%c%s%c%c%s%c", jcr->JobFiles,
ff_pkt->type, ff_pkt->fname, 0, attribs, 0, 0, attribsEx, 0);
}
- unstrip_path(ff_pkt);
+ if (ff_pkt->type != FT_DELETED) {
+ unstrip_path(ff_pkt);
+ }
Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg);
if (!stat) {
/* Imported functions */
extern int status_cmd(JCR *jcr);
extern int qstatus_cmd(JCR *jcr);
+extern int accurate_cmd(JCR *jcr);
/* Forward referenced functions */
static int backup_cmd(JCR *jcr);
{"RunBeforeJob", runbefore_cmd, 0},
{"RunAfterJob", runafter_cmd, 0},
{"Run", runscript_cmd, 0},
+ {"accurate", accurate_cmd, 0},
{NULL, NULL} /* list terminator */
};
}
fo->VerifyOpts[j] = 0;
break;
+ case 'C': /* accurate options */
+ /* Copy Accurate Options */
+ for (j=0; *p && *p != ':'; p++) {
+ fo->AccurateOpts[j] = *p;
+ if (j < (int)sizeof(fo->AccurateOpts) - 1) {
+ j++;
+ }
+ }
+ fo->AccurateOpts[j] = 0;
+ break;
case 'P': /* strip path */
/* Get integer */
p++; /* skip P */
level = get_memory(dir->msglen+1);
Dmsg1(110, "level_cmd: %s", dir->msg);
+ if (strstr(dir->msg, "accurate")) {
+ jcr->accurate = true;
+ }
if (sscanf(dir->msg, "level = %s ", level) != 1) {
goto bail_out;
}
/* Full backup requested? */
} else if (strcmp(level, "full") == 0) {
jcr->JobLevel = L_FULL;
- } else if (strcmp(level, "differential") == 0) {
+ } else if (strstr(level, "differential")) {
jcr->JobLevel = L_DIFFERENTIAL;
free_memory(level);
return 1;
- } else if (strcmp(level, "incremental") == 0) {
+ } else if (strstr(level, "incremental")) {
jcr->JobLevel = L_INCREMENTAL;
free_memory(level);
- return 1;
+ return 1;
/*
* We get his UTC since time, then sync the clocks and correct it
* to agree with our clock.
bclose(&rctx.bfd);
}
+ /* TODO: manage deleted files */
+ if (rctx.type == FT_DELETED) { /* deleted file */
+ continue;
+ }
+
/*
* Unpack attributes and do sanity check them
*/
return CF_CREATED;
}
+ case FT_DELETED:
+ Qmsg2(jcr, M_INFO, 0, _("Original file %s have been deleted: type=%d\n"), attr->fname, attr->type);
+ break;
/* The following should not occur */
case FT_NOACCESS:
case FT_NOFOLLOW:
Dmsg0(100, "Leave set_find_options()\n");
}
+void
+set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff))
+{
+ Dmsg0(1, "Enter set_find_changed_function()\n");
+ ff->check_fct = check_fct;
+}
+
/*
* For VSS we need to know which windows drives
* are used, because we create a snapshot of all used
int GZIP_level; /* GZIP level */
int strip_path; /* strip path count */
char VerifyOpts[MAX_FOPTS]; /* verify options */
+ char AccurateOpts[MAX_FOPTS]; /* accurate mode options */
alist regex; /* regex string(s) */
alist regexdir; /* regex string(s) for directories */
alist regexfile; /* regex string(s) for files */
findFILESET *fileset;
int (*file_save)(JCR *, FF_PKT *, bool); /* User's callback */
int (*plugin_save)(JCR *, FF_PKT *, bool); /* User's callback */
+ bool (*check_fct)(JCR *, FF_PKT *); /* optionnal user fct to check file changes */
/* Values set by accept_file while processing Options */
uint32_t flags; /* backup options */
return false;
}
+/*
+ * In incremental/diffential or accurate backup, we
+ * say if the current file has changed.
+ */
+static bool check_changes(JCR *jcr, FF_PKT *ff_pkt)
+{
+ /* in special mode (like accurate backup), user can
+ * choose his comparison function.
+ */
+ if (ff_pkt->check_fct) {
+ return ff_pkt->check_fct(jcr, ff_pkt);
+ }
+
+ /* in normal modes (incr/diff), we use this default
+ * behaviour
+ */
+ if (ff_pkt->incremental &&
+ (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
+ ((ff_pkt->flags & FO_MTIMEONLY) ||
+ ff_pkt->statp.st_ctime < ff_pkt->save_time)))
+ {
+ return false;
+ }
+
+ return true;
+}
+
/*
* Find a single file.
* handle_file is the callback for handling the file.
}
ff_pkt->volhas_attrlist = volume_has_attrlist(fname);
}
-
/*
* If this is an Incremental backup, see if file was modified
* since our last "save_time", presumably the last Full save
* or Incremental.
*/
- if (ff_pkt->incremental && !S_ISDIR(ff_pkt->statp.st_mode)) {
- Dmsg1(300, "Non-directory incremental: %s\n", ff_pkt->fname);
- /* Not a directory */
- if (ff_pkt->statp.st_mtime < ff_pkt->save_time
- && ((ff_pkt->flags & FO_MTIMEONLY) ||
- ff_pkt->statp.st_ctime < ff_pkt->save_time)) {
- /* Incremental option, file not changed */
- ff_pkt->type = FT_NOCHG;
- return handle_file(jcr, ff_pkt, top_level);
- }
+ if ( ff_pkt->incremental
+ && !S_ISDIR(ff_pkt->statp.st_mode)
+ && !check_changes(jcr, ff_pkt))
+ {
+ Dmsg1(500, "Non-directory incremental: %s\n", ff_pkt->fname);
+ ff_pkt->type = FT_NOCHG;
+ return handle_file(jcr, ff_pkt, top_level);
}
#ifdef HAVE_DARWIN_OS
link[len] = 0;
ff_pkt->link = link;
- if (ff_pkt->incremental &&
- (ff_pkt->statp.st_mtime < ff_pkt->save_time &&
- ((ff_pkt->flags & FO_MTIMEONLY) ||
- ff_pkt->statp.st_ctime < ff_pkt->save_time))) {
+ if (ff_pkt->incremental && !check_changes(jcr, ff_pkt)) {
/* Incremental option, directory entry not changed */
ff_pkt->type = FT_DIRNOCHG;
} else {
/* From find.c */
FF_PKT *init_find_files();
void set_find_options(FF_PKT *ff, int incremental, time_t mtime);
+void set_find_changed_function(FF_PKT *ff, bool check_fct(JCR *jcr, FF_PKT *ff));
int find_files(JCR *jcr, FF_PKT *ff, int file_sub(JCR *, FF_PKT *ff_pkt, bool),
int plugin_sub(JCR *, FF_PKT *ff_pkt, bool));
int match_files(JCR *jcr, FF_PKT *ff, int sub(JCR *, FF_PKT *ff_pkt, bool));
/* Forward referenced structures */
class JCR;
+class htable;
struct FF_PKT;
struct B_DB;
struct ATTR_DBR;
CRYPTO_CTX crypto; /* Crypto ctx */
DIRRES* director; /* Director resource */
bool VSS; /* VSS used by FD */
+ htable *file_list; /* Previous file list (accurate mode) */
#endif /* FILE_DAEMON */
res.c rwlock.c scan.c serial.c sha1.c \
signal.c smartall.c rblist.c tls.c tree.c \
util.c var.c watchdog.c workq.c btimers.c \
- address_conf.c pythonlib.c breg.c
+ address_conf.c pythonlib.c breg.c htable.c
LIBOBJS = attr.o base64.o berrno.o bsys.o bget_msg.o \
res.o rwlock.o scan.o serial.o sha1.o \
signal.o smartall.o rblist.o tls.o tree.o \
util.o var.o watchdog.o workq.o btimers.o \
- address_conf.o pythonlib.o breg.o
+ address_conf.o pythonlib.o breg.o htable.o
EXTRAOBJS = @OBJLIST@
char *p, *f;
guid_list *guid;
+ if (attr->type == FT_DELETED) { /* TODO: change this to get last seen values */
+ bsnprintf(buf, sizeof(buf),
+ "---------- - - - - ---------- -------- %s\n", attr->ofname);
+ Dmsg1(20, "%s", buf);
+ Jmsg(jcr, M_RESTORED, 1, "%s", buf);
+ return;
+ }
+
if (!jcr->id_list) {
jcr->id_list = new_guid_list();
}
build_attr_output_fnames(jcr, attr);
+ if (attr->type == FT_DELETED) { /* TODO: choose the right fname/ofname */
+ Jmsg(jcr, M_INFO, 0, _("%s was deleted.\n"), attr->fname);
+ extract = false;
+ return true;
+ }
+
extract = false;
stat = create_file(jcr, attr, &bfd, REPLACE_ALWAYS);
switch (stat) {
ar.ClientId = mjcr->ClientId;
ar.JobId = mjcr->JobId;
ar.Stream = rec->Stream;
- ar.FileIndex = rec->FileIndex;
+ if (type == FT_DELETED) {
+ ar.FileIndex = 0;
+ } else {
+ ar.FileIndex = rec->FileIndex;
+ }
ar.attr = ap;
if (dcr->VolFirstIndex == 0) {
dcr->VolFirstIndex = rec->FileIndex;