git: 2bb9180bb5d0 - main - ctld: Convert struct target to a C++ class

From: John Baldwin <jhb_at_FreeBSD.org>
Date: Mon, 04 Aug 2025 19:47:01 UTC
The branch main has been updated by jhb:

URL: https://cgit.FreeBSD.org/src/commit/?id=2bb9180bb5d0054bf79529d6a1cb56b61a94629a

commit 2bb9180bb5d0054bf79529d6a1cb56b61a94629a
Author:     John Baldwin <jhb@FreeBSD.org>
AuthorDate: 2025-08-04 19:38:07 +0000
Commit:     John Baldwin <jhb@FreeBSD.org>
CommitDate: 2025-08-04 19:38:07 +0000

    ctld: Convert struct target to a C++ class
    
    - Use std::string for string members.
    
    - Use std::array for the array of LUN pointers indexed by LUN id.
    
    - Move meat of the target_* functions from conf.cc into class methods.
    
    Sponsored by:   Chelsio Communications
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/1794
---
 usr.sbin/ctld/conf.cc      | 165 ++----------------
 usr.sbin/ctld/ctld.cc      | 406 ++++++++++++++++++++++++++++++++-------------
 usr.sbin/ctld/ctld.hh      |  55 +++++-
 usr.sbin/ctld/discovery.cc |  16 +-
 usr.sbin/ctld/kernel.cc    |  28 ++--
 usr.sbin/ctld/login.cc     |  22 +--
 6 files changed, 384 insertions(+), 308 deletions(-)

diff --git a/usr.sbin/ctld/conf.cc b/usr.sbin/ctld/conf.cc
index 8913cccd5889..81451009067c 100644
--- a/usr.sbin/ctld/conf.cc
+++ b/usr.sbin/ctld/conf.cc
@@ -341,217 +341,78 @@ target_finish(void)
 	target = NULL;
 }
 
