source: trunk/eraser/Eraser.DefaultPlugins/ErasureTargets/FileSystemObjectErasureTarget.cs @ 2632

Revision 2632, 11.8 KB checked in by lowjoel, 2 years ago (diff)

When erasing a file/folder, it is possible that after the erase is complete the file or containing directory was deleted by the user or another program before it can be erased. A FileNotFoundException? or DirectoryNotFoundException? would be raised respectively, so handle that situation by logging a warning.

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