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