1/*
2 * Copyright (c) 2014 Intel Corporation. All Rights Reserved
3 *
4 * This software is available to you under a choice of one of two
5 * licenses.  You may choose to be licensed under the terms of the GNU
6 * General Public License (GPL) Version 2, available from the file
7 * COPYING in the main directory of this source tree, or the
8 * OpenIB.org BSD license below:
9 *
10 *     Redistribution and use in source and binary forms, with or
11 *     without modification, are permitted provided that the following
12 *     conditions are met:
13 *
14 *      - Redistributions of source code must retain the above
15 *        copyright notice, this list of conditions and the following
16 *        disclaimer.
17 *
18 *      - Redistributions in binary form must reproduce the above
19 *        copyright notice, this list of conditions and the following
20 *        disclaimer in the documentation and/or other materials
21 *        provided with the distribution.
22 *
23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
27 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
28 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
29 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 * SOFTWARE.
31 *
32 */
33
34#if HAVE_CONFIG_H
35#  include <config.h>
36#endif				/* HAVE_CONFIG_H */
37
38#include <poll.h>
39#include <sys/types.h>
40#include <sys/stat.h>
41#include <fcntl.h>
42#include <assert.h>
43#include <string.h>
44#include <limits.h>
45#include <stdio.h>
46#include <syslog.h>
47#include <dirent.h>
48#include <errno.h>
49#include <unistd.h>
50#include <getopt.h>
51#include <stdlib.h>
52
53#include <libudev.h>
54
55struct udev *udev;
56struct udev_monitor *mon;
57
58#include "ibdiag_common.h"
59
60#define SYS_HOSTNAME "/proc/sys/kernel/hostname"
61#define DEF_SYS_DIR "/sys"
62char *sys_dir = DEF_SYS_DIR;
63#define SYS_INFINIBAND "class/infiniband"
64#define DEFAULT_RETRY_RATE 60
65#define DEFAULT_RETRY_COUNT 0
66#define DEFAULT_ND_FORMAT "%h %d"
67
68int failure_retry_rate = DEFAULT_RETRY_RATE;
69int set_retry_cnt = DEFAULT_RETRY_COUNT;
70int foreground = 0;
71char *pidfile = NULL;
72
73static void newline_to_null(char *str)
74{
75	char *term = index(str, '\n');
76	if (term)
77		*term = '\0';
78}
79
80static void strip_domain(char *str)
81{
82	char *term = index(str, '.');
83	if (term)
84		*term = '\0';
85}
86
87static void build_node_desc(char *dest, size_t len,
88		     const char *device, const char *hostname)
89{
90	char *end = dest + len-1;
91	const char *field;
92	char *src = ibd_nd_format;
93
94	while (*src && (dest < end)) {
95		if (*src != '%') {
96			*dest++ = *src++;
97		} else {
98			src++;
99			switch (*src) {
100			case 'h':
101				field = hostname;
102				while (*field && (*field != '.') && (dest < end))
103					*dest++ = *field++;
104				break;
105			case 'd':
106				field = device;
107				while (*field && (dest < end))
108					*dest++ = *field++;
109				break;
110			}
111			src++;
112		}
113	}
114	*dest = 0;
115}
116
117static int update_node_desc(const char *device, const char *hostname, int force)
118{
119	int rc;
120	char nd[128];
121	char new_nd[64];
122	char nd_file[PATH_MAX];
123	FILE *f;
124
125	snprintf(nd_file, sizeof(nd_file), "%s/%s/%s/node_desc",
126			sys_dir, SYS_INFINIBAND, device);
127	nd_file[sizeof(nd_file)-1] = '\0';
128
129	f = fopen(nd_file, "r+");
130	if (!f) {
131		syslog(LOG_ERR, "Failed to open %s\n", nd_file);
132		return -EIO;
133	}
134
135	if (!fgets(nd, sizeof(nd), f)) {
136		syslog(LOG_ERR, "Failed to read %s\n", nd_file);
137		rc = -EIO;
138		goto error;
139	}
140	newline_to_null(nd);
141
142	build_node_desc(new_nd, sizeof(new_nd), device, hostname);
143
144	if (!force && strncmp(new_nd, nd, sizeof(new_nd)) == 0) {
145		syslog(LOG_INFO, "%s: no change (%s)\n", device, new_nd);
146	} else {
147		syslog(LOG_INFO, "%s: change (%s) -> (%s)\n",
148			device, nd, new_nd);
149		rewind(f);
150		fprintf(f, new_nd);
151	}
152
153	rc = 0;
154error:
155	fclose(f);
156	return rc;
157}
158
159static int set_rdma_node_desc(const char *hostname, int force)
160{
161	DIR *class_dir;
162	struct dirent *dent;
163	char dev_dir[PATH_MAX];
164
165	snprintf(dev_dir, sizeof(dev_dir), "%s/%s", sys_dir, SYS_INFINIBAND);
166	dev_dir[sizeof(dev_dir)-1] = '\0';
167
168	class_dir = opendir(dev_dir);
169	if (!class_dir) {
170		syslog(LOG_INFO, "Failed to open %s", dev_dir);
171		return -ENOSYS;
172	}
173
174	while ((dent = readdir(class_dir))) {
175		int retry = set_retry_cnt;
176		if (dent->d_name[0] == '.')
177			continue;
178
179		while (update_node_desc(dent->d_name, hostname, force) && retry > 0) {
180			syslog(LOG_ERR, "retrying set Node Description on %s\n",
181				dent->d_name);
182			retry--;
183		}
184	}
185
186	closedir(class_dir);
187	return 0;
188}
189
190static int read_hostname(int fd, char *name, size_t len)
191{
192	int rc;
193	memset(name, 0, len);
194	if (read(fd, name, len-1) >= 0) {
195		newline_to_null(name);
196		strip_domain(name);
197		rc = 0;
198	} else {
199		syslog(LOG_ERR, "Read %s Failed\n", SYS_HOSTNAME);
200		rc = -EIO;
201	}
202	return rc;
203}
204
205static int process_opts(void *context, int ch, char *optarg)
206{
207	unsigned long tmp;
208	switch (ch) {
209	case 0:
210		pidfile = optarg;
211		break;
212	case 'f':
213		foreground = 1;
214		break;
215	case 't':
216		tmp = strtoul(optarg, NULL, 0);
217		if (tmp >= INT_MAX) {
218			syslog(LOG_ERR,
219				"Invalid retry rate specified: %lu s\n",
220				tmp);
221		} else {
222			failure_retry_rate = (int)tmp;
223		}
224		break;
225	case 'r':
226		tmp = strtoul(optarg, NULL, 0);
227		if (tmp >= INT_MAX) {
228			syslog(LOG_ERR,
229				"Invalid retry count specified: %lu\n",
230				tmp);
231		} else {
232			set_retry_cnt = (int)tmp;
233		}
234		break;
235	default:
236		return -1;
237	}
238	return 0;
239}
240
241#define MSG_MAX 2048
242static void udev_log_fn(struct udev *ud, int priority, const char *file, int line,
243		const char *fn, const char *format, va_list args)
244{
245	int off = 0;
246	char msg[MSG_MAX];
247	off = snprintf(msg, MSG_MAX, "libudev: %s:%d %s",
248			file, line, fn);
249	if (off < MSG_MAX-1)
250		vsnprintf(msg+off, MSG_MAX-off, format, args);
251	syslog(LOG_ERR, msg);
252}
253
254static void setup_udev(void)
255{
256	udev = udev_new();
257	if (!udev) {
258		syslog(LOG_ERR, "udev_new failed\n");
259		return;
260	}
261
262	udev_set_log_fn(udev, udev_log_fn);
263	udev_set_log_priority(udev, LOG_INFO);
264#if HAVE_UDEV_GET_SYS_PATH
265	sys_dir = (char *)udev_get_sys_path(udev);
266#endif
267}
268
269static int get_udev_fd(void)
270{
271	mon = udev_monitor_new_from_netlink(udev, "udev");
272	if (!mon) {
273		syslog(LOG_ERR, "udev monitoring failed\n");
274		return -1;
275	}
276
277	udev_monitor_filter_add_match_subsystem_devtype(mon, "infiniband", NULL);
278	udev_monitor_enable_receiving(mon);
279	return udev_monitor_get_fd(mon);
280}
281
282static void process_udev_event(int ud_fd, const char *hostname)
283{
284	struct udev_device *dev;
285
286	dev = udev_monitor_receive_device(mon);
287	if (dev) {
288		const char *device = udev_device_get_sysname(dev);
289		const char *action = udev_device_get_action(dev);
290
291		syslog(LOG_INFO, "Device event: %s, %s, %s\n",
292			udev_device_get_subsystem(dev),
293			device, action);
294
295		if (device && action
296		    && strncmp(action, "add", sizeof("add")) == 0)
297			update_node_desc(device, hostname, 1);
298
299		udev_device_unref(dev);
300	}
301}
302
303static void monitor(void)
304{
305	char hostname[128];
306	int hn_fd;
307	int rc;
308	struct pollfd fds[2];
309	int numfds = 1;
310	int ud_fd;
311
312	ud_fd = get_udev_fd();
313	if (ud_fd >= 0)
314		numfds = 2;
315
316	while (1) {
317		hn_fd = open(SYS_HOSTNAME, O_RDONLY);
318		if (hn_fd < 0) {
319			syslog(LOG_ERR,
320				"Open %s Failed: retry in %d seconds\n",
321				SYS_HOSTNAME, failure_retry_rate);
322			sleep(failure_retry_rate);
323			continue;
324		}
325
326		fds[0].fd = hn_fd;
327		fds[0].events = 0;
328		fds[0].revents = 0;
329
330		fds[1].fd = ud_fd;
331		fds[1].events = POLLIN;
332		fds[1].revents = 0;
333
334		rc = poll(fds, numfds, -1);
335
336		if (rc > 0) {
337			if (read_hostname(hn_fd, hostname, sizeof(hostname)) != 0)
338				hostname[0] = '\0';
339
340			if (fds[0].revents != 0)
341				syslog(LOG_ERR, "Hostname change: %s\n", hostname);
342
343			if (fds[1].revents != 0)
344				process_udev_event(ud_fd, hostname);
345
346			rc = set_rdma_node_desc((const char *)hostname, 0);
347		} else {
348			syslog(LOG_ERR, "Poll %s Failed\n", SYS_HOSTNAME);
349			rc = -EIO;
350		}
351
352		close(hn_fd);
353
354		if (rc)
355			sleep(failure_retry_rate);
356	}
357}
358
359static void remove_pidfile(void)
360{
361        if (pidfile)
362		unlink(pidfile);
363}
364
365static void write_pidfile(void)
366{
367	FILE *f;
368	if (pidfile) {
369		remove_pidfile();
370		f = fopen(pidfile, "w");
371		if (f) {
372			fprintf(f, "%d\n", getpid());
373			fclose(f);
374		} else {
375			syslog(LOG_ERR, "Failed to write pidfile : %s\n",
376				pidfile);
377			exit(errno);
378		}
379	}
380}
381
382int main(int argc, char *argv[])
383{
384	int fd;
385	char hostname[128];
386
387	openlog("rdma-ndd", LOG_PID | LOG_PERROR, LOG_DAEMON);
388
389	const struct ibdiag_opt opts[] = {
390		{"retry_timer", 't', 1, "<retry_timer>",
391			"Length of time to sleep when system errors occur "
392			"when attempting to poll and or read the hostname "
393			"from the system.\n"},
394		{"retry_count", 'r', 1, "<retry_count>",
395			"Number of times to attempt to retry setting "
396			"of the node description on failure\n"},
397		{"foreground", 'f', 0, NULL, "run in the foreground instead of as a daemon\n"},
398		{"pidfile", 0, 1, "<pidfile>", "specify a pid file (daemon mode only)\n"},
399		{0}
400	};
401
402	ibdiag_process_opts(argc, argv, NULL, "CPDLGtsKyevd", opts,
403			    process_opts, "", NULL);
404
405	if (!ibd_nd_format)
406		ibd_nd_format = DEFAULT_ND_FORMAT;
407
408	if (!foreground) {
409		closelog();
410		openlog("rdma-ndd", LOG_PID, LOG_DAEMON);
411		if (daemon(0, 0) != 0) {
412			syslog(LOG_ERR, "Failed to daemonize\n");
413			exit(errno);
414		}
415		write_pidfile();
416	}
417
418	setup_udev();
419
420	syslog(LOG_INFO, "Node Descriptor format (%s)\n", ibd_nd_format);
421
422	fd = open(SYS_HOSTNAME, O_RDONLY);
423	if (read_hostname(fd, hostname, sizeof(hostname)) != 0)
424		hostname[0] = '\0';
425	set_rdma_node_desc((const char *)hostname, 1);
426	close(fd);
427
428	monitor();
429
430	remove_pidfile();
431
432	return 0;
433}
434