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

Revision 899, 32.4 KB checked in by lowjoel, 6 years ago (diff)

Refactored out the filesystem-specific code from DirectExecutor? to a FileSystem? class. Currently only implemented for NTFS

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