server session FEATURE accepting NETCONF sessions on more SSH channels

Also some other enhancements and refactoring.
diff --git a/src/session.c b/src/session.c
index 9554add..f53e105 100644
--- a/src/session.c
+++ b/src/session.c
@@ -49,6 +49,67 @@
 
 extern struct nc_server_opts server_opts;
 
+/*
+ * @return 1 - success
+ *         0 - timeout
+ *        -1 - error
+ */
+int
+nc_timedlock(pthread_mutex_t *lock, int timeout, int *elapsed)
+{
+    int ret;
+    struct timespec ts_timeout, ts_old, ts_new;
+
+    if (timeout > 0) {
+        clock_gettime(CLOCK_REALTIME, &ts_timeout);
+
+        if (elapsed) {
+            ts_old = ts_timeout;
+        }
+
+        ts_timeout.tv_sec += timeout / 1000;
+        ts_timeout.tv_nsec += (timeout % 1000) * 1000000;
+
+        ret = pthread_mutex_timedlock(lock, &ts_timeout);
+
+        if (elapsed) {
+            clock_gettime(CLOCK_REALTIME, &ts_new);
+
+            *elapsed += (ts_new.tv_sec - ts_old.tv_sec) * 1000;
+            *elapsed += (ts_new.tv_nsec - ts_old.tv_nsec) / 1000000;
+        }
+    } else if (!timeout) {
+        ret = pthread_mutex_trylock(lock);
+    } else { /* timeout == -1 */
+        ret = pthread_mutex_lock(lock);
+    }
+
+    if (ret == ETIMEDOUT) {
+        /* timeout */
+        return 0;
+    } else if (ret) {
+        /* error */
+        ERR("Mutex lock failed (%s).", strerror(errno));
+        return -1;
+    }
+
+    /* ok */
+    return 1;
+}
+
+void
+nc_subtract_elapsed(int *timeout, struct timespec *old_ts)
+{
+    struct timespec new_ts;
+
+    clock_gettime(CLOCK_MONOTONIC_RAW, &new_ts);
+
+    *timeout -= (new_ts.tv_sec - old_ts->tv_sec) * 1000;
+    *timeout -= (new_ts.tv_nsec - old_ts->tv_nsec) / 1000000;
+
+    *old_ts = new_ts;
+}
+
 API NC_STATUS
 nc_session_get_status(const struct nc_session *session)
 {
@@ -166,54 +227,6 @@
     return NC_MSG_RPC;
 }
 
-/*
- * @return 1 - success
- *         0 - timeout
- *        -1 - error
- */
-int
-nc_timedlock(pthread_mutex_t *lock, int timeout, int *elapsed)
-{
-    int ret;
-    struct timespec ts_timeout, ts_old, ts_new;
-
-    if (timeout > 0) {
-        clock_gettime(CLOCK_REALTIME, &ts_timeout);
-
-        if (elapsed) {
-            ts_old = ts_timeout;
-        }
-
-        ts_timeout.tv_sec += timeout / 1000;
-        ts_timeout.tv_nsec += (timeout % 1000) * 1000000;
-
-        ret = pthread_mutex_timedlock(lock, &ts_timeout);
-
-        if (elapsed) {
-            clock_gettime(CLOCK_REALTIME, &ts_new);
-
-            *elapsed += (ts_new.tv_sec - ts_old.tv_sec) * 1000;
-            *elapsed += (ts_new.tv_nsec - ts_old.tv_nsec) / 1000000;
-        }
-    } else if (!timeout) {
-        ret = pthread_mutex_trylock(lock);
-    } else { /* timeout == -1 */
-        ret = pthread_mutex_lock(lock);
-    }
-
-    if (ret == ETIMEDOUT) {
-        /* timeout */
-        return 0;
-    } else if (ret) {
-        /* error */
-        ERR("Mutex lock failed (%s).", strerror(errno));
-        return -1;
-    }
-
-    /* ok */
-    return 1;
-}
-
 API void
 nc_session_free(struct nc_session *session)
 {
@@ -329,14 +342,44 @@
          * SSH channel). So destroy the SSH session only if there is no other NETCONF session using
          * it.
          */
-        if (!session->ti.libssh.next) {
+        multisession = 0;
+        if (session->ti.libssh.next) {
+            for (siter = session->ti.libssh.next; siter != session; siter = siter->ti.libssh.next) {
+                if (siter->status != NC_STATUS_STARTING) {
+                    multisession = 1;
+                    break;
+                }
+            }
+        }
+
+        if (!multisession) {
+            /* it's not multisession yet, but we still need to free the starting sessions */
+            if (session->ti.libssh.next) {
+                do {
+                    siter = session->ti.libssh.next;
+                    session->ti.libssh.next = siter->ti.libssh.next;
+
+                    /* free starting SSH NETCONF session (channel will be freed in ssh_free()) */
+                    if (session->side == NC_SERVER) {
+                        nc_ctx_lock(-1, NULL);
+                    }
+                    lydict_remove(session->ctx, session->username);
+                    lydict_remove(session->ctx, session->host);
+                    if (session->side == NC_SERVER) {
+                        nc_ctx_unlock();
+                    }
+                    if (!(session->flags & NC_SESSION_SHAREDCTX)) {
+                        ly_ctx_destroy(session->ctx);
+                    }
+
+                    free(siter);
+                } while (session->ti.libssh.next != session);
+            }
             if (connected) {
                 ssh_disconnect(session->ti.libssh.session);
             }
             ssh_free(session->ti.libssh.session);
         } else {
-            /* multiple NETCONF sessions on a single SSH session */
-            multisession = 1;
             /* remove the session from the list */
             for (siter = session->ti.libssh.next; siter->ti.libssh.next != session; siter = siter->ti.libssh.next);
             if (session->ti.libssh.next == siter) {
@@ -346,6 +389,17 @@
                 /* there are still multiple sessions, keep the ring list */
                 siter->ti.libssh.next = session->ti.libssh.next;
             }
+            /* change nc_sshcb_msg() argument, we need a RUNNING session and this one will be freed */
+            if (session->flags & NC_SESSION_SSH_MSG_CB) {
+                for (siter = session->ti.libssh.next; siter->status != NC_STATUS_RUNNING; siter = siter->ti.libssh.next) {
+                    if (siter->ti.libssh.next == session) {
+                        ERRINT;
+                        break;
+                    }
+                }
+                ssh_set_message_callback(session->ti.libssh.session, nc_sshcb_msg, siter);
+                siter->flags |= NC_SESSION_SSH_MSG_CB;
+            }
         }
         break;
 #endif