source: trunk/eraser6/Eraser.DefaultPlugins/FileSystems/Windows.cs @ 1863

Revision 1863, 10.6 KB checked in by lowjoel, 4 years ago (diff)

Forward port from Eraser 6.0: Set the file times inside a try-catch as if the file is locked an IOException will be thrown.

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