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