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