Example #1
0
        public static int RealMain(string[] _args)
        {
            //If we are on Windows, append the bundled "win-tools" programs to the search path
            //We add it last, to allow the user to override with other versions
            if (Platform.IsClientWindows)
            {
                Environment.SetEnvironmentVariable("PATH",
                                                   Environment.GetEnvironmentVariable("PATH") +
                                                   System.IO.Path.PathSeparator.ToString() +
                                                   System.IO.Path.Combine(
                                                       System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location),
                                                       "win-tools")
                                                   );
            }

            //If this executable is invoked directly, write to console, otherwise throw exceptions
            var writeConsole = System.Reflection.Assembly.GetEntryAssembly() == System.Reflection.Assembly.GetExecutingAssembly();

            //Find commandline options here for handling special startup cases
            var args               = new List <string>(_args);
            var argtuple           = Library.Utility.FilterCollector.ExtractOptions(new List <string>(args));
            var commandlineOptions = argtuple.Item1;
            var filter             = argtuple.Item2;

            foreach (var s in _args)
            {
                if (
                    s.Equals("help", StringComparison.OrdinalIgnoreCase) ||
                    s.Equals("/help", StringComparison.OrdinalIgnoreCase) ||
                    s.Equals("usage", StringComparison.OrdinalIgnoreCase) ||
                    s.Equals("/usage", StringComparison.OrdinalIgnoreCase))
                {
                    commandlineOptions["help"] = "";
                }
            }


            if (commandlineOptions.ContainsKey("tempdir") && !string.IsNullOrEmpty(commandlineOptions["tempdir"]))
            {
                Library.Utility.SystemContextSettings.DefaultTempPath = commandlineOptions["tempdir"];
            }

            Library.Utility.SystemContextSettings.StartSession();

            // Check if a parameters-file was provided. Skip if help was already specified
            if (!commandlineOptions.ContainsKey("help"))
            {
                // try and parse all parameter file aliases
                foreach (string parameterOption in new[] { "parameters-file", "parameters-file", "parameterfile" })
                {
                    if (commandlineOptions.ContainsKey(parameterOption) && !string.IsNullOrEmpty(commandlineOptions[parameterOption]))
                    {
                        string filename = commandlineOptions[parameterOption];
                        commandlineOptions.Remove(parameterOption);
                        if (!ReadOptionsFromFile(filename, ref filter, args, commandlineOptions))
                        {
                            return(100);
                        }
                        break;
                    }
                }
            }

            //If the commandline issues --help, just stop here
            if (commandlineOptions.ContainsKey("help"))
            {
                if (writeConsole)
                {
                    Console.WriteLine(Strings.Program.HelpDisplayDialog);

                    foreach (Library.Interface.ICommandLineArgument arg in SupportedCommands)
                    {
                        Console.WriteLine(Strings.Program.HelpDisplayFormat(arg.Name, arg.LongDescription));
                    }

                    return(0);
                }

                throw new Exception("Server invoked with --help");
            }

#if DEBUG
            //Log various information in the logfile
            if (!commandlineOptions.ContainsKey("log-file"))
            {
                commandlineOptions["log-file"]  = System.IO.Path.Combine(StartupPath, "Duplicati.debug.log");
                commandlineOptions["log-level"] = Duplicati.Library.Logging.LogMessageType.Profiling.ToString();
            }
