[Patch 1/1] CLD: Introduce the "New CLD" API

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The "traditional" CLD API is too difficult to program. In particular,
switching from a rigid "Group" policy to arbitrary paths in existing
clients in Chunk and tabled turned out to be next to impossible.
The issue is due to the fundamental nature of the API as based on very
fine-grained events.

So, in the interests of clients, introduce the "new" API. Its basic
definition is in <ncld.h>. It presents a filesystem-like interface,
extended with event callbacks for coarse events, such as an end of
CLD session.

The patch converts all in-tree clients from cldc_xxx to ncld_xxx and
discards some of code that became unused (test/util.c).

Signed-Off-By: Pete Zaitcev <zaitcev@xxxxxxxxxx>

---
 include/Makefile.am    |    2 
 include/ncld.h         |   88 ++
 lib/cldc-dns.c         |   92 +-
 lib/cldc.c             |  856 ++++++++++++++++++++++++++
 test/Makefile.am       |    5 
 test/it-works.c        |  125 ---
 test/load-file-event.c |  244 +------
 test/lock-file-event.c |  269 +-------
 test/save-file-event.c |  245 -------
 test/test.h            |    5 
 test/util.c            |   79 --
 tools/cldcli.c         | 1249 ++++++++++-----------------------------
 12 files changed, 1433 insertions(+), 1826 deletions(-)

diff --git a/include/Makefile.am b/include/Makefile.am
index 533f51d..cb82261 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -1,5 +1,5 @@
 
 EXTRA_DIST = cld-private.h
 
-include_HEADERS = cldc.h hail_log.h cld_common.h
+include_HEADERS = cldc.h hail_log.h cld_common.h ncld.h
 
