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