#endif

            try
            {
                // Setup the log redirect
                var logscope = Library.Logging.Log.StartScope(Program.LogHandler, null);

                if (commandlineOptions.ContainsKey("log-file"))
                {
#if DEBUG
                    // Only delete the --log-file when in DEBUG mode. Otherwise, don't delete and just append logs.
                    if (System.IO.File.Exists(commandlineOptions["log-file"]))
                    {
                        System.IO.File.Delete(commandlineOptions["log-file"]);
                    }
#endif

                    var loglevel = Duplicati.Library.Logging.LogMessageType.Error;

                    if (commandlineOptions.ContainsKey("log-level"))
                    {
                        Enum.TryParse <Duplicati.Library.Logging.LogMessageType>(commandlineOptions["log-level"], true, out loglevel);
                    }

                    Program.LogHandler.SetServerFile(commandlineOptions["log-file"], loglevel);
                }

                DataConnection = GetDatabaseConnection(commandlineOptions);

                if (!DataConnection.ApplicationSettings.FixedInvalidBackupId)
                {
                    DataConnection.FixInvalidBackupId();
                }

                try
                {
                    //This will also create DATAFOLDER if it does not exist
                    Instance = new SingleInstance(DataFolder);
                }
                catch (Exception ex)
                {
                    if (writeConsole)
                    {
                        Console.WriteLine(Strings.Program.StartupFailure(ex));
                        return(200);
                    }

                    throw new Exception(Strings.Program.StartupFailure(ex));
                }

                if (!Instance.IsFirstInstance)
                {
                    if (writeConsole)
                    {
                        Console.WriteLine(Strings.Program.AnotherInstanceDetected);
                        return(200);
                    }

                    throw new SingleInstance.MultipleInstanceException(Strings.Program.AnotherInstanceDetected);
                }

                StartOrStopUsageReporter();

                if (commandlineOptions.ContainsKey("webservice-password"))
                {
                    Program.DataConnection.ApplicationSettings.SetWebserverPassword(commandlineOptions["webservice-password"]);
                }

                Program.DataConnection.ApplicationSettings.GenerateWebserverPasswordTrayIcon();

                if (commandlineOptions.ContainsKey("webservice-allowed-hostnames"))
                {
                    Program.DataConnection.ApplicationSettings.SetAllowedHostnames(commandlineOptions["webservice-allowed-hostnames"]);
                }


                ApplicationExitEvent = new System.Threading.ManualResetEvent(false);

                Duplicati.Library.AutoUpdater.UpdaterManager.OnError += (Exception obj) =>
                {
                    Program.DataConnection.LogError(null, "Error in updater", obj);
                };

                UpdatePoller = new UpdatePollThread();
                DateTime lastPurge = new DateTime(0);

                System.Threading.TimerCallback purgeTempFilesCallback = (x) => {
                    try
                    {
                        if (Math.Abs((DateTime.Now - lastPurge).TotalHours) < 23)
                        {
                            return;
                        }

                        lastPurge = DateTime.Now;

                        foreach (var e in Program.DataConnection.GetTempFiles().Where((f) => f.Expires < DateTime.Now))
                        {
                            try
                            {
                                if (System.IO.File.Exists(e.Path))
                                {
                                    System.IO.File.Delete(e.Path);
                                }
                            }
                            catch (Exception ex)
                            {
                                Program.DataConnection.LogError(null, string.Format("Failed to delete temp file: {0}", e.Path), ex);
                            }

                            Program.DataConnection.DeleteTempFile(e.ID);
                        }


                        Duplicati.Library.Utility.TempFile.RemoveOldApplicationTempFiles((path, ex) => {
                            Program.DataConnection.LogError(null, string.Format("Failed to delete temp file: {0}", path), ex);
                        });

                        string pts;
                        if (!commandlineOptions.TryGetValue("log-retention", out pts))
                        {
                            pts = DEFAULT_LOG_RETENTION;
                        }

                        Program.DataConnection.PurgeLogData(Library.Utility.Timeparser.ParseTimeInterval(pts, DateTime.Now, true));
                    }
                    catch (Exception ex)
                    {
                        Program.DataConnection.LogError(null, "Failed during temp file cleanup", ex);
                    }
                };

                try
                {
                    PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromDays(1));
                }
                catch (ArgumentOutOfRangeException)
                {
                    //Bugfix for older Mono, slightly more resources used to avoid large values in the period field
                    PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
                }

                LiveControl = new LiveControls(DataConnection.ApplicationSettings);
                LiveControl.StateChanged          += new EventHandler(LiveControl_StateChanged);
                LiveControl.ThreadPriorityChanged += new EventHandler(LiveControl_ThreadPriorityChanged);
                LiveControl.ThrottleSpeedChanged  += new EventHandler(LiveControl_ThrottleSpeedChanged);

                Program.WorkThread = new Duplicati.Library.Utility.WorkerThread <Runner.IRunnerData>((x) =>
                {
                    Runner.Run(x, true);
                }, LiveControl.State == LiveControls.LiveControlState.Paused);
                Program.Scheduler = new Scheduler(WorkThread);

                Program.WorkThread.StartingWork     += (worker, task) => { SignalNewEvent(null, null); };
                Program.WorkThread.CompletedWork    += (worker, task) => { SignalNewEvent(null, null); };
                Program.WorkThread.WorkQueueChanged += (worker) => { SignalNewEvent(null, null); };
                Program.Scheduler.NewSchedule       += new EventHandler(SignalNewEvent);
                Program.WorkThread.OnError          += (worker, task, exception) => { Program.DataConnection.LogError(task?.BackupID, "Error in worker", exception); };

                var lastscheduleid = LastDataUpdateID;
                Program.StatusEventNotifyer.NewEvent += (sender, e) =>
                {
                    if (lastscheduleid != LastDataUpdateID)
                    {
                        lastscheduleid = LastDataUpdateID;
                        Program.Scheduler.Reschedule();
                    }
                };

                Action <long, Exception> registerTaskResult = (id, ex) => {
                    lock (Program.MainLock) {
                        // If the new results says it crashed, we store that instead of success
                        if (Program.TaskResultCache.Count > 0 && Program.TaskResultCache.Last().Key == id)
                        {
                            if (ex != null && Program.TaskResultCache.Last().Value == null)
                            {
                                Program.TaskResultCache.RemoveAt(Program.TaskResultCache.Count - 1);
                            }
                            else
                            {
                                return;
                            }
                        }

                        Program.TaskResultCache.Add(new KeyValuePair <long, Exception>(id, ex));
                        while (Program.TaskResultCache.Count > MAX_TASK_RESULT_CACHE_SIZE)
                        {
                            Program.TaskResultCache.RemoveAt(0);
                        }
                    }
                };

                Program.WorkThread.CompletedWork += (worker, task) => { registerTaskResult(task.TaskID, null); };
                Program.WorkThread.OnError       += (worker, task, exception) => { registerTaskResult(task.TaskID, exception); };


                Program.WebServer = new WebServer.Server(commandlineOptions);

                ServerPortChanged |= Program.WebServer.Port != DataConnection.ApplicationSettings.LastWebserverPort;
                DataConnection.ApplicationSettings.LastWebserverPort = Program.WebServer.Port;

                if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, "ping-pong-keepalive"))
                {
                    Program.PingPongThread = new System.Threading.Thread(PingPongMethod);
                    Program.PingPongThread.IsBackground = true;
                    Program.PingPongThread.Start();
                }

                ServerStartedEvent.Set();
                ApplicationExitEvent.WaitOne();
            }
            catch (SingleInstance.MultipleInstanceException mex)
            {
                System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(mex.ToString()));
                if (writeConsole)
                {
                    Console.WriteLine(Strings.Program.SeriousError(mex.ToString()));
                    return(100);
                }
                else
                {
                    throw mex;
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(ex.ToString()));
                if (writeConsole)
                {
                    Console.WriteLine(Strings.Program.SeriousError(ex.ToString()));
                    return(100);
                }
                else
                {
                    throw new Exception(Strings.Program.SeriousError(ex.ToString()), ex);
                }
            }
            finally
            {
                StatusEventNotifyer.SignalNewEvent();

                if (UpdatePoller != null)
                {
                    UpdatePoller.Terminate();
                }
                if (Scheduler != null)
                {
                    Scheduler.Terminate(true);
                }
                if (WorkThread != null)
                {
                    WorkThread.Terminate(true);
                }
                if (Instance != null)
                {
                    Instance.Dispose();
                }
                if (PurgeTempFilesTimer != null)
                {
                    PurgeTempFilesTimer.Dispose();
                }

                Library.UsageReporter.Reporter.ShutDown();

                if (PingPongThread != null)
                {
                    try { PingPongThread.Abort(); }
                    catch { }
                }

                if (LogHandler != null)
                {
                    LogHandler.Dispose();
                }
            }

            if (UpdatePoller != null && UpdatePoller.IsUpdateRequested)
            {
                return(Library.AutoUpdater.UpdaterManager.MAGIC_EXIT_CODE);
            }

            return(0);
        }
