1 /*- 2 * Copyright (c) 2002 M. Warner Losh. 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 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27 /* 28 * DEVD control daemon. 29 */ 30 31 // TODO list: 32 // o devd.conf and devd man pages need a lot of help: 33 // - devd.conf needs to lose the warning about zone files. 34 // - devd.conf needs more details on the supported statements. 35 // - devd.conf needs an example or two. 36 37 #include <sys/cdefs.h> 38 __FBSDID("$FreeBSD$"); 39 40 #include <sys/param.h> 41 #include <sys/types.h> 42 43 #include <ctype.h> 44 #include <dirent.h> 45 #include <errno.h> 46 #include <err.h> 47 #include <fcntl.h> 48 #include <regex.h> 49 #include <stdlib.h> 50 #include <stdio.h> 51 #include <string.h> 52 #include <unistd.h> 53 54 #include <algorithm> 55 #include <map> 56 #include <string> 57 #include <vector> 58 59 #include "devd.h" 60 61 #define CF "/etc/devd.conf" 62 63 using namespace std; 64 65 extern FILE *yyin; 66 extern int lineno; 67 68 static const char nomatch = '?'; 69 static const char attach = '+'; 70 static const char detach = '-'; 71 72 int dflag; 73 int romeo_must_die = 0; 74 75 static void event_loop(void); 76 static void usage(void); 77 78 template <class T> void 79 delete_and_clear(vector<T *> &v) 80 { 81 typename vector<T *>::const_iterator i; 82 83 for (i = v.begin(); i != v.end(); i++) 84 delete *i; 85 v.clear(); 86 } 87 88 class config; 89 90 class var_list 91 { 92 public: 93 var_list() {} 94 virtual ~var_list() {} 95 void set_variable(const string &var, const string &val); 96 const string &get_variable(const string &var) const; 97 bool is_set(const string &var) const; 98 static const string bogus; 99 static const string nothing; 100 private: 101 map<string, string> _vars; 102 }; 103 104 class eps 105 { 106 public: 107 eps() {} 108 virtual ~eps() {} 109 virtual bool do_match(config &) = 0; 110 virtual bool do_action(config &) = 0; 111 }; 112 113 class match : public eps 114 { 115 public: 116 match(config &, const char *var, const char *re); 117 virtual ~match(); 118 virtual bool do_match(config &); 119 virtual bool do_action(config &) { return true; } 120 private: 121 string _var; 122 string _re; 123 regex_t _regex; 124 }; 125 126 class action : public eps 127 { 128 public: 129 action(const char *cmd); 130 virtual ~action(); 131 virtual bool do_match(config &) { return true; } 132 virtual bool do_action(config &); 133 private: 134 string _cmd; 135 }; 136 137 class event_proc 138 { 139 public: 140 event_proc(); 141 virtual ~event_proc(); 142 int get_priority() const { return (_prio); } 143 void set_priority(int prio) { _prio = prio; } 144 void add(eps *); 145 bool matches(config &); 146 bool run(config &); 147 private: 148 int _prio; 149 vector<eps *> _epsvec; 150 }; 151 152 class config 153 { 154 public: 155 config() : _pidfile("") { push_var_table(); } 156 virtual ~config() { reset(); } 157 void add_attach(int, event_proc *); 158 void add_detach(int, event_proc *); 159 void add_directory(const char *); 160 void add_nomatch(int, event_proc *); 161 void set_pidfile(const char *); 162 void reset(); 163 void parse(); 164 void drop_pidfile(); 165 void push_var_table(); 166 void pop_var_table(); 167 void set_variable(const char *var, const char *val); 168 const string &get_variable(const string &var); 169 const string expand_string(const string &var); 170 char *set_vars(char *); 171 void find_and_execute(char); 172 protected: 173 void sort_vector(vector<event_proc *> &); 174 void parse_one_file(const char *fn); 175 void parse_files_in_dir(const char *dirname); 176 void expand_one(const char *&src, char *&dst, char *eod); 177 bool is_id_char(char); 178 bool chop_var(char *&buffer, char *&lhs, char *&rhs); 179 private: 180 vector<string> _dir_list; 181 string _pidfile; 182 vector<var_list *> _var_list_table; 183 vector<event_proc *> _attach_list; 184 vector<event_proc *> _detach_list; 185 vector<event_proc *> _nomatch_list; 186 }; 187 188 config cfg; 189 190 event_proc::event_proc() : _prio(-1) 191 { 192 // nothing 193 } 194 195 event_proc::~event_proc() 196 { 197 vector<eps *>::const_iterator i; 198 199 for (i = _epsvec.begin(); i != _epsvec.end(); i++) 200 delete *i; 201 _epsvec.clear(); 202 } 203 204 void 205 event_proc::add(eps *eps) 206 { 207 _epsvec.push_back(eps); 208 } 209 210 bool 211 event_proc::matches(config &c) 212 { 213 vector<eps *>::const_iterator i; 214 215 for (i = _epsvec.begin(); i != _epsvec.end(); i++) 216 if (!(*i)->do_match(c)) 217 return (false); 218 return (true); 219 } 220 221 bool 222 event_proc::run(config &c) 223 { 224 vector<eps *>::const_iterator i; 225 226 for (i = _epsvec.begin(); i != _epsvec.end(); i++) 227 if (!(*i)->do_action(c)) 228 return (false); 229 return (true); 230 } 231 232 action::action(const char *cmd) 233 : _cmd(cmd) 234 { 235 // nothing 236 } 237 238 action::~action() 239 { 240 // nothing 241 } 242 243 bool 244 action::do_action(config &c) 245 { 246 string s = c.expand_string(_cmd); 247 if (dflag) 248 fprintf(stderr, "Executing '%s'\n", s.c_str()); 249 ::system(s.c_str()); 250 return (true); 251 } 252 253 match::match(config &c, const char *var, const char *re) 254 : _var(var) 255 { 256 string pattern = re; 257 _re = "^"; 258 _re.append(c.expand_string(string(re))); 259 _re.append("$"); 260 regcomp(&_regex, _re.c_str(), REG_EXTENDED | REG_NOSUB); 261 } 262 263 match::~match() 264 { 265 regfree(&_regex); 266 } 267 268 bool 269 match::do_match(config &c) 270 { 271 string value = c.get_variable(_var); 272 bool retval; 273 274 if (dflag) 275 fprintf(stderr, "Testing %s=%s against %s\n", _var.c_str(), 276 value.c_str(), _re.c_str()); 277 278 retval = (regexec(&_regex, value.c_str(), 0, NULL, 0) == 0); 279 return retval; 280 } 281 282 const string var_list::bogus = "_$_$_$_$_B_O_G_U_S_$_$_$_$_"; 283 const string var_list::nothing = ""; 284 285 const string & 286 var_list::get_variable(const string &var) const 287 { 288 map<string, string>::const_iterator i; 289 290 i = _vars.find(var); 291 if (i == _vars.end()) 292 return (var_list::bogus); 293 return (i->second); 294 } 295 296 bool 297 var_list::is_set(const string &var) const 298 { 299 return (_vars.find(var) != _vars.end()); 300 } 301 302 void 303 var_list::set_variable(const string &var, const string &val) 304 { 305 if (dflag) 306 fprintf(stderr, "%s=%s\n", var.c_str(), val.c_str()); 307 _vars[var] = val; 308 } 309 310 void 311 config::reset(void) 312 { 313 _dir_list.clear(); 314 delete_and_clear(_var_list_table); 315 delete_and_clear(_attach_list); 316 delete_and_clear(_detach_list); 317 delete_and_clear(_nomatch_list); 318 } 319 320 void 321 config::parse_one_file(const char *fn) 322 { 323 if (dflag) 324 printf("Parsing %s\n", fn); 325 yyin = fopen(fn, "r"); 326 if (yyin == NULL) 327 err(1, "Cannot open config file %s", fn); 328 if (yyparse() != 0) 329 errx(1, "Cannot parse %s at line %d", fn, lineno); 330 fclose(yyin); 331 } 332 333 void 334 config::parse_files_in_dir(const char *dirname) 335 { 336 DIR *dirp; 337 struct dirent *dp; 338 char path[PATH_MAX]; 339 340 if (dflag) 341 printf("Parsing files in %s\n", dirname); 342 dirp = opendir(dirname); 343 if (dirp == NULL) 344 return; 345 readdir(dirp); /* Skip . */ 346 readdir(dirp); /* Skip .. */ 347 while ((dp = readdir(dirp)) != NULL) { 348 if (strcmp(dp->d_name + dp->d_namlen - 5, ".conf") == 0) { 349 snprintf(path, sizeof(path), "%s/%s", 350 dirname, dp->d_name); 351 parse_one_file(path); 352 } 353 } 354 } 355 356 class epv_greater { 357 public: 358 int operator()(event_proc *const&l1, event_proc *const&l2) 359 { 360 return (l1->get_priority() > l2->get_priority()); 361 } 362 }; 363 364 void 365 config::sort_vector(vector<event_proc *> &v) 366 { 367 sort(v.begin(), v.end(), epv_greater()); 368 } 369 370 void 371 config::parse(void) 372 { 373 vector<string>::const_iterator i; 374 375 parse_one_file(CF); 376 for (i = _dir_list.begin(); i != _dir_list.end(); i++) 377 parse_files_in_dir((*i).c_str()); 378 sort_vector(_attach_list); 379 sort_vector(_detach_list); 380 sort_vector(_nomatch_list); 381 } 382 383 void 384 config::drop_pidfile() 385 { 386 FILE *fp; 387 388 if (_pidfile == "") 389 return; 390 fp = fopen(_pidfile.c_str(), "w"); 391 if (fp == NULL) 392 return; 393 fprintf(fp, "%d\n", getpid()); 394 fclose(fp); 395 } 396 397 void 398 config::add_attach(int prio, event_proc *p) 399 { 400 p->set_priority(prio); 401 _attach_list.push_back(p); 402 } 403 404 void 405 config::add_detach(int prio, event_proc *p) 406 { 407 p->set_priority(prio); 408 _detach_list.push_back(p); 409 } 410 411 void 412 config::add_directory(const char *dir) 413 { 414 _dir_list.push_back(string(dir)); 415 } 416 417 void 418 config::add_nomatch(int prio, event_proc *p) 419 { 420 p->set_priority(prio); 421 _nomatch_list.push_back(p); 422 } 423 424 void 425 config::set_pidfile(const char *fn) 426 { 427 _pidfile = string(fn); 428 } 429 430 void 431 config::push_var_table() 432 { 433 var_list *vl; 434 435 vl = new var_list(); 436 _var_list_table.push_back(vl); 437 if (dflag) 438 fprintf(stderr, "Pushing table\n"); 439 } 440 441 void 442 config::pop_var_table() 443 { 444 delete _var_list_table.back(); 445 _var_list_table.pop_back(); 446 if (dflag) 447 fprintf(stderr, "Popping table\n"); 448 } 449 450 void 451 config::set_variable(const char *var, const char *val) 452 { 453 _var_list_table.back()->set_variable(var, val); 454 } 455 456 const string & 457 config::get_variable(const string &var) 458 { 459 vector<var_list *>::reverse_iterator i; 460 461 for (i = _var_list_table.rbegin(); i != _var_list_table.rend(); i++) { 462 if ((*i)->is_set(var)) 463 return ((*i)->get_variable(var)); 464 } 465 return (var_list::nothing); 466 } 467 468 bool 469 config::is_id_char(char ch) 470 { 471 return (ch != '\0' && (isalpha(ch) || isdigit(ch) || ch == '_' || 472 ch == '-')); 473 } 474 475 // XXX 476 // imp should learn how to make effective use of the string class. 477 void 478 config::expand_one(const char *&src, char *&dst, char *) 479 { 480 int count; 481 const char *var; 482 char buffer[1024]; 483 string varstr; 484 485 src++; 486 // $$ -> $ 487 if (*src == '$') { 488 *dst++ = *src++; 489 return; 490 } 491 492 // $(foo) -> $(foo) 493 // Not sure if I want to support this or not, so for now we just pass 494 // it through. 495 if (*src == '(') { 496 *dst++ = '$'; 497 count = 1; 498 while (count > 0) { 499 if (*src == ')') 500 count--; 501 else if (*src == '(') 502 count++; 503 *dst++ = *src++; 504 } 505 return; 506 } 507 508 // ${^A-Za-z] -> $\1 509 if (!isalpha(*src)) { 510 *dst++ = '$'; 511 *dst++ = *src++; 512 return; 513 } 514 515 // $var -> replace with value 516 var = src++; 517 while (is_id_char(*src)) 518 src++; 519 memcpy(buffer, var, src - var); 520 buffer[src - var] = '\0'; 521 varstr = get_variable(buffer); 522 strcpy(dst, varstr.c_str()); 523 dst += strlen(dst); 524 } 525 526 const string 527 config::expand_string(const string &s) 528 { 529 const char *src; 530 char *dst; 531 char buffer[1024]; 532 533 src = s.c_str(); 534 dst = buffer; 535 while (*src) { 536 if (*src == '$') 537 expand_one(src, dst, buffer + sizeof(buffer)); 538 else 539 *dst++ = *src++; 540 } 541 *dst++ = '\0'; 542 543 return (buffer); 544 } 545 546 bool 547 config::chop_var(char *&buffer, char *&lhs, char *&rhs) 548 { 549 char *walker; 550 551 if (*buffer == '\0') 552 return (false); 553 walker = lhs = buffer; 554 while (is_id_char(*walker)) 555 walker++; 556 if (*walker != '=') 557 return (false); 558 walker++; // skip = 559 if (*walker == '"') { 560 walker++; // skip " 561 rhs = walker; 562 while (*walker && *walker != '"') 563 walker++; 564 if (*walker != '"') 565 return (false); 566 rhs[-2] = '\0'; 567 *walker++ = '\0'; 568 } else { 569 rhs = walker; 570 while (*walker && !isspace(*walker)) 571 walker++; 572 if (*walker != '\0') 573 *walker++ = '\0'; 574 rhs[-1] = '\0'; 575 } 576 buffer = walker; 577 return (true); 578 } 579 580 581 char * 582 config::set_vars(char *buffer) 583 { 584 char *lhs; 585 char *rhs; 586 587 while (1) { 588 if (!chop_var(buffer, lhs, rhs)) 589 break; 590 set_variable(lhs, rhs); 591 } 592 return (buffer); 593 } 594 595 void 596 config::find_and_execute(char type) 597 { 598 vector<event_proc *> *l; 599 vector<event_proc *>::const_iterator i; 600 char *s; 601 602 switch (type) { 603 default: 604 return; 605 case nomatch: 606 l = &_nomatch_list; 607 s = "nomatch"; 608 break; 609 case attach: 610 l = &_attach_list; 611 s = "attach"; 612 break; 613 case detach: 614 l = &_detach_list; 615 s = "detach"; 616 break; 617 } 618 if (dflag) 619 fprintf(stderr, "Processing %s event\n", s); 620 for (i = l->begin(); i != l->end(); i++) { 621 if ((*i)->matches(*this)) { 622 (*i)->run(*this); 623 break; 624 } 625 } 626 627 } 628 629 630 static void 631 process_event(char *buffer) 632 { 633 char type; 634 char *sp; 635 636 sp = buffer + 1; 637 if (dflag) 638 fprintf(stderr, "Processing event '%s'\n", buffer); 639 type = *buffer++; 640 cfg.push_var_table(); 641 // No match doesn't have a device, and the format is a little 642 // different, so handle it separately. 643 if (type != nomatch) { 644 sp = strchr(sp, ' '); 645 if (sp == NULL) 646 return; /* Can't happen? */ 647 *sp++ = '\0'; 648 cfg.set_variable("device-name", buffer); 649 } 650 if (strncmp(sp, "at ", 3) == 0) 651 sp += 3; 652 sp = cfg.set_vars(sp); 653 if (strncmp(sp, "on ", 3) == 0) 654 cfg.set_variable("bus", sp + 3); 655 cfg.find_and_execute(type); 656 cfg.pop_var_table(); 657 } 658 659 static void 660 event_loop(void) 661 { 662 int rv; 663 int fd; 664 char buffer[DEVCTL_MAXBUF]; 665 666 fd = open(PATH_DEVCTL, O_RDONLY); 667 if (fd == -1) 668 err(1, "Can't open devctl"); 669 if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) 670 err(1, "Can't set close-on-exec flag"); 671 while (1) { 672 if (romeo_must_die) 673 break; 674 rv = read(fd, buffer, sizeof(buffer) - 1); 675 if (rv > 0) { 676 buffer[rv] = '\0'; 677 while (buffer[--rv] == '\n') 678 buffer[rv] = '\0'; 679 process_event(buffer); 680 } else if (rv < 0) { 681 if (errno != EINTR) 682 break; 683 } else { 684 /* EOF */ 685 break; 686 } 687 } 688 close(fd); 689 } 690 691 /* 692 * functions that the parser uses. 693 */ 694 void 695 add_attach(int prio, event_proc *p) 696 { 697 cfg.add_attach(prio, p); 698 } 699 700 void 701 add_detach(int prio, event_proc *p) 702 { 703 cfg.add_detach(prio, p); 704 } 705 706 void 707 add_directory(const char *dir) 708 { 709 cfg.add_directory(dir); 710 free(const_cast<char *>(dir)); 711 } 712 713 void 714 add_nomatch(int prio, event_proc *p) 715 { 716 cfg.add_nomatch(prio, p); 717 } 718 719 event_proc * 720 add_to_event_proc(event_proc *ep, eps *eps) 721 { 722 if (ep == NULL) 723 ep = new event_proc(); 724 ep->add(eps); 725 return (ep); 726 } 727 728 eps * 729 new_action(const char *cmd) 730 { 731 eps *e = new action(cmd); 732 free(const_cast<char *>(cmd)); 733 return (e); 734 } 735 736 eps * 737 new_match(const char *var, const char *re) 738 { 739 eps *e = new match(cfg, var, re); 740 free(const_cast<char *>(var)); 741 free(const_cast<char *>(re)); 742 return (e); 743 } 744 745 void 746 set_pidfile(const char *name) 747 { 748 cfg.set_pidfile(name); 749 free(const_cast<char *>(name)); 750 } 751 752 void 753 set_variable(const char *var, const char *val) 754 { 755 cfg.set_variable(var, val); 756 free(const_cast<char *>(var)); 757 free(const_cast<char *>(val)); 758 } 759 760 761 762 static void 763 gensighand(int) 764 { 765 romeo_must_die++; 766 _exit(0); 767 } 768 769 static void 770 usage() 771 { 772 fprintf(stderr, "usage: %s [-d]\n", getprogname()); 773 exit(1); 774 } 775 776 /* 777 * main 778 */ 779 int 780 main(int argc, char **argv) 781 { 782 int ch; 783 784 while ((ch = getopt(argc, argv, "d")) != -1) { 785 switch (ch) { 786 case 'd': 787 dflag++; 788 break; 789 default: 790 usage(); 791 } 792 } 793 794 cfg.parse(); 795 if (!dflag) 796 daemon(0, 0); 797 cfg.drop_pidfile(); 798 signal(SIGHUP, gensighand); 799 signal(SIGINT, gensighand); 800 signal(SIGTERM, gensighand); 801 event_loop(); 802 return (0); 803 } 804