peteraritchie / longpath Goto Github PK
View Code? Open in Web Editor NEWdrop-in library to support long paths in .NET
License: GNU Lesser General Public License v3.0
drop-in library to support long paths in .NET
License: GNU Lesser General Public License v3.0
If you invoke Directory.CreateDirectory, passing it a UNC path that is MAX_PATH (i.e. 260) characters long, it works fine. If you instead pass a UNC path that is MAX_PATH - 1 (i.e. 259) characters long, it throws a System.IO.PathTooLongException.
To allow organizations which require strong-named assemblies so they can use code-signing for their internal projects to consume Pri.LongPath, consider strong-naming the package.
Looks like you packaged the wrong assembly...
It happens to me all the time: I test the Debug configuration, and forget to rebuild the Release configuration before I package ;)
If I have an existing folder path that ends with a slash (e.g. "C:\test\folder") then the static method Directory.Exists(@"C:\test\folder") will find the folder and return true.
However, the instance property new Directory(@"C:\test\folder").Exists always seems to return false. If I remove the final slash from the end then it finds the folder and returns true.
This is easy to work around (always use the Static method), but I would have expected both the instance property and static method to behave the same (as they do in System.IO.Directory).
need a nuget package
I'm trying your library.
The application is a bit special: it's a namespace extension.
At some place, I've this method:
public string CreateFolder(string mirror)
{
var local = Path.Combine(this._rootFolder, handleInvalidCharacters(mirror));
if (!Directory.Exists(local))
Directory.CreateDirectory(local);
return local;
}
This method is called maybe 10x in parallel by windows.
The Directory.Exists return true ... but the folder doesn't exists. It's never created.
In VS if I type these commands::
Pri.LongPath.Directory.Exists(local)
true
System.IO.Directory.Exists(local)
false
If I try in a "safe" environment with one simple execution, the method correctly return false
Any idea ?
Thank you
Directory.Delete(string,bool) follows the reparse point, which is wrong.
System.IO.Directory.Delete:
"The behavior of this method differs slightly when deleting a directory that contains a reparse point, such as a symbolic link or a mount point. If the reparse point is a directory, such as a mount point, it is unmounted and the mount point is deleted. This method does not recurse through the reparse point. If the reparse point is a symbolic link to a file, the reparse point is deleted and not the target of the symbolic link. " https://msdn.microsoft.com/en-us/library/fxeahc5f%28v=vs.110%29.aspx
Fast fix would be:
public static void Delete(string path, bool recursive) {
var isReparsePoint=(new DirectoryInfo(path).Attributes & System.IO.FileAttributes.ReparsePoint) != 0;
if (isReparsePoint) { Delete(path); return;}
...
}
And by the way parameter "recursive" is never used!
If I run DirectoryInfo.Exists on the existing root of a FAT32 formatted drive it returns false.
The DirectoryInfo.SystemInfo.Exists return true. Also Directory.Exists(DirectoryInfo.FullName) returns true.
I only have this issue on FAT32.
When trying to compile a project referencing 'Pri.LongPath' installed from Nuget I get a compiliation error:
Referenced assembly 'Pri.LongPath' does not have a strong name
Can you please consider strong naming the 'Pri.LongPath'. Thanks!
You have the following in Directory.cs:
// To mimic Directory.CreateDirectory, we don't throw if the directory (not a file) already exists
...
// PR: Not sure this is even possible, we check for existance above.
You've obviously decided that it's not possible, and have commented out the code that deals with this (sorry I can't paste it here from work - my HTTP proxy blocks stuff that looks like code).
What happens if two people call this method at once from different threads?
This then throws, which it should not. :-(
In v2.0.0-beta the DirectoryInfo.EnumerateFiles()
method is no longer available from an instance of DirectoryInfo
class for projects that are .Net 4.5+
This worked fine in v1.3.2 and emulated the behaviour of System.IO
returning IEnumerable<FileInfo>
From my testing (using VS 2015 and the nuget prelease) it still works fine in .Net 4.0
Path.GetPathRoot(@"\\server\foo\bar\baz.txt")
should return \\server\foo
, but it returns \\
instead
System.IO.FileNotFoundException: File D:\ not found
File name: 'D:\'
at Pri.LongPath.Common.ThrowIOError(Int32 errorCode, String maybeFullPath)
at Pri.LongPath.FileSystemInfo.get_LastWriteTimeUtc()
at Pri.LongPath.FileSystemInfo.get_LastWriteTime()
at UserQuery.Main() in c:\Users\Tom\AppData\Local\Temp\LINQPad\_himmmwze\query_ijinft.cs:line 56
The same error occurs for all the creation/modification/las access properties.
In System.IO.DirectoryInfo, it works as expected.
class File has general purpose methods which are supposed to substitute some System.IO constructors.
Unfortunaltely they are INTERNAL and therefore not usable in the sence they are designed for.
Since File.cs ist static we even cant use inheritance to make them public.
Please help by making those methods in File.cs PUBLIC
public static class File
internal static StreamReader CreateStreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
internal static StreamWriter CreateStreamWriter(string path, bool append)
internal static StreamWriter CreateStreamWriter(string path, bool append, Encoding encoding)
internal static StreamWriter CreateText(string path, Encoding encoding)
Kind regards
efef77
input: ..\..\paket-files\fsharp\FSharp.Data\src\CommonRuntime\TextRuntime.fs
.net: ..\..\paket-files\fsharp\FSharp.Data\src\CommonRuntime
LongPath: s\fsharp\FSharp.Data\src\CommonRuntime
new DirectoryInfo(@"D:\tmp").LastWriteTimeUtc.Dump(); // OK
new DirectoryInfo(@"D:\tmp\").LastWriteTimeUtc.Dump(); // FileNotFoundException
This might be related to #18
Is it possible to put strong named assembly in the nuget package ?
Otherwise it's not possible to use the package is all situation (like mine)
All these methods and properties throw an exception e.g. if the file is not found:
File.GetCreationTime[Utc]
File.GetLastAccessTime[Utc]
Directory.GetCreationTime[Utc]
Directory.GetLastAccessTime[Utc]
Directory.GetLastWriteTime[Utc]
FileSystemInfo.CreationTime[Utc]
FileSystemInfo.LastAccessTime[Utc]
FileSystemInfo.LastWriteTime[Utc]
But File.GetLastWriteTime[Utc]
does not; it catches all exceptions and returns a default date (DateTime.FromFileTimeUtc(0L)
). This is inconsistent.
from Thomas Levesque:
if a file doesn't exist, System.IO.File.GetLastWriteTime gives a dummy date (1601/01/01), whereas Pri.LongPath.File.GetLastWriteTime throws an exception.
While investigating an issue I found this in my log file:
2016-10-26 09:36:41,141 - Scan - ERROR - MyApp.Business.SyncService - Error analyzing local changes
System.UnauthorizedAccessException: Empty path
à Pri.LongPath.Common.ThrowIOError(Int32 errorCode, String maybeFullPath)
à Pri.LongPath.FileSystemInfo.Refresh()
à Pri.LongPath.DirectoryInfo.get_Exists()
...
I first tried to understand how I could possibly end-up with a DirectoryInfo
with an empty path, since the path is validated at construction time, but looking at the code, I think the message is incorrect and misleading. The message "Empty path" comes from here:
case NativeMethods.ERROR_ACCESS_DENIED:
if (str.Length == 0)
throw new UnauthorizedAccessException("Empty path");
else
throw new UnauthorizedAccessException(String.Format("Access denied accessing {0}", str));
because the maybeFullPath
parameter is empty.
Common.ThrowIOError
is called from here:
catch (Exception)
{
if (state != State.Error)
Common.ThrowIOError(Marshal.GetLastWin32Error(), string.Empty);
}
Is there any reason not to pass FullPath
instead of string.Empty
?
Directory.EnumerateFiles(String, String) and Directory.EnumerateFiles(String, String, SearchOption) are excluded for .NET 4 but present for .NET 4.5. In the original System.IO API these overloads are available in .NET 4 (see https://msdn.microsoft.com/en-us/library/system.io.directory.enumeratefiles(v=vs.100).aspx)
When we pass a relative path to Pri.LongPath.Path.GetDirectoryName
, it returns an absolute path based on the current directory; it should return a relative path.
System.IO.Path.GetDirectoryName(@"foo\bar\baz") // "foo\bar"
Pri.LongPath.Path.GetDirectoryName(@"foo\bar\baz") // "C:\CurrentDir\foo\bar"
CON
, AUX
, LPT
, etc. were reserved names in MsDos. For compatibility Windows Explorer still prevents you from creating files or directories with those names. But in fact Windows internally does support it (like it supports long filenames even tho the Explorer does not).
Would be great if those names would be handled correctly.
The following example demonstrates that. Pri.LongPath is not able to create a directory named "aux".
using System;
using System.Runtime.InteropServices;
using Pri.LongPath;
namespace MetaTree
{
class Program
{
//Make sure temp already exists!
private readonly static string temp = @"D:\Temp\";
static void Main(string[] args)
{
//[System.ArgumentException]: The UNC path should be of the form \\server\share.
Directory.CreateDirectory(temp + "aux");
//With the WinAPI a prefix is needed, but it works
CreateDirectory(@"\\?\" + temp + "aux", IntPtr.Zero);
//Removes the aux Folder
//WARNING: The "aux" folder can not be removed with the windows explorer
RemoveDirectory(@"\\?\" + temp + "aux");
}
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CreateDirectory(string lpPathName, IntPtr lpSecurityAttributes);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool RemoveDirectory(string lpPathName);
}
}
I'm unfortunately using a version of Pri.LongPath that is a year or two old, so I'm not sure if this is still an issue. It is tough to reproduce though. I have a customer that hits this issue about once a week.
You can see below the stacktrace, and a mention of "System.IO.IOException - The parameter is incorrect" inside of Pri.LongPath.Directory.BeginFind().
At this point I do not have any additional information that could help in debugging this. Do you know where, within BeginFind(), I could maybe add some additional information to log files on this?
In the Path class:
private static int GetUncRootLength(string path)
{
var components = path.Split(new[] { DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
var root = string.Format(@"\\{0}\{1}\", components[0], components[1]);
return root.Length;
}
...throws an index out of bounds exception if you give it a path containing only forward-slashes (AltDirectorySeparatorChar
). I think the Split
should be for either. This occurs if you try to call Directory.Create()
with a long path (i.e. > MAX_PATH chars) with forward-slashes, for example.
(Apologies - I'm behind an office firewall where I can't make a pull request / tests for this. :-( )
Im trying to set acl programatically, but "File.SetAccessControl(string path, FileSecurity FileSecurity)"didn't work, "File.GetAccessControl(string path)" worked fine!
;
From Thomas Levesque:
FileInfo.ToString returns the full path in System.IO, whereas Pri.LongPath returns just the name
System.IO.Path.GetFileName
returns null
if the input is null
. Pri.LongPath.Path.GetFileName
used to do the same (since it simply delegated to System.IO.Path.GetFileName
).
However it no longer does so, since it calls Path.NormalizeLongPath
which throws if its input is null.
Regarding Pri.LongPath AssemblyVersion 2.0.1.0
Failed unit tests running the tests on D: drive instead of C: drive
I did some work on it:
So I recommend fixed test cases as follows.
They will run on any drive
-------------------------------
public class UncDirectoryTests
-------------------------------
[Test]
public void TestGetDirectoryRoot()
{
string plain = Directory.GetDirectoryRoot(directory);
string unc = Directory.GetDirectoryRoot(uncDirectory);
string must = @"\\localhost\X$\".Replace('X', plain[0]); // use current drive letter
Assert.IsTrue(must.Equals(unc, StringComparison.InvariantCultureIgnoreCase));
}
[Test]
public void TestGetPathRoot()
{
var root = Path.GetPathRoot(uncDirectory);
Assert.IsNotNull(root);
Assert.AreEqual(15, root.Length);
var plain = Path.GetPathRoot(directory);
string must = @"\\localhost\X$\".Replace('X', plain[0]); // use current drive letter
Assert.IsTrue(must.Equals(root, StringComparison.InvariantCultureIgnoreCase));
}
-------------------------------
public class DirectoryInfoTests
-------------------------------
[Test]
public void TestMoveToDifferentRoot()
{
var randomFileName = Path.GetRandomFileName();
var tempLongPathDirectory = Path.Combine(longPathDirectory, randomFileName);
var di = new DirectoryInfo(tempLongPathDirectory);
// get other drive letter
char letter = longPathDirectory[0];
if (letter < 'A' || letter >= 'Z') throw new Exception("invalid letter " + letter);
letter = (char)(letter + 1); // next other drive
string target = @"" + letter + @":\";
Assert.Throws<IOException>(() => di.MoveTo(target)); // move to other drive
}
may some one update the test cases?
thx for great work on library
regards efef77
Thanks for the nice package that solves 90% of my problems. The only thing left is that I am using a FileSystemWatcher. But that one is not implemented in LongPath. It fails with a pathtoolong exception. Are there any plans to include this?
I wrote a simple program that reads folder permissions recursively for a large network share. Everything goes fine until it gets to a certain point then it throws an AccessViolationException pointing to this line:
var length = NativeMethods.GetSecurityDescriptorLength(byteArray);
Any Ideas?
I've just seen the following in some of our logs, with v2.0.24:
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Text.StringBuilder.get_Chars(Int32 index)
at Pri.LongPath.Path.NormalizeLongPath(String path, String parameterName)
at Pri.LongPath.Path.TryNormalizeLongPath(String path, String& result)
at Pri.LongPath.Common.Exists(String path, Boolean& isDirectory)
at Pri.LongPath.File.Exists(String path)
Although sadly I'm not sure what path triggered this (although I think it was a relative path, if that helps). :-(
I tried your Pri.LongPath package yesterday & found the following issues:
In GetFileHandle(...)
:
Console.WriteLine("error {0} with {1}\n{2}", ex.Message, normalizedPath, ex.StackTrace);
Console.WriteLine("{0} {1} {2} {3}", mode, access, share, options);
This should surely go into the exception object that is thrown, instead?
Directory.SetAccessControl does not set InheritanceFlags and PropagationFlags
Directory.CreateDirectory(string path, DirectorySecurity directorySecurity) has the same problem
test to reproduce:
var localAdmins = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
security.AddAccessRule(new FileSystemAccessRule(localAdmins,
FileSystemRights.FullControl,
InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
PropagationFlags.NoPropagateInherit,
AccessControlType.Allow));
Pri.LongPath.Directory.CreateDirectory(path);
Pri.LongPath.Directory.SetAccessControl(path, security);
ShowDirectorySecurity(path);
// FileSystemRights=FullControl, InheritanceFlags=[None], PropagationFlags=[None], AccessControlType=Allow
Directory.Delete(path);
System.IO.Directory.CreateDirectory(path);
System.IO.Directory.SetAccessControl(path, security);
ShowDirectorySecurity(path);
// FileSystemRights=FullControl, InheritanceFlags=[ContainerInherit, ObjectInherit], PropagationFlags=[NoPropagateInherit], AccessControlType=Allow
Directory.Delete(path);
There is a bug in Pri.LongPath.Path.GetDirectoryName when the filename contains '. ' (dot and then whitespace).
System.IO.Path.GetDirectoryName("C:\\myfolder\\Con. approval.pdf") // ==> C:\myfolder (correct)
Pri.LongPath.Path.GetDirectoryName("C:\\myfolder\\Con. approval.pdf") // ==> \\. (not correct)
Seems to be a win32 bug:
NativeMethods.GetFullPathName("C:\\myfolder\\Con. approval.pdf", (uint)buffer.Capacity, buffer, IntPtr.Zero) ==> buffer is set to \\.\Con - which is not correct
I have reported this here: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364963(v=vs.85).aspx
Build for 4.6
I have the following exception sent in from a tester:
The filepath is ~160 characters and filename is another 60 characters, but the file does not exist. In fact, there's a good chance that the drive doesn't exist since this is my friend on a corporate computer with network drive.
Another thing I noticed is that fileInfo's createdTime and modifiedTime is much more sensitive (throws exceptions) when file does not exist verses the System.IO.FileInfo version. Not as important, I put a check first (if (fileInfo.Exists)), but now that throws exceptions too ;^)
The full .NET 4 is not needed to compile the library, so it'll be fine to use the .NET 4 Client Profile so that the DLL can be used even if the full .NET is not available.
But now, i'm having some problems on
Directory.SetAccessControl(string Path, DirectorySecurity directorySecurity )
File.SetAccessControl(string Path, FileSecurity fileSecurity )
doesn't removes the rule on my folder or file, with System.IO works fine.
Here's how i'm using.
public void RemoveFileSecurity(FileSystemAccessRule rule)
{
FileSecurity fileSecurity = File.GetAccessControl(Path);
fileSecurity.RemoveAccessRule(rule);
System.IO.File.SetAccessControl(Path, fileSecurity);
}
thanks in advance!
Hi @peteraritchie, apologies that I need to bother you again with this.
Do you think it would be possible to dual license LongPath under MIT?
I think our usage in Paket would comply with LGPL, but realistically speaking, if the decision is between having to consult a lawyer, or just not implementing long-path support yet, option b) is more likely.
Thank you again!
(ref fsprojects/Paket#2584)
While enumerating the directory, I came to realise that DirectoryInfo.FullName
is missing and the closest one DirectoryInfo.FullPath
(inherited) is marked as protected
.
Please add .FullName
property to the API.
I admit that this is a really strange one, but here is the situation
Create a c:\temp\test.txt in your disk
Run this piece of code
string file = @"c:\temp \test.txt";
Console.WriteLine("System.IO.File.Exists(file) = {0}", System.IO.File.Exists(file));
Console.WriteLine("Pri.LongPath.File.Exists(file) = {0}", Pri.LongPath.File.Exists(file));
Console.ReadKey();
If you look at the code I've inserted a space between temp and directory separator. Actually the standard File.IO.File.exists returns true, while Pri.LongPath.File returns false.
Gian Maria.
System.IO.File.Copy(@"e:\foo", @"e:\bar", false);
...gives "The file 'e:\bar' already exists."
in the thrown IOException
's Message
, whereas:
Pri.LongPath.File.Copy(@"e:\foo", @"e:\bar", false);
...gives only "The file exists."
I think this is because File.cs:135 is using throw Common.GetExceptionFromLastWin32Error()
instead of Common.ThrowIOError(errorCode, fullPath)
.
Maybe GetExceptionFromLastWin32Error()
could do with taking an optional fullPath
argument? It looks like perhaps ThrowIOError
and GetExceptionFromLastWin32Error
ought to be unified, given what they do - not sure on your thoughts on that.
Thanks for the brilliantly useful library, by the way! :-)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.