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 =============================*/
72using std::cout;
73using std::endl;
74using std::string;
75using std::stringstream;
76
77namespace DevdCtl
78{
79
80/*=========================== Class Implementations ==========================*/
81/*----------------------------------- Event ----------------------------------*/
82//- Event Static Protected Data ------------------------------------------------
83const string Event::s_theEmptyString;
84
85Event::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 ------------------------------------------------
94Event *
95Event::Builder(Event::Type type, NVPairMap &nvPairs,
96	       const string &eventString)
97{
98	return (new Event(type, nvPairs, eventString));
99}
100
101Event *
102Event::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
126bool
127Event::DevName(std::string &name) const
128{
129	return (false);
130}
131
132/* TODO: simplify this function with C++-11 <regex> methods */
133bool
134Event::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
172const char *
173Event::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 -------------------------------------------------------
186const string &
187Event::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
196bool
197Event::Contains(const string &varName) const
198{
199	return (m_nvPairs.find(varName) != m_nvPairs.end());
200}
201
202string
203Event::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
231void
232Event::Print() const
233{
234	cout << ToString() << std::flush;
235}
236
237void
238Event::Log(int priority) const
239{
240	syslog(priority, "%s", ToString().c_str());
241}
242
243//- Event Virtual Public Methods -----------------------------------------------
244Event::~Event()
245{
246	delete &m_nvPairs;
247}
248
249Event *
250Event::DeepCopy() const
251{
252	return (new Event(*this));
253}
254
255bool
256Event::Process() const
257{
258	return (false);
259}
260
261timeval
262Event::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
277bool
278Event::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
304bool
305Event::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 ----------------------------------------------------
326Event::Event(Type type, NVPairMap &map, const string &eventString)
327 : m_type(type),
328   m_nvPairs(map),
329   m_eventString(eventString)
330{
331}
332
333Event::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
340void
341Event::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
427void
428Event::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 -------------------------------------------
452Event *
453DevfsEvent::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 ----------------------------------------
460bool
461DevfsEvent::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 ------------------------------------------
488Event *
489DevfsEvent::DeepCopy() const
490{
491	return (new DevfsEvent(*this));
492}
493
494bool
495DevfsEvent::Process() const
496{
497	return (true);
498}
499
500//- DevfsEvent Public Methods --------------------------------------------------
501bool
502DevfsEvent::IsWholeDev() const
503{
504	string devName;
505
506	return (DevName(devName) && IsDiskDev() && IsWholeDev(devName));
507}
508
509bool
510DevfsEvent::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 -----------------------------------------------
520DevfsEvent::DevfsEvent(Event::Type type, NVPairMap &nvpairs,
521		       const string &eventString)
522 : Event(type, nvpairs, eventString)
523{
524}
525
526DevfsEvent::DevfsEvent(const DevfsEvent &src)
527 : Event(src)
528{
529}
530
531/*--------------------------------- GeomEvent --------------------------------*/
532//- GeomEvent Static Public Methods --------------------------------------------
533Event *
534GeomEvent::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 -------------------------------------------
541Event *
542GeomEvent::DeepCopy() const
543{
544	return (new GeomEvent(*this));
545}
546
547bool
548GeomEvent::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 ------------------------------------------------
559GeomEvent::GeomEvent(Event::Type type, NVPairMap &nvpairs,
560		     const string &eventString)
561 : Event(type, nvpairs, eventString),
562   m_devname(Value("devname"))
563{
564}
565
566GeomEvent::GeomEvent(const GeomEvent &src)
567 : Event(src),
568   m_devname(src.m_devname)
569{
570}
571
572/*--------------------------------- ZfsEvent ---------------------------------*/
573//- ZfsEvent Static Public Methods ---------------------------------------------
574Event *
575ZfsEvent::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 --------------------------------------------
582Event *
583ZfsEvent::DeepCopy() const
584{
585	return (new ZfsEvent(*this));
586}
587
588bool
589ZfsEvent::DevName(std::string &name) const
590{
591	return (false);
592}
593
594//- ZfsEvent Protected Methods -------------------------------------------------
595ZfsEvent::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
603ZfsEvent::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