source: trunk/eraser/Eraser/Program.cs @ 2127

Revision 2127, 22.7 KB checked in by lowjoel, 4 years ago (diff)

Complete the implementation of the secure move command. Addresses #60: Eraser Secure Cut.

  • 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;
41using Eraser.DefaultPlugins;
42
43namespace Eraser
44{
45    internal static partial class Program
46    {
47        /// <summary>
48        /// The common program arguments shared between the GUI and console programs.
49        /// </summary>
50        class Arguments
51        {
52            /// <summary>
53            /// True if the program should not be started with any user-visible interfaces.
54            /// </summary>
55            /// <remarks>Errors will also be silently ignored.</remarks>
56            [Arg("quiet", "The program should not be started with any user-visible interfaces. " +
57                "Errors will be silently ignored.", typeof(bool), false, false, null)]
58            public bool Quiet { get; set; }
59        }
60
61        /// <summary>
62        /// Program arguments which only apply to the GUI program.
63        /// </summary>
64        class GuiArguments : Arguments
65        {
66            /// <summary>
67            /// True if the command line specified atRestart, which should result in the
68            /// queueing of tasks meant for running at restart.
69            /// </summary>
70            [Arg("atRestart", "The program should queue all tasks scheduled for running at " +
71                "the system restart.", typeof(bool), false, false, null)]
72            public bool AtRestart { get; set; }
73        }
74
75        class ConsoleArguments : Arguments
76        {
77            /// <summary>
78            /// The Action which this handler is in charge of.
79            /// </summary>
80            [Arg(0, "The action this command line is stating.", typeof(string), true, null, null)]
81            public string Action { get; set; }
82
83            /// <summary>
84            /// The list of command line parameters not placed in a switch.
85            /// </summary>
86            public List<string> PositionalArguments { get; set; }
87        }
88
89        class AddTaskArguments : ConsoleArguments
90        {
91            /// <summary>
92            /// The erasure method which the user specified on the command line.
93            /// </summary>
94            [Arg("method", "The erasure method to use", typeof(Guid), false, null, null)]
95            public Guid ErasureMethod { get; set; }
96
97            /// <summary>
98            /// The schedule for the current set of targets.
99            /// </summary>
100            [Arg("schedule", "The schedule to use", typeof(Schedule), false, null, null)]
101            public string Schedule { get; set; }
102        }
103
104        class ShellArguments : ConsoleArguments
105        {
106            /// <summary>
107            /// The action which the shell extension has requested.
108            /// </summary>
109            [Arg("action", "The action selected by the user", typeof(string), true, null, null)]
110            public ShellActions ShellAction { get; set; }
111
112            /// <summary>
113            /// Whether the recycle bin was specified on the command line.
114            /// </summary>
115            [Arg("recycleBin", "The recycle bin as an erasure target", typeof(string), false, null, null)]
116            public bool RecycleBin { get; set; }
117
118            /// <summary>
119            /// The destination for secure move operations, only valid when
120            /// <see cref="ShellAction"/> is <see cref="ShellActions.SecureMove"/>
121            /// </summary>
122            [Arg("destination", "The destination for secure move operations", typeof(string), false, null, null)]
123            public string Destination { get; set; }
124        }
125
126        public enum ShellActions
127        {
128            /// <summary>
129            /// Erase the selected items now.
130            /// </summary>
131            EraseNow,
132
133            /// <summary>
134            /// Erase the selected items on restart.
135            /// </summary>
136            EraseOnRestart,
137
138            /// <summary>
139            /// Erase the unused space on the drive.
140            /// </summary>
141            EraseUnusedSpace,
142
143            /// <summary>
144            /// Securely moves a file from one drive to another (simple rename if the source and
145            /// destination drives are the same)
146            /// </summary>
147            SecureMove
148        }
149
150        /// <summary>
151        /// The main entry point for the application.
152        /// </summary>
153        [STAThread]
154        static int Main(string[] rawCommandLine)
155        {
156            //Immediately parse command line arguments. Start by substituting all
157            //response files ("@filename") arguments with the arguments found in the
158            //file
159            List<string> commandLine = new List<string>(rawCommandLine.Length);
160            foreach (string argument in rawCommandLine)
161            {
162                if (argument[0] == '@' && File.Exists(argument.Substring(1)))
163                {
164                    //The current parameter is a response file, parse the file
165                    //for arguments and substitute it.
166                    using (TextReader reader = new StreamReader(argument.Substring(1)))
167                    {
168                        commandLine.AddRange(Shell.ParseCommandLine(reader.ReadToEnd()));
169                    }
170                }
171                else
172                    commandLine.Add(argument);
173            }
174
175            string[] finalCommandLine = commandLine.ToArray();
176            ComLib.BoolMessageItem argumentParser = Args.Parse(finalCommandLine,
177                CommandLinePrefixes, CommandLineSeparators);
178            Args parsedArguments = (Args)argumentParser.Item;
179
180            //We default to a GUI if:
181            // - The parser did not succeed.
182            // - The parser resulted in an empty arguments list
183            // - The parser's argument at index 0 is not equal to the first argument (this
184            //   is when the user is passing GUI options -- command line options always
185            //   start with the action, e.g. Eraser help, or Eraser addtask
186            if (!argumentParser.Success || parsedArguments.IsEmpty ||
187                parsedArguments.Positional.Count == 0 ||
188                parsedArguments.Positional[0] != parsedArguments.Raw[0])
189            {
190                GUIMain(finalCommandLine);
191            }
192            else
193            {
194                return CommandMain(finalCommandLine);
195            }
196
197            //Return zero to signify success
198            return 0;
199        }
200
201        #region Console Program code
202        /// <summary>
203        /// Connects to the running Eraser instance for erasures.
204        /// </summary>
205        /// <returns>The connectin with the remote instance.</returns>
206        private static RemoteExecutorClient CommandConnect()
207        {
208            try
209            {
210                RemoteExecutorClient result = new RemoteExecutorClient();
211                result.Run();
212                if (!result.IsConnected)
213                {
214                    //The client cannot connect to the server. This probably means
215                    //that the server process isn't running. Start an instance.
216                    Process eraserInstance = Process.Start(
217                        Assembly.GetExecutingAssembly().Location, "/quiet");
218                    Thread.Sleep(0);
219                    eraserInstance.WaitForInputIdle();
220
221                    result.Run();
222                    if (!result.IsConnected)
223                        throw new IOException("Eraser cannot connect to the running " +
224                            "instance for erasures.");
225                }
226
227                return result;
228            }
229            catch (UnauthorizedAccessException e)
230            {
231                //We can't connect to the pipe because the other instance of Eraser
232                //is running with higher privileges than this instance.
233                throw new UnauthorizedAccessException("Another instance of Eraser " +
234                    "is already running but it is running with higher privileges than " +
235                    "this instance of Eraser. Tasks cannot be added in this manner.\n\n" +
236                    "Close the running instance of Eraser and start it again without " +
237                    "administrator privileges, or run the command again as an " +
238                    "administrator.", e);
239            }
240        }
241
242        /// <summary>
243        /// Runs Eraser as a command-line application.
244        /// </summary>
245        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
246        private static int CommandMain(string[] commandLine)
247        {
248            using (ConsoleProgram program = new ConsoleProgram(commandLine))
249            using (ManagerLibrary library = new ManagerLibrary(new Settings()))
250                try
251                {
252                    program.Handlers.Add("help",
253                        new ConsoleActionData(CommandHelp, new ConsoleArguments()));
254                    program.Handlers.Add("querymethods",
255                        new ConsoleActionData(CommandQueryMethods, new ConsoleArguments()));
256                    program.Handlers.Add("addtask",
257                        new ConsoleActionData(CommandAddTask, new AddTaskArguments()));
258                    program.Handlers.Add("importtasklist",
259                        new ConsoleActionData(CommandImportTaskList, new ConsoleArguments()));
260                    program.Handlers.Add("shell",
261                        new ConsoleActionData(CommandShell, new ShellArguments()));
262                    program.Run();
263                    return 0;
264                }
265                catch (UnauthorizedAccessException)
266                {
267                    return Win32ErrorCode.AccessDenied;
268                }
269                catch (Win32Exception e)
270                {
271                    Console.WriteLine(e.Message);
272                    return e.ErrorCode;
273                }
274                catch (Exception e)
275                {
276                    Console.WriteLine(e.Message);
277                    return 1;
278                }
279        }
280
281        /// <summary>
282        /// Prints the command line help for Eraser.
283        /// </summary>
284        private static void PrintCommandHelp()
285        {
286            Console.WriteLine(@"usage: Eraser <action> <arguments>
287where action is
288  help                Show this help message.
289  addtask             Adds tasks to the current task list.
290  querymethods        Lists all registered Erasure methods.
291  importtasklist      Imports an Eraser Task list to the current user's Task
292                      List.
293
294global parameters:
295  /quiet              Do not create a Console window to display progress.
296
297parameters for help:
298  eraser help
299
300  no parameters to set.
301
302parameters for addtask:
303  eraser addtask [/method=<methodGUID>] [/schedule=(now|manually|restart)] (recyclebin | unused=<volume> | dir=<directory> | file=<file>)[...]
304
305  /method             The Erasure method to use.
306  /schedule           The schedule the task will follow. The value must be one
307                      of:
308      now             The task will be queued for immediate execution.
309      manually        The task will be created but not queued for execution.
310      restart         The task will be queued for execution when the computer
311                      is next restarted.
312  recyclebin          Erases files and folders in the recycle bin
313  unused              Erases unused space in the volume.
314    optional arguments: unused=<drive>[,clusterTips[=(true|false)]]
315      clusterTips     If specified, the drive's files will have their
316                      cluster tips erased. This parameter accepts a Boolean
317                      value (true/false) as an argument; if none is specified
318                      true is assumed.
319  dir                 Erases files and folders in the directory
320    optional arguments: dir=<directory>[,-excludeMask][,+includeMask][,deleteIfEmpty]
321      excludeMask     A wildcard expression for files and folders to
322                      exclude.
323      includeMask     A wildcard expression for files and folders to
324                      include.
325                      The include mask is applied before the exclude mask.
326      deleteIfEmpty   Deletes the folder at the end of the erasure if it is
327                      empty.
328  file                Erases the specified file
329
330parameters for querymethods:
331  eraser querymethods
332
333  no parameters to set.
334
335parameters for importtasklist:
336  eraser importtasklist (file)[...]
337
338  [file]              A list of one or more files to import.
339
340All arguments are case sensitive.
341
342Response files can be used for very long command lines (generally, anything
343involving more than 32,000 characters.) Response files are used by prepending
344""@"" to the path to the file, and passing it into the command line. The
345contents of the response files' will be substituted at the same position into
346the command line.");
347            Console.Out.Flush();
348        }
349
350        /// <summary>
351        /// Prints the help text for Eraser (with copyright)
352        /// </summary>
353        /// <param name="arguments">Not used.</param>
354        private static void CommandHelp(ConsoleArguments arguments)
355        {
356            Console.WriteLine(@"Eraser {0}
357(c) 2008-2010 The Eraser Project
358Eraser is Open-Source Software: see http://eraser.heidi.ie/ for details.
359", Assembly.GetExecutingAssembly().GetName().Version);
360
361            PrintCommandHelp();
362        }
363
364        /// <summary>
365        /// Lists all registered erasure methods.
366        /// </summary>
367        /// <param name="arguments">Not used.</param>
368        private static void CommandQueryMethods(ConsoleArguments arguments)
369        {
370            //Output the header
371            const string methodFormat = "{0,-2} {1,-39} {2}";
372            Console.WriteLine(methodFormat, "", "Method", "GUID");
373            Console.WriteLine(new string('-', 79));
374
375            //Refresh the list of erasure methods
376            foreach (ErasureMethod method in ManagerLibrary.Instance.ErasureMethodRegistrar)
377            {
378                Console.WriteLine(methodFormat, (method is UnusedSpaceErasureMethod) ?
379                    "U" : "", method.Name, method.Guid.ToString());
380            }
381        }
382
383        /// <summary>
384        /// Parses the command line for tasks and adds them using the
385        /// <see cref="RemoteExecutor"/> class.
386        /// </summary>
387        /// <param name="arg">The command line parameters passed to the program.</param>
388        private static void CommandAddTask(ConsoleArguments arg)
389        {
390            AddTaskArguments arguments = (AddTaskArguments)arg;
391
392            //Create the task then set the method as well as schedule
393            Task task = new Task();
394            ErasureMethod method = arguments.ErasureMethod == Guid.Empty ?
395                ErasureMethodRegistrar.Default :
396                ManagerLibrary.Instance.ErasureMethodRegistrar[arguments.ErasureMethod];
397            switch (arguments.Schedule.ToUpperInvariant())
398            {
399                case "":
400                case "NOW":
401                    task.Schedule = Schedule.RunNow;
402                    break;
403                case "MANUALLY":
404                    task.Schedule = Schedule.RunManually;
405                    break;
406                case "RESTART":
407                    task.Schedule = Schedule.RunOnRestart;
408                    break;
409                default:
410                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
411                        "Unknown schedule type: {0}", arguments.Schedule), "/schedule");
412            }
413
414            //Parse the rest of the command line parameters as target expressions.
415            foreach (string argument in arguments.PositionalArguments)
416            {
417                bool processed = false;
418                foreach (ErasureTarget target in ManagerLibrary.Instance.ErasureTargetRegistrar)
419                    if (target.Configurer.ProcessArgument(argument))
420                    {
421                        target.Method = method;
422                        task.Targets.Add(target);
423                        processed = true;
424                        break;
425                    }
426
427                if (!processed)
428                {
429                    Console.WriteLine("Unknown argument: {0}, skipped.", argument);
430                    continue;
431                }
432            }
433
434            //Check the number of tasks in the task.
435            if (task.Targets.Count == 0)
436                throw new ArgumentException("Tasks must contain at least one erasure target.");
437
438            //Send the task out.
439            using (eraserClient = CommandConnect())
440                eraserClient.Tasks.Add(task);
441        }
442
443        /// <summary>
444        /// Imports the given tasklists and adds them to the global Eraser instance.
445        /// </summary>
446        /// <param name="args">The list of files specified on the command line.</param>
447        private static void CommandImportTaskList(ConsoleArguments args)
448        {
449            //Import the task list
450            using (eraserClient = CommandConnect())
451                foreach (string path in args.PositionalArguments)
452                    using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
453                        eraserClient.Tasks.LoadFromStream(stream);
454        }
455
456        /// <summary>
457        /// Handles the files from the Shell extension.
458        /// </summary>
459        /// <param name="args">The command line parameters passed to the program.</param>
460        private static void CommandShell(ConsoleArguments args)
461        {
462            switch (((ShellArguments)args).ShellAction)
463            {
464                case ShellActions.SecureMove:
465                    CommandShellSecureMove((ShellArguments)args);
466                    break;
467
468                default:
469                    CommandShellErase((ShellArguments)args);
470                    break;
471            }
472        }
473
474        /// <summary>
475        /// Handles the erasure of files from the Shell extension.
476        /// </summary>
477        /// <param name="args">The command line parameters passed to the program.</param>
478        private static void CommandShellErase(ShellArguments args)
479        {
480            //Construct a draft task.
481            Task task = new Task();
482            switch (args.ShellAction)
483            {
484                case ShellActions.EraseOnRestart:
485                    task.Schedule = Schedule.RunOnRestart;
486                    goto case ShellActions.EraseNow;
487
488                case ShellActions.EraseNow:
489                    foreach (string path in args.PositionalArguments)
490                    {
491                        //If the path doesn't exist, skip the file
492                        if (!(File.Exists(path) || Directory.Exists(path)))
493                            continue;
494
495                        FileSystemObjectErasureTarget target = null;
496                        if ((File.GetAttributes(path) & FileAttributes.Directory) != 0)
497                        {
498                            target = new FolderErasureTarget();
499                            target.Path = path;
500                        }
501                        else
502                        {
503                            target = new FileErasureTarget();
504                            target.Path = path;
505                        }
506
507                        task.Targets.Add(target);
508                    }
509
510                    //Was the recycle bin specified?
511                    if (args.RecycleBin)
512                        task.Targets.Add(new RecycleBinErasureTarget());
513                    break;
514
515                case ShellActions.EraseUnusedSpace:
516                    foreach (string path in args.PositionalArguments)
517                    {
518                        UnusedSpaceErasureTarget target = new UnusedSpaceErasureTarget();
519                        target.Drive = path;
520                        task.Targets.Add(target);
521                    }
522                    break;
523            }
524
525            //Confirm that the user wants the erase.
526            Application.EnableVisualStyles();
527            using (Form dialog = new ShellConfirmationDialog(task))
528            {
529                if (dialog.ShowDialog() != DialogResult.Yes)
530                    return;
531            }
532
533            //Then queue for erasure.
534            using (eraserClient = CommandConnect())
535                eraserClient.Tasks.Add(task);
536        }
537
538        /// <summary>
539        /// Handles the movement of files from the Shell extension.
540        /// </summary>
541        /// <param name="args">The command line parameters passed to the program.</param>
542        private static void CommandShellSecureMove(ShellArguments args)
543        {
544            //Construct a draft task.
545            Task task = new Task();
546            foreach (string path in args.PositionalArguments)
547            {
548                SecureMoveErasureTarget target = new SecureMoveErasureTarget();
549                target.Path = path;
550                target.Destination = args.Destination;
551
552                task.Targets.Add(target);
553            }
554
555            //Then queue for erasure.
556            using (eraserClient = CommandConnect())
557                eraserClient.Tasks.Add(task);
558        }
559        #endregion
560
561        #region GUI Program code
562        /// <summary>
563        /// Runs Eraser as a GUI application.
564        /// </summary>
565        /// <param name="commandLine">The command line parameters passed to Eraser.</param>
566        private static void GUIMain(string[] commandLine)
567        {
568            //Create a unique program instance ID for this user.
569            string instanceId = "Eraser-BAD0DAC6-C9EE-4acc-8701-C9B3C64BC65E-GUI-" +
570                WindowsIdentity.GetCurrent().User.ToString();
571
572            //Then initialise the instance and initialise the Manager library.
573            using (GuiProgram program = new GuiProgram(commandLine, instanceId))
574            using (ManagerLibrary library = new ManagerLibrary(new Settings()))
575            {
576                program.InitInstance += OnGUIInitInstance;
577                program.NextInstance += OnGUINextInstance;
578                program.ExitInstance += OnGUIExitInstance;
579                program.Run();
580            }
581        }
582
583        /// <summary>
584        /// Triggered when the Program is started for the first time.
585        /// </summary>
586        /// <param name="sender">The sender of the object.</param>
587        /// <param name="e">Event arguments.</param>
588        private static void OnGUIInitInstance(object sender, InitInstanceEventArgs e)
589        {
590            GuiProgram program = (GuiProgram)sender;
591            eraserClient = new RemoteExecutorServer();
592
593            //Set our UI language
594            EraserSettings settings = EraserSettings.Get();
595            Thread.CurrentThread.CurrentUICulture = new CultureInfo(settings.Language);
596            Application.SafeTopLevelCaptionFormat = S._("Eraser");
597
598            //Load the task list
599            try
600            {
601                if (File.Exists(TaskListPath))
602                {
603                    using (FileStream stream = new FileStream(TaskListPath, FileMode.Open,
604                        FileAccess.Read, FileShare.Read))
605                    {
606                        eraserClient.Tasks.LoadFromStream(stream);
607                    }
608                }
609            }
610            catch (InvalidDataException ex)
611            {
612                File.Delete(TaskListPath);
613                MessageBox.Show(S._("Could not load task list. All task entries have " +
614                    "been lost. The error returned was: {0}", ex.Message), S._("Eraser"),
615                    MessageBoxButtons.OK, MessageBoxIcon.Error,
616                    MessageBoxDefaultButton.Button1,
617                    Localisation.IsRightToLeft(null) ?
618                        MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign : 0);
619            }
620
621            //Create the main form
622            program.MainForm = new MainForm();
623
624            //Decide whether to display any UI
625            GuiArguments arguments = new GuiArguments();
626            Args.Parse(program.CommandLine, CommandLinePrefixes, CommandLineSeparators, arguments);
627            e.ShowMainForm = !arguments.AtRestart && !arguments.Quiet;
628
629            //Queue tasks meant for running at restart if we are given that command line.
630            if (arguments.AtRestart)
631                eraserClient.QueueRestartTasks();
632
633            //Run the eraser client.
634            eraserClient.Run();
635        }
636
637        /// <summary>
638        /// Triggered when a second instance of Eraser is started.
639        /// </summary>
640        /// <param name="sender">The sender of the event.</param>
641        /// <param name="e">Event argument.</param>
642        private static void OnGUINextInstance(object sender, NextInstanceEventArgs e)
643        {
644            //Another instance of the GUI Program has been started: show the main window
645            //now as we still do not have a facility to handle the command line arguments.
646            GuiProgram program = (GuiProgram)sender;
647
648            //Invoke the function if we aren't on the main thread
649            if (program.MainForm.InvokeRequired)
650            {
651                program.MainForm.Invoke(
652                    (GuiProgram.NextInstanceEventHandler)OnGUINextInstance,
653                    sender, e);
654                return;
655            }
656
657            program.MainForm.Show();
658        }
659
660        /// <summary>
661        /// Triggered when the first instance of Eraser is exited.
662        /// </summary>
663        /// <param name="sender">The sender of the event.</param>
664        /// <param name="e">Event argument.</param>
665        private static void OnGUIExitInstance(object sender, EventArgs e)
666        {
667            //Save the task list
668            if (!Directory.Exists(Program.AppDataPath))
669                Directory.CreateDirectory(Program.AppDataPath);
670            using (FileStream stream = new FileStream(TaskListPath, FileMode.Create,
671                FileAccess.Write, FileShare.None))
672            {
673                eraserClient.Tasks.SaveToStream(stream);
674            }
675
676            //Dispose the eraser executor instance
677            eraserClient.Dispose();
678        }
679        #endregion
680
681        /// <summary>
682        /// The acceptable list of command line prefixes we will accept.
683        /// </summary>
684        public const string CommandLinePrefixes = "(/|-|--)";
685
686        /// <summary>
687        /// The acceptable list of command line separators we will accept.
688        /// </summary>
689        public const string CommandLineSeparators = "(:|=)";
690
691        /// <summary>
692        /// The global Executor instance.
693        /// </summary>
694        public static Executor eraserClient;
695
696        /// <summary>
697        /// Path to the Eraser application data path.
698        /// </summary>
699        public static readonly string AppDataPath = Path.Combine(Environment.GetFolderPath(
700            Environment.SpecialFolder.LocalApplicationData), @"Eraser 6");
701
702        /// <summary>
703        /// File name of the Eraser task list.
704        /// </summary>
705        private const string TaskListFileName = @"Task List.ersx";
706
707        /// <summary>
708        /// Path to the Eraser task list.
709        /// </summary>
710        public static readonly string TaskListPath = Path.Combine(AppDataPath, TaskListFileName);
711
712        /// <summary>
713        /// Path to the Eraser settings key (relative to HKCU)
714        /// </summary>
715        public const string SettingsPath = @"SOFTWARE\Eraser\Eraser 6";
716    }
717}
Note: See TracBrowser for help on using the repository browser.