diff --git a/include/ncld.h b/include/ncld.h
new file mode 100644
index 0000000..8592f2d
--- /dev/null
+++ b/include/ncld.h
@@ -0,0 +1,88 @@
+#ifndef __NCLD_H__
+#define __NCLD_H__
+
+/*
+ * Copyright 2009 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING.  If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+/*
+ * The ncld.h API is a replacement for cldc.h. Do not include both.
+ *
+ * We do not believe into making "internal" structures "opaque"
+ * with pointers to void. Therefore, this header might want include
+ * some legacy definitions or whatnot, but users do not need to.
+ */
+#include <stdbool.h>
+#include <glib.h>
+#include <cldc.h>
+
+struct ncld_sess {
+	char *host;
+	unsigned short port;
+	GMutex *mutex;
+	GCond *cond;
+	GThread *thread;
+	bool is_up;
+	int errc;
+	GList *handles;
+	int to_thread[2];
+	struct cldc_udp *udp;
+	struct cld_timer udp_timer;
+	struct cld_timer_list tlist;
+	void (*event)(void *, unsigned int);
+	void *event_arg;
+};
+
+struct ncld_fh {
+	struct ncld_sess *ses;
+	struct cldc_fh *fh;		/* FIXME cldc_open2 take direct & */
+	bool is_open;
+	int errc;
+	int nios;
+	unsigned int event_mask;
+	void (*event_func)(void *, unsigned int);
+	void *event_arg;
+};
+
+struct ncld_read {
+	/* public to application */
+	const void *ptr;
+	long length;
+
+	struct ncld_fh *fh;
+	/* GCond *cond;   -- abusing the conditional of file handle for now */
+	bool is_done;
+	int errc;
+};
+
+extern struct ncld_sess *ncld_sess_open(const char *host, int port,
+	int *error, void (*event)(void *, unsigned int), void *ev_arg,
+	const char *cld_user, const char *cld_key);
+extern struct ncld_fh *ncld_open(struct ncld_sess *s, const char *fname,
+	unsigned int mode, int *error, unsigned int events,
+	void (*event)(void *, unsigned int), void *ev_arg);
+extern int ncld_del(struct ncld_sess *nsp, const char *fname);
+extern struct ncld_read *ncld_get(struct ncld_fh *fhp, int *error);
+extern void ncld_read_free(struct ncld_read *rp);
+extern int ncld_write(struct ncld_fh *, const void *data, long len);
+extern int ncld_trylock(struct ncld_fh *);
+extern int ncld_qlock(struct ncld_fh *);
+extern int ncld_unlock(struct ncld_fh *);
+extern void ncld_close(struct ncld_fh *);
+extern void ncld_sess_close(struct ncld_sess *s);
+extern void ncld_init(void);
+
+#endif /* __NCLD_H__ */
diff --git a/lib/cldc-dns.c b/lib/cldc-dns.c
index b534015..d67d941 100644
--- a/lib/cldc-dns.c
+++ b/lib/cldc-dns.c
@@ -67,9 +67,7 @@ int cldc_saveaddr(struct cldc_host *hp,
 
 	rc = getaddrinfo(hostname, portstr, &hints, &res0);
 	if (rc) {
-		HAIL_ERR(log, "getaddrinfo(%s,%s) failed: %s",
-			 hostname, portstr, gai_strerror(rc));
-		rc = -EINVAL;
+		rc = -(rc + 1200);
 		goto err_addr;
 	}
 
@@ -86,9 +84,7 @@ int cldc_saveaddr(struct cldc_host *hp,
 	}
 
 	if (!something_suitable) {
-		HAIL_ERR(log, "Host %s port %u has no addresses",
-			 hostname, port);
-		rc = -EINVAL;
+		rc = -1031;
 		goto err_suitable;
 	}
 
@@ -97,9 +93,6 @@ int cldc_saveaddr(struct cldc_host *hp,
 	hp->prio = priority;
 	hp->weight = weight;
 
-	HAIL_DEBUG(log, "%s: found CLD host %s prio %d weight %d",
-		   __func__, hostname, priority, weight);
-
 	freeaddrinfo(res0);
 	return 0;
 
@@ -117,35 +110,25 @@ err_name:
  * on YP-driven networks with nonqualified hostnames (at least for now).
  */
 static int cldc_make_fqdn(char *buf, int size, const char *srvname,
-			  const char *thishost, struct hail_log *log)
+			  const char *thishost)
 {
 	char *s;
 	int nlen;
 	int dlen;
 
 	nlen = strlen(srvname);
-	if (nlen >= size-20) {
-		HAIL_INFO(log, "%s: internal error (nlen %d size %d)",
-			  __func__, nlen, size);
+	if (nlen >= size-20)
 		return -1;
-	}
 
-	if (thishost == NULL) {
-		HAIL_INFO(log, "%s: internal error (null hostname)", __func__);
+	if (thishost == NULL)
 		return -1;
-	}
-	if ((s = strchr(thishost, '.')) == NULL) {
-		HAIL_INFO(log, "%s: hostname is not FQDN: \"%s\"",
-			  __func__, thishost);
+	if ((s = strchr(thishost, '.')) == NULL)
 		return -1;
-	}
 	s++;
 
 	dlen = strlen(s);
-	if (nlen + 1 + dlen + 1 > size) {
-		HAIL_INFO(log, "%s: domain is too long: \"%s\"", __func__, s);
+	if (nlen + 1 + dlen + 1 > size)
 		return -1;
-	}
 
 	memcpy(buf, srvname, nlen);
 	buf[nlen] = '.';
@@ -173,8 +156,7 @@ static void push_host(GList **host_list, struct cldc_host *hp_in)
  * This is not reentrant.  Better be called before any other threads
  * are started.
  */
-int cldc_getaddr(GList **host_list, const char *thishost,
-		struct hail_log *log)
+int cldc_getaddr(GList **host_list, const char *thishost, struct hail_log *log)
 {
 	enum { hostsz = 64 };
 	char cldb[hostsz];
@@ -195,19 +177,26 @@ int cldc_getaddr(GList **host_list, const char *thishost,
 	 * is a lookup in the DNS root (probably the standard-compliant
 	 * dot between "_cld" and "_udp" hurts us here).
 	 */
-	if (cldc_make_fqdn(cldb, hostsz, "_cld._udp", thishost, log) != 0)
+	if (cldc_make_fqdn(cldb, hostsz, "_cld._udp", thishost) != 0) {
+		if (log)
+			HAIL_INFO(log, "internal error in cldc_make_fqdn(%s)",
+				  thishost);
 		return -1;
+	}
 
 do_try_again:
 	rc = res_search(cldb, ns_c_in, ns_t_srv, resp, 512);
 	if (rc < 0) {
 		switch (h_errno) {
 		case HOST_NOT_FOUND:
-			HAIL_INFO(log, "%s: No _cld._udp SRV record", __func__);
+			if (log)
+				HAIL_INFO(log, "%s: No _cld._udp SRV record",
+					  __func__);
 			return -1;
 		case NO_DATA:
-			HAIL_INFO(log, "%s: Cannot find _cld._udp SRV record",
-				  __func__);
+			if (log)
+				HAIL_INFO(log, "%s: Cannot find _cld._udp"
+					  " SRV record", __func__);
 			return -1;
 		case TRY_AGAIN:
 			if (search_retries-- > 0)
@@ -215,20 +204,24 @@ do_try_again:
 			/* fall through */
 		case NO_RECOVERY:
 		default:
-			HAIL_ERR(log, "%s: res_search error (%d): %s",
-				 __func__, h_errno, hstrerror(h_errno));
+			if (log)
+				HAIL_ERR(log, "%s: res_search error (%d): %s",
+					 __func__, h_errno, hstrerror(h_errno));
 			return -1;
 		}
 	}
 	rlen = rc;
 
 	if (rlen == 0) {
-		HAIL_INFO(log, "%s: res_search returned empty reply", __func__);
+		if (log)
+			HAIL_INFO(log, "%s: res_search returned empty reply",
+				  __func__);
 		return -1;
 	}
 
 	if (ns_initparse(resp, rlen, &nsb) < 0) {
-		HAIL_ERR(log, "%s: ns_initparse error", __func__);
+		if (log)
+			HAIL_ERR(log, "%s: ns_initparse error", __func__);
 		return -1;
 	}
 
@@ -246,33 +239,46 @@ do_try_again:
 		case ns_t_srv:
 			rrlen = ns_rr_rdlen(rrb);
 			if (rrlen < 8) {	/* 2+2+2 and 2 for host */
-				HAIL_DEBUG(log, "%s: SRV len %d",
-					   __func__, rrlen);
+				if (log)
+					HAIL_DEBUG(log, "%s: SRV len %d",
+						   __func__, rrlen);
 				break;
 			}
 			p = ns_rr_rdata(rrb);
 			rc = dn_expand(resp, resp+rlen, p+6, hostb, hostsz);
 			if (rc < 0) {
-				HAIL_DEBUG(log, "%s: dn_expand error %d",
-					   __func__, rc);
+				if (log)
+					HAIL_DEBUG(log,
+						   "%s: dn_expand error %d",
+						   __func__, rc);
 				break;
 			}
 			if (rc < 2) {
-				HAIL_DEBUG(log, "%s: dn_expand short %d",
-					   __func__, rc);
+				if (log)
+					HAIL_DEBUG(log,
+						   "%s: dn_expand short %d",
+						   __func__, rc);
 				break;
 			}
 
 			if (cldc_saveaddr(&hp, ns_get16(p+0),
 					  ns_get16(p+2), ns_get16(p+4),
-					  rc, hostb, log))
+					  rc, hostb, NULL))
 				break;
 
+			if (log)
+				HAIL_DEBUG(log, "%s: found CLD host %s port %u"
+					   " prio %d weight %d",
+					   __func__, hp.host, hp.port,
+					   hp.prio, hp.weight);
+
 			push_host(host_list, &hp);
 			break;
 		case ns_t_cname:	/* impossible, but */
-			HAIL_DEBUG(log, "%s: CNAME in SRV request, ignored",
-				   __func__);
+			if (log)
+				HAIL_DEBUG(log,
+					   "%s: CNAME in SRV request, ignored",
+					   __func__);
 			break;
 		default:
 			;
diff --git a/lib/cldc.c b/lib/cldc.c
index 592dfcc..07b0d1c 100644
--- a/lib/cldc.c
+++ b/lib/cldc.c
@@ -30,13 +30,17 @@
 #include <errno.h>
 #include <time.h>
 #include <stdarg.h>
+#include <syslog.h>
+#include <poll.h>
 #include <openssl/sha.h>
 #include <openssl/hmac.h>
 #include <glib.h>
 #include <cld-private.h>
 #include <cldc.h>
 #include <cld_msg_rpc.h>
-#include <syslog.h>
+#include <ncld.h>
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
 
 enum {
 	CLDC_MSG_EXPIRE		= 5 * 60,
@@ -656,7 +660,11 @@ static void sess_expire(struct cldc_session *sess)
 	sess->ops->timer_ctl(sess->private, false, NULL, NULL, 0);
 
 	sess->ops->event(sess->private, sess, NULL, CE_SESS_FAILED);
-	/* FIXME why not sess_free here */
+	/*
+	 * Do not do sess_free here, or else the application is bound to
+	 * step into use-after-free. Only call sess_free when requested.
+	 * However, it may be requested right from this callback. Or later.
+	 */
 }
 
 static int sess_send_pkt(struct cldc_session *sess,
@@ -1275,6 +1283,850 @@ char *cldc_dirent_name(struct cld_dirent_cur *dc)
 }
 
 /*
+ * On error, return the code (not negated code like a kernel function would).
+ */
+static int ncld_getsrv(char **hostp, unsigned short *portp)
+{
+	enum { hostsz = 64 };
+	char hostb[hostsz];
+	GList *host_list = NULL;
+	GList *tmp;
+	struct cldc_host *hp;
+
+	if (gethostname(hostb, hostsz-1) < 0)
+		return errno;
+	hostb[hostsz-1] = 0;
+
+	if (cldc_getaddr(&host_list, hostb, NULL))
+		return 1001;
+
+	/*
+	 * This is a good place to compare weights and priorities, maybe later.
+	 * For now, just grab first host in the list.
+	 */
+	hp = host_list->data;		/* cannot be NULL if success above */
+	*hostp = hp->host;
+	*portp = hp->port;
+
+	for (tmp = host_list; tmp; tmp = tmp->next) {
+		hp = tmp->data;
+		free(hp->host);
+		free(hp);
+	}
+	g_list_free(host_list);
+	return 0;
+}
+
+static int ncld_gethost(char **hostp, unsigned short *portp,
+			const char *hostname, int port)
+{
+	if (port <= 0 || port >= 65536)
+		return EINVAL;
+	*portp = port;
+
+	if (!(*hostp = strdup(hostname)))
+		return ENOMEM;
+
+	return 0;
+}
+
+static void ncld_udp_timer_event(struct cld_timer *timer)
+{
+	struct ncld_sess *nsp = timer->userdata;
+	struct cldc_udp *udp = nsp->udp;
+
+	if (udp->cb)
+		udp->cb(udp->sess, udp->cb_private);
+}
+
+enum {
+	NCLD_CMD_END = 0,
+	NCLD_CMD_SESEV		/* argument - 4 bytes in host order */
+};
+
+/*
+ * All the error printouts are likely to be lost for daemons, but it's
+ * not a big deal. We abort instead of exist to indicate that something
+ * went wrong, so system features should report it (usualy as a core).
+ * When debugging, strace or -F mode will capture the output.
+ */
+static void ncld_thread_command(struct ncld_sess *nsp)
+{
+	ssize_t rrc;
+	unsigned char cmd;
+	uint32_t what;
+
+	rrc = read(nsp->to_thread[0], &cmd, 1);
+	if (rrc < 0) {
+		fprintf(stderr, "read error: %s\n", strerror(errno));
+		abort();
+	}
+	if (rrc < 1) {
+		fprintf(stderr, "short read\n");
+		abort();
+	}
+
+	if (cmd == NCLD_CMD_END) {
+		/* No answer to requestor. Wait with g_thread_join. */
+		g_thread_exit(NULL);
+	} else if (cmd == NCLD_CMD_SESEV) {
+		rrc = read(nsp->to_thread[0], &what, sizeof(uint32_t));
+		if (rrc < sizeof(uint32_t)) {
+			fprintf(stderr, "bad read param\n");
+			g_thread_exit(NULL);
+		}
+		if (nsp->event)
+			(*nsp->event)(nsp->event_arg, what);
+	} else {
+		fprintf(stderr, "bad command 0x%x\n", cmd);
+		abort();
+	}
+}
+
+static gpointer ncld_sess_thr(gpointer data)
+{
+	struct ncld_sess *nsp = data;
+	struct pollfd pfd[2];
+	time_t tmo;
+	int i;
+	int rc;
+
+	for (;;) {
+		g_mutex_lock(nsp->mutex);
+		tmo = cld_timers_run(&nsp->tlist);
+		g_mutex_unlock(nsp->mutex);
+
+		memset(pfd, 0, sizeof(pfd));
+		pfd[0].fd = nsp->to_thread[0];
+		pfd[0].events = POLLIN;
+		pfd[1].fd = nsp->udp->fd;
+		pfd[1].events = POLLIN;
+
+		rc = poll(pfd, 2, tmo ? tmo*1000 : -1);
+		if (rc == 0)
+			continue;
+		if (rc < 0) {
+			fprintf(stderr, "poll error: %s\n", strerror(errno));
+			abort();
+		}
+
+		for (i = 0; i < ARRAY_SIZE(pfd); i++) {
+			if (pfd[i].revents) {
+				if (i == 0) {
+					ncld_thread_command(nsp);
+				} else {
+					g_mutex_lock(nsp->mutex);
+					cldc_udp_receive_pkt(nsp->udp);
+					g_mutex_unlock(nsp->mutex);
+				}
+			}
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * Ask the thread to exit and wait until it does.
+ */
+static void ncld_thr_end(struct ncld_sess *nsp)
+{
+	unsigned char cmd;
+
+	cmd = NCLD_CMD_END;
+	write(nsp->to_thread[1], &cmd, 1);
+	g_thread_join(nsp->thread);
+}
+
+static bool ncld_p_timer_ctl(void *priv, bool add,
+			     int (*cb)(struct cldc_session *, void *),
+			     void *cb_priv, time_t secs)
+{
+	struct ncld_sess *nsp = priv;
+	struct cldc_udp *udp = nsp->udp;
+
+	if (add) {
+		udp->cb = cb;
+		udp->cb_private = cb_priv;
+		cld_timer_add(&nsp->tlist, &nsp->udp_timer, time(NULL) + secs);
+	} else {
+		cld_timer_del(&nsp->tlist, &nsp->udp_timer);
+	}
+	return true;
+}
+
+static int ncld_p_pkt_send(void *priv, const void *addr, size_t addrlen,
+			       const void *buf, size_t buflen)
+{
+	struct ncld_sess *nsp = priv;
+	return cldc_udp_pkt_send(nsp->udp, addr, addrlen, buf, buflen);
+}
+
+static void ncld_p_event(void *priv, struct cldc_session *csp,
+			 struct cldc_fh *fh, uint32_t what)
+{
+	struct ncld_sess *nsp = priv;
+	unsigned char cmd;
+
+	if (what == CE_SESS_FAILED) {
+		if (nsp->udp->sess != csp)
+			abort();
+		nsp->is_up = false;
+		/* XXX wake up all I/O waiters here */
+		/*
+		 * This is a trick. As a direct callback from clcd layer,
+		 * we are running under nsp->mutex, so we cannot call back
+		 * into a user of ncld. If we do, it may invoke another
+		 * ncld operation and deadlock. So, bump session callbacks
+		 * into the part of the helper thread that runs unlocked.
+		 */
+		cmd = NCLD_CMD_SESEV;
+		write(nsp->to_thread[1], &cmd, 1);
+		write(nsp->to_thread[1], &what, sizeof(what));
+	}
+}
+
+static struct cldc_ops ncld_ops = {
+	.timer_ctl	= ncld_p_timer_ctl,
+	.pkt_send	= ncld_p_pkt_send,
+	.event		= ncld_p_event,
+	.errlog		= NULL,
+};
+
+static int ncld_new_sess(struct cldc_call_opts *copts, enum cle_err_codes errc)
+{
+	struct ncld_sess *nsp = copts->private;
+
+	/*
+	 * All callbacks from cldc layer run on the context of the thread
+	 * with nsp->mutex locked, so we don't lock it again here.
+	 */
+	nsp->errc = errc;
+	nsp->is_up = true;
+	g_cond_broadcast(nsp->cond);
+	return 0;
+}
+
+static int ncld_wait_session(struct ncld_sess *nsp)
+{
+	g_mutex_lock(nsp->mutex);
+	while (!nsp->is_up)
+		g_cond_wait(nsp->cond, nsp->mutex);
+	g_mutex_unlock(nsp->mutex);
+	return nsp->errc;
+}
+
+/*
+ * Initiating session may take a while, since we retry failures.
+ * We promise that eventually we return from this function.
+ *
+ * On error, returns NULL and sets the error code (system errno if below 1000,
+ * otherwise our own mysterious codes that you should just print in decimal).
+ *
+ * Keep in mind that the (*event)() is likely to be called on a context
+ * of the extra thread that we spawn. At least we won't steal some random
+ * context and monkey with signals. Also, (*event)() may be called before
+ * this function returns, with the session going down, for example.
+ * This is kind of dirty, but oh well. Maybe we'll fix this later.
+ *
+ * @param host Host name (NULL if resolving SRV records)
+ * @param port Port
+ * @param error Buffer for the error code
+ * @param ev_func Session event function (ok to be NULL)
+ * @param ev_arg User-supplied argument to the session event function
+ * @param cld_user The user identifier to be used to authentication
+ * @param cld_key The user key to be used to authentication
+ */
+struct ncld_sess *ncld_sess_open(const char *host, int port, int *error,
+	void (*ev_func)(void *, unsigned int), void *ev_arg,
+	const char *cld_user, const char *cld_key)
+{
+	struct ncld_sess *nsp;
+	struct cldc_call_opts copts;
+	int err;
+	GError *gerr;
+	int rc;
+
+	err = ENOMEM;
+	nsp = malloc(sizeof(struct ncld_sess));
+	if (!nsp)
+		goto out_sesalloc;
+	memset(nsp, 0, sizeof(struct ncld_sess));
+	cld_timer_init(&nsp->udp_timer, "nsp-udp-timer", ncld_udp_timer_event,
+		       nsp);
+	nsp->mutex = g_mutex_new();
+	if (!nsp->mutex)
+		goto out_mutex;
+	nsp->cond = g_cond_new();
+	if (!nsp->cond)
+		goto out_cond;
+
+	if (!host) {
+		err = ncld_getsrv(&nsp->host, &nsp->port);
+		if (err)
+			goto out_srv;
+	} else {
+		err = ncld_gethost(&nsp->host, &nsp->port, host, port);
+		if (err)
+			goto out_srv;
+	}
+
+	nsp->event = ev_func;
+	nsp->event_arg = ev_arg;
+
+	if (pipe(nsp->to_thread) < 0) {
+		err = errno;
+		goto out_pipe_to;
+	}
+
+	if (cldc_udp_new(nsp->host, nsp->port, &nsp->udp)) {
+		err = 1023;
+		goto out_udp;
+	}
+
+	nsp->thread = g_thread_create(ncld_sess_thr, nsp, TRUE, &gerr);
+	if (nsp->thread == NULL) {
+		err = 1022;
+		goto out_thread;
+	}
+
+	memset(&copts, 0, sizeof(copts));
+	copts.cb = ncld_new_sess;
+	copts.private = nsp;
+	if (cldc_new_sess(&ncld_ops, &copts, nsp->udp->addr, nsp->udp->addr_len,
+			  cld_user, cld_key, nsp, &nsp->udp->sess)) {
+		err = 1024;
+		goto out_session;
+	}
+
+	/* nsp->udp->sess->log.verbose = 1; */
+
+	rc = ncld_wait_session(nsp);
+	if (rc) {
+		err = 1100 + rc % 1000;
+		goto out_start;
+	}
+
+	return nsp;
+
+out_start:
+	cldc_kill_sess(nsp->udp->sess);
+out_session:
+	ncld_thr_end(nsp);
+out_thread:
+	cldc_udp_free(nsp->udp);
+out_udp:
+	close(nsp->to_thread[0]);
+	close(nsp->to_thread[1]);
+out_pipe_to:
+	free(nsp->host);
+out_srv:
+	g_cond_free(nsp->cond);
+out_cond:
+	g_mutex_free(nsp->mutex);
+out_mutex:
+	free(nsp);
+out_sesalloc:
+	*error = err;
+	return NULL;
+}
+
+static int ncld_open_cb(struct cldc_call_opts *copts, enum cle_err_codes errc)
+{
+	struct ncld_fh *fhp = copts->private;
+
+	fhp->errc = errc;
+	fhp->is_open = true;
+	g_cond_broadcast(fhp->ses->cond);
+	return 0;
+}
+
+static int ncld_wait_open(struct ncld_fh *fhp)
+{
+	struct ncld_sess *nsp = fhp->ses;
+
+	g_mutex_lock(nsp->mutex);
+	while (!fhp->is_open)
+		g_cond_wait(nsp->cond, nsp->mutex);
+	g_mutex_unlock(nsp->mutex);
+	return fhp->errc;
+}
+
+/*
+ * We do not provide anything like ncld_set_callback because it inevitably
+ * leads to lost events. Unused arguments are not too onerous, so just
+ * put zero into the events mask if you don't want notifications.
+ *
+ * FIXME It's a moot point for now anyway, since we don't have unlock-notify,
+ * or a waiting lock anyway.
+ *
+ * On error, return NULL and set the error code (can be errno or our own code).
+ */
+struct ncld_fh *ncld_open(struct ncld_sess *nsp, const char *fname,
+	unsigned int mode, int *error, unsigned int events,
+	void (*ev_func)(void *, unsigned int), void *ev_arg)
+{
+	struct ncld_fh *fhp;
+	struct cldc_call_opts copts;
+	int err;
+	int rc;
+
+	err = EBUSY;
+	if (!nsp->is_up)
+		goto out_session;
+
+	err = ENOMEM;
+	fhp = malloc(sizeof(struct ncld_fh));
+	if (!fhp)
+		goto out_alloc;
+	memset(fhp, 0, sizeof(struct ncld_fh));
+	fhp->ses = nsp;
+	fhp->event_mask = events;
+	fhp->event_func = ev_func;
+	fhp->event_arg = ev_arg;
+
+	g_mutex_lock(nsp->mutex);
+	memset(&copts, 0, sizeof(copts));
+	copts.cb = ncld_open_cb;
+	copts.private = fhp;
+	rc = cldc_open(nsp->udp->sess, &copts, fname, mode, events, &fhp->fh);
+	if (rc) {
+		err = -rc;
+		g_mutex_unlock(nsp->mutex);
+		goto out_open;
+	}
+	g_mutex_unlock(nsp->mutex);
+
+	rc = ncld_wait_open(fhp);
+	if (rc) {
+		err = 1100 + rc;
+		goto out_start;
+	}
+
+	nsp->handles = g_list_prepend(nsp->handles, fhp);
+	return fhp;
+
+out_start:
+	/*
+	 * This is screwy, but we do nothing here. There is no way to free
+	 * a cldc file handle that failed its open. It is stuck in the
+	 * array of handles until the session closes (although it can be
+	 * garbage-collected if we're lucky).
+	 */
+out_open:
+	free(fhp);
+out_alloc:
+out_session:
+	*error = err;
+	return NULL;
+}
+
+struct ncld_delio {
+	struct ncld_sess *ses;
+	bool is_done;
+	int errc;
+};
+
+static int ncld_del_cb(struct cldc_call_opts *copts, enum cle_err_codes errc)
+{
+	struct ncld_delio *dp = copts->private;
+	struct ncld_sess *nsp = dp->ses;
+
+	dp->errc = errc;
+	dp->is_done = true;
+	g_cond_broadcast(nsp->cond);
+	return 0;
+}
+
+static int ncld_wait_del(struct ncld_delio *dp)
+{
+	struct ncld_sess *nsp = dp->ses;
+
+	g_mutex_lock(nsp->mutex);
+	while (!dp->is_done)
+		g_cond_wait(nsp->cond, nsp->mutex);
+	g_mutex_unlock(nsp->mutex);
+	return dp->errc;
+}
+
+int ncld_del(struct ncld_sess *nsp, const char *fname)
+{
+	struct cldc_call_opts copts;
+	struct ncld_delio dpb;
+	int rc;
+
+	if (!nsp->is_up)
+		return -EBUSY;
+
+	memset(&dpb, 0, sizeof(struct ncld_delio));
+	dpb.ses = nsp;
+
+	g_mutex_lock(nsp->mutex);
+	memset(&copts, 0, sizeof(copts));
+	copts.cb = ncld_del_cb;
+	copts.private = &dpb;
+	rc = cldc_del(nsp->udp->sess, &copts, fname);
+	if (rc) {
+		g_mutex_unlock(nsp->mutex);
+		return -rc;
+	}
+	/* XXX A delete operation is not accounted for end-session */
+	g_mutex_unlock(nsp->mutex);
+
+	rc = ncld_wait_del(&dpb);
+	if (rc)
+		return rc + 1100;
+
+	return 0;
+}
+
+static int ncld_read_cb(struct cldc_call_opts *copts, enum cle_err_codes errc)
+{
+	struct ncld_read *rp = copts->private;
+	struct ncld_fh *fhp = rp->fh;
+
+	if (errc) {
+		rp->errc = errc;
+	} else {
+		char *p;
+		size_t l;
+		cldc_call_opts_get_data(copts, &p, &l);
+		/* use assignments to defeat dumb gcc warnings. */
+		rp->ptr = p;
+		rp->length = l;
+	}
+	rp->is_done = true;
+	g_cond_broadcast(fhp->ses->cond);
+	return 0;
+}
+
+static int ncld_wait_read(struct ncld_read *rp)
+{
+	struct ncld_fh *fhp = rp->fh;
+	struct ncld_sess *nsp = fhp->ses;
+
+	g_mutex_lock(nsp->mutex);
+	while (!rp->is_done)
+		g_cond_wait(nsp->cond, nsp->mutex);
+	--fhp->nios;
+	g_mutex_unlock(nsp->mutex);
+	return rp->errc;
+}
+
+/*
+ * @error Error code buffer.
+ * @return Pointer to struct ncld_read or NULL if error.
+ */
+struct ncld_read *ncld_get(struct ncld_fh *fhp, int *error)
+{
+	struct ncld_sess *nsp = fhp->ses;
+	struct ncld_read *rp;
+	struct cldc_call_opts copts;
+	int rc;
+
+	if (!fhp->is_open) {
+		*error = EBUSY;
+		return NULL;
+	}
+
+	rp = malloc(sizeof(struct ncld_read));
+	if (!rp) {
+		*error = ENOMEM;
+		return NULL;
+	}
+	memset(rp, 0, sizeof(struct ncld_read));
+	rp->fh = fhp;
+
+	g_mutex_lock(nsp->mutex);
+	memset(&copts, 0, sizeof(copts));
+	copts.cb = ncld_read_cb;
+	copts.private = rp;
+	rc = cldc_get(fhp->fh, &copts, false);
+	if (rc) {
+		g_mutex_unlock(nsp->mutex);
+		free(rp);
+		*error = -rc;
+		return NULL;
+	}
+	fhp->nios++;
+	g_mutex_unlock(nsp->mutex);
+
+	rc = ncld_wait_read(rp);
+	if (rc) {
+		free(rp);
+		*error = rc + 1100;
+		return NULL;
+	}
+
+	return rp;
+}
+
+void ncld_read_free(struct ncld_read *rp)
+{
+
+	/*
+	 * FIXME: Actually the rp->ptr points to sess->msg_buf, so we
+	 * cannot issue 2 cldc_get independently.
+	 */
+	/* free(rp->ptr); */
+
+	free(rp);
+}
+
+struct ncld_genio {
+	struct ncld_fh *fh;
+	bool is_done;
+	int errc;
+};
+
+static int ncld_genio_cb(struct cldc_call_opts *copts, enum cle_err_codes errc)
+{
+	struct ncld_genio *ap = copts->private;
+	struct ncld_fh *fhp = ap->fh;
+
+	ap->errc = errc;
+	ap->is_done = true;
+	g_cond_broadcast(fhp->ses->cond);
+	return 0;
+}
+
+static int ncld_wait_genio(struct ncld_genio *ap)
+{
+	struct ncld_fh *fhp = ap->fh;
+	struct ncld_sess *nsp = fhp->ses;
+
+	g_mutex_lock(nsp->mutex);
+	while (!ap->is_done)
+		g_cond_wait(nsp->cond, nsp->mutex);
+	--fhp->nios;
+	g_mutex_unlock(nsp->mutex);
+	return ap->errc;
+}
+
+/*
+ * @return: Zero or error code.
+ */
+int ncld_write(struct ncld_fh *fhp, const void *data, long len)
+{
+	struct ncld_sess *nsp = fhp->ses;
+	struct cldc_call_opts copts;
+	struct ncld_genio apb;
+	int rc;
+
+	if (!fhp->is_open)
+		return -EBUSY;
+
+	memset(&apb, 0, sizeof(struct ncld_genio));
+	apb.fh = fhp;
+
+	g_mutex_lock(nsp->mutex);
+	memset(&copts, 0, sizeof(copts));
+	copts.cb = ncld_genio_cb;
+	copts.private = &apb;
+	rc = cldc_put(fhp->fh, &copts, data, len);
+	if (rc) {
+		g_mutex_unlock(nsp->mutex);
+		return -rc;
+	}
+	fhp->nios++;
+	g_mutex_unlock(nsp->mutex);
+
+	rc = ncld_wait_genio(&apb);
+	if (rc)
+		return rc + 1100;
+
+	return 0;
+}
+
+int ncld_trylock(struct ncld_fh *fhp)
+{
+	struct ncld_sess *nsp = fhp->ses;
+	struct cldc_call_opts copts;
+	struct ncld_genio apb;
+	int rc;
+
+	if (!fhp->is_open)
+		return -EBUSY;
+
+	memset(&apb, 0, sizeof(struct ncld_genio));
+	apb.fh = fhp;
+
+	g_mutex_lock(nsp->mutex);
+	memset(&copts, 0, sizeof(copts));
+	copts.cb = ncld_genio_cb;
+	copts.private = &apb;
+	rc = cldc_lock(fhp->fh, &copts, 0, false);
+	if (rc) {
+		g_mutex_unlock(nsp->mutex);
+		return -rc;
+	}
+	fhp->nios++;
+	g_mutex_unlock(nsp->mutex);
+
+	rc = ncld_wait_genio(&apb);
+	if (rc)
+		return rc + 1100;
+
+	return 0;
+}
+
+int ncld_unlock(struct ncld_fh *fhp)
+{
+	struct ncld_sess *nsp = fhp->ses;
+	struct cldc_call_opts copts;
+	struct ncld_genio apb;
+	int rc;
+
+	if (!fhp->is_open)
+		return -EBUSY;
+
+	memset(&apb, 0, sizeof(struct ncld_genio));
+	apb.fh = fhp;
+
+	g_mutex_lock(nsp->mutex);
+	memset(&copts, 0, sizeof(copts));
+	copts.cb = ncld_genio_cb;
+	copts.private = &apb;
+	rc = cldc_unlock(fhp->fh, &copts);
+	if (rc) {
+		g_mutex_unlock(nsp->mutex);
+		return -rc;
+	}
+	fhp->nios++;
+	g_mutex_unlock(nsp->mutex);
+
+	rc = ncld_wait_genio(&apb);
+	if (rc)
+		return rc + 1100;
+
+	return 0;
+}
+
+/*
+ * This probably does not do what you expect (or anything useful actually).
+ * When locks conflict, instead of waiting until the lock is acquired,
+ * we return when it was put in the queue on the server (with code 1).
+ * Applications using this supply a callback and a mask to ncld_open.
+ * FIXME: This does not work at present, since server does not post them.
+ */
+int ncld_qlock(struct ncld_fh *fhp)
+{
+	struct ncld_sess *nsp = fhp->ses;
+	struct cldc_call_opts copts;
+	struct ncld_genio apb;
+	int rc;
+
+	if (!fhp->is_open)
+		return -EBUSY;
+
+	memset(&apb, 0, sizeof(struct ncld_genio));
+	apb.fh = fhp;
+
+	g_mutex_lock(nsp->mutex);
+	memset(&copts, 0, sizeof(copts));
+	copts.cb = ncld_genio_cb;
+	copts.private = &apb;
+	rc = cldc_lock(fhp->fh, &copts, 0, true);
+	if (rc) {
+		g_mutex_unlock(nsp->mutex);
+		return -rc;
+	}
+	fhp->nios++;
+	g_mutex_unlock(nsp->mutex);
+
+	rc = ncld_wait_genio(&apb);
+	if (rc) {
+		if (rc != CLE_LOCK_PENDING)
+			return rc + 1100;
+		rc = 1;
+	}
+	return rc;
+}
+
+static int ncld_close_cb(struct cldc_call_opts *copts, enum cle_err_codes errc)
+{
+	struct ncld_fh *fhp = copts->private;
+
+	fhp->errc = errc;
+	fhp->is_open = false;
+	g_cond_broadcast(fhp->ses->cond);
+	return 0;
+}
+
+/*
+ * The poisoning in ncld_close is mostly for bug-catching. Do not try to
+ * force-close file handles if other threads bomb them I/Os. This cannot work
+ * for us, because users keep pointers, not file descriptor numbers.
+ * Applications should stop application I/O first, then close.
+ */
+void ncld_close(struct ncld_fh *fhp)
+{
+	struct ncld_sess *nsp = fhp->ses;
+	struct cldc_call_opts copts;
+	int rc;
+
+	if (!fhp->is_open)
+		abort();
+
+	g_mutex_lock(nsp->mutex);
+	memset(&copts, 0, sizeof(copts));
+	copts.cb = ncld_close_cb;
+	copts.private = fhp;
+	rc = cldc_close(fhp->fh, &copts);
+	g_mutex_unlock(nsp->mutex);
+
+	if (rc == 0) {
+		g_mutex_lock(nsp->mutex);
+		while (fhp->is_open)
+			g_cond_wait(nsp->cond, nsp->mutex);
+		g_mutex_unlock(nsp->mutex);
+		/* At this point, we ignore fhp->errc. */
+	}
+
+	/*
+	 * The cldc layer dowes not allow us to kill handles willy-nilly,
+	 * so the only thing we can do is to wait for I/Os to terminate.
+	 *
+	 * N.B.: this is making use of the fact that we only have one
+	 * conditional per session, and therefore end-of-I/O pokes us here.
+	 */
+	g_mutex_lock(nsp->mutex);
+	while (fhp->nios)
+		g_cond_wait(nsp->cond, nsp->mutex);
+	g_mutex_unlock(nsp->mutex);
+
+	nsp->handles = g_list_remove_all(nsp->handles, fhp);
+	free(fhp);
+}
+
+static void ncld_func_close(gpointer data, gpointer priv)
+{
+	ncld_close(data);
+}
+
+void ncld_sess_close(struct ncld_sess *nsp)
+{
+
+	g_list_foreach(nsp->handles, ncld_func_close, NULL);
+	g_list_free(nsp->handles);
+
+	cldc_kill_sess(nsp->udp->sess);
+	ncld_thr_end(nsp);
+	cldc_udp_free(nsp->udp);
+	close(nsp->to_thread[0]);
+	close(nsp->to_thread[1]);
+	g_cond_free(nsp->cond);
+	g_mutex_free(nsp->mutex);
+	free(nsp->host);
+	free(nsp);
+}
+
+void ncld_init(void)
+{
+	cldc_init();
+}
+
+/*
  * For extra safety, call cldc_init after g_thread_init, if present.
  * Currently we just call srand(), but since we use GLib, we may need
  * to add some Glib stuff here and that must come after g_thread_init.
diff --git a/test/Makefile.am b/test/Makefile.am
index ff4d5c7..9e66458 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -26,15 +26,10 @@ check_PROGRAMS		= it-works \
 			  save-file-event load-file-event lock-file-event
 
 TESTLDADD		= ../lib/libcldc.la	\
-			  libtest.a		\
 			  @GLIB_LIBS@ @CRYPTO_LIBS@
 it_works_LDADD		= $(TESTLDADD)
 save_file_event_LDADD	= $(TESTLDADD)
 load_file_event_LDADD	= $(TESTLDADD)
 lock_file_event_LDADD	= $(TESTLDADD)
 
-noinst_LIBRARIES	= libtest.a
-
-libtest_a_SOURCES	= util.c
-
 TESTS_ENVIRONMENT=top_srcdir=$(top_srcdir)
diff --git a/test/it-works.c b/test/it-works.c
index 085ea6b..d8f9ec1 100644
--- a/test/it-works.c
+++ b/test/it-works.c
@@ -26,95 +26,17 @@
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
-#include <cldc.h>
+#include <ncld.h>
 #include "test.h"
 
-static struct cldc_udp *udp;
-static struct cld_timer udp_tm;
-static struct cld_timer_list tlist;
-
-static bool do_timer_ctl(void *priv, bool add,
-			 int (*cb)(struct cldc_session *, void *),
-			 void *cb_priv, time_t secs)
-{
-	if (priv != udp) {
-		fprintf(stderr, "IE0: misuse of timer\n");
-		exit(1);
-	}
-
-	if (add) {
-		udp->cb = cb;
-		udp->cb_private = cb_priv;
-		cld_timer_add(&tlist, &udp_tm, time(NULL) + secs);
-	} else {
-		cld_timer_del(&tlist, &udp_tm);
-	}
-
-	return true;
-}
-
-static void timer_udp_event(struct cld_timer *timer)
-{
-	if (timer->userdata != udp) {
-		fprintf(stderr, "IE1: misuse of timer\n");
-		exit(1);
-	}
-
-	if (udp->cb)
-		udp->cb(udp->sess, udp->cb_private);
-}
-
-static void do_event(void *private, struct cldc_session *sess,
-		     struct cldc_fh *fh, uint32_t event_mask)
-{
-	fprintf(stderr, "EVENT(%x)\n", event_mask);
-}
-
-static int end_sess_cb(struct cldc_call_opts *copts, enum cle_err_codes errc)
-{
-	if (errc != CLE_OK) {
-		fprintf(stderr, "end-sess failed: %d\n", errc);
-		exit(1);
-	}
-
-	/* session ended; success */
-	exit(0);
-	return 0;
-}
-
-static int new_sess_cb(struct cldc_call_opts *copts_in, enum cle_err_codes errc)
-{
-	struct cldc_call_opts copts;
-	int rc;
-
-	if (errc != CLE_OK) {
-		fprintf(stderr, "new-sess failed: %d\n", errc);
-		exit(1);
-	}
-
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = end_sess_cb;
-
-	rc = cldc_end_sess(udp->sess, &copts);
-	if (rc) {
-		fprintf(stderr, "cldc_end_sess failed: %d\n", rc);
-		exit(1);
-	}
-
-	return 0;
-}
-
-static struct cldc_ops ops = {
-	.timer_ctl		= do_timer_ctl,
-	.pkt_send		= cldc_udp_pkt_send,
-	.event			= do_event,
-};
-
-static int init(void)
+int main (int argc, char *argv[])
 {
-	int rc;
+	struct ncld_sess *nsp;
+	int error;
 	int port;
-	struct cldc_call_opts copts;
+
+	g_thread_init(NULL);
+	ncld_init();
 
 	port = cld_readport(TEST_PORTFILE_CLD);
 	if (port < 0)
@@ -122,32 +44,15 @@ static int init(void)
 	if (port == 0)
 		return -1;
 
-	rc = cldc_udp_new("localhost", port, &udp);
-	if (rc)
-		return rc;
-
-	cld_timer_init(&udp_tm, "udp-timer", timer_udp_event, udp);
-
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = new_sess_cb;
-
-	rc = cldc_new_sess(&ops, &copts, udp->addr, udp->addr_len,
-			   "testuser", "testuser", udp, &udp->sess);
-	if (rc)
-		return rc;
-
-	// udp->sess->verbose = true;
-
-	return 0;
-}
+	nsp = ncld_sess_open(TEST_HOST, port, &error, NULL, NULL,
+			     TEST_USER, TEST_USER_KEY);
+	if (!nsp) {
+		fprintf(stderr, "ncld_sess_open(host %s port %u) failed: %d\n",
+			TEST_HOST, port, error);
+		exit(1);
+	}
 
-int main (int argc, char *argv[])
-{
-	g_thread_init(NULL);
-	cldc_init();
-	if (init())
-		return 1;
-	test_loop(&tlist, udp);
+	ncld_sess_close(nsp);
 	return 0;
 }
 
diff --git a/test/load-file-event.c b/test/load-file-event.c
index c328744..a267075 100644
--- a/test/load-file-event.c
+++ b/test/load-file-event.c
@@ -18,7 +18,7 @@
  */
 
 /*
- * Load a file from CLD.
+ * Load a file from CLD (written there by the previous test).
  */
 
 #define _GNU_SOURCE
@@ -30,239 +30,61 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
-#include <cldc.h>
+#include <ncld.h>
 #include "test.h"
 
-struct run {
-	struct cldc_udp *udp;
-	struct cld_timer_list tlist;
-	struct cld_timer tmr_udp;
-	struct cldc_fh *fh;
-	char *fname;
-};
-
-static int new_sess_cb(struct cldc_call_opts *copts, enum cle_err_codes errc);
-static int open_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc);
-static int read_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc);
-static int close_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc);
-static int end_sess_cb(struct cldc_call_opts *copts, enum cle_err_codes errc);
-
-static bool do_timer_ctl(void *priv, bool add,
-			 int (*cb)(struct cldc_session *, void *),
-			 void *cb_priv, time_t secs)
-{
-	struct run *rp = priv;
-
-	if (add) {
-		rp->udp->cb = cb;
-		rp->udp->cb_private = cb_priv;
-		cld_timer_add(&rp->tlist, &rp->tmr_udp, time(NULL) + secs);
-	} else {
-		cld_timer_del(&rp->tlist, &rp->tmr_udp);
-	}
-
-	return true;
-}
-
-static int do_pkt_send(void *priv, const void *addr, size_t addrlen,
-		       const void *buf, size_t buflen)
-{
-	struct run *rp = priv;
-	return cldc_udp_pkt_send(rp->udp, addr, addrlen, buf, buflen);
-}
-
-static void timer_udp_event(struct cld_timer *timer)
-{
-	struct run *rp = timer->userdata;
-	struct cldc_udp *udp = rp->udp;
-
-	if (udp->cb)
-		udp->cb(udp->sess, udp->cb_private);
-}
-
-static void do_event(void *private, struct cldc_session *sess,
-		     struct cldc_fh *fh, uint32_t event_mask)
-{
-	fprintf(stderr, "EVENT(0x%x)\n", event_mask);
-}
-
-static int new_sess_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc)
+int main(int argc, char *argv[])
 {
-	struct run *rp = coptarg->private;
-	struct cldc_call_opts copts;
-	int rc;
-
-	if (errc != CLE_OK) {
-		fprintf(stderr, "new-sess failed: %d\n", errc);
-		exit(1);
-	}
+	struct ncld_sess *nsp;
+	struct ncld_fh *fhp;
+	struct ncld_read *rp;
+	int port;
+	int error;
 
-	/* We use a fixed file name because we contact a private copy of CLD */
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = open_1_cb;
-	copts.private = rp;
-	rc = cldc_open(rp->udp->sess, &copts, rp->fname,
-		       COM_READ,
-		       CE_SESS_FAILED, &rp->fh);
-	if (rc) {
-		fprintf(stderr, "cldc_open call error %d\n", rc);
-		exit(1);
-	}
-	return 0;
-}
+	g_thread_init(NULL);
+	ncld_init();
 
