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