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: releng/11.0/lib/libdevdctl/event.cc 300915 2016-05-28 22:27:54Z bdrewery $"); 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 name = Value("devname"); 546 return (!name.empty()); 547} 548 549 550//- GeomEvent Protected Methods ------------------------------------------------ 551GeomEvent::GeomEvent(Event::Type type, NVPairMap &nvpairs, 552 const string &eventString) 553 : Event(type, nvpairs, eventString), 554 m_devname(Value("devname")) 555{ 556} 557 558GeomEvent::GeomEvent(const GeomEvent &src) 559 : Event(src), 560 m_devname(src.m_devname) 561{ 562} 563 564/*--------------------------------- ZfsEvent ---------------------------------*/ 565//- ZfsEvent Static Public Methods --------------------------------------------- 566Event * 567ZfsEvent::Builder(Event::Type type, NVPairMap &nvpairs, 568 const string &eventString) 569{ 570 return (new ZfsEvent(type, nvpairs, eventString)); 571} 572 573//- ZfsEvent Virtual Public Methods -------------------------------------------- 574Event * 575ZfsEvent::DeepCopy() const 576{ 577 return (new ZfsEvent(*this)); 578} 579 580bool 581ZfsEvent::DevName(std::string &name) const 582{ 583 return (false); 584} 585 586//- ZfsEvent Protected Methods ------------------------------------------------- 587ZfsEvent::ZfsEvent(Event::Type type, NVPairMap &nvpairs, 588 const string &eventString) 589 : Event(type, nvpairs, eventString), 590 m_poolGUID(Guid(Value("pool_guid"))), 591 m_vdevGUID(Guid(Value("vdev_guid"))) 592{ 593} 594 595ZfsEvent::ZfsEvent(const ZfsEvent &src) 596 : Event(src), 597 m_poolGUID(src.m_poolGUID), 598 m_vdevGUID(src.m_vdevGUID) 599{ 600} 601 602} // namespace DevdCtl 603