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 rewrite the main loop: 33 // - find best match 34 // - execute it. 35 // o need to insert the event_proc structures in order of priority. 36 // bigger numbers mean higher priority. 37 // o devd.conf and devd man pages need a lot of help: 38 // - devd.conf needs to lose the warning about zone files. 39 // - devd.conf needs more details on the supported statements. 40 // - devd.conf needs an example or two. 41 42 #include <sys/cdefs.h> 43 __FBSDID("$FreeBSD$"); 44 45 #include <sys/param.h> 46 #include <sys/types.h> 47 48 #include <ctype.h> 49 #include <dirent.h> 50 #include <errno.h> 51 #include <err.h> 52 #include <fcntl.h> 53 #include <regex.h> 54 #include <stdlib.h> 55 #include <stdio.h> 56 #include <string.h> 57 #include <unistd.h> 58 59 #include <map> 60 #include <string> 61 #include <vector> 62 63 #include "devd.h" 64 65 #define CF "/etc/devd.conf" 66 67 using namespace std; 68 69 extern FILE *yyin; 70 extern int lineno; 71 72 int dflag; 73 int romeo_must_die = 0; 74 75 static void event_loop(void); 76 static void usage(void); 77 78 class config; 79 80 81 class var_list 82 { 83 public: 84 var_list() {} 85 virtual ~var_list() {} 86 void set_variable(const string &var, const string &val); 87 const string &get_variable(const string &var) const; 88 bool is_set(const string &var) const; 89 static const string bogus; 90 static const string nothing; 91 private: 92 map<string, string> _vars; 93 }; 94 95 class eps 96 { 97 public: 98 eps() {} 99 virtual ~eps() {} 100 virtual bool do_match(config &) = 0; 101 virtual bool do_action(config &) = 0; 102 }; 103 104 class match : public eps 105 { 106 public: 107 match(config &, const char *var, const char *re); 108 virtual ~match(); 109 virtual bool do_match(config &); 110 virtual bool do_action(config &) { return true; } 111 private: 112 string _var; 113 string _re; 114 regex_t _regex; 115 }; 116 117 class action : public eps 118 { 119 public: 120 action(const char *cmd); 121 virtual ~action(); 122 virtual bool do_match(config &) { return true; } 123 virtual bool do_action(config &); 124 private: 125 string _cmd; 126 }; 127 128 class event_proc 129 { 130 public: 131 event_proc(); 132 virtual ~event_proc(); 133 int get_priority() { return (_prio); } 134 void set_priority(int prio) { _prio = prio; } 135 void add(eps *); 136 bool matches(config &); 137 bool run(config &); 138 private: 139 int _prio; 140 vector<eps *> _epsvec; 141 }; 142 143 class config 144 { 145 public: 146 config() : _pidfile("") { push_var_table(); } 147 virtual ~config() { reset(); } 148 void add_attach(int, event_proc *); 149 void add_detach(int, event_proc *); 150 void add_directory(const char *); 151 void add_nomatch(int, event_proc *); 152 void set_pidfile(const char *); 153 void reset(); 154 void parse(); 155 void drop_pidfile(); 156 void push_var_table(); 157 void pop_var_table(); 158 void set_variable(const char *var, const char *val); 159 const string &get_variable(const string &var); 160 const string expand_string(const string &var); 161 protected: 162 void parse_one_file(const char *fn); 163 void parse_files_in_dir(const char *dirname); 164 void expand_one(const char *&src, char *&dst, char *eod); 165 private: 166 vector<string> _dir_list; 167 string _pidfile; 168 vector<var_list *> _var_list_table; 169 vector<event_proc *> _attach_list; 170 vector<event_proc *> _detach_list; 171 vector<event_proc *> _nomatch_list; 172 }; 173 174 config cfg; 175 176 event_proc::event_proc() : _prio(-1) 177 { 178 // nothing 179 } 180 181 event_proc::~event_proc() 182 { 183 vector<eps *>::const_iterator i; 184 185 for (i = _epsvec.begin(); i != _epsvec.end(); i++) 186 delete *i; 187 _epsvec.clear(); 188 } 189 190 void 191 event_proc::add(eps *eps) 192 { 193 _epsvec.push_back(eps); 194 } 195 196 bool 197 event_proc::matches(config &c) 198 { 199 vector<eps *>::const_iterator i; 200 201 for (i = _epsvec.begin(); i != _epsvec.end(); i++) 202 if (!(*i)->do_match(c)) 203 return (false); 204 return (true); 205 } 206 207 bool 208 event_proc::run(config &c) 209 { 210 vector<eps *>::const_iterator i; 211 212 for (i = _epsvec.begin(); i != _epsvec.end(); i++) 213 if (!(*i)->do_action(c)) 214 return (false); 215 return (true); 216 } 217 218 action::action(const char *cmd) 219 : _cmd(cmd) 220 { 221 // nothing 222 } 223 224 action::~action() 225 { 226 // nothing 227 } 228 229 bool 230 action::do_action(config &c) 231 { 232 // xxx 233 ::system(c.expand_string(_cmd).c_str()); 234 return (true); 235 } 236 237 match::match(config &c, const char *var, const char *re) 238 : _var(var), _re(re) 239 { 240 string pattern = "^"; 241 pattern.append(c.expand_string(_re)); 242 pattern.append("$"); 243 regcomp(&_regex, pattern.c_str(), REG_EXTENDED | REG_NOSUB); 244 } 245 246 match::~match() 247 { 248 regfree(&_regex); 249 } 250 251 bool 252 match::do_match(config &c) 253 { 254 string value = c.get_variable(_var); 255 bool retval; 256 257 retval = (regexec(&_regex, value.c_str(), 0, NULL, 0) == 0); 258 return retval; 259 } 260 261 const string var_list::bogus = "_$_$_$_$_B_O_G_U_S_$_$_$_$_"; 262 const string var_list::nothing = ""; 263 264 const string & 265 var_list::get_variable(const string &var) const 266 { 267 map<string, string>::const_iterator i; 268 269 i = _vars.find(var); 270 if (i == _vars.end()) 271 return var_list::bogus; 272 return (i->second); 273 } 274 275 bool 276 var_list::is_set(const string &var) const 277 { 278 return (_vars.find(var) != _vars.end()); 279 } 280 281 void 282 var_list::set_variable(const string &var, const string &val) 283 { 284 _vars[var] = val; 285 } 286 287 void 288 config::reset(void) 289 { 290 _dir_list.clear(); 291 _var_list_table.clear(); 292 // XXX need to cleanup _{attach,detach,nomatch}_list 293 } 294 295 void 296 config::parse_one_file(const char *fn) 297 { 298 if (dflag) 299 printf("Parsing %s\n", fn); 300 yyin = fopen(fn, "r"); 301 if (yyin == NULL) 302 err(1, "Cannot open config file %s", fn); 303 if (yyparse() != 0) 304 errx(1, "Cannot parse %s at line %d", fn, lineno); 305 fclose(yyin); 306 } 307 308 void 309 config::parse_files_in_dir(const char *dirname) 310 { 311 DIR *dirp; 312 struct dirent *dp; 313 char path[PATH_MAX]; 314 315 if (dflag) 316 printf("Parsing files in %s\n", dirname); 317 dirp = opendir(dirname); 318 if (dirp == NULL) 319 return; 320 readdir(dirp); /* Skip . */ 321 readdir(dirp); /* Skip .. */ 322 while ((dp = readdir(dirp)) != NULL) { 323 if (strcmp(dp->d_name + dp->d_namlen - 5, ".conf") == 0) { 324 snprintf(path, sizeof(path), "%s/%s", 325 dirname, dp->d_name); 326 parse_one_file(path); 327 } 328 } 329 } 330 331 void 332 config::parse(void) 333 { 334 vector<string>::const_iterator i; 335 336 parse_one_file(CF); 337 for (i = _dir_list.begin(); i != _dir_list.end(); i++) 338 parse_files_in_dir((*i).c_str()); 339 } 340 341 void 342 config::drop_pidfile() 343 { 344 FILE *fp; 345 346 if (_pidfile == "") 347 return; 348 fp = fopen(_pidfile.c_str(), "w"); 349 if (fp == NULL) 350 return; 351 fprintf(fp, "%d\n", getpid()); 352 fclose(fp); 353 } 354 355 void 356 config::add_attach(int prio, event_proc *p) 357 { 358 p->set_priority(prio); 359 _attach_list.push_back(p); 360 } 361 362 void 363 config::add_detach(int prio, event_proc *p) 364 { 365 p->set_priority(prio); 366 _detach_list.push_back(p); 367 } 368 369 void 370 config::add_directory(const char *dir) 371 { 372 _dir_list.push_back(string(dir)); 373 } 374 375 void 376 config::add_nomatch(int prio, event_proc *p) 377 { 378 p->set_priority(prio); 379 _nomatch_list.push_back(p); 380 } 381 382 void 383 config::set_pidfile(const char *fn) 384 { 385 _pidfile = string(fn); 386 } 387 388 void 389 config::push_var_table() 390 { 391 var_list *vl; 392 393 vl = new var_list(); 394 _var_list_table.push_back(vl); 395 } 396 397 void 398 config::pop_var_table() 399 { 400 delete _var_list_table.back(); 401 _var_list_table.pop_back(); 402 } 403 404 void 405 config::set_variable(const char *var, const char *val) 406 { 407 _var_list_table.back()->set_variable(var, val); 408 } 409 410 const string & 411 config::get_variable(const string &var) 412 { 413 vector<var_list *>::reverse_iterator i; 414 415 for (i = _var_list_table.rbegin(); i != _var_list_table.rend(); i++) { 416 if ((*i)->is_set(var)) 417 return (var); 418 } 419 return (var_list::nothing); 420 } 421 422 // Hey script |<idz, here's a routine chock-full-o-buffer-overflows. 423 // XXX 424 // imp should learn how to make effective use of the string class for the shit. 425 void 426 config::expand_one(const char *&src, char *&dst, char *) 427 { 428 int count; 429 const char *var; 430 char buffer[1024]; 431 string varstr; 432 433 // $$ -> $ 434 if (*src == '$') { 435 *dst++ = *src++; 436 return; 437 } 438 439 // $(foo) -> $(foo) 440 // Not sure if I want to support this or not, so for now we just pass it through. 441 if (*src == '(') { 442 *dst++ = '$'; 443 count = 1; 444 while (count > 0) { 445 if (*src == ')') 446 count--; 447 else if (*src == '(') 448 count++; 449 *dst++ = *src++; 450 } 451 return; 452 } 453 454 // ${^A-Za-z] -> $\1 455 if (!isalpha(*src)) { 456 *dst++ = '$'; 457 *dst++ = *src++; 458 return; 459 } 460 461 // $var -> replace with value 462 var = src++; 463 while (*src && isalpha(*src) || isdigit(*src) || *src == '_' || *src == '-') 464 src++; 465 memcpy(buffer, var, src - var); 466 buffer[src - var] = '\0'; 467 varstr = get_variable(buffer); 468 strcpy(dst, varstr.c_str()); 469 dst += strlen(dst); 470 } 471 472 const string 473 config::expand_string(const string &s) 474 { 475 const char *src; 476 char *dst; 477 char buffer[1024]; 478 479 src = s.c_str(); 480 dst = buffer; 481 while (*src) { 482 if (*src == '$') 483 expand_one(++src, dst, buffer + sizeof(buffer)); 484 else 485 *dst++ = *src++; 486 } 487 *dst++ = '\0'; 488 489 return (buffer); 490 } 491 492 493 static void 494 process_event(const char *buffer) 495 { 496 char type; 497 char cmd[1024]; 498 char *sp; 499 500 // XXX should involve config 501 // XXX and set some variables 502 // XXX run the list and so forth 503 504 // Ignore unknown devices for now. 505 if (*buffer == '?') 506 return; 507 type = *buffer++; 508 sp = strchr(buffer, ' '); 509 if (sp == NULL) 510 return; /* Can't happen? */ 511 *sp = '\0'; 512 snprintf(cmd, sizeof(cmd), "/etc/devd-generic %s %s", buffer, 513 type == '+' ? "start" : "stop"); 514 if (dflag) 515 printf("Trying '%s'\n", cmd); 516 system(cmd); 517 } 518 519 static void 520 event_loop(void) 521 { 522 int rv; 523 int fd; 524 char buffer[DEVCTL_MAXBUF]; 525 526 fd = open(PATH_DEVCTL, O_RDONLY); 527 if (fd == -1) 528 err(1, "Can't open devctl"); 529 if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) 530 err(1, "Can't set close-on-exec flag"); 531 while (1) { 532 if (romeo_must_die) 533 break; 534 rv = read(fd, buffer, sizeof(buffer) - 1); 535 if (rv > 0) { 536 buffer[rv] = '\0'; 537 while (buffer[--rv] == '\n') 538 buffer[rv] = '\0'; 539 process_event(buffer); 540 } else if (rv < 0) { 541 if (errno != EINTR) 542 break; 543 } else { 544 /* EOF */ 545 break; 546 } 547 } 548 close(fd); 549 } 550 551 /* 552 * functions that the parser uses. 553 */ 554 void 555 add_attach(int prio, event_proc *p) 556 { 557 cfg.add_attach(prio, p); 558 } 559 560 void 561 add_detach(int prio, event_proc *p) 562 { 563 cfg.add_detach(prio, p); 564 } 565 566 void 567 add_directory(const char *dir) 568 { 569 cfg.add_directory(dir); 570 free(const_cast<char *>(dir)); 571 } 572 573 void 574 add_nomatch(int prio, event_proc *p) 575 { 576 cfg.add_nomatch(prio, p); 577 } 578 579 event_proc * 580 add_to_event_proc(event_proc *ep, eps *eps) 581 { 582 if (ep == NULL) 583 ep = new event_proc(); 584 ep->add(eps); 585 return (ep); 586 } 587 588 eps * 589 new_action(const char *cmd) 590 { 591 eps *e = new action(cmd); 592 free(const_cast<char *>(cmd)); 593 return (e); 594 } 595 596 eps * 597 new_match(const char *var, const char *re) 598 { 599 eps *e = new match(cfg, var, re); 600 free(const_cast<char *>(var)); 601 free(const_cast<char *>(re)); 602 return (e); 603 } 604 605 void 606 set_pidfile(const char *name) 607 { 608 cfg.set_pidfile(name); 609 free(const_cast<char *>(name)); 610 } 611 612 void 613 set_variable(const char *var, const char *val) 614 { 615 cfg.set_variable(var, val); 616 free(const_cast<char *>(var)); 617 free(const_cast<char *>(val)); 618 } 619 620 621 622 static void 623 gensighand(int) 624 { 625 romeo_must_die++; 626 _exit(0); 627 } 628 629 static void 630 usage() 631 { 632 fprintf(stderr, "usage: %s [-d]", getprogname()); 633 exit(1); 634 } 635 636 /* 637 * main 638 */ 639 int 640 main(int argc, char **argv) 641 { 642 int ch; 643 644 while ((ch = getopt(argc, argv, "d")) != -1) { 645 switch (ch) { 646 case 'd': 647 dflag++; 648 break; 649 default: 650 usage(); 651 } 652 } 653 654 cfg.parse(); 655 if (!dflag) 656 daemon(0, 0); 657 cfg.drop_pidfile(); 658 signal(SIGHUP, gensighand); 659 signal(SIGINT, gensighand); 660 signal(SIGTERM, gensighand); 661 event_loop(); 662 return (0); 663 } 664