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

Revision 229, 14.2 KB checked in by lowjoel, 6 years ago (diff)

Added the ReplaceTask? API to get unexecuted tasks replaced with new parameters.

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