-static int open_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc)
-{
-	struct run *rp = coptarg->private;
-	struct cldc_call_opts copts;
-	int rc;
+	port = cld_readport(TEST_PORTFILE_CLD);
+	if (port < 0)
+		return port;
+	if (port == 0)
+		return -1;
 
-	if (errc != CLE_OK) {
-		fprintf(stderr, "first-open failed: %d\n", errc);
-		exit(1);
-	}
-	if (rp->fh == NULL) {
-		fprintf(stderr, "first-open NULL fh\n");
-		exit(1);
-	}
-	if (!rp->fh->valid) {
-		fprintf(stderr, "first-open invalid fh\n");
+	nsp = ncld_sess_open(TEST_HOST, port, &error, NULL, NULL,
+			     TEST_USER, TEST_USER_KEY);
+	if (!nsp) {
+		fprintf(stderr, "ncld_sess_open(host %s port %u) failed: %d\n",
+			TEST_HOST, port, error);
 		exit(1);
 	}
 
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = read_1_cb;
-	copts.private = rp;
-	rc = cldc_get(rp->fh, &copts, false);
-	if (rc) {
-		fprintf(stderr, "cldc_get call error %d\n", rc);
+	fhp = ncld_open(nsp, TFNAME, COM_READ, &error, 0, NULL, NULL);
+	if (!fhp) {
+		fprintf(stderr, "ncld_open(%s) failed: %d\n", TFNAME, error);
 		exit(1);
 	}
 
-	return 0;
-}
-
-static int read_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc)
-{
-	struct run *rp = coptarg->private;
-	struct cldc_call_opts copts;
-	char *data;
-	size_t data_len;
-	int rc;
-
-	if (errc != CLE_OK) {
-		fprintf(stderr, "first-get failed: %d\n", errc);
+	rp = ncld_get(fhp, &error);
+	if (!rp) {
+		fprintf(stderr, "ncld_get failed: %d\n", error);
 		exit(1);
 	}
 
-	cldc_call_opts_get_data(coptarg, &data, &data_len);
-
-	if (data_len != TESTLEN) {
-		fprintf(stderr, "Bad CLD file length %zu\n", data_len);
+	if (rp->length != TESTLEN) {
+		fprintf(stderr, "Bad CLD file length %ld\n", rp->length);
 		exit(1);
 	}
 
-	if (memcmp(data, TESTSTR, TESTLEN)) {
+	if (memcmp(rp->ptr, TESTSTR, TESTLEN)) {
 		fprintf(stderr, "Bad CLD file content\n");
 		exit(1);
 	}
 
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = close_1_cb;
-	copts.private = rp;
-	rc = cldc_close(rp->fh, &copts);
-	if (rc) {
-		fprintf(stderr, "cldc_close call error %d\n", rc);
-		exit(1);
-	}
-
-	return 0;
-}
-
-static int close_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc)
-{
-	struct run *rp = coptarg->private;
-	struct cldc_call_opts copts;
-	int rc;
-
-	if (errc != CLE_OK) {
-		fprintf(stderr, "first-close failed: %d\n", errc);
-		exit(1);
-	}
-	rp->fh = NULL;
-
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = end_sess_cb;
-	copts.private = rp;
-	rc = cldc_end_sess(rp->udp->sess, &copts);
-	if (rc) {
-		fprintf(stderr, "cldc_end_sess call error %d\n", rc);
-		exit(1);
-	}
-	return 0;
-}
-
-static int end_sess_cb(struct cldc_call_opts *copts, enum cle_err_codes errc)
-{
-	if (errc != CLE_OK) {
-		fprintf(stderr, "end-sess failed: %d\n", errc);
-		exit(1);
-	}
-
-	/* session ended; success */
-	exit(0);
-
-	return 0;
-}
+	ncld_read_free(rp);
 
-static struct run run;
-
-static struct cldc_ops ops = {
-	.timer_ctl		= do_timer_ctl,
-	.pkt_send		= do_pkt_send,
-	.event			= do_event,
-};
-
-static int init(char *name)
-{
-	int rc;
-	int port;
-	struct cldc_call_opts copts;
-
-	run.fname = name;
-
-	port = cld_readport(TEST_PORTFILE_CLD);
-	if (port < 0)
-		return port;
-	if (port == 0)
-		return -1;
-
-	cld_timer_init(&run.tmr_udp, "udp-timer", timer_udp_event, &run);
-
-	rc = cldc_udp_new(TEST_HOST, port, &run.udp);
-	if (rc)
-		return rc;
-
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = new_sess_cb;
-	copts.private = &run;
-	rc = cldc_new_sess(&ops, &copts, run.udp->addr, run.udp->addr_len,
-			   TEST_USER, TEST_USER_KEY, &run, &run.udp->sess);
-	if (rc)
-		return rc;
-
-	// run.udp->sess->verbose = true;
-
-	return 0;
-}
-
-int main(int argc, char *argv[])
-{
-	g_thread_init(NULL);
-	cldc_init();
-	if (init(TFNAME))
-		return 1;
-	test_loop(&run.tlist, run.udp);
+	/* These two are perfect places to hang or crash, so don't just exit. */
+	ncld_close(fhp);
+	ncld_sess_close(nsp);
 	return 0;
 }
 
diff --git a/test/lock-file-event.c b/test/lock-file-event.c
index 2c99468..64e6020 100644
--- a/test/lock-file-event.c
+++ b/test/lock-file-event.c
@@ -25,267 +25,72 @@
 #include "cld-config.h"
 
 #include <sys/types.h>
-#include <unistd.h>
 #include <time.h>
 #include <stdlib.h>
 #include <stdio.h>
-#include <string.h>
-#include <cldc.h>
+#include <ncld.h>
 #include "test.h"
 
-struct run {
-	struct cldc_udp *udp;
-	struct cld_timer_list tlist;
-	struct cld_timer tmr_test;
-	struct cld_timer tmr_udp;
-	struct cldc_fh *fh;
-	char buf[LOCKLEN];
-};
-
-static int new_sess_cb(struct cldc_call_opts *copts, enum cle_err_codes errc);
-static int open_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc);
-static int write_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc);
-static int lock_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc);
-static void timer_1(struct run *rp);
-static int close_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc);
-static int end_sess_cb(struct cldc_call_opts *copts, enum cle_err_codes errc);
-
-static bool do_timer_ctl(void *priv, bool add,
-			 int (*cb)(struct cldc_session *, void *),
-			 void *cb_priv, time_t secs)
-{
-	struct run *rp = priv;
-
-	if (add) {
-		rp->udp->cb = cb;
-		rp->udp->cb_private = cb_priv;
-		cld_timer_add(&rp->tlist, &rp->tmr_udp, time(NULL) + secs);
-	} else {
-		cld_timer_del(&rp->tlist, &rp->tmr_udp);
-	}
-
-	return true;
-}
-
-static int do_pkt_send(void *priv, const void *addr, size_t addrlen,
-		       const void *buf, size_t buflen)
-{
-	struct run *rp = priv;
-	return cldc_udp_pkt_send(rp->udp, addr, addrlen, buf, buflen);
-}
-
-static void timer_udp_event(struct cld_timer *timer)
-{
-	struct run *rp = timer->userdata;
-	struct cldc_udp *udp = rp->udp;
-
-	if (udp->cb)
-		udp->cb(udp->sess, udp->cb_private);
-}
-
-static void do_event(void *private, struct cldc_session *sess,
-		     struct cldc_fh *fh, uint32_t event_mask)
-{
-	fprintf(stderr, "EVENT(0x%x)\n", event_mask);
-}
-
-static int new_sess_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc)
+int main (int argc, char *argv[])
 {
-	struct run *rp = coptarg->private;
-	struct cldc_call_opts copts;
+	struct ncld_sess *nsp;
+	struct ncld_fh *fhp;
+	int port;
+	struct timespec tm;
+	int error;
 	int rc;
 
-	if (errc != CLE_OK) {
-		fprintf(stderr, "new-sess failed: %d\n", errc);
-		exit(1);
-	}
-
-	/* We use a fixed file name because we contact a private copy of CLD */
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = open_1_cb;
-	copts.private = rp;
-	rc = cldc_open(rp->udp->sess, &copts, TLNAME,
-		       COM_WRITE | COM_LOCK | COM_CREATE,
-		       CE_SESS_FAILED, &rp->fh);
-	if (rc) {
-		fprintf(stderr, "cldc_open call error %d\n", rc);
-		exit(1);
-	}
-	return 0;
-}
-
-static int open_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc)
-{
-	struct run *rp = coptarg->private;
-	struct cldc_call_opts copts;
-	int rc;
+	g_thread_init(NULL);
+	ncld_init();
 
-	if (errc != CLE_OK) {
-		fprintf(stderr, "first-open failed: %d\n", errc);
-		exit(1);
-	}
-	if (rp->fh == NULL) {
-		fprintf(stderr, "first-open NULL fh\n");
-		exit(1);
-	}
-	if (!rp->fh->valid) {
-		fprintf(stderr, "first-open invalid fh\n");
-		exit(1);
-	}
+	port = cld_readport(TEST_PORTFILE_CLD);
+	if (port < 0)
+		return port;
+	if (port == 0)
+		return -1;
 
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = write_1_cb;
-	copts.private = rp;
-	rc = cldc_put(rp->fh, &copts, rp->buf, LOCKLEN);
-	if (rc) {
-		fprintf(stderr, "cldc_put call error %d\n", rc);
+	nsp = ncld_sess_open(TEST_HOST, port, &error, NULL, NULL,
+			     TEST_USER, TEST_USER_KEY);
+	if (!nsp) {
+		fprintf(stderr, "ncld_sess_open(host %s port %u) failed: %d\n",
+			TEST_HOST, port, error);
 		exit(1);
 	}
-	return 0;
-}
 
-static int write_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc)
-{
-	struct run *rp = coptarg->private;
-	struct cldc_call_opts copts;
-	int rc;
-
-	if (errc != CLE_OK) {
-		fprintf(stderr, "first-put failed: %d\n", errc);
+	fhp = ncld_open(nsp, TLNAME, COM_WRITE | COM_LOCK | COM_CREATE,
+			&error, 0, NULL, NULL);
+	if (!fhp) {
+		fprintf(stderr, "ncld_open(%s) failed: %d\n", TLNAME, error);
 		exit(1);
 	}
 
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = lock_1_cb;
-	copts.private = rp;
-	rc = cldc_lock(rp->fh, &copts, 0, false);
+	rc = ncld_write(fhp, LOCKSTR, LOCKLEN);
 	if (rc) {
-		fprintf(stderr, "cldc_lock call error %d\n", rc);
-		exit(1);
-	}
-	return 0;
-}
-
-static int lock_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc)
-{
-	struct run *rp = coptarg->private;
-
-	if (errc != CLE_OK) {
-		fprintf(stderr, "first-lock failed: %d\n", errc);
+		fprintf(stderr, "ncld_write failed: %d\n", rc);
 		exit(1);
 	}
 
-	/* Idle for 40s to verify that session sustains a protocol ping. */
-	cld_timer_add(&rp->tlist, &rp->tmr_test, time(NULL) + 40);
-	return 0;
-}
-
-static void timer_test_event(struct cld_timer *timer)
-{
-	struct run *rp = timer->userdata;
-
-	timer_1(rp);
-}
-
-static void timer_1(struct run *rp)
-{
-	struct cldc_call_opts copts;
-	int rc;
-
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = close_1_cb;
-	copts.private = rp;
-	rc = cldc_close(rp->fh, &copts);
+	rc = ncld_trylock(fhp);
 	if (rc) {
-		fprintf(stderr, "cldc_close call error %d\n", rc);
+		fprintf(stderr, "ncld_trylock failed: %d\n", rc);
 		exit(1);
 	}
-}
-
-static int close_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc)
-{
-	struct run *rp = coptarg->private;
-	struct cldc_call_opts copts;
-	int rc;
 
-	if (errc != CLE_OK) {
-		fprintf(stderr, "first-close failed: %d\n", errc);
-		exit(1);
-	}
-	rp->fh = NULL;
+	printf("idling 40s...\n"); fflush(stdout);
+	/* Idle for 40s to verify that session sustains a protocol ping. */
+	tm.tv_sec = 40;
+	tm.tv_nsec = 0;
+	nanosleep(&tm, NULL);
 
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = end_sess_cb;
-	copts.private = rp;
-	rc = cldc_end_sess(rp->udp->sess, &copts);
+	rc = ncld_unlock(fhp);
 	if (rc) {
-		fprintf(stderr, "cldc_end_sess call error %d\n", rc);
-		exit(1);
-	}
-	return 0;
-}
-
-static int end_sess_cb(struct cldc_call_opts *copts, enum cle_err_codes errc)
-{
-	if (errc != CLE_OK) {
-		fprintf(stderr, "end-sess failed: %d\n", errc);
+		fprintf(stderr, "ncld_unlock failed: %d\n", rc);
 		exit(1);
 	}
 
-	/* session ended; success */
-	exit(0);
-	return 0;
-}
-
-static struct run run;
-
-static struct cldc_ops ops = {
-	.timer_ctl		= do_timer_ctl,
-	.pkt_send		= do_pkt_send,
-	.event			= do_event,
-};
-
-static int init(void)
-{
-	int rc;
-	int port;
-	struct cldc_call_opts copts;
-
-	memcpy(run.buf, LOCKSTR, LOCKLEN);
-
-	port = cld_readport(TEST_PORTFILE_CLD);
-	if (port < 0)
-		return port;
-	if (port == 0)
-		return -1;
-
-	cld_timer_init(&run.tmr_test, "lock-timer", timer_test_event, &run);
-	cld_timer_init(&run.tmr_udp, "udp-timer", timer_udp_event, &run);
-
-	rc = cldc_udp_new(TEST_HOST, port, &run.udp);
-	if (rc)
-		return rc;
-
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = new_sess_cb;
-	copts.private = &run;
-	rc = cldc_new_sess(&ops, &copts, run.udp->addr, run.udp->addr_len,
-			   TEST_USER, TEST_USER_KEY, &run, &run.udp->sess);
-	if (rc)
-		return rc;
-
-	// run.udp->sess->verbose = true;
-
-	return 0;
-}
-
-int main (int argc, char *argv[])
-{
-	g_thread_init(NULL);
-	cldc_init();
-	if (init())
-		return 1;
-	test_loop(&run.tlist, run.udp);
+	/* These two are perfect places to hang or crash, so don't just exit. */
+	ncld_close(fhp);
+	ncld_sess_close(nsp);
 	return 0;
 }
 
