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

Revision 579, 35.0 KB checked in by cjax, 6 years ago (diff)

On RemoveFile? has a bug which potentially could have lead to infinite loop; simple but sorted now.

  • 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, IDisposable
42    {
43        public DirectExecutor()
44        {
45            thread = new Thread(delegate()
46                {
47                    Main();
48                }
49            );
50        }
51
52        void IDisposable.Dispose()
53        {
54            thread.Abort();
55            schedulerInterrupt.Set();
56        }
57
58        public override void Run()
59        {
60            thread.Start();
61        }
62
63        public override void AddTask(ref Task task)
64        {
65            lock (unusedIdsLock)
66            {
67                if (unusedIds.Count != 0)
68                {
69                    task.id = unusedIds[0];
70                    unusedIds.RemoveAt(0);
71                }
72                else
73                    task.id = ++nextId;
74            }
75
76            //Set the executor of the task
77            task.executor = this;
78
79            //Add the task to the set of tasks
80            lock (tasksLock)
81            {
82                tasks.Add(task.ID, 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
99        public override bool DeleteTask(uint taskId)
100        {
101            lock (tasksLock)
102            {
103                if (!tasks.ContainsKey(taskId))
104                    return false;
105
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
117            return true;
118        }
119
120        public override void ReplaceTask(Task task)
121        {
122            lock (tasksLock)
123            {
124                //Replace the task in the global set
125                if (!tasks.ContainsKey(task.ID))
126                    return;
127
128                tasks[task.ID] = task;
129
130                //Then replace the task if it is in the queue
131                for (int i = 0; i != scheduledTasks.Count; ++i)
132                    if (scheduledTasks.Values[i].id == task.ID)
133                    {
134                        scheduledTasks.RemoveAt(i);
135                        if (task.Schedule is RecurringSchedule)
136                            ScheduleTask(task);
137                        else if (task.Schedule == Schedule.RunNow)
138                            QueueTask(task);
139                    }
140            }
141        }
142
143        public override void QueueTask(Task task)
144        {
145            lock (tasksLock)
146            {
147                //Set the task variable to indicate that the task is already
148                //waiting to be executed.
149                task.queued = true;
150
151                //Queue the task to be run immediately.
152                scheduledTasks.Add(DateTime.Now, task);
153                schedulerInterrupt.Set();
154            }
155        }
156
157        public override void ScheduleTask(Task task)
158        {
159            RecurringSchedule schedule = (RecurringSchedule)task.Schedule;
160            if (schedule.MissedPreviousSchedule &&
161                ManagerLibrary.Instance.Settings.ExecuteMissedTasksImmediately)
162                //OK, we've missed the schedule and the user wants the thing
163                //to follow up immediately.
164                scheduledTasks.Add(DateTime.Now, task);
165            else
166                scheduledTasks.Add(schedule.NextRun, task);
167        }
168
169        public override void QueueRestartTasks()
170        {
171            lock (tasksLock)
172            {
173                foreach (Task task in tasks.Values)
174                    if (task.Schedule == Schedule.RunOnRestart)
175                        QueueTask(task);
176            }
177        }
178
179        public override void CancelTask(Task task)
180        {
181            lock (currentTask)
182            {
183                if (currentTask == task)
184                {
185                    currentTask.cancelled = true;
186                    return;
187                }
188            }
189
190            lock (tasksLock)
191                for (int i = 0; i != scheduledTasks.Count; ++i)
192                    if (scheduledTasks.Values[i] == task)
193                    {
194                        scheduledTasks.RemoveAt(i);
195                        return;
196                    }
197
198            throw new ArgumentOutOfRangeException("The task to be cancelled must " +
199                "either be currently executing or queued.");
200        }
201
202        public override Task GetTask(uint taskId)
203        {
204            lock (tasksLock)
205            {
206                if (!tasks.ContainsKey(taskId))
207                    return null;
208                return tasks[taskId];
209            }
210        }
211
212        public override List<Task> GetTasks()
213        {
214            lock (tasksLock)
215            {
216                Task[] result = new Task[tasks.Count];
217                tasks.Values.CopyTo(result, 0);
218                return new List<Task>(result);
219            }
220        }
221
222        public override void SaveTaskList(Stream stream)
223        {
224            lock (tasksLock)
225                new BinaryFormatter().Serialize(stream, tasks);
226        }
227
228        public override void LoadTaskList(Stream stream)
229        {
230            lock (tasksLock)
231            {
232                //Load the list into the dictionary
233                tasks = (Dictionary<uint, Task>)new BinaryFormatter().Deserialize(stream);
234
235                //Ignore the next portion if there are no tasks
236                if (tasks.Count == 0)
237                    return;
238
239                lock (unusedIdsLock)
240                {
241                    //Find gaps in the numbering
242                    nextId = 1;
243                    foreach (uint id in tasks.Keys)
244                    {
245                        Task currentTask = tasks[id];
246                        currentTask.executor = this;
247                        while (id > nextId)
248                            unusedIds.Add(nextId++);
249                        ++nextId;
250
251                        //Check if the task is recurring. If it is, check if we missed it.
252                        if (currentTask.Schedule is RecurringSchedule)
253                            ScheduleTask(currentTask);
254                    }
255
256                    //Decrement the ID, since the next ID will be preincremented
257                    //before use.
258                    --nextId;
259                }
260            }
261        }
262
263        /// <summary>
264        /// The thread entry point for this object. This object operates on a queue
265        /// and hence the thread will sequentially execute tasks.
266        /// </summary>
267        private void Main()
268        {
269            //The waiting thread will utilize a polling loop to check for new
270            //scheduled tasks. This will be checked every 30 seconds. However,
271            //when the thread is waiting for a new task, it can be interrupted.
272            while (thread.ThreadState != ThreadState.AbortRequested)
273            {
274                //Check for a new task
275                Task task = null;
276                lock (tasksLock)
277                {
278                    if (scheduledTasks.Count != 0 &&
279                        (scheduledTasks.Values[0].Schedule == Schedule.RunNow ||
280                         scheduledTasks.Keys[0] <= DateTime.Now))
281                    {
282                        task = scheduledTasks.Values[0];
283                        scheduledTasks.RemoveAt(0);
284                    }
285                }
286
287                if (task != null)
288                {
289                    //Set the currently executing task.
290                    currentTask = task;
291
292                    try
293                    {
294                        //Prevent the system from sleeping.
295                        KernelAPI.SetThreadExecutionState(KernelAPI.EXECUTION_STATE.ES_CONTINUOUS |
296                            KernelAPI.EXECUTION_STATE.ES_SYSTEM_REQUIRED);
297
298                        //Broadcast the task started event.
299                        task.queued = false;
300                        task.cancelled = false;
301                        task.OnTaskStarted(new TaskEventArgs(task));
302                        OnTaskProcessing(task);
303
304                        //Start a new log session to separate this session's events
305                        //from previous ones.
306                        task.Log.NewSession();
307
308                        //Run the task
309                        ProgressManager progress = new ProgressManager(currentTask);
310                        foreach (Task.ErasureTarget target in task.Targets)
311                            try
312                            {
313                                progress.Event.currentTarget = target;
314                                ++progress.Event.currentTargetIndex;
315                                if (target is Task.UnusedSpace)
316                                    EraseUnusedSpace(task, (Task.UnusedSpace)target, progress);
317                                else if (target is Task.FilesystemObject)
318                                    EraseFilesystemObject(task, (Task.FilesystemObject)target, progress);
319                                else
320                                    throw new ArgumentException("Unknown erasure target.");
321                            }
322                            catch (FatalException)
323                            {
324                                throw;
325                            }
326                            catch (Exception e)
327                            {
328                                task.Log.Add(new LogEntry(e.Message, LogLevel.ERROR));
329                            }
330                    }
331                    catch (FatalException e)
332                    {
333                        task.Log.Add(new LogEntry(e.Message, LogLevel.FATAL));
334                    }
335                    catch (Exception e)
336                    {
337                        task.Log.Add(new LogEntry(e.Message, LogLevel.ERROR));
338                    }
339                    finally
340                    {
341                        //Allow the system to sleep again.
342                        KernelAPI.SetThreadExecutionState(KernelAPI.EXECUTION_STATE.ES_CONTINUOUS |
343                            KernelAPI.EXECUTION_STATE.ES_SYSTEM_REQUIRED);
344
345                        //If the task is a recurring task, reschedule it since we are done.
346                        if (task.Schedule is RecurringSchedule)
347                            ((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
348
349                        //If the task is an execute on restart task, it is only run
350                        //once and can now be restored to an immediately executed task
351                        if (task.Schedule == Schedule.RunOnRestart)
352                            task.Schedule = Schedule.RunNow;
353
354                        //And the task finished event.
355                        task.OnTaskFinished(new TaskEventArgs(task));
356                        OnTaskProcessed(currentTask);
357                    }
358
359                    currentTask = null;
360                }
361
362                //Wait for half a minute to check for the next scheduled task.
363                schedulerInterrupt.WaitOne(30000, false);
364            }
365        }
366
367        /// <summary>
368        /// Provides a common interface to track the progress made by the Erase functions.
369        /// </summary>
370        private class ProgressManager
371        {
372            /// <summary>
373            /// Constructor.
374            /// </summary>
375            public ProgressManager(Task task)
376            {
377                startTime = DateTime.Now;
378                Event = new TaskProgressEventArgs(task);
379
380                foreach (Task.ErasureTarget target in task.Targets)
381                    totalData += target.TotalData;
382            }
383
384            /// <summary>
385            /// Computes the speed of the erase, in bytes per second, based on the
386            /// information collected in the previous 10 seconds.
387            /// </summary>
388            public int Speed
389            {
390                get
391                {
392                    if (DateTime.Now == startTime)
393                        return 0;
394
395                    if ((DateTime.Now - lastSpeedCalc).Seconds < 5 && lastSpeed != 0)
396                        return lastSpeed;
397
398                    lastSpeedCalc = DateTime.Now;
399                    lastSpeed = (int)(DataWritten / (DateTime.Now - startTime).TotalSeconds);
400                    return lastSpeed;
401                }
402            }
403
404            /// <summary>
405            /// Calculates the estimated amount of time left based on the total
406            /// amount of information to erase and the current speed of the erase
407            /// </summary>
408            public TimeSpan TimeLeft
409            {
410                get
411                {
412                    if (Speed == 0)
413                        return new TimeSpan(0, 0, -1);
414                    return new TimeSpan(0, 0, (int)((totalData - DataWritten) / Speed));
415                }
416            }
417
418            /// <summary>
419            /// Tracks the amount of data written, to determine the speed of the erase.
420            /// </summary>
421            public long DataWritten
422            {
423                get
424                {
425                    return dataWritten;
426                }
427                set
428                {
429                    dataWritten = value;
430                }
431            }
432
433            /// <summary>
434            /// The TaskProgressEventArgs object representing the progress of the current
435            /// task.
436            /// </summary>
437            public TaskProgressEventArgs Event
438            {
439                get
440                {
441                    return evt;
442                }
443                set
444                {
445                    evt = value;
446                }
447            }
448
449            private long totalData;
450            private DateTime startTime;
451            private DateTime lastSpeedCalc;
452            private int lastSpeed;
453            private long dataWritten;
454            private TaskProgressEventArgs evt;
455        }
456
457        /// <summary>
458        /// Executes a unused space erase.
459        /// </summary>
460        /// <param name="task">The task currently being executed</param>
461        /// <param name="target">The target of the unused space erase.</param>
462        /// <param name="progress">The progress manager object managing the progress of the task</param>
463        private void EraseUnusedSpace(Task task, Task.UnusedSpace target, ProgressManager progress)
464        {
465            //Check for sufficient privileges to run the unused space erasure.
466            if (!Permissions.IsAdministrator())
467            {
468                string exceptionString = "The program does not have the required permissions " +
469                    "to erase the unused space on disk";
470                if (Environment.OSVersion.Platform == PlatformID.Win32NT &&
471                    Environment.OSVersion.Version >= new Version(6, 0))
472                {
473                    exceptionString += ". Run the program as an administrator and retry the operation.";
474                }
475                throw new Exception(exceptionString);
476            }
477
478            //If the user is under disk quotas, log a warning message
479            if (VolumeInfo.FromMountpoint(target.Drive).HasQuota)
480                task.Log.Add(new LogEntry("The drive which is having its unused space erased has " +
481                    "disk quotas active. This will prevent the complete erasure of unused space and " +
482                    "will pose a security concern", LogLevel.WARNING));
483
484            //Get the erasure method if the user specified he wants the default.
485            ErasureMethod method = target.Method;
486           
487            //Erase the cluster tips of every file on the drive.
488            if (target.EraseClusterTips)
489            {
490                progress.Event.currentItemName = "Cluster tips";
491                progress.Event.currentTargetTotalPasses = method.Passes;
492                progress.Event.timeLeft = progress.TimeLeft;
493                task.OnProgressChanged(progress.Event);
494               
495                EraseClusterTips(task, target, method,
496                    delegate(int currentFile, string currentFilePath, int totalFiles)
497                    {
498                        progress.Event.currentItemName = "(Tips) " + currentFilePath;
499                        progress.Event.currentItemProgress = (float)currentFile / totalFiles;
500                        progress.Event.CurrentTargetProgress = progress.Event.CurrentItemProgress / 10;
501                        task.OnProgressChanged(progress.Event);
502                    }
503                );
504            }
505
506            //Make a folder to dump our temporary files in
507            DirectoryInfo info = new DirectoryInfo(target.Drive);
508            {
509                string directoryName;
510                do
511                    directoryName = GenerateRandomFileName(18);
512                while (Directory.Exists(info.FullName + Path.DirectorySeparatorChar + directoryName));
513                info = info.CreateSubdirectory(directoryName);
514            }
515
516            try
517            {
518                //Set the folder's compression flag off since we want to use as much
519                //space as possible
520                if (Eraser.Util.File.IsCompressed(info.FullName))
521                    Eraser.Util.File.SetCompression(info.FullName, false);
522
523                //Determine the total amount of data that needs to be written.
524                VolumeInfo volInfo = VolumeInfo.FromMountpoint(target.Drive);
525                long totalSize = method.CalculateEraseDataSize(null, volInfo.TotalFreeSpace);
526
527                //Continue creating files while there is free space.
528                progress.Event.currentItemName = "Unused space";
529                task.OnProgressChanged(progress.Event);
530                while (volInfo.AvailableFreeSpace > 0)
531                {
532                    //Generate a non-existant file name
533                    string currFile;
534                    do
535                        currFile = info.FullName + Path.DirectorySeparatorChar +
536                            GenerateRandomFileName(18);
537                    while (System.IO.File.Exists(currFile));
538
539                    //Create the stream
540                    using (FileStream stream = new FileStream(currFile,
541                        FileMode.CreateNew, FileAccess.Write))
542                    {
543                        //Set the length of the file to be the amount of free space left
544                        //or the maximum size of one of these dumps.
545                        long streamLength = Math.Min(ErasureMethod.FreeSpaceFileUnit,
546                            volInfo.AvailableFreeSpace);
547
548                        //Handle IO exceptions gracefully, because the filesystem
549                        //may require more space than demanded by us for file allocation.
550                        while (true)
551                            try
552                            {
553                                stream.SetLength(streamLength);
554                                break;
555                            }
556                            catch (IOException)
557                            {
558                                if (streamLength > volInfo.ClusterSize)
559                                    streamLength -= volInfo.ClusterSize;
560                                else
561                                    throw;
562                            }
563
564                        //Then run the erase task
565                        method.Erase(stream, long.MaxValue,
566                            PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG),
567                            delegate(long lastWritten, int currentPass)
568                            {
569                                progress.DataWritten += lastWritten;
570                                progress.Event.currentItemPass = currentPass;
571                                progress.Event.currentItemProgress = (float)progress.DataWritten / totalSize;
572                                if (target.EraseClusterTips)
573                                    progress.Event.CurrentTargetProgress = (float)
574                                        (0.1 + progress.Event.currentItemProgress * 0.8);
575                                else
576                                    progress.Event.CurrentTargetProgress = (float)
577                                        (progress.Event.currentItemProgress * 0.9);
578                                progress.Event.timeLeft = progress.TimeLeft;
579                                task.OnProgressChanged(progress.Event);
580
581                                lock (currentTask)
582                                    if (currentTask.cancelled)
583                                        throw new FatalException("The task was cancelled.");
584                            }
585                        );
586                    }
587                }
588
589                //Erase old file system records
590                progress.Event.currentItemName = "Old file system records";
591                task.OnProgressChanged(progress.Event);
592                EraseFilesystemRecords(info, method);
593            }
594            finally
595            {
596                progress.Event.currentItemName = "Removing temporary files";
597                task.OnProgressChanged(progress.Event);
598
599                //Remove the folder holding all our temporary files.
600                RemoveFolder(info);
601            }
602
603            //new NotImplementedException(): clear directory entries: Eraser.cpp@2348
604        }
605
606        private delegate void SubFoldersHandler(DirectoryInfo info);
607        private delegate void ClusterTipsEraseProgress(int currentFile,
608            string currentFilePath, int totalFiles);
609
610        private static void EraseClusterTips(Task task, Task.UnusedSpace target,
611            ErasureMethod method, ClusterTipsEraseProgress callback)
612        {
613            //List all the files which can be erased.
614            List<string> files = new List<string>();
615            SubFoldersHandler subFolders = null;
616
617            subFolders = delegate(DirectoryInfo info)
618            {
619                try
620                {
621                    //Skip this directory if it is a reparse point
622                    if ((info.Attributes & FileAttributes.ReparsePoint) != 0)
623                    {
624                        task.Log.Add(new LogEntry(string.Format(S._("Files in {0} did not have " +
625                            "their cluster tips erased because it is a hard link or a symbolic " +
626                            "link."), info.FullName), LogLevel.INFORMATION));
627                        return;
628                    }
629
630                    foreach (FileInfo file in info.GetFiles())
631                        if (Util.File.IsProtectedSystemFile(file.FullName))
632                            task.Log.Add(new LogEntry(string.Format("{0} did not have its " +
633                                "cluster tips erased, because it is a system file", file.FullName),
634                                LogLevel.INFORMATION));
635                        else if ((file.Attributes & FileAttributes.ReparsePoint) != 0)
636                            task.Log.Add(new LogEntry(string.Format(S._("{0} did not have its " +
637                                "cluster tips erased because it is a hard link or a symbolic link."),
638                                file.FullName), LogLevel.INFORMATION));
639                        else
640                        {
641                            foreach (string i in Util.File.GetADSes(file))
642                                files.Add(file.FullName + ':' + i);
643
644                            files.Add(file.FullName);
645                        }
646
647                    foreach (DirectoryInfo subDirInfo in info.GetDirectories())
648                        subFolders(subDirInfo);
649                }
650                catch (UnauthorizedAccessException e)
651                {
652                    task.Log.Add(new LogEntry(string.Format("{0} did not have its cluster tips erased because of " +
653                        "the following error: {1}", info.FullName, e.Message), LogLevel.ERROR));
654                }
655                catch (IOException e)
656                {
657                    task.Log.Add(new LogEntry(string.Format("{0} did not have its cluster tips erased because of " +
658                        "the following error: {1}", 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                try
668                {
669                    EraseFileClusterTips(files[i], method);
670                }
671                catch (Exception e)
672                {
673                    task.Log.Add(new LogEntry(string.Format("{0} did not have its cluster tips " +
674                        "erased. The error returned was: {1}", files[i], e.Message), LogLevel.ERROR));
675                }
676                callback(i, files[i], files.Count);
677            }
678        }
679
680        /// <summary>
681        /// Erases the cluster tips of the given file.
682        /// </summary>
683        /// <param name="file">The file to erase.</param>
684        /// <param name="method">The erasure method to use.</param>
685        private static void EraseFileClusterTips(string file, ErasureMethod method)
686        {
687            //Get the file access times
688            StreamInfo streamInfo = new StreamInfo(file);
689            DateTime lastAccess = DateTime.MinValue,
690                     lastWrite = DateTime.MinValue,
691                     created = DateTime.MinValue;
692            //Create the stream, lengthen the file, then tell the erasure method
693            //to erase the tips.
694            using (FileStream stream = streamInfo.Open(FileMode.Open, FileAccess.Write))
695            {
696                long fileLength = stream.Length;
697                long fileArea = GetFileArea(file);
698
699                try
700                {
701                    //Get the file access times
702                    FileInfo info = streamInfo.File;
703                    if (info != null)
704                    {
705                        lastAccess = info.LastAccessTime;
706                        lastWrite = info.LastWriteTime;
707                        created = info.CreationTime;
708                    }
709
710                    stream.SetLength(fileArea);
711                    stream.Seek(fileLength, SeekOrigin.Begin);
712
713                    //Erase the file
714                    method.Erase(stream, long.MaxValue, PRNGManager.GetInstance(
715                        ManagerLibrary.Instance.Settings.ActivePRNG), null);
716                }
717                finally
718                {
719                    //Make sure the file is restored!
720                    stream.SetLength(fileLength);
721
722                    //Set the file times
723                    FileInfo fileInfo = streamInfo.File;
724                    if (fileInfo != null)
725                    {
726                        fileInfo.LastAccessTime = lastAccess;
727                        fileInfo.LastWriteTime = lastWrite;
728                        fileInfo.CreationTime = created;
729                    }
730                }
731            }
732        }
733       
734        /// <summary>
735        /// Erases the old MFT or FAT records. This creates small one-byte files
736        /// until the MFT or FAT grows, if the disk is not full. If the disk is
737        /// full, just keep forcing the MFT to store files until nothing else
738        /// fits.
739        /// </summary>
740        /// <param name="info">The directory information structure containing
741        /// the path to store the temporary one-byte files. The MFT of that
742        /// drive will be erased.</param>
743        /// <param name="method">The method used to erase the records.</param>
744        private void EraseFilesystemRecords(DirectoryInfo info, ErasureMethod method)
745        {
746            VolumeInfo volInfo = VolumeInfo.FromMountpoint(info.FullName);
747            string volFormat = volInfo.VolumeFormat;
748            if (volFormat == "NTFS")
749            {
750                //If the volume is full, squeeze one-byte files.
751                try
752                {
753                    StreamInfo mftInfo = new StreamInfo(Path.Combine(volInfo.VolumeID, "$MFT"));
754                    long oldMFTSize = mftInfo.Length;
755                    for ( ; ; )
756                    {
757                        //Open this stream
758                        using (FileStream strm = new FileStream(Path.Combine(
759                            info.FullName, GenerateRandomFileName(18)),
760                            FileMode.CreateNew, FileAccess.Write))
761                        {
762                            //Stretch the file size to the size of one MFT record
763                            strm.SetLength(1);
764
765                            //Then run the erase task
766                            method.Erase(strm, long.MaxValue,
767                                PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG),
768                                null);
769                        }
770
771                        //Determine if we can stop. We will stop if the disk is not
772                        //full and the MFT has grown in size.
773                        if (volInfo.AvailableFreeSpace != 0 && mftInfo.Length != oldMFTSize)
774                            break;
775                    }
776                }
777                catch (IOException)
778                {
779                    //OK, enough squeezing.
780                }
781            }
782            else
783                throw new NotImplementedException("Could not erase old file system " +
784                    "records: Unsupported File system");
785        }
786
787        /// <summary>
788        /// Erases a file or folder on the volume.
789        /// </summary>
790        /// <param name="task">The task currently being processed.</param>
791        /// <param name="target">The target of the erasure.</param>
792        /// <param name="progress">The progress manager for the current task.</param>
793        private void EraseFilesystemObject(Task task, Task.FilesystemObject target,
794            ProgressManager progress)
795        {
796            //Retrieve the list of files to erase.
797            long dataTotal = 0;
798            List<string> paths = target.GetPaths(out dataTotal);
799
800            //Get the erasure method if the user specified he wants the default.
801            ErasureMethod method = target.Method;
802           
803            //Calculate the total amount of data required to finish the wipe.
804            dataTotal = method.CalculateEraseDataSize(paths, dataTotal);
805
806            //Iterate over every path, and erase the path.
807            for (int i = 0; i < paths.Count; ++i)
808            {
809                //Update the task progress
810                progress.Event.CurrentTargetProgress = i / (float)paths.Count;
811                progress.Event.currentTarget = target;
812                progress.Event.currentItemName = paths[i];
813                progress.Event.currentItemProgress = 0;
814                progress.Event.currentTargetTotalPasses = method.Passes;
815                task.OnProgressChanged(progress.Event);
816
817                //Make sure the file does not have any attributes which may affect
818                //the erasure process
819                bool isReadOnly = false;
820                StreamInfo info = new StreamInfo(paths[i]);
821                if ((info.Attributes & FileAttributes.Compressed) != 0 ||
822                    (info.Attributes & FileAttributes.Encrypted) != 0 ||
823                    (info.Attributes & FileAttributes.SparseFile) != 0)
824                {
825                    //Log the error
826                    throw new ArgumentException("Compressed, encrypted, or sparse" +
827                        "files cannot be erased with Eraser.");
828                }
829
830                //Remove the read-only flag, if it is set.
831                if (isReadOnly = info.IsReadOnly)
832                    info.IsReadOnly = false;
833
834                try
835                {
836                    //Create the file stream, and call the erasure method to write to
837                    //the stream.
838                    using (FileStream strm = info.Open(FileMode.Open, FileAccess.Write,
839                        FileShare.None, FileOptions.WriteThrough))
840                    {
841                        //Set the end of the stream after the wrap-round the cluster size
842                        strm.SetLength(GetFileArea(paths[i]));
843
844                        //If the stream is empty, there's nothing to overwrite. Continue
845                        //to the next entry
846                        if (strm.Length != 0)
847                        {
848                            //Then erase the file.
849                            long itemWritten = 0,
850                                 itemTotal = method.CalculateEraseDataSize(null, strm.Length);
851                            method.Erase(strm, long.MaxValue,
852                                PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG),
853                                delegate(long lastWritten, int currentPass)
854                                {
855                                    dataTotal -= lastWritten;
856                                    progress.DataWritten += lastWritten;
857                                    progress.Event.currentItemPass = currentPass;
858                                    progress.Event.currentItemProgress = (float)
859                                        ((itemWritten += lastWritten) / (float)itemTotal);
860                                    progress.Event.CurrentTargetProgress =
861                                        (i + progress.Event.currentItemProgress) /
862                                        (float)paths.Count;
863                                    progress.Event.timeLeft = progress.TimeLeft;
864                                    task.OnProgressChanged(progress.Event);
865
866                                    lock (currentTask)
867                                        if (currentTask.cancelled)
868                                            throw new FatalException("The task was cancelled.");
869                                }
870                            );
871                        }
872
873                        //Set the length of the file to 0.
874                        strm.Seek(0, SeekOrigin.Begin);
875                        strm.SetLength(0);
876                    }
877
878                    //Remove the file.
879                    FileInfo fileInfo = info.File;
880                    if (fileInfo != null)
881                        RemoveFile(fileInfo);
882                }
883                finally
884                {
885                    //Re-set the read-only flag
886                    info.IsReadOnly = isReadOnly;
887                }
888            }
889
890            //If the user requested a folder removal, do it.
891            if (target is Task.Folder)
892            {
893                Task.Folder fldr = (Task.Folder)target;
894                if (fldr.DeleteIfEmpty)
895                    RemoveFolder(new DirectoryInfo(fldr.Path));
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
913        /// <summary>
914        /// Securely removes files.
915        /// </summary>
916        /// <param name="info">The FileInfo object representing the file.</param>
917        private static void RemoveFile(FileInfo info)
918        {
919            //Set the date of the file to be invalid to prevent forensic
920            //detection
921            info.CreationTime = info.LastWriteTime = info.LastAccessTime =
922                new DateTime(1800, 1, 1, 0, 0, 0);
923            info.Attributes = FileAttributes.Normal;
924            info.Attributes = FileAttributes.NotContentIndexed;
925
926            //Rename the file a few times to erase the record from the MFT.
927            for (int i = 0, retries = 0; i < FilenameErasePasses; ++i)
928            {
929                //Rename the file.
930                string newPath = info.DirectoryName + Path.DirectorySeparatorChar +
931                    GenerateRandomFileName(info.Name.Length);
932
933                //Try to rename the file. If it fails, it is probably due to another
934                //process locking the file. Defer, then rename again.
935                try
936                {
937                    info.MoveTo(newPath);
938                }
939                catch (IOException)
940                {
941                    Thread.Sleep(100);
942                    // if we have been waiting for more than 3.2 seconds
943                    // we should just ignore this, we probably could not access
944                    // it in near future
945                    if (retries < 32) --i;
946                }
947            }
948
949            //If the user wants plausible deniability, find a random file on the same
950            //volume and write it over.
951            if (Manager.ManagerLibrary.Instance.Settings.PlausibleDeniability)
952            {
953                //Get the template file to copy
954                FileInfo shadowFileInfo;
955                {
956                    string shadowFile;
957                    List<string> entries = ManagerLibrary.Instance.Settings.PlausibleDeniabilityFiles.GetRange(
958                        0, ManagerLibrary.Instance.Settings.PlausibleDeniabilityFiles.Count);
959                    PRNG prng = PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG);
960                    do
961                    {
962                        if (entries.Count == 0)
963                            throw new FatalException("Plausible deniability was selected, but no decoy files " +
964                                "were found. The current file has been only replaced with random data.");
965
966                        int index = prng.Next(entries.Count - 1);
967                        if ((System.IO.File.GetAttributes(entries[index]) & FileAttributes.Directory) != 0)
968                            shadowFile = GetRandomFileName(new DirectoryInfo(entries[index]));
969                        else
970                            shadowFile = entries[index];
971
972                        entries.RemoveAt(index);
973                    }
974                    while (shadowFile.Length == 0 || Path.GetDirectoryName(shadowFile) == info.DirectoryName);
975                    shadowFileInfo = new FileInfo(shadowFile);
976                }
977
978                //Rename the file to have the same name as the shadow
979                info.MoveTo(info.DirectoryName + shadowFileInfo.Name);
980
981                //Dump the copy (the first 4MB, or less, depending on the file size and available
982                //user space)
983                long amountToCopy = Math.Min(4 * 1024 * 1024, shadowFileInfo.Length);
984                using (FileStream shadowFileStream = shadowFileInfo.OpenRead())
985                using (FileStream destFileStream = info.OpenWrite())
986                {
987                    while (destFileStream.Position < amountToCopy)
988                    {
989                        byte[] buf = new byte[524288];
990                        int bytesRead = shadowFileStream.Read(buf, 0, buf.Length);
991
992                        //Stop bothering if the input stream is at the end
993                        if (bytesRead == 0)
994                            break;
995
996                        //Dump the read contents onto the file to be deleted
997                        destFileStream.Write(buf, 0,
998                            (int)Math.Min(bytesRead, amountToCopy - destFileStream.Position));
999                    }
1000                }
1001            }
1002
1003            //Then delete the file.
1004            info.Delete();
1005        }
1006
1007        /// <summary>
1008        /// Removes the folder and all its contents.
1009        /// </summary>
1010        /// <param name="info">The folder to remove.</param>
1011        private static void RemoveFolder(DirectoryInfo info)
1012        {
1013            foreach (FileInfo file in info.GetFiles())
1014                RemoveFile(file);
1015            foreach (DirectoryInfo dir in info.GetDirectories())
1016                RemoveFolder(dir);
1017
1018            //Then clean up this folder.
1019            for (int i = 0; i < FilenameErasePasses; ++i)
1020            {
1021                //Rename the folder.
1022                string newPath = info.Parent.FullName + Path.DirectorySeparatorChar +
1023                    GenerateRandomFileName(info.Name.Length);
1024
1025                //Try to rename the file. If it fails, it is probably due to another
1026                //process locking the file. Defer, then rename again.
1027                try
1028                {
1029                    info.MoveTo(newPath);
1030                }
1031                catch (IOException)
1032                {
1033                    Thread.Sleep(100);
1034                    --i;
1035                }
1036            }
1037
1038            //Remove the folder
1039            info.Delete();
1040        }
1041
1042        /// <summary>
1043        /// Generates a random file name with the given length.
1044        /// </summary>
1045        /// <param name="length">The length of the file name to generate.</param>
1046        /// <returns>A random file name.</returns>
1047        private static string GenerateRandomFileName(int length)
1048        {
1049            //Get a random file name
1050            PRNG prng = PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG);
1051            byte[] newFileNameAry = new byte[length];
1052            prng.NextBytes(newFileNameAry);
1053
1054            //Validate the name
1055            string validFileNameChars = "0123456789abcdefghijklmnopqrstuvwxyz" +
1056                "ABCDEFGHIJKLMNOPQRSTUVWXYZ _+=-()[]{}',`~!";
1057            for (int j = 0, k = newFileNameAry.Length; j < k; ++j)
1058                newFileNameAry[j] = (byte)validFileNameChars[
1059                    (int)newFileNameAry[j] % validFileNameChars.Length];
1060
1061            return new System.Text.UTF8Encoding().GetString(newFileNameAry);
1062        }
1063
1064        /// <summary>
1065        /// Gets a random file from within the provided directory.
1066        /// </summary>
1067        /// <param name="info">The directory to get a random file name from.</param>
1068        /// <returns>A string containing the full path to the file.</returns>
1069        private static string GetRandomFileName(DirectoryInfo info)
1070        {
1071            //First retrieve the list of files and folders in the provided directory.
1072            FileSystemInfo[] entries = null;
1073            try
1074            {
1075                entries = info.GetFileSystemInfos();
1076            }
1077            catch (Exception)
1078            {
1079                return string.Empty;
1080            }
1081            if (entries.Length == 0)
1082                return string.Empty;
1083
1084            //Find a random entry.
1085            PRNG prng = PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG);
1086            string result = string.Empty;
1087            while (result.Length == 0)
1088            {
1089                int index = prng.Next(entries.Length - 1);
1090                if (entries[index] is DirectoryInfo)
1091                    result = GetRandomFileName((DirectoryInfo)entries[index]);
1092                else
1093                    result = ((FileInfo)entries[index]).FullName;
1094            }
1095
1096            return result;
1097        }
1098
1099        /// <summary>
1100        /// The thread object.
1101        /// </summary>
1102        private Thread thread;
1103
1104        /// <summary>
1105        /// The lock preventing concurrent access for the tasks list and the
1106        /// tasks queue.
1107        /// </summary>
1108        private object tasksLock = new object();
1109
1110        /// <summary>
1111        /// The list of tasks. Includes all immediate, reboot, and recurring tasks
1112        /// </summary>
1113        private Dictionary<uint, Task> tasks = new Dictionary<uint, Task>();
1114
1115        /// <summary>
1116        /// The queue of tasks. This queue is executed when the first element's
1117        /// timestamp (the key) has been past. This list assumes that all tasks
1118        /// are sorted by timestamp, smallest one first.
1119        /// </summary>
1120        private SortedList<DateTime, Task> scheduledTasks =
1121            new SortedList<DateTime, Task>();
1122
1123        /// <summary>
1124        /// The currently executing task.
1125        /// </summary>
1126        Task currentTask;
1127
1128        /// <summary>
1129        /// The list of task IDs for recycling.
1130        /// </summary>
1131        private List<uint> unusedIds = new List<uint>();
1132
1133        /// <summary>
1134        /// Lock preventing concurrent access for the IDs.
1135        /// </summary>
1136        private object unusedIdsLock = new object();
1137
1138        /// <summary>
1139        /// Incrementing ID. This value is incremented by one every time an ID
1140        /// is required by no unused IDs remain.
1141        /// </summary>
1142        private uint nextId = 0;
1143
1144        /// <summary>
1145        /// An automatically reset event allowing the addition of new tasks to
1146        /// interrupt the thread's sleeping state waiting for the next recurring
1147        /// task to be due.
1148        /// </summary>
1149        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
1150    }
1151}
Note: See TracBrowser for help on using the repository browser.