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