sources/Google.Solutions.Ssh/SftpChannel.cs (92 lines of code) (raw):

// // Copyright 2022 Google LLC // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // using Google.Solutions.Common.Util; using Google.Solutions.Ssh.Native; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Threading.Tasks; namespace Google.Solutions.Ssh { /// <summary> /// Channel for interacting with remote file. /// </summary> public class SftpChannel : SshChannelBase, ISftpChannel { /// <summary> /// Recommended buffer size to use for reading from, or /// writing to a stream. /// /// SFTP effectively limits the size of a packet to 32 KB, see /// <https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-4> /// /// libssh2 uses a slightly smaller limit of 30000 bytes /// (MAX_SFTP_OUTGOING_SIZE, MAX_SFTP_READ_SIZE). /// Using a buffer larger than 30000 bytes therefore doen't /// provide much value. /// /// Note that IAP/SSH Relay uses 16KB as maximum message size, /// so a 32000 byte packet will be split into 2 messages. /// That's still more efficient than using a SFTP packet /// size below 16 KB as it at least limits the number of /// SSH_FXP_STATUS packets that need to be exchanged. /// /// </summary> public const int BufferSize = 30000; /// <summary> /// Channel handle, must only be accessed on worker thread. /// </summary> private readonly Libssh2SftpChannel nativeChannel; /// <summary> /// Connection used by this channel. /// </summary> public override SshConnection Connection { get; } internal SftpChannel( SshConnection connection, Libssh2SftpChannel nativeChannel) { this.Connection = connection; this.nativeChannel = nativeChannel; } //--------------------------------------------------------------------- // Overrides. //--------------------------------------------------------------------- protected override void Close() { Debug.Assert(this.Connection.IsRunningOnWorkerThread); this.nativeChannel.Dispose(); } internal override void OnReceive() { // // We're never expecting any unsolicited data from the // server, so ignore the callback. // } internal override void OnReceiveError(Exception exception) { } //--------------------------------------------------------------------- // Publics. //--------------------------------------------------------------------- /// <summary> /// List contents of a directory. /// </summary> public Task<IReadOnlyCollection<SftpFileInfo>> ListFilesAsync( string remotePath) { Precondition.ExpectNotEmpty(remotePath, nameof(remotePath)); return this.Connection.RunAsync( c => { Debug.Assert(this.Connection.IsRunningOnWorkerThread); using (c.Session.AsBlocking()) { return this.nativeChannel.ListFiles(remotePath); } }, false); } /// <summary> /// Create or open a file. /// </summary> public Task<Stream> CreateFileAsync( string remotePath, FileMode mode, FileAccess access, FilePermissions permissions) { Precondition.ExpectNotEmpty(remotePath, nameof(remotePath)); var flags = mode switch { FileMode.Create => LIBSSH2_FXF_FLAGS.CREAT, FileMode.CreateNew => LIBSSH2_FXF_FLAGS.CREAT | LIBSSH2_FXF_FLAGS.EXCL, FileMode.OpenOrCreate => LIBSSH2_FXF_FLAGS.CREAT, FileMode.Open => (LIBSSH2_FXF_FLAGS)0, FileMode.Truncate => LIBSSH2_FXF_FLAGS.TRUNC, FileMode.Append => LIBSSH2_FXF_FLAGS.APPEND, _ => throw new ArgumentException("The file mode is invalid"), }; if (access.HasFlag(FileAccess.Read)) { flags |= LIBSSH2_FXF_FLAGS.READ; } if (access.HasFlag(FileAccess.Write)) { flags |= LIBSSH2_FXF_FLAGS.WRITE; } return this.Connection.RunAsync<Stream>( c => { Debug.Assert(this.Connection.IsRunningOnWorkerThread); using (c.Session.AsBlocking()) { return new SftpFileStream( this.Connection, this.nativeChannel.CreateFile( remotePath, flags, permissions), flags); } }, false); } } }