/// <summary> /// Returns the ADSes of the given file, forcing open handles to the files closed /// if necessary. /// </summary> /// <param name="file">The file to look for ADSes.</param> /// <param name="attempts">The number of tries to close open handles. Abort after a /// large number of tries (currently 10 attempts)</param> /// <returns>The list of ADSes the file contains.</returns> private static StreamInfo[] GetPathADSes(FileInfo file, ref int attempts) { try { return(file.GetADSes().ToArray()); } catch (FileNotFoundException) { return(new StreamInfo[0]); } catch (SharingViolationException) { //The system cannot open the file, try to force the file handle to close. if (!Host.Instance.Settings.ForceUnlockLockedFiles) { throw; } //Retry closing the file 10 times. If we can't do that, we should abort //since we may not be able to get the process information of processes //running with higher privileges. if (OpenHandle.Close(file.FullName).Count == 0 && ++attempts <= 10) { return(GetPathADSes(file, ref attempts)); } else { throw; } } catch (UnauthorizedAccessException e) { //The system cannot read the file, assume no ADSes for lack of //more information. Logger.Log(e.Message, LogLevel.Error); } return(new StreamInfo[0]); }
/// <summary> /// Obfuscates the file name and file attributes of the given file. /// </summary> /// <param name="info">The file to obfuscate.</param> private void ObfuscateFileSystemInfoName(FileSystemInfo info) { //If the file/directory doesn't exist, pass. if (!info.Exists) { return; } //Reset the file attributes to non-content indexed so indexing //services will not lock the file. try { info.Attributes = FileAttributes.NotContentIndexed; } catch (ArgumentException e) { //This is an undocumented exception: when the path we are setting //cannot be accessed (ERROR_ACCESS_DENIED is returned) an //ArgumentException is raised (no idea why!) throw new UnauthorizedAccessException(e.Message, e); } //Rename the file a few times to erase the entry from the file system //table. for (int i = 0, tries = 0; i < FileNameErasePasses; ++tries) { //Generate a new file name for the file/directory. string newPath = GenerateRandomFileName(info.GetParent(), info.Name.Length); try { ResetFileTimes(info); //Try to rename the file. If it fails, it is probably due to another //process locking the file. Defer, then rename again. info.MoveTo(newPath); ++i; } catch (IOException e) { switch (System.Runtime.InteropServices.Marshal.GetLastWin32Error()) { case Win32ErrorCode.AccessDenied: throw new UnauthorizedAccessException(S._("The file {0} could not " + "be erased because the file's permissions prevent access to the file.", info.FullName)); case Win32ErrorCode.SharingViolation: //If after FilenameEraseTries the file is still locked, some program is //definitely using the file; throw an exception. if (tries > FileNameEraseTries) { //Try to force the handle closed. if (tries > FileNameEraseTries + 1 || !Host.Instance.Settings.ForceUnlockLockedFiles) { throw new IOException(S._("The file {0} is currently in use " + "and cannot be removed.", info.FullName), e); } //Either we could not close all instances, or we already tried twice. Report //the error. string processes = string.Empty; { StringBuilder processStr = new StringBuilder(); foreach (OpenHandle handle in OpenHandle.Close(info.FullName)) { try { processStr.AppendFormat( System.Globalization.CultureInfo.InvariantCulture, "{0}, ", handle.Process.MainModule.FileName); } catch (System.ComponentModel.Win32Exception) { processStr.AppendFormat( System.Globalization.CultureInfo.InvariantCulture, "Process ID {0}, ", handle.Process.Id); } } if (processStr.Length > 2) { processes = processStr.ToString().Remove(processStr.Length - 2).Trim(); } else { processes = S._("(unknown)"); } } throw new SharingViolationException(S._( "Could not force closure of file \"{0}\" {1}", info.FullName, S._("(locked by {0})", processes))); } //Let the process locking the file release the lock Thread.Sleep(100); break; case Win32ErrorCode.DiskFull: //If the disk is full, we can't do anything except manually deleting //the file, break out of this loop. i = FileNameEraseTries; break; default: throw; } } } }
/// <summary> /// Attempts to erase a stream, trying to close all open handles if a process has /// a lock on the file. /// </summary> /// <param name="fsManager">The file system provider used to erase the stream.</param> /// <param name="method">The erasure method to use to erase the stream.</param> /// <param name="info">The stream to erase.</param> /// <param name="callback">The erasure progress callback.</param> private void TryEraseStream(IFileSystem fsManager, IErasureMethod method, StreamInfo info, ErasureMethodProgressFunction callback) { for (int i = 0; ; ++i) { try { //Make sure the file does not have any attributes which may affect //the erasure process if ((info.Attributes & FileAttributes.Compressed) != 0 || (info.Attributes & FileAttributes.Encrypted) != 0 || (info.Attributes & FileAttributes.SparseFile) != 0) { //Log the error Logger.Log(S._("The file {0} could not be erased because the file was " + "either compressed, encrypted or a sparse file.", info.FullName), LogLevel.Error); return; } //Do not erase reparse points, as they will cause other references to the file //to be to garbage. if ((info.Attributes & FileAttributes.ReparsePoint) == 0) { fsManager.EraseFileSystemObject(info, method, callback); } else { Logger.Log(S._("The file {0} is a hard link or a symbolic link thus the " + "contents of the file was not erased.", info.FullName), LogLevel.Notice); } return; } catch (FileNotFoundException) { Logger.Log(S._("The file {0} was not erased because it was " + "deleted before it could be erased.", info.FullName), LogLevel.Information); } catch (DirectoryNotFoundException) { Logger.Log(S._("The file {0} was not erased because the containing " + "directory was deleted before it could be erased.", info.FullName), LogLevel.Information); } catch (SharingViolationException) { if (!Host.Instance.Settings.ForceUnlockLockedFiles) { throw; } //Try closing all open handles. If it succeeds, we can run the erase again. //To prevent Eraser from deadlocking, we will only attempt this once. Some //programs may be aggressive and keep a handle open in a tight loop. List <OpenHandle> remainingHandles = OpenHandle.Close(info.FullName); if (i == 0 && remainingHandles.Count == 0) { continue; } //Either we could not close all instances, or we already tried twice. Report //the error. string processes = string.Empty; { StringBuilder processStr = new StringBuilder(); foreach (OpenHandle handle in remainingHandles) { try { processStr.AppendFormat( System.Globalization.CultureInfo.InvariantCulture, "{0}, ", handle.Process.MainModule.FileName); } catch (System.ComponentModel.Win32Exception) { processStr.AppendFormat( System.Globalization.CultureInfo.InvariantCulture, "Process ID {0}, ", handle.Process.Id); } } if (processStr.Length > 2) { processes = processStr.ToString().Remove(processStr.Length - 2).Trim(); } else { processes = S._("(unknown)"); } } throw new SharingViolationException(S._( "Could not force closure of file \"{0}\" {1}", info.FileName, S._("(locked by {0})", processes))); } } }