devd.cc revision 113787
178381Snyan/*- 278381Snyan * Copyright (c) 2002-2003 M. Warner Losh. 378381Snyan * All rights reserved. 478381Snyan * 578381Snyan * Redistribution and use in source and binary forms, with or without 678381Snyan * modification, are permitted provided that the following conditions 778381Snyan * are met: 878381Snyan * 1. Redistributions of source code must retain the above copyright 978381Snyan * notice, this list of conditions and the following disclaimer. 1078381Snyan * 2. Redistributions in binary form must reproduce the above copyright 1178381Snyan * notice, this list of conditions and the following disclaimer in the 1278381Snyan * documentation and/or other materials provided with the distribution. 1378381Snyan * 1478381Snyan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 1578381Snyan * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1678381Snyan * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1778381Snyan * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 1878381Snyan * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 1978381Snyan * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2078381Snyan * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2178381Snyan * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2278381Snyan * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2378381Snyan * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2478381Snyan * SUCH DAMAGE. 2578381Snyan */ 2678381Snyan 2778381Snyan/* 2878381Snyan * DEVD control daemon. 2978381Snyan */ 3078381Snyan 3178381Snyan// TODO list: 3278381Snyan// o devd.conf and devd man pages need a lot of help: 3378381Snyan// - devd.conf needs to lose the warning about zone files. 3478381Snyan// - devd.conf needs more details on the supported statements. 3578381Snyan 3678381Snyan#include <sys/cdefs.h> 3778381Snyan__FBSDID("$FreeBSD: head/sbin/devd/devd.cc 113787 2003-04-21 04:30:12Z imp $"); 3878381Snyan 3978381Snyan#include <sys/param.h> 4078381Snyan#include <sys/types.h> 4178381Snyan#include <sys/sysctl.h> 4278381Snyan 4378381Snyan#include <ctype.h> 4478381Snyan#include <dirent.h> 4578381Snyan#include <errno.h> 4678381Snyan#include <err.h> 4778381Snyan#include <fcntl.h> 4878381Snyan#include <regex.h> 4978381Snyan#include <stdlib.h> 5078381Snyan#include <stdio.h> 5178381Snyan#include <string.h> 5278381Snyan#include <unistd.h> 5378381Snyan 5478381Snyan#include <algorithm> 5578381Snyan#include <map> 5678381Snyan#include <string> 5778381Snyan#include <vector> 5878381Snyan 5978381Snyan#include "devd.h" 6078381Snyan 6178381Snyan#define CF "/etc/devd.conf" 6278381Snyan#define SYSCTL "hw.bus.devctl_disable" 6378381Snyan 6478381Snyanusing namespace std; 6578381Snyan 6678381Snyanextern FILE *yyin; 6778381Snyanextern int lineno; 6878381Snyan 6978381Snyanstatic const char nomatch = '?'; 7078381Snyanstatic const char attach = '+'; 7178381Snyanstatic const char detach = '-'; 7278381Snyan 7378381Snyanint dflag; 7478381Snyanint romeo_must_die = 0; 7578381Snyan 7678381Snyanstatic void event_loop(void); 7778381Snyanstatic void usage(void); 7878381Snyan 7978381Snyantemplate <class T> void 8078381Snyandelete_and_clear(vector<T *> &v) 8178381Snyan{ 8278381Snyan typename vector<T *>::const_iterator i; 8378381Snyan 8478381Snyan for (i = v.begin(); i != v.end(); i++) 8578381Snyan delete *i; 8678381Snyan v.clear(); 8778381Snyan} 8878381Snyan 8978381Snyanclass config; 9078381Snyan 9178381Snyanclass var_list 9278381Snyan{ 9378381Snyanpublic: 9478381Snyan var_list() {} 9578381Snyan virtual ~var_list() {} 9678381Snyan void set_variable(const string &var, const string &val); 9778381Snyan const string &get_variable(const string &var) const; 9878381Snyan bool is_set(const string &var) const; 9978381Snyan static const string bogus; 10078381Snyan static const string nothing; 10178381Snyanprivate: 10278381Snyan map<string, string> _vars; 10378381Snyan}; 10478381Snyan 10578381Snyanclass eps 10678381Snyan{ 10778381Snyanpublic: 10878381Snyan eps() {} 10978381Snyan virtual ~eps() {} 11078381Snyan virtual bool do_match(config &) = 0; 11178381Snyan virtual bool do_action(config &) = 0; 11278381Snyan}; 11378381Snyan 11478381Snyanclass match : public eps 11578381Snyan{ 11678381Snyanpublic: 11778381Snyan match(config &, const char *var, const char *re); 11878381Snyan virtual ~match(); 11978381Snyan virtual bool do_match(config &); 12078381Snyan virtual bool do_action(config &) { return true; } 12178381Snyanprivate: 12278381Snyan string _var; 12378381Snyan string _re; 12478381Snyan regex_t _regex; 12578381Snyan}; 12678381Snyan 12778381Snyanclass action : public eps 12878381Snyan{ 12978381Snyanpublic: 13078381Snyan action(const char *cmd); 13178381Snyan virtual ~action(); 13278381Snyan virtual bool do_match(config &) { return true; } 13378381Snyan virtual bool do_action(config &); 13478381Snyanprivate: 135 string _cmd; 136}; 137 138class event_proc 139{ 140public: 141 event_proc(); 142 virtual ~event_proc(); 143 int get_priority() const { return (_prio); } 144 void set_priority(int prio) { _prio = prio; } 145 void add(eps *); 146 bool matches(config &); 147 bool run(config &); 148private: 149 int _prio; 150 vector<eps *> _epsvec; 151}; 152 153class config 154{ 155public: 156 config() : _pidfile("") { push_var_table(); } 157 virtual ~config() { reset(); } 158 void add_attach(int, event_proc *); 159 void add_detach(int, event_proc *); 160 void add_directory(const char *); 161 void add_nomatch(int, event_proc *); 162 void set_pidfile(const char *); 163 void reset(); 164 void parse(); 165 void drop_pidfile(); 166 void push_var_table(); 167 void pop_var_table(); 168 void set_variable(const char *var, const char *val); 169 const string &get_variable(const string &var); 170 const string expand_string(const string &var); 171 char *set_vars(char *); 172 void find_and_execute(char); 173protected: 174 void sort_vector(vector<event_proc *> &); 175 void parse_one_file(const char *fn); 176 void parse_files_in_dir(const char *dirname); 177 void expand_one(const char *&src, char *&dst, char *eod); 178 bool is_id_char(char); 179 bool chop_var(char *&buffer, char *&lhs, char *&rhs); 180private: 181 vector<string> _dir_list; 182 string _pidfile; 183 vector<var_list *> _var_list_table; 184 vector<event_proc *> _attach_list; 185 vector<event_proc *> _detach_list; 186 vector<event_proc *> _nomatch_list; 187}; 188 189config cfg; 190 191event_proc::event_proc() : _prio(-1) 192{ 193 // nothing 194} 195 196event_proc::~event_proc() 197{ 198 vector<eps *>::const_iterator i; 199 200 for (i = _epsvec.begin(); i != _epsvec.end(); i++) 201 delete *i; 202 _epsvec.clear(); 203} 204 205void 206event_proc::add(eps *eps) 207{ 208 _epsvec.push_back(eps); 209} 210 211bool 212event_proc::matches(config &c) 213{ 214 vector<eps *>::const_iterator i; 215 216 for (i = _epsvec.begin(); i != _epsvec.end(); i++) 217 if (!(*i)->do_match(c)) 218 return (false); 219 return (true); 220} 221 222bool 223event_proc::run(config &c) 224{ 225 vector<eps *>::const_iterator i; 226 227 for (i = _epsvec.begin(); i != _epsvec.end(); i++) 228 if (!(*i)->do_action(c)) 229 return (false); 230 return (true); 231} 232 233action::action(const char *cmd) 234 : _cmd(cmd) 235{ 236 // nothing 237} 238 239action::~action() 240{ 241 // nothing 242} 243 244bool 245action::do_action(config &c) 246{ 247 string s = c.expand_string(_cmd); 248 if (dflag) 249 fprintf(stderr, "Executing '%s'\n", s.c_str()); 250 ::system(s.c_str()); 251 return (true); 252} 253 254match::match(config &c, const char *var, const char *re) 255 : _var(var) 256{ 257 string pattern = re; 258 _re = "^"; 259 _re.append(c.expand_string(string(re))); 260 _re.append("$"); 261 regcomp(&_regex, _re.c_str(), REG_EXTENDED | REG_NOSUB); 262} 263 264match::~match() 265{ 266 regfree(&_regex); 267} 268 269bool 270match::do_match(config &c) 271{ 272 string value = c.get_variable(_var); 273 bool retval; 274 275 if (dflag) 276 fprintf(stderr, "Testing %s=%s against %s\n", _var.c_str(), 277 value.c_str(), _re.c_str()); 278 279 retval = (regexec(&_regex, value.c_str(), 0, NULL, 0) == 0); 280 return retval; 281} 282 283const string var_list::bogus = "_$_$_$_$_B_O_G_U_S_$_$_$_$_"; 284const string var_list::nothing = ""; 285 286const string & 287var_list::get_variable(const string &var) const 288{ 289 map<string, string>::const_iterator i; 290 291 i = _vars.find(var); 292 if (i == _vars.end()) 293 return (var_list::bogus); 294 return (i->second); 295} 296 297bool 298var_list::is_set(const string &var) const 299{ 300 return (_vars.find(var) != _vars.end()); 301} 302 303void 304var_list::set_variable(const string &var, const string &val) 305{ 306 if (dflag) 307 fprintf(stderr, "%s=%s\n", var.c_str(), val.c_str()); 308 _vars[var] = val; 309} 310 311void 312config::reset(void) 313{ 314 _dir_list.clear(); 315 delete_and_clear(_var_list_table); 316 delete_and_clear(_attach_list); 317 delete_and_clear(_detach_list); 318 delete_and_clear(_nomatch_list); 319} 320 321void 322config::parse_one_file(const char *fn) 323{ 324 if (dflag) 325 printf("Parsing %s\n", fn); 326 yyin = fopen(fn, "r"); 327 if (yyin == NULL) 328 err(1, "Cannot open config file %s", fn); 329 if (yyparse() != 0) 330 errx(1, "Cannot parse %s at line %d", fn, lineno); 331 fclose(yyin); 332} 333 334void 335config::parse_files_in_dir(const char *dirname) 336{ 337 DIR *dirp; 338 struct dirent *dp; 339 char path[PATH_MAX]; 340 341 if (dflag) 342 printf("Parsing files in %s\n", dirname); 343 dirp = opendir(dirname); 344 if (dirp == NULL) 345 return; 346 readdir(dirp); /* Skip . */ 347 readdir(dirp); /* Skip .. */ 348 while ((dp = readdir(dirp)) != NULL) { 349 if (strcmp(dp->d_name + dp->d_namlen - 5, ".conf") == 0) { 350 snprintf(path, sizeof(path), "%s/%s", 351 dirname, dp->d_name); 352 parse_one_file(path); 353 } 354 } 355} 356 357class epv_greater { 358public: 359 int operator()(event_proc *const&l1, event_proc *const&l2) 360 { 361 return (l1->get_priority() > l2->get_priority()); 362 } 363}; 364 365void 366config::sort_vector(vector<event_proc *> &v) 367{ 368 sort(v.begin(), v.end(), epv_greater()); 369} 370 371void 372config::parse(void) 373{ 374 vector<string>::const_iterator i; 375 376 parse_one_file(CF); 377 for (i = _dir_list.begin(); i != _dir_list.end(); i++) 378 parse_files_in_dir((*i).c_str()); 379 sort_vector(_attach_list); 380 sort_vector(_detach_list); 381 sort_vector(_nomatch_list); 382} 383 384void 385config::drop_pidfile() 386{ 387 FILE *fp; 388 389 if (_pidfile == "") 390 return; 391 fp = fopen(_pidfile.c_str(), "w"); 392 if (fp == NULL) 393 return; 394 fprintf(fp, "%d\n", getpid()); 395 fclose(fp); 396} 397 398void 399config::add_attach(int prio, event_proc *p) 400{ 401 p->set_priority(prio); 402 _attach_list.push_back(p); 403} 404 405void 406config::add_detach(int prio, event_proc *p) 407{ 408 p->set_priority(prio); 409 _detach_list.push_back(p); 410} 411 412void 413config::add_directory(const char *dir) 414{ 415 _dir_list.push_back(string(dir)); 416} 417 418void 419config::add_nomatch(int prio, event_proc *p) 420{ 421 p->set_priority(prio); 422 _nomatch_list.push_back(p); 423} 424 425void 426config::set_pidfile(const char *fn) 427{ 428 _pidfile = string(fn); 429} 430 431void 432config::push_var_table() 433{ 434 var_list *vl; 435 436 vl = new var_list(); 437 _var_list_table.push_back(vl); 438 if (dflag) 439 fprintf(stderr, "Pushing table\n"); 440} 441 442void 443config::pop_var_table() 444{ 445 delete _var_list_table.back(); 446 _var_list_table.pop_back(); 447 if (dflag) 448 fprintf(stderr, "Popping table\n"); 449} 450 451void 452config::set_variable(const char *var, const char *val) 453{ 454 _var_list_table.back()->set_variable(var, val); 455} 456 457const string & 458config::get_variable(const string &var) 459{ 460 vector<var_list *>::reverse_iterator i; 461 462 for (i = _var_list_table.rbegin(); i != _var_list_table.rend(); i++) { 463 if ((*i)->is_set(var)) 464 return ((*i)->get_variable(var)); 465 } 466 return (var_list::nothing); 467} 468 469bool 470config::is_id_char(char ch) 471{ 472 return (ch != '\0' && (isalpha(ch) || isdigit(ch) || ch == '_' || 473 ch == '-')); 474} 475 476// XXX 477// imp should learn how to make effective use of the string class. 478void 479config::expand_one(const char *&src, char *&dst, char *) 480{ 481 int count; 482 const char *var; 483 char buffer[1024]; 484 string varstr; 485 486 src++; 487 // $$ -> $ 488 if (*src == '$') { 489 *dst++ = *src++; 490 return; 491 } 492 493 // $(foo) -> $(foo) 494 // Not sure if I want to support this or not, so for now we just pass 495 // it through. 496 if (*src == '(') { 497 *dst++ = '$'; 498 count = 1; 499 while (count > 0) { 500 if (*src == ')') 501 count--; 502 else if (*src == '(') 503 count++; 504 *dst++ = *src++; 505 } 506 return; 507 } 508 509 // ${^A-Za-z] -> $\1 510 if (!isalpha(*src)) { 511 *dst++ = '$'; 512 *dst++ = *src++; 513 return; 514 } 515 516 // $var -> replace with value 517 var = src++; 518 while (is_id_char(*src)) 519 src++; 520 memcpy(buffer, var, src - var); 521 buffer[src - var] = '\0'; 522 varstr = get_variable(buffer); 523 strcpy(dst, varstr.c_str()); 524 dst += strlen(dst); 525} 526 527const string 528config::expand_string(const string &s) 529{ 530 const char *src; 531 char *dst; 532 char buffer[1024]; 533 534 src = s.c_str(); 535 dst = buffer; 536 while (*src) { 537 if (*src == '$') 538 expand_one(src, dst, buffer + sizeof(buffer)); 539 else 540 *dst++ = *src++; 541 } 542 *dst++ = '\0'; 543 544 return (buffer); 545} 546 547bool 548config::chop_var(char *&buffer, char *&lhs, char *&rhs) 549{ 550 char *walker; 551 552 if (*buffer == '\0') 553 return (false); 554 walker = lhs = buffer; 555 while (is_id_char(*walker)) 556 walker++; 557 if (*walker != '=') 558 return (false); 559 walker++; // skip = 560 if (*walker == '"') { 561 walker++; // skip " 562 rhs = walker; 563 while (*walker && *walker != '"') 564 walker++; 565 if (*walker != '"') 566 return (false); 567 rhs[-2] = '\0'; 568 *walker++ = '\0'; 569 } else { 570 rhs = walker; 571 while (*walker && !isspace(*walker)) 572 walker++; 573 if (*walker != '\0') 574 *walker++ = '\0'; 575 rhs[-1] = '\0'; 576 } 577 while (isspace(*walker)) 578 walker++; 579 buffer = walker; 580 return (true); 581} 582 583 584char * 585config::set_vars(char *buffer) 586{ 587 char *lhs; 588 char *rhs; 589 590 while (1) { 591 if (!chop_var(buffer, lhs, rhs)) 592 break; 593 set_variable(lhs, rhs); 594 } 595 return (buffer); 596} 597 598void 599config::find_and_execute(char type) 600{ 601 vector<event_proc *> *l; 602 vector<event_proc *>::const_iterator i; 603 char *s; 604 605 switch (type) { 606 default: 607 return; 608 case nomatch: 609 l = &_nomatch_list; 610 s = "nomatch"; 611 break; 612 case attach: 613 l = &_attach_list; 614 s = "attach"; 615 break; 616 case detach: 617 l = &_detach_list; 618 s = "detach"; 619 break; 620 } 621 if (dflag) 622 fprintf(stderr, "Processing %s event\n", s); 623 for (i = l->begin(); i != l->end(); i++) { 624 if ((*i)->matches(*this)) { 625 (*i)->run(*this); 626 break; 627 } 628 } 629 630} 631 632 633static void 634process_event(char *buffer) 635{ 636 char type; 637 char *sp; 638 639 sp = buffer + 1; 640 if (dflag) 641 fprintf(stderr, "Processing event '%s'\n", buffer); 642 type = *buffer++; 643 cfg.push_var_table(); 644 // No match doesn't have a device, and the format is a little 645 // different, so handle it separately. 646 if (type != nomatch) { 647 sp = strchr(sp, ' '); 648 if (sp == NULL) 649 return; /* Can't happen? */ 650 *sp++ = '\0'; 651 cfg.set_variable("device-name", buffer); 652 if (strncmp(sp, "at ", 3) == 0) 653 sp += 3; 654 sp = cfg.set_vars(sp); 655 if (strncmp(sp, "on ", 3) == 0) 656 cfg.set_variable("bus", sp + 3); 657 } else { 658 //?vars at location on bus 659 sp = cfg.set_vars(sp); 660 if (strncmp(sp, "at ", 3) == 0) 661 sp += 3; 662 sp = cfg.set_vars(sp); 663 if (strncmp(sp, "on ", 3) == 0) 664 cfg.set_variable("bus", sp + 3); 665 } 666 667 cfg.find_and_execute(type); 668 cfg.pop_var_table(); 669} 670 671static void 672event_loop(void) 673{ 674 int rv; 675 int fd; 676 char buffer[DEVCTL_MAXBUF]; 677 678 fd = open(PATH_DEVCTL, O_RDONLY); 679 if (fd == -1) 680 err(1, "Can't open devctl"); 681 if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) 682 err(1, "Can't set close-on-exec flag"); 683 while (1) { 684 if (romeo_must_die) 685 break; 686 rv = read(fd, buffer, sizeof(buffer) - 1); 687 if (rv > 0) { 688 buffer[rv] = '\0'; 689 while (buffer[--rv] == '\n') 690 buffer[rv] = '\0'; 691 process_event(buffer); 692 } else if (rv < 0) { 693 if (errno != EINTR) 694 break; 695 } else { 696 /* EOF */ 697 break; 698 } 699 } 700 close(fd); 701} 702 703/* 704 * functions that the parser uses. 705 */ 706void 707add_attach(int prio, event_proc *p) 708{ 709 cfg.add_attach(prio, p); 710} 711 712void 713add_detach(int prio, event_proc *p) 714{ 715 cfg.add_detach(prio, p); 716} 717 718void 719add_directory(const char *dir) 720{ 721 cfg.add_directory(dir); 722 free(const_cast<char *>(dir)); 723} 724 725void 726add_nomatch(int prio, event_proc *p) 727{ 728 cfg.add_nomatch(prio, p); 729} 730 731event_proc * 732add_to_event_proc(event_proc *ep, eps *eps) 733{ 734 if (ep == NULL) 735 ep = new event_proc(); 736 ep->add(eps); 737 return (ep); 738} 739 740eps * 741new_action(const char *cmd) 742{ 743 eps *e = new action(cmd); 744 free(const_cast<char *>(cmd)); 745 return (e); 746} 747 748eps * 749new_match(const char *var, const char *re) 750{ 751 eps *e = new match(cfg, var, re); 752 free(const_cast<char *>(var)); 753 free(const_cast<char *>(re)); 754 return (e); 755} 756 757void 758set_pidfile(const char *name) 759{ 760 cfg.set_pidfile(name); 761 free(const_cast<char *>(name)); 762} 763 764void 765set_variable(const char *var, const char *val) 766{ 767 cfg.set_variable(var, val); 768 free(const_cast<char *>(var)); 769 free(const_cast<char *>(val)); 770} 771 772 773 774static void 775gensighand(int) 776{ 777 romeo_must_die++; 778 _exit(0); 779} 780 781static void 782usage() 783{ 784 fprintf(stderr, "usage: %s [-d]\n", getprogname()); 785 exit(1); 786} 787 788static void 789check_devd_enabled() 790{ 791 int val = 0; 792 size_t len; 793 794 len = sizeof(val); 795 if (sysctlbyname(SYSCTL, &val, &len, NULL, NULL) != 0) 796 errx(1, "devctl sysctl missing from kernel!"); 797 if (val) { 798 warnx("Setting " SYSCTL " to 0"); 799 val = 0; 800 sysctlbyname(SYSCTL, NULL, NULL, &val, sizeof(val)); 801 } 802} 803 804/* 805 * main 806 */ 807int 808main(int argc, char **argv) 809{ 810 int ch; 811 812 check_devd_enabled(); 813 while ((ch = getopt(argc, argv, "d")) != -1) { 814 switch (ch) { 815 case 'd': 816 dflag++; 817 break; 818 default: 819 usage(); 820 } 821 } 822 823 cfg.parse(); 824 if (!dflag) 825 daemon(0, 0); 826 cfg.drop_pidfile(); 827 signal(SIGHUP, gensighand); 828 signal(SIGINT, gensighand); 829 signal(SIGTERM, gensighand); 830 event_loop(); 831 return (0); 832} 833