public Recovery(VoronRecoveryConfiguration config) { _datafile = config.PathToDataFile; _output = config.OutputFileName; _pageSize = config.PageSizeInKb * Constants.Size.Kilobyte; _initialContextSize = config.InitialContextSizeInMB * Constants.Size.Megabyte; _initialContextLongLivedSize = config.InitialContextLongLivedSizeInKB * Constants.Size.Kilobyte; _option = StorageEnvironmentOptions.ForPath(config.DataFileDirectory, null, Path.Combine(config.DataFileDirectory, "Journal"), null, null); _copyOnWrite = !config.DisableCopyOnWriteMode; // by default CopyOnWriteMode will be true _option.CopyOnWriteMode = _copyOnWrite; _progressIntervalInSec = config.ProgressIntervalInSec; _previouslyWrittenDocs = new Dictionary <string, long>(); }
public Recovery(VoronRecoveryConfiguration config) { _datafile = config.PathToDataFile; _output = config.OutputFileName; _pageSize = config.PageSizeInKb * Constants.Size.Kilobyte; _numberOfFieldsInDocumentTable = config.NumberOfFiledsInDocumentTable; _initialContextSize = config.InitialContextSizeInMB * Constants.Size.Megabyte; _initialContextLongLivedSize = config.InitialContextLongLivedSizeInKB * Constants.Size.Kilobyte; _option = StorageEnvironmentOptions.ForPath(config.DataFileDirectory); _copyOnWrite = !config.DisableCopyOnWriteMode; // by default CopyOnWriteMode will be true //i'm setting CopyOnWriteMode this was because we want to keep it internal. _option.GetType().GetProperty("CopyOnWriteMode", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(_option, _copyOnWrite); _progressIntervalInSeconds = config.ProgressIntervalInSeconds; }
private static void ConfigureRecoveryCommand() { _app.Command("recover", cmd => { cmd.ExtendedHelpText = cmd.Description = "Recovering a database into recovery.ravendump."; cmd.HelpOption(HelpOptionString); var dataFileDirectoryArg = cmd.Argument("DataFileDirectory", "The database directory which contains the data file"); var recoverDirectoryArg = cmd.Argument("RecoverDirectory", "The directory to recover the recovery.ravendump file"); var outputFileNameArg = cmd.Option("--OutputFileName", "Will overwrite the default file name () with your own file name (under output directory).", CommandOptionType.SingleValue); var pageSizeInKbArg = cmd.Option("--PageSizeInKB", "Will set the recovery tool to work with page sizes other than 4kb.", CommandOptionType.SingleValue); var initialContextSizeInMbArg = cmd.Option("--InitialContextSizeInMB", "Will set the recovery tool to use a context of the provided size in MB.", CommandOptionType.SingleValue); var initialContextLongLivedSizeInKbArg = cmd.Option("--InitialContextLongLivedSizeInKB", "Will set the recovery tool to use a long lived context size of the provided size in KB.", CommandOptionType.SingleValue); var progressIntervalInSecArg = cmd.Option("--ProgressIntervalInSec", "Will set the recovery tool refresh to console rate interval in seconds.", CommandOptionType.SingleValue); var disableCopyOnWriteModeArg = cmd.Option("--DisableCopyOnWriteMode", "Default is false.", CommandOptionType.SingleValue); var ignoreInvalidJournalErrorsArg = cmd.Option("--IgnoreInvalidJournalErrors", "Default is false.", CommandOptionType.SingleValue); var ignoreDataIntegrityErrorsOfAlreadySyncedTransactionsArg = cmd.Option("--IgnoreInvalidDataErrorsOfAlreadySyncedTransactions", "Default is false.", CommandOptionType.SingleValue); var loggingModeArg = cmd.Option("--LoggingMode", "Logging mode: Operations or Information.", CommandOptionType.SingleValue); var masterKey = cmd.Option("--MasterKey", "Encryption key: base64 string of the encryption master key", CommandOptionType.SingleValue); cmd.OnExecute(() => { VoronRecoveryConfiguration config = new VoronRecoveryConfiguration { DataFileDirectory = dataFileDirectoryArg.Value, }; if (string.IsNullOrWhiteSpace(config.DataFileDirectory) || Directory.Exists(config.DataFileDirectory) == false || File.Exists(Path.Combine(config.DataFileDirectory, DatafileName)) == false) { return(ExitWithError($"Missing {nameof(config.DataFileDirectory)} argument", cmd)); } config.PathToDataFile = Path.Combine(config.DataFileDirectory, DatafileName); var recoverDirectory = recoverDirectoryArg.Value; if (string.IsNullOrWhiteSpace(recoverDirectory)) { return(ExitWithError("Missing RecoverDirectory argument", cmd)); } config.OutputFileName = Path.Combine(recoverDirectory, outputFileNameArg.HasValue() ? outputFileNameArg.Value() : RecoveryFileName); try { if (!Directory.Exists(recoverDirectory)) { Directory.CreateDirectory(recoverDirectory); } File.WriteAllText(config.OutputFileName, "I have write permission!"); File.Delete(config.OutputFileName); } catch { return(ExitWithError($"Cannot write to the output directory ({recoverDirectory}). " + "Permissions issue?", cmd)); } if (pageSizeInKbArg.HasValue()) { if (int.TryParse(pageSizeInKbArg.Value(), out var pageSize) == false || pageSize < 1) { return(ExitWithError($"{nameof(config.PageSizeInKB)} argument value ({pageSize}) is invalid", cmd)); } config.PageSizeInKB = pageSize; } if (initialContextSizeInMbArg.HasValue()) { if (int.TryParse(initialContextSizeInMbArg.Value(), out var contextSize) == false || contextSize < 1) { return(ExitWithError($"{nameof(config.InitialContextSizeInMB)} argument value ({contextSize}) is invalid", cmd)); } config.InitialContextSizeInMB = contextSize; } if (initialContextLongLivedSizeInKbArg.HasValue()) { if (int.TryParse(initialContextLongLivedSizeInKbArg.Value(), out var longLivedContextSize) == false || longLivedContextSize < 1) { return(ExitWithError($"{nameof(config.InitialContextLongLivedSizeInKB)} argument value ({longLivedContextSize}) is invalid", cmd)); } config.InitialContextLongLivedSizeInKB = longLivedContextSize; } if (progressIntervalInSecArg.HasValue()) { if (int.TryParse(progressIntervalInSecArg.Value(), out var refreshRate) == false || refreshRate < 1) { return(ExitWithError($"{nameof(config.ProgressIntervalInSec)} argument value ({refreshRate}) is invalid", cmd)); } config.ProgressIntervalInSec = refreshRate; } if (disableCopyOnWriteModeArg.HasValue()) { var value = disableCopyOnWriteModeArg.Value(); if (bool.TryParse(value, out var disableCopyOnWriteMode) == false) { return(ExitWithError($"{nameof(config.DisableCopyOnWriteMode)} argument value ({value}) is invalid", cmd)); } config.DisableCopyOnWriteMode = disableCopyOnWriteMode; } if (ignoreInvalidJournalErrorsArg.HasValue()) { var value = ignoreInvalidJournalErrorsArg.Value(); if (bool.TryParse(value, out var ignoreInvalidJournalErrors) == false) { return(ExitWithError($"{nameof(config.IgnoreInvalidJournalErrors)} argument value ({value}) is invalid", cmd)); } config.IgnoreInvalidJournalErrors = ignoreInvalidJournalErrors; } if (ignoreDataIntegrityErrorsOfAlreadySyncedTransactionsArg.HasValue()) { var value = ignoreDataIntegrityErrorsOfAlreadySyncedTransactionsArg.Value(); if (bool.TryParse(value, out var ignoreDataIntegrityErrorsOfAlreadySyncedTransactions) == false) { return(ExitWithError($"{nameof(config.IgnoreDataIntegrityErrorsOfAlreadySyncedTransactions)} argument value ({value}) is invalid", cmd)); } config.IgnoreDataIntegrityErrorsOfAlreadySyncedTransactions = ignoreDataIntegrityErrorsOfAlreadySyncedTransactions; } if (loggingModeArg.HasValue()) { var value = loggingModeArg.Value(); if (Enum.TryParse(value, out LogMode mode) == false) { return(ExitWithError($"{nameof(config.LoggingMode)} argument value ({value}) is invalid", cmd)); } config.LoggingMode = mode; } if (masterKey.HasValue()) { var value = masterKey.Value(); byte[] key; try { key = Convert.FromBase64String(value); } catch { return(ExitWithError($"{nameof(config.MasterKey)} argument value ({value}) is not a valid base64 string", cmd)); } config.MasterKey = key; } using (var recovery = new Recovery(config)) { var cts = new CancellationTokenSource(); Console.WriteLine("Press 'q' to quit the recovery process"); var cancellationTask = Task.Factory.StartNew(() => { while (Console.Read() != 'q') { } cts.Cancel(); //The reason i do an exit here is because if we are in the middle of journal recovery //we can't cancel it and it may take a long time. //That said i'm still going to give it a while to do a proper exit Task.Delay(5000).ContinueWith(_ => { Environment.Exit(1); }); }, cts.Token); try { recovery.Execute(Console.Out, cts.Token); } catch (Exception e) { if (e.Data["ReturnCode"] is int returnCode) { return(returnCode); } return(ExitWithError(e.Message, _app)); } cts.Cancel(); } return(0); }); }); }
public static VoronRecoveryArgsProcessStatus ProcessArgs(string[] args, out VoronRecoveryConfiguration config) { config = null; if (args.Length < 2) { return(VoronRecoveryArgsProcessStatus.NotEnoughArguments); } if (!Directory.Exists(args[0]) || !File.Exists(Path.Combine(args[0], DatafileName))) { return(VoronRecoveryArgsProcessStatus.MissingDataFile); } try { if (!Directory.Exists(args[1])) { Directory.CreateDirectory(args[1]); } var testFile = Path.Combine(args[1], RecoveryFileName); File.WriteAllText(testFile, "I have write permission!"); File.Delete(testFile); } catch { return(VoronRecoveryArgsProcessStatus.CantWriteToOutputDirectory); } config = new VoronRecoveryConfiguration() { DataFileDirectory = args[0], PathToDataFile = Path.Combine(args[0], DatafileName), OutputFileName = Path.Combine(args[1], RecoveryFileName) }; if (args.Length > 2 && args.Length % 2 != 0) { return(VoronRecoveryArgsProcessStatus.WrongNumberOfArgs); } for (var i = 2; i < args.Length; i += 2) { switch (args[i]) { case "-OutputFileName": config.OutputFileName = Path.Combine(args[1], args[i + 1]); break; case "-PageSizeInKB": int pageSize; if (int.TryParse(args[i + 1], out pageSize) == false || pageSize < 1) { return(VoronRecoveryArgsProcessStatus.InvalidPageSize); } config.PageSizeInKb = pageSize; break; case "-InitialContextSizeInMB": int contextSize; if (int.TryParse(args[i + 1], out contextSize) == false || contextSize < 1) { return(VoronRecoveryArgsProcessStatus.InvalidContextSize); } config.InitialContextSizeInMB = contextSize; break; case "-InitialContextLongLivedSizeInKB": int longLivedContextSize; if (int.TryParse(args[i + 1], out longLivedContextSize) == false || longLivedContextSize < 1) { return(VoronRecoveryArgsProcessStatus.InvalidLongLivedContextSize); } config.InitialContextLongLivedSizeInKB = longLivedContextSize; break; case "-RefreshRateInSeconds": int refreshRate; if (int.TryParse(args[i + 1], out refreshRate) == false || refreshRate < 1) { return(VoronRecoveryArgsProcessStatus.InvalidRefreshRate); } config.ProgressIntervalInSec = refreshRate; break; case "-DisableCopyOnWriteMode": bool disableCopyOnWriteMode; if (bool.TryParse(args[i + 1], out disableCopyOnWriteMode) == false) { return(VoronRecoveryArgsProcessStatus.BadArg); } config.DisableCopyOnWriteMode = disableCopyOnWriteMode; break; case "-LogMode": LogMode mode; if (Enum.TryParse(args[i + 1], out mode) == false) { return(VoronRecoveryArgsProcessStatus.BadArg); } config.LoggingMode = mode; break; default: return(VoronRecoveryArgsProcessStatus.BadArg); } } return(VoronRecoveryArgsProcessStatus.Success); }
public static void Main(string[] args) { VoronRecoveryConfiguration config; switch (VoronRecoveryConfiguration.ProcessArgs(args, out config)) { case VoronRecoveryConfiguration.VoronRecoveryArgsProcessStatus.Success: break; case VoronRecoveryConfiguration.VoronRecoveryArgsProcessStatus.NotEnoughArguments: PrintUsage(); return; case VoronRecoveryConfiguration.VoronRecoveryArgsProcessStatus.MissingDataFile: PrintUsage(); return; case VoronRecoveryConfiguration.VoronRecoveryArgsProcessStatus.CantWriteToOutputDirectory: PrintUsage(); return; case VoronRecoveryConfiguration.VoronRecoveryArgsProcessStatus.WrongNumberOfArgs: Console.WriteLine($"The given amount of args doesn't dived by 2 like expected.{Environment.NewLine}"); goto default; case VoronRecoveryConfiguration.VoronRecoveryArgsProcessStatus.InvalidPageSize: Console.WriteLine($"Page size should be a positive number.{Environment.NewLine}"); goto default; case VoronRecoveryConfiguration.VoronRecoveryArgsProcessStatus.InvalidTableValueCount: Console.WriteLine($"Table value count should be a positive number.{Environment.NewLine}"); goto default; case VoronRecoveryConfiguration.VoronRecoveryArgsProcessStatus.InvalidContextSize: Console.WriteLine($"Context size should be a positive number.{Environment.NewLine}"); goto default; case VoronRecoveryConfiguration.VoronRecoveryArgsProcessStatus.InvalidLongLivedContextSize: Console.WriteLine($"Long lived size should be a positive number.{Environment.NewLine}"); goto default; case VoronRecoveryConfiguration.VoronRecoveryArgsProcessStatus.InvalidRefreshRate: Console.WriteLine($"Refresh rate should be a positive number.{Environment.NewLine}"); goto default; case VoronRecoveryConfiguration.VoronRecoveryArgsProcessStatus.BadArg: Console.WriteLine($"Unexpected argument provided.{Environment.NewLine}"); goto default; default: PrintUsage(); return; } var recovery = new Recovery(config); var cts = new CancellationTokenSource(); Console.WriteLine("Press 'q' to quit the recovery process"); var cancellationTask = Task.Factory.StartNew(() => { while (Console.Read() != 'q') { } cts.Cancel(); //The reason i do an exit here is because if we are in the middle of journal recovery //we can't cancel it and it may take a long time. //That said i'm still going to give it a while to do a proper exit Task.Delay(5000).ContinueWith(_ => { Environment.Exit(1); }); }, cts.Token); recovery.Execute(cts.Token); cts.Cancel(); }