diff --git a/test/save-file-event.c b/test/save-file-event.c
index 1b46d29..a91dc65 100644
--- a/test/save-file-event.c
+++ b/test/save-file-event.c
@@ -30,205 +30,18 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
-#include <cldc.h>
+#include <ncld.h>
 #include "test.h"
 
-struct run {
-	struct cldc_udp *udp;
-	struct cld_timer_list tlist;
-	struct cld_timer tmr_udp;
-	struct cldc_fh *fh;
-	char *fname;
-	char *buf;
-	int len;
-};
-
-static int new_sess_cb(struct cldc_call_opts *copts, enum cle_err_codes errc);
-static int open_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc);
-static int write_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc);
-static void call_close(struct run *rp);
-static int close_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc);
-static int end_sess_cb(struct cldc_call_opts *copts, enum cle_err_codes errc);
-
-static bool do_timer_ctl(void *priv, bool add,
-			 int (*cb)(struct cldc_session *, void *),
-			 void *cb_priv, time_t secs)
-{
-	struct run *rp = priv;
-
-	if (add) {
-		rp->udp->cb = cb;
-		rp->udp->cb_private = cb_priv;
-		cld_timer_add(&rp->tlist, &rp->tmr_udp, time(NULL) + secs);
-	} else {
-		cld_timer_del(&rp->tlist, &rp->tmr_udp);
-	}
-	return true;
-}
-
-static int do_pkt_send(void *priv, const void *addr, size_t addrlen,
-		       const void *buf, size_t buflen)
-{
-	struct run *rp = priv;
-	return cldc_udp_pkt_send(rp->udp, addr, addrlen, buf, buflen);
-}
-
-static void timer_udp_event(struct cld_timer *timer)
-{
-	struct run *rp = timer->userdata;
-	struct cldc_udp *udp;
-
-	udp = rp->udp;
-	if (udp->cb)
-		udp->cb(udp->sess, udp->cb_private);
-}
-
-static void do_event(void *private, struct cldc_session *sess,
-		     struct cldc_fh *fh, uint32_t event_mask)
-{
-	fprintf(stderr, "EVENT(0x%x)\n", event_mask);
-}
-
-static int new_sess_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc)
-{
-	struct run *rp = coptarg->private;
-	struct cldc_call_opts copts;
-	int rc;
-
-	if (errc != CLE_OK) {
-		fprintf(stderr, "new-sess failed: %d\n", errc);
-		exit(1);
-	}
-
-	/* We use a fixed file name because we contact a private copy of CLD */
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = open_1_cb;
-	copts.private = rp;
-	rc = cldc_open(rp->udp->sess, &copts, rp->fname,
-		       COM_WRITE | COM_CREATE,
-		       CE_SESS_FAILED, &rp->fh);
-	if (rc) {
-		fprintf(stderr, "cldc_open call error %d\n", rc);
-		exit(1);
-	}
-	return 0;
-}
-
-static int open_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc)
-{
-	struct run *rp = coptarg->private;
-	struct cldc_call_opts copts;
-	int rc;
-
-	if (errc != CLE_OK) {
-		fprintf(stderr, "first-open failed: %d\n", errc);
-		exit(1);
-	}
-	if (rp->fh == NULL) {
-		fprintf(stderr, "first-open NULL fh\n");
-		exit(1);
-	}
-	if (!rp->fh->valid) {
-		fprintf(stderr, "first-open invalid fh\n");
-		exit(1);
-	}
-
-	if (rp->len == 0) {
-		call_close(rp);
-	} else {
-		memset(&copts, 0, sizeof(copts));
-		copts.cb = write_1_cb;
-		copts.private = rp;
-		rc = cldc_put(rp->fh, &copts, rp->buf, rp->len);
-		if (rc) {
-			fprintf(stderr, "cldc_put call error %d for %d bytes\n",
-				rc, rp->len);
-			exit(1);
-		}
-	}
-	return 0;
-}
-
-static int write_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc)
-{
-	struct run *rp = coptarg->private;
-
-	if (errc != CLE_OK) {
-		fprintf(stderr, "first-put failed: %d\n", errc);
-		exit(1);
-	}
-
-	call_close(rp);
-	return 0;
-}
-
-static void call_close(struct run *rp)
-{
-	struct cldc_call_opts copts;
-	int rc;
-
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = close_1_cb;
-	copts.private = rp;
-	rc = cldc_close(rp->fh, &copts);
-	if (rc) {
-		fprintf(stderr, "cldc_close call error %d\n", rc);
-		exit(1);
-	}
-}
-
-static int close_1_cb(struct cldc_call_opts *coptarg, enum cle_err_codes errc)
-{
-	struct run *rp = coptarg->private;
-	struct cldc_call_opts copts;
-	int rc;
-
-	if (errc != CLE_OK) {
-		fprintf(stderr, "first-close failed: %d\n", errc);
-		exit(1);
-	}
-	rp->fh = NULL;
-
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = end_sess_cb;
-	copts.private = rp;
-	rc = cldc_end_sess(rp->udp->sess, &copts);
-	if (rc) {
-		fprintf(stderr, "cldc_end_sess call error %d\n", rc);
-		exit(1);
-	}
-	return 0;
-}
-
-static int end_sess_cb(struct cldc_call_opts *copts, enum cle_err_codes errc)
-{
-	if (errc != CLE_OK) {
-		fprintf(stderr, "end-sess failed: %d\n", errc);
-		exit(1);
-	}
-
-	/* session ended; success */
-	exit(0);
-	return 0;
-}
-
-static struct run run;
-
-static struct cldc_ops ops = {
-	.timer_ctl		= do_timer_ctl,
-	.pkt_send		= do_pkt_send,
-	.event			= do_event,
-};
-
-static int init(char *name)
+int main(int argc, char *argv[])
 {
-	int rc;
+	struct ncld_sess *nsp;
+	struct ncld_fh *fhp;
 	int port;
-	struct cldc_call_opts copts;
+	int error;
 
-	run.fname = name;
-	run.buf = TESTSTR;
-	run.len = TESTLEN;
+	g_thread_init(NULL);
+	ncld_init();
 
 	port = cld_readport(TEST_PORTFILE_CLD);
 	if (port < 0)
@@ -236,32 +49,30 @@ static int init(char *name)
 	if (port == 0)
 		return -1;
 
-	cld_timer_init(&run.tmr_udp, "udp-timer", timer_udp_event, &run);
-
-	rc = cldc_udp_new(TEST_HOST, port, &run.udp);
-	if (rc)
-		return rc;
-
-	memset(&copts, 0, sizeof(copts));
-	copts.cb = new_sess_cb;
-	copts.private = &run;
-	rc = cldc_new_sess(&ops, &copts, run.udp->addr, run.udp->addr_len,
-			   TEST_USER, TEST_USER_KEY, &run, &run.udp->sess);
-	if (rc)
-		return rc;
+	nsp = ncld_sess_open(TEST_HOST, port, &error, NULL, NULL,
+			     TEST_USER, TEST_USER_KEY);
+	if (!nsp) {
+		fprintf(stderr, "ncld_sess_open(host %s port %u) failed: %d\n",
+			TEST_HOST, port, error);
+		exit(1);
+	}
 
-	// run.udp->sess->verbose = true;
+	fhp = ncld_open(nsp, TFNAME, COM_WRITE | COM_CREATE,
+			&error, 0, NULL, NULL);
+	if (!fhp) {
+		fprintf(stderr, "ncld_open(%s) failed: %d\n", TFNAME, error);
+		exit(1);
+	}
 
-	return 0;
-}
+	error = ncld_write(fhp, TESTSTR, TESTLEN);
+	if (error) {
+		fprintf(stderr, "ncld_write failed: %d\n", error);
+		exit(1);
+	}
 
