Skip to content

Commit 30455ae

Browse files
committed
Better error reporting.
- Use ex-info instead of Exception. Return plenty of metadata with each exception. Most deliberate exceptions now will contain a reference to the clj-libssh2.session.Session being used when it was thrown. - Improve some error messages to be more accurate and/or less confusing. Fixes #5
1 parent 9de5b48 commit 30455ae

File tree

8 files changed

+59
-42
lines changed

8 files changed

+59
-42
lines changed

src/clj_libssh2/agent.clj

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"Functions for interacting with an SSH agent. The agent is expected to be
33
available on the UNIX domain socket referred to by the SSH_AUTH_SOCK
44
environment variable."
5-
(:require [clj-libssh2.error :refer [handle-errors with-timeout]]
5+
(:require [clj-libssh2.error :as error :refer [handle-errors with-timeout]]
6+
[clj-libssh2.libssh2 :as libssh2]
67
[clj-libssh2.libssh2.agent :as libssh2-agent])
78
(:import [com.sun.jna.ptr PointerByReference]))
89

@@ -32,7 +33,10 @@
3233
(case ret
3334
0 (.getValue id)
3435
1 nil
35-
(throw (Exception. "An unknown error occurred")))))
36+
(throw (ex-info "libssh2_agent_get_identity returned a bad value."
37+
{:function "libssh2_agent_get_identity"
38+
:return ret
39+
:session session})))))
3640

3741
(defn authenticate
3842
"Attempt to authenticate a session using the agent.
@@ -50,7 +54,7 @@
5054
[session username]
5155
(let [ssh-agent (libssh2-agent/init (:session session))]
5256
(when (nil? ssh-agent)
53-
(throw (Exception. "Failed to initialize agent.")))
57+
(error/maybe-throw-error session libssh2/ERROR_ALLOC))
5458
(try
5559
(handle-errors session
5660
(with-timeout :agent
@@ -65,7 +69,9 @@
6569
(libssh2-agent/userauth ssh-agent username id)))
6670
id)
6771
false)))
68-
(throw (Exception. "Failed to authenticate with the agent.")))
72+
(throw (ex-info "Failed to authenticate using the SSH agent."
73+
{:username username
74+
:session session})))
6975
true
7076
(finally
7177
(handle-errors session

src/clj_libssh2/authentication.clj

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
[clj-libssh2.agent :as agent]
55
[clj-libssh2.error :refer [handle-errors with-timeout]]
66
[clj-libssh2.libssh2.userauth :as libssh2-userauth])
7-
(:import clojure.lang.PersistentArrayMap))
7+
(:import [java.io FileNotFoundException]
8+
[clojure.lang PersistentArrayMap]))
89

910
(defprotocol Credentials
1011
"A datatype to represent a way of authentication and the necessary data to
@@ -21,9 +22,7 @@
2122
(valid? [credentials] (and (some? username)
2223
(some? passphrase)
2324
(some? private-key)
24-
(some? public-key)
25-
(.exists (file private-key))
26-
(.exists (file public-key)))))
25+
(some? public-key))))
2726

2827
(defrecord PasswordCredentials [username password]
2928
Credentials
@@ -54,7 +53,7 @@
5453
[session credentials]
5554
(doseq [keyfile (map #(% credentials) [:private-key :public-key])]
5655
(when-not (.exists (file keyfile))
57-
(throw (Exception. (format "%s does not exist." keyfile)))))
56+
(throw (FileNotFoundException. keyfile))))
5857
(handle-errors session
5958
(with-timeout :auth
6059
(libssh2-userauth/publickey-fromfile (:session session)
@@ -81,4 +80,6 @@
8180
(authenticate session creds)
8281
(if (< 1 (count m))
8382
(recur (rest m))
84-
(throw (Exception. "Invalid credentials")))))))
83+
(throw (ex-info "Failed to determine credentials type."
84+
{:items (keys credentials)
85+
:session session})))))))

