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

Revision 2468, 13.4 KB checked in by lowjoel, 2 years ago (diff)

Instead of pushing events to the client, we will update our Progress object and the client should pull progress updates instead. This prepares the code to be used by the remote executor (for service execution).

In the interim, there is no progress feedback for all erasures.

  • 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            Task.Progress.Steps.Add(new SteppedProgressManagerStep(Progress,
208                1.0f / Task.Targets.Count));
209
210            //Iterate over every path, and erase the path.
211            for (int i = 0; i < paths.Count; ++i)
212            {
213                ProgressManager step = new ProgressManager();
214                Progress.Steps.Add(new SteppedProgressManagerStep(step,
215                    dataTotal == 0 ? 0.0f : paths[i].Length / (float)dataTotal,
216                    S._("Erasing files...")));
217                EraseStream(paths[i], step);
218                step.MarkComplete();
219            }
220        }
221
222        /// <summary>
223        /// Erases the provided stream, and updates progress using the provided
224        /// progress manager.
225        /// </summary>
226        /// <param name="info">The information regarding the stream that needs erasure.</param>
227        /// <param name="progress">The progress manager for the erasure of the current
228        /// stream.</param>
229        protected void EraseStream(StreamInfo info, ProgressManager progress)
230        {
231            //Check that the file exists - we do not want to bother erasing nonexistant files
232            if (!info.Exists)
233            {
234                Logger.Log(S._("The file {0} was not erased as the file does not exist.",
235                    info.FileName), LogLevel.Notice);
236                return;
237            }
238
239            //Get the filesystem provider to handle the secure file erasures
240            IFileSystem fsManager = Host.Instance.FileSystems[
241                VolumeInfo.FromMountPoint(info.DirectoryName)];
242
243            bool isReadOnly = false;
244
245            try
246            {
247                //Update the task progress
248                IErasureMethod method = EffectiveMethod;
249                /*OnProgressChanged(this, new ProgressChangedEventArgs(progress,
250                    new TaskProgressChangedEventArgs(info.FullName, 0, method.Passes)));*/
251
252                //Remove the read-only flag, if it is set.
253                if (isReadOnly = info.IsReadOnly)
254                    info.IsReadOnly = false;
255
256                //Define the callback function for progress reporting.
257                ErasureMethodProgressFunction callback =
258                    delegate(long lastWritten, long totalData, int currentPass)
259                    {
260                        if (Task.Canceled)
261                            throw new OperationCanceledException(S._("The task was cancelled."));
262
263                        progress.Total = totalData;
264                        progress.Completed += lastWritten;
265                        /*OnProgressChanged(this, new ProgressChangedEventArgs(progress,
266                            new TaskProgressChangedEventArgs(info.FullName, currentPass, method.Passes)));*/
267                    };
268
269                TryEraseStream(fsManager, method, info, callback);
270
271                //Remove the file.
272                FileInfo fileInfo = info.File;
273                if (fileInfo != null)
274                    fsManager.DeleteFile(fileInfo);
275                progress.MarkComplete();
276            }
277            catch (UnauthorizedAccessException)
278            {
279                Logger.Log(S._("The file {0} could not be erased because the file's " +
280                    "permissions prevent access to the file.", info.FullName), LogLevel.Error);
281            }
282            catch (SharingViolationException e)
283            {
284                Logger.Log(e.Message, LogLevel.Error);
285            }
286            finally
287            {
288                //Re-set the read-only flag if the file exists (i.e. there was an error)
289                if (isReadOnly && info.Exists && !info.IsReadOnly)
290                    info.IsReadOnly = isReadOnly;
291            }
292        }
293
294        /// <summary>
295        /// Attempts to erase a stream, trying to close all open handles if a process has
296        /// a lock on the file.
297        /// </summary>
298        /// <param name="fsManager">The file system provider used to erase the stream.</param>
299        /// <param name="method">The erasure method to use to erase the stream.</param>
300        /// <param name="info">The stream to erase.</param>
301        /// <param name="callback">The erasure progress callback.</param>
302        private void TryEraseStream(IFileSystem fsManager, IErasureMethod method, StreamInfo info,
303            ErasureMethodProgressFunction callback)
304        {
305            for (int i = 0; ; ++i)
306            {
307                try
308                {
309                    //Make sure the file does not have any attributes which may affect
310                    //the erasure process
311                    if ((info.Attributes & FileAttributes.Compressed) != 0 ||
312                        (info.Attributes & FileAttributes.Encrypted) != 0 ||
313                        (info.Attributes & FileAttributes.SparseFile) != 0)
314                    {
315                        //Log the error
316                        Logger.Log(S._("The file {0} could not be erased because the file was " +
317                            "either compressed, encrypted or a sparse file.", info.FullName),
318                            LogLevel.Error);
319                        return;
320                    }
321
322                    //Do not erase reparse points, as they will cause other references to the file
323                    //to be to garbage.
324                    if ((info.Attributes & FileAttributes.ReparsePoint) == 0)
325                        fsManager.EraseFileSystemObject(info, method, callback);
326                    else
327                        Logger.Log(S._("The file {0} is a hard link or a symbolic link thus the " +
328                            "contents of the file was not erased.", LogLevel.Notice));
329                    return;
330                }
331                catch (SharingViolationException)
332                {
333                    if (!ManagerLibrary.Settings.ForceUnlockLockedFiles)
334                        throw;
335
336                    //Try closing all open handles. If it succeeds, we can run the erase again.
337                    //To prevent Eraser from deadlocking, we will only attempt this once. Some
338                    //programs may be aggressive and keep a handle open in a tight loop.
339                    List<OpenHandle> remainingHandles = OpenHandle.Close(info.FullName);
340                    if (i == 0 && remainingHandles.Count == 0)
341                        continue;
342
343                    //Either we could not close all instances, or we already tried twice. Report
344                    //the error.
345                    StringBuilder processStr = new StringBuilder();
346                    foreach (OpenHandle handle in remainingHandles)
347                    {
348                        try
349                        {
350                            processStr.AppendFormat(
351                                System.Globalization.CultureInfo.InvariantCulture,
352                                "{0}, ", handle.Process.MainModule.FileName);
353                        }
354                        catch (System.ComponentModel.Win32Exception)
355                        {
356                            processStr.AppendFormat(
357                                System.Globalization.CultureInfo.InvariantCulture,
358                                "Process ID {0}, ", handle.Process.Id);
359                        }
360                    }
361
362                    throw new SharingViolationException(S._(
363                        "Could not force closure of file \"{0}\" {1}", info.FileName,
364                        S._("(locked by {0})",
365                            processStr.ToString().Remove(processStr.Length - 2)).Trim()),
366                        info.FileName);
367                }
368            }
369        }
370
371        /// <summary>
372        /// Writes a file for plausible deniability over the current stream.
373        /// </summary>
374        /// <param name="stream">The stream to write the data to.</param>
375        protected static void CopyPlausibleDeniabilityFile(Stream stream)
376        {
377            //Get the template file to copy
378            FileInfo shadowFileInfo;
379            {
380                string shadowFile = null;
381                List<string> entries = new List<string>(
382                    Host.Instance.Settings.PlausibleDeniabilityFiles);
383                IPrng prng = Host.Instance.Prngs.ActivePrng;
384                do
385                {
386                    if (entries.Count == 0)
387                        throw new FatalException(S._("Plausible deniability was selected, " +
388                            "but no decoy files were found. The current file has been only " +
389                            "replaced with random data."));
390
391                    //Get an item from the list of files, and then check that the item exists.
392                    int index = prng.Next(entries.Count - 1);
393                    shadowFile = entries[index];
394                    if (File.Exists(shadowFile) || Directory.Exists(shadowFile))
395                    {
396                        if ((File.GetAttributes(shadowFile) & FileAttributes.Directory) != 0)
397                        {
398                            DirectoryInfo dir = new DirectoryInfo(shadowFile);
399                            FileInfo[] files = dir.GetFiles("*", SearchOption.AllDirectories);
400                            entries.Capacity += files.Length;
401                            foreach (FileInfo f in files)
402                                entries.Add(f.FullName);
403                        }
404                        else
405                            shadowFile = entries[index];
406                    }
407                    else
408                        shadowFile = null;
409
410                    entries.RemoveAt(index);
411                }
412                while (string.IsNullOrEmpty(shadowFile));
413                shadowFileInfo = new FileInfo(shadowFile);
414            }
415
416            //Dump the copy (the first 4MB, or less, depending on the file size and size of
417            //the original file)
418            long amountToCopy = Math.Min(stream.Length,
419                Math.Min(4 * 1024 * 1024, shadowFileInfo.Length));
420            using (FileStream shadowFileStream = shadowFileInfo.OpenRead())
421            {
422                while (stream.Position < amountToCopy)
423                {
424                    byte[] buf = new byte[524288];
425                    int bytesRead = shadowFileStream.Read(buf, 0, buf.Length);
426
427                    //Stop bothering if the input stream is at the end
428                    if (bytesRead == 0)
429                        break;
430
431                    //Dump the read contents onto the file to be deleted
432                    stream.Write(buf, 0,
433                        (int)Math.Min(bytesRead, amountToCopy - stream.Position));
434                }
435            }
436        }
437    }
438}
Note: See TracBrowser for help on using the repository browser.