devd.cc revision 113787
178381Snyan/*-
278381Snyan * Copyright (c) 2002-2003 M. Warner Losh.
378381Snyan * All rights reserved.
478381Snyan *
578381Snyan * Redistribution and use in source and binary forms, with or without
678381Snyan * modification, are permitted provided that the following conditions
778381Snyan * are met:
878381Snyan * 1. Redistributions of source code must retain the above copyright
978381Snyan *    notice, this list of conditions and the following disclaimer.
1078381Snyan * 2. Redistributions in binary form must reproduce the above copyright
1178381Snyan *    notice, this list of conditions and the following disclaimer in the
1278381Snyan *    documentation and/or other materials provided with the distribution.
1378381Snyan *
1478381Snyan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1578381Snyan * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1678381Snyan * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1778381Snyan * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1878381Snyan * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1978381Snyan * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2078381Snyan * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2178381Snyan * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2278381Snyan * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2378381Snyan * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2478381Snyan * SUCH DAMAGE.
2578381Snyan */
2678381Snyan
2778381Snyan/*
2878381Snyan * DEVD control daemon.
2978381Snyan */
3078381Snyan
3178381Snyan// TODO list:
3278381Snyan//	o devd.conf and devd man pages need a lot of help:
3378381Snyan//	  - devd.conf needs to lose the warning about zone files.
3478381Snyan//	  - devd.conf needs more details on the supported statements.
3578381Snyan
3678381Snyan#include <sys/cdefs.h>
3778381Snyan__FBSDID("$FreeBSD: head/sbin/devd/devd.cc 113787 2003-04-21 04:30:12Z imp $");
3878381Snyan
3978381Snyan#include <sys/param.h>
4078381Snyan#include <sys/types.h>
4178381Snyan#include <sys/sysctl.h>
4278381Snyan
4378381Snyan#include <ctype.h>
4478381Snyan#include <dirent.h>
4578381Snyan#include <errno.h>
4678381Snyan#include <err.h>
4778381Snyan#include <fcntl.h>
4878381Snyan#include <regex.h>
4978381Snyan#include <stdlib.h>
5078381Snyan#include <stdio.h>
5178381Snyan#include <string.h>
5278381Snyan#include <unistd.h>
5378381Snyan
5478381Snyan#include <algorithm>
5578381Snyan#include <map>
5678381Snyan#include <string>
5778381Snyan#include <vector>
5878381Snyan
5978381Snyan#include "devd.h"
6078381Snyan
6178381Snyan#define CF "/etc/devd.conf"
6278381Snyan#define SYSCTL "hw.bus.devctl_disable"
6378381Snyan
6478381Snyanusing namespace std;
6578381Snyan
6678381Snyanextern FILE *yyin;
6778381Snyanextern int lineno;
6878381Snyan
6978381Snyanstatic const char nomatch = '?';
7078381Snyanstatic const char attach = '+';
7178381Snyanstatic const char detach = '-';
7278381Snyan
7378381Snyanint dflag;
7478381Snyanint romeo_must_die = 0;
7578381Snyan
7678381Snyanstatic void event_loop(void);
7778381Snyanstatic void usage(void);
7878381Snyan
7978381Snyantemplate <class T> void
8078381Snyandelete_and_clear(vector<T *> &v)
8178381Snyan{
8278381Snyan	typename vector<T *>::const_iterator i;
8378381Snyan
8478381Snyan	for (i = v.begin(); i != v.end(); i++)
8578381Snyan		delete *i;
8678381Snyan	v.clear();
8778381Snyan}
8878381Snyan
8978381Snyanclass config;
9078381Snyan
9178381Snyanclass var_list
9278381Snyan{
9378381Snyanpublic:
9478381Snyan	var_list() {}
9578381Snyan	virtual ~var_list() {}
9678381Snyan	void set_variable(const string &var, const string &val);
9778381Snyan	const string &get_variable(const string &var) const;
9878381Snyan	bool is_set(const string &var) const;
9978381Snyan	static const string bogus;
10078381Snyan	static const string nothing;
10178381Snyanprivate:
10278381Snyan	map<string, string> _vars;
10378381Snyan};
10478381Snyan
10578381Snyanclass eps
10678381Snyan{
10778381Snyanpublic:
10878381Snyan	eps() {}
10978381Snyan	virtual ~eps() {}
11078381Snyan	virtual bool do_match(config &) = 0;
11178381Snyan	virtual bool do_action(config &) = 0;
11278381Snyan};
11378381Snyan
11478381Snyanclass match : public eps
11578381Snyan{
11678381Snyanpublic:
11778381Snyan	match(config &, const char *var, const char *re);
11878381Snyan	virtual ~match();
11978381Snyan	virtual bool do_match(config &);
12078381Snyan	virtual bool do_action(config &) { return true; }
12178381Snyanprivate:
12278381Snyan	string _var;
12378381Snyan	string _re;
12478381Snyan	regex_t _regex;
12578381Snyan};
12678381Snyan
12778381Snyanclass action : public eps
12878381Snyan{
12978381Snyanpublic:
13078381Snyan	action(const char *cmd);
13178381Snyan	virtual ~action();
13278381Snyan	virtual bool do_match(config &) { return true; }
13378381Snyan	virtual bool do_action(config &);
13478381Snyanprivate:
135	string _cmd;
136};
137
138class event_proc
139{
140public:
141	event_proc();
142	virtual ~event_proc();
143	int get_priority() const { return (_prio); }
144	void set_priority(int prio) { _prio = prio; }
145	void add(eps *);
146	bool matches(config &);
147	bool run(config &);
148private:
149	int _prio;
150	vector<eps *> _epsvec;
151};
152
153class config
154{
155public:
156	config() : _pidfile("") { push_var_table(); }
157	virtual ~config() { reset(); }
158	void add_attach(int, event_proc *);
159	void add_detach(int, event_proc *);
160	void add_directory(const char *);
161	void add_nomatch(int, event_proc *);
162	void set_pidfile(const char *);
163	void reset();
164	void parse();
165	void drop_pidfile();
166	void push_var_table();
167	void pop_var_table();
168	void set_variable(const char *var, const char *val);
169	const string &get_variable(const string &var);
170	const string expand_string(const string &var);
171	char *set_vars(char *);
172	void find_and_execute(char);
173protected:
174	void sort_vector(vector<event_proc *> &);
175	void parse_one_file(const char *fn);
176	void parse_files_in_dir(const char *dirname);
177	void expand_one(const char *&src, char *&dst, char *eod);
178	bool is_id_char(char);
179	bool chop_var(char *&buffer, char *&lhs, char *&rhs);
180private:
181	vector<string> _dir_list;
182	string _pidfile;
183	vector<var_list *> _var_list_table;
184	vector<event_proc *> _attach_list;
185	vector<event_proc *> _detach_list;
186	vector<event_proc *> _nomatch_list;
187};
188
189config cfg;
190
191event_proc::event_proc() : _prio(-1)
192{
193	// nothing
194}
195
196event_proc::~event_proc()
197{
198	vector<eps *>::const_iterator i;
199
200	for (i = _epsvec.begin(); i != _epsvec.end(); i++)
201		delete *i;
202	_epsvec.clear();
203}
204
205void
206event_proc::add(eps *eps)
207{
208	_epsvec.push_back(eps);
209}
210
211bool
212event_proc::matches(config &c)
213{
214	vector<eps *>::const_iterator i;
215
216	for (i = _epsvec.begin(); i != _epsvec.end(); i++)
217		if (!(*i)->do_match(c))
218			return (false);
219	return (true);
220}
221
222bool
223event_proc::run(config &c)
224{
225	vector<eps *>::const_iterator i;
226
227	for (i = _epsvec.begin(); i != _epsvec.end(); i++)
228		if (!(*i)->do_action(c))
229			return (false);
230	return (true);
231}
232
233action::action(const char *cmd)
234	: _cmd(cmd)
235{
236	// nothing
237}
238
239action::~action()
240{
241	// nothing
242}
243
244bool
245action::do_action(config &c)
246{
247	string s = c.expand_string(_cmd);
248	if (dflag)
249		fprintf(stderr, "Executing '%s'\n", s.c_str());
250	::system(s.c_str());
251	return (true);
252}
253
254match::match(config &c, const char *var, const char *re)
255	: _var(var)
256{
257	string pattern = re;
258	_re = "^";
259	_re.append(c.expand_string(string(re)));
260	_re.append("$");
261	regcomp(&_regex, _re.c_str(), REG_EXTENDED | REG_NOSUB);
262}
263
264match::~match()
265{
266	regfree(&_regex);
267}
268
269bool
270match::do_match(config &c)
271{
272	string value = c.get_variable(_var);
273	bool retval;
274
275	if (dflag)
276		fprintf(stderr, "Testing %s=%s against %s\n", _var.c_str(),
277		    value.c_str(), _re.c_str());
278
279	retval = (regexec(&_regex, value.c_str(), 0, NULL, 0) == 0);
280	return retval;
281}
282
283const string var_list::bogus = "_$_$_$_$_B_O_G_U_S_$_$_$_$_";
284const string var_list::nothing = "";
285
286const string &
287var_list::get_variable(const string &var) const
288{
289	map<string, string>::const_iterator i;
290
291	i = _vars.find(var);
292	if (i == _vars.end())
293		return (var_list::bogus);
294	return (i->second);
295}
296
297bool
298var_list::is_set(const string &var) const
299{
300	return (_vars.find(var) != _vars.end());
301}
302
303void
304var_list::set_variable(const string &var, const string &val)
305{
306	if (dflag)
307		fprintf(stderr, "%s=%s\n", var.c_str(), val.c_str());
308	_vars[var] = val;
309}
310
311void
312config::reset(void)
313{
314	_dir_list.clear();
315	delete_and_clear(_var_list_table);
316	delete_and_clear(_attach_list);
317	delete_and_clear(_detach_list);
318	delete_and_clear(_nomatch_list);
319}
320
321void
322config::parse_one_file(const char *fn)
323{
324	if (dflag)
325		printf("Parsing %s\n", fn);
326	yyin = fopen(fn, "r");
327	if (yyin == NULL)
328		err(1, "Cannot open config file %s", fn);
329	if (yyparse() != 0)
330		errx(1, "Cannot parse %s at line %d", fn, lineno);
331	fclose(yyin);
332}
333
334void
335config::parse_files_in_dir(const char *dirname)
336{
337	DIR *dirp;
338	struct dirent *dp;
339	char path[PATH_MAX];
340
341	if (dflag)
342		printf("Parsing files in %s\n", dirname);
343	dirp = opendir(dirname);
344	if (dirp == NULL)
345		return;
346	readdir(dirp);		/* Skip . */
347	readdir(dirp);		/* Skip .. */
348	while ((dp = readdir(dirp)) != NULL) {
349		if (strcmp(dp->d_name + dp->d_namlen - 5, ".conf") == 0) {
350			snprintf(path, sizeof(path), "%s/%s",
351			    dirname, dp->d_name);
352			parse_one_file(path);
353		}
354	}
355}
356
357class epv_greater {
358public:
359	int operator()(event_proc *const&l1, event_proc *const&l2)
360	{
361		return (l1->get_priority() > l2->get_priority());
362	}
363};
364
365void
366config::sort_vector(vector<event_proc *> &v)
367{
368	sort(v.begin(), v.end(), epv_greater());
369}
370
371void
372config::parse(void)
373{
374	vector<string>::const_iterator i;
375
376	parse_one_file(CF);
377	for (i = _dir_list.begin(); i != _dir_list.end(); i++)
378		parse_files_in_dir((*i).c_str());
379	sort_vector(_attach_list);
380	sort_vector(_detach_list);
381	sort_vector(_nomatch_list);
382}
383
384void
385config::drop_pidfile()
386{
387	FILE *fp;
388
389	if (_pidfile == "")
390		return;
391	fp = fopen(_pidfile.c_str(), "w");
392	if (fp == NULL)
393		return;
394	fprintf(fp, "%d\n", getpid());
395	fclose(fp);
396}
397
398void
399config::add_attach(int prio, event_proc *p)
400{
401	p->set_priority(prio);
402	_attach_list.push_back(p);
403}
404
405void
406config::add_detach(int prio, event_proc *p)
407{
408	p->set_priority(prio);
409	_detach_list.push_back(p);
410}
411
412void
413config::add_directory(const char *dir)
414{
415	_dir_list.push_back(string(dir));
416}
417
418void
419config::add_nomatch(int prio, event_proc *p)
420{
421	p->set_priority(prio);
422	_nomatch_list.push_back(p);
423}
424
425void
426config::set_pidfile(const char *fn)
427{
428	_pidfile = string(fn);
429}
430
431void
432config::push_var_table()
433{
434	var_list *vl;
435
436	vl = new var_list();
437	_var_list_table.push_back(vl);
438	if (dflag)
439		fprintf(stderr, "Pushing table\n");
440}
441
442void
443config::pop_var_table()
444{
445	delete _var_list_table.back();
446	_var_list_table.pop_back();
447	if (dflag)
448		fprintf(stderr, "Popping table\n");
449}
450
451void
452config::set_variable(const char *var, const char *val)
453{
454	_var_list_table.back()->set_variable(var, val);
455}
456
457const string &
458config::get_variable(const string &var)
459{
460	vector<var_list *>::reverse_iterator i;
461
462	for (i = _var_list_table.rbegin(); i != _var_list_table.rend(); i++) {
463		if ((*i)->is_set(var))
464			return ((*i)->get_variable(var));
465	}
466	return (var_list::nothing);
467}
468
469bool
470config::is_id_char(char ch)
471{
472	return (ch != '\0' && (isalpha(ch) || isdigit(ch) || ch == '_' ||
473	    ch == '-'));
474}
475
476// XXX
477// imp should learn how to make effective use of the string class.
478void
479config::expand_one(const char *&src, char *&dst, char *)
480{
481	int count;
482	const char *var;
483	char buffer[1024];
484	string varstr;
485
486	src++;
487	// $$ -> $
488	if (*src == '$') {
489		*dst++ = *src++;
490		return;
491	}
492
493	// $(foo) -> $(foo)
494	// Not sure if I want to support this or not, so for now we just pass
495	// it through.
496	if (*src == '(') {
497		*dst++ = '$';
498		count = 1;
499		while (count > 0) {
500			if (*src == ')')
501				count--;
502			else if (*src == '(')
503				count++;
504			*dst++ = *src++;
505		}
506		return;
507	}
508
509	// ${^A-Za-z] -> $\1
510	if (!isalpha(*src)) {
511		*dst++ = '$';
512		*dst++ = *src++;
513		return;
514	}
515
516	// $var -> replace with value
517	var = src++;
518	while (is_id_char(*src))
519		src++;
520	memcpy(buffer, var, src - var);
521	buffer[src - var] = '\0';
522	varstr = get_variable(buffer);
523	strcpy(dst, varstr.c_str());
524	dst += strlen(dst);
525}
526
527const string
528config::expand_string(const string &s)
529{
530	const char *src;
531	char *dst;
532	char buffer[1024];
533
534	src = s.c_str();
535	dst = buffer;
536	while (*src) {
537		if (*src == '$')
538			expand_one(src, dst, buffer + sizeof(buffer));
539		else
540			*dst++ = *src++;
541	}
542	*dst++ = '\0';
543
544	return (buffer);
545}
546
547bool
548config::chop_var(char *&buffer, char *&lhs, char *&rhs)
549{
550	char *walker;
551
552	if (*buffer == '\0')
553		return (false);
554	walker = lhs = buffer;
555	while (is_id_char(*walker))
556		walker++;
557	if (*walker != '=')
558		return (false);
559	walker++;		// skip =
560	if (*walker == '"') {
561		walker++;	// skip "
562		rhs = walker;
563		while (*walker && *walker != '"')
564			walker++;
565		if (*walker != '"')
566			return (false);
567		rhs[-2] = '\0';
568		*walker++ = '\0';
569	} else {
570		rhs = walker;
571		while (*walker && !isspace(*walker))
572			walker++;
573		if (*walker != '\0')
574			*walker++ = '\0';
575		rhs[-1] = '\0';
576	}
577	while (isspace(*walker))
578		walker++;
579	buffer = walker;
580	return (true);
581}
582
583
584char *
585config::set_vars(char *buffer)
586{
587	char *lhs;
588	char *rhs;
589
590	while (1) {
591		if (!chop_var(buffer, lhs, rhs))
592			break;
593		set_variable(lhs, rhs);
594	}
595	return (buffer);
596}
597
598void
599config::find_and_execute(char type)
600{
601	vector<event_proc *> *l;
602	vector<event_proc *>::const_iterator i;
603	char *s;
604
605	switch (type) {
606	default:
607		return;
608	case nomatch:
609		l = &_nomatch_list;
610		s = "nomatch";
611		break;
612	case attach:
613		l = &_attach_list;
614		s = "attach";
615		break;
616	case detach:
617		l = &_detach_list;
618		s = "detach";
619		break;
620	}
621	if (dflag)
622		fprintf(stderr, "Processing %s event\n", s);
623	for (i = l->begin(); i != l->end(); i++) {
624		if ((*i)->matches(*this)) {
625			(*i)->run(*this);
626			break;
627		}
628	}
629
630}
631
632
633static void
634process_event(char *buffer)
635{
636	char type;
637	char *sp;
638
639	sp = buffer + 1;
640	if (dflag)
641		fprintf(stderr, "Processing event '%s'\n", buffer);
642	type = *buffer++;
643	cfg.push_var_table();
644	// No match doesn't have a device, and the format is a little
645	// different, so handle it separately.
646	if (type != nomatch) {
647		sp = strchr(sp, ' ');
648		if (sp == NULL)
649			return;	/* Can't happen? */
650		*sp++ = '\0';
651		cfg.set_variable("device-name", buffer);
652		if (strncmp(sp, "at ", 3) == 0)
653			sp += 3;
654		sp = cfg.set_vars(sp);
655		if (strncmp(sp, "on ", 3) == 0)
656			cfg.set_variable("bus", sp + 3);
657	} else {
658		//?vars at location on bus
659		sp = cfg.set_vars(sp);
660		if (strncmp(sp, "at ", 3) == 0)
661			sp += 3;
662		sp = cfg.set_vars(sp);
663		if (strncmp(sp, "on ", 3) == 0)
664			cfg.set_variable("bus", sp + 3);
665	}
666
667	cfg.find_and_execute(type);
668	cfg.pop_var_table();
669}
670
671static void
672event_loop(void)
673{
674	int rv;
675	int fd;
676	char buffer[DEVCTL_MAXBUF];
677
678	fd = open(PATH_DEVCTL, O_RDONLY);
679	if (fd == -1)
680		err(1, "Can't open devctl");
681	if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0)
682		err(1, "Can't set close-on-exec flag");
683	while (1) {
684		if (romeo_must_die)
685			break;
686		rv = read(fd, buffer, sizeof(buffer) - 1);
687		if (rv > 0) {
688			buffer[rv] = '\0';
689			while (buffer[--rv] == '\n')
690				buffer[rv] = '\0';
691			process_event(buffer);
692		} else if (rv < 0) {
693			if (errno != EINTR)
694				break;
695		} else {
696			/* EOF */
697			break;
698		}
699	}
700	close(fd);
701}
702
703/*
704 * functions that the parser uses.
705 */
706void
707add_attach(int prio, event_proc *p)
708{
709	cfg.add_attach(prio, p);
710}
711
712void
713add_detach(int prio, event_proc *p)
714{
715	cfg.add_detach(prio, p);
716}
717
718void
719add_directory(const char *dir)
720{
721	cfg.add_directory(dir);
722	free(const_cast<char *>(dir));
723}
724
725void
726add_nomatch(int prio, event_proc *p)
727{
728	cfg.add_nomatch(prio, p);
729}
730
731event_proc *
732add_to_event_proc(event_proc *ep, eps *eps)
733{
734	if (ep == NULL)
735		ep = new event_proc();
736	ep->add(eps);
737	return (ep);
738}
739
740eps *
741new_action(const char *cmd)
742{
743	eps *e = new action(cmd);
744	free(const_cast<char *>(cmd));
745	return (e);
746}
747
748eps *
749new_match(const char *var, const char *re)
750{
751	eps *e = new match(cfg, var, re);
752	free(const_cast<char *>(var));
753	free(const_cast<char *>(re));
754	return (e);
755}
756
757void
758set_pidfile(const char *name)
759{
760	cfg.set_pidfile(name);
761	free(const_cast<char *>(name));
762}
763
764void
765set_variable(const char *var, const char *val)
766{
767	cfg.set_variable(var, val);
768	free(const_cast<char *>(var));
769	free(const_cast<char *>(val));
770}
771
772
773
774static void
775gensighand(int)
776{
777	romeo_must_die++;
778	_exit(0);
779}
780
781static void
782usage()
783{
784	fprintf(stderr, "usage: %s [-d]\n", getprogname());
785	exit(1);
786}
787
788static void
789check_devd_enabled()
790{
791	int val = 0;
792	size_t len;
793
794	len = sizeof(val);
795	if (sysctlbyname(SYSCTL, &val, &len, NULL, NULL) != 0)
796		errx(1, "devctl sysctl missing from kernel!");
797	if (val) {
798		warnx("Setting " SYSCTL " to 0");
799		val = 0;
800		sysctlbyname(SYSCTL, NULL, NULL, &val, sizeof(val));
801	}
802}
803
804/*
805 * main
806 */
807int
808main(int argc, char **argv)
809{
810	int ch;
811
812	check_devd_enabled();
813	while ((ch = getopt(argc, argv, "d")) != -1) {
814		switch (ch) {
815		case 'd':
816			dflag++;
817			break;
818		default:
819			usage();
820		}
821	}
822
823	cfg.parse();
824	if (!dflag)
825		daemon(0, 0);
826	cfg.drop_pidfile();
827	signal(SIGHUP, gensighand);
828	signal(SIGINT, gensighand);
829	signal(SIGTERM, gensighand);
830	event_loop();
831	return (0);
832}
833