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