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	string devName;
281
282	if (!DevName(devName))
283		return (false);
284
285	string devPath(_PATH_DEV + devName);
286	int devFd(open(devPath.c_str(), O_RDONLY));
287	if (devFd == -1)
288		return (false);
289
290	/* Normalize the device name in case the DEVFS event is for a link. */
291	devName = fdevname(devFd);
292	path = _PATH_DEV + devName;
293
294	close(devFd);
295
296	return (true);
297}
298
299bool
300Event::PhysicalPath(std::string &path) const
301{
302	string devPath;
303
304	if (!DevPath(devPath))
305		return (false);
306
307	int devFd(open(devPath.c_str(), O_RDONLY));
308	if (devFd == -1)
309		return (false);
310
311	char physPath[MAXPATHLEN];
312	physPath[0] = '\0';
313	bool result(ioctl(devFd, DIOCGPHYSPATH, physPath) == 0);
314	close(devFd);
315	if (result)
316		path = physPath;
317	return (result);
318}
319
320//- Event Protected Methods ----------------------------------------------------
321Event::Event(Type type, NVPairMap &map, const string &eventString)
322 : m_type(type),
323   m_nvPairs(map),
324   m_eventString(eventString)
325{
326}
327
328Event::Event(const Event &src)
329 : m_type(src.m_type),
330   m_nvPairs(*new NVPairMap(src.m_nvPairs)),
331   m_eventString(src.m_eventString)
332{
333}
334
335void
336Event::ParseEventString(Event::Type type,
337			      const string &eventString,
338			      NVPairMap& nvpairs)
339{
340	size_t start;
341	size_t end;
342
343	switch (type) {
344	case ATTACH:
345	case DETACH:
346
347		/*
348		 * <type><device-name><unit> <pnpvars> \
349		 *                        at <location vars> <pnpvars> \
350		 *                        on <parent>
351		 *
352		 * Handle all data that doesn't conform to the
353		 * "name=value" format, and let the generic parser
354		 * below handle the rest.
355		 *
356		 * Type is a single char.  Skip it.
357		 */
358		start = 1;
359		end = eventString.find_first_of(" \t\n", start);
360		if (end == string::npos)
361			throw ParseException(ParseException::INVALID_FORMAT,
362					     eventString, start);
363
364		nvpairs["device-name"] = eventString.substr(start, end - start);
365
366		start = eventString.find(" on ", end);
367		if (end == string::npos)
368			throw ParseException(ParseException::INVALID_FORMAT,
369					     eventString, start);
370		start += 4;
371		end = eventString.find_first_of(" \t\n", start);
372		nvpairs["parent"] = eventString.substr(start, end);
373		break;
374	case NOTIFY:
375		break;
376	case NOMATCH:
377		throw ParseException(ParseException::DISCARDED_EVENT_TYPE,
378				     eventString);
379	default:
380		throw ParseException(ParseException::UNKNOWN_EVENT_TYPE,
381				     eventString);
382	}
383
384	/* Process common "key=value" format. */
385	for (start = 1; start < eventString.length(); start = end + 1) {
386
387		/* Find the '=' in the middle of the key/value pair. */
388		end = eventString.find('=', start);
389		if (end == string::npos)
390			break;
391
392		/*
393		 * Find the start of the key by backing up until
394		 * we hit whitespace or '!' (event type "notice").
395		 * Due to the devdctl format, all key/value pair must
396		 * start with one of these two characters.
397		 */
398		start = eventString.find_last_of("! \t\n", end);
399		if (start == string::npos)
400			throw ParseException(ParseException::INVALID_FORMAT,
401					     eventString, end);
402		start++;
403		string key(eventString.substr(start, end - start));
404
405		/*
406		 * Walk forward from the '=' until either we exhaust
407		 * the buffer or we hit whitespace.
408		 */
409		start = end + 1;
410		if (start >= eventString.length())
411			throw ParseException(ParseException::INVALID_FORMAT,
412					     eventString, end);
413		end = eventString.find_first_of(" \t\n", start);
414		if (end == string::npos)
415			end = eventString.length() - 1;
416		string value(eventString.substr(start, end - start));
417
418		nvpairs[key] = value;
419	}
420}
421
422void
423Event::TimestampEventString(std::string &eventString)
424{
425	if (eventString.size() > 0) {
426		/*
427		 * Add a timestamp as the final field of the event if it is
428		 * not already present.
429		 */
430		if (eventString.find("timestamp=") == string::npos) {
431			const size_t bufsize = 32;	// Long enough for a 64-bit int
432			timeval now;
433			char timebuf[bufsize];
434
435			size_t eventEnd(eventString.find_last_not_of('\n') + 1);
436			if (gettimeofday(&now, NULL) != 0)
437				err(1, "gettimeofday");
438			snprintf(timebuf, bufsize, " timestamp=%" PRId64,
439				(int64_t) now.tv_sec);
440			eventString.insert(eventEnd, timebuf);
441		}
442	}
443}
444
445/*-------------------------------- DevfsEvent --------------------------------*/
446//- DevfsEvent Static Public Methods -------------------------------------------
447Event *
448DevfsEvent::Builder(Event::Type type, NVPairMap &nvPairs,
449		    const string &eventString)
450{
451	return (new DevfsEvent(type, nvPairs, eventString));
452}
453
454//- DevfsEvent Static Protected Methods ----------------------------------------
455bool
456DevfsEvent::IsWholeDev(const string &devName)
457{
458	string::const_iterator i(devName.begin());
459
460	size_t start = devName.rfind('/');
461	if (start == string::npos) {
462		start = 0;
463	} else {
464		/* Just after the last '/'. */
465		start++;
466	}
467	i += start;
468
469	/* alpha prefix followed only by digits. */
470	for (; i < devName.end() && !isdigit(*i); i++)
471		;
472
473	if (i == devName.end())
474		return (false);
475
476	for (; i < devName.end() && isdigit(*i); i++)
477		;
478
479	return (i == devName.end());
480}
481
482//- DevfsEvent Virtual Public Methods ------------------------------------------
483Event *
484DevfsEvent::DeepCopy() const
485{
486	return (new DevfsEvent(*this));
487}
488
489bool
490DevfsEvent::Process() const
491{
492	return (true);
493}
494
495//- DevfsEvent Public Methods --------------------------------------------------
496bool
497DevfsEvent::IsWholeDev() const
498{
499	string devName;
500
501	return (DevName(devName) && IsDiskDev() && IsWholeDev(devName));
502}
503
504bool
505DevfsEvent::DevName(std::string &name) const
506{
507	if (Value("subsystem") != "CDEV")
508		return (false);
509
510	name = Value("cdev");
511	return (!name.empty());
512}
513
514//- DevfsEvent Protected Methods -----------------------------------------------
515DevfsEvent::DevfsEvent(Event::Type type, NVPairMap &nvpairs,
516		       const string &eventString)
517 : Event(type, nvpairs, eventString)
518{
519}
520
521DevfsEvent::DevfsEvent(const DevfsEvent &src)
522 : Event(src)
523{
524}
525
526/*--------------------------------- GeomEvent --------------------------------*/
527//- GeomEvent Static Public Methods --------------------------------------------
528Event *
529GeomEvent::Builder(Event::Type type, NVPairMap &nvpairs,
530		   const string &eventString)
531{
532	return (new GeomEvent(type, nvpairs, eventString));
533}
534
535//- GeomEvent Virtual Public Methods -------------------------------------------
536Event *
537GeomEvent::DeepCopy() const
538{
539	return (new GeomEvent(*this));
540}
541
542bool
543GeomEvent::DevName(std::string &name) const
544{
545	if (Value("subsystem") == "disk")
546		name = Value("devname");
547	else
548		name = Value("cdev");
549	return (!name.empty());
550}
551
552
553//- GeomEvent Protected Methods ------------------------------------------------
554GeomEvent::GeomEvent(Event::Type type, NVPairMap &nvpairs,
555		     const string &eventString)
556 : Event(type, nvpairs, eventString),
557   m_devname(Value("devname"))
558{
559}
560
561GeomEvent::GeomEvent(const GeomEvent &src)
562 : Event(src),
563   m_devname(src.m_devname)
564{
565}
566
567/*--------------------------------- ZfsEvent ---------------------------------*/
568//- ZfsEvent Static Public Methods ---------------------------------------------
569Event *
570ZfsEvent::Builder(Event::Type type, NVPairMap &nvpairs,
571		  const string &eventString)
572{
573	return (new ZfsEvent(type, nvpairs, eventString));
574}
575
576//- ZfsEvent Virtual Public Methods --------------------------------------------
577Event *
578ZfsEvent::DeepCopy() const
579{
580	return (new ZfsEvent(*this));
581}
582
583bool
584ZfsEvent::DevName(std::string &name) const
585{
586	return (false);
587}
588
589//- ZfsEvent Protected Methods -------------------------------------------------
590ZfsEvent::ZfsEvent(Event::Type type, NVPairMap &nvpairs,
591		   const string &eventString)
592 : Event(type, nvpairs, eventString),
593   m_poolGUID(Guid(Value("pool_guid"))),
594   m_vdevGUID(Guid(Value("vdev_guid")))
595{
596}
597
598ZfsEvent::ZfsEvent(const ZfsEvent &src)
599 : Event(src),
600   m_poolGUID(src.m_poolGUID),
601   m_vdevGUID(src.m_vdevGUID)
602{
603}
604
605} // namespace DevdCtl
606