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

Revision 269, 24.6 KB checked in by lowjoel, 7 years ago (diff)

-Implemented a few preliminary checks before running the unused disk space pass

-Quotas
-Elevation

-Fixed a few locked file errors. Use using clauses liberally!
-Replaced a few try-catch blocks with proper file/folder testing methods. Exceptions are meant to be exceptions and not expected things!

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