Example #2
0
        public static void RealMain(string[] args)
        {
            //If we are on Windows, append the bundled "win-tools" programs to the search path
            //We add it last, to allow the user to override with other versions
            if (!Library.Utility.Utility.IsClientLinux)
            {
                Environment.SetEnvironmentVariable("PATH",
                                                   Environment.GetEnvironmentVariable("PATH") +
                                                   System.IO.Path.PathSeparator.ToString() +
                                                   System.IO.Path.Combine(
                                                       System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location),
                                                       "win-tools")
                                                   );
            }

            //If this executable is invoked directly, write to console, otherwise throw exceptions
            bool writeConsole = System.Reflection.Assembly.GetEntryAssembly() == System.Reflection.Assembly.GetExecutingAssembly();

            //If we are on windows we encrypt the database by default
            //We do not encrypt on Linux as most distros use a SQLite library without encryption support,
            //Linux users can use an encrypted home folder, or install a SQLite library with encryption support
            if (!Library.Utility.Utility.IsClientLinux && string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DB_KEY_ENV_NAME)))
            {
                //Note that the password here is a default password and public knowledge
                //
                //The purpose of this is to prevent casual read of the database, as well
                // as protect from harddisk string scans, not to protect from determined
                // attacks.
                //
                //If you desire better security, start Duplicati once with the commandline option
                // --unencrypted-database to decrypt the database.
                //Then set the environment variable DUPLICATI_DB_KEY to the desired key,
                // and run Duplicati again without the --unencrypted-database option
                // to re-encrypt it with the new key
                //
                //If you change the key, please note that you need to supply the same
                // key when restoring the setup, as the setup being backed up will
                // be encrypted as well.
                Environment.SetEnvironmentVariable(DB_KEY_ENV_NAME, Library.AutoUpdater.AutoUpdateSettings.AppName + "_Key_42");
            }


            //Find commandline options here for handling special startup cases
            Dictionary <string, string> commandlineOptions = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(new List <string>(args));

            foreach (string s in args)
            {
                if (
                    s.Equals("help", StringComparison.InvariantCultureIgnoreCase) ||
                    s.Equals("/help", StringComparison.InvariantCultureIgnoreCase) ||
                    s.Equals("usage", StringComparison.InvariantCultureIgnoreCase) ||
                    s.Equals("/usage", StringComparison.InvariantCultureIgnoreCase))
                {
                    commandlineOptions["help"] = "";
                }
            }

            //If the commandline issues --help, just stop here
            if (commandlineOptions.ContainsKey("help"))
            {
                if (writeConsole)
                {
                    Console.WriteLine(Strings.Program.HelpDisplayDialog);

                    foreach (Library.Interface.ICommandLineArgument arg in SupportedCommands)
                    {
                        Console.WriteLine(Strings.Program.HelpDisplayFormat(arg.Name, arg.LongDescription));
                    }

                    return;
                }
                else
                {
                    throw new Exception("Server invoked with --help");
                }
            }

#if DEBUG
            //Log various information in the logfile
            if (!commandlineOptions.ContainsKey("log-file"))
            {
                commandlineOptions["log-file"]  = System.IO.Path.Combine(StartupPath, "Duplicati.debug.log");
                commandlineOptions["log-level"] = Duplicati.Library.Logging.LogMessageType.Profiling.ToString();
            }
