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 =============================*/
69using std::cout;
70using std::endl;
71using std::string;
72using std::stringstream;
73
74namespace DevdCtl
75{
76
77/*=========================== Class Implementations ==========================*/
78/*----------------------------------- Event ----------------------------------*/
79//- Event Static Protected Data ------------------------------------------------
80const string Event::s_theEmptyString;
81
82Event::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 ------------------------------------------------
91Event *
92Event::Builder(Event::Type type, NVPairMap &nvPairs,
93	       const string &eventString)
94{
95	return (new Event(type, nvPairs, eventString));
96}
97
98Event *
99Event::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
123bool
124Event::DevName(std::string &name) const
125{
126	return (false);
127}
128
129/* TODO: simplify this function with C++-11 <regex> methods */
130bool
131Event::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
169const char *
170Event::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 -------------------------------------------------------
183const string &
184Event::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
193bool
194Event::Contains(const string &varName) const
195{
196	return (m_nvPairs.find(varName) != m_nvPairs.end());
197}
198
199string
200Event::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
228void
229Event::Print() const
230{
231	cout << ToString() << std::flush;
232}
233
234void
235Event::Log(int priority) const
236{
237	syslog(priority, "%s", ToString().c_str());
238}
239
240//- Event Virtual Public Methods -----------------------------------------------
241Event::~Event()
242{
243	delete &m_nvPairs;
244}
245
246Event *
247Event::DeepCopy() const
248{
249	return (new Event(*this));
250}
251
252bool
253Event::Process() const
254{
255	return (false);
256}
257
258timeval
259Event::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
274bool
275Event::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
301bool
302Event::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 ----------------------------------------------------
323Event::Event(Type type, NVPairMap &map, const string &eventString)
324 : m_type(type),
325   m_nvPairs(map),
326   m_eventString(eventString)
327{
328}
329
330Event::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
337void
338Event::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
424void
425Event::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 -------------------------------------------
449Event *
450DevfsEvent::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 ----------------------------------------
457bool
458DevfsEvent::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 ------------------------------------------
485Event *
486DevfsEvent::DeepCopy() const
487{
488	return (new DevfsEvent(*this));
489}
490
491bool
492DevfsEvent::Process() const
493{
494	return (true);
495}
496
497//- DevfsEvent Public Methods --------------------------------------------------
498bool
499DevfsEvent::IsWholeDev() const
500{
501	string devName;
502
503	return (DevName(devName) && IsDiskDev() && IsWholeDev(devName));
504}
505
506bool
507DevfsEvent::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 -----------------------------------------------
517DevfsEvent::DevfsEvent(Event::Type type, NVPairMap &nvpairs,
518		       const string &eventString)
519 : Event(type, nvpairs, eventString)
520{
521}
522
523DevfsEvent::DevfsEvent(const DevfsEvent &src)
524 : Event(src)
525{
526}
527
528/*--------------------------------- GeomEvent --------------------------------*/
529//- GeomEvent Static Public Methods --------------------------------------------
530Event *
531GeomEvent::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 -------------------------------------------
538Event *
539GeomEvent::DeepCopy() const
540{
541	return (new GeomEvent(*this));
542}
543
544bool
545GeomEvent::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 ------------------------------------------------
556GeomEvent::GeomEvent(Event::Type type, NVPairMap &nvpairs,
557		     const string &eventString)
558 : Event(type, nvpairs, eventString),
559   m_devname(Value("devname"))
560{
561}
562
563GeomEvent::GeomEvent(const GeomEvent &src)
564 : Event(src),
565   m_devname(src.m_devname)
566{
567}
568
569/*--------------------------------- ZfsEvent ---------------------------------*/
570//- ZfsEvent Static Public Methods ---------------------------------------------
571Event *
572ZfsEvent::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 --------------------------------------------
579Event *
580ZfsEvent::DeepCopy() const
581{
582	return (new ZfsEvent(*this));
583}
584
585bool
586ZfsEvent::DevName(std::string &name) const
587{
588	return (false);
589}
590
591//- ZfsEvent Protected Methods -------------------------------------------------
592ZfsEvent::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
600ZfsEvent::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