-int main(int argc, char *argv[])
-{
-	g_thread_init(NULL);
-	cldc_init();
-	if (init(TFNAME))
-		return 1;
-	test_loop(&run.tlist, run.udp);
+	/* These two are perfect places to hang or crash, so don't just exit. */
+	ncld_close(fhp);
+	ncld_sess_close(nsp);
 	return 0;
 }
 
diff --git a/test/test.h b/test/test.h
index a70609d..abd9284 100644
--- a/test/test.h
+++ b/test/test.h
@@ -1,9 +1,6 @@
 #ifndef _CLD_TEST_H_
 #define _CLD_TEST_H_
 
-#include <stdbool.h>
-#include <cldc.h>
-
 #define TESTSTR          "longertestdata\n"
 #define TESTLEN  (sizeof("longertestdata\n")-1)
 
@@ -20,6 +17,4 @@
 
 #define TEST_PORTFILE_CLD	"cld.port"
 
-extern void test_loop(struct cld_timer_list *tlist, struct cldc_udp *udp);
-
 #endif
diff --git a/test/util.c b/test/util.c
deleted file mode 100644
index 7e5ad06..0000000
--- a/test/util.c
+++ /dev/null
@@ -1,79 +0,0 @@
-
-/*
- * Copyright 2009 Red Hat, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING.  If not, write to
- * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
- *
- */
-
-/*
- * General utilities for CLD tests.
- */
-
-#define _GNU_SOURCE
-#include "cld-config.h"
-
-#include <sys/types.h>
-#include <sys/select.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <time.h>
-#include <cldc.h>
-#include "test.h"
-
-void test_loop(struct cld_timer_list *tlist, struct cldc_udp *udp)
-{
-	int ufd = udp->fd;
-	fd_set rset;
-	struct timeval tm;
-	time_t tmo;
-	int rc;
-
-	for (;;) {
-		FD_ZERO(&rset);
-		FD_SET(ufd, &rset);
-
-		tmo = cld_timers_run(tlist);
-		if (tmo) {
-			tm.tv_sec = tmo;
-			tm.tv_usec = 0;
-			rc = select(ufd + 1, &rset, NULL, NULL, &tm);
-			if (rc < 0) {
-				fprintf(stderr, "select: error\n");
-				exit(1);
-			}
-			if (rc == 0)
-				continue;
-		} else {
-			rc = select(ufd + 1, &rset, NULL, NULL, NULL);
-			if (rc <= 0) {
-				fprintf(stderr, "select: nfd %d\n", rc);
-				exit(1);
-			}
-		}
-
-		if (FD_ISSET(ufd, &rset)) {
-			rc = cldc_udp_receive_pkt(udp);
-			if (rc) {
-				fprintf(stderr,
-					"cldc_udp_receive_pkt: error %d\n", rc);
-				exit(1);
-			}
-		} else {
-			fprintf(stderr, "noevent\n");
-			exit(1);
-		}
-	}
-}
-
diff --git a/tools/cldcli.c b/tools/cldcli.c
index 32289ac..85a7ef8 100644
--- a/tools/cldcli.c
+++ b/tools/cldcli.c
@@ -26,13 +26,10 @@
 #include <errno.h>
 #include <stdio.h>
 #include <argp.h>
-#include <poll.h>
 #include <locale.h>
 #include <stdarg.h>
 #include <ctype.h>
-#include <cldc.h>
-
-#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+#include <ncld.h>
 
 const char *argp_program_version = PACKAGE_VERSION;
 
@@ -40,11 +37,6 @@ enum {
 	CLD_PATH_MAX		= 1024,
 };
 