#endif
            // Allow override of the environment variables from the commandline
            if (commandlineOptions.ContainsKey("server-datafolder"))
            {
                Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, commandlineOptions["server-datafolder"]);
            }
            if (commandlineOptions.ContainsKey("server-encryption-key"))
            {
                Environment.SetEnvironmentVariable(DB_KEY_ENV_NAME, commandlineOptions["server-encryption-key"]);
            }

            //Set the %DUPLICATI_HOME% env variable, if it is not already set
            if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DATAFOLDER_ENV_NAME)))
            {
#if DEBUG
                //debug mode uses a lock file located in the app folder
                Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, StartupPath);
#else
                bool portableMode = commandlineOptions.ContainsKey("portable-mode") ? Library.Utility.Utility.ParseBool(commandlineOptions["portable-mode"], true) : false;

                if (portableMode)
                {
                    //Portable mode uses a data folder in the application home dir
                    Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, System.IO.Path.Combine(StartupPath, "data"));
                }
                else
                {
                    //Normal release mode uses the systems "Application Data" folder
                    Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Library.AutoUpdater.AutoUpdateSettings.AppName));
                }
#endif
            }

            try
            {
                try
                {
                    //This will also create Program.DATAFOLDER if it does not exist
                    Instance = new SingleInstance(Duplicati.Library.AutoUpdater.AutoUpdateSettings.AppName, Program.DATAFOLDER);
                }
                catch (Exception ex)
                {
                    if (writeConsole)
                    {
                        Console.WriteLine(Strings.Program.StartupFailure(ex));
                        return;
                    }
                    else
                    {
                        throw new Exception(Strings.Program.StartupFailure(ex));
                    }
                }

                if (!Instance.IsFirstInstance)
                {
                    if (writeConsole)
                    {
                        Console.WriteLine(Strings.Program.AnotherInstanceDetected);
                        return;
                    }
                    else
                    {
                        throw new SingleInstance.MultipleInstanceException(Strings.Program.AnotherInstanceDetected);
                    }
                }

                // Setup the log redirect
                Duplicati.Library.Logging.Log.CurrentLog = Program.LogHandler;

                if (commandlineOptions.ContainsKey("log-file"))
                {
                    if (System.IO.File.Exists(commandlineOptions["log-file"]))
                    {
                        System.IO.File.Delete(commandlineOptions["log-file"]);
                    }

                    var loglevel = Duplicati.Library.Logging.LogMessageType.Error;

                    if (commandlineOptions.ContainsKey("log-level"))
                    {
                        Enum.TryParse <Duplicati.Library.Logging.LogMessageType>(commandlineOptions["log-level"], true, out loglevel);
                    }

                    Program.LogHandler.SetServerFile(commandlineOptions["log-file"], loglevel);
                }

                Version sqliteVersion = new Version((string)Duplicati.Library.SQLiteHelper.SQLiteLoader.SQLiteConnectionType.GetProperty("SQLiteVersion").GetValue(null, null));
                if (sqliteVersion < new Version(3, 6, 3))
                {
                    if (writeConsole)
                    {
                        //The official Mono SQLite provider is also broken with less than 3.6.3
                        Console.WriteLine(Strings.Program.WrongSQLiteVersion(sqliteVersion, "3.6.3"));
                        return;
                    }
                    else
                    {
                        throw new Exception(Strings.Program.WrongSQLiteVersion(sqliteVersion, "3.6.3"));
                    }
                }

                //Create the connection instance
                System.Data.IDbConnection con = (System.Data.IDbConnection)Activator.CreateInstance(Duplicati.Library.SQLiteHelper.SQLiteLoader.SQLiteConnectionType);

                try
                {
                    DatabasePath = System.IO.Path.Combine(Program.DATAFOLDER, "Duplicati-server.sqlite");
                    if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(DatabasePath)))
                    {
                        System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(DatabasePath));
                    }

#if DEBUG
                    //Default is to not use encryption for debugging
                    Program.UseDatabaseEncryption = commandlineOptions.ContainsKey("unencrypted-database") ? !Library.Utility.Utility.ParseBool(commandlineOptions["unencrypted-database"], true) : false;
#else
                    Program.UseDatabaseEncryption = commandlineOptions.ContainsKey("unencrypted-database") ? !Library.Utility.Utility.ParseBool(commandlineOptions["unencrypted-database"], true) : true;