-static bool
-target_use_private_auth(const char *keyword)
-{
-	if (target->t_auth_group != nullptr) {
-		if (!target->t_private_auth) {
-			log_warnx("cannot use both auth-group and "
-			    "%s for target \"%s\"", keyword, target->t_name);
-			return (false);
-		}
-	} else {
-		target->t_auth_group = auth_group_new(target);
-		if (target->t_auth_group == nullptr)
-			return (false);
-		target->t_private_auth = true;
-	}
-	return (true);
-}
-
 bool
 target_add_chap(const char *user, const char *secret)
 {
-	if (!target_use_private_auth("chap"))
-		return (false);
-	return (target->t_auth_group->add_chap(user, secret));
+	return (target->add_chap(user, secret));
 }
 
 bool
 target_add_chap_mutual(const char *user, const char *secret,
     const char *user2, const char *secret2)
 {
-	if (!target_use_private_auth("chap-mutual"))
-		return (false);
-	return (target->t_auth_group->add_chap_mutual(user, secret, user2,
-	    secret2));
+	return (target->add_chap_mutual(user, secret, user2, secret2));
 }
 
 bool
 target_add_initiator_name(const char *name)
 {
-	if (!target_use_private_auth("initiator-name"))
-		return (false);
-	return (target->t_auth_group->add_initiator_name(name));
+	return (target->add_initiator_name(name));
 }
 
 bool
 target_add_initiator_portal(const char *addr)
 {
-	if (!target_use_private_auth("initiator-portal"))
-		return (false);
-	return (target->t_auth_group->add_initiator_portal(addr));
+	return (target->add_initiator_portal(addr));
 }
 
 bool
 target_add_lun(u_int id, const char *name)
 {
-	struct lun *t_lun;
-
-	if (id >= MAX_LUNS) {
-		log_warnx("LUN %u too big for target \"%s\"", id,
-		    target->t_name);
-		return (false);
-	}
-
-	if (target->t_luns[id] != NULL) {
-		log_warnx("duplicate LUN %u for target \"%s\"", id,
-		    target->t_name);
-		return (false);
-	}
-
-	t_lun = lun_find(conf, name);
-	if (t_lun == NULL) {
-		log_warnx("unknown LUN named %s used for target \"%s\"",
-		    name, target->t_name);
-		return (false);
-	}
-
-	target->t_luns[id] = t_lun;
-	return (true);
+	return (target->add_lun(id, name));
 }
 
 bool
 target_add_portal_group(const char *pg_name, const char *ag_name)
 {
-	struct portal_group *pg;
-	auth_group_sp ag;
-
-	pg = portal_group_find(conf, pg_name);
-	if (pg == NULL) {
-		log_warnx("unknown portal-group \"%s\" for target \"%s\"",
-		    pg_name, target->t_name);
-		return (false);
-	}
-
-	if (ag_name != NULL) {
-		ag = auth_group_find(conf, ag_name);
-		if (ag == NULL) {
-			log_warnx("unknown auth-group \"%s\" for target \"%s\"",
-			    ag_name, target->t_name);
-			return (false);
-		}
-	}
-
-	if (!port_new(conf, target, pg, std::move(ag))) {
-		log_warnx("can't link portal-group \"%s\" to target \"%s\"",
-		    pg_name, target->t_name);
-		return (false);
-	}
-	return (true);
+	return (target->add_portal_group(pg_name, ag_name));
 }
 
 bool
 target_set_alias(const char *alias)
 {
-	if (target->t_alias != NULL) {
-		log_warnx("alias for target \"%s\" specified more than once",
-		    target->t_name);
-		return (false);
-	}
-	target->t_alias = checked_strdup(alias);
-	return (true);
+	return (target->set_alias(alias));
 }
 
 bool
 target_set_auth_group(const char *name)
 {
-	if (target->t_auth_group != nullptr) {
-		if (target->t_private_auth)
-			log_warnx("cannot use both auth-group and explicit "
-			    "authorisations for target \"%s\"", target->t_name);
-		else
-			log_warnx("auth-group for target \"%s\" "
-			    "specified more than once", target->t_name);
-		return (false);
-	}
-	target->t_auth_group = auth_group_find(conf, name);
-	if (target->t_auth_group == nullptr) {
-		log_warnx("unknown auth-group \"%s\" for target \"%s\"", name,
-		    target->t_name);
-		return (false);
-	}
-	return (true);
+	return (target->set_auth_group(name));
 }
 
 bool
 target_set_auth_type(const char *type)
 {
-	if (!target_use_private_auth("auth-type"))
-		return (false);
-	return (target->t_auth_group->set_type(type));
+	return (target->set_auth_type(type));
 }
 
 bool
 target_set_physical_port(const char *pport)
 {
-	if (target->t_pport != NULL) {
-		log_warnx("cannot set multiple physical ports for target "
-		    "\"%s\"", target->t_name);
-		return (false);
-	}
-	target->t_pport = checked_strdup(pport);
-	return (true);
+	return (target->set_physical_port(pport));
 }
 
 bool
 target_set_redirection(const char *addr)
 {
-
-	if (target->t_redirection != NULL) {
-		log_warnx("cannot set redirection to \"%s\" for "
-		    "target \"%s\"; already defined",
-		    addr, target->t_name);
-		return (false);
-	}
-
-	target->t_redirection = checked_strdup(addr);
-
-	return (true);
+	return (target->set_redirection(addr));
 }
 
 bool
 target_start_lun(u_int id)
 {
-	struct lun *new_lun;
-	char *name;
-
-	if (id >= MAX_LUNS) {
-		log_warnx("LUN %u too big for target \"%s\"", id,
-		    target->t_name);
-		return (false);
-	}
-
-	if (target->t_luns[id] != NULL) {
-		log_warnx("duplicate LUN %u for target \"%s\"", id,
-		    target->t_name);
-		return (false);
-	}
-
-	if (asprintf(&name, "%s,lun,%u", target->t_name, id) <= 0)
-		log_err(1, "asprintf");
-
-	new_lun = lun_new(conf, name);
-	if (new_lun == NULL)
-		return (false);
-
-	new_lun->set_scsiname(name);
-	free(name);
-
-	target->t_luns[id] = new_lun;
-
-	lun = new_lun;
-	return (true);
+	lun = target->start_lun(id);
+	return (lun != nullptr);
 }
 
 bool
