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

Revision 335, 28.9 KB checked in by lowjoel, 7 years ago (diff)

Don't give up if we end up traversing into a directory which is locked or blocked to us.

Line 
1using System;
2using System.Collections.Generic;
3using System.Collections.Specialized;
4using System.Text;
5using System.Threading;
6using System.IO;
7
8using Eraser.Util;
9using System.Security.Principal;
10using System.Runtime.Serialization;
11using System.Runtime.Serialization.Formatters.Binary;
12
13namespace Eraser.Manager
14{
15    /// <summary>
16    /// The DirectExecutor class is used by the Eraser GUI directly when the program
17    /// is run without the help of a Service.
18    /// </summary>
19    public class DirectExecutor : Executor, IDisposable
20    {
21        public DirectExecutor()
22        {
23            thread = new Thread(delegate()
24                {
25                    Main();
26                }
27            );
28
29            thread.Start();
30            Thread.Sleep(0);
31        }
32
33        void IDisposable.Dispose()
34        {
35            thread.Abort();
36            schedulerInterrupt.Set();
37        }
38
39        public override void AddTask(ref Task task)
40        {
41            lock (unusedIdsLock)
42            {
43                if (unusedIds.Count != 0)
44                {
45                    task.id = unusedIds[0];
46                    unusedIds.RemoveAt(0);
47                }
48                else
49                    task.id = ++nextId;
50            }
51
52            //Set the executor of the task
53            task.executor = this;
54
55            //Add the task to the set of tasks
56            lock (tasksLock)
57            {
58                tasks.Add(task.ID, task);
59
60                //If the task is scheduled to run now, break the waiting thread and
61                //run it immediately
62                if (task.Schedule == Schedule.RunNow)
63                {
64                    QueueTask(task);
65                }
66                //If the task is scheduled, add the next execution time to the list
67                //of schduled tasks.
68                else if (task.Schedule != Schedule.RunOnRestart)
69                {
70                    scheduledTasks.Add((task.Schedule as RecurringSchedule).NextRun, task);
71                }
72            }
73        }
74
75        public override bool DeleteTask(uint taskId)
76        {
77            lock (tasksLock)
78            {
79                if (!tasks.ContainsKey(taskId))
80                    return false;
81
82                lock (unusedIdsLock)
83                    unusedIds.Add(taskId);
84                tasks.Remove(taskId);
85
86                for (int i = 0; i != scheduledTasks.Count; ++i)
87                    if (scheduledTasks.Values[i].id == taskId)
88                        scheduledTasks.RemoveAt(i);
89            }
90
91            return true;
92        }
93
94        public override void ReplaceTask(Task task)
95        {
96            lock (tasksLock)
97            {
98                //Replace the task in the global set
99                if (!tasks.ContainsKey(task.ID))
100                    return;
101
102                tasks[task.ID] = task;
103
104                //Then replace the task if it is in the queue
105                for (int i = 0; i != scheduledTasks.Count; ++i)
106                    if (scheduledTasks.Values[i].id == task.ID)
107                        scheduledTasks.Values[i] = task;
108            }
109        }
110
111        public override void QueueTask(Task task)
112        {
113            lock (tasksLock)
114            {
115                //Set the task variable to indicate that the task is already
116                //waiting to be executed.
117                task.queued = true;
118
119                //Queue the task to be run immediately.
120                scheduledTasks.Add(DateTime.Now, task);
121                schedulerInterrupt.Set();
122            }
123        }
124
125        public override void QueueRestartTasks()
126        {
127            lock (tasksLock)
128            {
129                foreach (Task task in tasks.Values)
130                    if (task.Schedule == Schedule.RunOnRestart)
131                        QueueTask(task);
132            }
133        }
134
135        public override void CancelTask(Task task)
136        {
137            lock (currentTask)
138            {
139                if (currentTask == task)
140                {
141                    currentTask.cancelled = true;
142                    return;
143                }
144            }
145
146            lock (tasksLock)
147                for (int i = 0; i != scheduledTasks.Count; ++i)
148                    if (scheduledTasks.Values[i] == task)
149                    {
150                        scheduledTasks.RemoveAt(i);
151                        return;
152                    }
153
154            throw new ArgumentOutOfRangeException("The task to be cancelled must " +
155                "either be currently executing or queued.");
156        }
157
158        public override Task GetTask(uint taskId)
159        {
160            lock (tasksLock)
161            {
162                if (!tasks.ContainsKey(taskId))
163                    return null;
164                return tasks[taskId];
165            }
166        }
167
168        public override List<Task> GetTasks()
169        {
170            lock (tasksLock)
171            {
172                Task[] result = new Task[tasks.Count];
173                tasks.Values.CopyTo(result, 0);
174                return new List<Task>(result);
175            }
176        }
177
178        public override void SaveTaskList(Stream stream)
179        {
180            lock (tasksLock)
181                new BinaryFormatter().Serialize(stream, tasks);
182        }
183
184        public override void LoadTaskList(Stream stream)
185        {
186            lock (tasksLock)
187            {
188                //Load the list into the dictionary
189                tasks = (Dictionary<uint, Task>)new BinaryFormatter().Deserialize(stream);
190
191                //Ignore the next portion if there are no tasks
192                if (tasks.Count == 0)
193                    return;
194
195                lock (unusedIdsLock)
196                {
197                    //Find gaps in the numbering
198                    nextId = 1;
199                    foreach (uint id in tasks.Keys)
200                    {
201                        tasks[id].executor = this;
202                        while (id > nextId)
203                            unusedIds.Add(++nextId);
204                        ++nextId;
205                    }
206
207                    //Decrement the ID, since the next ID will be preincremented
208                    //before use.
209                    --nextId;
210                }
211            }
212        }
213
214        /// <summary>
215        /// The thread entry point for this object. This object operates on a queue
216        /// and hence the thread will sequentially execute tasks.
217        /// </summary>
218        private void Main()
219        {
220            //The waiting thread will utilize a polling loop to check for new
221            //scheduled tasks. This will be checked every 30 seconds. However,
222            //when the thread is waiting for a new task, it can be interrupted.
223            while (thread.ThreadState != ThreadState.AbortRequested)
224            {
225                //Check for a new task
226                Task task = null;
227                lock (tasksLock)
228                {
229                    if (scheduledTasks.Count != 0 &&
230                        (scheduledTasks.Values[0].Schedule == Schedule.RunNow ||
231                         scheduledTasks.Keys[0] <= DateTime.Now))
232                    {
233                        task = scheduledTasks.Values[0];
234                        scheduledTasks.RemoveAt(0);
235                    }
236                }
237
238                if (task != null)
239                {
240                    //Set the currently executing task.
241                    currentTask = task;
242
243                    try
244                    {
245                        //Broadcast the task started event.
246                        task.queued = false;
247                        task.cancelled = false;
248                        task.OnTaskStarted(new TaskEventArgs(task));
249
250                        //Start a new log session to separate this session's events
251                        //from previous ones.
252                        task.Log.NewSession();
253
254                        //Run the task
255                        foreach (Task.ErasureTarget target in task.Targets)
256                            try
257                            {
258                                if (target is Task.UnusedSpace)
259                                    EraseUnusedSpace(task, (Task.UnusedSpace)target);
260                                else if (target is Task.FilesystemObject)
261                                    EraseFilesystemObject(task, (Task.FilesystemObject)target);
262                                else
263                                    throw new ArgumentException("Unknown erasure target.");
264                            }
265                            catch (FatalException)
266                            {
267                                throw;
268                            }
269                            catch (Exception e)
270                            {
271                                task.Log.Add(new LogEntry(e.Message, LogLevel.ERROR));
272                            }
273                    }
274                    catch (FatalException e)
275                    {
276                        task.Log.Add(new LogEntry(e.Message, LogLevel.FATAL));
277                    }
278                    finally
279                    {
280                        //If the task is a recurring task, reschedule it since we are done.
281                        if (task.Schedule is RecurringSchedule)
282                            ((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
283
284                        //If the task is an execute on restart task, it is only run
285                        //once and can now be restored to an immediately executed task
286                        if (task.Schedule == Schedule.RunOnRestart)
287                            task.Schedule = Schedule.RunNow;
288
289                        //And the task finished event.
290                        task.OnTaskFinished(new TaskEventArgs(task));
291                    }
292                }
293
294                //Wait for half a minute to check for the next scheduled task.
295                schedulerInterrupt.WaitOne(30000, false);
296            }
297        }
298
299        private class WriteStatistics
300        {
301            public WriteStatistics()
302            {
303                startTime = DateTime.Now;
304            }
305
306            public int Speed
307            {
308                get
309                {
310                    if (DateTime.Now == startTime)
311                        return 0;
312
313                    if ((DateTime.Now - lastSpeedCalc).Seconds < 10 && lastSpeed != 0)
314                        return lastSpeed;
315
316                    lastSpeedCalc = DateTime.Now;
317                    lastSpeed = (int)(dataWritten / (DateTime.Now - startTime).TotalSeconds);
318                    return lastSpeed;
319                }
320            }
321
322            public long DataWritten
323            {
324                get { return dataWritten; }
325                set { dataWritten = value; }
326            }
327
328            private DateTime startTime;
329            private DateTime lastSpeedCalc;
330            private long dataWritten;
331            private int lastSpeed;
332        }
333
334        /// <summary>
335        /// Executes a unused space erase.
336        /// </summary>
337        /// <param name="target">The target of the unused space erase.</param>
338        private void EraseUnusedSpace(Task task, Task.UnusedSpace target)
339        {
340            //Check for sufficient privileges to run the unused space erasure.
341            if (!Permissions.IsAdministrator())
342            {
343                string exceptionString = "The program does not have the required permissions " +
344                    "to erase the unused space on disk";
345                if (Environment.OSVersion.Platform == PlatformID.Win32NT &&
346                    Environment.OSVersion.Version >= new Version(6, 0))
347                {
348                    exceptionString += ". Run the program as administrator and retry the operation.";
349                }
350                throw new Exception(exceptionString);
351            }
352
353            //If the user is under disk quotas, log a warning message
354            if (Drive.HasQuota(target.Drive))
355                task.Log.Add(new LogEntry("The drive which is having its unused space erased has " +
356                    "disk quotas active. This will prevent the complete erasure of unused space and " +
357                    "will pose a security concern", LogLevel.WARNING));
358
359            //Get the erasure method if the user specified he wants the default.
360            ErasureMethod method = target.Method;
361            if (method == ErasureMethodManager.Default)
362                method = ErasureMethodManager.GetInstance(
363                    ManagerLibrary.Instance.Settings.DefaultUnusedSpaceErasureMethod);
364
365            TaskProgressEventArgs eventArgs = new TaskProgressEventArgs(task, 0, 0);
366            eventArgs.currentTarget = target;
367            eventArgs.currentItemName = "Cluster tips";
368            eventArgs.totalPasses = method.Passes;
369            eventArgs.timeLeft = -1;
370            task.OnProgressChanged(eventArgs);
371
372            //Erase the cluster tips of every file on the drive.
373            if (target.EraseClusterTips)
374            {
375                EraseClusterTips(task, target, method,
376                    delegate(int currentFile, string currentFilePath, int totalFiles)
377                    {
378                        eventArgs.currentItemName = "(Tips) " + currentFilePath;
379                        eventArgs.currentItemProgress = (int)((float)currentFile / totalFiles * 100);
380                        eventArgs.overallProgress = eventArgs.CurrentItemProgress / 10;
381                        task.OnProgressChanged(eventArgs);
382                    }
383                );
384            }
385
386            //Make a folder to dump our temporary files in
387            DirectoryInfo info = new DirectoryInfo(target.Drive).Root;
388            {
389                string directoryName;
390                do
391                    directoryName = GenerateRandomFileName(18);
392                while (Directory.Exists(info.FullName + Path.DirectorySeparatorChar + directoryName));
393                info = info.CreateSubdirectory(directoryName);
394            }
395
396            try
397            {
398                //Set the folder's compression flag off since we want to use as much
399                //space as possible
400                if (Eraser.Util.File.IsCompressed(info.FullName))
401                    Eraser.Util.File.SetCompression(info.FullName, false);
402
403                //Determine the total amount of data that needs to be written.
404                WriteStatistics statistics = new WriteStatistics();
405                DriveInfo driveInfo = new DriveInfo(info.Root.FullName);
406                long totalSize = method.CalculateEraseDataSize(null, driveInfo.TotalFreeSpace);
407
408                //Continue creating files while there is free space.
409                eventArgs.currentItemName = "Unused space";
410                task.OnProgressChanged(eventArgs);
411                while (driveInfo.TotalFreeSpace > 0)
412                {
413                    //Generate a non-existant file name
414                    string currFile;
415                    do
416                        currFile = info.FullName + Path.DirectorySeparatorChar +
417                            GenerateRandomFileName(18);
418                    while (System.IO.File.Exists(currFile));
419                   
420                    //Create the stream
421                    using (FileStream stream = new FileStream(currFile,
422                        FileMode.CreateNew, FileAccess.Write))
423                    {
424                        //Set the length of the file to be the amount of free space left
425                        //or the maximum size of one of these dumps.
426                        stream.SetLength(Math.Min(ErasureMethod.FreeSpaceFileUnit,
427                            driveInfo.TotalFreeSpace));
428
429                        //Then run the erase task
430                        method.Erase(stream, long.MaxValue,
431                            PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG),
432                            delegate(long lastWritten, int currentPass)
433                            {
434                                statistics.DataWritten += lastWritten;
435                                eventArgs.currentPass = currentPass;
436                                eventArgs.currentItemProgress = (int)(statistics.DataWritten * 100 / totalSize);
437                                eventArgs.overallProgress = (int)((10 + eventArgs.currentItemProgress * 0.8));
438
439                                if (statistics.Speed == 0)
440                                    eventArgs.timeLeft = -1;
441                                else
442                                    eventArgs.timeLeft = (int)((totalSize - statistics.DataWritten) / statistics.Speed);
443                                task.OnProgressChanged(eventArgs);
444
445                                lock (currentTask)
446                                    if (currentTask.cancelled)
447                                        throw new FatalException("The task was cancelled.");
448                            }
449                        );
450                    }
451                }
452
453                //Erase old file system records
454                eventArgs.currentItemName = "Old file system records";
455                task.OnProgressChanged(eventArgs);
456                EraseFilesystemRecords(info, method);
457            }
458            finally
459            {
460                eventArgs.currentItemName = "Removing temporary files";
461                task.OnProgressChanged(eventArgs);
462
463                //Remove the folder holding all our temporary files.
464                RemoveFolder(info);
465            }
466
467
468            //NotImplemented: clear directory entries: Eraser.cpp@2321
469        }
470
471        private delegate void SubFoldersHandler(DirectoryInfo info);
472        private delegate void ClusterTipsEraseProgress(int currentFile,
473            string currentFilePath, int totalFiles);
474        private static void EraseClusterTips(Task task, Task.UnusedSpace target,
475            ErasureMethod method, ClusterTipsEraseProgress callback)
476        {
477            //List all the files which can be erased.
478            List<string> files = new List<string>();
479            SubFoldersHandler subFolders = null;
480            subFolders = delegate(DirectoryInfo info)
481            {
482                try
483                {
484                    foreach (FileInfo file in info.GetFiles())
485                        if (Eraser.Util.File.IsProtectedSystemFile(file.FullName))
486                            task.Log.Add(new LogEntry(string.Format("{0} did not have its cluster tips " +
487                                "erased, because it is a system file", file.FullName), LogLevel.INFORMATION));
488                        else
489                        {
490                            foreach (string i in Util.File.GetADSes(file))
491                                files.Add(file.FullName + ':' + i);
492                            files.Add(file.FullName);
493                        }
494
495                    foreach (DirectoryInfo subDir in info.GetDirectories())
496                        subFolders(subDir);
497                }
498                catch (UnauthorizedAccessException)
499                {
500                }
501                catch (IOException)
502                {
503                }
504            };
505
506            subFolders(new DirectoryInfo(target.Drive));
507
508            //For every file, erase the cluster tips.
509            for (int i = 0, j = files.Count; i != j; ++i)
510            {
511                EraseFileClusterTips(files[i], method);
512                callback(i, files[i], files.Count);
513            }
514        }
515
516        /// <summary>
517        /// Erases the cluster tips of the given file.
518        /// </summary>
519        /// <param name="file">The file to erase.</param>
520        /// <param name="method">The erasure method to use.</param>
521        private static void EraseFileClusterTips(string file, ErasureMethod method)
522        {
523            //Get the file access times
524            StreamInfo streamInfo = new StreamInfo(file);
525            DateTime lastAccess = DateTime.MinValue, lastWrite = DateTime.MinValue,
526                     created = DateTime.MinValue;
527            {
528                FileInfo info = streamInfo.File;
529                if (info != null)
530                {
531                    lastAccess = info.LastAccessTime;
532                    lastWrite = info.LastWriteTime;
533                    created = info.CreationTime;
534                }
535            }
536
537            //Create the stream, lengthen the file, then tell the erasure method
538            //to erase the tips.
539            using (FileStream stream = streamInfo.Open(FileMode.Open, FileAccess.Write))
540            {
541                long fileLength = stream.Length;
542                long fileArea = GetFileArea(file);
543
544                try
545                {
546                    stream.SetLength(fileArea);
547                    stream.Seek(fileLength, SeekOrigin.Begin);
548
549                    //Erase the file
550                    method.Erase(stream, long.MaxValue, PRNGManager.GetInstance(
551                        ManagerLibrary.Instance.Settings.ActivePRNG), null);
552                }
553                finally
554                {
555                    //Make sure the file is restored!
556                    stream.SetLength(fileLength);
557                }
558            }
559
560            //Set the file times
561            FileInfo fileInfo = streamInfo.File;
562            if (fileInfo != null)
563            {
564                fileInfo.LastAccessTime = lastAccess;
565                fileInfo.LastWriteTime = lastWrite;
566                fileInfo.CreationTime = created;
567            }
568        }
569
570        /// <summary>
571        /// Erases the old MFT or FAT records. This creates small one-byte files
572        /// until the MFT or FAT grows, if the disk is not full. If the disk is
573        /// full, just keep forcing the MFT to store files until nothing else
574        /// fits.
575        /// </summary>
576        /// <param name="info">The directory information structure containing
577        /// the path to store the temporary one-byte files. The MFT of that
578        /// drive will be erased.</param>
579        /// <param name="method">The method used to erase the records.</param>
580        private void EraseFilesystemRecords(DirectoryInfo info, ErasureMethod method)
581        {
582            DriveInfo driveInfo = new DriveInfo(info.Root.FullName);
583            string driveFormat = driveInfo.DriveFormat;
584            if (driveFormat == "NTFS")
585            {
586                //If the volume is full, squeeze one-byte files.
587                try
588                {
589                    FileInfo mftInfo = new FileInfo(driveInfo.RootDirectory.FullName +
590                        Path.DirectorySeparatorChar + "$MFT");
591                    long oldMFTSize = mftInfo.Length;
592                    for ( ; ; )
593                    {
594                        //Open this stream
595                        using (FileStream strm = new FileStream(info.FullName +
596                            Path.DirectorySeparatorChar + GenerateRandomFileName(18),
597                            FileMode.CreateNew, FileAccess.Write))
598                        {
599                            //Stretch the file size to the size of one MFT record
600                            strm.SetLength(1);
601
602                            //Then run the erase task
603                            method.Erase(strm, long.MaxValue,
604                                PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG),
605                                null);
606                        }
607
608                        //Determine if we can stop. We will stop if the disk is not
609                        //full and the MFT has grown in size.
610                        if (driveInfo.TotalFreeSpace != 0 && mftInfo.Length != oldMFTSize)
611                            break;
612                    }
613                }
614                catch (IOException)
615                {
616                    //OK, enough squeezing.
617                }
618            }
619            else
620                throw new NotImplementedException("Could not erase old file system " +
621                    "records: Unsupported File system");
622        }
623
624        /// <summary>
625        /// Erases a file or folder on the volume.
626        /// </summary>
627        /// <param name="target">The target of the erasure.</param>
628        private void EraseFilesystemObject(Task task, Task.FilesystemObject target)
629        {
630            //Retrieve the list of files to erase.
631            long dataTotal = 0;
632            List<string> paths = target.GetPaths(out dataTotal);
633            TaskProgressEventArgs eventArgs = new TaskProgressEventArgs(task, 0, 0);
634
635            //Get the erasure method if the user specified he wants the default.
636            ErasureMethod method = target.Method;
637            if (method == ErasureMethodManager.Default)
638                method = ErasureMethodManager.GetInstance(ManagerLibrary.Instance.Settings.DefaultFileErasureMethod);
639
640            //Calculate the total amount of data required to finish the wipe.
641            dataTotal = method.CalculateEraseDataSize(paths, dataTotal);
642
643            //Record the start of the erasure pass so we can calculate speed of erasures
644            WriteStatistics statistics = new WriteStatistics();
645
646            //Iterate over every path, and erase the path.
647            for (int i = 0; i < paths.Count; ++i)
648            {
649                //Update the task progress
650                eventArgs.overallProgress = (i * 100) / paths.Count;
651                eventArgs.currentTarget = target;
652                eventArgs.currentItemName = paths[i];
653                eventArgs.currentItemProgress = 0;
654                eventArgs.totalPasses = method.Passes;
655                task.OnProgressChanged(eventArgs);
656
657                //Make sure the file does not have any attributes which may affect
658                //the erasure process
659                bool isReadOnly = false;
660                StreamInfo info = new StreamInfo(paths[i]);
661                if ((info.Attributes & FileAttributes.Compressed) != 0 ||
662                    (info.Attributes & FileAttributes.Encrypted) != 0 ||
663                    (info.Attributes & FileAttributes.SparseFile) != 0 ||
664                    (info.Attributes & FileAttributes.ReparsePoint) != 0)
665                {
666                    //Log the error
667                    throw new ArgumentException("Compressed, encrypted, or sparse" +
668                        "files cannot be erased with Eraser.");
669                }
670
671                //Remove the read-only flag, if it is set.
672                if (isReadOnly = info.IsReadOnly)
673                    info.IsReadOnly = false;
674
675                try
676                {
677                    //Create the file stream, and call the erasure method to write to
678                    //the stream.
679                    using (FileStream strm = info.Open(FileMode.Open, FileAccess.Write,
680                        FileShare.None, FileOptions.WriteThrough))
681                    {
682                        //Set the end of the stream after the wrap-round the cluster size
683                        strm.SetLength(GetFileArea(paths[i]));
684
685                        //If the stream is empty, there's nothing to overwrite. Continue
686                        //to the next entry
687                        if (strm.Length != 0)
688                        {
689                            //Then erase the file.
690                            long itemWritten = 0,
691                                 itemTotal = method.CalculateEraseDataSize(null, strm.Length);
692                            method.Erase(strm, long.MaxValue,
693                                PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG),
694                                delegate(long lastWritten, int currentPass)
695                                {
696                                    statistics.DataWritten += lastWritten;
697                                    eventArgs.currentPass = currentPass;
698                                    eventArgs.currentItemProgress = (int)
699                                        ((itemWritten += lastWritten) * 100 / itemTotal);
700                                    eventArgs.overallProgress = (int)
701                                        (statistics.DataWritten * 100 / dataTotal);
702
703                                    if (statistics.Speed != 0)
704                                        eventArgs.timeLeft = (int)
705                                            (dataTotal - statistics.DataWritten) / statistics.Speed;
706                                    else
707                                        eventArgs.timeLeft = -1;
708                                    task.OnProgressChanged(eventArgs);
709
710                                    lock (currentTask)
711                                        if (currentTask.cancelled)
712                                            throw new FatalException("The task was cancelled.");
713                                }
714                            );
715                        }
716
717                        //Set the length of the file to 0.
718                        strm.Seek(0, SeekOrigin.Begin);
719                        strm.SetLength(0);
720                    }
721
722                    //Remove the file.
723                    FileInfo fileInfo = info.File;
724                    if (fileInfo != null)
725                        RemoveFile(fileInfo);
726                }
727                finally
728                {
729                    //Re-set the read-only flag
730                    info.IsReadOnly = isReadOnly;
731                }
732            }
733
734            //If the user requested a folder removal, do it.
735            if (target is Task.Folder)
736            {
737                Task.Folder fldr = (Task.Folder)target;
738                if (fldr.DeleteIfEmpty)
739                    RemoveFolder(new DirectoryInfo(fldr.Path));
740            }
741        }
742
743        /// <summary>
744        /// Retrieves the size of the file on disk, calculated by the amount of
745        /// clusters allocated by it.
746        /// </summary>
747        /// <param name="filePath">The path to the file.</param>
748        /// <returns>The area of the file.</returns>
749        private static long GetFileArea(string filePath)
750        {
751            StreamInfo info = new StreamInfo(filePath);
752            uint clusterSize = Drive.GetClusterSize(info.Directory.Root.FullName);
753            return (info.Length + (clusterSize - 1)) & ~(clusterSize - 1);
754        }
755
756        /// <summary>
757        /// Securely removes files.
758        /// </summary>
759        /// <param name="info">The FileInfo object representing the file.</param>
760        private static void RemoveFile(FileInfo info)
761        {
762            //Set the date of the file to be invalid to prevent forensic
763            //detection
764            info.CreationTime = info.LastWriteTime = info.LastAccessTime =
765                new DateTime(1800, 1, 1, 0, 0, 0);
766            info.Attributes = FileAttributes.Normal;
767            info.Attributes = FileAttributes.NotContentIndexed;
768
769            //Rename the file a few times to erase the record from the MFT.
770            for (int i = 0; i < FilenameErasePasses; ++i)
771            {
772                //Rename the file.
773                string newPath = info.DirectoryName + Path.DirectorySeparatorChar +
774                    GenerateRandomFileName(info.Name.Length);
775
776                //Try to rename the file. If it fails, it is probably due to another
777                //process locking the file. Defer, then rename again.
778                try
779                {
780                    info.MoveTo(newPath);
781                }
782                catch (IOException)
783                {
784                    Thread.Sleep(100);
785                    --i;
786                }
787            }
788
789            //If the user wants plausible deniability, find a random file on the same
790            //volume and write it over.
791            if (Manager.ManagerLibrary.Instance.Settings.PlausibleDeniability)
792            {
793                //Get the template file to copy
794                string shadowFile;
795                FileInfo shadowFileInfo;
796                do
797                {
798                    shadowFile = GetRandomFileName(info.Directory.Root);
799                    shadowFileInfo = new FileInfo(shadowFile);
800                }
801                while (shadowFileInfo.DirectoryName == info.DirectoryName);
802               
803                //Rename the file to have the same name as the shadow
804                info.MoveTo(info.DirectoryName + shadowFileInfo.Name);
805
806                //Dump the copy (the first 4MB, or less, depending on the file size and available
807                //user space)
808                long amountToCopy = Math.Min(4 * 1024 * 1024, shadowFileInfo.Length);
809                using (FileStream shadowFileStream = shadowFileInfo.OpenRead())
810                using (FileStream destFileStream = info.OpenWrite())
811                {
812                    while (destFileStream.Position < amountToCopy)
813                    {
814                        byte[] buf = new byte[524288];
815                        int bytesRead = shadowFileStream.Read(buf, 0, buf.Length);
816
817                        //Stop bothering if the input stream is at the end
818                        if (bytesRead == 0)
819                            break;
820
821                        //Dump the read contents onto the file to be deleted
822                        destFileStream.Write(buf, 0,
823                            (int)Math.Min(bytesRead, amountToCopy - destFileStream.Position));
824                    }
825                }
826            }
827
828            //Then delete the file.
829            info.Delete();
830        }
831
832        /// <summary>
833        /// Removes the folder and all its contents.
834        /// </summary>
835        /// <param name="info">The folder to remove.</param>
836        private static void RemoveFolder(DirectoryInfo info)
837        {
838            foreach (FileInfo file in info.GetFiles())
839                RemoveFile(file);
840            foreach (DirectoryInfo dir in info.GetDirectories())
841                RemoveFolder(dir);
842
843            //Then clean up this folder.
844            for (int i = 0; i < FilenameErasePasses; ++i)
845            {
846                //Rename the folder.
847                string newPath = info.Parent.FullName + Path.DirectorySeparatorChar +
848                    GenerateRandomFileName(info.Name.Length);
849
850                //Try to rename the file. If it fails, it is probably due to another
851                //process locking the file. Defer, then rename again.
852                try
853                {
854                    info.MoveTo(newPath);
855                }
856                catch (IOException)
857                {
858                    Thread.Sleep(100);
859                    --i;
860                }
861            }
862
863            //Remove the folder
864            info.Delete();
865        }
866
867        /// <summary>
868        /// Generates a random file name with the given length.
869        /// </summary>
870        /// <param name="length">The length of the file name to generate.</param>
871        /// <returns>A random file name.</returns>
872        private static string GenerateRandomFileName(int length)
873        {
874            //Get a random file name
875            PRNG prng = PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG);
876            byte[] newFileNameAry = new byte[length];
877            prng.NextBytes(newFileNameAry);
878
879            //Validate the name
880            string validFileNameChars = "0123456789abcdefghijklmnopqrstuvwxyz" +
881                "ABCDEFGHIJKLMNOPQRSTUVWXYZ _+=-()[]{}',`~!";
882            for (int j = 0, k = newFileNameAry.Length; j < k; ++j)
883                newFileNameAry[j] = (byte)validFileNameChars[
884                    (int)newFileNameAry[j] % validFileNameChars.Length];
885
886            return new System.Text.UTF8Encoding().GetString(newFileNameAry);
887        }
888
889        /// <summary>
890        /// Gets a random file from within the provided directory.
891        /// </summary>
892        /// <param name="info">The directory to get a random file name from.</param>
893        /// <returns>A string containing the full path to the file.</returns>
894        private static string GetRandomFileName(DirectoryInfo info)
895        {
896            //First retrieve the list of files and folders in the provided directory.
897            FileSystemInfo[] entries = null;
898            try
899            {
900                entries = info.GetFileSystemInfos();
901            }
902            catch (Exception)
903            {
904                return string.Empty;
905            }
906            if (entries.Length == 0)
907                return string.Empty;
908
909            //Find a random entry.
910            PRNG prng = PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG);
911            string result = string.Empty;
912            while (result.Length == 0)
913            {
914                int index = prng.Next(entries.Length - 1);
915                if (entries[index] is DirectoryInfo)
916                    result = GetRandomFileName((DirectoryInfo)entries[index]);
917                else 
918                    result = ((FileInfo)entries[index]).FullName;
919            }
920
921            return result;
922        }
923
924        /// <summary>
925        /// The thread object.
926        /// </summary>
927        private Thread thread;
928
929        /// <summary>
930        /// The lock preventing concurrent access for the tasks list and the
931        /// tasks queue.
932        /// </summary>
933        private object tasksLock = new object();
934
935        /// <summary>
936        /// The list of tasks. Includes all immediate, reboot, and recurring tasks
937        /// </summary>
938        private Dictionary<uint, Task> tasks = new Dictionary<uint, Task>();
939
940        /// <summary>
941        /// The queue of tasks. This queue is executed when the first element's
942        /// timestamp (the key) has been past. This list assumes that all tasks
943        /// are sorted by timestamp, smallest one first.
944        /// </summary>
945        private SortedList<DateTime, Task> scheduledTasks =
946            new SortedList<DateTime, Task>();
947
948        /// <summary>
949        /// The currently executing task.
950        /// </summary>
951        Task currentTask;
952
953        /// <summary>
954        /// The list of task IDs for recycling.
955        /// </summary>
956        private List<uint> unusedIds = new List<uint>();
957
958        /// <summary>
959        /// Lock preventing concurrent access for the IDs.
960        /// </summary>
961        private object unusedIdsLock = new object();
962
963        /// <summary>
964        /// Incrementing ID. This value is incremented by one every time an ID
965        /// is required by no unused IDs remain.
966        /// </summary>
967        private uint nextId = 0;
968
969        /// <summary>
970        /// An automatically reset event allowing the addition of new tasks to
971        /// interrupt the thread's sleeping state waiting for the next recurring
972        /// task to be due.
973        /// </summary>
974        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
975    }
976}
Note: See TracBrowser for help on using the repository browser.