public List<KeyValuePair<BackupEntryBase, Exception>> VerifyBackupChain() { CommunicationStatistics stats = new CommunicationStatistics(DuplicatiOperationMode.Verify); SetupCommonOptions(stats); List<KeyValuePair<BackupEntryBase, Exception>> results = new List<KeyValuePair<BackupEntryBase, Exception>>(); if (m_options.DontReadManifests) throw new InvalidOperationException(Strings.Interface.ManifestsMustBeRead); if (m_options.SkipFileHashChecks) throw new InvalidOperationException(Strings.Interface.CannotVerifyWithoutHashes); if (!string.IsNullOrEmpty(m_options.SignatureCachePath)) { stats.LogWarning(Strings.Interface.DisablingSignatureCacheForVerification, null); m_options.SignatureCachePath = null; } using (BackendWrapper backend = new BackendWrapper(stats, m_backend, m_options)) { //Find the spot in the chain where we start ManifestEntry bestFit = backend.GetBackupSet(m_options.RestoreTime); //Get the list of manifests to validate List<ManifestEntry> entries = new List<ManifestEntry>(); entries.Add(bestFit); entries.AddRange(bestFit.Incrementals); entries.Reverse(); foreach (ManifestEntry me in entries) { Manifestfile mf = null; try { mf = GetManifest(backend, me); VerifyBackupChainWithFiles(backend, me); if (mf.SignatureHashes.Count != me.Volumes.Count) results.Add(new KeyValuePair<BackupEntryBase,Exception>(me, new Exception(string.Format(Strings.Interface.ManifestAndFileCountMismatchError, mf.SelfFilename, mf.SignatureHashes.Count, me.Volumes.Count)))); else results.Add(new KeyValuePair<BackupEntryBase,Exception>(me, null)); } catch (Exception ex) { results.Add(new KeyValuePair<BackupEntryBase,Exception>(me, ex)); } if (mf != null) { int volumes = Math.Min(mf.SignatureHashes.Count, me.Volumes.Count); for(int i = 0; i <volumes; i++) { if (m_options.Verificationlevel == VerificationLevel.Signature || m_options.Verificationlevel == VerificationLevel.Full) { try { using(Utility.TempFile tf = new Duplicati.Library.Utility.TempFile()) backend.Get(me.Volumes[i].Key, mf, tf, mf.SignatureHashes[i]); results.Add(new KeyValuePair<BackupEntryBase, Exception>(me.Volumes[i].Key, null)); } catch (Exception ex) { results.Add(new KeyValuePair<BackupEntryBase,Exception>(me.Volumes[i].Key, ex)); } } if (m_options.Verificationlevel == VerificationLevel.Full) { try { using(Utility.TempFile tf = new Duplicati.Library.Utility.TempFile()) backend.Get(me.Volumes[i].Value, mf, tf, mf.ContentHashes[i]); results.Add(new KeyValuePair<BackupEntryBase, Exception>(me.Volumes[i].Value, null)); } catch (Exception ex) { results.Add(new KeyValuePair<BackupEntryBase,Exception>(me.Volumes[i].Value, ex)); } } } } } //Re-generate verification file if (m_options.CreateVerificationFile) { //Stop any async operations if (m_options.AsynchronousUpload) backend.ExtractPendingUploads(); VerificationFile vf = new VerificationFile(entries, new FilenameStrategy(m_options)); using (Utility.TempFile tf = new Duplicati.Library.Utility.TempFile()) { vf.Save(tf); tf.Protected = true; backend.Put(new VerificationEntry(entries[entries.Count - 1].Time), tf); } } } return results; }
/// <summary> /// This function will examine all options passed on the commandline, and test for unsupported or deprecated values. /// Any errors will be logged into the statistics module. /// </summary> /// <param name="options">The commandline options given</param> /// <param name="backend">The backend url</param> /// <param name="stats">The statistics into which warnings are written</param> private void ValidateOptions(CommunicationStatistics stats) { //No point in going through with this if we can't report if (stats == null) return; //Keep a list of all supplied options Dictionary<string, string> ropts = m_options.RawOptions; //Keep a list of all supported options Dictionary<string, Library.Interface.ICommandLineArgument> supportedOptions = new Dictionary<string, Library.Interface.ICommandLineArgument>(); //There are a few internal options that are not accessible from outside, and thus not listed foreach (string s in Options.InternalOptions) supportedOptions[s] = null; //Figure out what module options are supported in the current setup List<Library.Interface.ICommandLineArgument> moduleOptions = new List<Duplicati.Library.Interface.ICommandLineArgument>(); Dictionary<string, string> disabledModuleOptions = new Dictionary<string, string>(); foreach (KeyValuePair<bool, Library.Interface.IGenericModule> m in m_options.LoadedModules) if (m.Value.SupportedCommands != null) if (m.Key) moduleOptions.AddRange(m.Value.SupportedCommands); else { foreach (Library.Interface.ICommandLineArgument c in m.Value.SupportedCommands) { disabledModuleOptions[c.Name] = m.Value.DisplayName + " (" + m.Value.Key + ")"; if (c.Aliases != null) foreach (string s in c.Aliases) disabledModuleOptions[s] = disabledModuleOptions[c.Name]; } } //Now run through all supported options, and look for deprecated options foreach (IList<Library.Interface.ICommandLineArgument> l in new IList<Library.Interface.ICommandLineArgument>[] { m_options.SupportedCommands, DynamicLoader.BackendLoader.GetSupportedCommands(m_backend), m_options.NoEncryption ? null : DynamicLoader.EncryptionLoader.GetSupportedCommands(m_options.EncryptionModule), moduleOptions, DynamicLoader.CompressionLoader.GetSupportedCommands(m_options.CompressionModule) }) { if (l != null) foreach (Library.Interface.ICommandLineArgument a in l) { if (supportedOptions.ContainsKey(a.Name) && Array.IndexOf(Options.KnownDuplicates, a.Name.ToLower()) < 0) stats.LogWarning(string.Format(Strings.Interface.DuplicateOptionNameWarning, a.Name), null); supportedOptions[a.Name] = a; if (a.Aliases != null) foreach (string s in a.Aliases) { if (supportedOptions.ContainsKey(s) && Array.IndexOf(Options.KnownDuplicates, s.ToLower()) < 0) stats.LogWarning(string.Format(Strings.Interface.DuplicateOptionNameWarning, s), null); supportedOptions[s] = a; } if (a.Deprecated) { List<string> aliases = new List<string>(); aliases.Add(a.Name); if (a.Aliases != null) aliases.AddRange(a.Aliases); foreach (string s in aliases) if (ropts.ContainsKey(s)) { string optname = a.Name; if (a.Name != s) optname += " (" + s + ")"; stats.LogWarning(string.Format(Strings.Interface.DeprecatedOptionUsedWarning, optname, a.DeprecationMessage), null); } } } } //Now look for options that were supplied but not supported foreach (string s in ropts.Keys) if (!supportedOptions.ContainsKey(s)) if (disabledModuleOptions.ContainsKey(s)) stats.LogWarning(string.Format(Strings.Interface.UnsupportedOptionDisabledModuleWarning, s, disabledModuleOptions[s]), null); else stats.LogWarning(string.Format(Strings.Interface.UnsupportedOptionWarning, s), null); //Look at the value supplied for each argument and see if is valid according to its type foreach (string s in ropts.Keys) { Library.Interface.ICommandLineArgument arg; if (supportedOptions.TryGetValue(s, out arg) && arg != null) { string validationMessage = ValidateOptionValue(arg, s, ropts[s]); if (validationMessage != null) stats.LogWarning(validationMessage, null); } } //TODO: Based on the action, see if all options are relevant }
/// <summary> /// Reads through a backup and finds the last backup entry that has a specific file /// </summary> /// <returns></returns> public List<KeyValuePair<string, DateTime>> FindLastFileVersion() { CommunicationStatistics stats = new CommunicationStatistics(DuplicatiOperationMode.FindLastFileVersion); SetupCommonOptions(stats); if (m_options.DontReadManifests) throw new Exception(Strings.Interface.ManifestsMustBeRead); if (string.IsNullOrEmpty(m_options.FileToRestore)) throw new Exception(Strings.Interface.NoFilesGivenError); string[] filesToFind = m_options.FileToRestore.Split(System.IO.Path.PathSeparator); KeyValuePair<string, DateTime>[] results = new KeyValuePair<string, DateTime>[filesToFind.Length]; for (int i = 0; i < results.Length; i++) results[i] = new KeyValuePair<string, DateTime>(filesToFind[i], new DateTime(0)); using (BackendWrapper backend = new BackendWrapper(stats, m_backend, m_options)) { //Extract the full backup set list List<ManifestEntry> fulls = backend.GetBackupSets(); //Flatten the list List<ManifestEntry> workList = new List<ManifestEntry>(); //The list is oldest first, this function work newest first fulls.Reverse(); foreach (ManifestEntry f in fulls) { f.Incrementals.Reverse(); workList.AddRange(f.Incrementals); workList.Add(f); } bool warned_manifest_v1 = false; foreach (ManifestEntry mf in workList) { List<Manifestfile.HashEntry> signatureHashes = null; Manifestfile mfi; using(Utility.TempFile tf = new Duplicati.Library.Utility.TempFile()) { backend.Get(mf, null, tf, null); mfi = new Manifestfile(tf, m_options.SkipFileHashChecks); if (!m_options.SkipFileHashChecks) signatureHashes = mfi.SignatureHashes; } //If there are no volumes, don't stop here bool any_unmatched = true; if (stats != null && !warned_manifest_v1 && (mfi.SourceDirs == null || mfi.SourceDirs.Length == 0)) { warned_manifest_v1 = true; stats.LogWarning(Strings.Interface.ManifestVersionRequiresRelativeNamesWarning, null); } foreach(KeyValuePair<SignatureEntry, ContentEntry> e in mf.Volumes) using (Utility.TempFile tf = new Duplicati.Library.Utility.TempFile()) { //Skip non-approved signature files if (signatureHashes != null && e.Key.Volumenumber > signatureHashes.Count) { stats.LogWarning(string.Format(Strings.Interface.SkippedUnlistedSignatureFileWarning, e.Key.Filename), null); continue; } backend.Get(e.Key, mfi, tf, signatureHashes == null ? null : signatureHashes[e.Key.Volumenumber - 1]); any_unmatched = false; RSync.RSyncDir.ContainsFile(mfi, filesToFind, DynamicLoader.CompressionLoader.GetModule(e.Key.Compression, tf, m_options.RawOptions)); for (int i = 0; i < filesToFind.Length; i++) { if (results[i].Value.Ticks == 0 && string.IsNullOrEmpty(filesToFind[i])) results[i] = new KeyValuePair<string,DateTime>(results[i].Key, mf.Time); else any_unmatched = true; } if (!any_unmatched) break; } if (!any_unmatched) break; } return new List<KeyValuePair<string,DateTime>>(results); } }