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$"); 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 65 using namespace std; 66 67 extern FILE *yyin; 68 extern int lineno; 69 70 static const char notify = '!'; 71 static const char nomatch = '?'; 72 static const char attach = '+'; 73 static const char detach = '-'; 74 75 int Dflag; 76 int dflag; 77 int nflag; 78 int romeo_must_die = 0; 79 80 static void event_loop(void); 81 static void usage(void); 82 83 template <class T> void 84 delete_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 93 config cfg; 94 95 event_proc::event_proc() : _prio(-1) 96 { 97 // nothing 98 } 99 100 event_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 109 void 110 event_proc::add(eps *eps) 111 { 112 _epsvec.push_back(eps); 113 } 114 115 bool 116 event_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 126 bool 127 event_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 137 action::action(const char *cmd) 138 : _cmd(cmd) 139 { 140 // nothing 141 } 142 143 action::~action() 144 { 145 // nothing 146 } 147 148 bool 149 action::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 158 match::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 168 match::~match() 169 { 170 regfree(&_regex); 171 } 172 173 bool 174 match::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 187 const string var_list::bogus = "_$_$_$_$_B_O_G_U_S_$_$_$_$_"; 188 const string var_list::nothing = ""; 189 190 const string & 191 var_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 201 bool 202 var_list::is_set(const string &var) const 203 { 204 return (_vars.find(var) != _vars.end()); 205 } 206 207 void 208 var_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 215 void 216 config::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 226 void 227 config::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 239 void 240 config::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 262 class epv_greater { 263 public: 264 int operator()(event_proc *const&l1, event_proc *const&l2) 265 { 266 return (l1->get_priority() > l2->get_priority()); 267 } 268 }; 269 270 void 271 config::sort_vector(vector<event_proc *> &v) 272 { 273 sort(v.begin(), v.end(), epv_greater()); 274 } 275 276 void 277 config::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 290 void 291 config::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 304 void 305 config::add_attach(int prio, event_proc *p) 306 { 307 p->set_priority(prio); 308 _attach_list.push_back(p); 309 } 310 311 void 312 config::add_detach(int prio, event_proc *p) 313 { 314 p->set_priority(prio); 315 _detach_list.push_back(p); 316 } 317 318 void 319 config::add_directory(const char *dir) 320 { 321 _dir_list.push_back(string(dir)); 322 } 323 324 void 325 config::add_nomatch(int prio, event_proc *p) 326 { 327 p->set_priority(prio); 328 _nomatch_list.push_back(p); 329 } 330 331 void 332 config::add_notify(int prio, event_proc *p) 333 { 334 p->set_priority(prio); 335 _notify_list.push_back(p); 336 } 337 338 void 339 config::set_pidfile(const char *fn) 340 { 341 _pidfile = string(fn); 342 } 343 344 void 345 config::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 355 void 356 config::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 364 void 365 config::set_variable(const char *var, const char *val) 366 { 367 _var_list_table.back()->set_variable(var, val); 368 } 369 370 const string & 371 config::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 382 bool 383 config::is_id_char(char ch) 384 { 385 return (ch != '\0' && (isalpha(ch) || isdigit(ch) || ch == '_' || 386 ch == '-')); 387 } 388 389 void 390 config::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 435 const string 436 config::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 453 bool 454 config::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 490 char * 491 config::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 504 void 505 config::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 543 static void 544 process_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 588 static void 589 event_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 */ 642 void 643 add_attach(int prio, event_proc *p) 644 { 645 cfg.add_attach(prio, p); 646 } 647 648 void 649 add_detach(int prio, event_proc *p) 650 { 651 cfg.add_detach(prio, p); 652 } 653 654 void 655 add_directory(const char *dir) 656 { 657 cfg.add_directory(dir); 658 free(const_cast<char *>(dir)); 659 } 660 661 void 662 add_nomatch(int prio, event_proc *p) 663 { 664 cfg.add_nomatch(prio, p); 665 } 666 667 void 668 add_notify(int prio, event_proc *p) 669 { 670 cfg.add_notify(prio, p); 671 } 672 673 event_proc * 674 add_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 682 eps * 683 new_action(const char *cmd) 684 { 685 eps *e = new action(cmd); 686 free(const_cast<char *>(cmd)); 687 return (e); 688 } 689 690 eps * 691 new_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 699 void 700 set_pidfile(const char *name) 701 { 702 cfg.set_pidfile(name); 703 free(const_cast<char *>(name)); 704 } 705 706 void 707 set_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 716 static void 717 gensighand(int) 718 { 719 romeo_must_die++; 720 _exit(0); 721 } 722 723 static void 724 usage() 725 { 726 fprintf(stderr, "usage: %s [-d]\n", getprogname()); 727 exit(1); 728 } 729 730 static void 731 check_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 */ 749 int 750 main(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