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