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, "%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 //?vars at location on bus 567 sp = cfg.set_vars(sp); 568 if (strncmp(sp, "at ", 3) == 0) 569 sp += 3; 570 sp = cfg.set_vars(sp); 571 if (strncmp(sp, "on ", 3) == 0) 572 cfg.set_variable("bus", sp + 3); 573 break; 574 case attach: /*FALLTHROUGH*/ 575 case detach: 576 sp = strchr(sp, ' '); 577 if (sp == NULL) 578 return; /* Can't happen? */ 579 *sp++ = '\0'; 580 cfg.set_variable("device-name", buffer); 581 if (strncmp(sp, "at ", 3) == 0) 582 sp += 3; 583 sp = cfg.set_vars(sp); 584 if (strncmp(sp, "on ", 3) == 0) 585 cfg.set_variable("bus", sp + 3); 586 break; 587 } 588 589 cfg.find_and_execute(type); 590 cfg.pop_var_table(); 591 } 592 593 int 594 create_socket(const char *name) 595 { 596 int fd, slen; 597 struct sockaddr_un sun; 598 599 if ((fd = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) 600 err(1, "socket"); 601 bzero(&sun, sizeof(sun)); 602 sun.sun_family = AF_UNIX; 603 strlcpy(sun.sun_path, name, sizeof(sun.sun_path)); 604 slen = SUN_LEN(&sun); 605 unlink(name); 606 if (bind(fd, (struct sockaddr *) & sun, slen) < 0) 607 err(1, "bind"); 608 listen(fd, 4); 609 fchown(fd, 0, 0); /* XXX - root.wheel */ 610 fchmod(fd, 0660); 611 return (fd); 612 } 613 614 list<int> clients; 615 616 void 617 notify_clients(const char *data, int len) 618 { 619 list<int> bad; 620 list<int>::const_iterator i; 621 622 for (i = clients.begin(); i != clients.end(); i++) { 623 if (write(*i, data, len) <= 0) { 624 bad.push_back(*i); 625 close(*i); 626 } 627 } 628 629 for (i = bad.begin(); i != bad.end(); i++) 630 clients.erase(find(clients.begin(), clients.end(), *i)); 631 } 632 633 void 634 new_client(int fd) 635 { 636 int s; 637 638 s = accept(fd, NULL, NULL); 639 if (s != -1) 640 clients.push_back(s); 641 } 642 643 static void 644 event_loop(void) 645 { 646 int rv; 647 int fd; 648 char buffer[DEVCTL_MAXBUF]; 649 int once = 0; 650 int server_fd, max_fd; 651 timeval tv; 652 fd_set fds; 653 654 fd = open(PATH_DEVCTL, O_RDONLY); 655 if (fd == -1) 656 err(1, "Can't open devctl device %s", PATH_DEVCTL); 657 if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) 658 err(1, "Can't set close-on-exec flag on devctl"); 659 server_fd = create_socket(PIPE); 660 max_fd = max(fd, server_fd) + 1; 661 while (1) { 662 if (romeo_must_die) 663 break; 664 if (!once && !dflag && !nflag) { 665 // Check to see if we have any events pending. 666 tv.tv_sec = 0; 667 tv.tv_usec = 0; 668 FD_ZERO(&fds); 669 FD_SET(fd, &fds); 670 rv = select(fd + 1, &fds, &fds, &fds, &tv); 671 // No events -> we've processed all pending events 672 if (rv == 0) { 673 if (Dflag) 674 fprintf(stderr, "Calling daemon\n"); 675 daemon(0, 0); 676 cfg.drop_pidfile(); 677 once++; 678 } 679 } 680 FD_ZERO(&fds); 681 FD_SET(fd, &fds); 682 FD_SET(server_fd, &fds); 683 rv = select(max_fd, &fds, NULL, NULL, NULL); 684 if (rv == -1) { 685 if (errno == EINTR) 686 continue; 687 err(1, "select"); 688 } 689 if (FD_ISSET(fd, &fds)) { 690 rv = read(fd, buffer, sizeof(buffer) - 1); 691 if (rv > 0) { 692 notify_clients(buffer, rv); 693 buffer[rv] = '\0'; 694 while (buffer[--rv] == '\n') 695 buffer[rv] = '\0'; 696 process_event(buffer); 697 } else if (rv < 0) { 698 if (errno != EINTR) 699 break; 700 } else { 701 /* EOF */ 702 break; 703 } 704 } 705 if (FD_ISSET(server_fd, &fds)) 706 new_client(server_fd); 707 } 708 close(fd); 709 } 710 711 /* 712 * functions that the parser uses. 713 */ 714 void 715 add_attach(int prio, event_proc *p) 716 { 717 cfg.add_attach(prio, p); 718 } 719 720 void 721 add_detach(int prio, event_proc *p) 722 { 723 cfg.add_detach(prio, p); 724 } 725 726 void 727 add_directory(const char *dir) 728 { 729 cfg.add_directory(dir); 730 free(const_cast<char *>(dir)); 731 } 732 733 void 734 add_nomatch(int prio, event_proc *p) 735 { 736 cfg.add_nomatch(prio, p); 737 } 738 739 void 740 add_notify(int prio, event_proc *p) 741 { 742 cfg.add_notify(prio, p); 743 } 744 745 event_proc * 746 add_to_event_proc(event_proc *ep, eps *eps) 747 { 748 if (ep == NULL) 749 ep = new event_proc(); 750 ep->add(eps); 751 return (ep); 752 } 753 754 eps * 755 new_action(const char *cmd) 756 { 757 eps *e = new action(cmd); 758 free(const_cast<char *>(cmd)); 759 return (e); 760 } 761 762 eps * 763 new_match(const char *var, const char *re) 764 { 765 eps *e = new match(cfg, var, re); 766 free(const_cast<char *>(var)); 767 free(const_cast<char *>(re)); 768 return (e); 769 } 770 771 void 772 set_pidfile(const char *name) 773 { 774 cfg.set_pidfile(name); 775 free(const_cast<char *>(name)); 776 } 777 778 void 779 set_variable(const char *var, const char *val) 780 { 781 cfg.set_variable(var, val); 782 free(const_cast<char *>(var)); 783 free(const_cast<char *>(val)); 784 } 785 786 787 788 static void 789 gensighand(int) 790 { 791 romeo_must_die++; 792 _exit(0); 793 } 794 795 static void 796 usage() 797 { 798 fprintf(stderr, "usage: %s [-d]\n", getprogname()); 799 exit(1); 800 } 801 802 static void 803 check_devd_enabled() 804 { 805 int val = 0; 806 size_t len; 807 808 len = sizeof(val); 809 if (sysctlbyname(SYSCTL, &val, &len, NULL, 0) != 0) 810 errx(1, "devctl sysctl missing from kernel!"); 811 if (val) { 812 warnx("Setting " SYSCTL " to 0"); 813 val = 0; 814 sysctlbyname(SYSCTL, NULL, NULL, &val, sizeof(val)); 815 } 816 } 817 818 /* 819 * main 820 */ 821 int 822 main(int argc, char **argv) 823 { 824 int ch; 825 826 check_devd_enabled(); 827 while ((ch = getopt(argc, argv, "Ddn")) != -1) { 828 switch (ch) { 829 case 'D': 830 Dflag++; 831 break; 832 case 'd': 833 dflag++; 834 break; 835 case 'n': 836 nflag++; 837 break; 838 default: 839 usage(); 840 } 841 } 842 843 cfg.parse(); 844 if (!dflag && nflag) { 845 daemon(0, 0); 846 cfg.drop_pidfile(); 847 } 848 signal(SIGHUP, gensighand); 849 signal(SIGINT, gensighand); 850 signal(SIGTERM, gensighand); 851 event_loop(); 852 return (0); 853 } 854