xref: /freebsd-13.1/sbin/devd/devd.cc (revision 463cfa80)
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