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

Revision 202, 9.7 KB checked in by lowjoel, 7 years ago (diff)

Reimplement the erasure method progress calculation to use floating point instead of fixed point, since floating point allows for significant figures, which is what's more important currently.

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                this.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            //Add the task to the set of tasks
49            lock (tasksLock)
50            {
51                tasks.Add(task.ID, task);
52
53                //If the task is scheduled to run now, break the waiting thread and
54                //run it immediately
55                if (task.Schedule == Schedule.RunNow)
56                {
57                    scheduledTasks.Add(DateTime.Now, task);
58                    schedulerInterrupt.Set();
59                }
60                //If the task is scheduled, add the next execution time to the list
61                //of schduled tasks.
62                else if (task.Schedule != Schedule.RunOnRestart)
63                {
64                    scheduledTasks.Add((task.Schedule as RecurringSchedule).NextRun, task);
65                }
66            }
67        }
68
69        public override bool DeleteTask(uint taskId)
70        {
71            lock (tasksLock)
72            {
73                if (!tasks.ContainsKey(taskId))
74                    return false;
75
76                lock (unusedIdsLock)
77                    unusedIds.Add(taskId);
78                tasks.Remove(taskId);
79            }
80            return true;
81        }
82
83        public override Task GetTask(uint taskId)
84        {
85            lock (tasksLock)
86            {
87                if (!tasks.ContainsKey(taskId))
88                    return null;
89                return tasks[taskId];
90            }
91        }
92
93        /// <summary>
94        /// The thread entry point for this object. This object operates on a queue
95        /// and hence the thread will sequentially execute tasks.
96        /// </summary>
97        private void Main()
98        {
99            //The waiting thread will utilize a polling loop to check for new
100            //scheduled tasks. This will be checked every 30 seconds. However,
101            //when the thread is waiting for a new task, it can be interrupted.
102            while (thread.ThreadState != ThreadState.AbortRequested)
103            {
104                //Check for a new task
105                Task task = null;
106                lock (tasksLock)
107                {
108                    if (scheduledTasks.Count != 0 &&
109                        (scheduledTasks.Values[0].Schedule == Schedule.RunNow ||
110                         scheduledTasks.Keys[0] <= DateTime.Now))
111                    {
112                        task = scheduledTasks.Values[0];
113                        scheduledTasks.RemoveAt(0);
114                    }
115                }
116
117                if (task != null)
118                {
119                    try
120                    {
121                        //Run the task
122                        foreach (Task.ErasureTarget target in task.Entries)
123                            try
124                            {
125                                if (target is Task.UnusedSpace)
126                                    EraseUnusedSpace(task, (Task.UnusedSpace)target);
127                                else if (target is Task.FilesystemObject)
128                                    EraseFilesystemObject(task, (Task.FilesystemObject)target);
129                                else
130                                    throw new ArgumentException("Unknown erasure target.");
131                            }
132                            catch (FatalException)
133                            {
134                                throw;
135                            }
136                            catch (Exception e)
137                            {
138                                task.LogEntry(new LogEntry(e.Message, LogLevel.ERROR));
139                            }
140                    }
141                    catch (FatalException e)
142                    {
143                        task.LogEntry(new LogEntry(e.Message, LogLevel.FATAL));
144                    }
145
146                    //If the task is a recurring task, reschedule it since we are done.
147                    if (task.Schedule is RecurringSchedule)
148                        ((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
149                }
150
151                //Wait for half a minute to check for the next scheduled task.
152                schedulerInterrupt.WaitOne(30000, false);
153            }
154        }
155
156        /// <summary>
157        /// Executes a unused space erase.
158        /// </summary>
159        /// <param name="target">The target of the unused space erase.</param>
160        private void EraseUnusedSpace(Task task, Task.UnusedSpace target)
161        {
162            throw new NotImplementedException("Unused space erasures are not "+
163                "currently implemented");
164        }
165
166        /// <summary>
167        /// Erases a file or folder on the volume.
168        /// </summary>
169        /// <param name="target">The target of the erasure.</param>
170        private void EraseFilesystemObject(Task task, Task.FilesystemObject target)
171        {
172            List<string> paths = target.GetPaths();
173            TaskProgressEventArgs eventArgs = new TaskProgressEventArgs(task, 0, 0);
174
175            //Get the erasure method if the user specified he wants the default.
176            ErasureMethod method = target.Method;
177            if (method == ErasureMethodManager.Default)
178                method = ErasureMethodManager.GetInstance(Globals.Settings.DefaultFileErasureMethod);
179
180            //Iterate over every path, and erase the path.
181            for (int i = 0; i < paths.Count; ++i)
182            {
183                //Update the task progress
184                eventArgs.overallProgress = (uint)(i * 100) / (uint)paths.Count;
185                eventArgs.currentItemName = paths[i];
186                eventArgs.currentItemProgress = 0;
187                eventArgs.totalPasses = method.Passes;
188                task.OnProgressChanged(eventArgs);
189
190                //Make sure the file does not have any attributes which may
191                //affect the erasure process
192                FileInfo info = new FileInfo(paths[i]);
193                if ((info.Attributes & FileAttributes.Compressed) != 0 ||
194                    (info.Attributes & FileAttributes.Encrypted) != 0 ||
195                    (info.Attributes & FileAttributes.SparseFile) != 0 ||
196                    (info.Attributes & FileAttributes.ReparsePoint) != 0)
197                {
198                    //Log the error
199                    throw new ArgumentException("Compressed, encrypted, or sparse" +
200                        "files cannot be erased with Eraser.");
201                }
202
203                //Remove the read-only flag, if it is set.
204                if ((info.Attributes & FileAttributes.ReadOnly) != 0)
205                    info.Attributes &= ~FileAttributes.ReadOnly;
206
207                //Create the file stream, and call the erasure method
208                //to write to the stream.
209                using (FileStream strm = new FileStream(info.FullName,
210                    FileMode.Open, FileAccess.Write, FileShare.None,
211                    8, FileOptions.WriteThrough))
212                {
213                    //Set the end of the stream after the wrap-round the cluster size
214                    uint clusterSize = Drives.GetDriveClusterSize(info.Directory.Root.FullName);
215                    long roundUpFileLength = strm.Length % clusterSize;
216                    if (roundUpFileLength != 0)
217                        strm.SetLength(strm.Length + (clusterSize - roundUpFileLength));
218
219                    //Then erase the file.
220                    method.Erase(strm, PRNGManager.GetInstance(Globals.Settings.ActivePRNG),
221                        delegate(float currentProgress, uint currentPass)
222                        {
223                            eventArgs.currentPass = currentPass;
224                            eventArgs.currentItemProgress = (uint)currentProgress * 100;
225                            eventArgs.overallProgress = (uint)(((i + currentProgress) /
226                                (float)paths.Count) * 100);
227                            task.OnProgressChanged(eventArgs);
228                        }
229                    );
230
231                    //Set the length of the file to 0.
232                    strm.Seek(0, SeekOrigin.Begin);
233                    strm.SetLength(0);
234                }
235
236                //Remove the file.
237                RemoveFile(info);
238            }
239        }
240
241        /// <summary>
242        /// Securely removes the filename of the file.
243        /// </summary>
244        /// <param name="info">The FileInfo object representing the file.</param>
245        private void RemoveFile(FileInfo info)
246        {
247            //Set the date of the file to be invalid to prevent forensic
248            //detection
249            info.CreationTime = info.LastWriteTime = info.LastAccessTime =
250                new DateTime(1800, 1, 1, 0, 0, 0);
251            info.Attributes = FileAttributes.Normal;
252            info.Attributes = FileAttributes.NotContentIndexed;
253
254            //Rename the file a few times to erase the record from the MFT.
255            for (uint i = 0; i < FilenameErasePasses; ++i)
256            {
257                //Get a random file name
258                PRNG prng = PRNGManager.GetInstance(Globals.Settings.ActivePRNG);
259                byte[] newFileNameAry = new byte[info.Name.Length];
260                prng.NextBytes(newFileNameAry);
261
262                //Validate the name
263                const string validFileNameChars = "0123456789abcdefghijklmnopqrs" +
264                    "tuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
265                for (int j = 0, k = newFileNameAry.Length; j < k; ++j)
266                    newFileNameAry[j] = (byte)validFileNameChars[
267                        (int)newFileNameAry[j] % validFileNameChars.Length];
268
269                //Rename the file.
270                string newPath = info.DirectoryName + Path.DirectorySeparatorChar +
271                    (new System.Text.UTF8Encoding()).GetString(newFileNameAry);
272                info.MoveTo(newPath);
273            }
274
275            //Then delete the file.
276            info.Delete();
277        }
278
279        /// <summary>
280        /// The thread object.
281        /// </summary>
282        private Thread thread;
283
284        /// <summary>
285        /// The lock preventing concurrent access for the tasks list and the
286        /// tasks queue.
287        /// </summary>
288        private object tasksLock = new object();
289
290        /// <summary>
291        /// The list of tasks. Includes all immediate, reboot, and recurring tasks
292        /// </summary>
293        private Dictionary<uint, Task> tasks = new Dictionary<uint, Task>();
294
295        /// <summary>
296        /// The queue of tasks. This queue is executed when the first element's
297        /// timestamp (the key) has been past. This list assumes that all tasks
298        /// are sorted by timestamp, smallest one first.
299        /// </summary>
300        private SortedList<DateTime, Task> scheduledTasks =
301            new SortedList<DateTime, Task>();
302
303        /// <summary>
304        /// The list of task IDs for recycling.
305        /// </summary>
306        private List<uint> unusedIds = new List<uint>();
307
308        /// <summary>
309        /// Lock preventing concurrent access for the IDs.
310        /// </summary>
311        private object unusedIdsLock = new object();
312
313        /// <summary>
314        /// Incrementing ID. This value is incremented by one every time an ID
315        /// is required by no unused IDs remain.
316        /// </summary>
317        private uint nextId = 0;
318
319        /// <summary>
320        /// An automatically reset event allowing the addition of new tasks to
321        /// interrupt the thread's sleeping state waiting for the next recurring
322        /// task to be due.
323        /// </summary>
324        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
325    }
326}
Note: See TracBrowser for help on using the repository browser.