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

Revision 405, 31.4 KB checked in by lowjoel, 6 years ago (diff)

-Make the DirectExecutor? support non-drive mountpoints, ie reparse points. Incomplete though, there are a few places which will change in the next commit.
-Fixed a potentially serious bug: Users with quotas should only fill to the AvailableFreeSpace? value, not the TotalFreeSpace? value since the latter will never be reached.

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