Skip to content

Commit

Permalink
Merge pull request #16 from JuliaWeb/sftp
Browse files Browse the repository at this point in the history
Add initial SFTP client support
  • Loading branch information
JamesWrigley authored Oct 6, 2024
2 parents 972f552 + 29bc60e commit 4dcead5
Show file tree
Hide file tree
Showing 16 changed files with 1,599 additions and 190 deletions.
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ makedocs(;
"index.md",
"Examples" => "examples.md",
"sessions_and_channels.md",
"sftp.md",
"server_support.md",
"utilities.md",
"bindings.md",
Expand Down
6 changes: 6 additions & 0 deletions docs/src/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@ Changelog](https://keepachangelog.com).
[`Base.run(::Cmd)`](@ref) ([#12]).
- Made it possible to assign callbacks to [`Callbacks.ServerCallbacks`](@ref) and
[`Callbacks.ChannelCallbacks`](@ref) by property ([#14]).
- [`close(::SshChannel)`](@ref) and [`closewrite(::SshChannel)`](@ref) now
support an `allow_fail` argument that will print a warning instead of throw an
exception if modifying the `lib.ssh_channel` fails ([#16]).
- Basic [SFTP](sftp.md) support.

### Fixed

- Fixed segfaults that would occur in [`SshChannel`](@ref) when its
[`Session`](@ref) is disconnected by the remote end ([#13]).
- Fixed some concurrency bugs in the [`Demo.DemoServer`](@ref) and
[`SessionEvent`](@ref) ([#15]).
- Fixed a race condition in the [`Demo.DemoServer`](@ref) that could cause
segfaults ([#16]).

## [v0.5.0] - 2024-08-10

Expand Down
3 changes: 1 addition & 2 deletions docs/src/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ Libssh is a fairly large project and most of the API still doesn't have
high-level wrappers in LibSSH.jl. For example:
- Reverse port forwarding
- Unix socket forwarding
- SFTP support
- SCP support
- Complete SFTP/SCP support

If you'd like to contribute new wrappers, the usual workflow is:
1. Add support for the feature in the [Demo server](@ref) and test it with the
Expand Down
50 changes: 48 additions & 2 deletions docs/src/examples.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#md # CurrentModule = LibSSH
#md # ```

# # A simple client
# ## Connecting and authenticating
#
# First we'll import the LibSSH package:

Expand Down Expand Up @@ -70,15 +70,61 @@ ssh.userauth_list(session)

@assert ssh.userauth_password(session, "foo") == ssh.AuthStatus_Success

# Now we're authenticated to the server and we can actually do something, like
# Going through all the authentication methods can be quite complicated, in
# practice it may be easier to use [`authenticate()`](@ref) which will handle
# all of that for you.

# ## Running commands
# Now that we're authenticated to the server we can actually do something, like
# running a command (see [Command execution](@ref)):

@assert read(`echo 'Hello world!'`, session, String) == "Hello world!\n"

# ## SFTP
# LibSSH.jl allows reading and writing remote files with the same API as local
# files with `Base`. Lets start by making a temporary directory and creating a
# file in it 'remotely':

tmpdir = mktempdir()
path = joinpath(tmpdir, "foo")

sftp = ssh.SftpSession(session)
file = open(path, sftp; write=true)
write(file, "foo") # this returns the number of bytes written

# We can read the file 'remotely':

open(path, sftp) do readonly_file
read(readonly_file, String)
end

# And do other IO-related things:

seekstart(file)
position(file)
#-
isreadable(file)
#-
iswritable(file)

# After using it we have to close it explicitly because the finalizer won't do
# it for us (see the [`Base.close(::SftpFile)`](@ref) docstring for details):

close(file)

# ## Disconnecting
# Now we can disconnect our client session:

close(sftp)
close(session)

# And stop the server:

demo.stop(demo_server)

# Note that sometimes the `DemoServer` will display a warning that closing an
# `SshChannel` failed because of `Socket error: disconnected`. That can be
# safely ignored, it just means that the socket was closed on the client side
# before the server could close the `SshChannel`, but the `SshChannel` memory
# will still be freed. It typically happens when doing SFTP operations since the
# [`SftpSession`](@ref) manages its own `lib.ssh_channel`.
2 changes: 1 addition & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@ so <3).

## Contents
```@contents
Pages = ["examples.md", "sessions_and_channels.md", "server_support.md", "utilities.md", "bindings.md"]
Pages = ["examples.md", "sessions_and_channels.md", "sftp.md", "server_support.md", "utilities.md", "bindings.md"]
Depth = 10
```
67 changes: 67 additions & 0 deletions docs/src/sftp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
```@meta
CurrentModule = LibSSH
```

# SFTP

A subset of the [SFTP
API](https://api.libssh.org/stable/group__libssh__sftp.html) is wrapped and
available in LibSSH.jl. See the [SFTP example](examples.md#SFTP) for an example
of basic usage.

Unlike the rest of the API, the SFTP C functions are blocking and only work with
blocking [`Session`](@ref)'s. This means that the library has to lock the
session while calling them and no other operations (blocking or unblocking) can
occur while they're being called. In practice this restriction may not be too
onerous since most calls shouldn't take long anyway, and the read/write
implementations use SFTP's asynchronous IO API so they shouldn't block for
long. If it's critical that SFTP operations don't interfere with other
operations (e.g. port forwarding) a workaround would be to open a separate
[`Session`](@ref) for SFTP.

Note that we call all blocking C functions using `@threadcall` so that they
don't block the scheduler, hence as a programmer you don't need to worry about
them hogging a whole thread until they complete.

```@contents
Pages = ["sftp.md"]
Depth = 3
```

## SftpSession
```@docs
SftpError
SftpSession
SftpSession(::Session)
SftpSession(::Function)
Base.close(::SftpSession)
Base.isopen(::SftpSession)
Base.lock(::SftpSession)
Base.unlock(::SftpSession)
Base.stat(::String, ::SftpSession)
get_extensions(::SftpSession)
get_limits(::SftpSession)
get_error(::SftpSession)
```

## SftpFile
```@docs
SftpFile
Base.open(::String, ::SftpSession)
Base.open(::Function, ::String, ::SftpSession)
Base.close(::SftpFile)
Base.read(::SftpFile)
Base.read(::SftpFile, ::Type{String})
Base.read!(::SftpFile, ::Vector{UInt8})
Base.write(::SftpFile, ::DenseVector)
Base.write(::SftpFile, ::AbstractString)
Base.isopen(::SftpFile)
Base.isreadable(::SftpFile)
Base.isreadonly(::SftpFile)
Base.iswritable(::SftpFile)
Base.position(::SftpFile)
Base.seek(::SftpFile, ::Integer)
Base.seekstart(::SftpFile)
Base.seekend(::SftpFile)
```
Loading

0 comments on commit 4dcead5

Please sign in to comment.