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 if (rv == 0) { 594 if (Dflag) 595 fprintf(stderr, "Calling daemon\n"); 596 daemon(0, 0); 597 cfg.drop_pidfile(); 598 once++; 599 } 600 } 601 rv = read(fd, buffer, sizeof(buffer) - 1); 602 if (rv > 0) { 603 buffer[rv] = '\0'; 604 while (buffer[--rv] == '\n') 605 buffer[rv] = '\0'; 606 process_event(buffer); 607 } else if (rv < 0) { 608 if (errno != EINTR) 609 break; 610 } else { 611 /* EOF */ 612 break; 613 } 614 } 615 close(fd); 616 } 617 618 /* 619 * functions that the parser uses. 620 */ 621 void 622 add_attach(int prio, event_proc *p) 623 { 624 cfg.add_attach(prio, p); 625 } 626 627 void 628 add_detach(int prio, event_proc *p) 629 { 630 cfg.add_detach(prio, p); 631 } 632 633 void 634 add_directory(const char *dir) 635 { 636 cfg.add_directory(dir); 637 free(const_cast<char *>(dir)); 638 } 639 640 void 641 add_nomatch(int prio, event_proc *p) 642 { 643 cfg.add_nomatch(prio, p); 644 } 645 646 event_proc * 647 add_to_event_proc(event_proc *ep, eps *eps) 648 { 649 if (ep == NULL) 650 ep = new event_proc(); 651 ep->add(eps); 652 return (ep); 653 } 654 655 eps * 656 new_action(const char *cmd) 657 { 658 eps *e = new action(cmd); 659 free(const_cast<char *>(cmd)); 660 return (e); 661 } 662 663 eps * 664 new_match(const char *var, const char *re) 665 { 666 eps *e = new match(cfg, var, re); 667 free(const_cast<char *>(var)); 668 free(const_cast<char *>(re)); 669 return (e); 670 } 671 672 void 673 set_pidfile(const char *name) 674 { 675 cfg.set_pidfile(name); 676 free(const_cast<char *>(name)); 677 } 678 679 void 680 set_variable(const char *var, const char *val) 681 { 682 cfg.set_variable(var, val); 683 free(const_cast<char *>(var)); 684 free(const_cast<char *>(val)); 685 } 686 687 688 689 static void 690 gensighand(int) 691 { 692 romeo_must_die++; 693 _exit(0); 694 } 695 696 static void 697 usage() 698 { 699 fprintf(stderr, "usage: %s [-d]\n", getprogname()); 700 exit(1); 701 } 702 703 static void 704 check_devd_enabled() 705 { 706 int val = 0; 707 size_t len; 708 709 len = sizeof(val); 710 if (sysctlbyname(SYSCTL, &val, &len, NULL, 0) != 0) 711 errx(1, "devctl sysctl missing from kernel!"); 712 if (val) { 713 warnx("Setting " SYSCTL " to 0"); 714 val = 0; 715 sysctlbyname(SYSCTL, NULL, NULL, &val, sizeof(val)); 716 } 717 } 718 719 /* 720 * main 721 */ 722 int 723 main(int argc, char **argv) 724 { 725 int ch; 726 727 check_devd_enabled(); 728 while ((ch = getopt(argc, argv, "Ddn")) != -1) { 729 switch (ch) { 730 case 'D': 731 Dflag++; 732 break; 733 case 'd': 734 dflag++; 735 break; 736 case 'n': 737 nflag++; 738 break; 739 default: 740 usage(); 741 } 742 } 743 744 cfg.parse(); 745 if (!dflag && nflag) { 746 daemon(0, 0); 747 cfg.drop_pidfile(); 748 } 749 signal(SIGHUP, gensighand); 750 signal(SIGINT, gensighand); 751 signal(SIGTERM, gensighand); 752 event_loop(); 753 return (0); 754 } 755