source: trunk/eraser/Eraser.DefaultPlugins/FileSystems/Windows.cs @ 2032

Revision 2032, 12.1 KB checked in by lowjoel, 4 years ago (diff)

Create a utility OpenHandle?.Close function which is shorthand for closing all file handles with a given path.

  • 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.IO;
28using System.Threading;
29using Eraser.Manager;
30using Eraser.Util;
31using Eraser.Util.ExtensionMethods;
32
33namespace Eraser.DefaultPlugins
34{
35    /// <summary>
36    /// Base class for all Windows filesystems.
37    /// </summary>
38    public abstract class WindowsFileSystem : FileSystem
39    {
40        public override void DeleteFile(FileInfo info)
41        {
42            //If the user wants plausible deniability, find a random file from the
43            //list of decoys and write it over.
44            if (Manager.ManagerLibrary.Settings.PlausibleDeniability)
45            {
46                using (FileStream fileStream = info.OpenWrite())
47                    CopyPlausibleDeniabilityFile(fileStream);
48            }
49
50            DeleteFileSystemInfo(info);
51        }
52
53        public override void DeleteFolder(DirectoryInfo info, bool recursive)
54        {
55            if (!recursive && info.GetFileSystemInfos().Length != 0)
56                throw new InvalidOperationException(S._("The folder {0} cannot be deleted as it is " +
57                    "not empty."));
58
59            //TODO: check for reparse points
60            foreach (DirectoryInfo dir in info.GetDirectories())
61                DeleteFolder(dir);
62            foreach (FileInfo file in info.GetFiles())
63                DeleteFile(file);
64
65            DeleteFileSystemInfo(info);
66        }
67
68        /// <summary>
69        /// Deletes a directory or file from disk. This assumes that directories already
70        /// have been deleted.
71        /// </summary>
72        /// <param name="info">The file or directory to delete.</param>
73        private void DeleteFileSystemInfo(FileSystemInfo info)
74        {
75            //If the file/directory doesn't exist, pass.
76            if (!info.Exists)
77                return;
78
79            //Reset the file attributes to non-content indexed so indexing
80            //services will not lock the file.
81            try
82            {
83                info.Attributes = FileAttributes.NotContentIndexed;
84            }
85            catch (ArgumentException e)
86            {
87                //This is an undocumented exception: when the path we are setting
88                //cannot be accessed (ERROR_ACCESS_DENIED is returned) an
89                //ArgumentException is raised (no idea why!)
90                throw new UnauthorizedAccessException(e.Message, e);
91            }
92
93            //Rename the file a few times to erase the entry from the file system
94            //table.
95            for (int i = 0, tries = 0; i < FileNameErasePasses; ++tries)
96            {
97                //Generate a new file name for the file/directory.
98                string newPath = GenerateRandomFileName(info.GetParent(), info.Name.Length);
99
100                try
101                {
102                    //Reset the file access times: after every rename the file times may change.
103                    info.CreationTime = info.LastWriteTime = info.LastAccessTime = MinTimestamp;
104
105                    //Try to rename the file. If it fails, it is probably due to another
106                    //process locking the file. Defer, then rename again.
107                    info.MoveTo(newPath);
108                    ++i;
109                }
110                catch (IOException e)
111                {
112                    switch (System.Runtime.InteropServices.Marshal.GetLastWin32Error())
113                    {
114                        case Win32ErrorCode.AccessDenied:
115                            throw new UnauthorizedAccessException(S._("The file {0} could not " +
116                                "be erased because the file's permissions prevent access to the file.",
117                                info.FullName));
118
119                        case Win32ErrorCode.SharingViolation:
120                            //If after FilenameEraseTries the file is still locked, some program is
121                            //definitely using the file; throw an exception.
122                            if (tries > FileNameEraseTries)
123                            {
124                                //Try to force the handle closed.
125                                if (tries > FileNameEraseTries + 1 ||
126                                    !ManagerLibrary.Settings.ForceUnlockLockedFiles)
127                                {
128                                    throw new IOException(S._("The file {0} is currently in use " +
129                                        "and cannot be removed.", info.FullName), e);
130                                }
131
132                                StringBuilder processStr = new StringBuilder();
133                                foreach (OpenHandle handle in OpenHandle.Close(info.FullName))
134                                {
135                                    try
136                                    {
137                                        processStr.AppendFormat(
138                                            System.Globalization.CultureInfo.InvariantCulture,
139                                            "{0}, ", (System.Diagnostics.Process.GetProcessById(handle.ProcessId)).MainModule.FileName);
140                                    }
141                                    catch (System.ComponentModel.Win32Exception)
142                                    {
143                                        processStr.AppendFormat(
144                                            System.Globalization.CultureInfo.InvariantCulture,
145                                            "Process ID {0}, ", handle.ProcessId);
146                                    }
147                                }
148
149                                if (processStr.Length != 0)
150                                    Logger.Log(S._("Could not force closure of file \"{0}\" {1}",
151                                            info.FullName, S._("(locked by {0})",
152                                                processStr.ToString().Remove(processStr.Length - 2)).Trim()),
153                                        LogLevel.Error);
154                            }
155
156                            //Let the process locking the file release the lock
157                            Thread.Sleep(100);
158                            break;
159
160                        default:
161                            throw;
162                    }
163                }
164            }
165
166            //Then delete the file.
167            for (int i = 0; i < FileNameEraseTries; ++i)
168                try
169                {
170                    info.Delete();
171                    break;
172                }
173                catch (IOException e)
174                {
175                    switch (System.Runtime.InteropServices.Marshal.GetLastWin32Error())
176                    {
177                        case Win32ErrorCode.AccessDenied:
178                            throw new UnauthorizedAccessException(S._("The file {0} could not " +
179                                "be erased because the file's permissions prevent access to the file.",
180                                info.FullName), e);
181
182                        case Win32ErrorCode.SharingViolation:
183                            //If after FilenameEraseTries the file is still locked, some program is
184                            //definitely using the file; throw an exception.
185                            if (i > FileNameEraseTries)
186                                throw new IOException(S._("The file {0} is currently in use and " +
187                                    "cannot be removed.", info.FullName), e);
188
189                            //Let the process locking the file release the lock
190                            Thread.Sleep(100);
191                            break;
192
193                        default:
194                            throw;
195                    }
196                }
197        }
198
199        public override void EraseClusterTips(VolumeInfo info, ErasureMethod method,
200            ClusterTipsSearchProgress searchCallback, ClusterTipsEraseProgress eraseCallback)
201        {
202            //List all the files which can be erased.
203            List<string> files = new List<string>();
204            if (!info.IsMounted)
205                throw new InvalidOperationException(S._("Could not erase cluster tips in {0} " +
206                    "as the volume is not mounted.", info.VolumeId));
207            ListFiles(new DirectoryInfo(info.MountPoints[0]), files, searchCallback);
208
209            //For every file, erase the cluster tips.
210            for (int i = 0, j = files.Count; i != j; ++i)
211            {
212                //Get the file attributes for restoring later
213                StreamInfo streamInfo = new StreamInfo(files[i]);
214                if (!streamInfo.Exists)
215                    continue;
216
217                try
218                {
219                    //Reset the file attributes.
220                    FileAttributes fileAttr = streamInfo.Attributes;
221                    streamInfo.Attributes = FileAttributes.Normal;
222
223                    try
224                    {
225                        EraseFileClusterTips(files[i], method);
226                    }
227                    finally
228                    {
229                        streamInfo.Attributes = fileAttr;
230                    }
231                }
232                catch (UnauthorizedAccessException)
233                {
234                    Logger.Log(S._("{0} did not have its cluster tips erased because you do not " +
235                        "have the required permissions to erase the file cluster tips.", files[i]),
236                        LogLevel.Information);
237                }
238                catch (IOException e)
239                {
240                    Logger.Log(S._("{0} did not have its cluster tips erased. The error returned " +
241                        "was: {1}", files[i], e.Message), LogLevel.Warning);
242                }
243
244                eraseCallback(i, files.Count, files[i]);
245            }
246        }
247
248        private void ListFiles(DirectoryInfo info, List<string> files,
249            ClusterTipsSearchProgress searchCallback)
250        {
251            try
252            {
253                //Skip this directory if it is a reparse point
254                if ((info.Attributes & FileAttributes.ReparsePoint) != 0)
255                {
256                    Logger.Log(S._("Files in {0} did not have their cluster tips erased because " +
257                        "it is a hard link or a symbolic link.", info.FullName),
258                        LogLevel.Information);
259                    return;
260                }
261
262                foreach (FileInfo file in info.GetFiles())
263                    if (file.IsProtectedSystemFile())
264                        Logger.Log(S._("{0} did not have its cluster tips erased, because it is " +
265                            "a system file", file.FullName), LogLevel.Information);
266                    else if ((file.Attributes & FileAttributes.ReparsePoint) != 0)
267                        Logger.Log(S._("{0} did not have its cluster tips erased because it is a " +
268                            "hard link or a symbolic link.", file.FullName), LogLevel.Information);
269                    else if ((file.Attributes & FileAttributes.Compressed) != 0 ||
270                        (file.Attributes & FileAttributes.Encrypted) != 0 ||
271                        (file.Attributes & FileAttributes.SparseFile) != 0)
272                    {
273                        Logger.Log(S._("{0} did not have its cluster tips erased because it is " +
274                            "compressed, encrypted or a sparse file.", file.FullName),
275                            LogLevel.Information);
276                    }
277                    else
278                    {
279                        try
280                        {
281                            foreach (string i in file.GetADSes())
282                                files.Add(file.FullName + ':' + i);
283
284                            files.Add(file.FullName);
285                        }
286                        catch (UnauthorizedAccessException e)
287                        {
288                            Logger.Log(S._("{0} did not have its cluster tips erased because of " +
289                                "the following error: {1}", info.FullName, e.Message),
290                                LogLevel.Information);
291                        }
292                        catch (IOException e)
293                        {
294                            Logger.Log(S._("{0} did not have its cluster tips erased because of " +
295                                "the following error: {1}", info.FullName, e.Message),
296                                LogLevel.Warning);
297                        }
298                    }
299
300                foreach (DirectoryInfo subDirInfo in info.GetDirectories())
301                {
302                    searchCallback(subDirInfo.FullName);
303                    ListFiles(subDirInfo, files, searchCallback);
304                }
305            }
306            catch (UnauthorizedAccessException e)
307            {
308                Logger.Log(S._("{0} did not have its cluster tips erased because of the " +
309                    "following error: {1}", info.FullName, e.Message), LogLevel.Information);
310            }
311            catch (IOException e)
312            {
313                Logger.Log(S._("{0} did not have its cluster tips erased because of the " +
314                    "following error: {1}", info.FullName, e.Message), LogLevel.Warning);
315            }
316        }
317
318        /// <summary>
319        /// Erases the cluster tips of the given file.
320        /// </summary>
321        /// <param name="file">The file to erase.</param>
322        /// <param name="method">The erasure method to use.</param>
323        private void EraseFileClusterTips(string file, ErasureMethod method)
324        {
325            //Get the file access times
326            StreamInfo streamInfo = new StreamInfo(file);
327            DateTime lastAccess = streamInfo.LastAccessTime;
328            DateTime lastWrite = streamInfo.LastWriteTime;
329            DateTime created = streamInfo.CreationTime;
330
331            //And get the file lengths to know how much to overwrite
332            long fileArea = GetFileArea(file);
333            long fileLength = streamInfo.Length;
334
335            //If the file length equals the file area there is no cluster tip to overwrite
336            if (fileArea == fileLength)
337                return;
338
339            //Otherwise, create the stream, lengthen the file, then tell the erasure
340            //method to erase the cluster tips.
341            try
342            {
343                using (FileStream stream = streamInfo.Open(FileMode.Open, FileAccess.Write,
344                    FileShare.None, FileOptions.WriteThrough))
345                {
346                    try
347                    {
348                        stream.SetLength(fileArea);
349                        stream.Seek(fileLength, SeekOrigin.Begin);
350
351                        //Erase the file
352                        method.Erase(stream, long.MaxValue,
353                            ManagerLibrary.Instance.PrngRegistrar[
354                                ManagerLibrary.Settings.ActivePrng],
355                            null);
356                    }
357                    finally
358                    {
359                        //Make sure the file length is restored!
360                        stream.SetLength(fileLength);
361                    }
362                }
363            }
364            finally
365            {
366                //Reset the file times
367                streamInfo.LastAccessTime = lastAccess;
368                streamInfo.LastWriteTime = lastWrite;
369                streamInfo.CreationTime = created;
370            }
371        }
372
373        public override long GetFileArea(string filePath)
374        {
375            StreamInfo info = new StreamInfo(filePath);
376            VolumeInfo volume = VolumeInfo.FromMountPoint(info.Directory.FullName);
377            long clusterSize = volume.ClusterSize;
378            return (info.Length + (clusterSize - 1)) & ~(clusterSize - 1);
379        }
380
381        /// <summary>
382        /// The minimum timestamp the file system can take. This is for secure file
383        /// deletion.
384        /// </summary>
385        protected abstract DateTime MinTimestamp { get; }
386    }
387}
Note: See TracBrowser for help on using the repository browser.