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