source: branches/eraser6/pluginsRewrite/Eraser.DefaultPlugins/ErasureTargets/FileSystemObjectErasureTarget.cs @ 2486

Revision 2486, 11.0 KB checked in by lowjoel, 2 years ago (diff)

Report progress updates by pushing information to the SteppedProgressManager? instance associated with each erasure target. Furthermore, do not manipulate the state of the Task object, instead, let the Task object manage its own Progress state.

Because of this, a new property Tag is created in ProgressManagerBase?, to hold state information.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using System.Text;
26
27using System.Runtime.Serialization;
28using System.Runtime.InteropServices;
29using System.Security.Permissions;
30using System.IO;
31
32using Eraser.Util;
33using Eraser.Util.ExtensionMethods;
34using Eraser.Plugins;
35using Eraser.Plugins.ExtensionPoints;
36using Eraser.Plugins.Registrars;
37
38namespace Eraser.DefaultPlugins
39{
40    /// <summary>
41    /// Class representing a tangible object (file/folder) to be erased.
42    /// </summary>
43    [Serializable]
44    abstract class FileSystemObjectErasureTarget : ErasureTargetBase
45    {
46        #region Serialization code
47        protected FileSystemObjectErasureTarget(SerializationInfo info, StreamingContext context)
48            : base(info, context)
49        {
50            Path = (string)info.GetValue("Path", typeof(string));
51        }
52
53        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
54        public override void GetObjectData(SerializationInfo info, StreamingContext context)
55        {
56            base.GetObjectData(info, context);
57            info.AddValue("Path", Path);
58        }
59        #endregion
60
61        /// <summary>
62        /// Constructor.
63        /// </summary>
64        protected FileSystemObjectErasureTarget()
65            : base()
66        {
67        }
68
69        /// <summary>
70        /// The Progress of this target's erasure. This property must be set
71        /// before <see cref="FileSystemObjectErasureTarget.Execute"/> is called.
72        /// </summary>
73        protected new SteppedProgressManager Progress
74        {
75            get
76            {
77                return (SteppedProgressManager)base.Progress;
78            }
79            set
80            {
81                base.Progress = value;
82            }
83        }
84
85        /// <summary>
86        /// Retrieves the list of files/folders to erase as a list.
87        /// </summary>
88        /// <returns>A list containing the paths to all the files to be erased.</returns>
89        protected abstract List<StreamInfo> GetPaths();
90
91        /// <summary>
92        /// Gets all files in the provided directory.
93        /// </summary>
94        /// <param name="info">The directory to look files in.</param>
95        /// <returns>A list of files found in the directory.</returns>
96        /// <remarks>This function does not recurse into directories which are
97        /// reparse points.</remarks>
98        protected static FileInfo[] GetFiles(DirectoryInfo info)
99        {
100            List<FileInfo> result = new List<FileInfo>();
101            if (info.Exists && (info.Attributes & FileAttributes.ReparsePoint) == 0)
102            {
103                try
104                {
105                    foreach (DirectoryInfo dir in info.GetDirectories())
106                        result.AddRange(GetFiles(dir));
107                    result.AddRange(info.GetFiles());
108                }
109                catch (UnauthorizedAccessException e)
110                {
111                    Logger.Log(S._("Could not erase files and subfolders in {0} because {1}",
112                        info.FullName, e.Message), LogLevel.Error);
113                }
114            }
115
116            return result.ToArray();
117        }
118
119        /// <summary>
120        /// Adds ADSes of the given file to the list, forcing the open handles to the
121        /// files closed if necessary.
122        /// </summary>
123        /// <param name="file">The file to look for ADSes</param>
124        protected static StreamInfo[] GetPathADSes(FileInfo file)
125        {
126            try
127            {
128                return file.GetADSes().ToArray();
129            }
130            catch (FileNotFoundException)
131            {
132            }
133            catch (SharingViolationException)
134            {
135                //The system cannot open the file, try to force the file handle to close.
136                if (!Host.Instance.Settings.ForceUnlockLockedFiles)
137                    throw;
138
139                StringBuilder processStr = new StringBuilder();
140                foreach (OpenHandle handle in OpenHandle.Close(file.FullName))
141                {
142                    try
143                    {
144                        processStr.AppendFormat(
145                            System.Globalization.CultureInfo.InvariantCulture,
146                            "{0}, ", handle.Process.MainModule.FileName);
147                    }
148                    catch (System.ComponentModel.Win32Exception)
149                    {
150                        processStr.AppendFormat(
151                            System.Globalization.CultureInfo.InvariantCulture,
152                            "Process ID {0}, ", handle.Process.Id);
153                    }
154                }
155
156                if (processStr.Length == 0)
157                    return GetPathADSes(file);
158                else
159                    throw;
160            }
161            catch (UnauthorizedAccessException e)
162            {
163                //The system cannot read the file, assume no ADSes for lack of
164                //more information.
165                Logger.Log(e.Message, LogLevel.Error);
166            }
167
168            return new StreamInfo[0];
169        }
170
171        /// <summary>
172        /// The path to the file or folder referred to by this object.
173        /// </summary>
174        public string Path { get; set; }
175
176        public sealed override IErasureMethod EffectiveMethod
177        {
178            get
179            {
180                if (Method != ErasureMethodRegistrar.Default)
181                    return base.EffectiveMethod;
182
183                return Host.Instance.ErasureMethods[
184                    Host.Instance.Settings.DefaultFileErasureMethod];
185            }
186        }
187
188        public override string ToString()
189        {
190            return Path;
191        }
192
193        /// <summary>
194        /// Erases the streams returned by the <see cref="GetPaths"/> function.
195        /// </summary>
196        /// <remarks>The <see cref="Progress"/> property must be defined prior
197        /// to the execution of this function.</remarks>
198        public override void Execute()
199        {
200            //Retrieve the list of files to erase.
201            List<StreamInfo> paths = GetPaths();
202            long dataTotal = paths.Sum(x => x.Length);
203
204            //Set the event's current target status.
205            if (Progress == null)
206                throw new InvalidOperationException("The Progress property must not be null.");
207
208            //Iterate over every path, and erase the path.
209            for (int i = 0; i < paths.Count; ++i)
210            {
211                //Create a new progress manager for the file.
212                ProgressManager step = new ProgressManager();
213                Progress.Steps.Add(new SteppedProgressManagerStep(step,
214                    dataTotal == 0 ? 0.0f : paths[i].Length / (float)dataTotal,
215                    paths[i].FullName));
216                EraseStream(paths[i], step);
217            }
218        }
219
220        /// <summary>
221        /// Erases the provided stream, and updates progress using the provided
222        /// progress manager.
223        /// </summary>
224        /// <param name="info">The information regarding the stream that needs erasure.</param>
225        /// <param name="progress">The progress manager for the erasure of the current
226        /// stream.</param>
227        protected void EraseStream(StreamInfo info, ProgressManager progress)
228        {
229            //Check that the file exists - we do not want to bother erasing nonexistant files
230            if (!info.Exists)
231            {
232                Logger.Log(S._("The file {0} was not erased as the file does not exist.",
233                    info.FileName), LogLevel.Notice);
234                return;
235            }
236
237            //Get the filesystem provider to handle the secure file erasures
238            IFileSystem fsManager = Host.Instance.FileSystems[
239                VolumeInfo.FromMountPoint(info.DirectoryName)];
240
241            bool isReadOnly = false;
242
243            try
244            {
245                //Update the task progress
246                IErasureMethod method = EffectiveMethod;
247               
248                //Remove the read-only flag, if it is set.
249                if (isReadOnly = info.IsReadOnly)
250                    info.IsReadOnly = false;
251
252                //Define the callback function for progress reporting.
253                ErasureMethodProgressFunction callback =
254                    delegate(long lastWritten, long totalData, int currentPass)
255                    {
256                        if (Task.Canceled)
257                            throw new OperationCanceledException(S._("The task was cancelled."));
258
259                        progress.Tag = new object[2] { currentPass, method.Passes };
260                        progress.Total = totalData;
261                        progress.Completed += lastWritten;
262                    };
263
264                TryEraseStream(fsManager, method, info, callback);
265
266                //Remove the file.
267                FileInfo fileInfo = info.File;
268                if (fileInfo != null)
269                    fsManager.DeleteFile(fileInfo);
270                progress.MarkComplete();
271            }
272            catch (UnauthorizedAccessException)
273            {
274                Logger.Log(S._("The file {0} could not be erased because the file's " +
275                    "permissions prevent access to the file.", info.FullName), LogLevel.Error);
276            }
277            catch (SharingViolationException e)
278            {
279                Logger.Log(e.Message, LogLevel.Error);
280            }
281            finally
282            {
283                //Re-set the read-only flag if the file exists (i.e. there was an error)
284                if (isReadOnly && info.Exists && !info.IsReadOnly)
285                    info.IsReadOnly = isReadOnly;
286            }
287        }
288
289        /// <summary>
290        /// Attempts to erase a stream, trying to close all open handles if a process has
291        /// a lock on the file.
292        /// </summary>
293        /// <param name="fsManager">The file system provider used to erase the stream.</param>
294        /// <param name="method">The erasure method to use to erase the stream.</param>
295        /// <param name="info">The stream to erase.</param>
296        /// <param name="callback">The erasure progress callback.</param>
297        private void TryEraseStream(IFileSystem fsManager, IErasureMethod method, StreamInfo info,
298            ErasureMethodProgressFunction callback)
299        {
300            for (int i = 0; ; ++i)
301            {
302                try
303                {
304                    //Make sure the file does not have any attributes which may affect
305                    //the erasure process
306                    if ((info.Attributes & FileAttributes.Compressed) != 0 ||
307                        (info.Attributes & FileAttributes.Encrypted) != 0 ||
308                        (info.Attributes & FileAttributes.SparseFile) != 0)
309                    {
310                        //Log the error
311                        Logger.Log(S._("The file {0} could not be erased because the file was " +
312                            "either compressed, encrypted or a sparse file.", info.FullName),
313                            LogLevel.Error);
314                        return;
315                    }
316
317                    //Do not erase reparse points, as they will cause other references to the file
318                    //to be to garbage.
319                    if ((info.Attributes & FileAttributes.ReparsePoint) == 0)
320                        fsManager.EraseFileSystemObject(info, method, callback);
321                    else
322                        Logger.Log(S._("The file {0} is a hard link or a symbolic link thus the " +
323                            "contents of the file was not erased.", LogLevel.Notice));
324                    return;
325                }
326                catch (SharingViolationException)
327                {
328                    if (!Host.Instance.Settings.ForceUnlockLockedFiles)
329                        throw;
330
331                    //Try closing all open handles. If it succeeds, we can run the erase again.
332                    //To prevent Eraser from deadlocking, we will only attempt this once. Some
333                    //programs may be aggressive and keep a handle open in a tight loop.
334                    List<OpenHandle> remainingHandles = OpenHandle.Close(info.FullName);
335                    if (i == 0 && remainingHandles.Count == 0)
336                        continue;
337
338                    //Either we could not close all instances, or we already tried twice. Report
339                    //the error.
340                    StringBuilder processStr = new StringBuilder();
341                    foreach (OpenHandle handle in remainingHandles)
342                    {
343                        try
344                        {
345                            processStr.AppendFormat(
346                                System.Globalization.CultureInfo.InvariantCulture,
347                                "{0}, ", handle.Process.MainModule.FileName);
348                        }
349                        catch (System.ComponentModel.Win32Exception)
350                        {
351                            processStr.AppendFormat(
352                                System.Globalization.CultureInfo.InvariantCulture,
353                                "Process ID {0}, ", handle.Process.Id);
354                        }
355                    }
356
357                    throw new SharingViolationException(S._(
358                        "Could not force closure of file \"{0}\" {1}", info.FileName,
359                        S._("(locked by {0})",
360                            processStr.ToString().Remove(processStr.Length - 2)).Trim()),
361                        info.FileName);
362                }
363            }
364        }
365    }
366}
Note: See TracBrowser for help on using the repository browser.