-enum thread_codes {
-	TC_OK,
-	TC_FAILED
-};
-
 static struct argp_option options[] = {
 	{ "debug", 'D', "LEVEL", 0,
 	  "Set debug output to LEVEL (0 = off, 2 = max verbose)" },
@@ -60,89 +52,46 @@ static struct argp_option options[] = {
 static const char doc[] =
 "cldcli - command line interface to coarse locking service";
 
+#define TAG "cldcli"
+
 enum creq_cmd {
-	CREQ_CD,
-	CREQ_CAT,
-	CREQ_LS,
 	CREQ_RM,
 	CREQ_MKDIR,
-	CREQ_CP_FC,		/* cpin: FS-to-CLD copy */
-	CREQ_CP_CF,		/* cpout: CLD-to-FS copy */
-	CREQ_LOCK,
-	CREQ_TRYLOCK,
 	CREQ_UNLOCK,
-	CREQ_LIST_LOCKS,
-};
-
-struct cp_fc_info {
-	void		*mem;
-	size_t		mem_len;
 };
 
 struct creq {
-	enum creq_cmd		cmd;
+	enum creq_cmd		cmd_unused;
 	char			path[CLD_PATH_MAX + 1];
-	struct cp_fc_info	*cfi;
-};
-
-enum { CRESP_MSGSZ = 64 };
-
-struct cresp {
-	enum thread_codes	tcode;
-	char			msg[CRESP_MSGSZ];
-	union {
-		size_t		file_len;
-		unsigned int	n_records;
-		GList		*list;
-	} u;
-};
-
-struct ls_rec {
-	char			name[CLD_INODE_NAME_MAX + 1];
 };
 
 struct cldcli_lock_info {
-	enum creq_cmd		cmd;
-	struct cldc_fh		*fh;
+	bool			is_wait;
+	struct ncld_fh		*fh;
 	uint64_t		id;
 	char			path[CLD_PATH_MAX + 1];
 };
 
-static unsigned long thread_running = 1;
 static GList *host_list;
 static char clicwd[CLD_PATH_MAX + 1] = "/";
-static int to_thread[2], from_thread[2];
-static GThread *cldthr;
 static char our_user[CLD_MAX_USERNAME + 1] = "cli_user";
-static bool cldcli_verbose;
 
 /* globals only for use in thread */
-static struct cldc_udp *thr_udp;
-static struct cldc_fh *thr_fh;
+static struct ncld_sess *nsp;
 static GList *thr_lock_list;
 static uint64_t thr_lock_id = 2;
-static struct cld_timer_list thr_list;
-static struct cld_timer thr_timer;
-static int (*cldc_timer_cb)(struct cldc_session *, void *);
-static void *cldc_timer_private;
 
 static error_t parse_opt (int key, char *arg, struct argp_state *state);
 
 static const struct argp argp = { options, parse_opt, NULL, doc };
 
-static void errc_msg(struct cresp *cresp, enum cle_err_codes errc)
-{
-	strncpy(cresp->msg, cld_errstr(errc), CRESP_MSGSZ);
-	cresp->msg[CRESP_MSGSZ-1] = 0;
-}
-
 static void applog(int prio, const char *fmt, ...)
 {
 	char buf[200];
 	va_list ap;
 
 	va_start(ap, fmt);
-	snprintf(buf, 200, "%s\n", fmt);
+	snprintf(buf, 200, TAG ": %s\n", fmt);
 	vfprintf(stderr, buf, ap);
 	va_end(ap);
 }
@@ -152,616 +101,7 @@ static struct hail_log cli_log = {
 	.verbose = 0,
 };
 
-static void do_write(int fd, const void *buf, size_t buflen, const char *msg)
-{
-	ssize_t rc;
-
-	rc = write(fd, buf, buflen);
-	if (rc < 0)
-		perror(msg);
-	else if (rc != buflen)
-		fprintf(stderr, "%s: short write\n", msg);
-}
-
-static void do_read(int fd, void *buf, size_t buflen, const char *msg)
-{
-	ssize_t rc;
-
-	rc = read(fd, buf, buflen);
-	if (rc < 0)
-		perror(msg);
-	else if (rc != buflen)
-		fprintf(stderr, "%s: short read\n", msg);
-}
-
-/* send message thread -> main */
-static void write_from_thread(const void *buf, size_t buflen)
-{
-	do_write(from_thread[1], buf, buflen, "write-from-thread");
-}
-
-/* send message main -> thread */
-static void write_to_thread(const void *buf, size_t buflen)
-{
-	do_write(to_thread[1], buf, buflen, "write-to-thread");
-}
-
-/* receive message thread -> main */
-static void read_from_thread(void *buf, size_t buflen)
-{
-	do_read(from_thread[0], buf, buflen, "read-from-thread");
-}
-
-/* receive message main -> thread */
-static void read_to_thread(void *buf, size_t buflen)
-{
-	do_read(to_thread[0], buf, buflen, "read-to-thread");
-}
-
-static int cb_ok_done(struct cldc_call_opts *copts_in, enum cle_err_codes errc)
-{
-	struct cresp cresp = { .tcode = TC_FAILED, };
-
-	if (errc == CLE_OK)
-		cresp.tcode = TC_OK;
-	errc_msg(&cresp, errc);
-
-	write_from_thread(&cresp, sizeof(cresp));
-
-	return 0;
-}
-
-static int cb_ls_2(struct cldc_call_opts *copts_in, enum cle_err_codes errc)
-{
-	struct cresp cresp = { .tcode = TC_FAILED, };
-	struct cldc_call_opts copts = { NULL, };
-	struct cld_dirent_cur dc;
-	int rc, i;
-	char *data;
-	size_t data_len;
-	bool first = true;
-
-	if (errc != CLE_OK) {
-		errc_msg(&cresp, errc);
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-	cldc_call_opts_get_data(copts_in, &data, &data_len);
-
-	rc = cldc_dirent_count(data, data_len);
-	if (rc < 0) {
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-
-	cresp.tcode = TC_OK;
-	cresp.u.n_records = rc;
-
-	write_from_thread(&cresp, sizeof(cresp));
-
-	cldc_dirent_cur_init(&dc, data, data_len);
-
-	for (i = 0; i < rc; i++) {
-		struct ls_rec lsr;
-		char *s;
-
-		if (first) {
-			first = false;
-
-			if (cldc_dirent_first(&dc) < 0)
-				break;
-		} else {
-			if (cldc_dirent_next(&dc) < 0)
-				break;
-		}
-
-		s = cldc_dirent_name(&dc);
-		strncpy(lsr.name, s, sizeof(lsr.name));
-		lsr.name[sizeof(lsr.name) - 1] = 0;
-		free(s);
-
-		write_from_thread(&lsr, sizeof(lsr));
-
-	}
-
-	cldc_dirent_cur_fini(&dc);
-
-	/* FIXME: race; should wait until close succeeds/fails before
-	 * returning any data.  'fh' may still be in use, otherwise.
-	 */
-	cldc_close(thr_fh, &copts);
-
-	return 0;
-}
-
-static int cb_ls_1(struct cldc_call_opts *copts_in, enum cle_err_codes errc)
-{
-	struct cresp cresp = { .tcode = TC_FAILED, };
-	struct cldc_call_opts copts = { .cb = cb_ls_2, };
-
-	if (errc != CLE_OK) {
-		errc_msg(&cresp, errc);
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-
-	if (cldc_get(thr_fh, &copts, false)) {
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-
-	return 0;
-}
-
-static int cb_cat_2(struct cldc_call_opts *copts_in, enum cle_err_codes errc)
-{
-	struct cresp cresp = { .tcode = TC_FAILED, };
-	struct cldc_call_opts copts = { NULL, };
-	char *data;
-	size_t data_len;
-
-	if (errc != CLE_OK) {
-		errc_msg(&cresp, errc);
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-
-	cldc_call_opts_get_data(copts_in, &data, &data_len);
-
-	cresp.tcode = TC_OK;
-	cresp.u.file_len = data_len;
-
-	write_from_thread(&cresp, sizeof(cresp));
-	write_from_thread(data, data_len);
-
-	/* FIXME: race; should wait until close succeeds/fails before
-	 * returning any data.  'fh' may still be in use, otherwise.
-	 */
-	cldc_close(thr_fh, &copts);
-
-	return 0;
-}
-
-static int cb_cat_1(struct cldc_call_opts *copts_in, enum cle_err_codes errc)
-{
-	struct cresp cresp = { .tcode = TC_FAILED, };
-	struct cldc_call_opts copts = { .cb = cb_cat_2, };
-
-	if (errc != CLE_OK) {
-		errc_msg(&cresp, errc);
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-
-	if (cldc_get(thr_fh, &copts, false)) {
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-
-	return 0;
-}
-
-static int cb_cp_cf_2(struct cldc_call_opts *copts_in, enum cle_err_codes errc)
-{
-	struct cresp cresp = { .tcode = TC_FAILED, };
-	struct cldc_call_opts copts = { NULL, };
-	char *data;
-	size_t data_len;
-
-	if (errc != CLE_OK) {
-		errc_msg(&cresp, errc);
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-
-	cldc_call_opts_get_data(copts_in, &data, &data_len);
-	cresp.tcode = TC_OK;
-	cresp.u.file_len = data_len;
-
-	write_from_thread(&cresp, sizeof(cresp));
-	write_from_thread(data, data_len);
-
-	/* FIXME: race; should wait until close succeeds/fails before
-	 * returning any data.  'fh' may still be in use, otherwise.
-	 */
-	cldc_close(thr_fh, &copts);
-
-	return 0;
-}
-
-static int cb_cp_cf_1(struct cldc_call_opts *copts_in, enum cle_err_codes errc)
-{
-	struct cresp cresp = { .tcode = TC_FAILED, };
-	struct cldc_call_opts copts = { .cb = cb_cp_cf_2, };
-
-	if (errc != CLE_OK) {
-		errc_msg(&cresp, errc);
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-
-	if (cldc_get(thr_fh, &copts, false)) {
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-
-	return 0;
-}
-
-static int cb_cp_fc_1(struct cldc_call_opts *copts_in, enum cle_err_codes errc)
-{
-	struct cresp cresp = { .tcode = TC_FAILED, };
-	struct cldc_call_opts copts = { .cb = cb_ok_done, };
-	struct cp_fc_info *cfi = copts_in->private;
-
-	if (errc != CLE_OK) {
-		errc_msg(&cresp, errc);
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-
-	if (cldc_put(thr_fh, &copts, cfi->mem, cfi->mem_len)) {
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-
-	return 0;
-}
-
-static int cb_cd_1(struct cldc_call_opts *copts_in, enum cle_err_codes errc)
-{
-	struct cresp cresp = { .tcode = TC_FAILED, };
-	struct cldc_call_opts copts = { .cb = cb_ok_done, };
-
-	if (errc != CLE_OK) {
-		errc_msg(&cresp, errc);
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-
-	if (cldc_close(thr_fh, &copts)) {
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-
-	return 0;
-}
-
-static int cb_mkdir_1(struct cldc_call_opts *copts_in, enum cle_err_codes errc)
-{
-	struct cresp cresp = { .tcode = TC_FAILED, };
-	struct cldc_call_opts copts = { NULL, };
-
-	if (errc == CLE_OK)
-		cresp.tcode = TC_OK;
-	errc_msg(&cresp, errc);
-
-	write_from_thread(&cresp, sizeof(cresp));
-
-	/* FIXME: race; should wait until close succeeds/fails before
-	 * returning any data.  'fh' may still be in use, otherwise.
-	 */
-	cldc_close(thr_fh, &copts);
-
-	return 0;
-}
-
-static int cb_lock_2(struct cldc_call_opts *copts_in, enum cle_err_codes errc)
-{
-	struct cldcli_lock_info *li = copts_in->private;
-
-	if ((errc == CLE_OK) ||
-	    ((li->cmd == CREQ_LOCK) && (errc == CLE_LOCK_PENDING)))
-		thr_lock_list = g_list_append(thr_lock_list, li);
-	else
-		free(li);
-
-	return cb_ok_done(copts_in, errc);
-}
-
-static int cb_lock_1(struct cldc_call_opts *copts_in, enum cle_err_codes errc)
-{
-	struct cresp cresp = { .tcode = TC_FAILED, };
-	struct cldc_call_opts copts = { .cb = cb_lock_2, };
-	struct cldcli_lock_info *li = copts_in->private;
-	bool wait_for_lock = (li->cmd == CREQ_LOCK);
-
-	if (errc != CLE_OK) {
-		errc_msg(&cresp, errc);
-		write_from_thread(&cresp, sizeof(cresp));
-		return 0;
-	}
-
-	copts.private = li;
-
-	if (cldc_lock(li->fh, &copts, 0, wait_for_lock)) {
-		write_from_thread(&cresp, sizeof(cresp));
-		free(li);
-		return 0;
-	}
-
-	return 0;
-}
-
-static int cb_unlock_1(struct cldc_call_opts *copts_in, enum cle_err_codes errc)
-{
-	struct cresp cresp = { .tcode = TC_FAILED, };
-	struct cldc_call_opts copts = { NULL, };
-	struct cldcli_lock_info *li = copts_in->private;
-
-	if (errc != CLE_OK) {
-		errc_msg(&cresp, errc);
-		write_from_thread(&cresp, sizeof(cresp));
-		goto out;
-	}
-
-	cresp.tcode = TC_OK;
-
-	write_from_thread(&cresp, sizeof(cresp));
-
-out:
-	cldc_close(li->fh, &copts);
-
-	free(li);
-	return 0;
-}
-
-static void handle_user_command(void)
-{
-	struct creq creq;
-	struct cresp cresp = { .tcode = TC_FAILED, };
-	struct cldc_call_opts copts = { NULL, };
-	int rc;
-
-	read_to_thread(&creq, sizeof(creq));
-
-	if (cli_log.verbose)
-		switch (creq.cmd) {
-		case CREQ_CD:
-		case CREQ_CAT:
-		case CREQ_LS:
-		case CREQ_RM:
-		case CREQ_MKDIR:
-		case CREQ_CP_FC:
-		case CREQ_CP_CF:
-		case CREQ_LOCK:
-		case CREQ_TRYLOCK:
-		case CREQ_UNLOCK:
-			fprintf(stderr, "DEBUG: thr rx'd path '%s'\n",
-				creq.path);
-			break;
-		case CREQ_LIST_LOCKS:
-			fprintf(stderr, "DEBUG: thr rx'd no path\n");
-			break;
-		}
-
-	switch (creq.cmd) {
-	case CREQ_CD:
-		copts.cb = cb_cd_1;
-		rc = cldc_open(thr_udp->sess, &copts, creq.path,
-			       COM_DIRECTORY, 0, &thr_fh);
-		if (rc) {
-			snprintf(cresp.msg, sizeof(cresp.msg),
-				 "cldc_open rc %d", rc);
-			write_from_thread(&cresp, sizeof(cresp));
-			return;
-		}
-		break;
-	case CREQ_CAT:
-		copts.cb = cb_cat_1;
-		rc = cldc_open(thr_udp->sess, &copts, creq.path,
-			       COM_READ, 0, &thr_fh);
-		if (rc) {
-			snprintf(cresp.msg, sizeof(cresp.msg),
-				 "cldc_open rc %d", rc);
-			write_from_thread(&cresp, sizeof(cresp));
-			return;
-		}
-		break;
-	case CREQ_CP_CF:
-		copts.cb = cb_cp_cf_1;
-		rc = cldc_open(thr_udp->sess, &copts, creq.path,
-			       COM_READ, 0, &thr_fh);
-		if (rc) {
-			snprintf(cresp.msg, sizeof(cresp.msg),
-				 "cldc_open rc %d", rc);
-			write_from_thread(&cresp, sizeof(cresp));
-			return;
-		}
-		break;
-	case CREQ_CP_FC:
-		copts.cb = cb_cp_fc_1;
-		copts.private = creq.cfi;
-		rc = cldc_open(thr_udp->sess, &copts, creq.path,
-			       COM_CREATE | COM_WRITE, 0, &thr_fh);
-		if (rc) {
-			snprintf(cresp.msg, sizeof(cresp.msg),
-				 "cldc_open rc %d", rc);
-			write_from_thread(&cresp, sizeof(cresp));
-			return;
-		}
-		break;
-	case CREQ_LS:
-		copts.cb = cb_ls_1;
-		rc = cldc_open(thr_udp->sess, &copts, creq.path,
-			       COM_DIRECTORY | COM_READ, 0, &thr_fh);
-		if (rc) {
-			snprintf(cresp.msg, sizeof(cresp.msg),
-				 "cldc_open rc %d", rc);
-			write_from_thread(&cresp, sizeof(cresp));
-			return;
-		}
-		break;
-	case CREQ_RM:
-		copts.cb = cb_ok_done;
-		rc = cldc_del(thr_udp->sess, &copts, creq.path);
-		if (rc) {
-			snprintf(cresp.msg, sizeof(cresp.msg),
-				 "cldc_del rc %d", rc);
-			write_from_thread(&cresp, sizeof(cresp));
-			return;
-		}
-		break;
-	case CREQ_MKDIR:
-		copts.cb = cb_mkdir_1;
-		rc = cldc_open(thr_udp->sess, &copts, creq.path,
-			       COM_DIRECTORY | COM_CREATE | COM_EXCL, 0,
-			       &thr_fh);
-		if (rc) {
-			snprintf(cresp.msg, sizeof(cresp.msg),
-				 "cldc_open rc %d", rc);
-			write_from_thread(&cresp, sizeof(cresp));
-			return;
-		}
-		break;
-	case CREQ_TRYLOCK:
-	case CREQ_LOCK: {
-		struct cldcli_lock_info *li;
-
-		li = calloc(1, sizeof(*li));
-		if (!li) {
-			strcpy(cresp.msg, "OOM");
-			write_from_thread(&cresp, sizeof(cresp));
-			return;
-		}
-
-		li->cmd = creq.cmd;
-		li->id = thr_lock_id++;
-		strncpy(li->path, creq.path, sizeof(li->path));
-
-		copts.cb = cb_lock_1;
-		copts.private = li;
-		rc = cldc_open(thr_udp->sess, &copts, creq.path,
-			       COM_LOCK, 0, &li->fh);
-		if (rc) {
-			snprintf(cresp.msg, sizeof(cresp.msg),
-				 "cldc_open rc %d", rc);
-			write_from_thread(&cresp, sizeof(cresp));
-			free(li);
-			return;
-		}
-
-		break;
-		}
-
-	case CREQ_UNLOCK: {
-		GList *tmp;
-		struct cldcli_lock_info *li = NULL;
-
-		tmp = thr_lock_list;
-		while (tmp) {
-			li = tmp->data;
-
-			if (!strncmp(li->path, creq.path, sizeof(li->path)))
-				break;
-
-			tmp = tmp->next;
-		}
-		if (!tmp) {
-			write_from_thread(&cresp, sizeof(cresp));
-			return;
-		}
-
-		thr_lock_list = g_list_delete_link(thr_lock_list, tmp);
-
-		copts.cb = cb_unlock_1;
-		copts.private = li;
-		rc = cldc_unlock(li->fh, &copts);
-		if (rc) {
-			snprintf(cresp.msg, sizeof(cresp.msg),
-				 "cldc_unlock rc %d", rc);
-			write_from_thread(&cresp, sizeof(cresp));
-			free(li);
-			return;
-		}
-
-		break;
-		}
-
-	case CREQ_LIST_LOCKS: {
-		GList *tmp, *content = NULL;
-
-		tmp = thr_lock_list;
-		while (tmp) {
-			char *s;
-			struct cldcli_lock_info *li;
-
-			li = tmp->data;
-			tmp = tmp->next;
-
-			s = g_strdup_printf("%llu %s\n",
-				 (unsigned long long) li->id,
-				 li->path);
-
-			content = g_list_append(content, s);
-		}
-
-		cresp.tcode = TC_OK;
-		cresp.u.list = content;
-		write_from_thread(&cresp, sizeof(cresp));
-		break;
-		}
-	}
-}
-
-static int cb_new_sess(struct cldc_call_opts *copts, enum cle_err_codes errc)
-{
-	char tcode = TC_FAILED;
-
-	if (errc != CLE_OK) {
-		write_from_thread(&tcode, 1);
-		return 0;
-	}
-
-	/* signal we are up and ready for commands */
-	tcode = TC_OK;
-	write_from_thread(&tcode, 1);
-
-	return 0;
-}
-
-static void cld_p_timer_cb(struct cld_timer *timer)
-{
-	int (*timer_cb)(struct cldc_session *, void *) = cldc_timer_cb;
-	void *private = cldc_timer_private;
-
-	if (!timer_cb)
-		return;
-
-	cldc_timer_cb = NULL;
-	cldc_timer_private = NULL;
-
-	timer_cb(thr_udp->sess, private);
-}
-
-static bool cld_p_timer_ctl(void *private, bool add,
-			    int (*cb)(struct cldc_session *, void *),
-			    void *cb_private, time_t secs)
-{
-	if (add) {
-		cldc_timer_cb = cb;
-		cldc_timer_private = cb_private;
-
-		thr_timer.fired = false;
-		thr_timer.cb = cld_p_timer_cb;
-		thr_timer.userdata = NULL;
-
-		cld_timer_add(&thr_list, &thr_timer, time(NULL) + secs);
-	} else {
-		cld_timer_del(&thr_list, &thr_timer);
-	}
-	return true;
-}
-
-static int cld_p_pkt_send(void *priv, const void *addr, size_t addrlen,
-			       const void *buf, size_t buflen)
-{
-	struct cldc_udp *udp = priv;
-	return cldc_udp_pkt_send(udp, addr, addrlen, buf, buflen);
-}
-
-static void cld_p_event(void *private, struct cldc_session *sess,
-			struct cldc_fh *fh, uint32_t what)
+static void sess_event(void *private, uint32_t what)
 {
 	fprintf(stderr, "FIXME: handle event(s) %s%s%s%s%s\n",
 		(what & CE_UPDATED) ? "updated " : "",
@@ -771,83 +111,6 @@ static void cld_p_event(void *private, struct cldc_session *sess,
 		(what & CE_SESS_FAILED) ? "sess-fail " : "");
 }
 
-static struct cldc_ops cld_ops = {
-	.timer_ctl	= cld_p_timer_ctl,
-	.pkt_send	= cld_p_pkt_send,
-	.event		= cld_p_event,
-	.errlog		= applog,
-};
-
-static gpointer cld_thread(gpointer dummy)
-{
-	struct cldc_host *dr;
-	struct cldc_call_opts copts = { .cb = cb_new_sess };
-	char tcode = TC_FAILED;
-	struct pollfd pfd[2];
-	time_t next_timeout;
-
-	if (!host_list) {
-		fprintf(stderr, "cldthr: no host list\n");
-		write_from_thread(&tcode, 1);
-		return NULL;
-	}
-
-	dr = host_list->data;
-
-	if (cldc_udp_new(dr->host, dr->port, &thr_udp)) {
-		fprintf(stderr, "cldthr: UDP create failed\n");
-		write_from_thread(&tcode, 1);
-		return NULL;
-	}
-
-	if (cldc_new_sess(&cld_ops, &copts, thr_udp->addr, thr_udp->addr_len,
-			  "cldcli", "cldcli", thr_udp, &thr_udp->sess)) {
-		fprintf(stderr, "cldthr: new_sess failed\n");
-		write_from_thread(&tcode, 1);
-		return NULL;
-	}
-
-	thr_udp->sess->log.verbose = cldcli_verbose;
-
-	pfd[0].fd = thr_udp->fd;
-	pfd[0].events = POLLIN;
-
-	pfd[1].fd = to_thread[0];
-	pfd[1].events = POLLIN;
-
-	next_timeout = cld_timers_run(&thr_list);
-
-	while (thread_running) {
-		int i, rc;
-
-		/* zero revents.  necessary??? */
-		for (i = 0; i < ARRAY_SIZE(pfd); i++)
-			pfd[i].revents = 0;
-
-		/* poll for activity */
-		rc = poll(pfd, 2,
-			  next_timeout ? (next_timeout * 1000) : -1);
-		if (rc < 0) {
-			perror("poll");
-			return NULL;
-		}
-
-		/* dispatch if activity found */
-		for (i = 0; i < ARRAY_SIZE(pfd); i++) {
-			if (pfd[i].revents) {
-				if (i == 0)
-					cldc_udp_receive_pkt(thr_udp);
-				else
-					handle_user_command();
-			}
-		}
-
-		next_timeout = cld_timers_run(&thr_list);
-	}
-
-	return NULL;
-}
-
 static bool make_abs_path(char *dest, size_t dest_len, const char *src)
 {
 	int len;
@@ -870,76 +133,128 @@ static bool make_abs_path(char *dest, size_t dest_len, const char *src)
 
 static void cmd_cd(const char *arg)
 {
-	struct creq creq = { CREQ_CD, };
-	struct cresp cresp;
+	struct creq creq = { 0, };
+	struct ncld_fh *fhp;
+	int error;
 
 	if (!*arg)
 		arg = "/";
 
 	if (!make_abs_path(creq.path, sizeof(creq.path), arg)) {
-		fprintf(stderr, "%s: path too long\n", arg);
+		fprintf(stderr, TAG ": %s: path too long\n", arg);
 		return;
 	}
 
-	/* send message to thread */
-	write_to_thread(&creq, sizeof(creq));
-
-	/* wait for and receive response from thread */
-	read_from_thread(&cresp, sizeof(cresp));
-
-	if (cresp.tcode != TC_OK) {
-		fprintf(stderr, "%s: change dir failed: %s\n", arg, cresp.msg);
+	fhp = ncld_open(nsp, creq.path, COM_DIRECTORY, &error, 0, NULL, NULL);
+	if (!fhp) {
+		if (error < 1000) {
+			fprintf(stderr, TAG ": cannot open path `%s': %s\n",
+				creq.path, strerror(error));
+		} else {
+			fprintf(stderr, TAG ": cannot open path `%s': %d\n",
+				creq.path, error);
+		}
 		return;
 	}
+	ncld_close(fhp);
 
 	strcpy(clicwd, creq.path);
 }
 
-static void show_lsr(const struct ls_rec *lsr)
-{
-	fprintf(stdout, "%s\n", lsr->name);
-}
-
 static void cmd_ls(const char *arg)
 {
-	struct creq creq = { CREQ_LS, };
-	struct cresp cresp;
+	struct creq creq = { 0, };
+	struct ncld_fh *fhp;
+	struct ncld_read *rp;
+	const char *data;
+	size_t data_len;
+	unsigned int n_records;
+	struct cld_dirent_cur dc;
+	bool first;
+	int error;
 	int i;
+	int rc;
 
 	if (!*arg)
 		arg = clicwd;
 
 	if (!make_abs_path(creq.path, sizeof(creq.path), arg)) {
-		fprintf(stderr, "%s: path too long\n", arg);
+		fprintf(stderr, TAG ": %s: path too long\n", arg);
+		return;
+	}
+
+	fhp = ncld_open(nsp, creq.path, COM_DIRECTORY | COM_READ, &error,
+			0, NULL, NULL);
+	if (!fhp) {
+		if (error < 1000) {
+			fprintf(stderr, TAG ": cannot open path `%s': %s\n",
+				creq.path, strerror(error));
+		} else {
+			fprintf(stderr, TAG ": cannot open path `%s': %d\n",
+				creq.path, error);
+		}
 		return;
 	}
 
-	/* send message to thread */
-	write_to_thread(&creq, sizeof(creq));
+	rp = ncld_get(fhp, &error);
+	if (!rp) {
+		if (error < 1000) {
+			fprintf(stderr, TAG ": cannot get on path `%s': %s\n",
+				creq.path, strerror(error));
+		} else {
+			fprintf(stderr, TAG ": cannot get on path `%s': %d\n",
+				creq.path, error);
+		}
+		ncld_close(fhp);
+		return;
+	}
 
-	/* wait for and receive response from thread */
-	read_from_thread(&cresp, sizeof(cresp));
+	data = rp->ptr;
+	data_len = rp->length;
 
-	if (cresp.tcode != TC_OK) {
-		fprintf(stderr, "ls(%s) failed: %s\n", creq.path, cresp.msg);
+	rc = cldc_dirent_count(data, data_len);
+	if (rc < 0) {
+		fprintf(stderr, TAG ": cldc_dirent_count failed on path `%s'\n",
+				creq.path);
+		ncld_read_free(rp);
+		ncld_close(fhp);
 		return;
 	}
+	n_records = rc;
 
-	for (i = 0; i < cresp.u.n_records; i++) {
-		struct ls_rec lsr;
+	cldc_dirent_cur_init(&dc, data, data_len);
+
+	first = true;
+	for (i = 0; i < n_records; i++) {
+		char *s;
 
-		read_from_thread(&lsr, sizeof(lsr));
+		if (first) {
+			first = false;
+
+			if (cldc_dirent_first(&dc) < 0)
+				break;
+		} else {
+			if (cldc_dirent_next(&dc) < 0)
+				break;
+		}
 
-		show_lsr(&lsr);
+		s = cldc_dirent_name(&dc);
+		printf("%s\n", s);
+		free(s);
 	}
+
+	cldc_dirent_cur_fini(&dc);
+
+	ncld_read_free(rp);
+	ncld_close(fhp);
 }
 
 static void cmd_cat(const char *arg)
 {
-	struct creq creq = { CREQ_CAT, };
-	struct cresp cresp;
-	size_t len;
-	void *mem;
+	struct creq creq = { 0, };
+	struct ncld_fh *fhp;
+	struct ncld_read *rp;
+	int error;
 
 	if (!*arg) {
 		fprintf(stderr, "cat: argument required\n");
@@ -947,77 +262,63 @@ static void cmd_cat(const char *arg)
 	}
 
 	if (!make_abs_path(creq.path, sizeof(creq.path), arg)) {
-		fprintf(stderr, "%s: path too long\n", arg);
+		fprintf(stderr, TAG ": %s: path too long\n", arg);
 		return;
 	}
 
-	/* send message to thread */
-	write_to_thread(&creq, sizeof(creq));
-
-	/* wait for and receive response from thread */
-	read_from_thread(&cresp, sizeof(cresp));
-
-	if (cresp.tcode != TC_OK) {
-		fprintf(stderr, "%s: cat failed: %s\n", arg, cresp.msg);
+	fhp = ncld_open(nsp, creq.path, COM_READ, &error, 0, NULL, NULL);
+	if (!fhp) {
+		if (error < 1000) {
+			fprintf(stderr, TAG ": cannot open path `%s': %s\n",
+				creq.path, strerror(error));
+		} else {
+			fprintf(stderr, TAG ": cannot open path `%s': %d\n",
+				creq.path, error);
+		}
 		return;
 	}
 
-	len = cresp.u.file_len;
-	mem = malloc(len);
-	if (!mem) {
-		fprintf(stderr, "%s: OOM (%u)\n", __func__, (unsigned int) len);
+	rp = ncld_get(fhp, &error);
+	if (!rp) {
+		fprintf(stderr, TAG ": cannot read from path `%s': %d\n",
+			creq.path, error);
+		ncld_close(fhp);
 		return;
 	}
 
-	/* read file data from thread */
-	read_from_thread(mem, len);
-
-	/* write file data to stdout */
-	(void) fwrite(mem, len, 1, stdout);
+	(void) fwrite(rp->ptr, rp->length, 1, stdout);
 	fprintf(stdout, "\n");
 
-	free(mem);
+	ncld_read_free(rp);
+	ncld_close(fhp);
 }
 
 static void cmd_list_locks(void)
 {
-	struct creq creq = { CREQ_LIST_LOCKS, };
-	struct cresp cresp;
-	GList *tmp, *content;
+	GList *tmp;
 
-	/* send message to thread */
-	write_to_thread(&creq, sizeof(creq));
-
-	/* wait for and receive response from thread */
-	read_from_thread(&cresp, sizeof(cresp));
-
-	if (cresp.tcode != TC_OK) {
-		fprintf(stderr, "list-locks failed: %s\n", cresp.msg);
-		return;
-	}
-
-	content = tmp = cresp.u.list;
+	tmp = thr_lock_list;
 	while (tmp) {
-		char *s;
+		struct cldcli_lock_info *li;
 
-		s = tmp->data;
+		li = tmp->data;
 		tmp = tmp->next;
 
-		printf("%s", s);
-
-		free(s);
+		printf("%llu %s\n", (unsigned long long) li->id, li->path);
 	}
-
-	g_list_free(content);
 }
 
-static void cmd_cp_io(const char *cmd, const char *arg, bool read_cld_file)
+static void cmd_cpin(const char *cmd, const char *arg)
 {
 	struct creq creq;
-	struct cresp cresp;
+	struct ncld_fh *fhp;
 	gchar **sv = NULL, *cld_path, *fs_path;
-	void *mem = NULL;
-	size_t flen = 0;
+	gchar *fs_content = NULL;
+	gsize fs_len = 0;
+	int error;
+	int rc;
+
+	memset(&creq, 0, sizeof(creq));
 
 	if (!*arg) {
 		fprintf(stderr, "%s: argument required\n", cmd);
@@ -1030,86 +331,157 @@ static void cmd_cp_io(const char *cmd, const char *arg, bool read_cld_file)
 		goto out;
 	}
 
-	memset(&creq, 0, sizeof(creq));
-
-	if (read_cld_file) {
-		creq.cmd = CREQ_CP_CF;
-		cld_path = sv[0];
-		fs_path = sv[1];
-	} else {
-		gchar *fs_content = NULL;
-		gsize fs_len = 0;
-
-		creq.cmd = CREQ_CP_FC;
-		cld_path = sv[1];
-		fs_path = sv[0];
-
-		if (!g_file_get_contents(fs_path, &fs_content,
-					 &fs_len, NULL)) {
-			fprintf(stderr, "Failed to read data from FS path %s\n",
-				fs_path);
-			goto out;
-		}
+	cld_path = sv[1];
+	fs_path = sv[0];
 
-		mem = fs_content;
-		flen = fs_len;
+	if (!g_file_get_contents(fs_path, &fs_content, &fs_len, NULL)) {
+		fprintf(stderr, TAG ": Failed to read data from FS path %s\n",
+			fs_path);
+		goto out;
 	}
 
 	if (!make_abs_path(creq.path, sizeof(creq.path), cld_path)) {
-		fprintf(stderr, "%s: path too long\n", arg);
+		fprintf(stderr, TAG ": %s: path too long\n", arg);
+		goto out;
+	}
+
+	fhp = ncld_open(nsp, creq.path, COM_CREATE | COM_WRITE,
+			&error, 0, NULL, NULL);
+	if (!fhp) {
+		fprintf(stderr, TAG ": %s: cannot open: %d\n", creq.path, error);
 		goto out;
 	}
 
-	creq.cfi = calloc(1, sizeof(*creq.cfi));
-	if (!creq.cfi) {
-		fprintf(stderr, "OOM\n");
+	rc = ncld_write(fhp, fs_content, fs_len);
+	if (rc) {
+		fprintf(stderr, TAG ": %s(%s -> %s) failed: %d\n",
+			cmd, sv[0], sv[1], rc);
+		ncld_close(fhp);
 		goto out;
 	}
 
-	creq.cfi->mem = mem;
-	creq.cfi->mem_len = flen;
+	ncld_close(fhp);
 
-	/* send message to thread */
-	write_to_thread(&creq, sizeof(creq));
+out:
+	g_strfreev(sv);
+	free(fs_content);
+}
 
-	/* wait for and receive response from thread */
-	read_from_thread(&cresp, sizeof(cresp));
+static void cmd_cpout(const char *cmd, const char *arg)
+{
+	struct creq creq;
+	struct ncld_fh *fhp;
+	struct ncld_read *rp;
+	gchar **sv = NULL, *cld_path, *fs_path;
+	int error;
+
+	memset(&creq, 0, sizeof(creq));
+
+	if (!*arg) {
+		fprintf(stderr, "%s: argument required\n", cmd);
+		return;
+	}
 
-	if (cresp.tcode != TC_OK) {
-		fprintf(stderr, "%s(%s -> %s) failed: %s\n",
-			cmd, sv[0], sv[1], cresp.msg);
+	sv = g_strsplit_set(arg, " \t\f\r\n", 2);
+	if (!sv || !sv[0] || !sv[1]) {
+		fprintf(stderr, "%s: two arguments required\n", cmd);
 		goto out;
 	}
 
-	if (read_cld_file) {
-		flen = cresp.u.file_len;
-		mem = malloc(flen);
-		if (!mem) {
-			fprintf(stderr, "%s: OOM (%u)\n",
-				__func__, (unsigned int) flen);
-			exit(1);
-		}
+	cld_path = sv[0];
+	fs_path = sv[1];
 
-		read_from_thread(mem, flen);
+	if (!make_abs_path(creq.path, sizeof(creq.path), cld_path)) {
+		fprintf(stderr, TAG ": %s: path too long\n", arg);
+		goto out;
+	}
 
-		if (!g_file_set_contents(fs_path, mem, flen, NULL)) {
-			fprintf(stderr, "Successfully read CLD data from %s,\n"
-				"but failed to write data to FS path %s\n",
-				cld_path,
-				fs_path);
-		}
+	fhp = ncld_open(nsp, creq.path, COM_READ, &error, 0, NULL, NULL);
+	if (!fhp) {
+		fprintf(stderr, TAG ": %s: cannot open: %d\n", creq.path, error);
+		goto out;
+	}
+	rp = ncld_get(fhp, &error);
+	if (!rp) {
+		fprintf(stderr, TAG ": cannot read from path `%s': %d\n",
+			creq.path, error);
+		ncld_close(fhp);
+		goto out;
+	}
+
+	if (!g_file_set_contents(fs_path, rp->ptr, rp->length, NULL)) {
+		fprintf(stderr, "Successfully read CLD data from %s,\n"
+			"but failed to write data to FS path %s\n",
+			cld_path, fs_path);
 	}
 
+	ncld_read_free(rp);
+	ncld_close(fhp);
+
 out:
-	free(creq.cfi);
 	g_strfreev(sv);
-	free(mem);
+}
+
+static void cmd_lock(const char *cmd, const char *arg, bool wait_for_lock)
+{
+	struct creq creq = { 0, };
+	struct ncld_fh *fhp;
+	struct cldcli_lock_info *li;
+	int error;
+	int rc;
+
+	if (!*arg) {
+		fprintf(stderr, "%s: argument required\n", cmd);
+		return;
+	}
+
+	if (!make_abs_path(creq.path, sizeof(creq.path), arg)) {
+		fprintf(stderr, TAG ": %s: path too long\n", arg);
+		return;
+	}
+
+	li = calloc(1, sizeof(*li));
+	if (!li) {
+		fprintf(stderr, TAG ": OOM\n");
+		return;
+	}
+
+	li->is_wait = wait_for_lock;
+	li->id = thr_lock_id++;
+	strncpy(li->path, creq.path, CLD_PATH_MAX);
+
+	fhp = ncld_open(nsp, creq.path, COM_LOCK, &error, 0, NULL, NULL);
+	if (!fhp) {
+		fprintf(stderr, TAG ": %s: cannot open: %d\n", creq.path, error);
+		free(li);
+		return;
+	}
+	li->fh = fhp;
+
+	if (wait_for_lock)
+		rc = ncld_qlock(fhp);
+	else
+		rc = ncld_trylock(fhp);
+
+	if (rc < 0) {
+		fprintf(stderr, TAG ": %s: cannot lock: %d\n", creq.path, error);
+		ncld_close(fhp);
+		free(li);
+		return;
+	}
+
+	if (rc > 0)
+		printf("lock %ld queued\n", (long)li->id);
+
+	thr_lock_list = g_list_append(thr_lock_list, li);
 }
 
 static void basic_cmd(const char *cmd, const char *arg, enum creq_cmd cmd_no)
 {
-	struct creq creq = { cmd_no, };
-	struct cresp cresp;
+	struct creq creq = { 0, };
+	struct ncld_fh *fhp;
+	int error;
+	int rc;
 
 	if (!*arg) {
 		fprintf(stderr, "%s: argument required\n", cmd);
@@ -1117,18 +489,57 @@ static void basic_cmd(const char *cmd, const char *arg, enum creq_cmd cmd_no)
 	}
 
 	if (!make_abs_path(creq.path, sizeof(creq.path), arg)) {
-		fprintf(stderr, "%s: path too long\n", arg);
+		fprintf(stderr, TAG ": %s: path too long\n", arg);
 		return;
 	}
 
-	/* send message to thread */
-	write_to_thread(&creq, sizeof(creq));
+	switch (cmd_no) {
+	case CREQ_RM:
+		rc = ncld_del(nsp, creq.path);
+		break;
+	case CREQ_MKDIR:
+		rc = 0;
+		fhp = ncld_open(nsp, creq.path,
+				COM_DIRECTORY | COM_CREATE | COM_EXCL, &error,
+				0, NULL, NULL);
+		if (fhp)
+			ncld_close(fhp);
+		else
+			rc = error;
+		break;
+
+	case CREQ_UNLOCK: {
+		GList *tmp;
+		struct cldcli_lock_info *li = NULL;
+
+		tmp = thr_lock_list;
+		while (tmp) {
+			li = tmp->data;
+
+			if (!strncmp(li->path, creq.path, sizeof(li->path)))
+				break;
 
-	/* wait for and receive response from thread */
-	read_from_thread(&cresp, sizeof(cresp));
+			tmp = tmp->next;
+		}
+		if (!tmp) {
+			fprintf(stderr, TAG ": no lock found\n");
+			return;
+		}
+
+		thr_lock_list = g_list_delete_link(thr_lock_list, tmp);
+
+		rc = ncld_unlock(li->fh);
+		ncld_close(li->fh);
+		free(li);
+		break;
+		}
+	default:
+		fprintf(stderr, TAG ": IE unknown cmd %d\n", cmd_no);
+		return;
+	}
 
-	if (cresp.tcode != TC_OK) {
-		fprintf(stderr, "%s(%s) failed: %s\n", cmd, arg, cresp.msg);
+	if (rc) {
+		fprintf(stderr, TAG ": %s(%s) failed: %d\n", cmd, arg, rc);
 		return;
 	}
 }
@@ -1222,7 +633,8 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state)
 		if (atoi(arg) >= 0 && atoi(arg) <= 2)
 			cli_log.verbose = atoi(arg);
 		else {
-			fprintf(stderr, "invalid debug level: '%s'\n", arg);
+			fprintf(stderr, TAG ": invalid debug level: '%s'\n",
+				arg);
 			argp_usage(state);
 		}
 		break;
@@ -1232,13 +644,13 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state)
 		break;
 	case 'u':
 		if (strlen(arg) >= CLD_MAX_USERNAME) {
-			fprintf(stderr, "invalid user: '%s'\n", arg);
+			fprintf(stderr, TAG ": invalid user: '%s'\n", arg);
 			argp_usage(state);
 		} else
 			strcpy(our_user, arg);
 		break;
 	case 'v':
-		cldcli_verbose = true;
+		cli_log.verbose = true;
 		break;
 	case ARGP_KEY_ARG:
 		argp_usage(state);	/* too many args */
@@ -1254,27 +666,27 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state)
 
 static void prompt(void)
 {
-	fprintf(stderr, "[%s %s]$ ", our_user, clicwd);
-	fflush(stderr);
+	printf("[%s %s]$ ", our_user, clicwd);
+	fflush(stdout);
 }
 
-static char linebuf[CLD_PATH_MAX + 1];
-
 int main (int argc, char *argv[])
 {
+	char linebuf[CLD_PATH_MAX + 1];
+	struct cldc_host *dr;
 	error_t aprc;
-	char tcode;
+	int error;
 
 	/* isspace() and strcasecmp() consistency requires this */
 	setlocale(LC_ALL, "C");
 
 	g_thread_init(NULL);
 
-	cldc_init();
+	ncld_init();
 
 	aprc = argp_parse(&argp, argc, argv, 0, NULL, NULL);
 	if (aprc) {
-		fprintf(stderr, "argp_parse failed: %s\n", strerror(aprc));
+		fprintf(stderr, TAG ": argp_parse failed: %s\n", strerror(aprc));
 		return 1;
 	}
 
@@ -1283,39 +695,35 @@ int main (int argc, char *argv[])
 		char hostb[hostsz];
 
 		if (gethostname(hostb, hostsz-1) < 0) {
-			fprintf(stderr, "gethostname error: %s\n",
+			fprintf(stderr, TAG ": gethostname error: %s\n",
 				strerror(errno));
 			return 1;
 		}
 		hostb[hostsz-1] = 0;
 		if (cldc_getaddr(&host_list, hostb, &cli_log)) {
-			fprintf(stderr, "Unable to find a CLD host\n");
+			fprintf(stderr, TAG ": Unable to find a CLD host\n");
 			return 1;
 		}
 	}
 
-	if ((pipe(from_thread) < 0) || (pipe(to_thread) < 0)) {
-		perror("pipe");
-		return 1;
-	}
-
-	cldthr = g_thread_create(cld_thread, NULL, TRUE, NULL);
-	if (!cldthr) {
-		fprintf(stderr, "thread creation failed\n");
-		return 1;
-	}
+	printf("Waiting for session startup...\n");
+	fflush(stdout);
+	dr = host_list->data;
 
-	fprintf(stderr, "Waiting for thread startup...\n");
-	if (read(from_thread[0], &tcode, 1) != 1) {
-		perror("read");
-		return 1;
-	}
-	if (tcode != TC_OK) {
-		fprintf(stderr, "thread startup failed\n");
+	nsp = ncld_sess_open(dr->host, dr->port, &error, sess_event, NULL,
+			     "cldcli", "cldcli");
+	if (!nsp) {
+		if (error < 1000) {
+			fprintf(stderr, TAG ": cannot open CLD session: %s\n",
+				strerror(error));
+		} else {
+			fprintf(stderr, TAG ": cannot open CLD session: %d\n",
+				error);
+		}
 		return 1;
 	}
 
-	fprintf(stderr, "Type 'help' at the prompt to list commands.\n");
+	printf("Type 'help' at the prompt to list commands.\n");
 	prompt();
 
 	while (fgets(linebuf, sizeof(linebuf), stdin) != NULL) {
@@ -1364,13 +772,13 @@ int main (int argc, char *argv[])
 		else if (!strcmp(tok1, "cat"))
 			cmd_cat(tok2);
 		else if (!strcmp(tok1, "cpin"))
-			cmd_cp_io(tok1, tok2, false);
+			cmd_cpin(tok1, tok2);
 		else if (!strcmp(tok1, "cpout"))
-			cmd_cp_io(tok1, tok2, true);
+			cmd_cpout(tok1, tok2);
 		else if (!strcmp(tok1, "lock"))
-			basic_cmd(tok1, tok2, CREQ_LOCK);
+			cmd_lock(tok1, tok2, true);
 		else if (!strcmp(tok1, "trylock"))
-			basic_cmd(tok1, tok2, CREQ_TRYLOCK);
+			cmd_lock(tok1, tok2, false);
 		else if (!strcmp(tok1, "unlock"))
 			basic_cmd(tok1, tok2, CREQ_UNLOCK);
 		else if ((!strcmp(tok1, "list")) && tok2 &&
@@ -1388,8 +796,7 @@ int main (int argc, char *argv[])
 		prompt();
 	}
 
-	thread_running = 0;
-
+	ncld_sess_close(nsp);
 	return 0;
 }
 
--
To unsubscribe from this list: send the line "unsubscribe hail-devel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Fedora Clound]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux