From: Alain Spineux Date: Fri, 17 Feb 2023 14:42:54 +0000 (+0100) Subject: Fix #9749: backup windows mount points and cleanup and improve win32 code X-Git-Tag: Beta-15.0.0~249 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=59e2b9cfaf84f2b2488e05b8249c6af101b4a0f4;p=thirdparty%2Fbacula.git Fix #9749: backup windows mount points and cleanup and improve win32 code --- diff --git a/bacula/src/filed/job.c b/bacula/src/filed/job.c index 20ee2293c..a719feae2 100644 --- a/bacula/src/filed/job.c +++ b/bacula/src/filed/job.c @@ -2132,7 +2132,21 @@ static int set_options(findFOPTS *fo, const char *opts) #ifdef HAVE_WIN32 /* TODO: merge find.c ? */ -static bool is_excluded(findFILESET *fileset, char *path) +/* Check if path is excluded by the exclude list of the fileset + * path is checked using Bacula's fnmatch() (in win32) + * and then compared char by char with some flexibility with the ending '/' + * or '\\' on the FileSet side + * File=C:/mnt/ in the FileSet will match C:/mnt and C:/mnt/ + * If you know that the path is a directory, th'n you can set is_path_a_dir + * and then + * File=C:/mnt in the FileSet will match C:/mnt and C:/mnt/ + * Notice that + * File=C:/mnt/+ (with a * and not a +) don't match C:/mnt nor C:/mnt/ + * + * This function is only used when "building" the FileSet, it is not "yet" used + * when walking the tree during the backup + */ +static bool is_excluded(findFILESET *fileset, char *path, bool is_path_a_dir) { int fnm_flags=FNM_CASEFOLD; /* FIXME: Not exactly accurate, the IGNORECASE option is not available... */ int fnmode=0; @@ -2169,12 +2183,16 @@ static bool is_excluded(findFILESET *fileset, char *path) } /* Looks to be the same string, but with a trailing slash */ - if (fname[0] && IsPathSeparator(fname[0]) && fname[1] == '\0' - && p[0] == '\0') - { + if (IsPathSeparator(fname[0]) && fname[1] == '\0' && p[0] == '\0') { Dmsg1(DT_VOLUME|50, "Reject: %s\n", path); return true; } + /* if path is a directory, try stripping the final '/' in path */ + if (is_path_a_dir && IsPathSeparator(p[0]) && p[1] == '\0' + && fname[0] == '\0') { + Dmsg1(DT_VOLUME|50, "Reject dir: %s\n", path); + return true; + } } } return false; @@ -2229,7 +2247,7 @@ static findINCEXE *is_included(findFILESET *fileset, char *path) } /* Here we include all drives that are listed in your mtab structure - * "/" backs up everyting with a drive letter. + * "/" backs up everything with a drive letter. * "OneFS = No" would then go down mounted file systems. */ static void list_drives(findFILESET *fileset, dlist *name_list, MTab *mtab) @@ -2247,27 +2265,23 @@ static void list_drives(findFILESET *fileset, dlist *name_list, MTab *mtab) } WCHAR *dir; - /* We determine the drive letter of the volume if any */ + /* We determine a path that is not excluded FileSet */ for (dir = elt->first(); dir ; dir = elt->next(dir)) { - if (wcslen(dir) == 3 && dir[1] == L':') { - break; + if (wcslen(dir) != 3 || dir[1] != L':') { + continue; /* Exclude path that are not Drive Letter */ } - } - if (dir && wchar_path_2_wutf8(&buf, dir) > 0) { - - /* Path from MTAb are using \ and the catalog - * contains c:\subdir\path/ instead of c:/subdir/path/ - * so we do a quick fix... - */ - for(char *p = buf; *p ; p++) { - if (*p == '\\') { - *p = '/'; - } + if (wchar_path_2_wutf8(&buf, dir) <= 0) { + continue; /* something wrong with the name, skip */ } - if (!is_excluded(fileset, buf)) { - Dmsg1(DT_VOLUME|50,"Adding drive in include list %s\n",buf); - name_list->append(new_dlistString(buf)); + win32_to_unix_slash(buf); + if (is_excluded(fileset, buf, true)) { + continue; /* the name is in the exclude list */ } + remove_win32_trailing_slash(buf); + /* add the name without the ending '/' */ + Dmsg1(DT_VOLUME|50,"Adding drive in include list %s\n",buf); + name_list->append(new_dlistString(buf)); + break; /* adding a Volume once is enough */ } } free_pool_memory(buf); @@ -2279,6 +2293,8 @@ static void list_drives(findFILESET *fileset, dlist *name_list, MTab *mtab) * are used, because we create a snapshot of all used * drives before operation * + * szDrives : is a list of the drives where we must active the snapshot + * this is used by some plugins like cdp & delta */ static int get_win32_driveletters(JCR *jcr, FF_PKT *ff, char* szDrives, void* mtab_def) @@ -2286,7 +2302,8 @@ get_win32_driveletters(JCR *jcr, FF_PKT *ff, char* szDrives, void* mtab_def) int nCount = 0; #ifdef HAVE_WIN32 findFILESET *fileset = ff->fileset; - findINCEXE *alldrives = NULL, *inc = NULL; + findINCEXE *alldrives = NULL; /* the fileset that holds "File = /" if any */ + findINCEXE *inc = NULL; uint64_t flags = 0; char drive[4]; @@ -2294,9 +2311,11 @@ get_win32_driveletters(JCR *jcr, FF_PKT *ff, char* szDrives, void* mtab_def) MTab *mtab = (MTab*) mtab_def; if (!mtab_def) { mtab = New(MTab()); - mtab->get(); + mtab->load_volumes(); } + dump_name_list(__FILE__, __LINE__, DT_VOLUME|50, "NameList in", fileset); + /* We check if we need to complete the fileset with File=/ */ if (fileset) { for (int i=0; iinclude_list.size(); i++) { @@ -2322,7 +2341,7 @@ get_win32_driveletters(JCR *jcr, FF_PKT *ff, char* szDrives, void* mtab_def) drive[0] = szDrives[i]; if (mtab->addInSnapshotSet(drive)) { /* When all volumes are selected, we can stop */ Dmsg0(DT_VOLUME|50, "All Volumes are marked, stopping the loop here\n"); - goto all_included; + goto all_included; // TODO ASX I don't like this goto, I would prefer a break } } } @@ -2330,7 +2349,7 @@ get_win32_driveletters(JCR *jcr, FF_PKT *ff, char* szDrives, void* mtab_def) if (fileset) { /* Writer dictates the snapshot included components - * but we still give the chanche to include extra files to + * but we still give the chance to include extra files to * the snapshot set via fileset include_list */ dlistString *node; @@ -2344,94 +2363,117 @@ get_win32_driveletters(JCR *jcr, FF_PKT *ff, char* szDrives, void* mtab_def) if (mtab->addInSnapshotSet(fname)) { /* When all volumes are selected, we can stop */ Dmsg0(DT_VOLUME|50, "All Volumes are marked, stopping the loop here\n"); - goto all_included; + goto all_included; // TODO ASX I don't like this goto, I would prefer a break } } } + dump_name_list(__FILE__, __LINE__, DT_VOLUME|50, "NameList intermediate", fileset); + /* TODO: it needs to be done Include by Include, but in the worst case, * we take too much snapshots... */ if (flags & FO_MULTIFS) { - /* Need to add subdirectories */ + /* Need to add subdirectories that are mount points + * The idea is that the FD will not cross directories that are mount + * points, but instead, we add the directory to the Include list. + */ POOLMEM *fn = get_pool_memory(PM_FNAME); MTabEntry *elt, *elt2; int len; Dmsg0(DT_VOLUME|50, "OneFS is set, looking for remaining volumes\n"); + mtab->dump(__FILE__, __LINE__, DT_VOLUME|50, "ASX mtab: "); foreach_rblist(elt, mtab->entries) { if (elt->in_SnapshotSet) { - continue; /* Already in */ + Dmsg1(DT_VOLUME|50, "Skip volume that is already in the snapshot list: %ls\n", elt->first()); + continue; /* Was already selected in the Include list */ + /* ASX We should not do that + * imaginons qu'un volume soit monté sur C:/mnt et F:/ en meme temps + * et qu'on a File=C:/, dans ce cas C:/ et c/mnt sont backupé + * plus tard l'utilisateur ajoute File=F:/toto + * ce dernier va forcer le snapshot sur le volume en question + * et du coups C:/mnt ne sera plus considéré et donc non backupé + * si on inclu aussi C:/mnt, on aura 2x toto + * on devrait pas faire un continue ici + */ } /* A volume can have multiple mount points */ for (wchar_t *p = elt->first() ; p && *p ; p = elt->next(p)) { wchar_path_2_wutf8(&fn, p); + win32_to_unix_slash(fn); + /* Notice that fn is ended with a '/' */ Dmsg1(DT_VOLUME|50, "Looking for path %s\n", fn); - /* First case, root drive (c:/, e:/, d:/), not a submount point */ + /* Bacula don't consider drive letter (X:/), as submount point + * Notice that when "File=/" is used, the drive letters have + * been already added above + */ len = strlen(fn); if (len <= 3) { - Dmsg1(DT_VOLUME|50, "Skiping %s\n", fn); - continue; + Dmsg1(DT_VOLUME|50, "Skipping drive letter: %s\n", fn); + continue; /* we are looking for mount point */ } /* First thing is to look in the exclude list to see if this directory * is explicitly excluded */ - if (!is_excluded(fileset, fn)) { - elt->setInSnapshotSet(); - } else { + if (is_excluded(fileset, fn, true)) { Dmsg1(DT_VOLUME|50, "Looks to be excluded %s\n", fn); - continue; + continue; /* try another path for the volume if any */ } - - /* c:/vol/vol2/vol3 - * will look c:/, then c:/vol/, then c:/vol2/ and if one of them - * is selected, the sub volume will be directly marked. + /* In the case of a FO_MULTIFS + * If X:/ is Included in the fileset, then every volumes mounted + * below X:/, let say X:/mnt must also be Included and the + * snapshot activated. + * + * Say differently, looking from the list of mount points, + * if X:/mnt is a mount point and X:/ is included, then X:/mnt + * must be included. + * In the case of "c:/vol/vol2/vol3/" (notice the ending '/') + * we will look for c:, c:/vol, c:/vol/vol2 and c:/vol/vol2/vol3 + * if one is included then we add c:/vol/vol2/vol3 */ - for (char *p1 = fn ; *p1 && !elt->in_SnapshotSet ; p1++) { - if (IsPathSeparator(*p1)) { - bool to_add=false; /* Add this volume to the FileSet ? */ - char c = *(p1 + 1); - *(p1 + 1) = 0; - - /* We look for the previous directory, and if marked, we mark - * the current one as well - */ - Dmsg1(DT_VOLUME|50, "Looking for %s\n", fn); - elt2 = mtab->search(fn); - if (elt2 && elt2->in_SnapshotSet) { - Dmsg0(DT_VOLUME|50, "Put volume in SnapshotSet\n"); - elt->setInSnapshotSet(); - to_add = true; - } - - /* Find where to add the new volume, normally near the - * root volume, or if we are using / - */ - if (alldrives) { - inc = alldrives; - } else { - inc = is_included(fileset, fn); - Dmsg2(DT_VOLUME|50, "Adding volume in fileset 0x%p %s\n", inc, fn); - } - - *(p1 + 1) = c; /* restore path separator */ - - /* We can add the current volume to the FileSet */ - if (to_add && inc != NULL) { - /* Convert the path to /xxx/xxx */ - dlistString *tmp = new_dlistString(fn); - for(char *p = tmp->c_str(); *p ; p++) { - if (*p == '\\') { - *p = '/'; - } - } - inc->name_list.append(tmp); - Dmsg1(DT_VOLUME|50,"Adding volume in file list %s\n",tmp->c_str()); - } + for (char *p1 = fn; *p1 != '\0'; p1++) { + if (!IsPathSeparator(*p1)) { + continue; + } + *p1 = '\0'; // replace the '/' with a '\0' + + /* We look for the previous directory, and if marked, we mark + * the current one as well + */ + Dmsg1(DT_VOLUME|50, "Looking for %s\n", fn); + elt2 = mtab->search(fn); + if (elt2 == NULL || !elt2->in_SnapshotSet) { + /* fn is not in a volume that is snapshoted */ + *p1 = '/'; /* restore path separator */ + continue; + } + + /* Find where to add the new volume, normally near the + * root volume, or if we are using / + */ + if (alldrives) { + inc = alldrives; + } else { + inc = is_included(fileset, fn); + Dmsg2(DT_VOLUME|50, "Adding volume in fileset 0x%p %s\n", inc, fn); + } + + *p1 = '/'; /* restore path separator */ + + /* We can add the current volume to the FileSet */ + if (inc != NULL) { + dlistString *tmp = new_dlistString(fn); + remove_win32_trailing_slash(tmp->c_str()); /* remove the trailing slash */ + inc->name_list.append(tmp); + Dmsg1(DT_VOLUME|50,"Adding volume in file list %s\n", fn); + elt->setInSnapshotSet(); + Dmsg1(DT_VOLUME|50, "Put volume in SnapshotSet: %s\n", fn); + break; } } } @@ -2439,6 +2481,8 @@ get_win32_driveletters(JCR *jcr, FF_PKT *ff, char* szDrives, void* mtab_def) free_pool_memory(fn); } all_included: + dump_name_list(__FILE__, __LINE__, DT_VOLUME|50, "NameList out", fileset); + /* Now, we look the volume list to know which one to include */ MTabEntry *elt; foreach_rblist(elt, mtab->entries) { @@ -3015,6 +3059,7 @@ static int backup_cmd(JCR *jcr) jcr->pVSSClient = VSSInit(); if (jcr->pVSSClient->InitializeForBackup(jcr)) { MTab *tab = jcr->pVSSClient->GetVolumeList(); + tab->dump(__FILE__, __LINE__, DT_VOLUME|50, "mtab: "); generate_plugin_event(jcr, bEventVssBackupAddComponents, tab); /* tell vss which drives to snapshot */ char szWinDriveLetters[27]; @@ -3040,6 +3085,7 @@ static int backup_cmd(JCR *jcr) } else { Jmsg(jcr, M_WARNING, 0, _("No drive letters found for generating VSS snapshots.\n")); } + tab->dump(__FILE__, __LINE__, DT_VOLUME|50, "mtab: "); } else { berrno be; Jmsg(jcr, M_FATAL, 0, _("VSS was not initialized properly. ERR=%s\n"), diff --git a/bacula/src/win32/compat/mtab.cpp b/bacula/src/win32/compat/mtab.cpp index fc8627d3d..30fb3c22f 100644 --- a/bacula/src/win32/compat/mtab.cpp +++ b/bacula/src/win32/compat/mtab.cpp @@ -107,6 +107,7 @@ MTab::MTab() { lasterror = ERROR_SUCCESS; lasterror_str = ""; nb_in_SnapshotSet = 0; + nb_in_SuitableForSnapshot = 0; entries = New(rblist(elt, &elt->link)); } @@ -120,21 +121,30 @@ MTab::~MTab() { } } +/* we are looking for DRIVE_FIXED drive */ UINT MTabEntry::getDriveType() { WCHAR *root = first(); // Make sure to discard CD-ROM and network drives if (!root) { - return 0; + return DRIVE_UNKNOWN; /* zero */ } driveType = GetDriveTypeW(root); return driveType; } -/* Return true if the current volume can be snapshoted (ie not CDROM or fat32) */ +/* Return true if the current volume can be snapshoted: + * if it is a FIXED disk with a Filesystem that support snapshot + * (ie not CDROM or fat32) + */ bool MTabEntry::isSuitableForSnapshot() +{ + return can_Snapshot; +} + +bool MTabEntry::initIsSuitableForSnapshot() { DWORD componentlength, fsflags; WCHAR fstype[50]; @@ -178,7 +188,9 @@ bail_out: return can_Snapshot; } -/* Find a volume for a specific path */ +/* Find a volume for a specific path + * "C:\\" or "c:\\mnt\\" -> \\?\Volume{086fc09e-7781-4076-9b2e-a4ff5c6b52c7}\ + */ MTabEntry *MTab::search(char *p) { wstring volume; @@ -209,11 +221,38 @@ bool MTab::addInSnapshotSet(char *p) elt->setInSnapshotSet(); } } - return nb_in_SnapshotSet == entries->size(); + return nb_in_SnapshotSet == nb_in_SuitableForSnapshot; +} +void MTab::dump(const char* file, int lineno, int lvl, const char *prefix) +{ + MTabEntry *elt; + WCHAR mountPaths[MAX_PATH]; + if (chk_dbglvl(lvl)) d_msg(file, lineno, lvl, "%s suitable for snapshot %d/%d/%d\n", prefix, nb_in_SnapshotSet, nb_in_SuitableForSnapshot, entries->size()); + foreach_rblist(elt, entries) { + // replace intermediat L'\0' in mountPaths by '+' for display + mountPaths[0]=L'\0'; + if (elt->mountPaths != NULL) { + WCHAR *s = elt->mountPaths; + WCHAR *d = mountPaths; + while (*s != L'\0' && d-mountPaths<(int)sizeof(mountPaths)) { + *d++ = *s++; + if (*s == L'\0' && s[1] != L'\0') { + *d++ = L'+'; + s++; + } + } + *d = L'\0'; + } + if (chk_dbglvl(lvl)) d_msg(file, lineno, lvl, "%s Vol=%ls dev=%ls mounts=%ls snap=%ls active=%c\n", prefix, elt->volumeName, elt->deviceName, mountPaths, elt->shadowCopyName, elt->in_SnapshotSet?'Y':'N'); + } } -/* Initialize the "entries" list will all existing volumes */ -bool MTab::get() +/* Load the list volumes that exists on the Windows host + * with tuples like : + * VolumeName='\\\\?\\Volume{086fc09e-7781-4076-9b2e-a4ff5c6b52c7}\\' + * DeviceName='\\Device\\HarddiskVolume2' + */ +bool MTab::load_volumes() { DWORD count = 0; WCHAR DeviceName[MAX_PATH] = L""; @@ -268,8 +307,10 @@ bool MTab::get() // VolumeName='\\\\?\\Volume{086fc09e-7781-4076-9b2e-a4ff5c6b52c7}\\' // DeviceName='\\Device\\HarddiskVolume2' MTabEntry *entry = New(MTabEntry(DeviceName, VolumeName)); + if (entry->initIsSuitableForSnapshot()) { + nb_in_SuitableForSnapshot++; + } entries->insert(entry, volume_cmp); - // // Move on to the next volume. Success = FindNextVolumeW(FindHandle, VolumeName, ARRAYSIZE(VolumeName)); diff --git a/bacula/src/win32/compat/mtab.h b/bacula/src/win32/compat/mtab.h index 960776be2..75b302d55 100644 --- a/bacula/src/win32/compat/mtab.h +++ b/bacula/src/win32/compat/mtab.h @@ -30,10 +30,12 @@ */ class MTabEntry: public SMARTALLOC { public: - WCHAR *volumeName; // Name of the current volume - WCHAR *mountPaths; // List of mount paths - WCHAR *deviceName; - WCHAR *shadowCopyName; + WCHAR *volumeName; // The uuid of the volume + // "\\?\Volume{086fc09e-7781-4076-9b2e-a4ff5c6b52c7}\" + WCHAR *mountPaths; // list of mount point (if any) separated by zero + // "C:\\\0C:\\mnt\\\0\0" + WCHAR *deviceName; // \\Device\\HarddiskVolume2 + WCHAR *shadowCopyName; // \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy216 bool in_SnapshotSet; bool can_Snapshot; UINT driveType; @@ -82,6 +84,9 @@ public: /* Return the drive type (cdrom, fixed, network, ...) */ UINT getDriveType(); + /* Check if the current volume can be snapshoted and initialize "can_Snapshot" */ + bool initIsSuitableForSnapshot(); + /* Return true if the current volume can be snapshoted (ie not CDROM or fat32) */ bool isSuitableForSnapshot(); @@ -101,7 +106,7 @@ public: } }; - /* Compute the path list assiciated with the current volume */ + /* Compute the path list associated with the current volume */ bool get_paths() { DWORD count = MAX_PATH + 1; bool ret = false; @@ -157,6 +162,7 @@ public: const char *lasterror_str; rblist *entries; /* MTabEntry */ int nb_in_SnapshotSet; + int nb_in_SuitableForSnapshot; MTab(); ~MTab(); @@ -167,8 +173,12 @@ public: /* Try to add a volume to the current snapshotset */ bool addInSnapshotSet(char *file); - /* Fill the "entries" list will all detected volumes of the system*/ - bool get(); + /* Fill the "entries" list will all detected volumes of the system */ + bool load_volumes(); + + /* Dump for debugging at the given debug level */ + void dump(const char* file, int lineno, int level, const char* prefix); + }; #endif /* BEE_MTAB_ENTRY_H */ diff --git a/bacula/src/win32/filed/vss_generic.cpp b/bacula/src/win32/filed/vss_generic.cpp index 5346555ce..2063a502a 100644 --- a/bacula/src/win32/filed/vss_generic.cpp +++ b/bacula/src/win32/filed/vss_generic.cpp @@ -287,7 +287,7 @@ bool VSSClientGeneric::Initialize(DWORD dwContext, bool bDuringRestore) } m_VolumeList = New(MTab()); // TODO: See if we do this part only in backup - if (!m_VolumeList->get()) { + if (!m_VolumeList->load_volumes()) { Jmsg(m_jcr, M_ERROR, 0, "Unable to list devices and volumes.\n"); return false; } @@ -490,7 +490,8 @@ bool VSSClientGeneric::CreateSnapshots(alist *mount_points) MTabEntry *elt = (MTabEntry*)m_VolumeList->entries->search(p, volume_cmp); ASSERT2(elt, "Should find the volume in the list"); Jmsg(m_jcr, M_INFO, 0, " Snapshot mount point: %ls\n", elt->first()); - Dmsg1(50, "AddToSnapshot OK for Vol: %ls\n", p); + Dmsg2(50, "AddToSnapshot OK for %ls vol=%ls\n", elt->first(), p); + /* Here the snapshot don't exist yet, elt->shadowCopyName == NULL */ } else { //Dmsg1(50, "AddToSnapshot() failed for Vol: %ls\n", (LPWSTR)volume.c_str()); Dmsg2(50, "AddToSnapshot() failed for path: %ls hresult=0x%x\n", p, hresult);