#endif
                    con.ConnectionString = "Data Source=" + DatabasePath;

                    //Attempt to open the database, handling any encryption present
                    OpenDatabase(con);

                    Duplicati.Library.SQLiteHelper.DatabaseUpgrader.UpgradeDatabase(con, DatabasePath, typeof(Duplicati.Server.Database.Connection));
                }
                catch (Exception ex)
                {
                    //Unwrap the reflection exceptions
                    if (ex is System.Reflection.TargetInvocationException && ex.InnerException != null)
                    {
                        ex = ex.InnerException;
                    }

                    if (writeConsole)
                    {
                        Console.WriteLine(Strings.Program.DatabaseOpenError(ex.Message));
                        return;
                    }
                    else
                    {
                        throw new Exception(Strings.Program.DatabaseOpenError(ex.Message), ex);
                    }
                }

                DataConnection = new Duplicati.Server.Database.Connection(con);

                if (!DataConnection.ApplicationSettings.FixedInvalidBackupId)
                {
                    DataConnection.FixInvalidBackupId();
                }

                StartOrStopUsageReporter();

                if (commandlineOptions.ContainsKey("webservice-password"))
                {
                    Program.DataConnection.ApplicationSettings.SetWebserverPassword(commandlineOptions["webservice-password"]);
                }

                ApplicationExitEvent = new System.Threading.ManualResetEvent(false);

                Duplicati.Library.AutoUpdater.UpdaterManager.OnError += (Exception obj) =>
                {
                    Program.DataConnection.LogError(null, "Error in updater", obj);
                };


                UpdatePoller = new UpdatePollThread();
                DateTime lastPurge = new DateTime(0);

                System.Threading.TimerCallback purgeTempFilesCallback = (x) => {
                    try
                    {
                        if (Math.Abs((DateTime.Now - lastPurge).TotalHours) < 23)
                        {
                            return;
                        }

                        lastPurge = DateTime.Now;

                        foreach (var e in Program.DataConnection.GetTempFiles().Where((f) => f.Expires < DateTime.Now))
                        {
                            try
                            {
                                if (System.IO.File.Exists(e.Path))
                                {
                                    System.IO.File.Delete(e.Path);
                                }
                            }
                            catch (Exception ex)
                            {
                                Program.DataConnection.LogError(null, string.Format("Failed to delete temp file: {0}", e.Path), ex);
                            }

                            Program.DataConnection.DeleteTempFile(e.ID);
                        }


                        Duplicati.Library.Utility.TempFile.RemoveOldApplicationTempFiles((path, ex) => {
                            Program.DataConnection.LogError(null, string.Format("Failed to delete temp file: {0}", path), ex);
                        });

                        string pts;
                        if (!commandlineOptions.TryGetValue("log-retention", out pts))
                        {
                            pts = DEFAULT_LOG_RETENTION;
                        }

                        Program.DataConnection.PurgeLogData(Library.Utility.Timeparser.ParseTimeInterval(pts, DateTime.Now, true));
                    }
                    catch (Exception ex)
                    {
                        Program.DataConnection.LogError(null, "Failed during temp file cleanup", ex);
                    }
                };

                try
                {
                    PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromDays(1));
                }
                catch (ArgumentOutOfRangeException)
                {
                    //Bugfix for older Mono, slightly more resources used to avoid large values in the period field
                    PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
                }

                LiveControl = new LiveControls(DataConnection.ApplicationSettings);
                LiveControl.StateChanged          += new EventHandler(LiveControl_StateChanged);
                LiveControl.ThreadPriorityChanged += new EventHandler(LiveControl_ThreadPriorityChanged);
                LiveControl.ThrottleSpeedChanged  += new EventHandler(LiveControl_ThrottleSpeedChanged);

                Program.WorkThread = new Duplicati.Library.Utility.WorkerThread <Runner.IRunnerData>((x) =>
                {
                    Runner.Run(x, true);
                }, LiveControl.State == LiveControls.LiveControlState.Paused);
                Program.Scheduler = new Scheduler(WorkThread);

                Program.WorkThread.StartingWork     += (worker, task) => { SignalNewEvent(null, null); };
                Program.WorkThread.CompletedWork    += (worker, task) => { SignalNewEvent(null, null); };
                Program.WorkThread.WorkQueueChanged += (worker) => { SignalNewEvent(null, null); };
                Program.Scheduler.NewSchedule       += new EventHandler(SignalNewEvent);
                Program.WorkThread.OnError          += (worker, task, exception) => { Program.DataConnection.LogError(task == null ? null : task.BackupID, "Error in worker", exception); };

                Action <long, Exception> registerTaskResult = (id, ex) => {
                    lock (Program.MainLock) {
                        // If the new results says it crashed, we store that instead of success
                        if (Program.TaskResultCache.Count > 0 && Program.TaskResultCache.Last().Key == id)
                        {
                            if (ex != null && Program.TaskResultCache.Last().Value == null)
                            {
                                Program.TaskResultCache.RemoveAt(Program.TaskResultCache.Count - 1);
                            }
                            else
                            {
                                return;
                            }
                        }

                        Program.TaskResultCache.Add(new KeyValuePair <long, Exception>(id, ex));
                        while (Program.TaskResultCache.Count > MAX_TASK_RESULT_CACHE_SIZE)
                        {
                            Program.TaskResultCache.RemoveAt(0);
                        }
                    }
                };

                Program.WorkThread.CompletedWork += (worker, task) => { registerTaskResult(task.TaskID, null); };
                Program.WorkThread.OnError       += (worker, task, exception) => { registerTaskResult(task.TaskID, exception); };


                Program.WebServer = new WebServer.Server(commandlineOptions);

                if (Program.WebServer.Port != DataConnection.ApplicationSettings.LastWebserverPort)
                {
                    ServerPortChanged = true;
                }
                DataConnection.ApplicationSettings.LastWebserverPort = Program.WebServer.Port;

                if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, "ping-pong-keepalive"))
                {
                    Program.PingPongThread = new System.Threading.Thread(PingPongMethod);
                    Program.PingPongThread.IsBackground = true;
                    Program.PingPongThread.Start();
                }

                ServerStartedEvent.Set();
                ApplicationExitEvent.WaitOne();
            }
            catch (SingleInstance.MultipleInstanceException mex)
            {
                System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(mex.ToString()));
                if (writeConsole)
                {
                    Console.WriteLine(Strings.Program.SeriousError(mex.ToString()));
                }
                else
                {
                    throw mex;
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(ex.ToString()));
                if (writeConsole)
                {
                    Console.WriteLine(Strings.Program.SeriousError(ex.ToString()));
                }
                else
                {
                    throw new Exception(Strings.Program.SeriousError(ex.ToString()), ex);
                }
            }
            finally
            {
                StatusEventNotifyer.SignalNewEvent();

                if (UpdatePoller != null)
                {
                    UpdatePoller.Terminate();
                }
                if (Scheduler != null)
                {
                    Scheduler.Terminate(true);
                }
                if (WorkThread != null)
                {
                    WorkThread.Terminate(true);
                }
                if (Instance != null)
                {
                    Instance.Dispose();
                }
                if (PurgeTempFilesTimer != null)
                {
                    PurgeTempFilesTimer.Dispose();
                }

                if (PingPongThread != null)
                {
                    try { PingPongThread.Abort(); }
                    catch { }
                }

                if (LogHandler != null)
                {
                    LogHandler.Dispose();
                }
            }
        }
