source: branches/eraser6/Manager/DirectExecutor.cs @ 915

Revision 915, 32.3 KB checked in by lowjoel, 5 years ago (diff)

-Don't nest publicly accessible classes in classes.
-Camelcasing for abbreviations
-Abstract class constructors should be protected

  • 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;
34
35namespace Eraser.Manager
36{
37    /// <summary>
38    /// The DirectExecutor class is used by the Eraser GUI directly when the program
39    /// is run without the help of a Service.
40    /// </summary>
41    public class DirectExecutor : Executor
42    {
43        public DirectExecutor()
44        {
45            thread = new Thread(Main);
46        }
47
48        protected override void Dispose(bool disposing)
49        {
50            thread.Abort();
51            schedulerInterrupt.Set();
52            schedulerInterrupt.Close();
53            base.Dispose(disposing);
54        }
55
56        public override void Run()
57        {
58            thread.CurrentUICulture = Thread.CurrentThread.CurrentUICulture;
59            thread.Start();
60        }
61
62        public override void AddTask(Task task)
63        {
64            lock (unusedIdsLock)
65            {
66                if (unusedIds.Count != 0)
67                {
68                    task.Id = unusedIds[0];
69                    unusedIds.RemoveAt(0);
70                }
71                else
72                    task.Id = ++nextId;
73            }
74
75            //Set the executor of the task
76            task.Executor = this;
77
78            //Add the task to the set of tasks
79            lock (tasksLock)
80                tasks.Add(task.Id, task);
81
82            //Call all the event handlers who registered to be notified of tasks
83            //being added.
84            OnTaskAdded(task);
85
86            //If the task is scheduled to run now, break the waiting thread and
87            //run it immediately
88            if (task.Schedule == Schedule.RunNow)
89            {
90                QueueTask(task);
91            }
92            //If the task is scheduled, add the next execution time to the list
93            //of schduled tasks.
94            else if (task.Schedule != Schedule.RunOnRestart)
95            {
96                ScheduleTask(task);
97            }
98        }
99
100        public override bool DeleteTask(uint taskId)
101        {
102            lock (tasksLock)
103            {
104                if (!tasks.ContainsKey(taskId))
105                    return false;
106
107                Task task = tasks[taskId];
108                lock (unusedIdsLock)
109                    unusedIds.Add(taskId);
110                tasks.Remove(taskId);
111
112                for (int i = 0; i != scheduledTasks.Count; )
113                    if (scheduledTasks.Values[i].Id == taskId)
114                        scheduledTasks.RemoveAt(i);
115                    else
116                        ++i;
117
118                //Call all event handlers registered to be notified of task deletions.
119                OnTaskDeleted(task);
120            }
121
122            return true;
123        }
124
125        public override void ReplaceTask(Task task)
126        {
127            lock (tasksLock)
128            {
129                //Replace the task in the global set
130                if (!tasks.ContainsKey(task.Id))
131                    return;
132
133                tasks[task.Id] = task;
134
135                //Then replace the task if it is in the queue
136                for (int i = 0; i != scheduledTasks.Count; ++i)
137                    if (scheduledTasks.Values[i].Id == task.Id)
138                    {
139                        scheduledTasks.RemoveAt(i);
140                        if (task.Schedule is RecurringSchedule)
141                            ScheduleTask(task);
142                        else if (task.Schedule == Schedule.RunNow)
143                            QueueTask(task);
144                    }
145            }
146        }
147
148        public override void QueueTask(Task task)
149        {
150            lock (tasksLock)
151            {
152                //Set the task variable to indicate that the task is already
153                //waiting to be executed.
154                task.Queued = true;
155
156                //Queue the task to be run immediately.
157                scheduledTasks.Add(DateTime.Now, task);
158                schedulerInterrupt.Set();
159            }
160        }
161
162        public override void ScheduleTask(Task task)
163        {
164            RecurringSchedule schedule = (RecurringSchedule)task.Schedule;
165            if (schedule.MissedPreviousSchedule &&
166                ManagerLibrary.Instance.Settings.ExecuteMissedTasksImmediately)
167                //OK, we've missed the schedule and the user wants the thing
168                //to follow up immediately.
169                scheduledTasks.Add(DateTime.Now, task);
170            else
171                scheduledTasks.Add(schedule.NextRun, task);
172        }
173
174        public override void QueueRestartTasks()
175        {
176            lock (tasksLock)
177            {
178                foreach (Task task in tasks.Values)
179                    if (task.Schedule == Schedule.RunOnRestart)
180                        QueueTask(task);
181            }
182        }
183
184        public override void CancelTask(Task task)
185        {
186            lock (currentTask)
187            {
188                if (currentTask == task)
189                {
190                    currentTask.Canceled = true;
191                    return;
192                }
193            }
194
195            lock (tasksLock)
196                for (int i = 0; i != scheduledTasks.Count; ++i)
197                    if (scheduledTasks.Values[i] == task)
198                    {
199                        scheduledTasks.RemoveAt(i);
200                        return;
201                    }
202
203            throw new ArgumentOutOfRangeException(S._("The task to be cancelled must " +
204                "either be currently executing or queued."));
205        }
206
207        public override Task GetTask(uint taskId)
208        {
209            lock (tasksLock)
210            {
211                if (!tasks.ContainsKey(taskId))
212                    return null;
213                return tasks[taskId];
214            }
215        }
216
217        public override ICollection<Task> GetTasks()
218        {
219            lock (tasksLock)
220            {
221                Task[] result = new Task[tasks.Count];
222                tasks.Values.CopyTo(result, 0);
223                return new List<Task>(result);
224            }
225        }
226
227        public override void SaveTaskList(Stream stream)
228        {
229            lock (tasksLock)
230                new BinaryFormatter().Serialize(stream, tasks);
231        }
232
233        public override void LoadTaskList(Stream stream)
234        {
235            lock (tasksLock)
236            {
237                //Load the list into the dictionary
238                tasks = (Dictionary<uint, Task>)new BinaryFormatter().Deserialize(stream);
239
240                //Ignore the next portion if there are no tasks
241                if (tasks.Count == 0)
242                    return;
243
244                lock (unusedIdsLock)
245                {
246                    //Find gaps in the numbering
247                    nextId = 1;
248                    foreach (uint id in tasks.Keys)
249                    {
250                        Task task = tasks[id];
251                        task.Executor = this;
252                        while (id > nextId)
253                            unusedIds.Add(nextId++);
254                        ++nextId;
255
256                        //Check if the task is recurring. If it is, check if we missed it.
257                        if (task.Schedule is RecurringSchedule)
258                            ScheduleTask(task);
259                    }
260
261                    //Decrement the ID, since the next ID will be preincremented
262                    //before use.
263                    --nextId;
264                }
265            }
266        }
267
268        /// <summary>
269        /// The thread entry point for this object. This object operates on a queue
270        /// and hence the thread will sequentially execute tasks.
271        /// </summary>
272        private void Main()
273        {
274            //The waiting thread will utilize a polling loop to check for new
275            //scheduled tasks. This will be checked every 30 seconds. However,
276            //when the thread is waiting for a new task, it can be interrupted.
277            while (thread.ThreadState != ThreadState.AbortRequested)
278            {
279                //Check for a new task
280                Task task = null;
281                lock (tasksLock)
282                {
283                    if (scheduledTasks.Count != 0 &&
284                        (scheduledTasks.Values[0].Schedule == Schedule.RunNow ||
285                         scheduledTasks.Keys[0] <= DateTime.Now))
286                    {
287                        task = scheduledTasks.Values[0];
288                        scheduledTasks.RemoveAt(0);
289                    }
290                }
291
292                if (task != null)
293                {
294                    //Set the currently executing task.
295                    currentTask = task;
296
297                    try
298                    {
299                        //Prevent the system from sleeping.
300                        KernelAPI.SetThreadExecutionState(KernelAPI.EXECUTION_STATE.ES_CONTINUOUS |
301                            KernelAPI.EXECUTION_STATE.ES_SYSTEM_REQUIRED);
302
303                        //Broadcast the task started event.
304                        task.Queued = false;
305                        task.Canceled = false;
306                        task.OnTaskStarted(new TaskEventArgs(task));
307                        OnTaskProcessing(task);
308
309                        //Start a new log session to separate this session's events
310                        //from previous ones.
311                        task.Log.NewSession();
312
313                        //Run the task
314                        TaskProgressManager progress = new TaskProgressManager(currentTask);
315                        foreach (ErasureTarget target in task.Targets)
316                            try
317                            {
318                                progress.Event.CurrentTarget = target;
319                                ++progress.Event.CurrentTargetIndex;
320                                if (target is UnusedSpaceTarget)
321                                    EraseUnusedSpace(task, (UnusedSpaceTarget)target, progress);
322                                else if (target is FileSystemObjectTarget)
323                                    EraseFilesystemObject(task, (FileSystemObjectTarget)target, progress);
324                                else
325                                    throw new ArgumentException(S._("Unknown erasure target."));
326                            }
327                            catch (FatalException)
328                            {
329                                throw;
330                            }
331                            catch (Exception e)
332                            {
333                                task.Log.Add(new LogEntry(e.Message, LogLevel.Error));
334                            }
335                    }
336                    catch (FatalException e)
337                    {
338                        task.Log.Add(new LogEntry(e.Message, LogLevel.Fatal));
339                    }
340                    catch (Exception e)
341                    {
342                        task.Log.Add(new LogEntry(e.Message, LogLevel.Error));
343                    }
344                    finally
345                    {
346                        //Allow the system to sleep again.
347                        KernelAPI.SetThreadExecutionState(KernelAPI.EXECUTION_STATE.ES_CONTINUOUS);
348
349                        //If the task is a recurring task, reschedule it since we are done.
350                        if (task.Schedule is RecurringSchedule)
351                            ((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
352
353                        //If the task is an execute on restart task, it is only run
354                        //once and can now be restored to an immediately executed task
355                        if (task.Schedule == Schedule.RunOnRestart)
356                            task.Schedule = Schedule.RunNow;
357
358                        //And the task finished event.
359                        task.OnTaskFinished(new TaskEventArgs(task));
360                        OnTaskProcessed(currentTask);
361                    }
362
363                    currentTask = null;
364                }
365
366                //Wait for half a minute to check for the next scheduled task.
367                schedulerInterrupt.WaitOne(30000, false);
368            }
369        }
370
371        /// <summary>
372        /// Manages the progress for any operation.
373        /// </summary>
374        private class ProgressManager
375        {
376            /// <summary>
377            /// Starts measuring the speed of the task.
378            /// </summary>
379            public void Start()
380            {
381                startTime = DateTime.Now;
382            }
383
384            /// <summary>
385            /// Tracks the amount of the operation completed.
386            /// </summary>
387            public long Completed
388            {
389                get
390                {
391                    return completed;
392                }
393                set
394                {
395                    lastCompleted += value - completed;
396                    completed = value;
397                }
398            }
399
400            /// <summary>
401            /// The amount to reach before the operation completes.
402            /// </summary>
403            public long Total
404            {
405                get
406                {
407                    return total;
408                }
409                set
410                {
411                    total = value;
412                }
413            }
414
415            /// <summary>
416            /// Gets the percentage of the operation completed.
417            /// </summary>
418            public float Progress
419            {
420                get
421                {
422                    return (float)((double)Completed / Total);
423                }
424            }
425
426            /// <summary>
427            /// Computes the speed of the erase, in units of completion per second,
428            /// based on the information collected in the previous 15 seconds.
429            /// </summary>
430            public int Speed
431            {
432                get
433                {
434                    if (DateTime.Now == startTime)
435                        return 0;
436
437                    if ((DateTime.Now - lastSpeedCalc).Seconds < 15 && lastSpeed != 0)
438                        return lastSpeed;
439
440                    lastSpeed = (int)(lastCompleted / (DateTime.Now - lastSpeedCalc).TotalSeconds);
441                    lastSpeedCalc = DateTime.Now;
442                    lastCompleted = 0;
443                    return lastSpeed;
444                }
445            }
446
447            /// <summary>
448            /// Calculates the estimated amount of time left based on the total
449            /// amount of information to erase and the current speed of the erase
450            /// </summary>
451            public TimeSpan TimeLeft
452            {
453                get
454                {
455                    if (Speed == 0)
456                        return new TimeSpan(0, 0, -1);
457                    return new TimeSpan(0, 0, (int)((Total - Completed) / Speed));
458                }
459            }
460
461            /// <summary>
462            /// The starting time of the operation, used to determine average speed.
463            /// </summary>
464            private DateTime startTime;
465
466            /// <summary>
467            /// The last time a speed calculation was computed so that speed is not
468            /// computed too often.
469            /// </summary>
470            private DateTime lastSpeedCalc;
471
472            /// <summary>
473            /// The last calculated speed of the operation.
474            /// </summary>
475            private int lastSpeed;
476
477            /// <summary>
478            /// The amount of the operation completed since the last speed computation.
479            /// </summary>
480            private long lastCompleted;
481
482            /// <summary>
483            /// The amount of the operation completed.
484            /// </summary>
485            private long completed;
486
487            /// <summary>
488            /// The amount to reach before the operation is completed.
489            /// </summary>
490            private long total;
491        }
492
493        /// <summary>
494        /// Provides a common interface to track the progress made by the Erase functions.
495        /// </summary>
496        private class TaskProgressManager : ProgressManager
497        {
498            /// <summary>
499            /// Constructor.
500            /// </summary>
501            public TaskProgressManager(Task task)
502            {
503                foreach (ErasureTarget target in task.Targets)
504                    Total += target.TotalData;
505
506                Event = new TaskProgressEventArgs(task);
507                Start();
508            }
509
510            /// <summary>
511            /// The TaskProgressEventArgs object representing the progress of the current
512            /// task.
513            /// </summary>
514            public TaskProgressEventArgs Event
515            {
516                get
517                {
518                    return evt;
519                }
520                set
521                {
522                    evt = value;
523                }
524            }
525
526            private TaskProgressEventArgs evt;
527        }
528
529        #region Unused Space erasure functions
530        /// <summary>
531        /// Executes a unused space erase.
532        /// </summary>
533        /// <param name="task">The task currently being executed</param>
534        /// <param name="target">The target of the unused space erase.</param>
535        /// <param name="progress">The progress manager object managing the progress of the task</param>
536        private void EraseUnusedSpace(Task task, UnusedSpaceTarget target, TaskProgressManager progress)
537        {
538            //Check for sufficient privileges to run the unused space erasure.
539            if (!Permissions.IsAdministrator())
540            {
541                if (Environment.OSVersion.Platform == PlatformID.Win32NT &&
542                    Environment.OSVersion.Version >= new Version(6, 0))
543                {
544                    throw new UnauthorizedAccessException(S._("The program does not have the required " +
545                        "permissions to erase the unused space on disk. Run the program as an administrator " +
546                        "and retry the operation."));
547                }
548                else
549                    throw new UnauthorizedAccessException(S._("The program does not have the required permissions " +
550                        "to erase the unused space on disk"));
551            }
552
553            //If the user is under disk quotas, log a warning message
554            if (VolumeInfo.FromMountpoint(target.Drive).HasQuota)
555                task.Log.Add(new LogEntry(S._("The drive which is having its unused space erased " +
556                    "has disk quotas active. This will prevent the complete erasure of unused " +
557                    "space and will pose a security concern"), LogLevel.Warning));
558
559            //Get the erasure method if the user specified he wants the default.
560            ErasureMethod method = target.Method;
561           
562            //Erase the cluster tips of every file on the drive.
563            if (target.EraseClusterTips)
564            {
565                progress.Event.CurrentItemName = S._("Cluster tips");
566                progress.Event.CurrentTargetTotalPasses = method.Passes;
567                progress.Event.TimeLeft = progress.TimeLeft;
568                task.OnProgressChanged(progress.Event);
569
570                ProgressManager tipProgress = new ProgressManager();
571                tipProgress.Start();
572                EraseClusterTips(task, target, method,
573                    delegate(int currentFile, string currentFilePath, int totalFiles)
574                    {
575                        tipProgress.Total = totalFiles;
576                        tipProgress.Completed = currentFile;
577
578                        progress.Event.CurrentItemName = S._("(Tips) {0}", currentFilePath);
579                        progress.Event.CurrentItemProgress = tipProgress.Progress;
580                        progress.Event.CurrentTargetProgress = progress.Event.CurrentItemProgress / 10;
581                        progress.Event.TimeLeft = tipProgress.TimeLeft;
582                        task.OnProgressChanged(progress.Event);
583
584                        lock (currentTask)
585                            if (currentTask.Canceled)
586                                throw new FatalException(S._("The task was cancelled."));
587                    }
588                );
589            }
590
591            //Make a folder to dump our temporary files in
592            DirectoryInfo info = new DirectoryInfo(target.Drive);
593            VolumeInfo volInfo = VolumeInfo.FromMountpoint(target.Drive);
594            FileSystem fsManager = FileSystem.Get(volInfo);
595            info = info.CreateSubdirectory(FileSystem.GenerateRandomFileName(info, 18));
596
597            try
598            {
599                //Set the folder's compression flag off since we want to use as much
600                //space as possible
601                if (Eraser.Util.File.IsCompressed(info.FullName))
602                    Eraser.Util.File.SetCompression(info.FullName, false);
603
604                //Continue creating files while there is free space.
605                progress.Event.CurrentItemName = S._("Unused space");
606                task.OnProgressChanged(progress.Event);
607                while (volInfo.AvailableFreeSpace > 0)
608                {
609                    //Generate a non-existant file name
610                    string currFile = FileSystem.GenerateRandomFileName(info, 18);
611
612                    //Create the stream
613                    using (FileStream stream = new FileStream(currFile, FileMode.CreateNew,
614                        FileAccess.Write, FileShare.None, 8, FileOptions.WriteThrough))
615                    {
616                        //Set the length of the file to be the amount of free space left
617                        //or the maximum size of one of these dumps.
618                        long streamLength = Math.Min(ErasureMethod.FreeSpaceFileUnit,
619                            volInfo.AvailableFreeSpace);
620
621                        //Handle IO exceptions gracefully, because the filesystem
622                        //may require more space than demanded by us for file allocation.
623                        while (true)
624                            try
625                            {
626                                stream.SetLength(streamLength);
627                                break;
628                            }
629                            catch (IOException)
630                            {
631                                if (streamLength > volInfo.ClusterSize)
632                                    streamLength -= volInfo.ClusterSize;
633                                else
634                                    throw;
635                            }
636
637                        //Then run the erase task
638                        method.Erase(stream, long.MaxValue,
639                            PrngManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePrng),
640                            delegate(long lastWritten, int currentPass)
641                            {
642                                progress.Completed += lastWritten;
643                                progress.Event.CurrentItemPass = currentPass;
644                                progress.Event.CurrentItemProgress = progress.Progress;
645                                if (target.EraseClusterTips)
646                                    progress.Event.CurrentTargetProgress = (float)
647                                        (0.1f + progress.Event.CurrentItemProgress * 0.8f);
648                                else
649                                    progress.Event.CurrentTargetProgress = (float)
650                                        (progress.Event.CurrentItemProgress * 0.9f);
651                                progress.Event.TimeLeft = progress.TimeLeft;
652                                task.OnProgressChanged(progress.Event);
653
654                                lock (currentTask)
655                                    if (currentTask.Canceled)
656                                        throw new FatalException(S._("The task was cancelled."));
657                            }
658                        );
659                    }
660                }
661
662                //Erase old resident file system table files
663                progress.Event.CurrentItemName = S._("Old resident file system table files");
664                task.OnProgressChanged(progress.Event);
665                fsManager.EraseOldFileSystemResidentFiles(volInfo, method, null);
666            }
667            finally
668            {
669                //Remove the folder holding all our temporary files.
670                progress.Event.CurrentItemName = S._("Removing temporary files");
671                task.OnProgressChanged(progress.Event);
672                fsManager.DeleteFolder(info);
673            }
674
675            //Then clean the old file system entries
676            progress.Event.CurrentItemName = S._("Old file system entries");
677            ProgressManager fsEntriesProgress = new ProgressManager();
678            fsEntriesProgress.Start();
679            fsManager.EraseDirectoryStructures(volInfo,
680                delegate(int currentFile, int totalFiles)
681                {
682                    lock (currentTask)
683                        if (currentTask.Canceled)
684                            throw new FatalException(S._("The task was cancelled."));
685
686                    //Compute the progress
687                    fsEntriesProgress.Total = totalFiles;
688                    fsEntriesProgress.Completed = currentFile;
689
690                    //Set the event parameters, then broadcast the progress event.
691                    progress.Event.TimeLeft = fsEntriesProgress.TimeLeft;
692                    progress.Event.CurrentItemProgress = fsEntriesProgress.Progress;
693                    progress.Event.CurrentTargetProgress = (float)(
694                        0.9 + progress.Event.CurrentItemProgress / 10);
695                    task.OnProgressChanged(progress.Event);
696                }
697            );
698        }
699
700        private delegate void SubFoldersHandler(DirectoryInfo info);
701        private delegate void ClusterTipsEraseProgress(int currentFile,
702            string currentFilePath, int totalFiles);
703
704        private static void EraseClusterTips(Task task, UnusedSpaceTarget target,
705            ErasureMethod method, ClusterTipsEraseProgress callback)
706        {
707            //List all the files which can be erased.
708            List<string> files = new List<string>();
709            SubFoldersHandler subFolders = null;
710
711            subFolders = delegate(DirectoryInfo info)
712            {
713                //Check if we've been cancelled
714                if (task.Canceled)
715                    throw new FatalException(S._("The task was cancelled."));
716
717                try
718                {
719                    //Skip this directory if it is a reparse point
720                    if ((info.Attributes & FileAttributes.ReparsePoint) != 0)
721                    {
722                        task.Log.Add(new LogEntry(S._("Files in {0} did not have their cluster " +
723                            "tips erased because it is a hard link or a symbolic link.",
724                            info.FullName), LogLevel.Information));
725                        return;
726                    }
727
728                    foreach (FileInfo file in info.GetFiles())
729                        if (Util.File.IsProtectedSystemFile(file.FullName))
730                            task.Log.Add(new LogEntry(S._("{0} did not have its cluster tips " +
731                                "erased, because it is a system file", file.FullName),
732                                LogLevel.Information));
733                        else if ((file.Attributes & FileAttributes.ReparsePoint) != 0)
734                            task.Log.Add(new LogEntry(S._("{0} did not have its cluster tips " +
735                                "erased because it is a hard link or a symbolic link.",
736                                file.FullName), LogLevel.Information));
737                        else if ((file.Attributes & FileAttributes.Compressed) != 0 ||
738                            (file.Attributes & FileAttributes.Encrypted) != 0 ||
739                            (file.Attributes & FileAttributes.SparseFile) != 0)
740                        {
741                            task.Log.Add(new LogEntry(S._("{0} did not have its cluster tips " +
742                                "erased because it is compressed, encrypted or a sparse file.",
743                                file.FullName), LogLevel.Information));
744                        }
745                        else
746                        {
747                            try
748                            {
749                                foreach (string i in Util.File.GetADSes(file))
750                                    files.Add(file.FullName + ':' + i);
751
752                                files.Add(file.FullName);
753                            }
754                            catch (IOException e)
755                            {
756                                task.Log.Add(new LogEntry(S._("{0} did not have its cluster tips erased " +
757                                    "because of the following error: {1}", info.FullName, e.Message),
758                                    LogLevel.Error));
759                            }
760                        }
761
762                    foreach (DirectoryInfo subDirInfo in info.GetDirectories())
763                        subFolders(subDirInfo);
764                }
765                catch (UnauthorizedAccessException e)
766                {
767                    task.Log.Add(new LogEntry(S._("{0} did not have its cluster tips erased " +
768                        "because of the following error: {1}", info.FullName, e.Message),
769                        LogLevel.Error));
770                }
771                catch (IOException e)
772                {
773                    task.Log.Add(new LogEntry(S._("{0} did not have its cluster tips erased " +
774                        "because of the following error: {1}", info.FullName, e.Message),
775                        LogLevel.Error));
776                }
777            };
778
779            subFolders(new DirectoryInfo(target.Drive));
780
781            //For every file, erase the cluster tips.
782            for (int i = 0, j = files.Count; i != j; ++i)
783            {
784                //Get the file attributes for restoring later
785                StreamInfo info = new StreamInfo(files[i]);
786                FileAttributes fileAttr = info.Attributes;
787
788                try
789                {
790                    //Reset the file attributes.
791                    info.Attributes = FileAttributes.Normal;
792                    EraseFileClusterTips(files[i], method);
793                }
794                catch (Exception e)
795                {
796                    task.Log.Add(new LogEntry(S._("{0} did not have its cluster tips erased. " +
797                        "The error returned was: {1}", files[i], e.Message), LogLevel.Error));
798                }
799                finally
800                {
801                    info.Attributes = fileAttr;
802                }
803                callback(i, files[i], files.Count);
804            }
805        }
806
807        /// <summary>
808        /// Erases the cluster tips of the given file.
809        /// </summary>
810        /// <param name="file">The file to erase.</param>
811        /// <param name="method">The erasure method to use.</param>
812        private static void EraseFileClusterTips(string file, ErasureMethod method)
813        {
814            //Get the file access times
815            StreamInfo streamInfo = new StreamInfo(file);
816            DateTime lastAccess = DateTime.MinValue,
817                     lastWrite = DateTime.MinValue,
818                     created = DateTime.MinValue;
819            //Create the stream, lengthen the file, then tell the erasure method
820            //to erase the tips.
821            using (FileStream stream = streamInfo.Open(FileMode.Open, FileAccess.Write,
822                FileShare.None, FileOptions.WriteThrough))
823            {
824                long fileLength = stream.Length;
825                long fileArea = GetFileArea(file);
826
827                try
828                {
829                    //Get the file access times
830                    FileInfo info = streamInfo.File;
831                    if (info != null)
832                    {
833                        lastAccess = info.LastAccessTime;
834                        lastWrite = info.LastWriteTime;
835                        created = info.CreationTime;
836                    }
837
838                    stream.SetLength(fileArea);
839                    stream.Seek(fileLength, SeekOrigin.Begin);
840
841                    //Erase the file
842                    method.Erase(stream, long.MaxValue, PrngManager.GetInstance(
843                        ManagerLibrary.Instance.Settings.ActivePrng), null);
844                }
845                finally
846                {
847                    //Make sure the file is restored!
848                    stream.SetLength(fileLength);
849                }
850            }
851
852            //Set the file times
853            FileInfo fileInfo = streamInfo.File;
854            if (fileInfo != null)
855            {
856                fileInfo.LastAccessTime = lastAccess;
857                fileInfo.LastWriteTime = lastWrite;
858                fileInfo.CreationTime = created;
859            }
860        }
861        #endregion
862
863        #region Filesystem Object erasure functions
864        /// <summary>
865        /// Erases a file or folder on the volume.
866        /// </summary>
867        /// <param name="task">The task currently being processed.</param>
868        /// <param name="target">The target of the erasure.</param>
869        /// <param name="progress">The progress manager for the current task.</param>
870        private void EraseFilesystemObject(Task task, FileSystemObjectTarget target,
871            TaskProgressManager progress)
872        {
873            //Retrieve the list of files to erase.
874            long dataTotal = 0;
875            List<string> paths = target.GetPaths(out dataTotal);
876
877            //Get the erasure method if the user specified he wants the default.
878            ErasureMethod method = target.Method;
879
880            //Calculate the total amount of data required to finish the wipe.
881            dataTotal = method.CalculateEraseDataSize(paths, dataTotal);
882
883            //Iterate over every path, and erase the path.
884            for (int i = 0; i < paths.Count; ++i)
885            {
886                //Update the task progress
887                progress.Event.CurrentTargetProgress = i / (float)paths.Count;
888                progress.Event.CurrentTarget = target;
889                progress.Event.CurrentItemName = paths[i];
890                progress.Event.CurrentItemProgress = 0;
891                progress.Event.CurrentTargetTotalPasses = method.Passes;
892                task.OnProgressChanged(progress.Event);
893               
894                //Get the filesystem provider to handle the secure file erasures
895                StreamInfo info = new StreamInfo(paths[i]);
896                FileSystem fsManager = FileSystem.Get(VolumeInfo.FromMountpoint(info.DirectoryName));
897
898                //Remove the read-only flag, if it is set.
899                bool isReadOnly = false;
900                if (isReadOnly = info.IsReadOnly)
901                    info.IsReadOnly = false;
902
903                try
904                {
905                    //Make sure the file does not have any attributes which may affect
906                    //the erasure process
907                    if ((info.Attributes & FileAttributes.Compressed) != 0 || 
908                        (info.Attributes & FileAttributes.Encrypted) != 0 ||
909                        (info.Attributes & FileAttributes.SparseFile) != 0)
910                    {
911                        //Log the error
912                        task.Log.Add(new LogEntry(S._("The file {0} could not be erased " +
913                            "because the file was either compressed, encrypted or a sparse file.",
914                            info.FullName), LogLevel.Error));
915                    }
916
917                    //Create the file stream, and call the erasure method to write to
918                    //the stream.
919                    using (FileStream strm = info.Open(FileMode.Open, FileAccess.Write,
920                        FileShare.None, FileOptions.WriteThrough))
921                    {
922                        //Set the end of the stream after the wrap-round the cluster size
923                        strm.SetLength(GetFileArea(paths[i]));
924
925                        //If the stream is empty, there's nothing to overwrite. Continue
926                        //to the next entry
927                        if (strm.Length != 0)
928                        {
929                            //Then erase the file.
930                            long itemWritten = 0,
931                                 itemTotal = method.CalculateEraseDataSize(null, strm.Length);
932                            method.Erase(strm, long.MaxValue,
933                                PrngManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePrng),
934                                delegate(long lastWritten, int currentPass)
935                                {
936                                    dataTotal -= lastWritten;
937                                    progress.Completed += lastWritten;
938                                    progress.Event.CurrentItemPass = currentPass;
939                                    progress.Event.CurrentItemProgress = (float)
940                                        ((itemWritten += lastWritten) / (float)itemTotal);
941                                    progress.Event.CurrentTargetProgress =
942                                        (i + progress.Event.CurrentItemProgress) /
943                                        (float)paths.Count;
944                                    progress.Event.TimeLeft = progress.TimeLeft;
945                                    task.OnProgressChanged(progress.Event);
946
947                                    lock (currentTask)
948                                        if (currentTask.Canceled)
949                                            throw new FatalException(S._("The task was cancelled."));
950                                }
951                            );
952                        }
953
954                        //Set the length of the file to 0.
955                        strm.Seek(0, SeekOrigin.Begin);
956                        strm.SetLength(0);
957                    }
958
959                    //Remove the file.
960                    FileInfo fileInfo = info.File;
961                    if (fileInfo != null)
962                        fsManager.DeleteFile(fileInfo);
963                }
964                catch (UnauthorizedAccessException)
965                {
966                    task.Log.Add(new LogEntry(S._("The file {0} could not be erased because the " +
967                        "file's permissions prevent access to the file.", info.FullName),
968                        LogLevel.Error));
969                }
970                catch (FileLoadException)
971                {
972                    task.Log.Add(new LogEntry(S._("The file {0} could not be erased because the " +
973                        "file is currently in use.", info.FullName), LogLevel.Error));
974                }
975                finally
976                {
977                    //Re-set the read-only flag
978                    info.IsReadOnly = isReadOnly;
979                }
980            }
981
982            //If the user requested a folder removal, do it.
983            if (target is FolderTarget)
984            {
985                progress.Event.CurrentItemName = S._("Removing folders...");
986                task.OnProgressChanged(progress.Event);
987
988                FolderTarget fldr = (FolderTarget)target;
989                if (fldr.DeleteIfEmpty)
990                {
991                    FileSystem fsManager = FileSystem.Get(VolumeInfo.FromMountpoint(fldr.Path));
992                    fsManager.DeleteFolder(new DirectoryInfo(fldr.Path));
993                }
994            }
995
996            //If the user was erasing the recycle bin, clear the bin.
997            if (target is RecycleBinTarget)
998            {
999                progress.Event.CurrentItemName = S._("Emptying recycle bin...");
1000                task.OnProgressChanged(progress.Event);
1001
1002                ShellAPI.SHEmptyRecycleBin(IntPtr.Zero, null,
1003                    ShellAPI.SHEmptyRecycleBinFlags.SHERB_NOCONFIRMATION |
1004                    ShellAPI.SHEmptyRecycleBinFlags.SHERB_NOPROGRESSUI |
1005                    ShellAPI.SHEmptyRecycleBinFlags.SHERB_NOSOUND);
1006            }
1007        }
1008
1009        /// <summary>
1010        /// Retrieves the size of the file on disk, calculated by the amount of
1011        /// clusters allocated by it.
1012        /// </summary>
1013        /// <param name="filePath">The path to the file.</param>
1014        /// <returns>The area of the file.</returns>
1015        private static long GetFileArea(string filePath)
1016        {
1017            StreamInfo info = new StreamInfo(filePath);
1018            VolumeInfo volume = VolumeInfo.FromMountpoint(info.Directory.FullName);
1019            long clusterSize = volume.ClusterSize;
1020            return (info.Length + (clusterSize - 1)) & ~(clusterSize - 1);
1021        }
1022        #endregion
1023
1024        /// <summary>
1025        /// The thread object.
1026        /// </summary>
1027        private Thread thread;
1028
1029        /// <summary>
1030        /// The lock preventing concurrent access for the tasks list and the
1031        /// tasks queue.
1032        /// </summary>
1033        private object tasksLock = new object();
1034
1035        /// <summary>
1036        /// The list of tasks. Includes all immediate, reboot, and recurring tasks
1037        /// </summary>
1038        private Dictionary<uint, Task> tasks = new Dictionary<uint, Task>();
1039
1040        /// <summary>
1041        /// The queue of tasks. This queue is executed when the first element's
1042        /// timestamp (the key) has been past. This list assumes that all tasks
1043        /// are sorted by timestamp, smallest one first.
1044        /// </summary>
1045        private SortedList<DateTime, Task> scheduledTasks =
1046            new SortedList<DateTime, Task>();
1047
1048        /// <summary>
1049        /// The currently executing task.
1050        /// </summary>
1051        Task currentTask;
1052
1053        /// <summary>
1054        /// The list of task IDs for recycling.
1055        /// </summary>
1056        private List<uint> unusedIds = new List<uint>();
1057
1058        /// <summary>
1059        /// Lock preventing concurrent access for the IDs.
1060        /// </summary>
1061        private object unusedIdsLock = new object();
1062
1063        /// <summary>
1064        /// Incrementing ID. This value is incremented by one every time an ID
1065        /// is required by no unused IDs remain.
1066        /// </summary>
1067        private uint nextId;
1068
1069        /// <summary>
1070        /// An automatically reset event allowing the addition of new tasks to
1071        /// interrupt the thread's sleeping state waiting for the next recurring
1072        /// task to be due.
1073        /// </summary>
1074        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
1075    }
1076}
Note: See TracBrowser for help on using the repository browser.