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

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

Implements #273: Use NtSetInformationFile? to deeply set file times.

  • 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.SetTimes(MinTimestamp, MinTimestamp, MinTimestamp, 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}, ", handle.Process.MainModule.FileName);
140                                    }
141                                    catch (System.ComponentModel.Win32Exception)
142                                    {
143                                        processStr.AppendFormat(
144                                            System.Globalization.CultureInfo.InvariantCulture,
145                                            "Process ID {0}, ", handle.Process.Id);
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                        case Win32ErrorCode.DiskFull:
161                            //If the disk is full, we can't do anything except manually deleting
162                            //the file, break out of this loop.
163                            i = FileNameEraseTries;
164                            break;
165
166                        default:
167                            throw;
168                    }
169                }
170            }
171
172            //Then delete the file.
173            for (int i = 0; i < FileNameEraseTries; ++i)
174                try
175                {
176                    info.Delete();
177                    break;
178                }
179                catch (IOException e)
180                {
181                    switch (System.Runtime.InteropServices.Marshal.GetLastWin32Error())
182                    {
183                        case Win32ErrorCode.AccessDenied:
184                            throw new UnauthorizedAccessException(S._("The file {0} could not " +
185                                "be erased because the file's permissions prevent access to the file.",
186                                info.FullName), e);
187
188                        case Win32ErrorCode.SharingViolation:
189                            //If after FilenameEraseTries the file is still locked, some program is
190                            //definitely using the file; throw an exception.
191                            if (i > FileNameEraseTries)
192                                throw new IOException(S._("The file {0} is currently in use and " +
193                                    "cannot be removed.", info.FullName), e);
194
195                            //Let the process locking the file release the lock
196                            Thread.Sleep(100);
197                            break;
198
199                        default:
200                            throw;
201                    }
202                }
203        }
204
205        public override void EraseClusterTips(VolumeInfo info, ErasureMethod method,
206            ClusterTipsSearchProgress searchCallback, ClusterTipsEraseProgress eraseCallback)
207        {
208            //List all the files which can be erased.
209            List<string> files = new List<string>();
210            if (!info.IsMounted)
211                throw new InvalidOperationException(S._("Could not erase cluster tips in {0} " +
212                    "as the volume is not mounted.", info.VolumeId));
213            ListFiles(info.MountPoints[0], files, searchCallback);
214
215            //For every file, erase the cluster tips.
216            for (int i = 0, j = files.Count; i != j; ++i)
217            {
218                //Get the file attributes for restoring later
219                StreamInfo streamInfo = new StreamInfo(files[i]);
220                if (!streamInfo.Exists)
221                    continue;
222
223                try
224                {
225                    //Reset the file attributes.
226                    FileAttributes fileAttr = streamInfo.Attributes;
227                    streamInfo.Attributes = FileAttributes.Normal;
228
229                    try
230                    {
231                        EraseFileClusterTips(files[i], method);
232                    }
233                    finally
234                    {
235                        streamInfo.Attributes = fileAttr;
236                    }
237                }
238                catch (UnauthorizedAccessException)
239                {
240                    Logger.Log(S._("{0} did not have its cluster tips erased because you do not " +
241                        "have the required permissions to erase the file cluster tips.", files[i]),
242                        LogLevel.Information);
243                }
244                catch (IOException e)
245                {
246                    Logger.Log(S._("{0} did not have its cluster tips erased. The error returned " +
247                        "was: {1}", files[i], e.Message), LogLevel.Warning);
248                }
249
250                eraseCallback(i, files.Count, files[i]);
251            }
252        }
253
254        private void ListFiles(DirectoryInfo info, List<string> files,
255            ClusterTipsSearchProgress searchCallback)
256        {
257            try
258            {
259                //Skip this directory if it is a reparse point
260                if ((info.Attributes & FileAttributes.ReparsePoint) != 0)
261                {
262                    Logger.Log(S._("Files in {0} did not have their cluster tips erased because " +
263                        "it is a hard link or a symbolic link.", info.FullName),
264                        LogLevel.Information);
265                    return;
266                }
267
268                foreach (FileInfo file in info.GetFiles())
269                    if (file.IsProtectedSystemFile())
270                        Logger.Log(S._("{0} did not have its cluster tips erased, because it is " +
271                            "a system file", file.FullName), LogLevel.Information);
272                    else if ((file.Attributes & FileAttributes.ReparsePoint) != 0)
273                        Logger.Log(S._("{0} did not have its cluster tips erased because it is a " +
274                            "hard link or a symbolic link.", file.FullName), LogLevel.Information);
275                    else if ((file.Attributes & FileAttributes.Compressed) != 0 ||
276                        (file.Attributes & FileAttributes.Encrypted) != 0 ||
277                        (file.Attributes & FileAttributes.SparseFile) != 0)
278                    {
279                        Logger.Log(S._("{0} did not have its cluster tips erased because it is " +
280                            "compressed, encrypted or a sparse file.", file.FullName),
281                            LogLevel.Information);
282                    }
283                    else
284                    {
285                        try
286                        {
287                            foreach (StreamInfo stream in file.GetADSes())
288                                files.Add(stream.FullName);
289
290                            files.Add(file.FullName);
291                        }
292                        catch (UnauthorizedAccessException 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.Information);
297                        }
298                        catch (IOException e)
299                        {
300                            Logger.Log(S._("{0} did not have its cluster tips erased because of " +
301                                "the following error: {1}", info.FullName, e.Message),
302                                LogLevel.Warning);
303                        }
304                    }
305
306                foreach (DirectoryInfo subDirInfo in info.GetDirectories())
307                {
308                    searchCallback(subDirInfo.FullName);
309                    ListFiles(subDirInfo, files, searchCallback);
310                }
311            }
312            catch (UnauthorizedAccessException e)
313            {
314                Logger.Log(S._("{0} did not have its cluster tips erased because of the " +
315                    "following error: {1}", info.FullName, e.Message), LogLevel.Information);
316            }
317            catch (IOException e)
318            {
319                Logger.Log(S._("{0} did not have its cluster tips erased because of the " +
320                    "following error: {1}", info.FullName, e.Message), LogLevel.Warning);
321            }
322        }
323
324        /// <summary>
325        /// Erases the cluster tips of the given file.
326        /// </summary>
327        /// <param name="file">The file to erase.</param>
328        /// <param name="method">The erasure method to use.</param>
329        private void EraseFileClusterTips(string file, ErasureMethod method)
330        {
331            //Get the file access times
332            StreamInfo streamInfo = new StreamInfo(file);
333            DateTime lastAccess = streamInfo.LastAccessTime;
334            DateTime lastWrite = streamInfo.LastWriteTime;
335            DateTime created = streamInfo.CreationTime;
336
337            //And get the file lengths to know how much to overwrite
338            long fileArea = GetFileArea(file);
339            long fileLength = streamInfo.Length;
340
341            //If the file length equals the file area there is no cluster tip to overwrite
342            if (fileArea == fileLength)
343                return;
344
345            //Otherwise, create the stream, lengthen the file, then tell the erasure
346            //method to erase the cluster tips.
347            try
348            {
349                using (FileStream stream = streamInfo.Open(FileMode.Open, FileAccess.Write,
350                    FileShare.None, FileOptions.WriteThrough))
351                {
352                    try
353                    {
354                        stream.SetLength(fileArea);
355                        stream.Seek(fileLength, SeekOrigin.Begin);
356
357                        //Erase the file
358                        method.Erase(stream, long.MaxValue,
359                            ManagerLibrary.Instance.PrngRegistrar[
360                                ManagerLibrary.Settings.ActivePrng],
361                            null);
362                    }
363                    finally
364                    {
365                        //Make sure the file length is restored!
366                        stream.SetLength(fileLength);
367                    }
368                }
369            }
370            finally
371            {
372                //Reset the file times
373                streamInfo.SetTimes(MinTimestamp, created, lastWrite, lastAccess);
374            }
375        }
376
377        public override long GetFileArea(string filePath)
378        {
379            StreamInfo info = new StreamInfo(filePath);
380            VolumeInfo volume = VolumeInfo.FromMountPoint(info.Directory.FullName);
381            long clusterSize = volume.ClusterSize;
382            return (info.Length + (clusterSize - 1)) & ~(clusterSize - 1);
383        }
384
385        /// <summary>
386        /// The minimum timestamp the file system can take. This is for secure file
387        /// deletion.
388        /// </summary>
389        protected abstract DateTime MinTimestamp { get; }
390    }
391}
Note: See TracBrowser for help on using the repository browser.