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

Revision 2335, 11.2 KB checked in by lowjoel, 19 months ago (diff)

Allow file unlocking to be done in the actual erasure as well, in addition to when finding stream ADSes. This allows a greater number of files to be unlocked. Addresses #394

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