/* * $Id$ * Copyright 2008-2010 The Eraser Project * Original Author: Joel Low * Modified By: * * This file is part of Eraser. * * Eraser is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * A copy of the GNU General Public License can be found at * . */ using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Text.RegularExpressions; using System.Runtime.Serialization; using System.Security.Permissions; using System.Threading; using Eraser.Util; using Eraser.Util.ExtensionMethods; namespace Eraser.Manager { /// /// Deals with an erase task. /// [Serializable] public class Task : ISerializable { #region Serialization code protected Task(SerializationInfo info, StreamingContext context) { Name = (string)info.GetValue("Name", typeof(string)); Executor = context.Context as Executor; Targets = (ErasureTargetsCollection)info.GetValue("Targets", typeof(ErasureTargetsCollection)); Targets.Owner = this; Log = (Logger)info.GetValue("Log", typeof(Logger)); Canceled = false; Schedule schedule = (Schedule)info.GetValue("Schedule", typeof(Schedule)); if (schedule.GetType() == Schedule.RunManually.GetType()) Schedule = Schedule.RunManually; else if (schedule.GetType() == Schedule.RunNow.GetType()) Schedule = Schedule.RunNow; else if (schedule.GetType() == Schedule.RunOnRestart.GetType()) Schedule = Schedule.RunOnRestart; else if (schedule is RecurringSchedule) Schedule = schedule; else throw new InvalidDataException(S._("An invalid type was found when loading " + "the task schedule")); } [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Name", Name); info.AddValue("Schedule", Schedule); info.AddValue("Targets", Targets); info.AddValue("Log", Log); } #endregion /// /// Constructor. /// public Task() { Name = string.Empty; Targets = new ErasureTargetsCollection(this); Schedule = Schedule.RunNow; Canceled = false; Log = new Logger(); } /// /// Cancels the task from running, or, if the task is queued for running, /// removes the task from the queue. /// public void Cancel() { Executor.UnqueueTask(this); Canceled = true; } /// /// The Executor object which is managing this task. /// public Executor Executor { get; internal set; } /// /// The name for this task. This is just an opaque value for the user to /// recognize the task. /// public string Name { get; set; } /// /// The name of the task, used for display in UI elements. /// public string UIText { get { //Simple case, the task name was given by the user. if (Name.Length != 0) return Name; string result = string.Empty; if (Targets.Count < 5) { //Simpler case, small set of data. foreach (ErasureTarget tgt in Targets) result += tgt.UIText + ", "; return result.Remove(result.Length - 2); } else { //Ok, we've quite a few entries, get the first, the mid and the end. result = Targets[0].UIText + ", "; result += Targets[Targets.Count / 2].UIText + ", "; result += Targets[Targets.Count - 1].UIText; return S._("{0} and {1} other targets", result, Targets.Count - 3); } } } /// /// Gets the status of the task - whether it is being executed. /// public bool Executing { get; private set; } /// /// Gets whether this task is currently queued to run. This is true only /// if the queue it is in is an explicit request, i.e will run when the /// executor is idle. /// public bool Queued { get { return Executor.IsTaskQueued(this); } } /// /// Gets whether the task has been cancelled from execution. /// public bool Canceled { get; internal set; } /// /// The set of data to erase when this task is executed. /// public ErasureTargetsCollection Targets { get; private set; } /// /// The schedule for running the task. /// public Schedule Schedule { get { return schedule; } set { if (value.Owner != null) throw new ArgumentException("The schedule provided can only " + "belong to one task at a time"); if (schedule is RecurringSchedule) ((RecurringSchedule)schedule).Owner = null; schedule = value; if (schedule is RecurringSchedule) ((RecurringSchedule)schedule).Owner = this; OnTaskEdited(); } } /// /// The log entries which this task has accumulated. /// public Logger Log { get; private set; } /// /// The progress manager object which manages the progress of this task. /// public SteppedProgressManager Progress { get { if (!Executing) throw new InvalidOperationException("The progress of an erasure can only " + "be queried when the task is being executed."); return progress; } private set { progress = value; } } private Schedule schedule; private SteppedProgressManager progress; #region Events /// /// The task has been edited. /// public EventHandler TaskEdited { get; set; } /// /// The start of the execution of a task. /// public EventHandler TaskStarted { get; set; } /// /// The event object holding all event handlers. /// public EventHandler ProgressChanged { get; set; } /// /// The completion of the execution of a task. /// public EventHandler TaskFinished { get; set; } /// /// Broadcasts the task edited event. /// internal void OnTaskEdited() { if (TaskEdited != null) TaskEdited(this, EventArgs.Empty); } /// /// Broadcasts the task execution start event. /// internal void OnTaskStarted() { if (TaskStarted != null) TaskStarted(this, EventArgs.Empty); Executing = true; Progress = new SteppedProgressManager(); } /// /// Broadcasts a ProgressChanged event. The sender will be the erasure target /// which broadcast this event; e.UserState will contain extra information /// about the progress which is stored as a TaskProgressChangedEventArgs /// object. /// /// The which is reporting /// progress. /// The new progress value. /// e.UserState must be of the type /// /// Both sender and e cannot be null. internal void OnProgressChanged(ErasureTarget sender, ProgressChangedEventArgs e) { if (sender == null) throw new ArgumentNullException("sender"); if (e == null) throw new ArgumentNullException("sender"); if (e.UserState.GetType() != typeof(TaskProgressChangedEventArgs)) throw new ArgumentException("The Task.OnProgressChanged event expects a " + "TaskProgressEventArgs argument for the ProgressChangedEventArgs' UserState " + "object.", "e"); if (ProgressChanged != null) ProgressChanged(sender, e); } /// /// Broadcasts the task execution completion event. /// internal void OnTaskFinished() { if (TaskFinished != null) TaskFinished(this, EventArgs.Empty); Executing = false; Progress = null; } #endregion } /// /// Represents a generic target of erasure /// [Serializable] public abstract class ErasureTarget : ISerializable { #region Serialization code protected ErasureTarget(SerializationInfo info, StreamingContext context) { Guid methodGuid = (Guid)info.GetValue("Method", typeof(Guid)); if (methodGuid == Guid.Empty) method = ErasureMethodManager.Default; else method = ErasureMethodManager.GetInstance(methodGuid); } [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Method", method.Guid); } #endregion /// /// Constructor. /// protected ErasureTarget() { } /// /// The method used for erasing the file. If the variable is equal to /// ErasureMethodManager.Default then the default is queried for the /// task type. Check the property to see if /// this variable was set on deliberately or if the result of the get /// call is from the inferred default. /// public virtual ErasureMethod Method { get { return method; } set { method = value; MethodDefined = method != ErasureMethodManager.Default; } } /// /// Checks whether a method has been selected for this target. This is /// because the Method property will return non-default erasure methods /// only. /// public bool MethodDefined { get; private set; } /// /// The task which owns this target. /// public Task Task { get; internal set; } /// /// Retrieves the text to display representing this task. /// public abstract string UIText { get; } /// /// Retrieves the amount of data that needs to be written in order to /// complete the erasure. /// public abstract long TotalData { get; } /// /// Erasure method to use for the target. /// private ErasureMethod method; /// /// The progress of this target. /// public ProgressManagerBase Progress { get; internal set; } } /// /// Class representing a tangible object (file/folder) to be erased. /// [Serializable] public abstract class FileSystemObjectTarget : ErasureTarget { #region Serialization code protected FileSystemObjectTarget(SerializationInfo info, StreamingContext context) : base(info, context) { Path = (string)info.GetValue("Path", typeof(string)); } [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue("Path", Path); } #endregion /// /// Constructor. /// protected FileSystemObjectTarget() : base() { Method = ErasureMethodManager.Default; } /// /// Retrieves the list of files/folders to erase as a list. /// /// Returns the total size in bytes of the /// items. /// A list containing the paths to all the files to be erased. internal abstract List GetPaths(out long totalSize); /// /// Adds ADSes of the given file to the list. /// /// The list to add the ADS paths to. /// The file to look for ADSes protected void GetPathADSes(ICollection list, out long totalSize, string file) { totalSize = 0; try { //Get the ADS names IList adses = new FileInfo(file).GetADSes(); //Then prepend the path. foreach (string adsName in adses) { string adsPath = file + ':' + adsName; list.Add(adsPath); StreamInfo info = new StreamInfo(adsPath); totalSize += info.Length; } } catch (IOException) { if (System.Runtime.InteropServices.Marshal.GetLastWin32Error() == Win32ErrorCode.SharingViolation) { //The system cannot open the file, try to force the file handle to close. if (!ManagerLibrary.Settings.ForceUnlockLockedFiles) throw; foreach (OpenHandle handle in OpenHandle.Items) if (handle.Path == file && handle.Close()) { GetPathADSes(list, out totalSize, file); return; } } else throw; } catch (UnauthorizedAccessException e) { //The system cannot read the file, assume no ADSes for lack of //more information. Task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Error)); } } /// /// The path to the file or folder referred to by this object. /// public string Path { get; set; } public sealed override ErasureMethod Method { get { if (base.MethodDefined) return base.Method; return ErasureMethodManager.GetInstance( ManagerLibrary.Settings.DefaultFileErasureMethod); } set { base.Method = value; } } public override string UIText { get { string fileName = System.IO.Path.GetFileName(Path); string directoryName = System.IO.Path.GetDirectoryName(Path); return string.IsNullOrEmpty(fileName) ? (string.IsNullOrEmpty(directoryName) ? Path : directoryName) : fileName; } } public override long TotalData { get { long totalSize = 0; List paths = GetPaths(out totalSize); return Method.CalculateEraseDataSize(paths, totalSize); } } } /// /// Class representing a unused space erase. /// [Serializable] public class UnusedSpaceTarget : ErasureTarget { #region Serialization code protected UnusedSpaceTarget(SerializationInfo info, StreamingContext context) : base(info, context) { Drive = (string)info.GetValue("Drive", typeof(string)); EraseClusterTips = (bool)info.GetValue("EraseClusterTips", typeof(bool)); } [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue("Drive", Drive); info.AddValue("EraseClusterTips", EraseClusterTips); } #endregion /// /// Constructor. /// public UnusedSpaceTarget() : base() { Method = ErasureMethodManager.Default; } public override sealed ErasureMethod Method { get { if (base.MethodDefined) return base.Method; return ErasureMethodManager.GetInstance( ManagerLibrary.Settings.DefaultUnusedSpaceErasureMethod); } set { base.Method = value; } } public override string UIText { get { return S._("Unused disk space ({0})", Drive); } } public override long TotalData { get { VolumeInfo info = VolumeInfo.FromMountPoint(Drive); return Method.CalculateEraseDataSize(null, info.AvailableFreeSpace); } } /// /// The drive to erase /// public string Drive { get; set; } /// /// Whether cluster tips should be erased. /// public bool EraseClusterTips { get; set; } } /// /// Class representing a file to be erased. /// [Serializable] public class FileTarget : FileSystemObjectTarget { #region Serialization code protected FileTarget(SerializationInfo info, StreamingContext context) : base(info, context) { } #endregion /// /// Constructor. /// public FileTarget() { } internal override List GetPaths(out long totalSize) { totalSize = 0; List result = new List(); FileInfo fileInfo = new FileInfo(Path); if (fileInfo.Exists) { GetPathADSes(result, out totalSize, Path); totalSize += fileInfo.Length; } result.Add(Path); return result; } } /// /// Represents a folder and its files which are to be erased. /// [Serializable] public class FolderTarget : FileSystemObjectTarget { #region Serialization code protected FolderTarget(SerializationInfo info, StreamingContext context) : base(info, context) { IncludeMask = (string)info.GetValue("IncludeMask", typeof(string)); ExcludeMask = (string)info.GetValue("ExcludeMask", typeof(string)); DeleteIfEmpty = (bool)info.GetValue("DeleteIfEmpty", typeof(bool)); } [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue("IncludeMask", IncludeMask); info.AddValue("ExcludeMask", ExcludeMask); info.AddValue("DeleteIfEmpty", DeleteIfEmpty); } #endregion /// /// Constructor. /// public FolderTarget() { IncludeMask = string.Empty; ExcludeMask = string.Empty; DeleteIfEmpty = true; } internal override List GetPaths(out long totalSize) { //Get a list to hold all the resulting paths. List result = new List(); //Open the root of the search, including every file matching the pattern DirectoryInfo dir = new DirectoryInfo(Path); //List recursively all the files which match the include pattern. FileInfo[] files = GetFiles(dir); //Then exclude each file and finalize the list and total file size totalSize = 0; if (ExcludeMask.Length != 0) { string regex = Regex.Escape(ExcludeMask).Replace("\\*", ".*"). Replace("\\?", "."); Regex excludePattern = new Regex(regex, RegexOptions.IgnoreCase); foreach (FileInfo file in files) if (file.Exists && (file.Attributes & FileAttributes.ReparsePoint) == 0 && excludePattern.Matches(file.FullName).Count == 0) { totalSize += file.Length; GetPathADSes(result, out totalSize, file.FullName); result.Add(file.FullName); } } else foreach (FileInfo file in files) { if (!file.Exists || (file.Attributes & FileAttributes.ReparsePoint) != 0) continue; //Get the size of the file and its ADSes totalSize += file.Length; long adsesSize = 0; GetPathADSes(result, out adsesSize, file.FullName); totalSize += adsesSize; //Append this file to the list of files to erase. result.Add(file.FullName); } //Return the filtered list. return result; } /// /// Gets all files in the provided directory. /// /// The directory to look files in. /// A list of files found in the directory matching the IncludeMask /// property. private FileInfo[] GetFiles(DirectoryInfo info) { List result = new List(); if (info.Exists) { try { foreach (DirectoryInfo dir in info.GetDirectories()) result.AddRange(GetFiles(dir)); if (IncludeMask.Length == 0) result.AddRange(info.GetFiles()); else result.AddRange(info.GetFiles(IncludeMask, SearchOption.TopDirectoryOnly)); } catch (UnauthorizedAccessException e) { Task.Log.LastSessionEntries.Add(new LogEntry(S._("Could not erase files and " + "subfolders in {0} because {1}", info.FullName, e.Message), LogLevel.Error)); } } return result.ToArray(); } /// /// A wildcard expression stating the condition for the set of files to include. /// The include mask is applied before the exclude mask is applied. If this value /// is empty, all files and folders within the folder specified is included. /// public string IncludeMask { get; set; } /// /// A wildcard expression stating the condition for removing files from the set /// of included files. If this value is omitted, all files and folders extracted /// by the inclusion mask is erased. /// public string ExcludeMask { get; set; } /// /// Determines if Eraser should delete the folder after the erase process. /// public bool DeleteIfEmpty { get; set; } } [Serializable] public class RecycleBinTarget : FileSystemObjectTarget { #region Serialization code protected RecycleBinTarget(SerializationInfo info, StreamingContext context) : base(info, context) { } #endregion public RecycleBinTarget() { } internal override List GetPaths(out long totalSize) { totalSize = 0; List result = new List(); string[] rootDirectory = new string[] { "$RECYCLE.BIN", "RECYCLER" }; foreach (DriveInfo drive in DriveInfo.GetDrives()) { foreach (string rootDir in rootDirectory) { DirectoryInfo dir = new DirectoryInfo( System.IO.Path.Combine( System.IO.Path.Combine(drive.Name, rootDir), System.Security.Principal.WindowsIdentity.GetCurrent(). User.ToString())); if (!dir.Exists) continue; GetRecyclerFiles(dir, result, ref totalSize); } } return result; } /// /// Retrieves all files within this folder, without exclusions. /// /// The DirectoryInfo object representing the folder to traverse. /// The list of files to store path information in. /// Receives the total size of the files. private void GetRecyclerFiles(DirectoryInfo info, List paths, ref long totalSize) { try { foreach (FileInfo fileInfo in info.GetFiles()) { if (!fileInfo.Exists || (fileInfo.Attributes & FileAttributes.ReparsePoint) != 0) continue; totalSize += fileInfo.Length; GetPathADSes(paths, out totalSize, fileInfo.FullName); paths.Add(fileInfo.FullName); } foreach (DirectoryInfo directoryInfo in info.GetDirectories()) if ((directoryInfo.Attributes & FileAttributes.ReparsePoint) == 0) GetRecyclerFiles(directoryInfo, paths, ref totalSize); } catch (UnauthorizedAccessException e) { Task.Log.LastSessionEntries.Add(new LogEntry(e.Message, LogLevel.Error)); } } /// /// Retrieves the text to display representing this task. /// public override string UIText { get { return S._("Recycle Bin"); } } } /// /// Maintains a collection of erasure targets. /// [Serializable] public class ErasureTargetsCollection : IList, ISerializable { #region Constructors internal ErasureTargetsCollection(Task owner) { this.list = new List(); this.owner = owner; } internal ErasureTargetsCollection(Task owner, int capacity) : this(owner) { list.Capacity = capacity; } internal ErasureTargetsCollection(Task owner, IEnumerable targets) : this(owner) { list.AddRange(targets); } #endregion #region Serialization Code protected ErasureTargetsCollection(SerializationInfo info, StreamingContext context) { list = (List)info.GetValue("list", typeof(List)); } [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("list", list); } #endregion #region IEnumerable Members public IEnumerator GetEnumerator() { return list.GetEnumerator(); } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region ICollection Members public void Add(ErasureTarget item) { item.Task = owner; list.Add(item); } public void Clear() { foreach (ErasureTarget item in list) Remove(item); } public bool Contains(ErasureTarget item) { return list.Contains(item); } public void CopyTo(ErasureTarget[] array, int arrayIndex) { list.CopyTo(array, arrayIndex); } public int Count { get { return list.Count; } } public bool IsReadOnly { get { return false; } } public bool Remove(ErasureTarget item) { int index = list.IndexOf(item); if (index < 0) return false; RemoveAt(index); return true; } #endregion #region IList Members public int IndexOf(ErasureTarget item) { return list.IndexOf(item); } public void Insert(int index, ErasureTarget item) { item.Task = owner; list.Insert(index, item); } public void RemoveAt(int index) { list.RemoveAt(index); } public ErasureTarget this[int index] { get { return list[index]; } set { list[index] = value; } } #endregion /// /// The owner of this list of targets. /// public Task Owner { get { return owner; } internal set { owner = value; foreach (ErasureTarget target in list) target.Task = owner; } } /// /// The owner of this list of targets. All targets added to this list /// will have the owner set to this object. /// private Task owner; /// /// The list bring the data store behind this object. /// private List list; } /// /// A base event class for all event arguments involving a task. /// public class TaskEventArgs : EventArgs { /// /// Constructor. /// /// The task being referred to by this event. public TaskEventArgs(Task task) { Task = task; } /// /// The executing task. /// public Task Task { get; private set; } } /// /// Stores extra information in the /// structure that is not conveyed in the ProgressManagerBase classes. /// public class TaskProgressChangedEventArgs { /// /// Constructor. /// /// The item whose erasure progress is being erased. /// The current pass number for this item. /// The total number of passes to complete erasure /// of this item. public TaskProgressChangedEventArgs(string itemName, int itemPass, int itemTotalPasses) { ItemName = itemName; ItemPass = itemPass; ItemTotalPasses = itemTotalPasses; } /// /// The file name of the item being erased. /// public string ItemName { get; private set; } /// /// The pass number of a multi-pass erasure method. /// public int ItemPass { get; private set; } /// /// The total number of passes to complete before this erasure method is /// completed. /// public int ItemTotalPasses { get; private set; } } }