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