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