1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2023-2024 Chelsio Communications, Inc.
5 * Written by: John Baldwin <jhb@FreeBSD.org>
6 */
7
8#include <sys/param.h>
9#include <sys/event.h>
10#include <sys/linker.h>
11#include <sys/module.h>
12#include <sys/socket.h>
13#include <netinet/in.h>
14#include <assert.h>
15#include <err.h>
16#include <errno.h>
17#include <libnvmf.h>
18#include <libutil.h>
19#include <netdb.h>
20#include <signal.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <unistd.h>
25
26#include "internal.h"
27
28bool data_digests = false;
29bool header_digests = false;
30bool flow_control_disable = false;
31bool kernel_io = false;
32
33static const char *subnqn;
34static volatile bool quit = false;
35
36static void
37usage(void)
38{
39	fprintf(stderr, "nvmfd -K [-FGg] [-P port] [-p port] [-t transport] [-n subnqn]\n"
40	    "nvmfd [-dDFH] [-P port] [-p port] [-t transport] [-n subnqn]\n"
41	    "\tdevice [device [...]]\n"
42	    "\n"
43	    "Devices use one of the following syntaxes:\n"
44	    "\tpathame      - file or disk device\n"
45	    "\tramdisk:size - memory disk of given size\n");
46	exit(1);
47}
48
49static void
50handle_sig(int sig __unused)
51{
52	quit = true;
53}
54
55static void
56register_listen_socket(int kqfd, int s, void *udata)
57{
58	struct kevent kev;
59
60	if (listen(s, -1) != 0)
61		err(1, "listen");
62
63	EV_SET(&kev, s, EVFILT_READ, EV_ADD, 0, 0, udata);
64	if (kevent(kqfd, &kev, 1, NULL, 0, NULL) == -1)
65		err(1, "kevent: failed to add listen socket");
66}
67
68static void
69create_passive_sockets(int kqfd, const char *port, bool discovery)
70{
71	struct addrinfo hints, *ai, *list;
72	bool created;
73	int error, s;
74
75	memset(&hints, 0, sizeof(hints));
76	hints.ai_flags = AI_PASSIVE;
77	hints.ai_family = AF_UNSPEC;
78	hints.ai_protocol = IPPROTO_TCP;
79	error = getaddrinfo(NULL, port, &hints, &list);
80	if (error != 0)
81		errx(1, "%s", gai_strerror(error));
82	created = false;
83
84	for (ai = list; ai != NULL; ai = ai->ai_next) {
85		s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
86		if (s == -1)
87			continue;
88
89		if (bind(s, ai->ai_addr, ai->ai_addrlen) != 0) {
90			close(s);
91			continue;
92		}
93
94		if (discovery) {
95			register_listen_socket(kqfd, s, (void *)1);
96		} else {
97			register_listen_socket(kqfd, s, (void *)2);
98			discovery_add_io_controller(s, subnqn);
99		}
100		created = true;
101	}
102
103	freeaddrinfo(list);
104	if (!created)
105		err(1, "Failed to create any listen sockets");
106}
107
108static void
109handle_connections(int kqfd)
110{
111	struct kevent ev;
112	int s;
113
114	signal(SIGHUP, handle_sig);
115	signal(SIGINT, handle_sig);
116	signal(SIGQUIT, handle_sig);
117	signal(SIGTERM, handle_sig);
118
119	while (!quit) {
120		if (kevent(kqfd, NULL, 0, &ev, 1, NULL) == -1) {
121			if (errno == EINTR)
122				continue;
123			err(1, "kevent");
124		}
125
126		assert(ev.filter == EVFILT_READ);
127
128		s = accept(ev.ident, NULL, NULL);
129		if (s == -1) {
130			warn("accept");
131			continue;
132		}
133
134		switch ((uintptr_t)ev.udata) {
135		case 1:
136			handle_discovery_socket(s);
137			break;
138		case 2:
139			handle_io_socket(s);
140			break;
141		default:
142			__builtin_unreachable();
143		}
144	}
145}
146
147int
148main(int ac, char **av)
149{
150	struct pidfh *pfh;
151	const char *dport, *ioport, *transport;
152	pid_t pid;
153	int ch, error, kqfd;
154	bool daemonize;
155	static char nqn[NVMF_NQN_MAX_LEN];
156
157	/* 7.4.9.3 Default port for discovery */
158	dport = "8009";
159
160	pfh = NULL;
161	daemonize = true;
162	ioport = "0";
163	subnqn = NULL;
164	transport = "tcp";
165	while ((ch = getopt(ac, av, "dFgGKn:P:p:t:")) != -1) {
166		switch (ch) {
167		case 'd':
168			daemonize = false;
169			break;
170		case 'F':
171			flow_control_disable = true;
172			break;
173		case 'G':
174			data_digests = true;
175			break;
176		case 'g':
177			header_digests = true;
178			break;
179		case 'K':
180			kernel_io = true;
181			break;
182		case 'n':
183			subnqn = optarg;
184			break;
185		case 'P':
186			dport = optarg;
187			break;
188		case 'p':
189			ioport = optarg;
190			break;
191		case 't':
192			transport = optarg;
193			break;
194		default:
195			usage();
196		}
197	}
198
199	av += optind;
200	ac -= optind;
201
202	if (kernel_io) {
203		if (ac > 0)
204			usage();
205		if (modfind("nvmft") == -1 && kldload("nvmft") == -1)
206			warn("couldn't load nvmft");
207	} else {
208		if (ac < 1)
209			usage();
210	}
211
212	if (strcasecmp(transport, "tcp") == 0) {
213	} else
214		errx(1, "Invalid transport %s", transport);
215
216	if (subnqn == NULL) {
217		error = nvmf_nqn_from_hostuuid(nqn);
218		if (error != 0)
219			errc(1, error, "Failed to generate NQN");
220		subnqn = nqn;
221	}
222
223	if (!kernel_io)
224		register_devices(ac, av);
225
226	init_discovery();
227	init_io(subnqn);
228
229	if (daemonize) {
230		pfh = pidfile_open(NULL, 0600, &pid);
231		if (pfh == NULL) {
232			if (errno == EEXIST)
233				errx(1, "Daemon already running, pid: %jd",
234				    (intmax_t)pid);
235			warn("Cannot open or create pidfile");
236		}
237
238		if (daemon(0, 0) != 0) {
239			pidfile_remove(pfh);
240			err(1, "Failed to fork into the background");
241		}
242
243		pidfile_write(pfh);
244	}
245
246	kqfd = kqueue();
247	if (kqfd == -1) {
248		pidfile_remove(pfh);
249		err(1, "kqueue");
250	}
251
252	create_passive_sockets(kqfd, dport, true);
253	create_passive_sockets(kqfd, ioport, false);
254
255	handle_connections(kqfd);
256	shutdown_io();
257	if (pfh != NULL)
258		pidfile_remove(pfh);
259	return (0);
260}
261