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