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

Revision 240, 15.2 KB checked in by lowjoel, 6 years ago (diff)

Reschedule the task before saying that it is complete.

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