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