0 votes
by (260 points)
edited

I have run into an issue when trying to upload a file with a source local path that exceeds 260 characters to a GlobalScape EFT server. The PutFile method fails with an SftpException that says "The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters."

You can reproduce this by making a file on your local C:\ drive that exceeds 260 characters.

1) Create Folder C:\LongPath

2) In C:\LongPath, create a 0 byte file named: "1bcdefghijklmnopqrstuvwxyz2bcdefghijklmnopqrstuvwxyz3bcdefghijklmnopqrstuvwxyz4bcd" + "efghijklmnopqrstuvwxyz5bcdefghijklmnopqrstuvwxyz6bcdefghijklmnopqrstuvwxyz7bcdefgh" + "ijklmnopqrstuvwxyz8bcdefghijklmnopqrstuvwxyz9bcdefghijklmnopqrstuvwxyz10bcdefgh.txt"

3) Rename C:\LongPath to C:\LongPathExceedsMaxPath

4) Attempt to upload the file.

While I realize this problem is likely related to limitations in the .Net IO library, Microsoft provides a workaround to the 260 character limit by using a different naming convention and pre-pending the file paths with \\?\. With this naming convention and implementation it can support source paths up to 32,767 characters. The path would then be \\?\C:\LongPathExceedsMaxPath\1bcdedf.....gh.txt See the following link: http://msdn.microsoft.com/en-us/library/aa365247(v=vs.85).aspx#maxpath

Once using the \\?\ naming convention, you may have to utilize the Kernel32 library methods CreateFile and ReadFile to open and read data from the file.

I'm actually using BeginPutFile and EndPutFile to transfer the file asynchronously, but both methods show the same behavior. Is there any chance this is planned for a future build of Rebex SFTP? Thanks!

Applies to: Rebex SFTP

1 Answer

0 votes
by (148k points)
edited
 
Best answer

You are right, this is actually a limitation of .NET's System.IO classes. Trying to open the file using System.IO.File.OpenRead(path) fails with the same exception.

We will consider utilizing the \? naming convention for too-long paths in one of the next releases, but you can easily work around this with the current version of Rebex SFTP by using the stream based-API and the following custom stream object:

C#:

    using System.IO;
    using System.Runtime.InteropServices;
    using Microsoft.Win32.SafeHandles;

    public class FileStream2 : FileStream
    {
        private const uint GENERIC_READ = 0x80000000;
        private const uint GENERIC_WRITE = 0x40000000;

        private const uint FILE_SHARE_READ = 0x00000001;
        private const uint FILE_SHARE_WRITE = 0x00000002;
        private const uint FILE_SHARE_DELETE = 0x00000004;

        private const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;

        private const int CREATE_NEW = 1;
        private const int CREATE_ALWAYS = 2;
        private const int OPEN_EXISTING = 3;
        private const int OPEN_ALWAYS = 4;
        private const int TRUNCATE_EXISTING = 5;

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);

        public FileStream2(string path, FileMode mode, FileAccess access, FileShare share)
            : base(Open(path, mode, access, share), access)
        {
        }

        private static SafeFileHandle Open(string path, FileMode mode, FileAccess access, FileShare share)
        {
            uint dwDesiredAccess = 0;
            if ((access & FileAccess.Read) == FileAccess.Read)
                dwDesiredAccess |= GENERIC_READ;
            if ((access & FileAccess.Write) == FileAccess.Write)
                dwDesiredAccess |= GENERIC_WRITE;

            uint dwShareMode = ((uint)share) & 7;

            uint dwCreationDisposition;
            switch (mode)
            {
                case FileMode.CreateNew:
                case FileMode.Create:
                case FileMode.Open:
                case FileMode.OpenOrCreate:
                case FileMode.Truncate:
                    dwCreationDisposition = (uint)mode;
                    break;
                default:
                    throw new ArgumentException("Unsupported mode.", "mode");
            }

            SafeFileHandle handle = CreateFile(path, dwDesiredAccess, dwShareMode, IntPtr.Zero, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
            if (handle.IsInvalid)
            {
                int errorCode = Marshal.GetLastWin32Error();
                string errorMessage = new Win32Exception(errorCode).Message;
                throw new IOException(errorMessage, errorCode);
            }

            return handle;
        }
    }

With this class, you can open file stream using the \? naming convention and use them with Rebex SFTP API:

using (FileStream2 source = new FileStream2(longLocalPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    sftp.PutFile(source, remotePath);
}

using (FileStream2 target = new FileStream2(longLocalPath, FileMode.Create, FileAccess.Write, FileShare.Read))
{
    sftp.GetFile(remotePath, target);
}

A similar approach will work with BeginPutFile/BeginGetFile as well, but instead of using, you would have to pass the stream to the callback method (using the state object, for example) and close it there.

by (260 points)
edited

Perfect - I had not considered this approach. Thank you.

...