|
1 | 1 | (ns clj-libssh2.channel
|
2 | 2 | "Functions for manipulating channels within an SSH session."
|
| 3 | + (:refer-clojure :exclude [read]) |
3 | 4 | (:require [net.n01se.clojure-jna :as jna]
|
4 | 5 | [clj-libssh2.error :refer [handle-errors]]
|
5 | 6 | [clj-libssh2.libssh2 :as libssh2]
|
6 | 7 | [clj-libssh2.libssh2.channel :as libssh2-channel]
|
| 8 | + [clj-libssh2.libssh2.scp :as libssh2-scp] |
7 | 9 | [clj-libssh2.socket :refer [block block-return wait]])
|
8 | 10 | (:import [java.io InputStream PushbackInputStream]
|
9 |
| - [com.sun.jna.ptr IntByReference PointerByReference])) |
| 11 | + [com.sun.jna.ptr IntByReference PointerByReference] |
| 12 | + [clj_libssh2.struct Stat])) |
10 | 13 |
|
11 | 14 | (defn close
|
12 | 15 | "Close a channel.
|
|
124 | 127 | [session]
|
125 | 128 | (block-return session (libssh2-channel/open-session (:session session))))
|
126 | 129 |
|
| 130 | +(defn open-scp-recv |
| 131 | + "Create a new channel dedicated to receiving a file using SCP. |
| 132 | +
|
| 133 | + Arguments: |
| 134 | +
|
| 135 | + session The clj-libssh2.session.Session object for the current session. |
| 136 | + remote-path The path on the remote machine of the file to receive. |
| 137 | +
|
| 138 | + Return: |
| 139 | +
|
| 140 | + A map containing a newly-allocated channel object and the Stat object for |
| 141 | + the file information as reported by the remote host. Throws exception on |
| 142 | + failure." |
| 143 | + [session remote-path] |
| 144 | + (let [fileinfo (Stat/newInstance)] |
| 145 | + {:channel (block-return session |
| 146 | + (libssh2-scp/recv (:session session) remote-path fileinfo)) |
| 147 | + :fileinfo fileinfo})) |
| 148 | + |
| 149 | +(defn open-scp-send |
| 150 | + "Create a new channel dedicated to sending a file using SCP. |
| 151 | +
|
| 152 | + Arguments: |
| 153 | +
|
| 154 | + session The clj-libssh2.session.Session object for the current session. |
| 155 | + remote-path The path on the remote machine of the file to send. |
| 156 | + props A map with the following keys and values: |
| 157 | +
|
| 158 | + :atime The last accessed time to be set on the remote file. |
| 159 | + :mode The mode that the remote file should have. |
| 160 | + :mtime The last modified time to be set on the remote file. |
| 161 | + :size The size of the file in bytes. |
| 162 | +
|
| 163 | + Return: |
| 164 | +
|
| 165 | + A newly-allocated channel object for sending a file via SCP." |
| 166 | + [session remote-path {:keys [atime mode mtime size] :as props}] |
| 167 | + (let [mode (or mode 0644) |
| 168 | + mtime (or mtime 0) |
| 169 | + atime (or atime 0)] |
| 170 | + (block-return session |
| 171 | + (libssh2-scp/send64 (:session session) remote-path mode size mtime atime)))) |
| 172 | + |
127 | 173 | (defn send-eof
|
128 | 174 | "Tell the remote process that we won't send any more input.
|
129 | 175 |
|
|
173 | 219 | (fail-if-forbidden
|
174 | 220 | (libssh2-channel/setenv channel (->str k) (->str v))))))))
|
175 | 221 |
|
| 222 | +(defn read |
| 223 | + "Attempt to read a chunk of data from a stream on a channel. |
| 224 | +
|
| 225 | + Arguments: |
| 226 | +
|
| 227 | + session The clj-libssh2.session.Session object for the current |
| 228 | + session. |
| 229 | + channel A valid channel for this session. |
| 230 | + ssh-stream-id 0 for STDOUT, 1 for STDERR or any other number that the |
| 231 | + process on the other end wishes to send data on. |
| 232 | + size The number of bytes to request. |
| 233 | +
|
| 234 | + Return: |
| 235 | +
|
| 236 | + A map with the following keys and values: |
| 237 | +
|
| 238 | + :status One of :eof, :ready or :eagain depending on whether the stream |
| 239 | + has reported EOF, has potentially more bytes to read immediately |
| 240 | + or has no bytes to read right now but might in the future. |
| 241 | + :received The number of bytes received. |
| 242 | + :data A byte array containing the data which was received." |
| 243 | + [session channel ssh-stream-id size] |
| 244 | + (let [buf1 (jna/make-cbuf size) |
| 245 | + res (handle-errors session |
| 246 | + (libssh2-channel/read-ex channel ssh-stream-id buf1 size))] |
| 247 | + (if (< 0 res) |
| 248 | + (let [buf2 (byte-array res (byte 0))] |
| 249 | + (.get buf1 buf2 0 res) |
| 250 | + {:status :ready |
| 251 | + :received res |
| 252 | + :data buf2}) |
| 253 | + {:status (if (= libssh2/ERROR_EAGAIN res) :eagain :eof) |
| 254 | + :received 0 |
| 255 | + :data nil}))) |
| 256 | + |
176 | 257 | (defn pull
|
177 | 258 | "Read some output from a given stream on a channel.
|
178 | 259 |
|
179 |
| - This should probably only be called from pump. |
180 |
| -
|
181 | 260 | Arguments:
|
182 | 261 |
|
183 | 262 | session The clj-libssh2.session.Session object for the current
|
|
195 | 274 | read immediately."
|
196 | 275 | [session channel ssh-stream-id output-stream]
|
197 | 276 | (let [size (-> session :options :read-chunk-size)
|
198 |
| - buf1 (jna/make-cbuf size) |
199 |
| - res (handle-errors session |
200 |
| - (libssh2-channel/read-ex channel ssh-stream-id buf1 size))] |
201 |
| - (when (and (some? output-stream) (< 0 res)) |
202 |
| - (let [buf2 (byte-array size (byte 0))] |
203 |
| - (.get buf1 buf2 0 res) |
204 |
| - (.write output-stream buf2 0 res))) |
205 |
| - (condp = res |
206 |
| - 0 :eof |
207 |
| - libssh2/ERROR_EAGAIN :eagain |
208 |
| - :ready))) |
| 277 | + res (read session channel ssh-stream-id size) |
| 278 | + {status :status received :received data :data} res] |
| 279 | + (when (and (some? output-stream) (< 0 received)) |
| 280 | + (.write output-stream data 0 received)) |
| 281 | + status)) |
209 | 282 |
|
210 | 283 | (defn push
|
211 | 284 | "Write some input to a given stream on a channel.
|
212 | 285 |
|
213 |
| - This should probably only be called from pump. |
214 |
| -
|
215 | 286 | Arguments:
|
216 | 287 |
|
217 | 288 | session The clj-libssh2.session.Session object for the current
|
|
384 | 455 | session The clj-libssh2.session.Session object for the current session.
|
385 | 456 | channel This will be bound to the result of a call to open."
|
386 | 457 | [session channel & body]
|
387 |
| - `(let [~channel (open ~session)] |
| 458 | + `(let [session# ~session |
| 459 | + ~channel (open session#)] |
| 460 | + (try |
| 461 | + (do ~@body) |
| 462 | + (finally |
| 463 | + (close session# ~channel) |
| 464 | + (free session# ~channel))))) |
| 465 | + |
| 466 | +(defmacro with-scp-recv-channel |
| 467 | + "A convenience macro like with-channel, but for SCP receive operations. |
| 468 | +
|
| 469 | + Arguments: |
| 470 | +
|
| 471 | + session The clj-libssh2.session.Session object for the current session. |
| 472 | + channel This will be bound to the channel when it's opened. |
| 473 | + path The path of the file to receive from the remote machine. |
| 474 | + fileinfo This will be bound to the Stat struct describing the properties of |
| 475 | + the remote file." |
| 476 | + [session channel path fileinfo & body] |
| 477 | + `(let [session# ~session |
| 478 | + {~channel :channel ~fileinfo :fileinfo} (open-scp-recv session# ~path)] |
| 479 | + (try |
| 480 | + (do ~@body) |
| 481 | + (finally |
| 482 | + (close session# ~channel) |
| 483 | + (free session# ~channel))))) |
| 484 | + |
| 485 | +(defmacro with-scp-send-channel |
| 486 | + "A convenience macro like with-channel, but for sending files using SCP. |
| 487 | +
|
| 488 | + Arguments: |
| 489 | +
|
| 490 | + session The clj-libssh2.session.Session object for the current session. |
| 491 | + channel This will be bound to the channel when it's opened. |
| 492 | + path The path where the file should be places on the remote machine. |
| 493 | + props A map describing the properties which should be applied to the |
| 494 | + file when it's transferred to the other side. The keys and values |
| 495 | + are as follows: |
| 496 | +
|
| 497 | + :atime The last accessed time to be set on the remote file. |
| 498 | + :mode The mode that the remote file should have. |
| 499 | + :mtime The last modified time to be set on the remote file. |
| 500 | + :size The size of the file in bytes." |
| 501 | + [session channel path props & body] |
| 502 | + `(let [session# ~session |
| 503 | + path# ~path |
| 504 | + props# ~props |
| 505 | + ~channel (open-scp-send session# path# props#)] |
388 | 506 | (try
|
389 | 507 | (do ~@body)
|
390 | 508 | (finally
|
391 |
| - (close ~session ~channel) |
392 |
| - (free ~session ~channel))))) |
| 509 | + (close session# ~channel) |
| 510 | + (free session# ~channel))))) |
0 commit comments