src/clj_libssh2/channel.clj

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@
164164
:else (str v)))
165165
fail-if-forbidden (fn [ret]
166166
(if (= libssh2/ERROR_CHANNEL_REQUEST_DENIED ret)
167-
(throw (Exception. "Setting environment variables is not permitted."))
167+
(throw (ex-info "Setting environment variables is not permitted."
168+
{:session session}))
168169
ret))]
169170
(doseq [[k v] env]
170171
(block session
@@ -322,9 +323,11 @@
322323
(when (and (= pump-fn pull)
323324
(= :eagain new-status)
324325
(< (-> session :options :read-timeout) (- now last-read-time)))
325-
(throw (Exception. (format "Read timeout for %s stream %d"
326-
(-> stream :direction name)
327-
(-> stream :id)))))
326+
(throw (ex-info "Read timeout on a channel."
327+
{:direction (-> stream :direction name)
328+
:id (-> stream :id)
329+
:timeout (-> session :options :read-timeout)
330+
:session session})))
328331
(assoc stream :status new-status :last-read-time now))
329332
stream))
330333

src/clj_libssh2/error.clj

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,9 @@
109109
which may be useful to debug the error handling itself:
110110
111111
:error The error message from error-messages, if any.
112-
:session-error The error message from session-error-message, if any.
113-
:error-code The numeric return value."
112+
:error-code The numeric return value.
113+
:session The clj-libssh2.session.Session object, if any.
114+
:session-error The error message from session-error-message, if any."
114115
[session error-code]
115116
(when (and (some? error-code)
116117
(> 0 error-code)
@@ -121,8 +122,9 @@
121122
default-message
122123
(format "An unknown error occurred: %d" error-code))
123124
{:error default-message
124-
:session-error session-message
125-
:error-code error-code})))))
125+
:error-code error-code
126+
:session session
127+
:session-error session-message})))))
126128

127129
(defmacro handle-errors
128130
"Run some code that might return a negative number to indicate an error.
@@ -197,7 +199,9 @@
197199
timeout# (get-timeout ~timeout)]
198200
(loop [timedout# false]
199201
(if timedout#
200-
(throw (Exception. "Timeout!"))
202+
(throw (ex-info "Timeout exceeded."
203+
{:timeout ~timeout
204+
:timeout-length timeout#}))
201205
(let [r# (do ~@body)]
202206
(if (= r# libssh2/ERROR_EAGAIN)
203207
(recur (< timeout# (- (System/currentTimeMillis) start#)))

src/clj_libssh2/known_hosts.clj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"Utilities for checking the fingerprint of a remote machine against a list of
33
known hosts."
44
(:require [clojure.java.io :refer [file]]
5-
[clj-libssh2.error :refer [handle-errors with-timeout]]
5+
[clj-libssh2.error :as error :refer [handle-errors with-timeout]]
66
[clj-libssh2.libssh2 :as libssh2]
77
[clj-libssh2.libssh2.knownhost :as libssh2-knownhost]
88
[clj-libssh2.libssh2.session :as libssh2-session])
@@ -39,7 +39,9 @@
3939
libssh2/ERROR_HOSTKEY_SIGN
4040
0)
4141
libssh2/KNOWNHOST_CHECK_FAILURE libssh2/ERROR_HOSTKEY_SIGN
42-
(throw (Exception. (format "Unknown return code from libssh2-knownhost/checkp: %d" result)))))
42+
(throw (ex-info "Unknown return code from libssh2_knownhost_checkp."
43+
{:function "libssh2_knownhost_checkp"
44+
:return result}))))
4345

4446
(defn- host-fingerprint
4547
"Get the remote host's fingerprint.
@@ -131,7 +133,7 @@
131133
fail-on-mismatch (-> session-options :fail-unless-known-hosts-matches)
132134
fail-on-missing (-> session-options :fail-if-not-in-known-hosts)]
133135
(when (nil? known-hosts)
134-
(throw (Exception. "Failed to initialize known hosts store.")))
136+
(error/maybe-throw-error session libssh2/ERROR_ALLOC))
135137
(try
136138
(load-known-hosts session known-hosts file)
137139
(check-fingerprint session

src/clj_libssh2/session.clj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[clj-libssh2.libssh2 :as libssh2]
55
[clj-libssh2.libssh2.session :as libssh2-session]
66
[clj-libssh2.authentication :refer [authenticate]]
7-
[clj-libssh2.error :refer [handle-errors with-timeout]]
7+
[clj-libssh2.error :as error :refer [handle-errors with-timeout]]
88
[clj-libssh2.known-hosts :as known-hosts]
99
[clj-libssh2.socket :as socket]))
1010

@@ -37,7 +37,7 @@
3737
[]
3838
(let [session (libssh2-session/init)]
3939
(when-not session
40-
(throw (Exception. "Failed to create a libssh2 session.")))
40+
(error/maybe-throw-error nil libssh2/ERROR_ALLOC))
4141
session))
4242

4343
(defn- create-session-options
@@ -73,15 +73,15 @@
7373
nil or throws an exception if requested."
7474
([session]
7575
(destroy-session session "Shutting down normally." false))
76-
([{session :session} reason raise]
76+
([session reason raise]
7777
(handle-errors nil
7878
(with-timeout :session
79-
(libssh2-session/disconnect session reason)))
79+
(libssh2-session/disconnect (:session session) reason)))
8080
(handle-errors nil
8181
(with-timeout :session
82-
(libssh2-session/free session)))
82+
(libssh2-session/free (:session session))))
8383
(when raise
84-
(throw (Exception. reason)))))
84+
(throw (ex-info reason {:session session})))))
8585

