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

Revision 1040, 33.4 KB checked in by lowjoel, 5 years ago (diff)

Prevent the file from being re-created after an erasure (wrong file creation constant passed to CreateFile?)

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