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

Revision 282, 24.3 KB checked in by lowjoel, 6 years ago (diff)

When erasing unused space, use using clauses to close the file stream after we are done with them.

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            EraseClusterTips(task, target, method,
370                delegate(int currentFile, string currentFilePath, int totalFiles)
371                {
372                    eventArgs.currentItemName = "(Tips) " + currentFilePath;
373                    eventArgs.currentItemProgress = (int)((float)currentFile / totalFiles * 100);
374                    eventArgs.overallProgress = eventArgs.CurrentItemProgress / 10;
375                    task.OnProgressChanged(eventArgs);
376                }
377            );
378
379            //Make a folder to dump our temporary files in
380            DirectoryInfo info = new DirectoryInfo(target.Drive).Root;
381            {
382                string directoryName;
383                do
384                    directoryName = GetRandomFileName(18);
385                while (Directory.Exists(info.FullName + Path.DirectorySeparatorChar + directoryName));
386                info = info.CreateSubdirectory(directoryName);
387            }
388
389            try
390            {
391                //Set the folder's compression flag off since we want to use as much
392                //space as possible
393                if (Eraser.Util.File.IsCompressed(info.FullName))
394                    Eraser.Util.File.SetCompression(info.FullName, false);
395
396                //Determine the total amount of data that needs to be written.
397                WriteStatistics statistics = new WriteStatistics();
398                long totalSize = method.CalculateEraseDataSize(
399                    null, Drive.GetFreeSpace(info.Root.FullName));
400
401                //Continue creating files while there is free space.
402                eventArgs.currentItemName = "Unused space";
403                task.OnProgressChanged(eventArgs);
404                while (Drive.GetFreeSpace(info.Root.FullName) > 0)
405                {
406                    //Generate a non-existant file name
407                    string currFile;
408                    do
409                        currFile = info.FullName + Path.DirectorySeparatorChar +
410                            GetRandomFileName(18);
411                    while (System.IO.File.Exists(currFile));
412                   
413                    //Create the stream
414                    using (FileStream stream = new FileStream(currFile,
415                        FileMode.CreateNew, FileAccess.Write))
416                    {
417
418                        //Set the length of the file to be the amount of free space left
419                        //or the maximum size of one of these dumps.
420                        stream.SetLength(Math.Min(ErasureMethod.FreeSpaceFileUnit,
421                            Drive.GetFreeSpace(info.Root.FullName)));
422
423                        //Then run the erase task
424                        method.Erase(stream, long.MaxValue,
425                            PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG),
426                            delegate(long lastWritten, int currentPass)
427                            {
428                                statistics.DataWritten += lastWritten;
429                                eventArgs.currentPass = currentPass;
430                                eventArgs.currentItemProgress = (int)(statistics.DataWritten * 100 / totalSize);
431                                eventArgs.overallProgress = (int)((10 + eventArgs.currentItemProgress * 0.8));
432
433                                if (statistics.Speed == 0)
434                                    eventArgs.timeLeft = -1;
435                                else
436                                    eventArgs.timeLeft = (int)((totalSize - statistics.DataWritten) / statistics.Speed);
437                                task.OnProgressChanged(eventArgs);
438
439                                lock (currentTask)
440                                    if (currentTask.cancelled)
441                                        throw new FatalException("The task was cancelled.");
442                            }
443                        );
444                    }
445                }
446
447                //If the drive is using NTFS, try to squeeze entries into the MFT as well
448                eventArgs.currentItemName = "Resident MFT entries";
449                task.OnProgressChanged(eventArgs);
450
451                try
452                {
453                    for ( ; ; )
454                    {
455                        //Open this stream
456                        using (FileStream strm = new FileStream(info.FullName + Path.DirectorySeparatorChar +
457                            GetRandomFileName(18), FileMode.CreateNew, FileAccess.Write))
458                        {
459                            //Stretch the file size to the size of one MFT record
460                            strm.SetLength(1);
461
462                            //Then run the erase task
463                            method.Erase(strm, long.MaxValue,
464                                PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG),
465                                null);
466                        }
467                    }
468                }
469                catch (IOException)
470                {
471                    //OK, enough squeezing.
472                }
473            }
474            finally
475            {
476                eventArgs.currentItemName = "Removing temporary files";
477                task.OnProgressChanged(eventArgs);
478
479                //Remove the folder holding all our temporary files.
480                RemoveFolder(info);
481            }
482
483
484            //NotImplemented: clear directory entries: Eraser.cpp@2321
485        }
486
487        private delegate void SubFoldersHandler(DirectoryInfo info);
488        private delegate void ClusterTipsEraseProgress(int currentFile,
489            string currentFilePath, int totalFiles);
490        private static void EraseClusterTips(Task task, Task.UnusedSpace target,
491            ErasureMethod method, ClusterTipsEraseProgress callback)
492        {
493            //List all the files which can be erased.
494            List<string> files = new List<string>();
495            SubFoldersHandler subFolders = null;
496            subFolders = delegate(DirectoryInfo info)
497            {
498                try
499                {
500                    foreach (FileInfo file in info.GetFiles())
501                        if (Eraser.Util.File.IsProtectedSystemFile(file.FullName))
502                            task.LogEntry(new LogEntry(string.Format("{0} did not have its cluster tips " +
503                                "erased, because it is a system file", file.FullName), LogLevel.INFORMATION));
504                        else
505                            files.Add(file.FullName);
506
507                    foreach (DirectoryInfo subDir in info.GetDirectories())
508                        subFolders(subDir);
509                }
510                catch (UnauthorizedAccessException)
511                {
512                }
513                catch (IOException)
514                {
515                }
516            };
517
518            subFolders(new DirectoryInfo(target.Drive));
519
520            //For every file, erase the cluster tips.
521            for (int i = 0, j = files.Count; i != j; ++i)
522            {
523                EraseFileClusterTips(files[i], method);
524                callback(i, files[i], files.Count);
525            }
526        }
527
528        /// <summary>
529        /// Erases the cluster tips of the given file.
530        /// </summary>
531        /// <param name="file">The file to erase.</param>
532        /// <param name="method">The erasure method to use.</param>
533        private static void EraseFileClusterTips(string file, ErasureMethod method)
534        {
535            //Get the file access times
536            DateTime lastAccess, lastWrite, created;
537            {
538                FileInfo info = new FileInfo(file);
539                lastAccess = info.LastAccessTime;
540                lastWrite = info.LastWriteTime;
541                created = info.CreationTime;
542            }
543
544            //Create the stream, lengthen the file, then tell the erasure method
545            //to erase the tips.
546            using (FileStream stream = new FileStream(file, FileMode.Open,
547                FileAccess.Write, FileShare.ReadWrite))
548            {
549                long fileLength = stream.Length;
550                long fileArea = GetFileArea(file);
551
552                try
553                {
554                    stream.SetLength(fileArea);
555                    stream.Seek(fileLength, SeekOrigin.Begin);
556
557                    //Erase the file
558                    method.Erase(stream, long.MaxValue, PRNGManager.GetInstance(
559                        ManagerLibrary.Instance.Settings.ActivePRNG), null);
560                }
561                finally
562                {
563                    //Make sure the file is restored!
564                    stream.SetLength(fileLength);
565                }
566            }
567
568            //Set the file times
569            {
570                FileInfo info = new FileInfo(file);
571                info.LastAccessTime = lastAccess;
572                info.LastWriteTime = lastWrite;
573                info.CreationTime = created;
574            }
575        }
576
577        /// <summary>
578        /// Erases a file or folder on the volume.
579        /// </summary>
580        /// <param name="target">The target of the erasure.</param>
581        private void EraseFilesystemObject(Task task, Task.FilesystemObject target)
582        {
583            //Retrieve the list of files to erase.
584            long dataTotal = 0;
585            List<string> paths = target.GetPaths(out dataTotal);
586            TaskProgressEventArgs eventArgs = new TaskProgressEventArgs(task, 0, 0);
587
588            //Get the erasure method if the user specified he wants the default.
589            ErasureMethod method = target.Method;
590            if (method == ErasureMethodManager.Default)
591                method = ErasureMethodManager.GetInstance(ManagerLibrary.Instance.Settings.DefaultFileErasureMethod);
592
593            //Calculate the total amount of data required to finish the wipe. This
594            //value is just the total about of data to be erased multiplied by
595            //number of passes
596            dataTotal = method.CalculateEraseDataSize(paths, dataTotal);
597
598            //Record the start of the erasure pass so we can calculate speed of erasures
599            WriteStatistics statistics = new WriteStatistics();
600
601            //Iterate over every path, and erase the path.
602            for (int i = 0; i < paths.Count; ++i)
603            {
604                //Update the task progress
605                eventArgs.overallProgress = (i * 100) / paths.Count;
606                eventArgs.currentTarget = target;
607                eventArgs.currentItemName = paths[i];
608                eventArgs.currentItemProgress = 0;
609                eventArgs.totalPasses = method.Passes;
610                task.OnProgressChanged(eventArgs);
611
612                //Make sure the file does not have any attributes which may
613                //affect the erasure process
614                FileInfo info = new FileInfo(paths[i]);
615                if ((info.Attributes & FileAttributes.Compressed) != 0 ||
616                    (info.Attributes & FileAttributes.Encrypted) != 0 ||
617                    (info.Attributes & FileAttributes.SparseFile) != 0 ||
618                    (info.Attributes & FileAttributes.ReparsePoint) != 0)
619                {
620                    //Log the error
621                    throw new ArgumentException("Compressed, encrypted, or sparse" +
622                        "files cannot be erased with Eraser.");
623                }
624
625                //Remove the read-only flag, if it is set.
626                if ((info.Attributes & FileAttributes.ReadOnly) != 0)
627                    info.Attributes &= ~FileAttributes.ReadOnly;
628
629                //Create the file stream, and call the erasure method
630                //to write to the stream.
631                using (FileStream strm = new FileStream(info.FullName,
632                    FileMode.Open, FileAccess.Write, FileShare.None,
633                    8, FileOptions.WriteThrough))
634                {
635                    //Set the end of the stream after the wrap-round the cluster size
636                    strm.SetLength(GetFileArea(info.FullName));
637
638                    //Then erase the file.
639                    long itemWritten = 0,
640                         itemTotal = method.CalculateEraseDataSize(null, strm.Length);
641                    method.Erase(strm, long.MaxValue,
642                        PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG),
643                        delegate(long lastWritten, int currentPass)
644                        {
645                            statistics.DataWritten += lastWritten;
646                            eventArgs.currentPass = currentPass;
647                            eventArgs.currentItemProgress = (int)((itemWritten += lastWritten) * 100 / itemTotal);
648                            eventArgs.overallProgress = (int)(statistics.DataWritten * 100 / dataTotal);
649
650                            if (statistics.Speed != 0)
651                                eventArgs.timeLeft = (int)(dataTotal - statistics.DataWritten) / statistics.Speed;
652                            else
653                                eventArgs.timeLeft = -1;
654                            task.OnProgressChanged(eventArgs);
655
656                            lock (currentTask)
657                                if (currentTask.cancelled)
658                                    throw new FatalException("The task was cancelled.");
659                        }
660                    );
661
662                    //Set the length of the file to 0.
663                    strm.Seek(0, SeekOrigin.Begin);
664                    strm.SetLength(0);
665                }
666
667                //Remove the file.
668                RemoveFile(info);
669            }
670
671            //If the user requested a folder removal, do it.
672            if (target is Task.Folder)
673            {
674                Task.Folder fldr = (Task.Folder)target;
675                if (fldr.DeleteIfEmpty)
676                    RemoveFolder(new DirectoryInfo(fldr.Path));
677            }
678        }
679
680        /// <summary>
681        /// Retrieves the size of the file on disk, calculated by the amount of
682        /// clusters allocated by it.
683        /// </summary>
684        /// <param name="filePath">The path to the file.</param>
685        /// <returns>The area of the file.</returns>
686        private static long GetFileArea(string filePath)
687        {
688            FileInfo info = new FileInfo(filePath);
689            uint clusterSize = Drive.GetClusterSize(info.Directory.Root.FullName);
690            return (info.Length + (clusterSize - 1)) & ~(clusterSize - 1);
691        }
692
693        /// <summary>
694        /// Securely removes files.
695        /// </summary>
696        /// <param name="info">The FileInfo object representing the file.</param>
697        private static void RemoveFile(FileInfo info)
698        {
699            //Set the date of the file to be invalid to prevent forensic
700            //detection
701            info.CreationTime = info.LastWriteTime = info.LastAccessTime =
702                new DateTime(1800, 1, 1, 0, 0, 0);
703            info.Attributes = FileAttributes.Normal;
704            info.Attributes = FileAttributes.NotContentIndexed;
705
706            //Rename the file a few times to erase the record from the MFT.
707            for (int i = 0; i < FilenameErasePasses; ++i)
708            {
709                //Rename the file.
710                string newPath = info.DirectoryName + Path.DirectorySeparatorChar +
711                    GetRandomFileName(info.Name.Length);
712
713                //Try to rename the file. If it fails, it is probably due to another
714                //process locking the file. Defer, then rename again.
715                try
716                {
717                    info.MoveTo(newPath);
718                }
719                catch (IOException)
720                {
721                    Thread.Sleep(100);
722                    --i;
723                }
724            }
725
726            //Then delete the file.
727            info.Delete();
728        }
729
730        private static void RemoveFolder(DirectoryInfo info)
731        {
732            foreach (FileInfo file in info.GetFiles())
733                RemoveFile(file);
734            foreach (DirectoryInfo dir in info.GetDirectories())
735                RemoveFolder(dir);
736
737            //Then clean up this folder.
738            for (int i = 0; i < FilenameErasePasses; ++i)
739            {
740                //Rename the folder.
741                string newPath = info.Parent.FullName + Path.DirectorySeparatorChar +
742                    GetRandomFileName(info.Name.Length);
743
744                //Try to rename the file. If it fails, it is probably due to another
745                //process locking the file. Defer, then rename again.
746                try
747                {
748                    info.MoveTo(newPath);
749                }
750                catch (IOException)
751                {
752                    Thread.Sleep(100);
753                    --i;
754                }
755            }
756
757            //Remove the folder
758            info.Delete();
759        }
760
761        /// <summary>
762        /// Generates a random file name with the given length.
763        /// </summary>
764        /// <param name="length">The length of the file name to generate.</param>
765        /// <returns>A random file name.</returns>
766        private static string GetRandomFileName(int length)
767        {
768            //Get a random file name
769            PRNG prng = PRNGManager.GetInstance(ManagerLibrary.Instance.Settings.ActivePRNG);
770            byte[] newFileNameAry = new byte[length];
771            prng.NextBytes(newFileNameAry);
772
773            //Validate the name
774            string validFileNameChars = "0123456789abcdefghijklmnopqrstuvwxyz" +
775                "ABCDEFGHIJKLMNOPQRSTUVWXYZ _+=-()[]{}',`~!";
776            for (int j = 0, k = newFileNameAry.Length; j < k; ++j)
777                newFileNameAry[j] = (byte)validFileNameChars[
778                    (int)newFileNameAry[j] % validFileNameChars.Length];
779
780            return new System.Text.UTF8Encoding().GetString(newFileNameAry);
781        }
782
783        /// <summary>
784        /// The thread object.
785        /// </summary>
786        private Thread thread;
787
788        /// <summary>
789        /// The lock preventing concurrent access for the tasks list and the
790        /// tasks queue.
791        /// </summary>
792        private object tasksLock = new object();
793
794        /// <summary>
795        /// The list of tasks. Includes all immediate, reboot, and recurring tasks
796        /// </summary>
797        private Dictionary<uint, Task> tasks = new Dictionary<uint, Task>();
798
799        /// <summary>
800        /// The queue of tasks. This queue is executed when the first element's
801        /// timestamp (the key) has been past. This list assumes that all tasks
802        /// are sorted by timestamp, smallest one first.
803        /// </summary>
804        private SortedList<DateTime, Task> scheduledTasks =
805            new SortedList<DateTime, Task>();
806
807        /// <summary>
808        /// The currently executing task.
809        /// </summary>
810        Task currentTask;
811
812        /// <summary>
813        /// The list of task IDs for recycling.
814        /// </summary>
815        private List<uint> unusedIds = new List<uint>();
816
817        /// <summary>
818        /// Lock preventing concurrent access for the IDs.
819        /// </summary>
820        private object unusedIdsLock = new object();
821
822        /// <summary>
823        /// Incrementing ID. This value is incremented by one every time an ID
824        /// is required by no unused IDs remain.
825        /// </summary>
826        private uint nextId = 0;
827
828        /// <summary>
829        /// An automatically reset event allowing the addition of new tasks to
830        /// interrupt the thread's sleeping state waiting for the next recurring
831        /// task to be due.
832        /// </summary>
833        AutoResetEvent schedulerInterrupt = new AutoResetEvent(true);
834    }
835}
Note: See TracBrowser for help on using the repository browser.