diff --git a/usr.sbin/ctld/ctld.cc b/usr.sbin/ctld/ctld.cc
index 1dbc6988b9d1..2aaba32ed101 100644
--- a/usr.sbin/ctld/ctld.cc
+++ b/usr.sbin/ctld/ctld.cc
@@ -51,6 +51,7 @@
 #include <unistd.h>
 #include <cam/scsi/scsi_all.h>
 
+#include <algorithm>
 #include <libutil++.hh>
 
 #include "conf.h"
@@ -101,7 +102,6 @@ conf_new(void)
 	struct conf *conf;
 
 	conf = new struct conf();
-	TAILQ_INIT(&conf->conf_targets);
 
 	conf->conf_isns_period = 900;
 	conf->conf_isns_timeout = 5;
@@ -115,12 +115,8 @@ conf_new(void)
 void
 conf_delete(struct conf *conf)
 {
-	struct target *targ, *tmp;
-
 	assert(conf->conf_pidfh == NULL);
 
-	TAILQ_FOREACH_SAFE(targ, &conf->conf_targets, t_next, tmp)
-		target_delete(targ);
 	free(conf->conf_pidfile_path);
 	delete conf;
 }
@@ -410,13 +406,6 @@ auth_group_new(struct conf *conf, const char *name)
 	return (pair.first->second.get());
 }
 
-auth_group_sp
-auth_group_new(struct target *target)
-{
-	return (std::make_shared<auth_group>(freebsd::stringf("target \"%s\"",
-	    target->t_name)));
-}
-
 auth_group_sp
 auth_group_find(const struct conf *conf, const char *name)
 {
@@ -516,13 +505,13 @@ parse_addr_port(const char *address, const char *def_port)
 void
 portal_group::add_port(struct portal_group_port *port)
 {
-	pg_ports.emplace(port->target()->t_name, port);
+	pg_ports.emplace(port->target()->name(), port);
 }
 
 void
 portal_group::remove_port(struct portal_group_port *port)
 {
-	auto it = pg_ports.find(port->target()->t_name);
+	auto it = pg_ports.find(port->target()->name());
 	pg_ports.erase(it);
 }
 
@@ -821,11 +810,10 @@ isns::send_request(int s, struct isns_req req)
 static struct isns_req
 isns_register_request(struct conf *conf, const char *hostname)
 {
-	struct target *target;
 	const struct portal_group *pg;
 
 	isns_req req(ISNS_FUNC_DEVATTRREG, ISNS_FLAG_CLIENT, "register");
-	req.add_str(32, TAILQ_FIRST(&conf->conf_targets)->t_name);
+	req.add_str(32, conf->conf_first_target->name());
 	req.add_delim();
 	req.add_str(1, hostname);
 	req.add_32(2, 2); /* 2 -- iSCSI */
@@ -840,12 +828,14 @@ isns_register_request(struct conf *conf, const char *hostname)
 			req.add_port(17, portal->ai());
 		}
 	}
