devd.cc revision 114086
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 114086 2003-04-26 20:59:04Z 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			// == 2 is a kernel bug, but we hang if we don't
594			// make allowances for a while.
595			if (rv == 0 || rv == 2) {
596				if (Dflag)
597					fprintf(stderr, "Calling daemon\n");
598				daemon(0, 0);
599				once++;
600			}
601		}
602		rv = read(fd, buffer, sizeof(buffer) - 1);
603		if (rv > 0) {
604			buffer[rv] = '\0';
605			while (buffer[--rv] == '\n')
606				buffer[rv] = '\0';
607			process_event(buffer);
608		} else if (rv < 0) {
609			if (errno != EINTR)
610				break;
611		} else {
612			/* EOF */
613			break;
614		}
615	}
616	close(fd);
617}
618
619/*
620 * functions that the parser uses.
621 */
622void
623add_attach(int prio, event_proc *p)
624{
625	cfg.add_attach(prio, p);
626}
627
628void
629add_detach(int prio, event_proc *p)
630{
631	cfg.add_detach(prio, p);
632}
633
634void
635add_directory(const char *dir)
636{
637	cfg.add_directory(dir);
638	free(const_cast<char *>(dir));
639}
640
641void
642add_nomatch(int prio, event_proc *p)
643{
644	cfg.add_nomatch(prio, p);
645}
646
647event_proc *
648add_to_event_proc(event_proc *ep, eps *eps)
649{
650	if (ep == NULL)
651		ep = new event_proc();
652	ep->add(eps);
653	return (ep);
654}
655
656eps *
657new_action(const char *cmd)
658{
659	eps *e = new action(cmd);
660	free(const_cast<char *>(cmd));
661	return (e);
662}
663
664eps *
665new_match(const char *var, const char *re)
666{
667	eps *e = new match(cfg, var, re);
668	free(const_cast<char *>(var));
669	free(const_cast<char *>(re));
670	return (e);
671}
672
673void
674set_pidfile(const char *name)
675{
676	cfg.set_pidfile(name);
677	free(const_cast<char *>(name));
678}
679
680void
681set_variable(const char *var, const char *val)
682{
683	cfg.set_variable(var, val);
684	free(const_cast<char *>(var));
685	free(const_cast<char *>(val));
686}
687
688
689
690static void
691gensighand(int)
692{
693	romeo_must_die++;
694	_exit(0);
695}
696
697static void
698usage()
699{
700	fprintf(stderr, "usage: %s [-d]\n", getprogname());
701	exit(1);
702}
703
704static void
705check_devd_enabled()
706{
707	int val = 0;
708	size_t len;
709
710	len = sizeof(val);
711	if (sysctlbyname(SYSCTL, &val, &len, NULL, NULL) != 0)
712		errx(1, "devctl sysctl missing from kernel!");
713	if (val) {
714		warnx("Setting " SYSCTL " to 0");
715		val = 0;
716		sysctlbyname(SYSCTL, NULL, NULL, &val, sizeof(val));
717	}
718}
719
720/*
721 * main
722 */
723int
724main(int argc, char **argv)
725{
726	int ch;
727
728	check_devd_enabled();
729	while ((ch = getopt(argc, argv, "Ddn")) != -1) {
730		switch (ch) {
731		case 'D':
732			Dflag++;
733			break;
734		case 'd':
735			dflag++;
736			break;
737		case 'n':
738			nflag++;
739			break;
740		default:
741			usage();
742		}
743	}
744
745	cfg.parse();
746	if (!dflag && nflag)
747		daemon(0, 0);
748	cfg.drop_pidfile();
749	signal(SIGHUP, gensighand);
750	signal(SIGINT, gensighand);
751	signal(SIGTERM, gensighand);
752	event_loop();
753	return (0);
754}
755