8686
(defn- handshake
8787
"Perform the startup handshake with the remote host.
@@ -147,9 +147,9 @@
147147
(authenticate session credentials)
148148
(swap! sessions conj session)
149149
session
150-
(catch Exception e
150+
(catch Throwable t
151151
(close session)
152-
(throw e)))))
152+
(throw t)))))
153153

154154
(defmacro with-session
155155
"A convenience macro for running some code with a particular session.

src/clj_libssh2/socket.clj

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,18 @@
2929
port)]
3030
(when (> 0 socket)
3131
;; Magic numbers are from libsimplesocket.h
32-
(throw (Exception. (condp = socket
33-
SIMPLE_SOCKET_BAD_ADDRESS
34-
(format "%s is not a valid IP address" address)
32+
(let [message (condp = socket
33+
SIMPLE_SOCKET_BAD_ADDRESS
34+
(format "%s is not a valid IP address" address)
3535

36-
SIMPLE_SOCKET_SOCKET_FAILED
37-
"Failed to create a TCP socket"
36+
SIMPLE_SOCKET_SOCKET_FAILED
37+
"Failed to create a TCP socket"
3838

39-
SIMPLE_SOCKET_CONNECT_FAILED
40-
(format "Failed to connect to %s:%d" address port)
39+
SIMPLE_SOCKET_CONNECT_FAILED
40+
(format "Failed to connect to %s:%d" address port)
4141

42-
"An unknown error occurred"))))
42+
"simple_socket_connect returned a bad value")]
43+
(throw (ex-info message {:socket socket}))))
4344
socket))
4445

4546
(defn select

test/clj_libssh2/test_authentication.clj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
(session/close session))
1717
(is (= 0 (count @session/sessions))))
1818

19-
; This is more fully tested in clj-libssh2.test-authentication
19+
; This is more fully tested in clj-libssh2.test-agent
2020
(deftest agent-authentication-works
2121
(is (auth (->AgentCredentials (test/ssh-user)))))
2222

@@ -49,10 +49,10 @@
4949
(is (valid? unauthorized))
5050
(is (thrown? Exception (auth unauthorized))))
5151
(testing "It fails if the private key file doesn't exist"
52-
(is (not (valid? bad-privkey)))
52+
(is (valid? bad-privkey))
5353
(is (thrown? Exception (auth bad-privkey))))
5454
(testing "It fails if the public key file doesn't exist"
55-
(is (not (valid? bad-pubkey)))
55+
(is (valid? bad-pubkey))
5656
(is (thrown? Exception (auth bad-pubkey))))
5757
(testing "It fails if the passphrase is incorrect"
5858
(is (valid? with-wrong-passphrase))
@@ -78,5 +78,5 @@
7878
(deftest authenticating-with-a-map-fails-if-there's-no-equivalent-record
7979
(is (thrown-with-msg?
8080
Exception
81-
#"Invalid credentials"
81+
#"Failed to determine credentials type"
8282
(auth {:password "foo"}))))

0 commit comments

Comments
 (0)