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

Revision 1866, 10.9 KB checked in by lowjoel, 4 years ago (diff)

When deleting files which do not belong to the current user an ArgumentException? is raised. Handle this situation.

  • 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            //Reset the file attributes to non-content indexed so indexing
79            //services will not lock the file.
80            try
81            {
82                info.Attributes = FileAttributes.NotContentIndexed;
83            }
84            catch (ArgumentException e)
85            {
86                //This is an undocumented exception: when the path we are setting
87                //cannot be accessed (ERROR_ACCESS_DENIED is returned) an
88                //ArgumentException is raised (no idea why!)
89                throw new UnauthorizedAccessException(e.Message, e);
90            }
91
92            //Rename the file a few times to erase the entry from the file system
93            //table.
94            for (int i = 0, tries = 0; i < FileNameErasePasses; ++tries)
95            {
96                //Generate a new file name for the file/directory.
97                string newPath = GenerateRandomFileName(info.GetParent(), info.Name.Length);
98
99                try
100                {
101                    //Reset the file access times: after every rename the file times may change.
102                    info.CreationTime = info.LastWriteTime = info.LastAccessTime = MinTimestamp;
103
104                    //Try to rename the file. If it fails, it is probably due to another
105                    //process locking the file. Defer, then rename again.
106                    info.MoveTo(newPath);
107                    ++i;
108                }
109                catch (IOException e)
110                {
111                    switch (System.Runtime.InteropServices.Marshal.GetLastWin32Error())
112                    {
113                        case Win32ErrorCode.AccessDenied:
114                            throw new UnauthorizedAccessException(S._("The file {0} could not " +
115                                "be erased because the file's permissions prevent access to the file.",
116                                info.FullName));
117
118                        case Win32ErrorCode.SharingViolation:
119                            //If after FilenameEraseTries the file is still locked, some program is
120                            //definitely using the file; throw an exception.
121                            if (tries > FileNameEraseTries)
122                                throw new IOException(S._("The file {0} is currently in use and " +
123                                    "cannot be removed.", info.FullName), e);
124
125                            //Let the process locking the file release the lock
126                            Thread.Sleep(100);
127                            break;
128
129                        default:
130                            throw;
131                    }
132                }
133            }
134
135            //Then delete the file.
136            for (int i = 0; i < FileNameEraseTries; ++i)
137                try
138                {
139                    info.Delete();
140                    break;
141                }
142                catch (IOException e)
143                {
144                    switch (System.Runtime.InteropServices.Marshal.GetLastWin32Error())
145                    {
146                        case Win32ErrorCode.AccessDenied:
147                            throw new UnauthorizedAccessException(S._("The file {0} could not " +
148                                "be erased because the file's permissions prevent access to the file.",
149                                info.FullName), e);
150
151                        case Win32ErrorCode.SharingViolation:
152                            //If after FilenameEraseTries the file is still locked, some program is
153                            //definitely using the file; throw an exception.
154                            if (i > FileNameEraseTries)
155                                throw new IOException(S._("The file {0} is currently in use and " +
156                                    "cannot be removed.", info.FullName), e);
157
158                            //Let the process locking the file release the lock
159                            Thread.Sleep(100);
160                            break;
161
162                        default:
163                            throw;
164                    }
165                }
166        }
167
168        public override void EraseClusterTips(VolumeInfo info, ErasureMethod method,
169            ClusterTipsSearchProgress searchCallback, ClusterTipsEraseProgress eraseCallback)
170        {
171            //List all the files which can be erased.
172            List<string> files = new List<string>();
173            if (!info.IsMounted)
174                throw new InvalidOperationException(S._("Could not erase cluster tips in {0} " +
175                    "as the volume is not mounted.", info.VolumeId));
176            ListFiles(new DirectoryInfo(info.MountPoints[0]), files, searchCallback);
177
178            //For every file, erase the cluster tips.
179            for (int i = 0, j = files.Count; i != j; ++i)
180            {
181                //Get the file attributes for restoring later
182                StreamInfo streamInfo = new StreamInfo(files[i]);
183                if (!streamInfo.Exists)
184                    continue;
185
186                FileAttributes fileAttr = streamInfo.Attributes;
187
188                try
189                {
190                    //Reset the file attributes.
191                    streamInfo.Attributes = FileAttributes.Normal;
192                    EraseFileClusterTips(files[i], method);
193                }
194                catch (UnauthorizedAccessException)
195                {
196                    Logger.Log(S._("{0} did not have its cluster tips erased because you do not " +
197                        "have the required permissions to erase the file cluster tips.", files[i]),
198                        LogLevel.Information);
199                }
200                catch (IOException e)
201                {
202                    Logger.Log(S._("{0} did not have its cluster tips erased. The error returned " +
203                        "was: {1}", files[i], e.Message), LogLevel.Error);
204                }
205                finally
206                {
207                    streamInfo.Attributes = fileAttr;
208                }
209
210                eraseCallback(i, files.Count, files[i]);
211            }
212        }
213
214        private void ListFiles(DirectoryInfo info, List<string> files,
215            ClusterTipsSearchProgress searchCallback)
216        {
217            try
218            {
219                //Skip this directory if it is a reparse point
220                if ((info.Attributes & FileAttributes.ReparsePoint) != 0)
221                {
222                    Logger.Log(S._("Files in {0} did not have their cluster tips erased because " +
223                        "it is a hard link or a symbolic link.", info.FullName),
224                        LogLevel.Information);
225                    return;
226                }
227
228                foreach (FileInfo file in info.GetFiles())
229                    if (file.IsProtectedSystemFile())
230                        Logger.Log(S._("{0} did not have its cluster tips erased, because it is " +
231                            "a system file", file.FullName), LogLevel.Information);
232                    else if ((file.Attributes & FileAttributes.ReparsePoint) != 0)
233                        Logger.Log(S._("{0} did not have its cluster tips erased because it is a " +
234                            "hard link or a symbolic link.", file.FullName), LogLevel.Information);
235                    else if ((file.Attributes & FileAttributes.Compressed) != 0 ||
236                        (file.Attributes & FileAttributes.Encrypted) != 0 ||
237                        (file.Attributes & FileAttributes.SparseFile) != 0)
238                    {
239                        Logger.Log(S._("{0} did not have its cluster tips erased because it is " +
240                            "compressed, encrypted or a sparse file.", file.FullName),
241                            LogLevel.Information);
242                    }
243                    else
244                    {
245                        try
246                        {
247                            foreach (string i in file.GetADSes())
248                                files.Add(file.FullName + ':' + i);
249
250                            files.Add(file.FullName);
251                        }
252                        catch (UnauthorizedAccessException e)
253                        {
254                            Logger.Log(S._("{0} did not have its cluster tips erased because of " +
255                                "the following error: {1}", info.FullName, e.Message),
256                                LogLevel.Error);
257                        }
258                        catch (IOException e)
259                        {
260                            Logger.Log(S._("{0} did not have its cluster tips erased because of " +
261                                "the following error: {1}", info.FullName, e.Message),
262                                LogLevel.Error);
263                        }
264                    }
265
266                foreach (DirectoryInfo subDirInfo in info.GetDirectories())
267                {
268                    searchCallback(subDirInfo.FullName);
269                    ListFiles(subDirInfo, files, searchCallback);
270                }
271            }
272            catch (UnauthorizedAccessException e)
273            {
274                Logger.Log(S._("{0} did not have its cluster tips erased because of the " +
275                    "following error: {1}", info.FullName, e.Message), LogLevel.Error);
276            }
277            catch (IOException e)
278            {
279                Logger.Log(S._("{0} did not have its cluster tips erased because of the " +
280                    "following error: {1}", info.FullName, e.Message), LogLevel.Error);
281            }
282        }
283
284        /// <summary>
285        /// Erases the cluster tips of the given file.
286        /// </summary>
287        /// <param name="file">The file to erase.</param>
288        /// <param name="method">The erasure method to use.</param>
289        private void EraseFileClusterTips(string file, ErasureMethod method)
290        {
291            //Get the file access times
292            StreamInfo streamInfo = new StreamInfo(file);
293            DateTime lastAccess = streamInfo.LastAccessTime;
294            DateTime lastWrite = streamInfo.LastWriteTime;
295            DateTime created = streamInfo.CreationTime;
296
297            //And get the file lengths to know how much to overwrite
298            long fileArea = GetFileArea(file);
299            long fileLength = streamInfo.Length;
300
301            //If the file length equals the file area there is no cluster tip to overwrite
302            if (fileArea == fileLength)
303                return;
304
305            //Otherwise, create the stream, lengthen the file, then tell the erasure
306            //method to erase the cluster tips.
307            using (FileStream stream = streamInfo.Open(FileMode.Open, FileAccess.Write,
308                FileShare.None, FileOptions.WriteThrough))
309            {
310                try
311                {
312                    stream.SetLength(fileArea);
313                    stream.Seek(fileLength, SeekOrigin.Begin);
314
315                    //Erase the file
316                    method.Erase(stream, long.MaxValue,
317                        ManagerLibrary.Instance.PrngRegistrar[ManagerLibrary.Settings.ActivePrng],
318                        null);
319                }
320                finally
321                {
322                    //Make sure the file length is restored!
323                    stream.SetLength(fileLength);
324
325                    //Reset the file times
326                    streamInfo.LastAccessTime = lastAccess;
327                    streamInfo.LastWriteTime = lastWrite;
328                    streamInfo.CreationTime = created;
329                }
330            }
331        }
332
333        public override long GetFileArea(string filePath)
334        {
335            StreamInfo info = new StreamInfo(filePath);
336            VolumeInfo volume = VolumeInfo.FromMountPoint(info.Directory.FullName);
337            long clusterSize = volume.ClusterSize;
338            return (info.Length + (clusterSize - 1)) & ~(clusterSize - 1);
339        }
340
341        /// <summary>
342        /// The minimum timestamp the file system can take. This is for secure file
343        /// deletion.
344        /// </summary>
345        protected abstract DateTime MinTimestamp { get; }
346    }
347}
Note: See TracBrowser for help on using the repository browser.