devd.cc revision 113785
1/*- 2 * Copyright (c) 2002-2003 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 36#include <sys/cdefs.h> 37__FBSDID("$FreeBSD: head/sbin/devd/devd.cc 113785 2003-04-21 04:00:01Z 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 while (isspace(*walker)) 576 walker++; 577 buffer = walker; 578 return (true); 579} 580 581 582char * 583config::set_vars(char *buffer) 584{ 585 char *lhs; 586 char *rhs; 587 588 while (1) { 589 if (!chop_var(buffer, lhs, rhs)) 590 break; 591 set_variable(lhs, rhs); 592 } 593 return (buffer); 594} 595 596void 597config::find_and_execute(char type) 598{ 599 vector<event_proc *> *l; 600 vector<event_proc *>::const_iterator i; 601 char *s; 602 603 switch (type) { 604 default: 605 return; 606 case nomatch: 607 l = &_nomatch_list; 608 s = "nomatch"; 609 break; 610 case attach: 611 l = &_attach_list; 612 s = "attach"; 613 break; 614 case detach: 615 l = &_detach_list; 616 s = "detach"; 617 break; 618 } 619 if (dflag) 620 fprintf(stderr, "Processing %s event\n", s); 621 for (i = l->begin(); i != l->end(); i++) { 622 if ((*i)->matches(*this)) { 623 (*i)->run(*this); 624 break; 625 } 626 } 627 628} 629 630 631static void 632process_event(char *buffer) 633{ 634 char type; 635 char *sp; 636 637 sp = buffer + 1; 638 if (dflag) 639 fprintf(stderr, "Processing event '%s'\n", buffer); 640 type = *buffer++; 641 cfg.push_var_table(); 642 // No match doesn't have a device, and the format is a little 643 // different, so handle it separately. 644 if (type != nomatch) { 645 sp = strchr(sp, ' '); 646 if (sp == NULL) 647 return; /* Can't happen? */ 648 *sp++ = '\0'; 649 cfg.set_variable("device-name", buffer); 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 } else { 656 //?vars at location on bus 657 sp = cfg.set_vars(sp); 658 if (strncmp(sp, "at ", 3) == 0) 659 sp += 3; 660 sp = cfg.set_vars(sp); 661 if (strncmp(sp, "on ", 3) == 0) 662 cfg.set_variable("bus", sp + 3); 663 } 664 665 cfg.find_and_execute(type); 666 cfg.pop_var_table(); 667} 668 669static void 670event_loop(void) 671{ 672 int rv; 673 int fd; 674 char buffer[DEVCTL_MAXBUF]; 675 676 fd = open(PATH_DEVCTL, O_RDONLY); 677 if (fd == -1) 678 err(1, "Can't open devctl"); 679 if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) 680 err(1, "Can't set close-on-exec flag"); 681 while (1) { 682 if (romeo_must_die) 683 break; 684 rv = read(fd, buffer, sizeof(buffer) - 1); 685 if (rv > 0) { 686 buffer[rv] = '\0'; 687 while (buffer[--rv] == '\n') 688 buffer[rv] = '\0'; 689 process_event(buffer); 690 } else if (rv < 0) { 691 if (errno != EINTR) 692 break; 693 } else { 694 /* EOF */ 695 break; 696 } 697 } 698 close(fd); 699} 700 701/* 702 * functions that the parser uses. 703 */ 704void 705add_attach(int prio, event_proc *p) 706{ 707 cfg.add_attach(prio, p); 708} 709 710void 711add_detach(int prio, event_proc *p) 712{ 713 cfg.add_detach(prio, p); 714} 715 716void 717add_directory(const char *dir) 718{ 719 cfg.add_directory(dir); 720 free(const_cast<char *>(dir)); 721} 722 723void 724add_nomatch(int prio, event_proc *p) 725{ 726 cfg.add_nomatch(prio, p); 727} 728 729event_proc * 730add_to_event_proc(event_proc *ep, eps *eps) 731{ 732 if (ep == NULL) 733 ep = new event_proc(); 734 ep->add(eps); 735 return (ep); 736} 737 738eps * 739new_action(const char *cmd) 740{ 741 eps *e = new action(cmd); 742 free(const_cast<char *>(cmd)); 743 return (e); 744} 745 746eps * 747new_match(const char *var, const char *re) 748{ 749 eps *e = new match(cfg, var, re); 750 free(const_cast<char *>(var)); 751 free(const_cast<char *>(re)); 752 return (e); 753} 754 755void 756set_pidfile(const char *name) 757{ 758 cfg.set_pidfile(name); 759 free(const_cast<char *>(name)); 760} 761 762void 763set_variable(const char *var, const char *val) 764{ 765 cfg.set_variable(var, val); 766 free(const_cast<char *>(var)); 767 free(const_cast<char *>(val)); 768} 769 770 771 772static void 773gensighand(int) 774{ 775 romeo_must_die++; 776 _exit(0); 777} 778 779static void 780usage() 781{ 782 fprintf(stderr, "usage: %s [-d]\n", getprogname()); 783 exit(1); 784} 785 786/* 787 * main 788 */ 789int 790main(int argc, char **argv) 791{ 792 int ch; 793 794 while ((ch = getopt(argc, argv, "d")) != -1) { 795 switch (ch) { 796 case 'd': 797 dflag++; 798 break; 799 default: 800 usage(); 801 } 802 } 803 804 cfg.parse(); 805 if (!dflag) 806 daemon(0, 0); 807 cfg.drop_pidfile(); 808 signal(SIGHUP, gensighand); 809 signal(SIGINT, gensighand); 810 signal(SIGTERM, gensighand); 811 event_loop(); 812 return (0); 813} 814