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