【问题标题】:DirectoryInfo.EnumerateFiles(...) causes UnauthorizedAccessException (and other exceptions)DirectoryInfo.EnumerateFiles(...) 导致 UnauthorizedAccessException (和其他异常)
【发布时间】:2012-10-29 21:37:52
【问题描述】:

我最近需要枚举整个文件系统以查找特定类型的文件以进行审计。由于对要扫描的文件系统的权限有限,这导致我遇到了几个异常。其中,最流行的是UnauthorizedAccessException,让我很懊恼的是PathTooLongException

这些通常不是问题,只是它们使 IEnumerable 无效,使我无法完成扫描。

【问题讨论】:

  • 我只想说我认为上面 Brubaker 先生提供的 FileSystemEnumerable 课程绝对精彩,所以我想鼓励进一步使用/开发它。为此,我将其发布为仅包含一个文件的 GitHub 存储库,以及 README.md。我链接回这个 StackOverflow 帖子。请在以下位置找到课程:github.com/astrohart/FileSystemEnumerable 欢迎所有人 fork 回购/提交问题/创建拉取请求。感谢大家!布赖恩哈特

标签: c# algorithm .net-4.0


【解决方案1】:

为了解决这个问题,我创建了一个替代文件系统枚举器。尽管它可能并不完美,但它的执行速度相当快,并捕获了我遇到的两个异常。它将查找与传递给它的搜索模式匹配的任何目录或文件。

// This code is public domain
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using log4net;

public class FileSystemEnumerable : IEnumerable<FileSystemInfo>
{
    private ILog _logger = LogManager.GetLogger(typeof(FileSystemEnumerable));

    private readonly DirectoryInfo _root;
    private readonly IList<string> _patterns;
    private readonly SearchOption _option;

    public FileSystemEnumerable(DirectoryInfo root, string pattern, SearchOption option)
    {
        _root = root;
        _patterns = new List<string> { pattern };
        _option = option;
    }

    public FileSystemEnumerable(DirectoryInfo root, IList<string> patterns, SearchOption option)
    {
        _root = root;
        _patterns = patterns;
        _option = option;
    }

    public IEnumerator<FileSystemInfo> GetEnumerator()
    {
        if (_root == null || !_root.Exists) yield break;

        IEnumerable<FileSystemInfo> matches = new List<FileSystemInfo>();
        try
        {
            _logger.DebugFormat("Attempting to enumerate '{0}'", _root.FullName);
            foreach (var pattern in _patterns)
            {
                _logger.DebugFormat("Using pattern '{0}'", pattern);
                matches = matches.Concat(_root.EnumerateDirectories(pattern, SearchOption.TopDirectoryOnly))
                                 .Concat(_root.EnumerateFiles(pattern, SearchOption.TopDirectoryOnly));
            }
        }
        catch (UnauthorizedAccessException)
        {
            _logger.WarnFormat("Unable to access '{0}'. Skipping...", _root.FullName);
            yield break;
        }
        catch (PathTooLongException ptle)
        {
            _logger.Warn(string.Format(@"Could not process path '{0}\{1}'.", _root.Parent.FullName, _root.Name), ptle);
            yield break;
        } catch (System.IO.IOException e)
        {
            // "The symbolic link cannot be followed because its type is disabled."
            // "The specified network name is no longer available."
            _logger.Warn(string.Format(@"Could not process path (check SymlinkEvaluation rules)'{0}\{1}'.", _root.Parent.FullName, _root.Name), e);
            yield break;
        }


        _logger.DebugFormat("Returning all objects that match the pattern(s) '{0}'", string.Join(",", _patterns));
        foreach (var file in matches)
        {
            yield return file;
        }

        if (_option == SearchOption.AllDirectories)
        {
            _logger.DebugFormat("Enumerating all child directories.");
            foreach (var dir in _root.EnumerateDirectories("*", SearchOption.TopDirectoryOnly))
            {
                _logger.DebugFormat("Enumerating '{0}'", dir.FullName);
                var fileSystemInfos = new FileSystemEnumerable(dir, _patterns, _option);
                foreach (var match in fileSystemInfos)
                {
                    yield return match;
                }
            }
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

用法相当简单。

//This code is public domain
var root = new DirectoryInfo(@"c:\wherever");
var searchPattern = @"*.txt";
var searchOption = SearchOption.AllDirectories;
var enumerable = new FileSystemEnumerable(root, searchPattern, searchOption);

如果人们觉得它有用,可以免费使用它。

【讨论】:

  • "如果人们觉得它有用,可以免费使用它。" : 这不完全正确。您在 CC BY SA 3(知识共享)下发挥作用,这使得它实际上使用起来很微妙。您可以在注释中的代码 sn-p 中明确声明“公共领域”(copyleft)或 zlib 许可证(最弱的版权许可证)。谢谢。
  • 我断言,截至 2015 年 7 月 14 日,上述答案中的代码是具有所有权利和特权的公共领域。
  • 我发现我需要捕获System.IO.IOException,以应对我正在使用具有远程到本地 simlink 目录的网络驱动器的情况,该目录具有这样的 simlink 扩展规则 disabled on current machine。我相应地调整了你的答案。如果遇到指向祖先文件夹的simlink,它也会无限递归;就我而言,我只是忽略了属性为FileAttributes.ReparsePoint 的目录,但这对于一般答案可能不够优雅
  • 这对我很有帮助。恕我直言,我确实做了一个小的改进,将多个构造函数减少到一个同样简单的版本。 ` public FileSystemEnumerable(DirectoryInfo root, SearchOption option, params string[] patterns) { _root = root; _patterns = patterns.ToList(); _option = 选项; }`
  • 通过将_root.EnumerateDirectories()_root.EnumerateFiles() 链接起来(强制将输出按类型“分组”在一起?)加上在GetEnumerator() 末尾对_root.EnumerateDirectories() 的另一个调用,在低级别我相信这会导致它为目录中的每个对象FindNextFile() 三次,这对于具有大量子目录的目录来说是不利的。这是因为所有三个Enumerate*s() 方法都会执行完整目录扫描,而不管搜索对象类型如何
【解决方案2】:

这是另一种方式,管理您自己的枚举迭代:

IEnumerator<string> errFiles=Directory.EnumerateFiles(baseDir, "_error.txt", SearchOption.AllDirectories).GetEnumerator();
while (true)
{
    try
    {
        if (!errFiles.MoveNext())
            break;
        string errFile = errFiles.Current;
        // processing
    } catch (Exception e)
    {
        log.Warn("Ignoring error finding in: " + baseDir, e);
    }
}

【讨论】:

  • 我已经有一段时间没有解决这个问题了,但如果我没记错的话,问题是 Directory.EnumerateFiles(...) 抛出异常,因为它之前会预先检查目录中的所有内容返回枚举器。
  • 不是这种情况,我很确定。使用 GetFiles() 时会出现这种情况,因为所有文件都被迭代和收集,但使用 EnumerateFiles(),权限断言单独发生在 MoveNext() 上。由于try and while,错误导致记录并移动到下一个。
  • 啊,是的,我相信您在解决权限问题方面是正确的。尽管据我所知,它们并没有捕获在 MoveNext() 期间可能发生的所有错误。例如 PathTooLongException 不会被自动捕获和处理。我对文件系统枚举器的内部实现的理解是,任何确实发生但未在枚举器实例中处理的异常都会导致它失效。也就是说,自从我深入研究并验证这种行为以来已经有好几年了。
猜你喜欢
  • 1970-01-01
  • 2012-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-21
  • 1970-01-01
相关资源
最近更新 更多