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