devd.cc revision 114086
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 114086 2003-04-26 20:59:04Z 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 nomatch = '?'; 71static const char attach = '+'; 72static const char detach = '-'; 73 74int Dflag; 75int dflag; 76int nflag; 77int romeo_must_die = 0; 78 79static void event_loop(void); 80static void usage(void); 81 82template <class T> void 83delete_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 92config cfg; 93 94event_proc::event_proc() : _prio(-1) 95{ 96 // nothing 97} 98 99event_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 108void 109event_proc::add(eps *eps) 110{ 111 _epsvec.push_back(eps); 112} 113 114bool 115event_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 125bool 126event_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 136action::action(const char *cmd) 137 : _cmd(cmd) 138{ 139 // nothing 140} 141 142action::~action() 143{ 144 // nothing 145} 146 147bool 148action::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 157match::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 167match::~match() 168{ 169 regfree(&_regex); 170} 171 172bool 173match::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 186const string var_list::bogus = "_$_$_$_$_B_O_G_U_S_$_$_$_$_"; 187const string var_list::nothing = ""; 188 189const string & 190var_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 200bool 201var_list::is_set(const string &var) const 202{ 203 return (_vars.find(var) != _vars.end()); 204} 205 206void 207var_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 214void 215config::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 224void 225config::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 237void 238config::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 260class epv_greater { 261public: 262 int operator()(event_proc *const&l1, event_proc *const&l2) 263 { 264 return (l1->get_priority() > l2->get_priority()); 265 } 266}; 267 268void 269config::sort_vector(vector<event_proc *> &v) 270{ 271 sort(v.begin(), v.end(), epv_greater()); 272} 273 274void 275config::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 287void 288config::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 301void 302config::add_attach(int prio, event_proc *p) 303{ 304 p->set_priority(prio); 305 _attach_list.push_back(p); 306} 307 308void 309config::add_detach(int prio, event_proc *p) 310{ 311 p->set_priority(prio); 312 _detach_list.push_back(p); 313} 314 315void 316config::add_directory(const char *dir) 317{ 318 _dir_list.push_back(string(dir)); 319} 320 321void 322config::add_nomatch(int prio, event_proc *p) 323{ 324 p->set_priority(prio); 325 _nomatch_list.push_back(p); 326} 327 328void 329config::set_pidfile(const char *fn) 330{ 331 _pidfile = string(fn); 332} 333 334void 335config::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 345void 346config::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 354void 355config::set_variable(const char *var, const char *val) 356{ 357 _var_list_table.back()->set_variable(var, val); 358} 359 360const string & 361config::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 372bool 373config::is_id_char(char ch) 374{ 375 return (ch != '\0' && (isalpha(ch) || isdigit(ch) || ch == '_' || 376 ch == '-')); 377} 378 379void 380config::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 425const string 426config::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 443bool 444config::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 480char * 481config::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 494void 495config::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 529static void 530process_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 567static void 568event_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 once++; 600 } 601 } 602 rv = read(fd, buffer, sizeof(buffer) - 1); 603 if (rv > 0) { 604 buffer[rv] = '\0'; 605 while (buffer[--rv] == '\n') 606 buffer[rv] = '\0'; 607 process_event(buffer); 608 } else if (rv < 0) { 609 if (errno != EINTR) 610 break; 611 } else { 612 /* EOF */ 613 break; 614 } 615 } 616 close(fd); 617} 618 619/* 620 * functions that the parser uses. 621 */ 622void 623add_attach(int prio, event_proc *p) 624{ 625 cfg.add_attach(prio, p); 626} 627 628void 629add_detach(int prio, event_proc *p) 630{ 631 cfg.add_detach(prio, p); 632} 633 634void 635add_directory(const char *dir) 636{ 637 cfg.add_directory(dir); 638 free(const_cast<char *>(dir)); 639} 640 641void 642add_nomatch(int prio, event_proc *p) 643{ 644 cfg.add_nomatch(prio, p); 645} 646 647event_proc * 648add_to_event_proc(event_proc *ep, eps *eps) 649{ 650 if (ep == NULL) 651 ep = new event_proc(); 652 ep->add(eps); 653 return (ep); 654} 655 656eps * 657new_action(const char *cmd) 658{ 659 eps *e = new action(cmd); 660 free(const_cast<char *>(cmd)); 661 return (e); 662} 663 664eps * 665new_match(const char *var, const char *re) 666{ 667 eps *e = new match(cfg, var, re); 668 free(const_cast<char *>(var)); 669 free(const_cast<char *>(re)); 670 return (e); 671} 672 673void 674set_pidfile(const char *name) 675{ 676 cfg.set_pidfile(name); 677 free(const_cast<char *>(name)); 678} 679 680void 681set_variable(const char *var, const char *val) 682{ 683 cfg.set_variable(var, val); 684 free(const_cast<char *>(var)); 685 free(const_cast<char *>(val)); 686} 687 688 689 690static void 691gensighand(int) 692{ 693 romeo_must_die++; 694 _exit(0); 695} 696 697static void 698usage() 699{ 700 fprintf(stderr, "usage: %s [-d]\n", getprogname()); 701 exit(1); 702} 703 704static void 705check_devd_enabled() 706{ 707 int val = 0; 708 size_t len; 709 710 len = sizeof(val); 711 if (sysctlbyname(SYSCTL, &val, &len, NULL, NULL) != 0) 712 errx(1, "devctl sysctl missing from kernel!"); 713 if (val) { 714 warnx("Setting " SYSCTL " to 0"); 715 val = 0; 716 sysctlbyname(SYSCTL, NULL, NULL, &val, sizeof(val)); 717 } 718} 719 720/* 721 * main 722 */ 723int 724main(int argc, char **argv) 725{ 726 int ch; 727 728 check_devd_enabled(); 729 while ((ch = getopt(argc, argv, "Ddn")) != -1) { 730 switch (ch) { 731 case 'D': 732 Dflag++; 733 break; 734 case 'd': 735 dflag++; 736 break; 737 case 'n': 738 nflag++; 739 break; 740 default: 741 usage(); 742 } 743 } 744 745 cfg.parse(); 746 if (!dflag && nflag) 747 daemon(0, 0); 748 cfg.drop_pidfile(); 749 signal(SIGHUP, gensighand); 750 signal(SIGINT, gensighand); 751 signal(SIGTERM, gensighand); 752 event_loop(); 753 return (0); 754} 755