Example #3
0
        public static void RealMain(string[] args)
        {
            //If we are on Windows, append the bundled "win-tools" programs to the search path
            //We add it last, to allow the user to override with other versions
            if (!Library.Utility.Utility.IsClientLinux)
            {
                Environment.SetEnvironmentVariable("PATH",
                    Environment.GetEnvironmentVariable("PATH") +
                    System.IO.Path.PathSeparator.ToString() +
                    System.IO.Path.Combine(
                        System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location),
                        "win-tools")
                );
            }

            //If this executable is invoked directly, write to console, otherwise throw exceptions
            bool writeConsole = System.Reflection.Assembly.GetEntryAssembly() == System.Reflection.Assembly.GetExecutingAssembly();

            //If we are on windows we encrypt the database by default
            //We do not encrypt on Linux as most distros use a SQLite library without encryption support,
            //Linux users can use an encrypted home folder, or install a SQLite library with encryption support
            if (!Library.Utility.Utility.IsClientLinux && string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DB_KEY_ENV_NAME)))
            {
                //Note that the password here is a default password and public knowledge
                //
                //The purpose of this is to prevent casual read of the database, as well
                // as protect from harddisk string scans, not to protect from determined
                // attacks.
                //
                //If you desire better security, start Duplicati once with the commandline option
                // --unencrypted-database to decrypt the database.
                //Then set the environment variable DUPLICATI_DB_KEY to the desired key,
                // and run Duplicati again without the --unencrypted-database option
                // to re-encrypt it with the new key
                //
                //If you change the key, please note that you need to supply the same
                // key when restoring the setup, as the setup being backed up will
                // be encrypted as well.
                Environment.SetEnvironmentVariable(DB_KEY_ENV_NAME, Library.AutoUpdater.AutoUpdateSettings.AppName + "_Key_42");
            }

            //Find commandline options here for handling special startup cases
            Dictionary<string, string> commandlineOptions = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(new List<string>(args));

            foreach(string s in args)
                if (
                    s.Equals("help", StringComparison.InvariantCultureIgnoreCase) ||
                    s.Equals("/help", StringComparison.InvariantCultureIgnoreCase) ||
                    s.Equals("usage", StringComparison.InvariantCultureIgnoreCase) ||
                    s.Equals("/usage", StringComparison.InvariantCultureIgnoreCase))
                    commandlineOptions["help"] = "";

            //If the commandline issues --help, just stop here
            if (commandlineOptions.ContainsKey("help"))
            {
                if (writeConsole)
                {
                    Console.WriteLine(Strings.Program.HelpDisplayDialog);

                    foreach(Library.Interface.ICommandLineArgument arg in SupportedCommands)
                        Console.WriteLine(Strings.Program.HelpDisplayFormat(arg.Name, arg.LongDescription));

                    return;
                }
                else
                {
                    throw new Exception("Server invoked with --help");
                }

            }

            #if DEBUG
            //Log various information in the logfile
            if (!commandlineOptions.ContainsKey("log-file"))
            {
                commandlineOptions["log-file"] = System.IO.Path.Combine(StartupPath, "Duplicati.debug.log");
                commandlineOptions["log-level"] = Duplicati.Library.Logging.LogMessageType.Profiling.ToString();
            }
            #endif
            // Allow override of the environment variables from the commandline
            if (commandlineOptions.ContainsKey("server-datafolder"))
                Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, commandlineOptions["server-datafolder"]);
            if (commandlineOptions.ContainsKey("server-encryption-key"))
                Environment.SetEnvironmentVariable(DB_KEY_ENV_NAME, commandlineOptions["server-encryption-key"]);

            //Set the %DUPLICATI_HOME% env variable, if it is not already set
            if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DATAFOLDER_ENV_NAME)))
            {
            #if DEBUG
                //debug mode uses a lock file located in the app folder
                Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, StartupPath);
            #else
                bool portableMode = commandlineOptions.ContainsKey("portable-mode") ? Library.Utility.Utility.ParseBool(commandlineOptions["portable-mode"], true) : false;

                if (portableMode)
                {
                    //Portable mode uses a data folder in the application home dir
                    Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, System.IO.Path.Combine(StartupPath, "data"));
                }
                else
                {
                    //Normal release mode uses the systems "Application Data" folder
                    Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Library.AutoUpdater.AutoUpdateSettings.AppName));
                }
            #endif
            }

            try
            {
                try
                {
                    //This will also create Program.DATAFOLDER if it does not exist
                    Instance = new SingleInstance(Duplicati.Library.AutoUpdater.AutoUpdateSettings.AppName, Program.DATAFOLDER);
                }
                catch (Exception ex)
                {
                    if (writeConsole)
                    {
                        Console.WriteLine(Strings.Program.StartupFailure(ex));
                        return;
                    }
                    else
                    {
                        throw new Exception(Strings.Program.StartupFailure(ex));
                    }
                }

                if (!Instance.IsFirstInstance)
                {
                    if (writeConsole)
                    {
                        Console.WriteLine(Strings.Program.AnotherInstanceDetected);
                        return;
                    }
                    else
                    {
                        throw new SingleInstance.MultipleInstanceException(Strings.Program.AnotherInstanceDetected);
                    }
                }

                // Setup the log redirect
                Duplicati.Library.Logging.Log.CurrentLog = Program.LogHandler;

                if (commandlineOptions.ContainsKey("log-file"))
                {
                    if (System.IO.File.Exists(commandlineOptions["log-file"]))
                        System.IO.File.Delete(commandlineOptions["log-file"]);

                    var loglevel = Duplicati.Library.Logging.LogMessageType.Error;

                    if (commandlineOptions.ContainsKey("log-level"))
                        Enum.TryParse<Duplicati.Library.Logging.LogMessageType>(commandlineOptions["log-level"], true, out loglevel);

                    Program.LogHandler.SetServerFile(commandlineOptions["log-file"], loglevel);
                }

                Version sqliteVersion = new Version((string)Duplicati.Library.SQLiteHelper.SQLiteLoader.SQLiteConnectionType.GetProperty("SQLiteVersion").GetValue(null, null));
                if (sqliteVersion < new Version(3, 6, 3))
                {
                    if (writeConsole)
                    {
                        //The official Mono SQLite provider is also broken with less than 3.6.3
                        Console.WriteLine(Strings.Program.WrongSQLiteVersion(sqliteVersion, "3.6.3"));
                        return;
                    }
                    else
                    {
                        throw new Exception(Strings.Program.WrongSQLiteVersion(sqliteVersion, "3.6.3"));
                    }
                }

                //Create the connection instance
                System.Data.IDbConnection con = (System.Data.IDbConnection)Activator.CreateInstance(Duplicati.Library.SQLiteHelper.SQLiteLoader.SQLiteConnectionType);

                try
                {
                    DatabasePath = System.IO.Path.Combine(Program.DATAFOLDER, "Duplicati-server.sqlite");
                    if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(DatabasePath)))
                        System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(DatabasePath));

            #if DEBUG
                    //Default is to not use encryption for debugging
                    Program.UseDatabaseEncryption = commandlineOptions.ContainsKey("unencrypted-database") ? !Library.Utility.Utility.ParseBool(commandlineOptions["unencrypted-database"], true) : false;
            #else
                    Program.UseDatabaseEncryption = commandlineOptions.ContainsKey("unencrypted-database") ? !Library.Utility.Utility.ParseBool(commandlineOptions["unencrypted-database"], true) : true;
            #endif
                    con.ConnectionString = "Data Source=" + DatabasePath;

                    //Attempt to open the database, handling any encryption present
                    OpenDatabase(con);

                    Duplicati.Library.SQLiteHelper.DatabaseUpgrader.UpgradeDatabase(con, DatabasePath, typeof(Duplicati.Server.Database.Connection));
                }
                catch (Exception ex)
                {
                    //Unwrap the reflection exceptions
                    if (ex is System.Reflection.TargetInvocationException && ex.InnerException != null)
                        ex = ex.InnerException;

                    if (writeConsole)
                    {
                        Console.WriteLine(Strings.Program.DatabaseOpenError(ex.Message));
                        return;
                    }
                    else
                    {
                        throw new Exception(Strings.Program.DatabaseOpenError(ex.Message), ex);
                    }
                }

                DataConnection = new Duplicati.Server.Database.Connection(con);

                if (!DataConnection.ApplicationSettings.FixedInvalidBackupId)
                    DataConnection.FixInvalidBackupId();

                if (commandlineOptions.ContainsKey("webservice-password"))
                    Program.DataConnection.ApplicationSettings.SetWebserverPassword(commandlineOptions["webservice-password"]);

                ApplicationExitEvent = new System.Threading.ManualResetEvent(false);

                Duplicati.Library.AutoUpdater.UpdaterManager.OnError += (Exception obj) =>
                {
                    Program.DataConnection.LogError(null, "Error in updater", obj);
                };

                UpdatePoller = new UpdatePollThread();
                DateTime lastPurge = new DateTime(0);

                System.Threading.TimerCallback purgeTempFilesCallback = (x) => {
                    try
                    {
                        if (Math.Abs((DateTime.Now - lastPurge).TotalHours) < 23)
                            return;

                        lastPurge = DateTime.Now;

                        foreach(var e in Program.DataConnection.GetTempFiles().Where((f) => f.Expires < DateTime.Now))
                        {
                            try
                            {
                                if (System.IO.File.Exists(e.Path))
                                    System.IO.File.Delete(e.Path);
                            }
                            catch (Exception ex)
                            {
                                Program.DataConnection.LogError(null, string.Format("Failed to delete temp file: {0}", e.Path), ex);
                            }

                            Program.DataConnection.DeleteTempFile(e.ID);
                        }

                        Duplicati.Library.Utility.TempFile.RemoveOldApplicationTempFiles((path, ex) => {
                            Program.DataConnection.LogError(null, string.Format("Failed to delete temp file: {0}", path), ex);
                        });

                        string pts;
                        if (!commandlineOptions.TryGetValue("log-retention", out pts))
                            pts = DEFAULT_LOG_RETENTION;

                        Program.DataConnection.PurgeLogData(Library.Utility.Timeparser.ParseTimeInterval(pts, DateTime.Now, true));
                    }
                    catch (Exception ex)
                    {
                        Program.DataConnection.LogError(null, "Failed during temp file cleanup", ex);
                    }
                };

                try
                {
                    PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromDays(1));
                }
                catch (ArgumentOutOfRangeException)
                {
                    //Bugfix for older Mono, slightly more resources used to avoid large values in the period field
                    PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
                }

                LiveControl = new LiveControls(DataConnection.ApplicationSettings);
                LiveControl.StateChanged += new EventHandler(LiveControl_StateChanged);
                LiveControl.ThreadPriorityChanged += new EventHandler(LiveControl_ThreadPriorityChanged);
                LiveControl.ThrottleSpeedChanged += new EventHandler(LiveControl_ThrottleSpeedChanged);

                Program.WorkThread = new Duplicati.Library.Utility.WorkerThread<Runner.IRunnerData>((x) =>
                {
                    Runner.Run(x, true);
                }, LiveControl.State == LiveControls.LiveControlState.Paused);
                Program.Scheduler = new Scheduler(WorkThread);

                Program.WorkThread.StartingWork += (worker, task) => { SignalNewEvent(null, null); };
                Program.WorkThread.CompletedWork += (worker, task) => { SignalNewEvent(null, null); };
                Program.WorkThread.WorkQueueChanged += (worker) => { SignalNewEvent(null, null); };
                Program.Scheduler.NewSchedule += new EventHandler(SignalNewEvent);
                Program.WorkThread.OnError += (worker, task, exception) => { Program.DataConnection.LogError(task == null ? null : task.BackupID, "Error in worker", exception); };

                Action<long, Exception> registerTaskResult = (id, ex) => {
                    lock(Program.MainLock) {

                        // If the new results says it crashed, we store that instead of success
                        if (Program.TaskResultCache.Count > 0 && Program.TaskResultCache.Last().Key == id)
                        {
                            if (ex != null && Program.TaskResultCache.Last().Value == null)
                                Program.TaskResultCache.RemoveAt(Program.TaskResultCache.Count - 1);
                            else
                                return;
                        }

                        Program.TaskResultCache.Add(new KeyValuePair<long, Exception>(id, ex));
                        while(Program.TaskResultCache.Count > MAX_TASK_RESULT_CACHE_SIZE)
                            Program.TaskResultCache.RemoveAt(0);
                    }
                };

                Program.WorkThread.CompletedWork += (worker, task) => { registerTaskResult(task.TaskID, null); };
                Program.WorkThread.OnError += (worker, task, exception) => { registerTaskResult(task.TaskID, exception); };

                Program.WebServer = new WebServer.Server(commandlineOptions);

                if (Program.WebServer.Port != DataConnection.ApplicationSettings.LastWebserverPort)
                    ServerPortChanged = true;
                DataConnection.ApplicationSettings.LastWebserverPort = Program.WebServer.Port;

                if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, "ping-pong-keepalive"))
                {
                    Program.PingPongThread = new System.Threading.Thread(PingPongMethod);
                    Program.PingPongThread.IsBackground = true;
                    Program.PingPongThread.Start();
                }

                ServerStartedEvent.Set();
                ApplicationExitEvent.WaitOne();
            }
            catch (SingleInstance.MultipleInstanceException mex)
            {
                System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(mex.ToString()));
                if (writeConsole)
                    Console.WriteLine(Strings.Program.SeriousError(mex.ToString()));
                else
                    throw mex;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(ex.ToString()));
                if (writeConsole)
                    Console.WriteLine(Strings.Program.SeriousError(ex.ToString()));
                else
                    throw new Exception(Strings.Program.SeriousError(ex.ToString()), ex);
            }
            finally
            {
                StatusEventNotifyer.SignalNewEvent();

                if (UpdatePoller != null)
                    UpdatePoller.Terminate();
                if (Scheduler != null)
                    Scheduler.Terminate(true);
                if (WorkThread != null)
                    WorkThread.Terminate(true);
                if (Instance != null)
                    Instance.Dispose();
                if (PurgeTempFilesTimer != null)
                    PurgeTempFilesTimer.Dispose();

                if (PingPongThread != null)
                    try { PingPongThread.Abort(); }
                    catch { }

                if (LogHandler != null)
                    LogHandler.Dispose();

            }
        }