xref: /freebsd-12.1/sbin/devd/devd.cc (revision 8d5d039f)
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