File.Exists() incorrectly returns false when path is too long

后端 未结 5 584
无人及你
无人及你 2020-12-06 10:48

I am currently working on a program that traverses through various directories to ensure that specific files are present by using File.Exists().

The app

相关标签:
5条回答
  • 2020-12-06 10:52

    You need to P/Invoke Win32 APIs to get this to work properly:

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        static extern uint GetFileAttributes(string lpFileName);
    
        public static bool DirectoryExists(string path)
        {
            uint attributes = GetFileAttributes(path.StartsWith(@"\\?\") ? path : @"\\?\" + path);
            if (attributes != 0xFFFFFFFF)
            {
                return ((FileAttributes)attributes).HasFlag(FileAttributes.Directory);
            }
            else
            {
                return false;
            }
        }
    
        public static bool FileExists(string path)
        {
            uint attributes = GetFileAttributes(path.StartsWith(@"\\?\") ? path : @"\\?\" + path);
            if (attributes != 0xFFFFFFFF)
            {
                return !((FileAttributes)attributes).HasFlag(FileAttributes.Directory);
            }
            else
            {
                return false;
            }
        }
    
    0 讨论(0)
  • 2020-12-06 10:59

    This is ugly and inefficient, but it DOES get around the MAX_PATH limitation:

    const int MAX_PATH = 260;
    
    private static void checkPath(string path)
    {
        if (path.Length >= MAX_PATH)
        {
            checkFile_LongPath(path);
        }
        else if (!File.Exists(path))
        {
            Console.WriteLine("   *  File: " + path + " does not exist.");
        }
    }
    

    And here is the checkFile_LongPath function:

    private static void checkFile_LongPath(string path)
    {
        string[] subpaths = path.Split('\\');
        StringBuilder sbNewPath = new StringBuilder(subpaths[0]);
        // Build longest subpath that is less than MAX_PATH characters
        for (int i = 1; i < subpaths.Length; i++)
        {
            if (sbNewPath.Length + subpaths[i].Length >= MAX_PATH)
            {
                subpaths = subpaths.Skip(i).ToArray();
                break;
            }
            sbNewPath.Append("\\" + subpaths[i]);
        }
        DirectoryInfo dir = new DirectoryInfo(sbNewPath.ToString());
        bool foundMatch = dir.Exists;
        if (foundMatch)
        {
            // Make sure that all of the subdirectories in our path exist.
            // Skip the last entry in subpaths, since it is our filename.
            // If we try to specify the path in dir.GetDirectories(), 
            // We get a max path length error.
            int i = 0;
            while(i < subpaths.Length - 1 && foundMatch)
            {
                foundMatch = false;
                foreach (DirectoryInfo subDir in dir.GetDirectories())
                {
                    if (subDir.Name == subpaths[i])
                    {
                        // Move on to the next subDirectory
                        dir = subDir;
                        foundMatch = true;
                        break;
                    }
                }
                i++;
            }
            if (foundMatch)
            {
                foundMatch = false;
                // Now that we've gone through all of the subpaths, see if our file exists.
                // Once again, If we try to specify the path in dir.GetFiles(), 
                // we get a max path length error.
                foreach (FileInfo fi in dir.GetFiles())
                {
                    if (fi.Name == subpaths[subpaths.Length - 1])
                    {
                        foundMatch = true;
                        break;
                    }
                }
            }
        }
        // If we didn't find a match, write to the console.
        if (!foundMatch)
        {
            Console.WriteLine("   *  File: " + path + " does not exist.");
        }
    }
    
    0 讨论(0)
  • 2020-12-06 11:08

    Check

    1. Manifest permissions
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    
    1. Use file provider to create and access file
        <provider
                android:name="androidx.core.content.FileProvider"
                android:authorities="your_package.fileprovider"
                android:exported="false"
                android:grantUriPermissions="true">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/file_list" />
        </provider>
    
    1. file_list content:
        <?xml version="1.0" encoding="utf-8"?>
        <paths>
            <external-path
                name="external"
                path="." />
            <external-files-path
                name="external_files"
                path="." />
            <cache-path
                name="cache"
                path="." />
            <external-cache-path
                name="external_cache"
                path="." />
            <files-path
                name="files"
                path="." />
        </paths>
    
    1. Keep your filename short
    0 讨论(0)
  • 2020-12-06 11:12

    Never had the issue myself, someone on another SO post suggests opening a handle to the file, thus avoiding the whole 'exists' check in the first place. Not sure if this still has the 'long filename' issue:

    It's the second answer here:

    Check if a file/directory exists: is there a better way?

    Not sure if that's useful :P

    0 讨论(0)
  • 2020-12-06 11:16

    From MSDN - Naming Files, Paths, and Namespaces:

    In the Windows API (with some exceptions discussed in the following paragraphs), the maximum length for a path is MAX_PATH, which is defined as 260 characters.

    ...

    The Windows API has many functions that also have Unicode versions to permit an extended-length path for a maximum total path length of 32,767 characters. This type of path is composed of components separated by backslashes, each up to the value returned in the lpMaximumComponentLength parameter of the GetVolumeInformation function (this value is commonly 255 characters). To specify an extended-length path, use the "\\?\" prefix. For example, "\\?\D:\very long path".

    ...

    Because you cannot use the "\\?\" prefix with a relative path, relative paths are always limited to a total of MAX_PATH characters.

    (Emphasis added)

    If all your paths are full paths, you could update your code to use the extended-length path specifier as follows:

    const longPathSpecifier = @"\\?";
    
    private void checkFile(string path)
    {
        // Add the long-path specifier if it's missing
        string longPath = (path.StartsWith(longPathSpecifier) ? path : longPathSpecifier  + path);
    
        if (!File.Exists(longPath))
        {
            // Print the original path
             Console.WriteLine("   *  File: " + path + " does not exist.");
        }
    }
    

    Update:

    For file I/O, the "\?\" prefix to a path string tells the Windows APIs to disable all string parsing and to send the string that follows it straight to the file system. For example, if the file system supports large paths and file names, you can exceed the MAX_PATH limits that are otherwise enforced by the Windows APIs.

    At least on my system (using Windows 7), long file names are not supported, so I can't verify if the above solution will work for you.

    Update: I found a solution that does work, but it is fairly ugly. Here's what I did in pseudo-code:

    1. Split the path into an array of directories
    2. Get the longest portion of your path that is less than 260 characters (MAX_PATH).
    3. Create a DirectoryInfo for that portion of your path ("dir" for future reference).
    4. For the remaining directories in your path:
      a. Call dir.GetDirectories() and check if the next directory is contained in the results
      b. if so, set dir to that DirectoryInfo and keep digging
      c. if not, then the path doesn't exist
    5. Once we've gone through all of the directories leading up to our file, call dir.GetFiles() and see if our file exists in the returned FileInfo objects.
    0 讨论(0)
提交回复
热议问题