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

Revision 226, 13.8 KB checked in by lowjoel, 6 years ago (diff)

-Allow Executor.QueueTask? to queue any sort of tasks - queueing means that the task needs to be executed when the thread is no longer busy
-Allow CancelTask? to cancel queued tasks as well
-Added Task.Queued to allow clients to see if a task is being queued on top of whether it is being executed

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