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

Revision 221, 13.0 KB checked in by lowjoel, 6 years ago (diff)

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