Skip to content

Commit bbf7fc1

Browse files
authored
Merge pull request #25 from JuliaWeb/sftp
Various fixes
2 parents 84e0e81 + d9fba3d commit bbf7fc1

File tree

7 files changed

+65
-25
lines changed

7 files changed

+65
-25
lines changed

docs/src/changelog.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@ Changelog](https://keepachangelog.com).
1515
construction ([#21]).
1616
- Our [`Base.run()`](@ref) methods now accept plain `String`s as well as `Cmd`s
1717
([#24]).
18+
- Implemented convenience [`Base.read(::String, ::SftpSession)`](@ref) methods
19+
that will take a `String` filename without having to open the file explicitly
20+
([#25]).
21+
- Added support for specifying whether a [`Session`](@ref) should use the users
22+
SSH config with the `process_config` option ([#25]).
1823

1924
### Fixed
2025

2126
- Improved handling of possible errors in [`Base.readdir()`](@ref) ([#20]).
27+
- Fixed exception handling for [`Base.run()`](@ref), now it throws a
28+
[`SshProcessFailedException`](@ref) or [`LibSSHException`](@ref) on command
29+
failure instead of a plain `TaskFailedException` ([#25]).
2230

2331
## [v0.6.0] - 2024-10-11
2432

docs/src/sessions_and_channels.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ SshProcessFailedException
111111
SshProcess
112112
Base.wait(::SshProcess)
113113
Base.success(::SshProcess)
114-
Base.run(::Cmd, ::Session)
115-
Base.read(::Cmd, ::Session)
116-
Base.read(::Cmd, ::Session, ::Type{String})
117-
Base.readchomp(::Cmd, ::Session)
118-
Base.success(::Cmd, ::Session)
114+
Base.run(::Union{Cmd, String}, ::Session)
115+
Base.read(::Union{Cmd, String}, ::Session)
116+
Base.read(::Union{Cmd, String}, ::Session, ::Type{String})
117+
Base.readchomp(::Union{Cmd, String}, ::Session)
118+
Base.success(::Union{Cmd, String}, ::Session)
119119
```
120120

121121
#### Direct port forwarding

docs/src/sftp.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ Base.open(::String, ::SftpSession)
5959
Base.open(::Function, ::String, ::SftpSession)
6060
Base.close(::SftpFile)
6161
Base.read(::SftpFile)
62+
Base.read(::String, ::SftpSession)
6263
Base.read(::SftpFile, ::Type{String})
64+
Base.read(::String, ::SftpSession, ::Type{String})
6365
Base.read!(::SftpFile, ::Vector{UInt8})
6466
Base.write(::SftpFile, ::DenseVector)
6567
Base.write(::SftpFile, ::AbstractString)

src/channel.jl

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -493,8 +493,18 @@ $(TYPEDSIGNATURES)
493493
function Base.wait(process::SshProcess)
494494
try
495495
wait(process._task)
496-
catch ex
497-
if process.cmd isa Cmd && !process.cmd.ignorestatus
496+
catch task_ex
497+
ex = process._task.exception
498+
499+
# The idea is that SshProcessFailedException's and LibSSHException's are
500+
# somewhat expected so we always unwrap them from the
501+
# TaskFailedException before throwing, which is a slightly nicer API to
502+
# work with.
503+
if ex isa SshProcessFailedException || ex isa LibSSHException
504+
if !(process.cmd isa Cmd && process.cmd.ignorestatus)
505+
throw(process._task.exception)
506+
end
507+
else
498508
rethrow()
499509
end
500510
end
@@ -591,6 +601,8 @@ An easy way of getting around these restrictions is to pass the command as a
591601
# Throws
592602
- [`SshProcessFailedException`](@ref): if the command fails and `ignorestatus()`
593603
wasn't used.
604+
- [`LibSSHException`](@ref): if running the command fails for some other
605+
reason.
594606
595607
# Arguments
596608
- `cmd`: The command to run. This will be converted to a string for running
@@ -652,10 +664,10 @@ function Base.run(cmd::Union{Cmd, String}, session::Session;
652664
set_channel_callbacks(process._sshchan, callbacks)
653665

654666
process._task = Threads.@spawn _exec_command(process)
655-
errormonitor(process._task)
667+
656668
if wait
657669
# Note the use of Base.wait() to avoid aliasing with the `wait` argument
658-
Base.wait(process._task)
670+
Base.wait(process)
659671

660672
if print_out
661673
print(String(copy(process.out)))

src/session.jl

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ mutable struct Session
2626
ssh_dir::Union{String, Nothing}
2727
known_hosts::Union{String, Nothing}
2828
gssapi_server_identity::Union{String, Nothing}
29+
process_config::Bool
2930

3031
_lock::ReentrantLock
3132
_auth_methods::Union{Vector{AuthMethod}, Nothing}
@@ -68,7 +69,7 @@ mutable struct Session
6869
lib.ssh_set_blocking(ptr, 0)
6970

7071
session = new(ptr, own, [], nothing,
71-
-1, nothing, nothing, nothing,
72+
-1, nothing, nothing, nothing, true,
7273
ReentrantLock(), nothing, AuthMethod[], true,
7374
Threads.Event(true), CloseableCondition(), false)
7475

@@ -137,6 +138,13 @@ $(TYPEDSIGNATURES)
137138
Constructor for creating a client session. Use this if you want to connect to a
138139
server.
139140
141+
!!! warning
142+
By default libssh will try to follow the settings in any found SSH config
143+
files. If a proxyjump is configured for `host` libssh will try to set up the
144+
proxy itself, which usually does not play well with Julia's event loop. In
145+
such situations you will probably want to pass `process_config=false` and
146+
set up the proxyjump explicitly using a [`Forwarder`](@ref).
147+
140148
# Throws
141149
- [`LibSSHException`](@ref): if a session couldn't be created, or there was an
142150
error initializing the `user` property.
@@ -152,6 +160,7 @@ server.
152160
- `log_verbosity=nothing`: Set the log verbosity for the session.
153161
- `auto_connect=true`: Whether to automatically call
154162
[`connect()`](@ref).
163+
- `process_config=true`: Whether to process any found SSH config files.
155164
156165
# Examples
157166
@@ -163,7 +172,8 @@ julia> session = ssh.Session(ip"12.34.56.78", 2222)
163172
"""
164173
function Session(host::Union{AbstractString, Sockets.IPAddr}, port=22;
165174
socket::Union{Sockets.TCPSocket, RawFD, Nothing}=nothing,
166-
user=nothing, log_verbosity=nothing, auto_connect=true)
175+
user=nothing, log_verbosity=nothing, auto_connect=true,
176+
process_config=true)
167177
session_ptr = lib.ssh_new()
168178
if session_ptr == C_NULL
169179
throw(LibSSHException("Could not initialize Session for host $(host)"))
@@ -192,6 +202,8 @@ function Session(host::Union{AbstractString, Sockets.IPAddr}, port=22;
192202
session.user = user
193203
end
194204

205+
session.process_config = process_config
206+
195207
if auto_connect
196208
connect(session)
197209
end
@@ -295,10 +307,11 @@ const SESSION_PROPERTY_OPTIONS = Dict(:host => (SSH_OPTIONS_HOST, Cstring),
295307
:ssh_dir => (SSH_OPTIONS_SSH_DIR, Cstring),
296308
:known_hosts => (SSH_OPTIONS_KNOWNHOSTS, Cstring),
297309
:gssapi_server_identity => (SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, Cstring),
298-
:log_verbosity => (SSH_OPTIONS_LOG_VERBOSITY, Cuint))
310+
:log_verbosity => (SSH_OPTIONS_LOG_VERBOSITY, Cuint),
311+
:process_config => (SSH_OPTIONS_PROCESS_CONFIG, Bool))
299312
# These properties cannot be retrieved from the libssh API (i.e. with
300313
# ssh_options_get()), so we store them in the Session object instead.
301-
const SAVED_PROPERTIES = (:log_verbosity, :gssapi_server_identity, :ssh_dir, :known_hosts)
314+
const SAVED_PROPERTIES = (:log_verbosity, :gssapi_server_identity, :ssh_dir, :known_hosts, :process_config)
302315

303316
function Base.propertynames(::Session, private::Bool=false)
304317
fields = fieldnames(Session)

src/sftp.jl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ mutable struct SftpSession
142142
- [`LibSSHException`](@ref): If creating the SFTP session fails.
143143
"""
144144
function SftpSession(session::Session)
145-
if !isopen(session)
146-
throw(ArgumentError("Session is closed, cannot create an SftpSession with it"))
145+
if !isconnected(session)
146+
throw(ArgumentError("Session is disconnected, cannot create an SftpSession with it"))
147147
end
148148

149149
ptr = @lockandblock session lib.sftp_new(session.ptr)
@@ -907,6 +907,9 @@ function Base.read(file::SftpFile, nb::Integer=typemax(Int))
907907
return out
908908
end
909909

910+
"$(TYPEDSIGNATURES)"
911+
Base.read(filename::AbstractString, sftp::SftpSession) = open(read, filename, sftp)
912+
910913
"""
911914
$(TYPEDSIGNATURES)
912915
@@ -973,6 +976,10 @@ Read the whole file as a `String`.
973976
"""
974977
Base.read(file::SftpFile, ::Type{String}) = String(read(file))
975978

979+
"$(TYPEDSIGNATURES)"
980+
Base.read(filename::AbstractString, sftp::SftpSession, ::Type{String}) = String(read(filename, sftp))
981+
982+
976983
"""
977984
$(TYPEDSIGNATURES)
978985

test/LibSSHTests.jl

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ end
355355
session.gssapi_server_identity = "foo.com"
356356
@test session.gssapi_server_identity == "foo.com"
357357
@test session.fd == RawFD(-1)
358+
session.process_config = false
359+
@test !session.process_config
358360

359361
# Test setting an initial user
360362
ssh.Session("localhost"; user="foo", auto_connect=false) do session2
@@ -562,16 +564,8 @@ end
562564
cmd = setenv(`echo \$foo`, "foo" => "bar")
563565
@test readchomp(cmd, session) == "bar"
564566

565-
# Test command failure. We redirect error output to devnull to hide
566-
# the errors displayed by the errormonitor() task.
567-
try
568-
redirect_stderr(devnull) do
569-
run(`foo`, session)
570-
end
571-
catch ex
572-
@test ex isa TaskFailedException
573-
@test current_exceptions(ex.task)[1][1] isa ssh.SshProcessFailedException
574-
end
567+
# Test command failure
568+
@test_throws ssh.SshProcessFailedException run(`foo`, session)
575569

576570
# Test passing a String instead of a Cmd
577571
mktempdir() do tmpdir
@@ -773,6 +767,10 @@ end
773767
# Shouldn't be able to read from a closed file
774768
close(file)
775769
@test_throws ArgumentError read(file)
770+
771+
# Test reading by passing just a filename
772+
write(path, msg)
773+
@test read(path, sftp, String) == msg
776774
end
777775
end
778776
end

0 commit comments

Comments
 (0)