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

Revision 439, 32.2 KB checked in by lowjoel, 6 years ago (diff)

Rescheduled (recurring) tasks need to be removed from the priority queue for tasks since their placement may change.

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