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