Index: src/dird/fd_cmds.c =================================================================== --- src/dird/fd_cmds.c (révision 6443) +++ src/dird/fd_cmds.c (copie de travail) @@ -50,7 +50,7 @@ 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"; @@ -226,7 +226,7 @@ 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); } @@ -240,24 +240,25 @@ bool send_level_command(JCR *jcr) { BSOCK *fd = jcr->file_bsock; + const char *accurate=jcr->job->accurate?"accurate_":""; /* * Send Level command to File daemon */ switch (jcr->JobLevel) { case L_BASE: - fd->fsend(levelcmd, "base", " ", 0); + fd->fsend(levelcmd, "", "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, "", "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: Index: src/dird/backup.c =================================================================== --- src/dird/backup.c (révision 6443) +++ src/dird/backup.c (copie de travail) @@ -44,6 +44,7 @@ #include "bacula.h" #include "dird.h" #include "ua.h" +#include "findlib/find.h" /* Commands sent to File daemon */ static char backupcmd[] = "backup\n"; @@ -96,7 +97,85 @@ return true; } +static int accurate_list_handler(void *ctx, int num_fields, char **row) +{ + JCR *jcr = (JCR *)ctx; + + if (job_canceled(jcr)) { + return 1; + } + + if (row[0] > 0) { + jcr->file_bsock->fsend("%s%s%c%s", row[1], row[2], 0, row[3]); + } + return 0; +} + +bool db_accurate_get_jobids(JCR *jcr, POOLMEM *jobids) +{ + pm_strcpy(jobids, "13"); + return 1; +} + +bool send_accurate_current_files(JCR *jcr) +{ + char buf[MAXSTRING]; + char ed1[50], ed2[50]; + + 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, jobids); + + bsnprintf(buf, sizeof(buf), + "CREATE TEMPORARY TABLE btemp2%s AS ( " + "SELECT max(FileId) as FileId, PathId, FilenameId " + "FROM (SELECT FileId, PathId, FilenameId FROM File WHERE JobId IN (%s)) AS F " + "GROUP BY PathId, FilenameId ) ", + edit_uint64(jcr->JobId, ed1), + jobids); + db_sql_query(jcr->db, buf, NULL, NULL); + + // TODO: compter le nombre de rows + jcr->file_bsock->fsend("accurate files=%s\n", edit_uint64(5895, ed2)); /* TODO: change protocol to something like nb= */ + + bsnprintf(buf, sizeof(buf), + "SELECT File.FileIndex, Path.Path, Filename.Name, File.LStat " + "FROM btemp2%s JOIN Path USING (PathId) JOIN Filename USING (FilenameId) " + "JOIN File USING (FileId) " + "WHERE File.FileIndex > 0", + ed1, jobids); + db_sql_query(jcr->db, buf, accurate_list_handler, (void *)jcr); + + bsnprintf(buf, sizeof(buf), "DROP TABLE btemp2%s", ed1); + free_pool_memory(jobids); + /* + CREATE TEMPORARY TABLE btemp2 AS ( + SELECT max(FileId) as FileId, PathId, FilenameId + FROM (SELECT FileId, PathId, FilenameId FROM File WHERE JobId IN (39867,40341)) AS F + GROUP BY PathId, FilenameId ) + + SELECT File.FileIndex, Path.Path, Filename.Name, File.LStat + FROM btemp2 JOIN Path USING (PathId) JOIN Filename USING (FilenameId) + JOIN File USING (FileId) + WHERE File.FileIndex > 0 + + DROP TABLE btemp2 +*/ +/* +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 +*/ + + jcr->file_bsock->signal(BNET_EOD); + return true; +} + +/* * Do a backup of the specified FileSet * * Returns: false on failure @@ -225,6 +304,14 @@ Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db)); } + /* + * If backup is in accurate mode, FD will send the list of + * all files. + */ + if (!send_accurate_current_files(jcr)) { + goto bail_out; + } + /* Send backup command */ fd->fsend(backupcmd); if (!response(jcr, fd, OKbackup, "backup", DISPLAY_ERROR)) { @@ -234,6 +321,7 @@ /* Pickup Job termination data */ stat = wait_for_job_termination(jcr); db_write_batch_file_records(jcr); /* used by bulk batch file insert */ + if (stat == JS_Terminated) { backup_cleanup(jcr, stat); return true; Index: src/dird/inc_conf.c =================================================================== --- src/dird/inc_conf.c (révision 6443) +++ src/dird/inc_conf.c (copie de travail) @@ -94,6 +94,7 @@ * Items that are valid in an Options resource */ static RES_ITEM options_items[] = { + {"accurate", store_opts, {0}, 0, 0, 0}, {"compression", store_opts, {0}, 0, 0, 0}, {"signature", store_opts, {0}, 0, 0, 0}, {"verify", store_opts, {0}, 0, 0, 0}, @@ -153,7 +154,8 @@ INC_KW_NOATIME, INC_KW_ENHANCEDWILD, INC_KW_CHKCHANGES, - INC_KW_STRIPPATH + INC_KW_STRIPPATH, + INC_KW_ACCURATE }; /* @@ -163,6 +165,7 @@ * options given above. */ static struct s_kw FS_option_kw[] = { + {"accurate", INC_KW_ACCURATE}, {"compression", INC_KW_COMPRESSION}, {"signature", INC_KW_DIGEST}, {"encryption", INC_KW_ENCRYPTION}, @@ -251,6 +254,8 @@ {"no", INC_KW_ENHANCEDWILD, "0"}, {"yes", INC_KW_CHKCHANGES, "c"}, {"no", INC_KW_CHKCHANGES, "0"}, + {"yes", INC_KW_ACCURATE, "C"}, + {"no", INC_KW_ACCURATE, "0"}, {NULL, 0, 0} }; Index: src/filed/backup.c =================================================================== --- src/filed/backup.c (révision 6443) +++ src/filed/backup.c (copie de travail) @@ -48,8 +48,213 @@ static bool crypto_session_start(JCR *jcr); static void crypto_session_end(JCR *jcr); static bool crypto_session_send(JCR *jcr, BSOCK *sd); +static bool encode_and_send_deleted_file(JCR *jcr, char *fname); +#include "lib/htable.c" +typedef struct CurFile { + char *fname; + char *lstat; + hlink link; +} CurFile; + /* + * This function is called for each file seen in fileset. + * + * If the file is skipped (attr=NULL), we will check if this + * file have been backuped before. If not, we decide to backup it. + * + * If the file have attr=AAA, we check attr with lstat + * + */ +/* TODO: tweak verify code to use the same function */ +bool accurate_check_file(JCR *jcr, FF_PKT *ff_pkt, bool saved) +{ + char *p; + int stat=false; + struct stat statc; /* catalog stat */ + char *Opts_Digest = ff_pkt->VerifyOpts; + char *fname = ff_pkt->fname; + CurFile *elt; + + int32_t LinkFIc; + + if (jcr->accurate == false || jcr->JobLevel == L_FULL) { + return true; + } + + if (ff_pkt->type == FT_DIREND || ff_pkt->type == FT_REPARSE || ff_pkt->type == FT_DIRNOCHG) { + fname = ff_pkt->link; + } + + // TODO: check for /path/ and /path/file + elt = (CurFile *) jcr->file_list->lookup(fname); + + if (!elt) { + // TODO: we must backup it ! + Dmsg1(1, "accurate %s = yes (not found)\n", fname); + return true; + } + + if (saved || *elt->lstat == '\0') { + Dmsg1(1, "accurate %s = no (already seen)\n", fname); + *elt->lstat = '\0'; + return false; + } + + 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_INFO, 0, _(" st_ino differ. Cat: %s File: %s\n"), + 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_INFO, 0, _(" st_mode differ. Cat: %x File: %x\n"), + (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_INFO, 0, _(" st_nlink differ. Cat: %d File: %d\n"), + (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_INFO, 0, _(" st_uid differ. Cat: %u File: %u\n"), + (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_INFO, 0, _(" st_gid differ. Cat: %u File: %u\n"), + (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_INFO, 0, _(" st_size differ. Cat: %s File: %s\n"), + 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_INFO, 0, _(" st_atime differs\n")); + stat = true; + } + break; + case 'm': + if (statc.st_mtime != ff_pkt->statp.st_mtime) { + Jmsg(jcr, M_INFO, 0, _(" st_mtime differs\n")); + stat = true; + } + break; + case 'c': /* ctime */ + if (statc.st_ctime != ff_pkt->statp.st_ctime) { + Jmsg(jcr, M_INFO, 0, _(" st_ctime differs\n")); + stat = true; + } + break; + case 'd': /* file size decrease */ + if (statc.st_size > ff_pkt->statp.st_size) { + Jmsg(jcr, M_INFO, 0, _(" st_size decrease. Cat: %s File: %s\n"), + 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; + } + } + *elt->lstat = '\0'; /* mark it as deleted */ + Dmsg2(1, "accurate %s = %i\n", fname, stat); + return stat; +} + +int accurate_get_current_file_list_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); + + /* 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); + memcpy(elt->fname, dir->msg, dir->msglen); + elt->fname[dir->msglen]='\0'; + elt->lstat = elt->fname + len + 1; + Dmsg2(100, "hash[%s]=%s\n", elt->fname, elt->lstat); + jcr->file_list->insert(elt->fname, elt); + } + } + + jcr->file_list->stats(); +// dir->fsend("2000 OK accurate\n"); + + return true; +} + +bool accurate_send_deleted_list(JCR *jcr) +{ + if (jcr->accurate == false || jcr->JobLevel == L_FULL) { + return true; + } + + CurFile *elt; + foreach_htable (elt, jcr->file_list) { + Dmsg3(100, "elt = 0x%x fname=%s lstat=%s\n", elt, elt->fname, elt->lstat); + if (*elt->lstat != '\0') { + encode_and_send_deleted_file(jcr, elt->fname); + } + free(elt->fname); + } + jcr->file_list->destroy(); /* TODO: clean htable when this function is not reached ? */ + free(jcr->file_list); + jcr->file_list = NULL; + return true; +} + +/* * Find all the requested files and send them * to the Storage daemon. * @@ -66,7 +271,6 @@ BSOCK *sd; bool ok = true; // TODO landonf: Allow user to specify encryption algorithm - sd = jcr->store_bsock; set_jcr_job_status(jcr, JS_Running); @@ -134,7 +338,10 @@ ok = false; /* error */ set_jcr_job_status(jcr, JS_ErrorTerminated); } + Dmsg1(1, "jcr->accurate == %i\n", jcr->accurate); + accurate_send_deleted_list(jcr); /* send deleted list to SD */ + free_pool_memory(jcr->acl_text); stop_heartbeat_monitor(jcr); @@ -354,9 +561,19 @@ } case FT_DIRNOCHG: case FT_NOCHG: + /* TODO: in accurate mode, we have to change NOCHG attribute to FT_REG... */ +// if (!accurate_check_file(jcr, ff_pkt, false)) { +// Jmsg(jcr, M_SKIPPED, 1, _(" Unchanged file skipped: %s\n"), ff_pkt->fname); +// return 1; +// } Jmsg(jcr, M_SKIPPED, 1, _(" Unchanged file skipped: %s\n"), ff_pkt->fname); return 1; case FT_ISARCH: + /* TODO: in accurate mode, we have to change NOCHG attribute to FT_REG... */ +// if (!accurate_check_file(jcr, ff_pkt, false)) { +// Jmsg(jcr, M_NOTSAVED, 0, _(" Archive file not saved: %s\n"), ff_pkt->fname); +// return 1; +// } Jmsg(jcr, M_NOTSAVED, 0, _(" Archive file not saved: %s\n"), ff_pkt->fname); return 1; case FT_NOOPEN: { @@ -1118,6 +1335,9 @@ } unstrip_path(ff_pkt); + /* list backuped files */ + accurate_check_file(jcr, ff_pkt, true); + Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg); if (!stat) { Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), @@ -1128,6 +1348,58 @@ return true; } +static bool encode_and_send_deleted_file(JCR *jcr, char *fname) +{ + BSOCK *sd = jcr->store_bsock; + char *attribs; + char *attribsEx; + int stat; +#ifdef FD_NO_SEND_TEST + return true; +#endif + + attribs = " "; + attribsEx = " "; + + /* + * Send Attributes header to Storage daemon + * + */ + if (!sd->fsend("%ld %d 0", 0, STREAM_UNIX_ATTRIBUTES)) { + Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), + sd->bstrerror()); + return false; + } + Dmsg1(300, ">stored: attrhdr %s\n", sd->msg); + + /* + * Send file attributes to Storage daemon + * File_index + * File type + * Filename (full path) + * Encoded attributes + * Link name (if type==FT_LNK or FT_LNKSAVED) + * Encoded extended-attributes (for Win32) + * + * For a directory, link is the same as fname, but with trailing + * slash. For a linked file, link is the link. + */ + stat = sd->fsend("%ld %d %s%c%s%c%s%c%s%c", + 0 /* FileIndex */, + FT_NOSTAT /* FileType */, + fname /* FileName */, + 0, attribs, 0, 0, 0, attribsEx, 0); + + Dmsg2(300, ">stored: attr len=%d: %s\n", sd->msglen, sd->msg); + if (!stat) { + Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"), + sd->bstrerror()); + return false; + } + sd->signal(BNET_EOD); /* indicate end of attributes data */ + return true; +} + /* * Do in place strip of path */ Index: src/filed/job.c =================================================================== --- src/filed/job.c (révision 6443) +++ src/filed/job.c (copie de travail) @@ -49,6 +49,7 @@ /* Imported functions */ extern int status_cmd(JCR *jcr); extern int qstatus_cmd(JCR *jcr); +extern int accurate_get_current_file_list_cmd(JCR *jcr); /* Forward referenced functions */ static int backup_cmd(JCR *jcr); @@ -106,6 +107,7 @@ {"RunBeforeJob", runbefore_cmd, 0}, {"RunAfterJob", runafter_cmd, 0}, {"Run", runscript_cmd, 0}, + {"accurate", accurate_get_current_file_list_cmd, 0}, {NULL, NULL} /* list terminator */ }; @@ -1087,6 +1089,9 @@ case 'c': fo->flags |= FO_CHKCHANGES; break; + case 'C': + fo->flags |= FO_ACCURATE; + break; default: Emsg1(M_ERROR, 0, _("Unknown include/exclude option: %c\n"), *p); break; @@ -1195,6 +1200,9 @@ 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; } @@ -1204,14 +1212,14 @@ /* 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. Index: src/stored/append.c =================================================================== --- src/stored/append.c (révision 6443) +++ src/stored/append.c (copie de travail) @@ -146,7 +146,7 @@ /* Read Stream header from the File daemon. * The stream header consists of the following: - * file_index (sequential Bacula file index, base 1) + * file_index (sequential Bacula file index, base 1, 0 for deleted files) * stream (Bacula number to distinguish parts of data) * info (Info for Storage daemon -- compressed, encryped, ...) * info is not currently used, so is read, but ignored! @@ -185,16 +185,18 @@ Dmsg2(890, " 0 && (file_index == last_file_index || - file_index == last_file_index + 1))) { - Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n")); - ok = false; - break; + if (file_index != 0) { /* TODO: handle file_index == 0 */ + if (!(file_index > 0 && (file_index == last_file_index || + file_index == last_file_index + 1))) { + Jmsg0(jcr, M_FATAL, 0, _("File index from FD not positive or sequential\n")); + ok = false; + break; + } + if (file_index != last_file_index) { + jcr->JobFiles = file_index; + last_file_index = file_index; + } } - if (file_index != last_file_index) { - jcr->JobFiles = file_index; - last_file_index = file_index; - } /* Read data stream from the File daemon. * The data stream is just raw bytes @@ -212,25 +214,26 @@ stream_to_ascii(buf1, rec.Stream,rec.FileIndex), rec.data_len); - while (!write_record_to_block(dcr->block, &rec)) { - Dmsg2(850, "!write_record_to_block data_len=%d rem=%d\n", rec.data_len, - rec.remainder); - if (!write_block_to_device(dcr)) { - Dmsg2(90, "Got write_block_to_dev error on device %s. %s\n", - dev->print_name(), dev->bstrerror()); - ok = false; - break; - } - } - if (!ok) { - Dmsg0(400, "Not OK\n"); - break; - } - jcr->JobBytes += rec.data_len; /* increment bytes this job */ - Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n", - FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId, - stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len); + while (!write_record_to_block(dcr->block, &rec)) { + Dmsg2(850, "!write_record_to_block data_len=%d rem=%d\n", rec.data_len, + rec.remainder); + if (!write_block_to_device(dcr)) { + Dmsg2(90, "Got write_block_to_dev error on device %s. %s\n", + dev->print_name(), dev->bstrerror()); + ok = false; + break; + } + if (!ok) { + Dmsg0(400, "Not OK\n"); + break; + } + jcr->JobBytes += rec.data_len; /* increment bytes this job */ + Dmsg4(850, "write_record FI=%s SessId=%d Strm=%s len=%d\n", + FI_to_ascii(buf1, rec.FileIndex), rec.VolSessionId, + stream_to_ascii(buf2, rec.Stream, rec.FileIndex), rec.data_len); + } + /* Send attributes and digest to Director for Catalog */ if (stream == STREAM_UNIX_ATTRIBUTES || stream == STREAM_UNIX_ATTRIBUTES_EX || crypto_digest_stream_type(stream) != CRYPTO_DIGEST_NONE) { Index: src/jcr.h =================================================================== --- src/jcr.h (révision 6443) +++ src/jcr.h (copie de travail) @@ -119,6 +119,7 @@ /* Forward referenced structures */ class JCR; +class htable; struct FF_PKT; struct B_DB; struct ATTR_DBR; @@ -318,6 +319,7 @@ 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 */ Index: src/findlib/find.h =================================================================== --- src/findlib/find.h (révision 6443) +++ src/findlib/find.h (copie de travail) @@ -108,6 +108,7 @@ #define FO_ENHANCEDWILD (1<<23) /* Enhanced wild card processing */ #define FO_CHKCHANGES (1<<24) /* Check if file have been modified during backup */ #define FO_STRIPPATH (1<<25) /* Check for stripping path */ +#define FO_ACCURATE (1<<26) /* Accurate mode */ struct s_included_file { struct s_included_file *next;