source: trunk/eraser6/Eraser.Manager/DirectExecutor.cs @ 1102

Revision 1102, 37.0 KB checked in by lowjoel, 5 years ago (diff)

A whole host of scheduler fixes:

  • When cancelling scheduled tasks we removed the scheduled version of the task as well so tasks will no longer run on the schedule
  • When editing tasks the schedules were not updated and scheduled tasks still ran on the old schedule
  • Determining whether the task was queued manually for execution was a little unpredictable, this is now fixed
  • For consistency, when tasks are queued we will prevent editing of tasks

And a few stylistic fixes

  • Do not catch general exception types
  • Use automatic properties where possible
  • Don't use multiple casts
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By: Kasra Nassiri <cjax@users.sourceforge.net> @17/10/2008
6 * Modified By:
7 *
8 * This file is part of Eraser.
9 *
10 * Eraser is free software: you can redistribute it and/or modify it under the
11 * terms of the GNU General Public License as published by the Free Software
12 * Foundation, either version 3 of the License, or (at your option) any later
13 * version.
14 *
15 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
16 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 *
19 * A copy of the GNU General Public License can be found at
20 * <http://www.gnu.org/licenses/>.
21 */
22
23using System;
24using System.Collections.Generic;
25using System.Collections.Specialized;
26using System.Text;
27using System.Threading;
28using System.IO;
29
30using Eraser.Util;
31using System.Security.Principal;
32using System.Runtime.Serialization;
33using System.Runtime.Serialization.Formatters.Binary;
34using System.Security.Permissions;
35using Eraser.Unlocker;
36
37namespace Eraser.Manager
38{
39    /// <summary>
40    /// The DirectExecutor class is used by the Eraser GUI directly when the program
41    /// is run without the help of a Service.
42    /// </summary>
43    public class DirectExecutor : Executor
44    {
45        public DirectExecutor()
46        {
47            TaskAdded += OnTaskAdded;
48            TaskDeleted += OnTaskDeleted;
49            Tasks = new DirectExecutorTasksCollection(this);
50            thread = new Thread(Main);
51        }
52
53        protected override void Dispose(bool disposing)
54        {
55            if (disposing)
56            {
57                thread.Abort();
58                schedulerInterrupt.Set();
59                schedulerInterrupt.Close();
60            }
61
62            base.Dispose(disposing);
63        }
64
65        public override void Run()
66        {
67            thread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture;
68            thread.Start();
69        }
70
71        public override void QueueTask(Task task)
72        {
73            lock (tasksLock)
74            {
75                //Queue the task to be run immediately.
76                DateTime executionTime = DateTime.Now;
77                if (!scheduledTasks.ContainsKey(executionTime))
78                    scheduledTasks.Add(executionTime, new List<Task>());
79                scheduledTasks[executionTime].Add(task);
80                schedulerInterrupt.Set();
81            }
82        }
83
84        public override void ScheduleTask(Task task)
85        {
86            RecurringSchedule schedule = (RecurringSchedule)task.Schedule;
87            if (schedule == null)
88                return;
89
90            DateTime executionTime = (schedule.MissedPreviousSchedule &&
91                ManagerLibrary.Settings.ExecuteMissedTasksImmediately) ?
92                    DateTime.Now : schedule.NextRun;
93
94            lock (tasksLock)
95            {
96                if (!scheduledTasks.ContainsKey(executionTime))
97                    scheduledTasks.Add(executionTime, new List<Task>());
98                scheduledTasks[executionTime].Add(task);
99            }
100        }
101
102        public override void QueueRestartTasks()
103        {
104            lock (tasksLock)
105            {
106                foreach (Task task in tasks.Values)
107                    if (task.Schedule == Schedule.RunOnRestart)
108                        QueueTask(task);
109            }
110        }
111
112        public override void UnqueueTask(Task task)
113        {
114            lock (tasksLock)
115                for (int i = 0; i != scheduledTasks.Count; ++i)
116                    for (int j = 0; j < scheduledTasks.Values[i].Count; ++j)
117                    {
118                        Task currentTask = scheduledTasks.Values[i][j];
119                        if (currentTask == task &&
120                            (!(currentTask.Schedule is RecurringSchedule) ||
121                                ((RecurringSchedule)currentTask.Schedule).NextRun != scheduledTasks.Keys[i]))
122                        {
123                            scheduledTasks.Values[i].RemoveAt(i);
124                        }
125                    }
126        }
127
128        internal override bool IsTaskQueued(Task task)
129        {
130            lock (tasksLock)
131                foreach (KeyValuePair<DateTime, List<Task>> tasks in scheduledTasks)
132                    foreach (Task i in tasks.Value)
133                        if (task == i)
134                            if (task.Schedule is RecurringSchedule)
135                            {
136                                if (((RecurringSchedule)task.Schedule).NextRun != tasks.Key)
137                                    return true;
138                            }
139                            else
140                                return true;
141
142            return false;
143        }
144
145        private void OnTaskAdded(object sender, TaskEventArgs e)
146        {
147            e.Task.TaskEdited += OnTaskEdited;
148        }
149
150        private void OnTaskEdited(object sender, TaskEventArgs e)
151        {
152            //Find all schedule entries containing the task - since the user cannot make
153            //edits to the task when it is queued (only if it is scheduled) remove
154            //all task references and add them back
155            lock (tasksLock)
156                for (int i = 0; i != scheduledTasks.Count; ++i)
157                    for (int j = 0; j < scheduledTasks.Values[i].Count; )
158                    {
159                        Task currentTask = scheduledTasks.Values[i][j];
160                        if (currentTask == e.Task)
161                            scheduledTasks.Values[i].RemoveAt(j);
162                        else
163                            j++;
164                    }
165
166            //Then reschedule the task
167            if (e.Task.Schedule is RecurringSchedule)
168                ScheduleTask(e.Task);
169        }
170
171        private void OnTaskDeleted(object sender, TaskEventArgs e)
172        {
173            e.Task.TaskEdited -= OnTaskEdited;
174        }
175
176        public override ExecutorTasksCollection Tasks { get; protected set; }
177
178        /// <summary>
179        /// The thread entry point for this object. This object operates on a queue
180        /// and hence the thread will sequentially execute tasks.
181        /// </summary>
182        private void Main()
183        {
184            //The waiting thread will utilize a polling loop to check for new
185            //scheduled tasks. This will be checked every 30 seconds. However,
186            //when the thread is waiting for a new task, it can be interrupted.
187            while (thread.ThreadState != ThreadState.AbortRequested)
188            {
189                //Check for a new task
190                Task task = null;
191                lock (tasksLock)
192                {
193                    while (scheduledTasks.Count != 0)
194                        if (scheduledTasks.Values[0].Count == 0)
195                        {
196                            //Clean all all time slots at the start of the queue which are
197                            //empty
198                            scheduledTasks.RemoveAt(0);
199                        }
200                        else
201                        {
202                            if (scheduledTasks.Keys[0] <= DateTime.Now)
203                            {
204                                List<Task> tasks = scheduledTasks.Values[0];
205                                task = tasks[0];
206                                tasks.RemoveAt(0);
207                            }
208
209                            //Do schedule queue maintenance: clean up all empty timeslots
210                            if (task == null)
211                            {
212                                for (int i = 0; i < scheduledTasks.Count; )
213                                    if (scheduledTasks.Values[i].Count == 0)
214                                        scheduledTasks.RemoveAt(i);
215                                    else
216                                        ++i;
217                            }
218
219                            break;
220                        }
221                }
222
223                if (task != null)
224                {
225                    //Set the currently executing task.
226                    currentTask = task;
227
228                    try
229                    {
230                        //Prevent the system from sleeping.
231                        KernelApi.SetThreadExecutionState(ThreadExecutionState.Continuous |
232                            ThreadExecutionState.SystemRequired);
233
234                        //Broadcast the task started event.
235                        task.Canceled = false;
236                        task.OnTaskStarted(new TaskEventArgs(task));
237                        OnTaskProcessing(new TaskEventArgs(task));
238
239                        //Start a new log session to separate this session's events
240                        //from previous ones.
241                        task.Log.Entries.NewSession();
242
243                        //Run the task
244                        TaskProgressManager progress = new TaskProgressManager(task);
245                        foreach (ErasureTarget target in task.Targets)
246                            try
247                            {
248                                progress.Event.CurrentTarget = target;
249                                ++progress.Event.CurrentTargetIndex;
250
251                                UnusedSpaceTarget unusedSpaceTarget =
252                                    target as UnusedSpaceTarget;
253                                FileSystemObjectTarget fileSystemObjectTarget =
254                                    target as FileSystemObjectTarget;
255
256                                if (unusedSpaceTarget != null)
257                                    EraseUnusedSpace(task, unusedSpaceTarget, progress);
258                                else if (fileSystemObjectTarget != null)
259                                    EraseFilesystemObject(task, fileSystemObjectTarget, progress);
260                                else
261                                    throw new ArgumentException(S._("Unknown erasure target."));
262                            }
263                            catch (FatalException)
264                            {
265                                throw;
266                            }
267                            catch (Exception e)
268                            {
269                                task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Error));
270                            }
271                    }
272                    catch (FatalException e)
273                    {
274                        task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Fatal));
275                    }
276                    catch (Exception e)
277                    {
278                        task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Error));
279                    }
280                    finally
281                    {
282                        //Allow the system to sleep again.
283                        KernelApi.SetThreadExecutionState(ThreadExecutionState.Continuous);
284
285                        //If the task is a recurring task, reschedule it since we are done.
286                        if (task.Schedule is RecurringSchedule)
287                            ((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
288
289                        //If the task is an execute on restart task, it is only run
290                        //once and can now be restored to an immediately executed task
291                        if (task.Schedule == Schedule.RunOnRestart)
292                            task.Schedule = Schedule.RunNow;
293
294                        //And the task finished event.
295                        task.OnTaskFinished(new TaskEventArgs(task));
296                        OnTaskProcessed(new TaskEventArgs(task));
297
298                        //Remove the actively executing task from our instance variable
299                        currentTask = null;
300                    }
301                }
302
303                //Wait for half a minute to check for the next scheduled task.
304                schedulerInterrupt.WaitOne(30000, false);
305            }
306        }
307
308        /// <summary>
309        /// Manages the progress for any operation.
310        /// </summary>
311        private class ProgressManager
312        {
313            /// <summary>
314            /// Starts measuring the speed of the task.
315            /// </summary>
316            public void Start()
317            {
318                startTime = DateTime.Now;
319            }
320
321            /// <summary>
322            /// Tracks the amount of the operation completed.
323            /// </summary>
324            public long Completed
325            {
326                get
327                {
328                    return completed;
329                }
330                set
331                {
332                    lastCompleted += value - completed;
333                    completed = value;
334                }
335            }
336
337            /// <summary>
338            /// The amount to reach before the operation completes.
339            /// </summary>
340            public long Total
341            {
342                get
343                {
344                    return total;
345                }
346                set
347                {
348                    total = value;
349                }
350            }
351
352            /// <summary>
353            /// Gets the percentage of the operation completed.
354            /// </summary>
355            public float Progress
356            {
357                get
358                {
359                    return (float)((double)Completed / Total);
360                }
361            }
362
363            /// <summary>
364            /// Computes the speed of the erase, in units of completion per second,
365            /// based on the information collected in the previous 15 seconds.
366            /// </summary>
367            public int Speed
368            {
369                get
370                {
371                    if (DateTime.Now == startTime)
372                        return 0;
373
374                    if ((DateTime.Now - lastSpeedCalc).Seconds < 5 && lastSpeed != 0)
375                        return lastSpeed;
376
377                    //Calculate how much time has passed
378                    double timeElapsed = (DateTime.Now - lastSpeedCalc).TotalSeconds;
379                    if (timeElapsed == 0.0)
380                        return 0;
381
382                    //Then compute the speed of the calculation
383                    lastSpeed = (int)(lastCompleted / timeElapsed);
384                    lastSpeedCalc = DateTime.Now;
385                    lastCompleted = 0;
386                    return lastSpeed;
387                }
388            }
389
390            /// <summary>
391            /// Calculates the estimated amount of time left based on the total
392            /// amount of information to erase and the current speed of the erase
393            /// </summary>
394            public TimeSpan TimeLeft
395            {
396                get
397                {
398                    if (Speed == 0)
399                        return new TimeSpan(0, 0, -1);
400                    return new TimeSpan(0, 0, (int)((Total - Completed) / Speed));
401                }
402            }
403
404            /// <summary>
405            /// The starting time of the operation, used to determine average speed.
406            /// </summary>
407            private DateTime startTime;
408
409            /// <summary>
410            /// The last time a speed calculation was computed so that speed is not
411            /// computed too often.
412            /// </summary>
413            private DateTime lastSpeedCalc;
414
415            /// <summary>
416            /// The last calculated speed of the operation.
417            /// </summary>
418            private int lastSpeed;
419
420            /// <summary>
421            /// The amount of the operation completed since the last speed computation.
422            /// </summary>
423            private long lastCompleted;
424
425            /// <summary>
426            /// The amount of the operation completed.
427            /// </summary>
428            private long completed;
429
430            /// <summary>
431            /// The amount to reach before the operation is completed.
432            /// </summary>
433            private long total;
434        }
435
436        /// <summary>
437        /// Provides a common interface to track the progress made by the Erase functions.
438        /// </summary>
439        private class TaskProgressManager : ProgressManager
440        {
441            /// <summary>
442            /// Constructor.
443            /// </summary>
444            public TaskProgressManager(Task task)
445            {
446                foreach (ErasureTarget target in task.Targets)
447                    Total += target.TotalData;
448
449                Event = new TaskProgressEventArgs(task);
450                Start();
451            }
452
453            /// <summary>
454            /// The TaskProgressEventArgs object representing the progress of the current
455            /// task.
456            /// </summary>
457            public TaskProgressEventArgs Event
458            {
459                get
460                {
461                    return evt;
462                }
463                set
464                {
465                    evt = value;
466                }
467            }
468
469            private TaskProgressEventArgs evt;
470        }
471
472        #region Unused Space erasure functions
473        /// <summary>
474        /// Executes a unused space erase.
475        /// </summary>
476        /// <param name="task">The task currently being executed</param>
477        /// <param name="target">The target of the unused space erase.</param>
478        /// <param name="progress">The progress manager object managing the progress of the task</param>
479        private void EraseUnusedSpace(Task task, UnusedSpaceTarget target, TaskProgressManager progress)
480        {
481            //Check for sufficient privileges to run the unused space erasure.
482            if (!AdvApi.IsAdministrator())
483            {
484                if (Environment.OSVersion.Platform == PlatformID.Win32NT &&
485                    Environment.OSVersion.Version >= new Version(6, 0))
486                {
487                    throw new UnauthorizedAccessException(S._("The program does not have the " +
488                        "required permissions to erase the unused space on disk. Run the program " +
489                        "as an administrator and retry the operation."));
490                }
491                else
492                    throw new UnauthorizedAccessException(S._("The program does not have the " +
493                        "required permissions to erase the unused space on disk"));
494            }
495
496            //If the user is under disk quotas, log a warning message
497            if (VolumeInfo.FromMountpoint(target.Drive).HasQuota)
498                task.Log.LastSessionEntries.Add(new LogEntry(S._("The drive which is having its " +
499                    "unused space erased has disk quotas active. This will prevent the complete " +
500                    "erasure of unused space and will pose a security concern"), LogLevel.Warning));
501
502            //Get the erasure method if the user specified he wants the default.
503            ErasureMethod method = target.Method;
504           
505            //Erase the cluster tips of every file on the drive.
506            if (target.EraseClusterTips)
507            {
508                progress.Event.CurrentTargetStatus = S._("Searching for files' cluster tips...");
509                progress.Event.CurrentTargetTotalPasses = method.Passes;
510                progress.Event.CurrentItemProgress = -1.0f;
511                progress.Event.TimeLeft = new TimeSpan(0, 0, -1);
512
513                //Start counting statistics
514                ProgressManager tipProgress = new ProgressManager();
515                tipProgress.Start();
516
517                //Define the callback handlers
518                ClusterTipsSearchProgress searchProgress = delegate(string path)
519                    {
520                        progress.Event.CurrentItemName = path;
521                        task.OnProgressChanged(progress.Event);
522
523                        lock (currentTask)
524                            if (currentTask.Canceled)
525                                throw new FatalException(S._("The task was cancelled."));
526                    };
527
528                ClusterTipsEraseProgress eraseProgress =
529                    delegate(int currentFile, int totalFiles, string currentFilePath)
530                    {
531                        tipProgress.Total = totalFiles;
532                        tipProgress.Completed = currentFile;
533
534                        progress.Event.CurrentTargetStatus = S._("Erasing cluster tips...");
535                        progress.Event.CurrentItemName = currentFilePath;
536                        progress.Event.CurrentItemProgress = tipProgress.Progress;
537                        progress.Event.CurrentTargetProgress = progress.Event.CurrentItemProgress / 10;
538                        progress.Event.TimeLeft = tipProgress.TimeLeft;
539                        task.OnProgressChanged(progress.Event);
540
541                        lock (currentTask)
542                            if (currentTask.Canceled)
543                                throw new FatalException(S._("The task was cancelled."));
544                    };
545
546                EraseClusterTips(task, target, method, searchProgress, eraseProgress);
547            }
548
549            //Make a folder to dump our temporary files in
550            DirectoryInfo info = new DirectoryInfo(target.Drive);
551            VolumeInfo volInfo = VolumeInfo.FromMountpoint(target.Drive);
552            FileSystem fsManager = FileSystem.Get(volInfo);
553            info = info.CreateSubdirectory(Path.GetFileName(
554                FileSystem.GenerateRandomFileName(info, 18)));
555
556            try
557            {
558                //Set the folder's compression flag off since we want to use as much
559                //space as possible
560                if (Eraser.Util.File.IsCompressed(info.FullName))
561                    Eraser.Util.File.SetCompression(info.FullName, false);
562
563                //Continue creating files while there is free space.
564                progress.Event.CurrentTargetStatus = S._("Erasing unused space...");
565                progress.Event.CurrentItemName = target.Drive;
566                task.OnProgressChanged(progress.Event);
567                while (volInfo.AvailableFreeSpace > 0)
568                {
569                    //Generate a non-existant file name
570                    string currFile = FileSystem.GenerateRandomFileName(info, 18);
571
572                    //Create the stream
573                    using (FileStream stream = new FileStream(currFile, FileMode.CreateNew,
574                        FileAccess.Write, FileShare.None, 8, FileOptions.WriteThrough))
575                    {
576                        //Set the length of the file to be the amount of free space left
577                        //or the maximum size of one of these dumps.
578                        long streamLength = Math.Min(ErasureMethod.FreeSpaceFileUnit,
579                            volInfo.AvailableFreeSpace);
580
581                        //Handle IO exceptions gracefully, because the filesystem
582                        //may require more space than demanded by us for file allocation.
583                        while (true)
584                            try
585                            {
586                                stream.SetLength(streamLength);
587                                break;
588                            }
589                            catch (IOException)
590                            {
591                                if (streamLength > volInfo.ClusterSize)
592                                    streamLength -= volInfo.ClusterSize;
593                                else
594                                    throw;
595                            }
596
597                        //Then run the erase task
598                        method.Erase(stream, long.MaxValue,
599                            PrngManager.GetInstance(ManagerLibrary.Settings.ActivePrng),
600                            delegate(long lastWritten, int currentPass)
601                            {
602                                progress.Completed += lastWritten;
603                                progress.Event.CurrentItemPass = currentPass;
604                                progress.Event.CurrentItemProgress = progress.Progress;
605                                if (target.EraseClusterTips)
606                                    progress.Event.CurrentTargetProgress = (float)
607                                        (0.1f + progress.Event.CurrentItemProgress * 0.8f);
608                                else
609                                    progress.Event.CurrentTargetProgress = (float)
610                                        (progress.Event.CurrentItemProgress * 0.9f);
611                                progress.Event.TimeLeft = progress.TimeLeft;
612                                task.OnProgressChanged(progress.Event);
613
614                                lock (currentTask)
615                                    if (currentTask.Canceled)
616                                        throw new FatalException(S._("The task was cancelled."));
617                            }
618                        );
619                    }
620                }
621
622                //Erase old resident file system table files
623                progress.Event.CurrentItemName = S._("Old resident file system table files");
624                task.OnProgressChanged(progress.Event);
625                fsManager.EraseOldFileSystemResidentFiles(volInfo, method, null);
626            }
627            finally
628            {
629                //Remove the folder holding all our temporary files.
630                progress.Event.CurrentTargetStatus = S._("Removing temporary files...");
631                task.OnProgressChanged(progress.Event);
632                fsManager.DeleteFolder(info);
633            }
634
635            //Then clean the old file system entries
636            progress.Event.CurrentTargetStatus = S._("Erasing unused directory structures...");
637            ProgressManager fsEntriesProgress = new ProgressManager();
638            fsEntriesProgress.Start();
639            fsManager.EraseDirectoryStructures(volInfo,
640                delegate(int currentFile, int totalFiles)
641                {
642                    lock (currentTask)
643                        if (currentTask.Canceled)
644                            throw new FatalException(S._("The task was cancelled."));
645
646                    //Compute the progress
647                    fsEntriesProgress.Total = totalFiles;
648                    fsEntriesProgress.Completed = currentFile;
649
650                    //Set the event parameters, then broadcast the progress event.
651                    progress.Event.TimeLeft = fsEntriesProgress.TimeLeft;
652                    progress.Event.CurrentItemProgress = fsEntriesProgress.Progress;
653                    progress.Event.CurrentTargetProgress = (float)(
654                        0.9 + progress.Event.CurrentItemProgress / 10);
655                    task.OnProgressChanged(progress.Event);
656                }
657            );
658        }
659
660        private delegate void SubFoldersHandler(DirectoryInfo info);
661        private delegate void ClusterTipsSearchProgress(string currentPath);
662        private delegate void ClusterTipsEraseProgress(int currentFile, int totalFiles,
663            string currentFilePath);
664
665        private static void EraseClusterTips(Task task, UnusedSpaceTarget target,
666            ErasureMethod method, ClusterTipsSearchProgress searchCallback,
667            ClusterTipsEraseProgress eraseCallback)
668        {
669            //List all the files which can be erased.
670            List<string> files = new List<string>();
671            SubFoldersHandler subFolders = null;
672
673            subFolders = delegate(DirectoryInfo info)
674            {
675                //Check if we've been cancelled
676                if (task.Canceled)
677                    throw new FatalException(S._("The task was cancelled."));
678
679                try
680                {
681                    //Skip this directory if it is a reparse point
682                    if ((info.Attributes & FileAttributes.ReparsePoint) != 0)
683                    {
684                        task.Log.LastSessionEntries.Add(new LogEntry(S._("Files in {0} did " +
685                            "not have their cluster tips erased because it is a hard link or " +
686                            "a symbolic link.", info.FullName), LogLevel.Information));
687                        return;
688                    }
689
690                    foreach (FileInfo file in info.GetFiles())
691                        if (Util.File.IsProtectedSystemFile(file.FullName))
692                            task.Log.LastSessionEntries.Add(new LogEntry(S._("{0} did not have " +
693                                "its cluster tips erased, because it is a system file",
694                                file.FullName), LogLevel.Information));
695                        else if ((file.Attributes & FileAttributes.ReparsePoint) != 0)
696                            task.Log.LastSessionEntries.Add(new LogEntry(S._("{0} did not have " +
697                                "its cluster tips erased because it is a hard link or a " +
698                                "symbolic link.", file.FullName), LogLevel.Information));
699                        else if ((file.Attributes & FileAttributes.Compressed) != 0 ||
700                            (file.Attributes & FileAttributes.Encrypted) != 0 ||
701                            (file.Attributes & FileAttributes.SparseFile) != 0)
702                        {
703                            task.Log.LastSessionEntries.Add(new LogEntry(S._("{0} did not have " +
704                                "its cluster tips erased because it is compressed, encrypted " +
705                                "or a sparse file.", file.FullName), LogLevel.Information));
706                        }
707                        else
708                        {
709                            try
710                            {
711                                foreach (string i in Util.File.GetADSes(file))
712                                    files.Add(file.FullName + ':' + i);
713
714                                files.Add(file.FullName);
715                            }
716                            catch (UnauthorizedAccessException e)
717                            {
718                                task.Log.LastSessionEntries.Add(new LogEntry(S._("{0} did not " +
719                                    "have its cluster tips erased because of the following " +
720                                    "error: {1}", info.FullName, e.Message), LogLevel.Error));
721                            }
722                            catch (IOException e)
723                            {
724                                task.Log.LastSessionEntries.Add(new LogEntry(S._("{0} did not " +
725                                    "have its cluster tips erased because of the following " +
726                                    "error: {1}", info.FullName, e.Message), LogLevel.Error));
727                            }
728                        }
729
730                    foreach (DirectoryInfo subDirInfo in info.GetDirectories())
731                    {
732                        searchCallback(subDirInfo.FullName);
733                        subFolders(subDirInfo);
734                    }
735                }
736                catch (UnauthorizedAccessException e)
737                {
738                    task.Log.LastSessionEntries.Add(new LogEntry(S._("{0} did not have its " +
739                        "cluster tips erased because of the following error: {1}",
740                        info.FullName, e.Message), LogLevel.Error));
741                }
742                catch (IOException e)
743                {
744                    task.Log.LastSessionEntries.Add(new LogEntry(S._("{0} did not have its " +
745                        "cluster tips erased because of the following error: {1}",
746                        info.FullName, e.Message), LogLevel.Error));
747                }
748            };
749
750            subFolders(new DirectoryInfo(target.Drive));
751
752            //For every file, erase the cluster tips.
753            for (int i = 0, j = files.Count; i != j; ++i)
754            {
755                //Get the file attributes for restoring later
756                StreamInfo info = new StreamInfo(files[i]);
757                FileAttributes fileAttr = info.Attributes;
758
759                try
760                {
761                    //Reset the file attributes.
762                    info.Attributes = FileAttributes.Normal;
763                    EraseFileClusterTips(files[i], method);
764                }
765                catch (IOException e)
766                {
767                    task.Log.LastSessionEntries.Add(new LogEntry(S._("{0} did not have its " +
768                        "cluster tips erased. The error returned was: {1}", files[i],
769                        e.Message), LogLevel.Error));
770                }
771                finally
772                {
773                    info.Attributes = fileAttr;
774                }
775                eraseCallback(i, files.Count, files[i]);
776            }
777        }
778
779        /// <summary>
780        /// Erases the cluster tips of the given file.
781        /// </summary>
782        /// <param name="file">The file to erase.</param>
783        /// <param name="method">The erasure method to use.</param>
784        private static void EraseFileClusterTips(string file, ErasureMethod method)
785        {
786            //Get the file access times
787            StreamInfo streamInfo = new StreamInfo(file);
788            DateTime lastAccess = streamInfo.LastAccessTime;
789            DateTime lastWrite = streamInfo.LastWriteTime;
790            DateTime created = streamInfo.CreationTime;
791
792            //And get the file lengths to know how much to overwrite
793            long fileArea = GetFileArea(file);
794            long fileLength = streamInfo.Length;
795
796            //If the file length equals the file area there is no cluster tip to overwrite
797            if (fileArea == fileLength)
798                return;
799
800            try
801            {
802                //Otherwise, create the stream, lengthen the file, then tell the erasure
803                //method to erase the cluster tips.
804                using (FileStream stream = streamInfo.Open(FileMode.Open, FileAccess.Write,
805                    FileShare.None, FileOptions.WriteThrough))
806                {
807                    try
808                    {
809                        stream.SetLength(fileArea);
810                        stream.Seek(fileLength, SeekOrigin.Begin);
811
812                        //Erase the file
813                        method.Erase(stream, long.MaxValue, PrngManager.GetInstance(
814                            ManagerLibrary.Settings.ActivePrng), null);
815                    }
816                    finally
817                    {
818                        //Make sure the file length is restored!
819                        stream.SetLength(fileLength);
820                    }
821                }
822            }
823            finally
824            {
825                //Reset the file times
826                streamInfo.LastAccessTime = lastAccess;
827                streamInfo.LastWriteTime = lastWrite;
828                streamInfo.CreationTime = created;
829            }
830        }
831        #endregion
832
833        #region Filesystem Object erasure functions
834        /// <summary>
835        /// Erases a file or folder on the volume.
836        /// </summary>
837        /// <param name="task">The task currently being processed.</param>
838        /// <param name="target">The target of the erasure.</param>
839        /// <param name="progress">The progress manager for the current task.</param>
840        private void EraseFilesystemObject(Task task, FileSystemObjectTarget target,
841            TaskProgressManager progress)
842        {
843            //Retrieve the list of files to erase.
844            long dataTotal = 0;
845            List<string> paths = target.GetPaths(out dataTotal);
846
847            //Get the erasure method if the user specified he wants the default.
848            ErasureMethod method = target.Method;
849
850            //Calculate the total amount of data required to finish the wipe.
851            dataTotal = method.CalculateEraseDataSize(paths, dataTotal);
852
853            //Set the event's current target status.
854            progress.Event.CurrentTargetStatus = S._("Erasing files...");
855
856            //Iterate over every path, and erase the path.
857            for (int i = 0; i < paths.Count; ++i)
858            {
859                //Update the task progress
860                progress.Event.CurrentTargetProgress = i / (float)paths.Count;
861                progress.Event.CurrentTarget = target;
862                progress.Event.CurrentItemName = paths[i];
863                progress.Event.CurrentItemProgress = 0;
864                progress.Event.CurrentTargetTotalPasses = method.Passes;
865                task.OnProgressChanged(progress.Event);
866               
867                //Get the filesystem provider to handle the secure file erasures
868                StreamInfo info = new StreamInfo(paths[i]);
869                FileSystem fsManager = FileSystem.Get(VolumeInfo.FromMountpoint(info.DirectoryName));
870                bool isReadOnly = false;
871               
872                try
873                {
874                    //Remove the read-only flag, if it is set.
875                    if (isReadOnly = info.IsReadOnly)
876                        info.IsReadOnly = false;
877
878                    //Make sure the file does not have any attributes which may affect
879                    //the erasure process
880                    if ((info.Attributes & FileAttributes.Compressed) != 0 || 
881                        (info.Attributes & FileAttributes.Encrypted) != 0 ||
882                        (info.Attributes & FileAttributes.SparseFile) != 0)
883                    {
884                        //Log the error
885                        task.Log.LastSessionEntries.Add(new LogEntry(S._("The file {0} could " +
886                            "not be erased because the file was either compressed, encrypted or " +
887                            "a sparse file.", info.FullName), LogLevel.Error));
888                    }
889
890                    //Create the file stream, and call the erasure method to write to
891                    //the stream.
892                    long fileArea = GetFileArea(paths[i]);
893                    using (FileStream strm = info.Open(FileMode.Open, FileAccess.Write,
894                        FileShare.None, FileOptions.WriteThrough))
895                    {
896                        //Set the end of the stream after the wrap-round the cluster size
897                        strm.SetLength(fileArea);
898
899                        //If the stream is empty, there's nothing to overwrite. Continue
900                        //to the next entry
901                        if (strm.Length != 0)
902                        {
903                            //Then erase the file.
904                            long itemWritten = 0,
905                                 itemTotal = method.CalculateEraseDataSize(null, strm.Length);
906                            method.Erase(strm, long.MaxValue,
907                                PrngManager.GetInstance(ManagerLibrary.Settings.ActivePrng),
908                                delegate(long lastWritten, int currentPass)
909                                {
910                                    dataTotal -= lastWritten;
911                                    progress.Completed += lastWritten;
912                                    progress.Event.CurrentItemPass = currentPass;
913                                    progress.Event.CurrentItemProgress = (float)
914                                        ((itemWritten += lastWritten) / (float)itemTotal);
915                                    progress.Event.CurrentTargetProgress =
916                                        (i + progress.Event.CurrentItemProgress) /
917                                        (float)paths.Count;
918                                    progress.Event.TimeLeft = progress.TimeLeft;
919                                    task.OnProgressChanged(progress.Event);
920
921                                    lock (currentTask)
922                                        if (currentTask.Canceled)
923                                            throw new FatalException(S._("The task was cancelled."));
924                                }
925                            );
926                        }
927
928                        //Set the length of the file to 0.
929                        strm.Seek(0, SeekOrigin.Begin);
930                        strm.SetLength(0);
931                    }
932
933                    //Remove the file.
934                    FileInfo fileInfo = info.File;
935                    if (fileInfo != null)
936                        fsManager.DeleteFile(fileInfo);
937                }
938                catch (UnauthorizedAccessException)
939                {
940                    task.Log.LastSessionEntries.Add(new LogEntry(S._("The file {0} could not " +
941                        "be erased because the file's permissions prevent access to the file.",
942                        info.FullName), LogLevel.Error));
943                }
944                catch (FileLoadException)
945                {
946                    List<OpenHandle> openHandles = OpenHandle.Items;
947                    List<System.Diagnostics.Process> processes = new List<System.Diagnostics.Process>();
948                    foreach (OpenHandle handle in openHandles)
949                        if (handle.Path == paths[i])
950                            processes.Add(System.Diagnostics.Process.GetProcessById(handle.ProcessId));
951
952                    StringBuilder processStr = new StringBuilder();
953                    foreach (System.Diagnostics.Process process in processes)
954                        processStr.AppendFormat(System.Globalization.CultureInfo.InvariantCulture,
955                            "{0}, ", process.MainModule.FileName);
956
957                    task.Log.LastSessionEntries.Add(new LogEntry(S._(
958                        "Could not force closure of file \"{0}\" (locked by {1})",
959                        paths[i], processStr.ToString().Remove(processStr.Length - 2)), LogLevel.Error));
960                }
961                finally
962                {
963                    //Re-set the read-only flag if the file exists (i.e. there was an error)
964                    if (isReadOnly && info.Exists && !info.IsReadOnly)
965                        info.IsReadOnly = isReadOnly;
966                }
967            }
968
969            //If the user requested a folder removal, do it.
970            if (target is FolderTarget)
971            {
972                progress.Event.CurrentTargetStatus = S._("Removing folders...");
973                task.OnProgressChanged(progress.Event);
974
975                FolderTarget fldr = (FolderTarget)target;
976                if (fldr.DeleteIfEmpty)
977                {
978                    DirectoryInfo info = new DirectoryInfo(fldr.Path);
979
980                    //See if this is the root of a volume.
981                    bool isVolumeRoot = info.Parent == null;
982                    foreach (VolumeInfo volume in VolumeInfo.Volumes)
983                        foreach (string mountPoint in volume.MountPoints)
984                            if (info.FullName == mountPoint)
985                                isVolumeRoot = true;
986
987                    //If the folder is a mount point, then don't delete it. If it isn't,
988                    //search for files under the folder to see if it is empty.
989                    if (!isVolumeRoot && info.Exists && info.GetFiles("*", SearchOption.AllDirectories).Length == 0)
990                    {
991                        FileSystem fsManager = FileSystem.Get(VolumeInfo.FromMountpoint(fldr.Path));
992                        fsManager.DeleteFolder(info);
993                    }
994                }
995            }
996
997            //If the user was erasing the recycle bin, clear the bin.
998            if (target is RecycleBinTarget)
999            {
1000                progress.Event.CurrentTargetStatus = S._("Emptying recycle bin...");
1001                task.OnProgressChanged(progress.Event);
1002
1003                ShellApi.EmptyRecycleBin(EmptyRecycleBinOptions.NoConfirmation |
1004                    EmptyRecycleBinOptions.NoProgressUI | EmptyRecycleBinOptions.NoSound);
1005            }
1006        }
1007
1008        /// <summary>
1009        /// Retrieves the size of the file on disk, calculated by the amount of
1010        /// clusters allocated by it.
1011        /// </summary>
1012        /// <param name="filePath">The path to the file.</param>
1013        /// <returns>The area of the file.</returns>
1014        private static long GetFileArea(string filePath)
1015        {
1016            StreamInfo info = new StreamInfo(filePath);
1017            VolumeInfo volume = VolumeInfo.FromMountpoint(info.Directory.FullName);
1018            long clusterSize = volume.ClusterSize;
1019            return (info.Length + (clusterSize - 1)) & ~(clusterSize - 1);
1020        }
1021        #endregion
1022
1023        /// <summary>
1024        /// The thread object.
1025        /// </summary>
1026        private Thread thread;
1027
1028        /// <summary>
1029        /// The lock preventing concurrent access for the tasks list and the
1030        /// tasks queue.
1031        /// </summary>
1032        private object tasksLock = new object();
1033
1034        /// <summary>
1035        /// The list of tasks. Includes all immediate, reboot, and recurring tasks
1036        /// </summary>
1037        private Dictionary<uint, Task> tasks = new Dictionary<uint, Task>();
1038
1039        /// <summary>
1040        /// The queue of tasks. This queue is executed when the first element's
1041        /// timestamp (the key) has been past. This list assumes that all tasks
1042        /// are sorted by timestamp, smallest one first.
1043        /// </summary>
1044        private SortedList<DateTime, List<Task>> scheduledTasks =
1045            new SortedList<DateTime, List<Task>>();
1046
1047        /// <summary>
1048        /// The currently executing task.
1049        /// </summary>
1050        Task currentTask;
1051
1052        /// <summary>
1053        /// An automatically reset event allowing the addition of new tasks to
1054        /// interrupt the thread's sleeping state waiting for the next recurring
1055        /// task to be due.
1056        /// </summary>
1057        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
1058    }
1059
1060    public class DirectExecutorTasksCollection : ExecutorTasksCollection
1061    {
1062        /// <summary>
1063        /// Constructor.
1064        /// </summary>
1065        /// <param name="executor">The <see cref="DirectExecutor"/> object owning
1066        /// this list.</param>
1067        public DirectExecutorTasksCollection(DirectExecutor executor)
1068            : base(executor)
1069        {
1070        }
1071
1072        #region IList<Task> Members
1073        public override int IndexOf(Task item)
1074        {
1075            return list.IndexOf(item);
1076        }
1077
1078        public override void Insert(int index, Task item)
1079        {
1080            item.Executor = Owner;
1081            lock (list)
1082                list.Insert(index, item);
1083
1084            //Call all the event handlers who registered to be notified of tasks
1085            //being added.
1086            Owner.OnTaskAdded(new TaskEventArgs(item));
1087
1088            //If the task is scheduled to run now, break the waiting thread and
1089            //run it immediately
1090            if (item.Schedule == Schedule.RunNow)
1091            {
1092                Owner.QueueTask(item);
1093            }
1094            //If the task is scheduled, add the next execution time to the list
1095            //of schduled tasks.
1096            else if (item.Schedule != Schedule.RunOnRestart)
1097            {
1098                Owner.ScheduleTask(item);
1099            }
1100        }
1101
1102        public override void RemoveAt(int index)
1103        {
1104            lock (list)
1105            {
1106                Task task = list[index];
1107                task.Cancel();
1108                task.Executor = null;
1109                list.RemoveAt(index);
1110
1111                //Call all event handlers registered to be notified of task deletions.
1112                Owner.OnTaskDeleted(new TaskEventArgs(task));
1113            }
1114        }
1115
1116        public override Task this[int index]
1117        {
1118            get
1119            {
1120                return list[index];
1121            }
1122            set
1123            {
1124                list[index] = value;
1125            }
1126        }
1127        #endregion
1128
1129        #region ICollection<Task> Members
1130        public override void Add(Task item)
1131        {
1132            Insert(Count, item);
1133        }
1134
1135        public override void Clear()
1136        {
1137            foreach (Task task in list)
1138                Remove(task);
1139        }
1140
1141        public override bool Contains(Task item)
1142        {
1143            return list.Contains(item);
1144        }
1145
1146        public override void CopyTo(Task[] array, int arrayIndex)
1147        {
1148            list.CopyTo(array, arrayIndex);
1149        }
1150
1151        public override int Count
1152        {
1153            get { return list.Count; }
1154        }
1155
1156        public override bool Remove(Task item)
1157        {
1158            lock (list)
1159            {
1160                int index = list.IndexOf(item);
1161                if (index < 0)
1162                    return false;
1163
1164                RemoveAt(index);
1165            }
1166
1167            return true;
1168        }
1169        #endregion
1170
1171        #region IEnumerable<Task> Members
1172        public override IEnumerator<Task> GetEnumerator()
1173        {
1174            return list.GetEnumerator();
1175        }
1176        #endregion
1177
1178        public override void SaveToStream(Stream stream)
1179        {
1180            lock (list)
1181                new BinaryFormatter().Serialize(stream, list);
1182        }
1183
1184        public override void LoadFromStream(Stream stream)
1185        {
1186            //Load the list into the dictionary
1187            StreamingContext context = new StreamingContext(
1188                StreamingContextStates.All, Owner);
1189            List<Task> deserialised = (List<Task>)new BinaryFormatter(null, context).Deserialize(stream);
1190            list.AddRange(deserialised);
1191
1192            foreach (Task task in deserialised)
1193            {
1194                Owner.OnTaskAdded(new TaskEventArgs(task));
1195                if (task.Schedule is RecurringSchedule)
1196                    Owner.ScheduleTask(task);
1197            }
1198        }
1199
1200        /// <summary>
1201        /// The data store for this object.
1202        /// </summary>
1203        private List<Task> list = new List<Task>();
1204    }
1205}
Note: See TracBrowser for help on using the repository browser.