-	TAILQ_FOREACH(target, &conf->conf_targets, t_next) {
-		req.add_str(32, target->t_name);
+	for (const auto &kv : conf->conf_targets) {
+		const struct target *target = kv.second.get();
+
+		req.add_str(32, target->name());
 		req.add_32(33, 1); /* 1 -- Target*/
-		if (target->t_alias != NULL)
-			req.add_str(34, target->t_alias);
-		for (const port *port : target->t_ports) {
+		if (target->has_alias())
+			req.add_str(34, target->alias());
+		for (const port *port : target->ports()) {
 			pg = port->portal_group();
 			if (pg == nullptr)
 				continue;
@@ -863,7 +853,7 @@ static struct isns_req
 isns_check_request(struct conf *conf, const char *hostname)
 {
 	isns_req req(ISNS_FUNC_DEVATTRQRY, ISNS_FLAG_CLIENT, "check");
-	req.add_str(32, TAILQ_FIRST(&conf->conf_targets)->t_name);
+	req.add_str(32, conf->conf_first_target->name());
 	req.add_str(1, hostname);
 	req.add_delim();
 	req.add(2, 0, NULL);
@@ -874,7 +864,7 @@ static struct isns_req
 isns_deregister_request(struct conf *conf, const char *hostname)
 {
 	isns_req req(ISNS_FUNC_DEVDEREG, ISNS_FLAG_CLIENT, "deregister");
-	req.add_str(32, TAILQ_FIRST(&conf->conf_targets)->t_name);
+	req.add_str(32, conf->conf_first_target->name());
 	req.add_delim();
 	req.add_str(1, hostname);
 	return (req);
@@ -887,8 +877,7 @@ isns_register_targets(struct conf *conf, struct isns *isns,
 	int error;
 	char hostname[256];
 
-	if (TAILQ_EMPTY(&conf->conf_targets) ||
-	    conf->conf_portal_groups.empty())
+	if (conf->conf_targets.empty() || conf->conf_portal_groups.empty())
 		return;
 	set_timeout(conf->conf_isns_timeout, false);
 	freebsd::fd_up s = isns->connect();
@@ -900,7 +889,7 @@ isns_register_targets(struct conf *conf, struct isns *isns,
 	if (error != 0)
 		log_err(1, "gethostname");
 
-	if (oldconf == NULL || TAILQ_EMPTY(&oldconf->conf_targets))
+	if (oldconf == nullptr || oldconf->conf_first_target == nullptr)
 		oldconf = conf;
 	isns->send_request(s, isns_deregister_request(oldconf, hostname));
 	isns->send_request(s, isns_register_request(conf, hostname));
@@ -914,8 +903,7 @@ isns_check(struct conf *conf, struct isns *isns)
 	int error;
 	char hostname[256];
 
-	if (TAILQ_EMPTY(&conf->conf_targets) ||
-	    conf->conf_portal_groups.empty())
+	if (conf->conf_targets.empty() || conf->conf_portal_groups.empty())
 		return;
 	set_timeout(conf->conf_isns_timeout, false);
 	freebsd::fd_up s = isns->connect();
@@ -941,8 +929,7 @@ isns_deregister_targets(struct conf *conf, struct isns *isns)
 	int error;
 	char hostname[256];
 
-	if (TAILQ_EMPTY(&conf->conf_targets) ||
-	    conf->conf_portal_groups.empty())
+	if (conf->conf_targets.empty() || conf->conf_portal_groups.empty())
 		return;
 	set_timeout(conf->conf_isns_timeout, false);
 	freebsd::fd_up s = isns->connect();
@@ -987,13 +974,13 @@ kports::find_port(std::string_view name)
 port::port(struct target *target) :
 	p_target(target)
 {
-	target->t_ports.push_back(this);
+	target->add_port(this);
 }
 
 void
 port::clear_references()
 {
-	p_target->t_ports.remove(this);
+	p_target->remove_port(this);
 }
 
 portal_group_port::portal_group_port(struct target *target,
@@ -1029,7 +1016,7 @@ port_new(struct conf *conf, struct target *target, struct portal_group *pg,
     auth_group_sp ag)
 {
 	std::string name = freebsd::stringf("%s-%s", pg->name(),
-	    target->t_name);
+	    target->name());
 	const auto &pair = conf->conf_ports.try_emplace(name,
 	    std::make_unique<portal_group_port>(target, pg, ag));
 	if (!pair.second) {
@@ -1045,7 +1032,7 @@ port_new(struct conf *conf, struct target *target, struct portal_group *pg,
     uint32_t ctl_port)
 {
 	std::string name = freebsd::stringf("%s-%s", pg->name(),
-	    target->t_name);
+	    target->name());
 	const auto &pair = conf->conf_ports.try_emplace(name,
 	    std::make_unique<portal_group_port>(target, pg, ctl_port));
 	if (!pair.second) {
@@ -1060,7 +1047,7 @@ static bool
 port_new_pp(struct conf *conf, struct target *target, struct pport *pp)
 {
 	std::string name = freebsd::stringf("%s-%s", pp->name(),
-	    target->t_name);
+	    target->name());
 	const auto &pair = conf->conf_ports.try_emplace(name,
 	    std::make_unique<kernel_port>(target, pp));
 	if (!pair.second) {
@@ -1084,7 +1071,7 @@ port_new_ioctl(struct conf *conf, struct kports &kports, struct target *target,
 	if (pport != NULL)
 		return (port_new_pp(conf, target, pport));
 
-	std::string name = pname + "-" + target->t_name;
+	std::string name = pname + "-" + target->name();
 	const auto &pair = conf->conf_ports.try_emplace(name,
 	    std::make_unique<ioctl_port>(target, pp, vp));
 	if (!pair.second) {
@@ -1107,55 +1094,280 @@ portal_group::find_port(std::string_view target) const
 struct target *
 target_new(struct conf *conf, const char *name)
 {
-	struct target *targ;
-	int i, len;
+	if (!valid_iscsi_name(name, log_warnx))
+		return (nullptr);
+
+	/*
+	 * RFC 3722 requires us to normalize the name to lowercase.
+	 */
+	std::string t_name(name);
+	for (char &c : t_name)
+		c = tolower(c);
 
-	targ = target_find(conf, name);
-	if (targ != NULL) {
+	auto const &pair = conf->conf_targets.try_emplace(t_name,
+	    std::make_unique<target>(conf, t_name));
+	if (!pair.second) {
 		log_warnx("duplicated target \"%s\"", name);
 		return (NULL);
 	}
-	if (valid_iscsi_name(name, log_warnx) == false) {
-		return (NULL);
+
+	if (conf->conf_first_target == nullptr)
+		conf->conf_first_target = pair.first->second.get();
+	return (pair.first->second.get());
+}
+
+struct target *
+target_find(struct conf *conf, const char *name)
+{
+	auto it = conf->conf_targets.find(name);
+	if (it == conf->conf_targets.end())
+		return (nullptr);
+	return (it->second.get());
+}
+
+bool
+target::use_private_auth(const char *keyword)
+{
+	if (t_private_auth)
+		return (true);
+
+	if (t_auth_group != nullptr) {
+		log_warnx("cannot use both auth-group and %s for target \"%s\"",
+		    keyword, name());
+		return (false);
 	}
-	targ = new target();
-	targ->t_name = checked_strdup(name);
 
-	/*
-	 * RFC 3722 requires us to normalize the name to lowercase.
-	 */
-	len = strlen(name);
-	for (i = 0; i < len; i++)
-		targ->t_name[i] = tolower(targ->t_name[i]);
+	std::string label = freebsd::stringf("target \"%s\"", name());
+	t_auth_group = std::make_shared<struct auth_group>(label);
+	t_private_auth = true;
+	return (true);
+}
+
+bool
+target::add_chap(const char *user, const char *secret)
+{
+	if (!use_private_auth("chap"))
+		return (false);
+	return (t_auth_group->add_chap(user, secret));
+}
+
+bool
+target::add_chap_mutual(const char *user, const char *secret,
+    const char *user2, const char *secret2)
+{
+	if (!use_private_auth("chap-mutual"))
+		return (false);
+	return (t_auth_group->add_chap_mutual(user, secret, user2, secret2));
+}
+
+bool
+target::add_initiator_name(std::string_view name)
+{
+	if (!use_private_auth("initiator-name"))
+		return (false);
+	return (t_auth_group->add_initiator_name(name));
+}
+
+bool
+target::add_initiator_portal(const char *addr)
+{
+	if (!use_private_auth("initiator-portal"))
+		return (false);
+	return (t_auth_group->add_initiator_portal(addr));
+}
+
+bool
+target::add_lun(u_int id, const char *lun_name)
+{
+	struct lun *t_lun;
+
+	if (id >= MAX_LUNS) {
+		log_warnx("LUN %u too big for target \"%s\"", id, name());
+		return (false);
+	}
+
+	if (t_luns[id] != NULL) {
+		log_warnx("duplicate LUN %u for target \"%s\"", id, name());
+		return (false);
+	}
+
+	t_lun = lun_find(t_conf, lun_name);
+	if (t_lun == NULL) {
+		log_warnx("unknown LUN named %s used for target \"%s\"",
+		    lun_name, name());
+		return (false);
+	}
+
+	t_luns[id] = t_lun;
+	return (true);
+}
+
+bool
+target::add_portal_group(const char *pg_name, const char *ag_name)
+{
+	struct portal_group *pg;
+	auth_group_sp ag;
+
+	pg = portal_group_find(t_conf, pg_name);
+	if (pg == NULL) {
+		log_warnx("unknown portal-group \"%s\" for target \"%s\"",
+		    pg_name, name());
+		return (false);
+	}
+
+	if (ag_name != NULL) {
+		ag = auth_group_find(t_conf, ag_name);
+		if (ag == NULL) {
+			log_warnx("unknown auth-group \"%s\" for target \"%s\"",
+			    ag_name, name());
+			return (false);
+		}
+	}
+
+	if (!port_new(t_conf, this, pg, std::move(ag))) {
+		log_warnx("can't link portal-group \"%s\" to target \"%s\"",
+		    pg_name, name());
+		return (false);
+	}
+	return (true);
+}
+
+bool
+target::set_alias(std::string_view alias)
+{
+	if (has_alias()) {
+		log_warnx("alias for target \"%s\" specified more than once",
+		    name());
+		return (false);
+	}
+	t_alias = alias;
+	return (true);
+}
+
+bool
+target::set_auth_group(const char *ag_name)
+{
+	if (t_auth_group != nullptr) {
+		if (t_private_auth)
+			log_warnx("cannot use both auth-group and explicit "
+			    "authorisations for target \"%s\"", name());
+		else
+			log_warnx("auth-group for target \"%s\" "
+			    "specified more than once", name());
+		return (false);
+	}
+	t_auth_group = auth_group_find(t_conf, ag_name);
+	if (t_auth_group == nullptr) {
+		log_warnx("unknown auth-group \"%s\" for target \"%s\"",
+		    ag_name, name());
+		return (false);
+	}
+	return (true);
+}
+
+bool
+target::set_auth_type(const char *type)
+{
+	if (!use_private_auth("auth-type"))
+		return (false);
+	return (t_auth_group->set_type(type));
+}
+
+bool
+target::set_physical_port(std::string_view pport)
+{
+	if (!t_pport.empty()) {
+		log_warnx("cannot set multiple physical ports for target "
+		    "\"%s\"", name());
+		return (false);
+	}
+	t_pport = pport;
+	return (true);
+}
+
+bool
+target::set_redirection(const char *addr)
+{
+	if (!t_redirection.empty()) {
+		log_warnx("cannot set redirection to \"%s\" for "
+		    "target \"%s\"; already defined",
+		    addr, name());
+		return (false);
+	}
 
-	targ->t_conf = conf;
-	TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next);
+	t_redirection = addr;
+	return (true);
+}
+
+struct lun *
+target::start_lun(u_int id)
+{
+	struct lun *new_lun;
 
-	return (targ);
+	if (id >= MAX_LUNS) {
+		log_warnx("LUN %u too big for target \"%s\"", id,
+		    name());
+		return (nullptr);
+	}
+
+	if (t_luns[id] != NULL) {
+		log_warnx("duplicate LUN %u for target \"%s\"", id,
+		    name());
+		return (nullptr);
+	}
+
+	std::string lun_name = freebsd::stringf("%s,lun,%u", name(), id);
+	new_lun = lun_new(t_conf, lun_name.c_str());
+	if (new_lun == nullptr)
+		return (nullptr);
+
+	new_lun->set_scsiname(lun_name.c_str());
+
+	t_luns[id] = new_lun;
+
+	return (new_lun);
 }
 
 void
-target_delete(struct target *targ)
+target::add_port(struct port *port)
 {
-	TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next);
+	t_ports.push_back(port);
+}
 
-	free(targ->t_pport);
-	free(targ->t_name);
-	free(targ->t_redirection);
-	delete targ;
+void
+target::remove_port(struct port *port)
+{
+	t_ports.remove(port);
 }
 
-struct target *
-target_find(struct conf *conf, const char *name)
+void
+target::remove_lun(struct lun *lun)
 {
-	struct target *targ;
+	/* XXX: clang is not able to deduce the type without the cast. */
+	std::replace(t_luns.begin(), t_luns.end(), lun,
+	    static_cast<struct lun *>(nullptr));
+}
 
-	TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
-		if (strcasecmp(targ->t_name, name) == 0)
-			return (targ);
+void
+target::verify()
+{
+	if (t_auth_group == nullptr) {
+		t_auth_group = auth_group_find(t_conf, "default");
+		assert(t_auth_group != nullptr);
+	}
+	if (t_ports.empty()) {
+		struct portal_group *pg = portal_group_find(t_conf, "default");
+		assert(pg != NULL);
+		port_new(t_conf, this, pg, nullptr);
 	}
 
-	return (NULL);
+	bool found = std::any_of(t_luns.begin(), t_luns.end(),
+	    [](struct lun *lun) { return (lun != nullptr); });
+	if (!found && t_redirection.empty())
+		log_warnx("no LUNs defined for target \"%s\"", name());
+	if (found && !t_redirection.empty())
+		log_debugx("target \"%s\" contains luns,  but configured "
+		    "for redirection", name());
 }
 
 lun::lun(struct conf *conf, std::string_view name)
@@ -1178,15 +1390,8 @@ lun_new(struct conf *conf, const char *name)
 static void
 conf_delete_target_luns(struct conf *conf, struct lun *lun)
 {
-	struct target *targ;
-	int i;
-
-	TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
-		for (i = 0; i < MAX_LUNS; i++) {
-			if (targ->t_luns[i] == lun)
-				targ->t_luns[i] = NULL;
-		}
-	}
+	for (const auto &kv : conf->conf_targets)
+		kv.second->remove_lun(lun);
 }
 
 struct lun *
@@ -1518,11 +1723,6 @@ lun::verify()
 bool
 conf_verify(struct conf *conf)
 {
-	struct portal_group *pg;
-	struct target *targ;
-	bool found;
-	int i;
-
 	if (conf->conf_pidfile_path == NULL)
 		conf->conf_pidfile_path = checked_strdup(DEFAULT_PIDFILE);
 
@@ -1546,31 +1746,8 @@ conf_verify(struct conf *conf)
 		}
 	}
 
-	TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
-		if (targ->t_auth_group == NULL) {
-			targ->t_auth_group = auth_group_find(conf,
-			    "default");
-			assert(targ->t_auth_group != NULL);
-		}
-		if (targ->t_ports.empty()) {
-			pg = portal_group_find(conf, "default");
-			assert(pg != NULL);
-			port_new(conf, targ, pg, nullptr);
-		}
-		found = false;
-		for (i = 0; i < MAX_LUNS; i++) {
-			if (targ->t_luns[i] != NULL)
-				found = true;
-		}
-		if (!found && targ->t_redirection == NULL) {
-			log_warnx("no LUNs defined for target \"%s\"",
-			    targ->t_name);
-		}
-		if (found && targ->t_redirection != NULL) {
-			log_debugx("target \"%s\" contains luns, "
-			    " but configured for redirection",
-			    targ->t_name);
-		}
+	for (auto &kv : conf->conf_targets) {
+		kv.second->verify();
 	}
 	for (auto &kv : conf->conf_portal_groups) {
 		kv.second->verify(conf);
@@ -2361,40 +2538,41 @@ conf_new_from_file(const char *path, bool ucl)
 static bool
 new_pports_from_conf(struct conf *conf, struct kports &kports)
 {
-	struct target *targ;
 	struct pport *pp;
 	int ret, i_pp, i_vp;
 
-	TAILQ_FOREACH(targ, &conf->conf_targets, t_next) {
-		if (!targ->t_pport)
+	for (auto &kv : conf->conf_targets) {
+		struct target *targ = kv.second.get();
+
+		if (!targ->has_pport())
 			continue;
 
-		ret = sscanf(targ->t_pport, "ioctl/%d/%d", &i_pp, &i_vp);
+		ret = sscanf(targ->pport(), "ioctl/%d/%d", &i_pp, &i_vp);
 		if (ret > 0) {
 			if (!port_new_ioctl(conf, kports, targ, i_pp, i_vp)) {
 				log_warnx("can't create new ioctl port "
-				    "for target \"%s\"", targ->t_name);
+				    "for target \"%s\"", targ->name());
 				return (false);
 			}
 
 			continue;
 		}
 
-		pp = kports.find_port(targ->t_pport);
+		pp = kports.find_port(targ->pport());
 		if (pp == NULL) {
 			log_warnx("unknown port \"%s\" for target \"%s\"",
-			    targ->t_pport, targ->t_name);
+			    targ->pport(), targ->name());
 			return (false);
 		}
 		if (pp->linked()) {
 			log_warnx("can't link port \"%s\" to target \"%s\", "
 			    "port already linked to some target",
-			    targ->t_pport, targ->t_name);
+			    targ->pport(), targ->name());
 			return (false);
 		}
 		if (!port_new_pp(conf, targ, pp)) {
 			log_warnx("can't link port \"%s\" to target \"%s\"",
-			    targ->t_pport, targ->t_name);
+			    targ->pport(), targ->name());
 			return (false);
 		}
 	}
diff --git a/usr.sbin/ctld/ctld.hh b/usr.sbin/ctld/ctld.hh
index 7fb0ed7a8bea..a242a3282b31 100644
--- a/usr.sbin/ctld/ctld.hh
+++ b/usr.sbin/ctld/ctld.hh
@@ -41,6 +41,7 @@
 #include <libiscsiutil.h>
 #include <libutil.h>
 
+#include <array>
 #include <list>
 #include <memory>
 #include <string>
@@ -347,16 +348,53 @@ private:
 };
 
 struct target {
-	TAILQ_ENTRY(target)		t_next;
+	target(struct conf *conf, std::string_view name) :
+		t_conf(conf), t_name(name) {}
+
+	bool has_alias() const { return !t_alias.empty(); }
+	bool has_pport() const { return !t_pport.empty(); }
+	bool has_redirection() const { return !t_redirection.empty(); }
+	const char *alias() const { return t_alias.c_str(); }
+	const char *name() const { return t_name.c_str(); }
+	const char *pport() const { return t_pport.c_str(); }
+	bool private_auth() const { return t_private_auth; }
+	const char *redirection() const { return t_redirection.c_str(); }
+
+	struct auth_group *auth_group() const { return t_auth_group.get(); }
+	const std::list<port *> &ports() const { return t_ports; }
+	const struct lun *lun(int idx) const { return t_luns[idx]; }
+
+	bool add_chap(const char *user, const char *secret);
+	bool add_chap_mutual(const char *user, const char *secret,
+	    const char *user2, const char *secret2);
+	bool add_initiator_name(std::string_view name);
+	bool add_initiator_portal(const char *addr);
+	bool add_lun(u_int id, const char *lun_name);
+	bool add_portal_group(const char *pg_name, const char *ag_name);
+	bool set_alias(std::string_view alias);
+	bool set_auth_group(const char *ag_name);
+	bool set_auth_type(const char *type);
+	bool set_physical_port(std::string_view pport);
+	bool set_redirection(const char *addr);
+	struct lun *start_lun(u_int id);
+
+	void add_port(struct port *port);
+	void remove_lun(struct lun *lun);
+	void remove_port(struct port *port);
+	void verify();
+
+private:
+	bool use_private_auth(const char *keyword);
+
 	struct conf			*t_conf;
-	struct lun			*t_luns[MAX_LUNS] = {};
+	std::array<struct lun *, MAX_LUNS> t_luns;
 	auth_group_sp			t_auth_group;
 	std::list<port *>		t_ports;
-	char				*t_name;
-	char				*t_alias;
-	char				*t_redirection;
+	std::string			t_name;
+	std::string			t_alias;
+	std::string			t_redirection;
 	/* Name of this target's physical port, if any, i.e. "isp0" */
-	char				*t_pport;
+	std::string			t_pport;
 	bool				t_private_auth;
 };
 
@@ -379,11 +417,12 @@ struct conf {
 
 	char				*conf_pidfile_path = nullptr;
 	std::unordered_map<std::string, std::unique_ptr<lun>> conf_luns;
-	TAILQ_HEAD(, target)		conf_targets;
+	std::unordered_map<std::string, std::unique_ptr<target>> conf_targets;
 	std::unordered_map<std::string, auth_group_sp> conf_auth_groups;
 	std::unordered_map<std::string, std::unique_ptr<port>> conf_ports;
 	std::unordered_map<std::string, portal_group_up> conf_portal_groups;
 	std::unordered_map<std::string, isns> conf_isns;
+	struct target			*conf_first_target = nullptr;
 	int				conf_isns_period;
 	int				conf_isns_timeout;
 	int				conf_debug;
@@ -466,7 +505,6 @@ void			conf_start(struct conf *new_conf);
 bool			conf_verify(struct conf *conf);
 
 struct auth_group	*auth_group_new(struct conf *conf, const char *name);
-auth_group_sp		auth_group_new(struct target *target);
*** 233 LINES SKIPPED ***