source: trunk/eraser6/Eraser/Program.cs @ 1819

Revision 1819, 21.3 KB checked in by lowjoel, 5 years ago (diff)

Follow the old behaviour -- without specifying /schedule, we assume the task is meant to run immediately.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Windows.Forms;
25
26using System.IO;
27using System.Threading;
28using System.Globalization;
29using System.ComponentModel;
30using System.Runtime.Serialization;
31using System.Security.Principal;
32using System.Text.RegularExpressions;
33
34using System.Reflection;
35using System.Diagnostics;
36
37using ComLib.Arguments;
38
39using Eraser.Manager;
40using Eraser.Util;
41
42namespace Eraser
43{
44    internal static partial class Program
45    {
46        /// <summary>
47        /// The common program arguments shared between the GUI and console programs.
48        /// </summary>
49        class Arguments
50        {
51            /// <summary>
52            /// True if the program should not be started with any user-visible interfaces.
53            /// </summary>
54            /// <remarks>Errors will also be silently ignored.</remarks>
55            [Arg("quiet", "The program should not be started with any user-visible interfaces. " +
56                "Errors will be silently ignored.", typeof(bool), false, false, null)]
57            public bool Quiet { get; set; }
58        }
59
60        /// <summary>
61        /// Program arguments which only apply to the GUI program.
62        /// </summary>
63        class GuiArguments : Arguments
64        {
65            /// <summary>
66            /// True if the command line specified atRestart, which should result in the
67            /// queueing of tasks meant for running at restart.
68            /// </summary>
69            [Arg("atRestart", "The program should queue all tasks scheduled for running at " +
70                "the system restart.", typeof(bool), false, false, null)]
71            public bool AtRestart { get; set; }
72        }
73
74        class ConsoleArguments : Arguments
75        {
76            /// <summary>
77            /// The Action which this handler is in charge of.
78            /// </summary>
79            [Arg(0, "The action this command line is stating.", typeof(string), true, null, null)]
80            public string Action { get; set; }
81
82            /// <summary>
83            /// The list of command line parameters not placed in a switch.
84            /// </summary>
85            public List<string> PositionalArguments { get; set; }
86        }
87
88        class AddTaskArguments : ConsoleArguments
89        {
90            /// <summary>
91            /// The erasure method which the user specified on the command line.
92            /// </summary>
93            [Arg("method", "The erasure method to use", typeof(Guid), false, null, null)]
94            public Guid ErasureMethod { get; set; }
95
96            /// <summary>
97            /// The schedule for the current set of targets.
98            /// </summary>
99            [Arg("schedule", "The schedule to use", typeof(Schedule), false, null, null)]
100            public string Schedule { get; set; }
101        }
102
103        /// <summary>
104        /// The main entry point for the application.
105        /// </summary>
106        [STAThread]
107        static int Main(string[] commandLine)
108        {
109            //Initialise our crash handler
110            BlackBox blackBox = BlackBox.Get();
111
112            //Immediately parse command line arguments
113            ComLib.BoolMessageItem argumentParser = Args.Parse(commandLine,
114                CommandLinePrefixes, CommandLineSeparators);
115            Args parsedArguments = (Args)argumentParser.Item;
116
117            //We default to a GUI if:
118            // - The parser did not succeed.
119            // - The parser resulted in an empty arguments list
120            // - The parser's argument at index 0 is not equal to the first argument (this
121            //   is when the user is passing GUI options -- command line options always
122            //   start with the action, e.g. Eraser help, or Eraser addtask
123            if (!argumentParser.Success || parsedArguments.IsEmpty ||
124                parsedArguments.Positional.Count == 0 ||
125                parsedArguments.Positional[0] != parsedArguments.Raw[0])
126            {
127                GUIMain(commandLine);
128            }
129            else
130            {
131                return CommandMain(commandLine);
132            }
133
134            //Return zero to signify success
135            return 0;
136        }
137
138        #region Console Program code
139        /// <summary>
140        /// Runs Eraser as a command-line application.
141        /// </summary>
142        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
143        private static int CommandMain(string[] commandLine)
144        {
145            using (ConsoleProgram program = new ConsoleProgram(commandLine))
146            using (ManagerLibrary library = new ManagerLibrary(new Settings()))
147                try
148                {
149                    program.Handlers.Add("help",
150                        new ConsoleActionData(CommandHelp, new ConsoleArguments()));
151                    program.Handlers.Add("querymethods",
152                        new ConsoleActionData(CommandQueryMethods, new ConsoleArguments()));
153                    program.Handlers.Add("addtask",
154                        new ConsoleActionData(CommandAddTask, new AddTaskArguments()));
155                    program.Handlers.Add("importtasklist",
156                        new ConsoleActionData(CommandImportTaskList, new ConsoleArguments()));
157                    program.Run();
158                    return 0;
159                }
160                catch (UnauthorizedAccessException)
161                {
162                    return Win32ErrorCode.AccessDenied;
163                }
164                catch (Win32Exception e)
165                {
166                    Console.WriteLine(e.Message);
167                    return e.ErrorCode;
168                }
169                catch (Exception e)
170                {
171                    Console.WriteLine(e.Message);
172                    return 1;
173                }
174        }
175
176        /// <summary>
177        /// Prints the command line help for Eraser.
178        /// </summary>
179        private static void PrintCommandHelp()
180        {
181            Console.WriteLine(@"usage: Eraser <action> <arguments>
182where action is
183  help                Show this help message.
184  addtask             Adds tasks to the current task list.
185  querymethods        Lists all registered Erasure methods.
186  importtasklist      Imports an Eraser Task list to the current user's Task
187                      List.
188
189global parameters:
190  /quiet              Do not create a Console window to display progress.
191
192parameters for help:
193  eraser help
194
195  no parameters to set.
196
197parameters for addtask:
198  eraser addtask [/method=<methodGUID>] [/schedule=(now|manually|restart)] (recyclebin | unused=<volume> | dir=<directory> | file=<file>)[...]
199
200  /method             The Erasure method to use.
201  /schedule           The schedule the task will follow. The value must be one
202                      of:
203      now             The task will be queued for immediate execution.
204      manually        The task will be created but not queued for execution.
205      restart         The task will be queued for execution when the computer
206                      is next restarted.
207  recyclebin          Erases files and folders in the recycle bin
208  unused              Erases unused space in the volume.
209    optional arguments: unused=<drive>[,clusterTips[=(true|false)]]
210      clusterTips     If specified, the drive's files will have their
211                      cluster tips erased. This parameter accepts a Boolean
212                      value (true/false) as an argument; if none is specified
213                      true is assumed.
214  dir                 Erases files and folders in the directory
215    optional arguments: dir=<directory>[,-excludeMask][,+includeMask][,deleteIfEmpty]
216      excludeMask     A wildcard expression for files and folders to
217                      exclude.
218      includeMask     A wildcard expression for files and folders to
219                      include.
220                      The include mask is applied before the exclude mask.
221      deleteIfEmpty   Deletes the folder at the end of the erasure if it is
222                      empty.
223  file                Erases the specified file
224
225parameters for querymethods:
226  eraser querymethods
227
228  no parameters to set.
229
230parameters for importtasklist:
231  eraser importtasklist (file)[...]
232
233  [file]              A list of one or more files to import.
234
235All arguments are case sensitive.");
236            Console.Out.Flush();
237        }
238
239        /// <summary>
240        /// Prints the help text for Eraser (with copyright)
241        /// </summary>
242        /// <param name="arguments">Not used.</param>
243        private static void CommandHelp(ConsoleArguments arguments)
244        {
245            Console.WriteLine(@"Eraser {0}
246(c) 2008-2010 The Eraser Project
247Eraser is Open-Source Software: see http://eraser.heidi.ie/ for details.
248", Assembly.GetExecutingAssembly().GetName().Version);
249
250            PrintCommandHelp();
251        }
252
253        /// <summary>
254        /// Lists all registered erasure methods.
255        /// </summary>
256        /// <param name="arguments">Not used.</param>
257        private static void CommandQueryMethods(ConsoleArguments arguments)
258        {
259            //Output the header
260            const string methodFormat = "{0,-2} {1,-39} {2}";
261            Console.WriteLine(methodFormat, "", "Method", "GUID");
262            Console.WriteLine(new string('-', 79));
263
264            //Refresh the list of erasure methods
265            foreach (ErasureMethod method in ManagerLibrary.Instance.ErasureMethodRegistrar)
266            {
267                Console.WriteLine(methodFormat, (method is UnusedSpaceErasureMethod) ?
268                    "U" : "", method.Name, method.Guid.ToString());
269            }
270        }
271
272        /// <summary>
273        /// Parses the command line for tasks and adds them using the
274        /// <see cref="RemoteExecutor"/> class.
275        /// </summary>
276        /// <param name="arg">The command line parameters passed to the program.</param>
277        private static void CommandAddTask(ConsoleArguments arg)
278        {
279            AddTaskArguments arguments = (AddTaskArguments)arg;
280
281            //Create the task then set the method as well as schedule
282            Task task = new Task();
283            ErasureMethod method = arguments.ErasureMethod == Guid.Empty ?
284                ErasureMethodRegistrar.Default :
285                ManagerLibrary.Instance.ErasureMethodRegistrar[arguments.ErasureMethod];
286            switch (arguments.Schedule.ToUpperInvariant())
287            {
288                case "":
289                case "NOW":
290                    task.Schedule = Schedule.RunNow;
291                    break;
292                case "MANUALLY":
293                    task.Schedule = Schedule.RunManually;
294                    break;
295                case "RESTART":
296                    task.Schedule = Schedule.RunOnRestart;
297                    break;
298                default:
299                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
300                        "Unknown schedule type: {0}", arguments.Schedule), "/schedule");
301            }
302
303            //Parse the rest of the command line parameters as target expressions.
304            List<string> trueValues = new List<string>(new string[] { "yes", "true" });
305            string[] strings = new string[] {
306                //The recycle bin target
307                "(?<recycleBin>recyclebin)",
308
309                //The unused space erasure target, taking the optional clusterTips
310                //argument which defaults to true; if none is specified it's assumed
311                //false
312                "unused=(?<unusedVolume>.*)(?<unusedTips>,clusterTips(=(?<unusedTipsValue>true|false))?)?",
313
314                //The directory target, taking a list of + and - wildcard expressions.
315                "dir=(?<directoryName>.*)(?<directoryParams>(?<directoryExcludeMask>,-[^,]+)|(?<directoryIncludeMask>,\\+[^,]+)|(?<directoryDeleteIfEmpty>,deleteIfEmpty(=(?<directoryDeleteIfEmptyValue>true|false))?))*",
316
317                //The file target.
318                "file=(?<fileName>.*)"
319            };
320
321            Regex regex = new Regex(string.Join("|", strings),
322                RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.RightToLeft);
323            foreach (string argument in arguments.PositionalArguments)
324            {
325                Match match = regex.Match(argument);
326                if (match.Captures.Count == 0)
327                {
328                    Console.WriteLine("Unknown argument: {0}, skipped.", argument);
329                    continue;
330                }
331
332                ErasureTarget target = null;
333                if (match.Groups["recycleBin"].Success)
334                {
335                    target = new RecycleBinTarget();
336                }
337                else if (match.Groups["unusedVolume"].Success)
338                {
339                    UnusedSpaceTarget unusedSpaceTarget = new UnusedSpaceTarget();
340                    target = unusedSpaceTarget;
341                    unusedSpaceTarget.Drive = match.Groups["unusedVolume"].Value;
342
343                    if (!match.Groups["unusedTips"].Success)
344                        unusedSpaceTarget.EraseClusterTips = false;
345                    else if (!match.Groups["unusedTipsValue"].Success)
346                        unusedSpaceTarget.EraseClusterTips = true;
347                    else
348                        unusedSpaceTarget.EraseClusterTips =
349                            trueValues.IndexOf(match.Groups["unusedTipsValue"].Value) != -1;
350                }
351                else if (match.Groups["directoryName"].Success)
352                {
353                    FolderTarget folderTarget = new FolderTarget();
354                    target = folderTarget;
355
356                    folderTarget.Path = match.Groups["directoryName"].Value;
357                    if (!match.Groups["directoryDeleteIfEmpty"].Success)
358                        folderTarget.DeleteIfEmpty = false;
359                    else if (!match.Groups["directoryDeleteIfEmptyValue"].Success)
360                        folderTarget.DeleteIfEmpty = true;
361                    else
362                        folderTarget.DeleteIfEmpty =
363                            trueValues.IndexOf(match.Groups["directoryDeleteIfEmptyValue"].Value) != -1;
364                    if (match.Groups["directoryExcludeMask"].Success)
365                        folderTarget.ExcludeMask += match.Groups["directoryExcludeMask"].Value.Remove(0, 2) + ' ';
366                    if (match.Groups["directoryIncludeMask"].Success)
367                        folderTarget.IncludeMask += match.Groups["directoryIncludeMask"].Value.Remove(0, 2) + ' ';
368                }
369                else if (match.Groups["fileName"].Success)
370                {
371                    FileTarget fileTarget = new FileTarget();
372                    target = fileTarget;
373                    fileTarget.Path = match.Groups["fileName"].Value;
374                }
375
376                if (target == null)
377                    continue;
378
379                target.Method = method;
380                task.Targets.Add(target);
381            }
382
383            //Check the number of tasks in the task.
384            if (task.Targets.Count == 0)
385                throw new ArgumentException("Tasks must contain at least one erasure target.");
386
387            //Send the task out.
388            try
389            {
390                using (RemoteExecutorClient client = new RemoteExecutorClient())
391                {
392                    client.Run();
393                    if (!client.IsConnected)
394                    {
395                        //The client cannot connect to the server. This probably means
396                        //that the server process isn't running. Start an instance.
397                        Process eraserInstance = Process.Start(
398                            Assembly.GetExecutingAssembly().Location, "/quiet");
399                        Thread.Sleep(0);
400                        eraserInstance.WaitForInputIdle();
401
402                        client.Run();
403                        if (!client.IsConnected)
404                            throw new IOException("Eraser cannot connect to the running " +
405                                "instance for erasures.");
406                    }
407
408                    client.Tasks.Add(task);
409                }
410            }
411            catch (UnauthorizedAccessException e)
412            {
413                //We can't connect to the pipe because the other instance of Eraser
414                //is running with higher privileges than this instance.
415                throw new UnauthorizedAccessException("Another instance of Eraser " +
416                    "is already running but it is running with higher privileges than " +
417                    "this instance of Eraser. Tasks cannot be added in this manner.\n\n" +
418                    "Close the running instance of Eraser and start it again without " +
419                    "administrator privileges, or run the command again as an " +
420                    "administrator.", e);
421            }
422        }
423
424        /// <summary>
425        /// Imports the given tasklists and adds them to the global Eraser instance.
426        /// </summary>
427        /// <param name="args">The list of files specified on the command line.</param>
428        private static void CommandImportTaskList(ConsoleArguments args)
429        {
430            //Import the task list
431            try
432            {
433                using (RemoteExecutorClient client = new RemoteExecutorClient())
434                {
435                    client.Run();
436                    if (!client.IsConnected)
437                    {
438                        //The client cannot connect to the server. This probably means
439                        //that the server process isn't running. Start an instance.
440                        Process eraserInstance = Process.Start(
441                            Assembly.GetExecutingAssembly().Location, "/quiet");
442                        eraserInstance.WaitForInputIdle();
443
444                        client.Run();
445                        if (!client.IsConnected)
446                            throw new IOException("Eraser cannot connect to the running " +
447                                "instance for erasures.");
448                    }
449
450                    foreach (string path in args.PositionalArguments)
451                        using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
452                            client.Tasks.LoadFromStream(stream);
453                }
454            }
455            catch (UnauthorizedAccessException e)
456            {
457                //We can't connect to the pipe because the other instance of Eraser
458                //is running with higher privileges than this instance.
459                throw new UnauthorizedAccessException("Another instance of Eraser " +
460                    "is already running but it is running with higher privileges than " +
461                    "this instance of Eraser. Tasks cannot be added in this manner.\n\n" +
462                    "Close the running instance of Eraser and start it again without " +
463                    "administrator privileges, or run the command again as an " +
464                    "administrator.", e);
465            }
466        }
467        #endregion
468
469        #region GUI Program code
470        /// <summary>
471        /// Runs Eraser as a GUI application.
472        /// </summary>
473        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
474        private static void GUIMain(string[] commandLine)
475        {
476            //Create a unique program instance ID for this user.
477            string instanceId = "Eraser-BAD0DAC6-C9EE-4acc-8701-C9B3C64BC65E-GUI-" +
478                WindowsIdentity.GetCurrent().User.ToString();
479
480            //Then initialise the instance and initialise the Manager library.
481            using (GuiProgram program = new GuiProgram(commandLine, instanceId))
482            using (ManagerLibrary library = new ManagerLibrary(new Settings()))
483            {
484                program.InitInstance += OnGUIInitInstance;
485                program.NextInstance += OnGUINextInstance;
486                program.ExitInstance += OnGUIExitInstance;
487                program.Run();
488            }
489        }
490
491        /// <summary>
492        /// Triggered when the Program is started for the first time.
493        /// </summary>
494        /// <param name="sender">The sender of the object.</param>
495        /// <param name="e">Event arguments.</param>
496        private static void OnGUIInitInstance(object sender, InitInstanceEventArgs e)
497        {
498            GuiProgram program = (GuiProgram)sender;
499            eraserClient = new RemoteExecutorServer();
500
501            //Set our UI language
502            EraserSettings settings = EraserSettings.Get();
503            Thread.CurrentThread.CurrentUICulture = new CultureInfo(settings.Language);
504            Application.SafeTopLevelCaptionFormat = S._("Eraser");
505
506            //Register the BlackBox UI handler
507            Application.Idle += OnGUIIdle;
508
509            //Load the task list
510            try
511            {
512                if (File.Exists(TaskListPath))
513                {
514                    using (FileStream stream = new FileStream(TaskListPath, FileMode.Open,
515                        FileAccess.Read, FileShare.Read))
516                    {
517                        eraserClient.Tasks.LoadFromStream(stream);
518                    }
519                }
520            }
521            catch (SerializationException ex)
522            {
523                File.Delete(TaskListPath);
524                MessageBox.Show(S._("Could not load task list. All task entries have " +
525                    "been lost. The error returned was: {0}", ex.Message), S._("Eraser"),
526                    MessageBoxButtons.OK, MessageBoxIcon.Error,
527                    MessageBoxDefaultButton.Button1,
528                    Localisation.IsRightToLeft(null) ?
529                        MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign : 0);
530            }
531
532            //Create the main form
533            program.MainForm = new MainForm();
534
535            //Decide whether to display any UI
536            GuiArguments arguments = new GuiArguments();
537            Args.Parse(program.CommandLine, CommandLinePrefixes, CommandLineSeparators, arguments);
538            e.ShowMainForm = !arguments.AtRestart && !arguments.Quiet;
539
540            //Queue tasks meant for running at restart if we are given that command line.
541            if (arguments.AtRestart)
542                eraserClient.QueueRestartTasks();
543
544            //Run the eraser client.
545            eraserClient.Run();
546        }
547
548        private static void OnGUIIdle(object sender, EventArgs e)
549        {
550            Application.Idle -= OnGUIIdle;
551            BlackBox blackBox = BlackBox.Get();
552
553            bool allSubmitted = true;
554            foreach (BlackBoxReport report in blackBox.GetDumps())
555                if (!report.Submitted)
556                {
557                    allSubmitted = false;
558                    break;
559                }
560
561            if (allSubmitted)
562                return;
563
564            BlackBoxMainForm form = new BlackBoxMainForm();
565            form.Show();
566        }
567
568        /// <summary>
569        /// Triggered when a second instance of Eraser is started.
570        /// </summary>
571        /// <param name="sender">The sender of the event.</param>
572        /// <param name="e">Event argument.</param>
573        private static void OnGUINextInstance(object sender, NextInstanceEventArgs e)
574        {
575            //Another instance of the GUI Program has been started: show the main window
576            //now as we still do not have a facility to handle the command line arguments.
577            GuiProgram program = (GuiProgram)sender;
578
579            //Invoke the function if we aren't on the main thread
580            if (program.MainForm.InvokeRequired)
581            {
582                program.MainForm.Invoke(
583                    (GuiProgram.NextInstanceEventHandler)OnGUINextInstance,
584                    sender, e);
585                return;
586            }
587
588            program.MainForm.Show();
589        }
590
591        /// <summary>
592        /// Triggered when the first instance of Eraser is exited.
593        /// </summary>
594        /// <param name="sender">The sender of the event.</param>
595        /// <param name="e">Event argument.</param>
596        private static void OnGUIExitInstance(object sender, EventArgs e)
597        {
598            //Save the task list
599            if (!Directory.Exists(Program.AppDataPath))
600                Directory.CreateDirectory(Program.AppDataPath);
601            using (FileStream stream = new FileStream(TaskListPath, FileMode.Create,
602                FileAccess.Write, FileShare.None))
603            {
604                eraserClient.Tasks.SaveToStream(stream);
605            }
606
607            //Dispose the eraser executor instance
608            eraserClient.Dispose();
609        }
610        #endregion
611
612        /// <summary>
613        /// The acceptable list of command line prefixes we will accept.
614        /// </summary>
615        public const string CommandLinePrefixes = "(/|-|--)";
616
617        /// <summary>
618        /// The acceptable list of command line separators we will accept.
619        /// </summary>
620        public const string CommandLineSeparators = "(:|=)";
621
622        /// <summary>
623        /// The global Executor instance.
624        /// </summary>
625        public static Executor eraserClient;
626
627        /// <summary>
628        /// Path to the Eraser application data path.
629        /// </summary>
630        public static readonly string AppDataPath = Path.Combine(Environment.GetFolderPath(
631            Environment.SpecialFolder.LocalApplicationData), @"Eraser 6");
632
633        /// <summary>
634        /// File name of the Eraser task list.
635        /// </summary>
636        private const string TaskListFileName = @"Task List.ersx";
637
638        /// <summary>
639        /// Path to the Eraser task list.
640        /// </summary>
641        public static readonly string TaskListPath = Path.Combine(AppDataPath, TaskListFileName);
642
643        /// <summary>
644        /// Path to the Eraser settings key (relative to HKCU)
645        /// </summary>
646        public const string SettingsPath = @"SOFTWARE\Eraser\Eraser 6";
647    }
648}
Note: See TracBrowser for help on using the repository browser.