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

Revision 294, 25.0 KB checked in by lowjoel, 7 years ago (diff)

Replaced all Drive.GetFreeSpace? calls with DriveInfo?.TotalAvailableSpace?

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.Security.Principal;
10using System.Runtime.Serialization;
11using System.Runtime.Serialization.Formatters.Binary;
12
13namespace Eraser.Manager
14{
15    /// <summary>
16    /// The DirectExecutor class is used by the Eraser GUI directly when the program
17    /// is run without the help of a Service.
18    /// </summary>
19    public class DirectExecutor : Executor, IDisposable
20    {
21        public DirectExecutor()
22        {
23            thread = new Thread(delegate()
24                {
25                    Main();
26                }
27            );
28
29            thread.Start();
30            Thread.Sleep(0);
31        }
32
33        void IDisposable.Dispose()
34        {
35            thread.Abort();
36            schedulerInterrupt.Set();
37        }
38
39        public override void AddTask(ref Task task)
40        {
41            lock (unusedIdsLock)
42            {
43                if (unusedIds.Count != 0)
44                {
45                    task.id = unusedIds[0];
46                    unusedIds.RemoveAt(0);
47                }
48                else
49                    task.id = ++nextId;
50            }
51
52            //Set the executor of the task
53            task.executor = this;
54
55            //Add the task to the set of tasks
56            lock (tasksLock)
57            {
58                tasks.Add(task.ID, task);
59
60                //If the task is scheduled to run now, break the waiting thread and
61                //run it immediately
62                if (task.Schedule == Schedule.RunNow)
63                {
64                    QueueTask(task);
65                }
66                //If the task is scheduled, add the next execution time to the list
67                //of schduled tasks.
68                else if (task.Schedule != Schedule.RunOnRestart)
69                {
70                    scheduledTasks.Add((task.Schedule as RecurringSchedule).NextRun, task);
71                }
72            }
73        }
74
75        public override bool DeleteTask(uint taskId)
76        {
77            lock (tasksLock)
78            {
79                if (!tasks.ContainsKey(taskId))
80                    return false;
81
82                lock (unusedIdsLock)
83                    unusedIds.Add(taskId);
84                tasks.Remove(taskId);
85
86                for (int i = 0; i != scheduledTasks.Count; ++i)
87                    if (scheduledTasks.Values[i].id == taskId)
88                        scheduledTasks.RemoveAt(i);
89            }
90
91            return true;
92        }
93
94        public override void ReplaceTask(Task task)
95        {
96            lock (tasksLock)
97            {
98                //Replace the task in the global set
99                if (!tasks.ContainsKey(task.ID))
100                    return;
101
102                tasks[task.ID] = task;
103
104                //Then replace the task if it is in the queue
105                for (int i = 0; i != scheduledTasks.Count; ++i)
106                    if (scheduledTasks.Values[i].id == task.ID)
107                        scheduledTasks.Values[i] = task;
108            }
109        }
110
111        public override void QueueTask(Task task)
112        {
113            lock (tasksLock)
114            {
115                //Set the task variable to indicate that the task is already
116                //waiting to be executed.
117                task.queued = true;
118
119                //Queue the task to be run immediately.
120                scheduledTasks.Add(DateTime.Now, task);
121                schedulerInterrupt.Set();
122            }
123        }
124
125        public override void QueueRestartTasks()
126        {
127            lock (tasksLock)
128            {
129                foreach (Task task in tasks.Values)
130                    if (task.Schedule == Schedule.RunOnRestart)
131                        QueueTask(task);
132            }
133        }
134
135        public override void CancelTask(Task task)
136        {
137            lock (currentTask)
138            {
139                if (currentTask == task)
140                {
141                    currentTask.cancelled = true;
142                    return;
143                }
144            }
145
146            lock (tasksLock)
147                for (int i = 0; i != scheduledTasks.Count; ++i)
148                    if (scheduledTasks.Values[i] == task)
149                    {
150                        scheduledTasks.RemoveAt(i);
151                        return;
152                    }
153
154            throw new ArgumentOutOfRangeException("The task to be cancelled must " +
155                "either be currently executing or queued.");
156        }
157
158        public override Task GetTask(uint taskId)
159        {
160            lock (tasksLock)
161            {
162                if (!tasks.ContainsKey(taskId))
163                    return null;
164                return tasks[taskId];
165            }
166        }
167
168        public override List<Task> GetTasks()
169        {
170            lock (tasksLock)
171            {
172                Task[] result = new Task[tasks.Count];
173                tasks.Values.CopyTo(result, 0);
174                return new List<Task>(result);
175            }
176        }
177
178        public override void SaveTaskList(Stream stream)
179        {
180            lock (tasksLock)
181                new BinaryFormatter().Serialize(stream, tasks);
182        }
183
184        public override void LoadTaskList(Stream stream)
185        {
186            lock (tasksLock)
187            {
188                //Load the list into the dictionary
189                tasks = (Dictionary<uint, Task>)new BinaryFormatter().Deserialize(stream);
190
191                //Ignore the next portion if there are no tasks
192                if (tasks.Count == 0)
193                    return;
194
195                lock (unusedIdsLock)
196                {
197                    //Find gaps in the numbering
198                    nextId = 1;
199                    foreach (uint id in tasks.Keys)
200                    {
201                        tasks[id].executor = this;
202                        while (id > nextId)
203                            unusedIds.Add(++nextId);
204                        ++nextId;
205                    }
206
207                    //Decrement the ID, since the next ID will be preincremented
208                    //before use.
209                    --nextId;
210                }
211            }
212        }
213
214        /// <summary>
215        /// The thread entry point for this object. This object operates on a queue
216        /// and hence the thread will sequentially execute tasks.
217        /// </summary>
218        private void Main()
219        {
220            //The waiting thread will utilize a polling loop to check for new
221            //scheduled tasks. This will be checked every 30 seconds. However,
222            //when the thread is waiting for a new task, it can be interrupted.
223            while (thread.ThreadState != ThreadState.AbortRequested)
224            {
225                //Check for a new task
226                Task task = null;
227                lock (tasksLock)
228                {
229                    if (scheduledTasks.Count != 0 &&
230                        (scheduledTasks.Values[0].Schedule == Schedule.RunNow ||
231                         scheduledTasks.Keys[0] <= DateTime.Now))
232                    {
233                        task = scheduledTasks.Values[0];
234                        scheduledTasks.RemoveAt(0);
235                    }
236                }
237
238                if (task != null)
239                {
240                    //Set the currently executing task.
241                    currentTask = task;
242
243                    try
244                    {
245                        //Broadcast the task started event.
246                        task.queued = false;
247                        task.cancelled = false;
248                        task.OnTaskStarted(new TaskEventArgs(task));
249
250                        //Run the task
251                        foreach (Task.ErasureTarget target in task.Targets)
252                            try
253                            {
254                                if (target is Task.UnusedSpace)
255                                    EraseUnusedSpace(task, (Task.UnusedSpace)target);
256                                else if (target is Task.FilesystemObject)
257                                    EraseFilesystemObject(task, (Task.FilesystemObject)target);
258                                else
259                                    throw new ArgumentException("Unknown erasure target.");
260                            }
261                            catch (FatalException)
262                            {
263                                throw;
264                            }
265                            catch (Exception e)
266                            {
267                                task.LogEntry(new LogEntry(e.Message, LogLevel.ERROR));
268                            }
269                    }
270                    catch (FatalException e)
271                    {
272                        task.LogEntry(new LogEntry(e.Message, LogLevel.FATAL));
273                    }
274                    finally
275                    {
276                        //If the task is a recurring task, reschedule it since we are done.
277                        if (task.Schedule is RecurringSchedule)
278                            ((RecurringSchedule)task.Schedule).Reschedule(DateTime.Now);
279
280                        //If the task is an execute on restart task, it is only run
281                        //once and can now be restored to an immediately executed task
282                        if (task.Schedule == Schedule.RunOnRestart)
283                            task.Schedule = Schedule.RunNow;
284
285                        //And the task finished event.
286                        task.OnTaskFinished(new TaskEventArgs(task));
287                    }
288                }
289
290                //Wait for half a minute to check for the next scheduled task.
291                schedulerInterrupt.WaitOne(30000, false);
292            }
293        }
294
295        private class WriteStatistics
296        {
297            public WriteStatistics()
298            {
299                startTime = DateTime.Now;
300            }
301
302            public int Speed
303            {
304                get
305                {
306                    if (DateTime.Now == startTime)
307                        return 0;
308
309                    if ((DateTime.Now - lastSpeedCalc).Seconds < 10 && lastSpeed != 0)
310                        return lastSpeed;
311
312                    lastSpeedCalc = DateTime.Now;
313                    lastSpeed = (int)(dataWritten / (DateTime.Now - startTime).TotalSeconds);
314                    return lastSpeed;
315                }
316            }
317
318            public long DataWritten
319            {
320                get { return dataWritten; }
321                set { dataWritten = value; }
322            }
323
324            private DateTime startTime;
325            private DateTime lastSpeedCalc;
326            private long dataWritten;
327            private int lastSpeed;
328        }
329
330        /// <summary>
331        /// Executes a unused space erase.
332        /// </summary>
333        /// <param name="target">The target of the unused space erase.</param>
334        private void EraseUnusedSpace(Task task, Task.UnusedSpace target)
335        {
336            //Check for sufficient privileges to run the unused space erasure.
337            if (!Permissions.IsAdministrator())
338            {
339                string exceptionString = "The program does not have the required permissions " +
340                    "to erase the unused space on disk";
341                if (Environment.OSVersion.Platform == PlatformID.Win32NT &&
342                    Environment.OSVersion.Version >= new Version(6, 0))
343                {
344                    exceptionString += ". Run the program as administrator and retry the operation.";
345                }
346                throw new Exception(exceptionString);
347            }
348
349            //If the user is under disk quotas, log a warning message
350            if (Drive.HasQuota(target.Drive))
351                task.LogEntry(new LogEntry("The drive which is having its unused space erased has " +
352                    "disk quotas active. This will prevent the complete erasure of unused space and " +
353                    "will pose a security concern", LogLevel.WARNING));
354
355            //Get the erasure method if the user specified he wants the default.
356            ErasureMethod method = target.Method;
357            if (method == ErasureMethodManager.Default)
358                method = ErasureMethodManager.GetInstance(
359                    ManagerLibrary.Instance.Settings.DefaultUnusedSpaceErasureMethod);
360
361            TaskProgressEventArgs eventArgs = new TaskProgressEventArgs(task, 0, 0);
362            eventArgs.currentTarget = target;
363            eventArgs.currentItemName = "Cluster tips";
364            eventArgs.totalPasses = method.Passes;
365            eventArgs.timeLeft = -1;
366            task.OnProgressChanged(eventArgs);
367
368            //Erase the cluster tips of every file on the drive.
369            if (target.EraseClusterTips)
370            {
371                EraseClusterTips(task, target, method,
372                    delegate(int currentFile, string currentFilePath, int totalFiles)
373                    {
374                        eventArgs.currentItemName = "(Tips) " + currentFilePath;
375                        eventArgs.currentItemProgress = (int)((float)currentFile / totalFiles * 100);
376                        eventArgs.overallProgress = eventArgs.CurrentItemProgress / 10;
377                        task.OnProgressChanged(eventArgs);
378                    }
379                );
380            }
381
382            //Make a folder to dump our temporary files in
383            DirectoryInfo info = new DirectoryInfo(target.Drive).Root;
384            {
385                string directoryName;
386                do
387                    directoryName = GetRandomFileName(18);
388                while (Directory.Exists(info.FullName + Path.DirectorySeparatorChar + directoryName));
389                info = info.CreateSubdirectory(directoryName);
390            }
391
392            try
393            {
394                //Set the folder's compression flag off since we want to use as much
395                //space as possible
396                if (Eraser.Util.File.IsCompressed(info.FullName))
397                    Eraser.Util.File.SetCompression(info.FullName, false);
398
399                //Determine the total amount of data that needs to be written.
400                WriteStatistics statistics = new WriteStatistics();
401                DriveInfo driveInfo = new DriveInfo(info.Root.FullName);
402                long totalSize = method.CalculateEraseDataSize(null, driveInfo.TotalFreeSpace);
403
404                //Continue creating files while there is free space.
405                eventArgs.currentItemName = "Unused space";
406                task.OnProgressChanged(eventArgs);
407                while (driveInfo.TotalFreeSpace > 0)
408                {
409                    //Generate a non-existant file name
410                    string currFile;
411                    do
412                        currFile = info.FullName + Path.DirectorySeparatorChar +
413                            GetRandomFileName(18);
414                    while (System.IO.File.Exists(currFile));
415                   
416                    //Create the stream
417                    using (FileStream stream = new FileStream(currFile,
418                        FileMode.CreateNew, FileAccess.Write))
419                    {
420                        //Set the length of the file to be the amount of free space left
421                        //or the maximum size of one of these dumps.
422                        stream.SetLength(Math.Min(ErasureMethod.FreeSpaceFileUnit,
423                            driveInfo.TotalFreeSpace));
424
425                        //Then run the erase task
426                        method.Erase(stream, long.MaxValue,
427                            PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG),
428                            delegate(long lastWritten, int currentPass)
429                            {
430                                statistics.DataWritten += lastWritten;
431                                eventArgs.currentPass = currentPass;
432                                eventArgs.currentItemProgress = (int)(statistics.DataWritten * 100 / totalSize);
433                                eventArgs.overallProgress = (int)((10 + eventArgs.currentItemProgress * 0.8));
434
435                                if (statistics.Speed == 0)
436                                    eventArgs.timeLeft = -1;
437                                else
438                                    eventArgs.timeLeft = (int)((totalSize - statistics.DataWritten) / statistics.Speed);
439                                task.OnProgressChanged(eventArgs);
440
441                                lock (currentTask)
442                                    if (currentTask.cancelled)
443                                        throw new FatalException("The task was cancelled.");
444                            }
445                        );
446                    }
447                }
448
449                //If the drive is using NTFS, try to squeeze entries into the MFT as well
450                eventArgs.currentItemName = "Resident MFT entries";
451                task.OnProgressChanged(eventArgs);
452
453                try
454                {
455                    for ( ; ; )
456                    {
457                        //Open this stream
458                        using (FileStream strm = new FileStream(info.FullName + Path.DirectorySeparatorChar +
459                            GetRandomFileName(18), FileMode.CreateNew, FileAccess.Write))
460                        {
461                            //Stretch the file size to the size of one MFT record
462                            strm.SetLength(1);
463
464                            //Then run the erase task
465                            method.Erase(strm, long.MaxValue,
466                                PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG),
467                                null);
468                        }
469                    }
470                }
471                catch (IOException)
472                {
473                    //OK, enough squeezing.
474                }
475            }
476            finally
477            {
478                eventArgs.currentItemName = "Removing temporary files";
479                task.OnProgressChanged(eventArgs);
480
481                //Remove the folder holding all our temporary files.
482                RemoveFolder(info);
483            }
484
485
486            //NotImplemented: clear directory entries: Eraser.cpp@2321
487        }
488
489        private delegate void SubFoldersHandler(DirectoryInfo info);
490        private delegate void ClusterTipsEraseProgress(int currentFile,
491            string currentFilePath, int totalFiles);
492        private static void EraseClusterTips(Task task, Task.UnusedSpace target,
493            ErasureMethod method, ClusterTipsEraseProgress callback)
494        {
495            //List all the files which can be erased.
496            List<string> files = new List<string>();
497            SubFoldersHandler subFolders = null;
498            subFolders = delegate(DirectoryInfo info)
499            {
500                try
501                {
502                    foreach (FileInfo file in info.GetFiles())
503                        if (Eraser.Util.File.IsProtectedSystemFile(file.FullName))
504                            task.LogEntry(new LogEntry(string.Format("{0} did not have its cluster tips " +
505                                "erased, because it is a system file", file.FullName), LogLevel.INFORMATION));
506                        else
507                        {
508                            foreach (string i in Util.File.GetADSes(file))
509                                files.Add(file.FullName + ':' + i);
510                            files.Add(file.FullName);
511                        }
512
513                    foreach (DirectoryInfo subDir in info.GetDirectories())
514                        subFolders(subDir);
515                }
516                catch (UnauthorizedAccessException)
517                {
518                }
519                catch (IOException)
520                {
521                }
522            };
523
524            subFolders(new DirectoryInfo(target.Drive));
525
526            //For every file, erase the cluster tips.
527            for (int i = 0, j = files.Count; i != j; ++i)
528            {
529                EraseFileClusterTips(files[i], method);
530                callback(i, files[i], files.Count);
531            }
532        }
533
534        /// <summary>
535        /// Erases the cluster tips of the given file.
536        /// </summary>
537        /// <param name="file">The file to erase.</param>
538        /// <param name="method">The erasure method to use.</param>
539        private static void EraseFileClusterTips(string file, ErasureMethod method)
540        {
541            //Get the file access times
542            StreamInfo streamInfo = new StreamInfo(file);
543            DateTime lastAccess = DateTime.MinValue, lastWrite = DateTime.MinValue,
544                     created = DateTime.MinValue;
545            {
546                FileInfo info = streamInfo.File;
547                if (info != null)
548                {
549                    lastAccess = info.LastAccessTime;
550                    lastWrite = info.LastWriteTime;
551                    created = info.CreationTime;
552                }
553            }
554
555            //Create the stream, lengthen the file, then tell the erasure method
556            //to erase the tips.
557            using (FileStream stream = streamInfo.Open(FileMode.Open, FileAccess.Write))
558            {
559                long fileLength = stream.Length;
560                long fileArea = GetFileArea(file);
561
562                try
563                {
564                    stream.SetLength(fileArea);
565                    stream.Seek(fileLength, SeekOrigin.Begin);
566
567                    //Erase the file
568                    method.Erase(stream, long.MaxValue, PRNGManager.GetInstance(
569                        ManagerLibrary.Instance.Settings.ActivePRNG), null);
570                }
571                finally
572                {
573                    //Make sure the file is restored!
574                    stream.SetLength(fileLength);
575                }
576            }
577
578            //Set the file times
579            FileInfo fileInfo = streamInfo.File;
580            if (fileInfo != null)
581            {
582                fileInfo.LastAccessTime = lastAccess;
583                fileInfo.LastWriteTime = lastWrite;
584                fileInfo.CreationTime = created;
585            }
586        }
587
588        /// <summary>
589        /// Erases a file or folder on the volume.
590        /// </summary>
591        /// <param name="target">The target of the erasure.</param>
592        private void EraseFilesystemObject(Task task, Task.FilesystemObject target)
593        {
594            //Retrieve the list of files to erase.
595            long dataTotal = 0;
596            List<string> paths = target.GetPaths(out dataTotal);
597            TaskProgressEventArgs eventArgs = new TaskProgressEventArgs(task, 0, 0);
598
599            //Get the erasure method if the user specified he wants the default.
600            ErasureMethod method = target.Method;
601            if (method == ErasureMethodManager.Default)
602                method = ErasureMethodManager.GetInstance(ManagerLibrary.Instance.Settings.DefaultFileErasureMethod);
603
604            //Calculate the total amount of data required to finish the wipe.
605            dataTotal = method.CalculateEraseDataSize(paths, dataTotal);
606
607            //Record the start of the erasure pass so we can calculate speed of erasures
608            WriteStatistics statistics = new WriteStatistics();
609
610            //Iterate over every path, and erase the path.
611            for (int i = 0; i < paths.Count; ++i)
612            {
613                //Update the task progress
614                eventArgs.overallProgress = (i * 100) / paths.Count;
615                eventArgs.currentTarget = target;
616                eventArgs.currentItemName = paths[i];
617                eventArgs.currentItemProgress = 0;
618                eventArgs.totalPasses = method.Passes;
619                task.OnProgressChanged(eventArgs);
620
621                //Make sure the file does not have any attributes which may affect
622                //the erasure process
623                bool isReadOnly = false;
624                StreamInfo info = new StreamInfo(paths[i]);
625                if ((info.Attributes & FileAttributes.Compressed) != 0 ||
626                    (info.Attributes & FileAttributes.Encrypted) != 0 ||
627                    (info.Attributes & FileAttributes.SparseFile) != 0 ||
628                    (info.Attributes & FileAttributes.ReparsePoint) != 0)
629                {
630                    //Log the error
631                    throw new ArgumentException("Compressed, encrypted, or sparse" +
632                        "files cannot be erased with Eraser.");
633                }
634
635                //Remove the read-only flag, if it is set.
636                if (isReadOnly = info.IsReadOnly)
637                    info.IsReadOnly = false;
638
639                try
640                {
641                    //Create the file stream, and call the erasure method to write to
642                    //the stream.
643                    using (FileStream strm = info.Open(FileMode.Open, FileAccess.Write,
644                        FileShare.None, FileOptions.WriteThrough))
645                    {
646                        //Set the end of the stream after the wrap-round the cluster size
647                        strm.SetLength(GetFileArea(paths[i]));
648
649                        //If the stream is empty, there's nothing to overwrite. Continue
650                        //to the next entry
651                        if (strm.Length != 0)
652                        {
653                            //Then erase the file.
654                            long itemWritten = 0,
655                                 itemTotal = method.CalculateEraseDataSize(null, strm.Length);
656                            method.Erase(strm, long.MaxValue,
657                                PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG),
658                                delegate(long lastWritten, int currentPass)
659                                {
660                                    statistics.DataWritten += lastWritten;
661                                    eventArgs.currentPass = currentPass;
662                                    eventArgs.currentItemProgress = (int)
663                                        ((itemWritten += lastWritten) * 100 / itemTotal);
664                                    eventArgs.overallProgress = (int)
665                                        (statistics.DataWritten * 100 / dataTotal);
666
667                                    if (statistics.Speed != 0)
668                                        eventArgs.timeLeft = (int)
669                                            (dataTotal - statistics.DataWritten) / statistics.Speed;
670                                    else
671                                        eventArgs.timeLeft = -1;
672                                    task.OnProgressChanged(eventArgs);
673
674                                    lock (currentTask)
675                                        if (currentTask.cancelled)
676                                            throw new FatalException("The task was cancelled.");
677                                }
678                            );
679                        }
680
681                        //Set the length of the file to 0.
682                        strm.Seek(0, SeekOrigin.Begin);
683                        strm.SetLength(0);
684                    }
685
686                    //Remove the file.
687                    FileInfo fileInfo = info.File;
688                    if (fileInfo != null)
689                        RemoveFile(fileInfo);
690                }
691                finally
692                {
693                    //Re-set the read-only flag
694                    info.IsReadOnly = isReadOnly;
695                }
696            }
697
698            //If the user requested a folder removal, do it.
699            if (target is Task.Folder)
700            {
701                Task.Folder fldr = (Task.Folder)target;
702                if (fldr.DeleteIfEmpty)
703                    RemoveFolder(new DirectoryInfo(fldr.Path));
704            }
705        }
706
707        /// <summary>
708        /// Retrieves the size of the file on disk, calculated by the amount of
709        /// clusters allocated by it.
710        /// </summary>
711        /// <param name="filePath">The path to the file.</param>
712        /// <returns>The area of the file.</returns>
713        private static long GetFileArea(string filePath)
714        {
715            StreamInfo info = new StreamInfo(filePath);
716            uint clusterSize = Drive.GetClusterSize(info.Directory.Root.FullName);
717            return (info.Length + (clusterSize - 1)) & ~(clusterSize - 1);
718        }
719
720        /// <summary>
721        /// Securely removes files.
722        /// </summary>
723        /// <param name="info">The FileInfo object representing the file.</param>
724        private static void RemoveFile(FileInfo info)
725        {
726            //Set the date of the file to be invalid to prevent forensic
727            //detection
728            info.CreationTime = info.LastWriteTime = info.LastAccessTime =
729                new DateTime(1800, 1, 1, 0, 0, 0);
730            info.Attributes = FileAttributes.Normal;
731            info.Attributes = FileAttributes.NotContentIndexed;
732
733            //Rename the file a few times to erase the record from the MFT.
734            for (int i = 0; i < FilenameErasePasses; ++i)
735            {
736                //Rename the file.
737                string newPath = info.DirectoryName + Path.DirectorySeparatorChar +
738                    GetRandomFileName(info.Name.Length);
739
740                //Try to rename the file. If it fails, it is probably due to another
741                //process locking the file. Defer, then rename again.
742                try
743                {
744                    info.MoveTo(newPath);
745                }
746                catch (IOException)
747                {
748                    Thread.Sleep(100);
749                    --i;
750                }
751            }
752
753            //Then delete the file.
754            info.Delete();
755        }
756
757        private static void RemoveFolder(DirectoryInfo info)
758        {
759            foreach (FileInfo file in info.GetFiles())
760                RemoveFile(file);
761            foreach (DirectoryInfo dir in info.GetDirectories())
762                RemoveFolder(dir);
763
764            //Then clean up this folder.
765            for (int i = 0; i < FilenameErasePasses; ++i)
766            {
767                //Rename the folder.
768                string newPath = info.Parent.FullName + Path.DirectorySeparatorChar +
769                    GetRandomFileName(info.Name.Length);
770
771                //Try to rename the file. If it fails, it is probably due to another
772                //process locking the file. Defer, then rename again.
773                try
774                {
775                    info.MoveTo(newPath);
776                }
777                catch (IOException)
778                {
779                    Thread.Sleep(100);
780                    --i;
781                }
782            }
783
784            //Remove the folder
785            info.Delete();
786        }
787
788        /// <summary>
789        /// Generates a random file name with the given length.
790        /// </summary>
791        /// <param name="length">The length of the file name to generate.</param>
792        /// <returns>A random file name.</returns>
793        private static string GetRandomFileName(int length)
794        {
795            //Get a random file name
796            PRNG prng = PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG);
797            byte[] newFileNameAry = new byte[length];
798            prng.NextBytes(newFileNameAry);
799
800            //Validate the name
801            string validFileNameChars = "0123456789abcdefghijklmnopqrstuvwxyz" +
802                "ABCDEFGHIJKLMNOPQRSTUVWXYZ _+=-()[]{}',`~!";
803            for (int j = 0, k = newFileNameAry.Length; j < k; ++j)
804                newFileNameAry[j] = (byte)validFileNameChars[
805                    (int)newFileNameAry[j] % validFileNameChars.Length];
806
807            return new System.Text.UTF8Encoding().GetString(newFileNameAry);
808        }
809
810        /// <summary>
811        /// The thread object.
812        /// </summary>
813        private Thread thread;
814
815        /// <summary>
816        /// The lock preventing concurrent access for the tasks list and the
817        /// tasks queue.
818        /// </summary>
819        private object tasksLock = new object();
820
821        /// <summary>
822        /// The list of tasks. Includes all immediate, reboot, and recurring tasks
823        /// </summary>
824        private Dictionary<uint, Task> tasks = new Dictionary<uint, Task>();
825
826        /// <summary>
827        /// The queue of tasks. This queue is executed when the first element's
828        /// timestamp (the key) has been past. This list assumes that all tasks
829        /// are sorted by timestamp, smallest one first.
830        /// </summary>
831        private SortedList<DateTime, Task> scheduledTasks =
832            new SortedList<DateTime, Task>();
833
834        /// <summary>
835        /// The currently executing task.
836        /// </summary>
837        Task currentTask;
838
839        /// <summary>
840        /// The list of task IDs for recycling.
841        /// </summary>
842        private List<uint> unusedIds = new List<uint>();
843
844        /// <summary>
845        /// Lock preventing concurrent access for the IDs.
846        /// </summary>
847        private object unusedIdsLock = new object();
848
849        /// <summary>
850        /// Incrementing ID. This value is incremented by one every time an ID
851        /// is required by no unused IDs remain.
852        /// </summary>
853        private uint nextId = 0;
854
855        /// <summary>
856        /// An automatically reset event allowing the addition of new tasks to
857        /// interrupt the thread's sleeping state waiting for the next recurring
858        /// task to be due.
859        /// </summary>
860        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
861    }
862}
Note: See TracBrowser for help on using the repository browser.