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

Revision 225, 13.2 KB checked in by lowjoel, 7 years ago (diff)

Define a QueueTask? function for immediate tasks to be executed repeatedly.

Line 
1using System;
2using System.Collections.Generic;
3using System.Collections.Specialized;
4using System.Text;
5using System.Threading;
6using System.IO;
7
8using Eraser.Util;
9
10namespace Eraser.Manager
11{
12    /// <summary>
13    /// The DirectExecutor class is used by the Eraser GUI directly when the program
14    /// is run without the help of a Service.
15    /// </summary>
16    public class DirectExecutor : Executor, IDisposable
17    {
18        public DirectExecutor()
19        {
20            thread = new Thread(delegate()
21            {
22                Main();
23            });
24
25            thread.Start();
26            Thread.Sleep(0);
27        }
28
29        void IDisposable.Dispose()
30        {
31            thread.Abort();
32            schedulerInterrupt.Set();
33        }
34
35        public override void AddTask(ref Task task)
36        {
37            lock (unusedIdsLock)
38            {
39                if (unusedIds.Count != 0)
40                {
41                    task.id = unusedIds[0];
42                    unusedIds.RemoveAt(0);
43                }
44                else
45                    task.id = ++nextId;
46            }
47
48            //Set the executor of the task
49            task.executor = this;
50
51            //Add the task to the set of tasks
52            lock (tasksLock)
53            {
54                tasks.Add(task.ID, task);
55                QueueTask(task);
56            }
57        }
58
59        public override bool DeleteTask(uint taskId)
60        {
61            lock (tasksLock)
62            {
63                if (!tasks.ContainsKey(taskId))
64                    return false;
65
66                lock (unusedIdsLock)
67                    unusedIds.Add(taskId);
68                tasks.Remove(taskId);
69            }
70            return true;
71        }
72
73        public override void QueueTask(Task task)
74        {
75            //Ignore tasks which are only run on computer restart.
76            if (task.Schedule == Schedule.RunOnRestart)
77                return;
78
79            lock (tasksLock)
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                    scheduledTasks.Add(DateTime.Now, task);
86                    schedulerInterrupt.Set();
87                }
88                //If the task is scheduled, add the next execution time to the list
89                //of schduled tasks.
90                else
91                {
92                    scheduledTasks.Add((task.Schedule as RecurringSchedule).NextRun, task);
93                }
94            }
95        }
96
97        public override void CancelTask(Task task)
98        {
99            lock (currentTask)
100                if (currentTask == task)
101                    currentTask.cancelled = true;
102        }
103
104        public override Task GetTask(uint taskId)
105        {
106            lock (tasksLock)
107            {
108                if (!tasks.ContainsKey(taskId))
109                    return null;
110                return tasks[taskId];
111            }
112        }
113
114        /// <summary>
115        /// The thread entry point for this object. This object operates on a queue
116        /// and hence the thread will sequentially execute tasks.
117        /// </summary>
118        private void Main()
119        {
120            //The waiting thread will utilize a polling loop to check for new
121            //scheduled tasks. This will be checked every 30 seconds. However,
122            //when the thread is waiting for a new task, it can be interrupted.
123            while (thread.ThreadState != ThreadState.AbortRequested)
124            {
125                //Check for a new task
126                Task task = null;
127                lock (tasksLock)
128                {
129                    if (scheduledTasks.Count != 0 &&
130                        (scheduledTasks.Values[0].Schedule == Schedule.RunNow ||
131                         scheduledTasks.Keys[0] <= DateTime.Now))
132                    {
133                        task = scheduledTasks.Values[0];
134                        scheduledTasks.RemoveAt(0);
135                    }
136                }
137
138                if (task != null)
139                {
140                    //Set the currently executing task.
141                    currentTask = task;
142
143                    try
144                    {
145                        //Broadcast the task started event.
146                        task.cancelled = false;
147                        task.OnTaskStarted(new TaskEventArgs(task));
148
149                        //Run the task
150                        foreach (Task.ErasureTarget target in task.Entries)
151                            try
152                            {
153                                if (target is Task.UnusedSpace)
154                                    EraseUnusedSpace(task, (Task.UnusedSpace)target);
155                                else if (target is Task.FilesystemObject)
156                                    EraseFilesystemObject(task, (Task.FilesystemObject)target);
157                                else
158                                    throw new ArgumentException("Unknown erasure target.");
159                            }
160                            catch (FatalException)
161                            {
162                                throw;
163                            }
164                            catch (Exception e)
165                            {
166                                task.LogEntry(new LogEntry(e.Message, LogLevel.ERROR));
167                            }
168                    }
169                    catch (FatalException e)
170                    {
171                        task.LogEntry(new LogEntry(e.Message, LogLevel.FATAL));
172                    }
173                    finally
174                    {
175                        //And the task finished event.
176                        task.OnTaskFinished(new TaskEventArgs(task));
177
178                        //If the task is a recurring task, reschedule it since we are done.
179                        if (task.Schedule is RecurringSchedule)
180                            ((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
181                    }
182                }
183
184                //Wait for half a minute to check for the next scheduled task.
185                schedulerInterrupt.WaitOne(30000, false);
186            }
187        }
188
189        /// <summary>
190        /// Executes a unused space erase.
191        /// </summary>
192        /// <param name="target">The target of the unused space erase.</param>
193        private void EraseUnusedSpace(Task task, Task.UnusedSpace target)
194        {
195            throw new NotImplementedException("Unused space erasures are not "+
196                "currently implemented");
197        }
198
199        /// <summary>
200        /// Erases a file or folder on the volume.
201        /// </summary>
202        /// <param name="target">The target of the erasure.</param>
203        private void EraseFilesystemObject(Task task, Task.FilesystemObject target)
204        {
205            //Retrieve the list of files to erase.
206            long totalSize = 0;
207            List<string> paths = target.GetPaths(out totalSize);
208            TaskProgressEventArgs eventArgs = new TaskProgressEventArgs(task, 0, 0);
209
210            //Get the erasure method if the user specified he wants the default.
211            ErasureMethod method = target.Method;
212            if (method == ErasureMethodManager.Default)
213                method = ErasureMethodManager.GetInstance(Globals.Settings.DefaultFileErasureMethod);
214
215            //Calculate the total amount of data required to finish the wipe. This
216            //value is just the total about of data to be erased multiplied by
217            //number of passes
218            totalSize *= method.Passes;
219
220            //Record the start of the erasure pass so we can calculate speed of erasures
221            long totalLeft = totalSize;
222            long overallWriteSpeed = 0;
223            DateTime startTime = DateTime.Now;
224
225            //Iterate over every path, and erase the path.
226            for (int i = 0; i < paths.Count; ++i)
227            {
228                //Update the task progress
229                eventArgs.overallProgress = (i * 100) / paths.Count;
230                eventArgs.currentTarget = target;
231                eventArgs.currentItemName = paths[i];
232                eventArgs.currentItemProgress = 0;
233                eventArgs.totalPasses = method.Passes;
234                task.OnProgressChanged(eventArgs);
235
236                //Make sure the file does not have any attributes which may
237                //affect the erasure process
238                FileInfo info = new FileInfo(paths[i]);
239                if ((info.Attributes & FileAttributes.Compressed) != 0 ||
240                    (info.Attributes & FileAttributes.Encrypted) != 0 ||
241                    (info.Attributes & FileAttributes.SparseFile) != 0 ||
242                    (info.Attributes & FileAttributes.ReparsePoint) != 0)
243                {
244                    //Log the error
245                    throw new ArgumentException("Compressed, encrypted, or sparse" +
246                        "files cannot be erased with Eraser.");
247                }
248
249                //Remove the read-only flag, if it is set.
250                if ((info.Attributes & FileAttributes.ReadOnly) != 0)
251                    info.Attributes &= ~FileAttributes.ReadOnly;
252
253                //Create the file stream, and call the erasure method
254                //to write to the stream.
255                using (FileStream strm = new FileStream(info.FullName,
256                    FileMode.Open, FileAccess.Write, FileShare.None,
257                    8, FileOptions.WriteThrough))
258                {
259                    //Set the end of the stream after the wrap-round the cluster size
260                    uint clusterSize = Drives.GetDriveClusterSize(info.Directory.Root.FullName);
261                    long roundUpFileLength = strm.Length % clusterSize;
262                    if (roundUpFileLength != 0)
263                        strm.SetLength(strm.Length + (clusterSize - roundUpFileLength));
264
265                    //Then erase the file.
266                    method.Erase(strm, PRNGManager.GetInstance(Globals.Settings.ActivePRNG),
267                        delegate(float currentProgress, int currentPass)
268                        {
269                            long amountWritten = (long)(currentProgress * (info.Length * method.Passes));
270                            if (overallWriteSpeed != 0)
271                                eventArgs.timeLeft = (int)((totalLeft - amountWritten) /
272                                    overallWriteSpeed);
273                            else if (amountWritten != 0 && (DateTime.Now - startTime).TotalSeconds != 0)
274                                overallWriteSpeed = (long)(amountWritten /
275                                    (DateTime.Now - startTime).TotalSeconds);
276
277                            eventArgs.currentPass = currentPass;
278                            eventArgs.currentItemProgress = (int)
279                                ((float)currentProgress * 100.0);
280                            eventArgs.overallProgress = (int)
281                                (((i + currentProgress) / (float)paths.Count) * 100);
282                            task.OnProgressChanged(eventArgs);
283
284                            lock (currentTask)
285                                if (currentTask.cancelled)
286                                    throw new FatalException("The task was cancelled.");
287                        }
288                    );
289
290                    //Update the speed-influencing statistics.
291                    totalLeft -= info.Length * method.Passes;
292                    overallWriteSpeed = (long)((totalSize - totalLeft) /
293                        (DateTime.Now - startTime).TotalSeconds);
294
295                    //Set the length of the file to 0.
296                    strm.Seek(0, SeekOrigin.Begin);
297                    strm.SetLength(0);
298                }
299
300                //Remove the file.
301                RemoveFile(info);
302            }
303
304            //If the user requested a folder removal, do it.
305            if (target is Task.Folder)
306            {
307                Task.Folder fldr = (Task.Folder)target;
308                if (fldr.DeleteIfEmpty)
309                    RemoveFolder(new DirectoryInfo(fldr.Path));
310            }
311        }
312
313        /// <summary>
314        /// Securely removes files.
315        /// </summary>
316        /// <param name="info">The FileInfo object representing the file.</param>
317        private void RemoveFile(FileInfo info)
318        {
319            //Set the date of the file to be invalid to prevent forensic
320            //detection
321            info.CreationTime = info.LastWriteTime = info.LastAccessTime =
322                new DateTime(1800, 1, 1, 0, 0, 0);
323            info.Attributes = FileAttributes.Normal;
324            info.Attributes = FileAttributes.NotContentIndexed;
325
326            //Rename the file a few times to erase the record from the MFT.
327            for (int i = 0; i < FilenameErasePasses; ++i)
328            {
329                //Get a random file name
330                PRNG prng = PRNGManager.GetInstance(Globals.Settings.ActivePRNG);
331                byte[] newFileNameAry = new byte[info.Name.Length];
332                prng.NextBytes(newFileNameAry);
333
334                //Validate the name
335                const string validFileNameChars = "0123456789abcdefghijklmnopqrs" +
336                    "tuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
337                for (int j = 0, k = newFileNameAry.Length; j < k; ++j)
338                    newFileNameAry[j] = (byte)validFileNameChars[
339                        (int)newFileNameAry[j] % validFileNameChars.Length];
340
341                //Rename the file.
342                string newPath = info.DirectoryName + Path.DirectorySeparatorChar +
343                    (new System.Text.UTF8Encoding()).GetString(newFileNameAry);
344                info.MoveTo(newPath);
345            }
346
347            //Then delete the file.
348            info.Delete();
349        }
350
351        private void RemoveFolder(DirectoryInfo info)
352        {
353            foreach (FileInfo file in info.GetFiles())
354                RemoveFile(file);
355            foreach (DirectoryInfo dir in info.GetDirectories())
356                RemoveFolder(dir);
357
358            //Then clean up this folder.
359            for (int i = 0; i < FilenameErasePasses; ++i)
360            {
361                //Get a random file name
362                PRNG prng = PRNGManager.GetInstance(Globals.Settings.ActivePRNG);
363                byte[] newFileNameAry = new byte[info.Name.Length];
364                prng.NextBytes(newFileNameAry);
365
366                //Validate the name
367                const string validFileNameChars = "0123456789abcdefghijklmnopqrs" +
368                    "tuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
369                for (int j = 0, k = newFileNameAry.Length; j < k; ++j)
370                    newFileNameAry[j] = (byte)validFileNameChars[
371                        (int)newFileNameAry[j] % validFileNameChars.Length];
372
373                //Rename the folder.
374                string newPath = info.Parent.FullName + Path.DirectorySeparatorChar +
375                    (new System.Text.UTF8Encoding()).GetString(newFileNameAry);
376
377                //Try to rename the file. If it fails, it is probably due to another
378                //process locking the file. Defer, then rename again.
379                try
380                {
381                    info.MoveTo(newPath);
382                }
383                catch (IOException)
384                {
385                    Thread.Sleep(100);
386                    --i;
387                }
388            }
389
390            //Remove the folder
391            info.Delete();
392        }
393
394        /// <summary>
395        /// The thread object.
396        /// </summary>
397        private Thread thread;
398
399        /// <summary>
400        /// The lock preventing concurrent access for the tasks list and the
401        /// tasks queue.
402        /// </summary>
403        private object tasksLock = new object();
404
405        /// <summary>
406        /// The list of tasks. Includes all immediate, reboot, and recurring tasks
407        /// </summary>
408        private Dictionary<uint, Task> tasks = new Dictionary<uint, Task>();
409
410        /// <summary>
411        /// The queue of tasks. This queue is executed when the first element's
412        /// timestamp (the key) has been past. This list assumes that all tasks
413        /// are sorted by timestamp, smallest one first.
414        /// </summary>
415        private SortedList<DateTime, Task> scheduledTasks =
416            new SortedList<DateTime, Task>();
417
418        /// <summary>
419        /// The currently executing task.
420        /// </summary>
421        Task currentTask;
422
423        /// <summary>
424        /// The list of task IDs for recycling.
425        /// </summary>
426        private List<uint> unusedIds = new List<uint>();
427
428        /// <summary>
429        /// Lock preventing concurrent access for the IDs.
430        /// </summary>
431        private object unusedIdsLock = new object();
432
433        /// <summary>
434        /// Incrementing ID. This value is incremented by one every time an ID
435        /// is required by no unused IDs remain.
436        /// </summary>
437        private uint nextId = 0;
438
439        /// <summary>
440        /// An automatically reset event allowing the addition of new tasks to
441        /// interrupt the thread's sleeping state waiting for the next recurring
442        /// task to be due.
443        /// </summary>
444        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
445    }
446}
Note: See TracBrowser for help on using the repository browser.