xref: /freebsd/usr.sbin/nvmfd/nvmfd.c (revision e63d20b70ee1dbee9b075f29de6f30cdcfe1abe1)
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 
28 bool data_digests = false;
29 bool header_digests = false;
30 bool flow_control_disable = false;
31 bool kernel_io = false;
32 
33 static const char *subnqn;
34 static volatile bool quit = false;
35 
36 static void
37 usage(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 
49 static void
50 handle_sig(int sig __unused)
51 {
52 	quit = true;
53 }
54 
55 static void
56 register_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 
68 static void
69 create_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 
108 static void
109 handle_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 
147 int
148 main(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