xref: /freebsd/lib/libdevdctl/event.cc (revision 315ee00f)
1 /*-
2  * Copyright (c) 2011, 2012, 2013, 2016 Spectra Logic Corporation
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions, and the following disclaimer,
10  *    without modification.
11  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12  *    substantially similar to the "NO WARRANTY" disclaimer below
13  *    ("Disclaimer") and any redistribution must be conditioned upon
14  *    including a substantially similar Disclaimer requirement for further
15  *    binary redistribution.
16  *
17  * NO WARRANTY
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
27  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGES.
29  *
30  * Authors: Justin T. Gibbs     (Spectra Logic Corporation)
31  */
32 
33 /**
34  * \file event.cc
35  *
36  * Implementation of the class hierarchy used to express events
37  * received via the devdctl API.
38  */
39 #include <sys/cdefs.h>
40 #include <sys/disk.h>
41 #include <sys/filio.h>
42 #include <sys/param.h>
43 #include <sys/stat.h>
44 
45 #include <err.h>
46 #include <fcntl.h>
47 #include <inttypes.h>
48 #include <paths.h>
49 #include <stdlib.h>
50 #include <syslog.h>
51 #include <unistd.h>
52 
53 #include <cstdarg>
54 #include <cstring>
55 #include <iostream>
56 #include <list>
57 #include <map>
58 #include <sstream>
59 #include <string>
60 
61 #include "guid.h"
62 #include "event.h"
63 #include "event_factory.h"
64 #include "exception.h"
65 /*================================== Macros ==================================*/
66 #define NUM_ELEMENTS(x) (sizeof(x) / sizeof(*x))
67 
68 /*============================ Namespace Control =============================*/
69 using std::cout;
70 using std::endl;
71 using std::string;
72 using std::stringstream;
73 
74 namespace DevdCtl
75 {
76 
77 /*=========================== Class Implementations ==========================*/
78 /*----------------------------------- Event ----------------------------------*/
79 //- Event Static Protected Data ------------------------------------------------
80 const string Event::s_theEmptyString;
81 
82 Event::EventTypeRecord Event::s_typeTable[] =
83 {
84 	{ Event::NOTIFY,  "Notify" },
85 	{ Event::NOMATCH, "No Driver Match" },
86 	{ Event::ATTACH,  "Attach" },
87 	{ Event::DETACH,  "Detach" }
88 };
89 
90 //- Event Static Public Methods ------------------------------------------------
91 Event *
92 Event::Builder(Event::Type type, NVPairMap &nvPairs,
93 	       const string &eventString)
94 {
95 	return (new Event(type, nvPairs, eventString));
96 }
97 
98 Event *
99 Event::CreateEvent(const EventFactory &factory, const string &eventString)
100 {
101 	NVPairMap &nvpairs(*new NVPairMap);
102 	Type       type(static_cast<Event::Type>(eventString[0]));
103 
104 	try {
105 		ParseEventString(type, eventString, nvpairs);
106 	} catch (const ParseException &exp) {
107 		if (exp.GetType() == ParseException::INVALID_FORMAT)
108 			exp.Log();
109 		return (NULL);
110 	}
111 
112 	/*
113 	 * Allow entries in our table for events with no system specified.
114 	 * These entries should specify the string "none".
115 	 */
116 	NVPairMap::iterator system_item(nvpairs.find("system"));
117 	if (system_item == nvpairs.end())
118 		nvpairs["system"] = "none";
119 
120 	return (factory.Build(type, nvpairs, eventString));
121 }
122 
123 bool
124 Event::DevName(std::string &name) const
125 {
126 	return (false);
127 }
128 
129 /* TODO: simplify this function with C++-11 <regex> methods */
130 bool
131 Event::IsDiskDev() const
132 {
133 	const int numDrivers = 2;
134 	static const char *diskDevNames[numDrivers] =
135 	{
136 		"da",
137 		"ada"
138 	};
139 	const char **dName;
140 	string devName;
141 
142 	if (! DevName(devName))
143 		return false;
144 
145 	size_t find_start = devName.rfind('/');
146 	if (find_start == string::npos) {
147 		find_start = 0;
148 	} else {
149 		/* Just after the last '/'. */
150 		find_start++;
151 	}
152 
153 	for (dName = &diskDevNames[0];
154 	     dName <= &diskDevNames[numDrivers - 1]; dName++) {
155 
156 		size_t loc(devName.find(*dName, find_start));
157 		if (loc == find_start) {
158 			size_t prefixLen(strlen(*dName));
159 
160 			if (devName.length() - find_start >= prefixLen
161 			 && isdigit(devName[find_start + prefixLen]))
162 				return (true);
163 		}
164 	}
165 
166 	return (false);
167 }
168 
169 const char *
170 Event::TypeToString(Event::Type type)
171 {
172 	EventTypeRecord *rec(s_typeTable);
173 	EventTypeRecord *lastRec(s_typeTable + NUM_ELEMENTS(s_typeTable) - 1);
174 
175 	for (; rec <= lastRec; rec++) {
176 		if (rec->m_type == type)
177 			return (rec->m_typeName);
178 	}
179 	return ("Unknown");
180 }
181 
182 //- Event Public Methods -------------------------------------------------------
183 const string &
184 Event::Value(const string &varName) const
185 {
186 	NVPairMap::const_iterator item(m_nvPairs.find(varName));
187 	if (item == m_nvPairs.end())
188 		return (s_theEmptyString);
189 
190 	return (item->second);
191 }
192 
193 bool
194 Event::Contains(const string &varName) const
195 {
196 	return (m_nvPairs.find(varName) != m_nvPairs.end());
197 }
198 
199 string
200 Event::ToString() const
201 {
202 	stringstream result;
203 
204 	NVPairMap::const_iterator devName(m_nvPairs.find("device-name"));
205 	if (devName != m_nvPairs.end())
206 		result << devName->second << ": ";
207 
208 	NVPairMap::const_iterator systemName(m_nvPairs.find("system"));
209 	if (systemName != m_nvPairs.end()
210 	 && systemName->second != "none")
211 		result << systemName->second << ": ";
212 
213 	result << TypeToString(GetType()) << ' ';
214 
215 	for (NVPairMap::const_iterator curVar = m_nvPairs.begin();
216 	     curVar != m_nvPairs.end(); curVar++) {
217 		if (curVar == devName || curVar == systemName)
218 			continue;
219 
220 		result << ' '
221 		     << curVar->first << "=" << curVar->second;
222 	}
223 	result << endl;
224 
225 	return (result.str());
226 }
227 
228 void
229 Event::Print() const
230 {
231 	cout << ToString() << std::flush;
232 }
233 
234 void
235 Event::Log(int priority) const
236 {
237 	syslog(priority, "%s", ToString().c_str());
238 }
239 
240 //- Event Virtual Public Methods -----------------------------------------------
241 Event::~Event()
242 {
243 	delete &m_nvPairs;
244 }
245 
246 Event *
247 Event::DeepCopy() const
248 {
249 	return (new Event(*this));
250 }
251 
252 bool
253 Event::Process() const
254 {
255 	return (false);
256 }
257 
258 timeval
259 Event::GetTimestamp() const
260 {
261 	timeval tv_timestamp;
262 	struct tm tm_timestamp;
263 
264 	if (!Contains("timestamp")) {
265 		throw Exception("Event contains no timestamp: %s",
266 				m_eventString.c_str());
267 	}
268 	strptime(Value(string("timestamp")).c_str(), "%s", &tm_timestamp);
269 	tv_timestamp.tv_sec = mktime(&tm_timestamp);
270 	tv_timestamp.tv_usec = 0;
271 	return (tv_timestamp);
272 }
273 
274 bool
275 Event::DevPath(std::string &path) const
276 {
277 	char buf[SPECNAMELEN + 1];
278 	string devName;
279 
280 	if (!DevName(devName))
281 		return (false);
282 
283 	string devPath(_PATH_DEV + devName);
284 	int devFd(open(devPath.c_str(), O_RDONLY));
285 	if (devFd == -1)
286 		return (false);
287 
288 	/* Normalize the device name in case the DEVFS event is for a link. */
289 	if (fdevname_r(devFd, buf, sizeof(buf)) == NULL) {
290 		close(devFd);
291 		return (false);
292 	}
293 	devName = buf;
294 	path = _PATH_DEV + devName;
295 
296 	close(devFd);
297 
298 	return (true);
299 }
300 
301 bool
302 Event::PhysicalPath(std::string &path) const
303 {
304 	string devPath;
305 
306 	if (!DevPath(devPath))
307 		return (false);
308 
309 	int devFd(open(devPath.c_str(), O_RDONLY));
310 	if (devFd == -1)
311 		return (false);
312 
313 	char physPath[MAXPATHLEN];
314 	physPath[0] = '\0';
315 	bool result(ioctl(devFd, DIOCGPHYSPATH, physPath) == 0);
316 	close(devFd);
317 	if (result)
318 		path = physPath;
319 	return (result);
320 }
321 
322 //- Event Protected Methods ----------------------------------------------------
323 Event::Event(Type type, NVPairMap &map, const string &eventString)
324  : m_type(type),
325    m_nvPairs(map),
326    m_eventString(eventString)
327 {
328 }
329 
330 Event::Event(const Event &src)
331  : m_type(src.m_type),
332    m_nvPairs(*new NVPairMap(src.m_nvPairs)),
333    m_eventString(src.m_eventString)
334 {
335 }
336 
337 void
338 Event::ParseEventString(Event::Type type,
339 			      const string &eventString,
340 			      NVPairMap& nvpairs)
341 {
342 	size_t start;
343 	size_t end;
344 
345 	switch (type) {
346 	case ATTACH:
347 	case DETACH:
348 
349 		/*
350 		 * <type><device-name><unit> <pnpvars> \
351 		 *                        at <location vars> <pnpvars> \
352 		 *                        on <parent>
353 		 *
354 		 * Handle all data that doesn't conform to the
355 		 * "name=value" format, and let the generic parser
356 		 * below handle the rest.
357 		 *
358 		 * Type is a single char.  Skip it.
359 		 */
360 		start = 1;
361 		end = eventString.find_first_of(" \t\n", start);
362 		if (end == string::npos)
363 			throw ParseException(ParseException::INVALID_FORMAT,
364 					     eventString, start);
365 
366 		nvpairs["device-name"] = eventString.substr(start, end - start);
367 
368 		start = eventString.find(" on ", end);
369 		if (end == string::npos)
370 			throw ParseException(ParseException::INVALID_FORMAT,
371 					     eventString, start);
372 		start += 4;
373 		end = eventString.find_first_of(" \t\n", start);
374 		nvpairs["parent"] = eventString.substr(start, end);
375 		break;
376 	case NOTIFY:
377 		break;
378 	case NOMATCH:
379 		throw ParseException(ParseException::DISCARDED_EVENT_TYPE,
380 				     eventString);
381 	default:
382 		throw ParseException(ParseException::UNKNOWN_EVENT_TYPE,
383 				     eventString);
384 	}
385 
386 	/* Process common "key=value" format. */
387 	for (start = 1; start < eventString.length(); start = end + 1) {
388 
389 		/* Find the '=' in the middle of the key/value pair. */
390 		end = eventString.find('=', start);
391 		if (end == string::npos)
392 			break;
393 
394 		/*
395 		 * Find the start of the key by backing up until
396 		 * we hit whitespace or '!' (event type "notice").
397 		 * Due to the devdctl format, all key/value pair must
398 		 * start with one of these two characters.
399 		 */
400 		start = eventString.find_last_of("! \t\n", end);
401 		if (start == string::npos)
402 			throw ParseException(ParseException::INVALID_FORMAT,
403 					     eventString, end);
404 		start++;
405 		string key(eventString.substr(start, end - start));
406 
407 		/*
408 		 * Walk forward from the '=' until either we exhaust
409 		 * the buffer or we hit whitespace.
410 		 */
411 		start = end + 1;
412 		if (start >= eventString.length())
413 			throw ParseException(ParseException::INVALID_FORMAT,
414 					     eventString, end);
415 		end = eventString.find_first_of(" \t\n", start);
416 		if (end == string::npos)
417 			end = eventString.length() - 1;
418 		string value(eventString.substr(start, end - start));
419 
420 		nvpairs[key] = value;
421 	}
422 }
423 
424 void
425 Event::TimestampEventString(std::string &eventString)
426 {
427 	if (eventString.size() > 0) {
428 		/*
429 		 * Add a timestamp as the final field of the event if it is
430 		 * not already present.
431 		 */
432 		if (eventString.find(" timestamp=") == string::npos) {
433 			const size_t bufsize = 32;	// Long enough for a 64-bit int
434 			timeval now;
435 			char timebuf[bufsize];
436 
437 			size_t eventEnd(eventString.find_last_not_of('\n') + 1);
438 			if (gettimeofday(&now, NULL) != 0)
439 				err(1, "gettimeofday");
440 			snprintf(timebuf, bufsize, " timestamp=%" PRId64,
441 				(int64_t) now.tv_sec);
442 			eventString.insert(eventEnd, timebuf);
443 		}
444 	}
445 }
446 
447 /*-------------------------------- DevfsEvent --------------------------------*/
448 //- DevfsEvent Static Public Methods -------------------------------------------
449 Event *
450 DevfsEvent::Builder(Event::Type type, NVPairMap &nvPairs,
451 		    const string &eventString)
452 {
453 	return (new DevfsEvent(type, nvPairs, eventString));
454 }
455 
456 //- DevfsEvent Static Protected Methods ----------------------------------------
457 bool
458 DevfsEvent::IsWholeDev(const string &devName)
459 {
460 	string::const_iterator i(devName.begin());
461 
462 	size_t start = devName.rfind('/');
463 	if (start == string::npos) {
464 		start = 0;
465 	} else {
466 		/* Just after the last '/'. */
467 		start++;
468 	}
469 	i += start;
470 
471 	/* alpha prefix followed only by digits. */
472 	for (; i < devName.end() && !isdigit(*i); i++)
473 		;
474 
475 	if (i == devName.end())
476 		return (false);
477 
478 	for (; i < devName.end() && isdigit(*i); i++)
479 		;
480 
481 	return (i == devName.end());
482 }
483 
484 //- DevfsEvent Virtual Public Methods ------------------------------------------
485 Event *
486 DevfsEvent::DeepCopy() const
487 {
488 	return (new DevfsEvent(*this));
489 }
490 
491 bool
492 DevfsEvent::Process() const
493 {
494 	return (true);
495 }
496 
497 //- DevfsEvent Public Methods --------------------------------------------------
498 bool
499 DevfsEvent::IsWholeDev() const
500 {
501 	string devName;
502 
503 	return (DevName(devName) && IsDiskDev() && IsWholeDev(devName));
504 }
505 
506 bool
507 DevfsEvent::DevName(std::string &name) const
508 {
509 	if (Value("subsystem") != "CDEV")
510 		return (false);
511 
512 	name = Value("cdev");
513 	return (!name.empty());
514 }
515 
516 //- DevfsEvent Protected Methods -----------------------------------------------
517 DevfsEvent::DevfsEvent(Event::Type type, NVPairMap &nvpairs,
518 		       const string &eventString)
519  : Event(type, nvpairs, eventString)
520 {
521 }
522 
523 DevfsEvent::DevfsEvent(const DevfsEvent &src)
524  : Event(src)
525 {
526 }
527 
528 /*--------------------------------- GeomEvent --------------------------------*/
529 //- GeomEvent Static Public Methods --------------------------------------------
530 Event *
531 GeomEvent::Builder(Event::Type type, NVPairMap &nvpairs,
532 		   const string &eventString)
533 {
534 	return (new GeomEvent(type, nvpairs, eventString));
535 }
536 
537 //- GeomEvent Virtual Public Methods -------------------------------------------
538 Event *
539 GeomEvent::DeepCopy() const
540 {
541 	return (new GeomEvent(*this));
542 }
543 
544 bool
545 GeomEvent::DevName(std::string &name) const
546 {
547 	if (Value("subsystem") == "disk")
548 		name = Value("devname");
549 	else
550 		name = Value("cdev");
551 	return (!name.empty());
552 }
553 
554 
555 //- GeomEvent Protected Methods ------------------------------------------------
556 GeomEvent::GeomEvent(Event::Type type, NVPairMap &nvpairs,
557 		     const string &eventString)
558  : Event(type, nvpairs, eventString),
559    m_devname(Value("devname"))
560 {
561 }
562 
563 GeomEvent::GeomEvent(const GeomEvent &src)
564  : Event(src),
565    m_devname(src.m_devname)
566 {
567 }
568 
569 /*--------------------------------- ZfsEvent ---------------------------------*/
570 //- ZfsEvent Static Public Methods ---------------------------------------------
571 Event *
572 ZfsEvent::Builder(Event::Type type, NVPairMap &nvpairs,
573 		  const string &eventString)
574 {
575 	return (new ZfsEvent(type, nvpairs, eventString));
576 }
577 
578 //- ZfsEvent Virtual Public Methods --------------------------------------------
579 Event *
580 ZfsEvent::DeepCopy() const
581 {
582 	return (new ZfsEvent(*this));
583 }
584 
585 bool
586 ZfsEvent::DevName(std::string &name) const
587 {
588 	return (false);
589 }
590 
591 //- ZfsEvent Protected Methods -------------------------------------------------
592 ZfsEvent::ZfsEvent(Event::Type type, NVPairMap &nvpairs,
593 		   const string &eventString)
594  : Event(type, nvpairs, eventString),
595    m_poolGUID(Guid(Value("pool_guid"))),
596    m_vdevGUID(Guid(Value("vdev_guid")))
597 {
598 }
599 
600 ZfsEvent::ZfsEvent(const ZfsEvent &src)
601  : Event(src),
602    m_poolGUID(src.m_poolGUID),
603    m_vdevGUID(src.m_vdevGUID)
604 {
605 }
606 
607 } // namespace DevdCtl
608