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

Revision 266, 16.4 KB checked in by lowjoel, 6 years ago (diff)

-Restore the Executor property of tasks when they are loaded
-Define the GetFileArea? function to calculate the physical size of the file on-disk

Line 
1using System;
2using System.Collections.Generic;
3using System.Collections.Specialized;
4using System.Text;
5using System.Threading;
6using System.IO;
7
8using Eraser.Util;
9using System.Runtime.Serialization;
10using System.Runtime.Serialization.Formatters.Binary;
11
12namespace Eraser.Manager
13{
14    /// <summary>
15    /// The DirectExecutor class is used by the Eraser GUI directly when the program
16    /// is run without the help of a Service.
17    /// </summary>
18    public class DirectExecutor : Executor, IDisposable
19    {
20        public DirectExecutor()
21        {
22            thread = new Thread(delegate()
23            {
24                Main();
25            });
26
27            thread.Start();
28            Thread.Sleep(0);
29        }
30
31        void IDisposable.Dispose()
32        {
33            thread.Abort();
34            schedulerInterrupt.Set();
35        }
36
37        public override void AddTask(ref Task task)
38        {
39            lock (unusedIdsLock)
40            {
41                if (unusedIds.Count != 0)
42                {
43                    task.id = unusedIds[0];
44                    unusedIds.RemoveAt(0);
45                }
46                else
47                    task.id = ++nextId;
48            }
49
50            //Set the executor of the task
51            task.executor = this;
52
53            //Add the task to the set of tasks
54            lock (tasksLock)
55            {
56                tasks.Add(task.ID, task);
57
58                //If the task is scheduled to run now, break the waiting thread and
59                //run it immediately
60                if (task.Schedule == Schedule.RunNow)
61                {
62                    QueueTask(task);
63                }
64                //If the task is scheduled, add the next execution time to the list
65                //of schduled tasks.
66                else if (task.Schedule != Schedule.RunOnRestart)
67                {
68                    scheduledTasks.Add((task.Schedule as RecurringSchedule).NextRun, task);
69                }
70            }
71        }
72
73        public override bool DeleteTask(uint taskId)
74        {
75            lock (tasksLock)
76            {
77                if (!tasks.ContainsKey(taskId))
78                    return false;
79
80                lock (unusedIdsLock)
81                    unusedIds.Add(taskId);
82                tasks.Remove(taskId);
83
84                for (int i = 0; i != scheduledTasks.Count; ++i)
85                    if (scheduledTasks.Values[i].id == taskId)
86                        scheduledTasks.RemoveAt(i);
87            }
88
89            return true;
90        }
91
92        public override void ReplaceTask(Task task)
93        {
94            lock (tasksLock)
95            {
96                //Replace the task in the global set
97                if (!tasks.ContainsKey(task.ID))
98                    return;
99
100                tasks[task.ID] = task;
101
102                //Then replace the task if it is in the queue
103                for (int i = 0; i != scheduledTasks.Count; ++i)
104                    if (scheduledTasks.Values[i].id == task.ID)
105                        scheduledTasks.Values[i] = task;
106            }
107        }
108
109        public override void QueueTask(Task task)
110        {
111            lock (tasksLock)
112            {
113                //Set the task variable to indicate that the task is already
114                //waiting to be executed.
115                task.queued = true;
116
117                //Queue the task to be run immediately.
118                scheduledTasks.Add(DateTime.Now, task);
119                schedulerInterrupt.Set();
120            }
121        }
122
123        public override void QueueRestartTasks()
124        {
125            lock (tasksLock)
126            {
127                foreach (Task task in tasks.Values)
128                    if (task.Schedule == Schedule.RunOnRestart)
129                        QueueTask(task);
130            }
131        }
132
133        public override void CancelTask(Task task)
134        {
135            lock (currentTask)
136            {
137                if (currentTask == task)
138                {
139                    currentTask.cancelled = true;
140                    return;
141                }
142            }
143
144            lock (tasksLock)
145                for (int i = 0; i != scheduledTasks.Count; ++i)
146                    if (scheduledTasks.Values[i] == task)
147                    {
148                        scheduledTasks.RemoveAt(i);
149                        return;
150                    }
151
152            throw new ArgumentOutOfRangeException("The task to be cancelled must " +
153                "either be currently executing or queued.");
154        }
155
156        public override Task GetTask(uint taskId)
157        {
158            lock (tasksLock)
159            {
160                if (!tasks.ContainsKey(taskId))
161                    return null;
162                return tasks[taskId];
163            }
164        }
165
166        public override List<Task> GetTasks()
167        {
168            lock (tasksLock)
169            {
170                Task[] result = new Task[tasks.Count];
171                tasks.Values.CopyTo(result, 0);
172                return new List<Task>(result);
173            }
174        }
175
176        public override void SaveTaskList(Stream stream)
177        {
178            lock (tasksLock)
179                new BinaryFormatter().Serialize(stream, tasks);
180        }
181
182        public override void LoadTaskList(Stream stream)
183        {
184            lock (tasksLock)
185            {
186                //Load the list into the dictionary
187                tasks = (Dictionary<uint, Task>)new BinaryFormatter().Deserialize(stream);
188
189                //Ignore the next portion if there are no tasks
190                if (tasks.Count == 0)
191                    return;
192
193                lock (unusedIdsLock)
194                {
195                    //Find gaps in the numbering
196                    nextId = 1;
197                    foreach (uint id in tasks.Keys)
198                    {
199                        tasks[id].executor = this;
200                        while (id > nextId)
201                            unusedIds.Add(++nextId);
202                        ++nextId;
203                    }
204
205                    //Decrement the ID, since the next ID will be preincremented
206                    //before use.
207                    --nextId;
208                }
209            }
210        }
211
212        /// <summary>
213        /// The thread entry point for this object. This object operates on a queue
214        /// and hence the thread will sequentially execute tasks.
215        /// </summary>
216        private void Main()
217        {
218            //The waiting thread will utilize a polling loop to check for new
219            //scheduled tasks. This will be checked every 30 seconds. However,
220            //when the thread is waiting for a new task, it can be interrupted.
221            while (thread.ThreadState != ThreadState.AbortRequested)
222            {
223                //Check for a new task
224                Task task = null;
225                lock (tasksLock)
226                {
227                    if (scheduledTasks.Count != 0 &&
228                        (scheduledTasks.Values[0].Schedule == Schedule.RunNow ||
229                         scheduledTasks.Keys[0] <= DateTime.Now))
230                    {
231                        task = scheduledTasks.Values[0];
232                        scheduledTasks.RemoveAt(0);
233                    }
234                }
235
236                if (task != null)
237                {
238                    //Set the currently executing task.
239                    currentTask = task;
240
241                    try
242                    {
243                        //Broadcast the task started event.
244                        task.queued = false;
245                        task.cancelled = false;
246                        task.OnTaskStarted(new TaskEventArgs(task));
247
248                        //Run the task
249                        foreach (Task.ErasureTarget target in task.Targets)
250                            try
251                            {
252                                if (target is Task.UnusedSpace)
253                                    EraseUnusedSpace(task, (Task.UnusedSpace)target);
254                                else if (target is Task.FilesystemObject)
255                                    EraseFilesystemObject(task, (Task.FilesystemObject)target);
256                                else
257                                    throw new ArgumentException("Unknown erasure target.");
258                            }
259                            catch (FatalException)
260                            {
261                                throw;
262                            }
263                            catch (Exception e)
264                            {
265                                task.LogEntry(new LogEntry(e.Message, LogLevel.ERROR));
266                            }
267                    }
268                    catch (FatalException e)
269                    {
270                        task.LogEntry(new LogEntry(e.Message, LogLevel.FATAL));
271                    }
272                    finally
273                    {
274                        //If the task is a recurring task, reschedule it since we are done.
275                        if (task.Schedule is RecurringSchedule)
276                            ((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
277
278                        //If the task is an execute on restart task, it is only run
279                        //once and can now be restored to an immediately executed task
280                        if (task.Schedule == Schedule.RunOnRestart)
281                            task.Schedule = Schedule.RunNow;
282
283                        //And the task finished event.
284                        task.OnTaskFinished(new TaskEventArgs(task));
285                    }
286                }
287
288                //Wait for half a minute to check for the next scheduled task.
289                schedulerInterrupt.WaitOne(30000, false);
290            }
291        }
292
293        private class WriteStatistics
294        {
295            public WriteStatistics()
296            {
297                startTime = DateTime.Now;
298            }
299
300            public int Speed
301            {
302                get
303                {
304                    if (DateTime.Now == startTime)
305                        return 0;
306
307                    if ((DateTime.Now - lastSpeedCalc).Seconds < 10 && lastSpeed != 0)
308                        return lastSpeed;
309
310                    lastSpeedCalc = DateTime.Now;
311                    lastSpeed = (int)(dataWritten / (DateTime.Now - startTime).TotalSeconds);
312                    return lastSpeed;
313                }
314            }
315
316            public long DataWritten
317            {
318                get { return dataWritten; }
319                set { dataWritten = value; }
320            }
321
322            private DateTime startTime;
323            private DateTime lastSpeedCalc;
324            private long dataWritten;
325            private int lastSpeed;
326        }
327
328        /// <summary>
329        /// Executes a unused space erase.
330        /// </summary>
331        /// <param name="target">The target of the unused space erase.</param>
332        private void EraseUnusedSpace(Task task, Task.UnusedSpace target)
333        {
334            throw new NotImplementedException("Unused space erasures are not "+
335                "currently implemented");
336        }
337
338        /// <summary>
339        /// Erases a file or folder on the volume.
340        /// </summary>
341        /// <param name="target">The target of the erasure.</param>
342        private void EraseFilesystemObject(Task task, Task.FilesystemObject target)
343        {
344            //Retrieve the list of files to erase.
345            long dataTotal = 0;
346            List<string> paths = target.GetPaths(out dataTotal);
347            TaskProgressEventArgs eventArgs = new TaskProgressEventArgs(task, 0, 0);
348
349            //Get the erasure method if the user specified he wants the default.
350            ErasureMethod method = target.Method;
351            if (method == ErasureMethodManager.Default)
352                method = ErasureMethodManager.GetInstance(Globals.Settings.DefaultFileErasureMethod);
353
354            //Calculate the total amount of data required to finish the wipe. This
355            //value is just the total about of data to be erased multiplied by
356            //number of passes
357            dataTotal = method.CalculateEraseDataSize(paths, dataTotal);
358
359            //Record the start of the erasure pass so we can calculate speed of erasures
360            WriteStatistics statistics = new WriteStatistics();
361
362            //Iterate over every path, and erase the path.
363            for (int i = 0; i < paths.Count; ++i)
364            {
365                //Update the task progress
366                eventArgs.overallProgress = (i * 100) / paths.Count;
367                eventArgs.currentTarget = target;
368                eventArgs.currentItemName = paths[i];
369                eventArgs.currentItemProgress = 0;
370                eventArgs.totalPasses = method.Passes;
371                task.OnProgressChanged(eventArgs);
372
373                //Make sure the file does not have any attributes which may
374                //affect the erasure process
375                FileInfo info = new FileInfo(paths[i]);
376                if ((info.Attributes & FileAttributes.Compressed) != 0 ||
377                    (info.Attributes & FileAttributes.Encrypted) != 0 ||
378                    (info.Attributes & FileAttributes.SparseFile) != 0 ||
379                    (info.Attributes & FileAttributes.ReparsePoint) != 0)
380                {
381                    //Log the error
382                    throw new ArgumentException("Compressed, encrypted, or sparse" +
383                        "files cannot be erased with Eraser.");
384                }
385
386                //Remove the read-only flag, if it is set.
387                if ((info.Attributes & FileAttributes.ReadOnly) != 0)
388                    info.Attributes &= ~FileAttributes.ReadOnly;
389
390                //Create the file stream, and call the erasure method
391                //to write to the stream.
392                using (FileStream strm = new FileStream(info.FullName,
393                    FileMode.Open, FileAccess.Write, FileShare.None,
394                    8, FileOptions.WriteThrough))
395                {
396                    //Set the end of the stream after the wrap-round the cluster size
397                    strm.SetLength(GetFileArea(info.FullName));
398
399                    //Then erase the file.
400                    long itemWritten = 0,
401                         itemTotal = method.CalculateEraseDataSize(null, strm.Length);
402                    method.Erase(strm, long.MaxValue,
403                        PRNGManager.GetInstance(Globals.Settings.ActivePRNG),
404                        delegate(long lastWritten, int currentPass)
405                        {
406                            statistics.DataWritten += lastWritten;
407                            eventArgs.currentPass = currentPass;
408                            eventArgs.currentItemProgress = (int)((itemWritten += lastWritten) * 100 / itemTotal);
409                            eventArgs.overallProgress = (int)(statistics.DataWritten * 100 / dataTotal);
410
411                            if (statistics.Speed != 0)
412                                eventArgs.timeLeft = (int)(dataTotal - statistics.DataWritten) / statistics.Speed;
413                            else
414                                eventArgs.timeLeft = -1;
415                            task.OnProgressChanged(eventArgs);
416
417                            lock (currentTask)
418                                if (currentTask.cancelled)
419                                    throw new FatalException("The task was cancelled.");
420                        }
421                    );
422
423                    //Set the length of the file to 0.
424                    strm.Seek(0, SeekOrigin.Begin);
425                    strm.SetLength(0);
426                }
427
428                //Remove the file.
429                RemoveFile(info);
430            }
431
432            //If the user requested a folder removal, do it.
433            if (target is Task.Folder)
434            {
435                Task.Folder fldr = (Task.Folder)target;
436                if (fldr.DeleteIfEmpty)
437                    RemoveFolder(new DirectoryInfo(fldr.Path));
438            }
439        }
440
441        /// <summary>
442        /// Retrieves the size of the file on disk, calculated by the amount of
443        /// clusters allocated by it.
444        /// </summary>
445        /// <param name="filePath">The path to the file.</param>
446        /// <returns>The area of the file.</returns>
447        private static long GetFileArea(string filePath)
448        {
449            FileInfo info = new FileInfo(filePath);
450            uint clusterSize = Drive.GetClusterSize(info.Directory.Root.FullName);
451            return (info.Length + (clusterSize - 1)) & ~(clusterSize - 1);
452        }
453
454        /// <summary>
455        /// Securely removes files.
456        /// </summary>
457        /// <param name="info">The FileInfo object representing the file.</param>
458        private static void RemoveFile(FileInfo info)
459        {
460            //Set the date of the file to be invalid to prevent forensic
461            //detection
462            info.CreationTime = info.LastWriteTime = info.LastAccessTime =
463                new DateTime(1800, 1, 1, 0, 0, 0);
464            info.Attributes = FileAttributes.Normal;
465            info.Attributes = FileAttributes.NotContentIndexed;
466
467            //Rename the file a few times to erase the record from the MFT.
468            for (int i = 0; i < FilenameErasePasses; ++i)
469            {
470                //Rename the file.
471                string newPath = info.DirectoryName + Path.DirectorySeparatorChar +
472                    GetRandomFileName(info.Name.Length);
473
474                //Try to rename the file. If it fails, it is probably due to another
475                //process locking the file. Defer, then rename again.
476                try
477                {
478                    info.MoveTo(newPath);
479                }
480                catch (IOException)
481                {
482                    Thread.Sleep(100);
483                    --i;
484                }
485            }
486
487            //Then delete the file.
488            info.Delete();
489        }
490
491        private static void RemoveFolder(DirectoryInfo info)
492        {
493            foreach (FileInfo file in info.GetFiles())
494                RemoveFile(file);
495            foreach (DirectoryInfo dir in info.GetDirectories())
496                RemoveFolder(dir);
497
498            //Then clean up this folder.
499            for (int i = 0; i < FilenameErasePasses; ++i)
500            {
501                //Rename the folder.
502                string newPath = info.Parent.FullName + Path.DirectorySeparatorChar +
503                    GetRandomFileName(info.Name.Length);
504
505                //Try to rename the file. If it fails, it is probably due to another
506                //process locking the file. Defer, then rename again.
507                try
508                {
509                    info.MoveTo(newPath);
510                }
511                catch (IOException)
512                {
513                    Thread.Sleep(100);
514                    --i;
515                }
516            }
517
518            //Remove the folder
519            info.Delete();
520        }
521
522        /// <summary>
523        /// Generates a random file name with the given length.
524        /// </summary>
525        /// <param name="length">The length of the file name to generate.</param>
526        /// <returns>A random file name.</returns>
527        private static string GetRandomFileName(int length)
528        {
529            //Get a random file name
530            PRNG prng = PRNGManager.GetInstance(Globals.Settings.ActivePRNG);
531            byte[] newFileNameAry = new byte[length];
532            prng.NextBytes(newFileNameAry);
533
534            //Validate the name
535            string validFileNameChars = "0123456789abcdefghijklmnopqrstuvwxyz" +
536                "ABCDEFGHIJKLMNOPQRSTUVWXYZ _+=-()[]{}',`~!";
537            for (int j = 0, k = newFileNameAry.Length; j < k; ++j)
538                newFileNameAry[j] = (byte)validFileNameChars[
539                    (int)newFileNameAry[j] % validFileNameChars.Length];
540
541            return new System.Text.UTF8Encoding().GetString(newFileNameAry);
542        }
543
544        /// <summary>
545        /// The thread object.
546        /// </summary>
547        private Thread thread;
548
549        /// <summary>
550        /// The lock preventing concurrent access for the tasks list and the
551        /// tasks queue.
552        /// </summary>
553        private object tasksLock = new object();
554
555        /// <summary>
556        /// The list of tasks. Includes all immediate, reboot, and recurring tasks
557        /// </summary>
558        private Dictionary<uint, Task> tasks = new Dictionary<uint, Task>();
559
560        /// <summary>
561        /// The queue of tasks. This queue is executed when the first element's
562        /// timestamp (the key) has been past. This list assumes that all tasks
563        /// are sorted by timestamp, smallest one first.
564        /// </summary>
565        private SortedList<DateTime, Task> scheduledTasks =
566            new SortedList<DateTime, Task>();
567
568        /// <summary>
569        /// The currently executing task.
570        /// </summary>
571        Task currentTask;
572
573        /// <summary>
574        /// The list of task IDs for recycling.
575        /// </summary>
576        private List<uint> unusedIds = new List<uint>();
577
578        /// <summary>
579        /// Lock preventing concurrent access for the IDs.
580        /// </summary>
581        private object unusedIdsLock = new object();
582
583        /// <summary>
584        /// Incrementing ID. This value is incremented by one every time an ID
585        /// is required by no unused IDs remain.
586        /// </summary>
587        private uint nextId = 0;
588
589        /// <summary>
590        /// An automatically reset event allowing the addition of new tasks to
591        /// interrupt the thread's sleeping state waiting for the next recurring
592        /// task to be due.
593        /// </summary>
594        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
595    }
596}
Note: See TracBrowser for help on using the repository browser.