1

Closed

ScpClient.Download Hangs for Single File

description

For me, connecting to a recently built Debian Linux box to download a single file, the ScpClient.ReadByte(Stream) hangs due to an infinite loop waiting for more bytes on the stream, that never come.

The while loop smells, but that wasn't the fix. I didn't want to touch that vital mechanism. I could see that there is a check in the InternalDownload(...) method that will break out of the (different) loop when the download is just for a file.

This check is done using the strong type of FileInfo in the FileSystemInfo instance sent in. For me, this is a DirectoryInfo all the time, since my Windows client copy command doesn't know whether the Linux path is a file or folder (as it doesn't have access to Linux to see). It's PowerShell...

For example: Copy-FromSshSession -ComputerName uktnlx01 -RemotePath /boot/grub/chain.mod -LocalPath c:\

So the user (me) supplies a DirectoryInfo object of the destination folder to copy the Linux path to, be it a file or folder.

Anyway, the InternalDownload method will never see a FileInfo so it keep looping for more. I solved it by adding a boolean flag that is set when the loop sees a directory info message, thus indicating that the download is for an entire folder, i.e. I've let the Linux messages inform the logic, not the client.

Here's the code: Don't know mark-down so I'll try and pretty it up after posting:
    private void InternalDownload(ChannelSession channel, Stream input, FileSystemInfo fileSystemInfo)
    {
        DateTime modifiedTime = DateTime.Now;
        DateTime accessedTime = DateTime.Now;

        var startDirectoryFullName = fileSystemInfo.FullName;
        var currentDirectoryFullName = startDirectoryFullName;
        var directoryCounter = 0;

        bool hasDirectoryMessageBeenSeen = false;

        while (true)
        {
            var message = ReadString(input);

            if (message == "E")
            {
                this.SendConfirmation(channel); //  Send reply

                directoryCounter--;

                currentDirectoryFullName = new DirectoryInfo(currentDirectoryFullName).Parent.FullName;

                //if (currentDirectoryFullName == startDirectoryFullName)
                if (directoryCounter == 0)
                    break;
                else
                    continue;
            }

            var match = _directoryInfoRe.Match(message);
            if (match.Success)
            {
                hasDirectoryMessageBeenSeen = true;

                this.SendConfirmation(channel); //  Send reply

                //  Read directory
                var mode = long.Parse(match.Result("${mode}"));
                var filename = match.Result("${filename}");

                DirectoryInfo newDirectoryInfo;
                if (directoryCounter > 0)
                {
                    newDirectoryInfo = Directory.CreateDirectory(string.Format("{0}{1}{2}", currentDirectoryFullName, Path.DirectorySeparatorChar, filename));
                    newDirectoryInfo.LastAccessTime = accessedTime;
                    newDirectoryInfo.LastWriteTime = modifiedTime;
                }
                else
                {
                    //  Dont create directory for first level
                    newDirectoryInfo = fileSystemInfo as DirectoryInfo;
                }

                directoryCounter++;

                currentDirectoryFullName = newDirectoryInfo.FullName;
                continue;
            }

            match = _fileInfoRe.Match(message);
            if (match.Success)
            {
                //  Read file
                this.SendConfirmation(channel); //  Send reply

                var mode = match.Result("${mode}");
                var length = long.Parse(match.Result("${length}"));
                var fileName = match.Result("${filename}");

                var fileInfo = fileSystemInfo as FileInfo;

                if (fileInfo == null)
                    fileInfo = new FileInfo(string.Format("{0}{1}{2}", currentDirectoryFullName, Path.DirectorySeparatorChar, fileName));

                using (var output = fileInfo.OpenWrite())
                {
                    this.InternalFileDownload(channel, input, output, fileName, length);
                }

                fileInfo.LastAccessTime = accessedTime;
                fileInfo.LastWriteTime = modifiedTime;

                // Fixes bug. Needs to exit loop after single file copy, so to determine if the copy task was for a single file
                // we check whether a directory message has not been seen, which will only happen on single file copy.
                //
                if (!hasDirectoryMessageBeenSeen)
                    break;

                continue;
            }

            match = _timestampRe.Match(message);
            if (match.Success)
            {
                //  Read timestamp
                this.SendConfirmation(channel); //  Send reply

                var mtime = long.Parse(match.Result("${mtime}"));
                var atime = long.Parse(match.Result("${atime}"));

                var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
                modifiedTime = zeroTime.AddSeconds(mtime);
                accessedTime = zeroTime.AddSeconds(atime);
                continue;
            }

            this.SendConfirmation(channel, 1, string.Format("\"{0}\" is not valid protocol message.", message));
        }
    }
Closed Jan 10, 2013 at 2:09 PM by olegkap
Fixed in 22442 code commit

comments

olegkap wrote Jan 10, 2013 at 1:06 PM

Ok,
So to understand it better.
You experience this problem when you try to download file as a direcotry and then in hangs,
or vise versa?

olegkap wrote Jan 10, 2013 at 1:30 PM

Nevermind,

I figure it out, I think.

I fixed it, but slightly different since I already had similar information in place.
I used this:
                if (directoryCounter == 0)
                    break;
                continue;
I just commited this code so please take a look and let me know if it works.

Thanks,
Oleg

lukepuplett wrote Jan 10, 2013 at 2:05 PM

Cheers Oleg, I stuck another fix for a different issue at the bottom of some other conversation - that was wrong of me and I'm profoundly sorry :)

I'll dig it out and make a proper issue.

olegkap wrote Jan 10, 2013 at 3:55 PM

Yea, sure ok, no problem just let me know