freebsd-dev/sbin/devd/devd.cc
Warner Losh b1ee04b18e o Skip white space between variables better. This makes
'a="b" c="d" at loc=1 on busN' properly set 'c' and process the rest of
  the stirng.  Before it would ignore everything after variable 'a'.
o Parse nomatch and other events differently.  They are more different
  than the code allowed for, so we weren't properly parsing nomatch
  events.  It appears this fixes some of the demand loading issues that
  I was having with devd.

Noticed by: Gary Palmer
2003-04-21 04:00:01 +00:00

814 lines
15 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*-
* Copyright (c) 2002-2003 M. Warner Losh.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* DEVD control daemon.
*/
// TODO list:
// o devd.conf and devd man pages need a lot of help:
// - devd.conf needs to lose the warning about zone files.
// - devd.conf needs more details on the supported statements.
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/types.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <err.h>
#include <fcntl.h>
#include <regex.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <algorithm>
#include <map>
#include <string>
#include <vector>
#include "devd.h"
#define CF "/etc/devd.conf"
using namespace std;
extern FILE *yyin;
extern int lineno;
static const char nomatch = '?';
static const char attach = '+';
static const char detach = '-';
int dflag;
int romeo_must_die = 0;
static void event_loop(void);
static void usage(void);
template <class T> void
delete_and_clear(vector<T *> &v)
{
typename vector<T *>::const_iterator i;
for (i = v.begin(); i != v.end(); i++)
delete *i;
v.clear();
}
class config;
class var_list
{
public:
var_list() {}
virtual ~var_list() {}
void set_variable(const string &var, const string &val);
const string &get_variable(const string &var) const;
bool is_set(const string &var) const;
static const string bogus;
static const string nothing;
private:
map<string, string> _vars;
};
class eps
{
public:
eps() {}
virtual ~eps() {}
virtual bool do_match(config &) = 0;
virtual bool do_action(config &) = 0;
};
class match : public eps
{
public:
match(config &, const char *var, const char *re);
virtual ~match();
virtual bool do_match(config &);
virtual bool do_action(config &) { return true; }
private:
string _var;
string _re;
regex_t _regex;
};
class action : public eps
{
public:
action(const char *cmd);
virtual ~action();
virtual bool do_match(config &) { return true; }
virtual bool do_action(config &);
private:
string _cmd;
};
class event_proc
{
public:
event_proc();
virtual ~event_proc();
int get_priority() const { return (_prio); }
void set_priority(int prio) { _prio = prio; }
void add(eps *);
bool matches(config &);
bool run(config &);
private:
int _prio;
vector<eps *> _epsvec;
};
class config
{
public:
config() : _pidfile("") { push_var_table(); }
virtual ~config() { reset(); }
void add_attach(int, event_proc *);
void add_detach(int, event_proc *);
void add_directory(const char *);
void add_nomatch(int, event_proc *);
void set_pidfile(const char *);
void reset();
void parse();
void drop_pidfile();
void push_var_table();
void pop_var_table();
void set_variable(const char *var, const char *val);
const string &get_variable(const string &var);
const string expand_string(const string &var);
char *set_vars(char *);
void find_and_execute(char);
protected:
void sort_vector(vector<event_proc *> &);
void parse_one_file(const char *fn);
void parse_files_in_dir(const char *dirname);
void expand_one(const char *&src, char *&dst, char *eod);
bool is_id_char(char);
bool chop_var(char *&buffer, char *&lhs, char *&rhs);
private:
vector<string> _dir_list;
string _pidfile;
vector<var_list *> _var_list_table;
vector<event_proc *> _attach_list;
vector<event_proc *> _detach_list;
vector<event_proc *> _nomatch_list;
};
config cfg;
event_proc::event_proc() : _prio(-1)
{
// nothing
}
event_proc::~event_proc()
{
vector<eps *>::const_iterator i;
for (i = _epsvec.begin(); i != _epsvec.end(); i++)
delete *i;
_epsvec.clear();
}
void
event_proc::add(eps *eps)
{
_epsvec.push_back(eps);
}
bool
event_proc::matches(config &c)
{
vector<eps *>::const_iterator i;
for (i = _epsvec.begin(); i != _epsvec.end(); i++)
if (!(*i)->do_match(c))
return (false);
return (true);
}
bool
event_proc::run(config &c)
{
vector<eps *>::const_iterator i;
for (i = _epsvec.begin(); i != _epsvec.end(); i++)
if (!(*i)->do_action(c))
return (false);
return (true);
}
action::action(const char *cmd)
: _cmd(cmd)
{
// nothing
}
action::~action()
{
// nothing
}
bool
action::do_action(config &c)
{
string s = c.expand_string(_cmd);
if (dflag)
fprintf(stderr, "Executing '%s'\n", s.c_str());
::system(s.c_str());
return (true);
}
match::match(config &c, const char *var, const char *re)
: _var(var)
{
string pattern = re;
_re = "^";
_re.append(c.expand_string(string(re)));
_re.append("$");
regcomp(&_regex, _re.c_str(), REG_EXTENDED | REG_NOSUB);
}
match::~match()
{
regfree(&_regex);
}
bool
match::do_match(config &c)
{
string value = c.get_variable(_var);
bool retval;
if (dflag)
fprintf(stderr, "Testing %s=%s against %s\n", _var.c_str(),
value.c_str(), _re.c_str());
retval = (regexec(&_regex, value.c_str(), 0, NULL, 0) == 0);
return retval;
}
const string var_list::bogus = "_$_$_$_$_B_O_G_U_S_$_$_$_$_";
const string var_list::nothing = "";
const string &
var_list::get_variable(const string &var) const
{
map<string, string>::const_iterator i;
i = _vars.find(var);
if (i == _vars.end())
return (var_list::bogus);
return (i->second);
}
bool
var_list::is_set(const string &var) const
{
return (_vars.find(var) != _vars.end());
}
void
var_list::set_variable(const string &var, const string &val)
{
if (dflag)
fprintf(stderr, "%s=%s\n", var.c_str(), val.c_str());
_vars[var] = val;
}
void
config::reset(void)
{
_dir_list.clear();
delete_and_clear(_var_list_table);
delete_and_clear(_attach_list);
delete_and_clear(_detach_list);
delete_and_clear(_nomatch_list);
}
void
config::parse_one_file(const char *fn)
{
if (dflag)
printf("Parsing %s\n", fn);
yyin = fopen(fn, "r");
if (yyin == NULL)
err(1, "Cannot open config file %s", fn);
if (yyparse() != 0)
errx(1, "Cannot parse %s at line %d", fn, lineno);
fclose(yyin);
}
void
config::parse_files_in_dir(const char *dirname)
{
DIR *dirp;
struct dirent *dp;
char path[PATH_MAX];
if (dflag)
printf("Parsing files in %s\n", dirname);
dirp = opendir(dirname);
if (dirp == NULL)
return;
readdir(dirp); /* Skip . */
readdir(dirp); /* Skip .. */
while ((dp = readdir(dirp)) != NULL) {
if (strcmp(dp->d_name + dp->d_namlen - 5, ".conf") == 0) {
snprintf(path, sizeof(path), "%s/%s",
dirname, dp->d_name);
parse_one_file(path);
}
}
}
class epv_greater {
public:
int operator()(event_proc *const&l1, event_proc *const&l2)
{
return (l1->get_priority() > l2->get_priority());
}
};
void
config::sort_vector(vector<event_proc *> &v)
{
sort(v.begin(), v.end(), epv_greater());
}
void
config::parse(void)
{
vector<string>::const_iterator i;
parse_one_file(CF);
for (i = _dir_list.begin(); i != _dir_list.end(); i++)
parse_files_in_dir((*i).c_str());
sort_vector(_attach_list);
sort_vector(_detach_list);
sort_vector(_nomatch_list);
}
void
config::drop_pidfile()
{
FILE *fp;
if (_pidfile == "")
return;
fp = fopen(_pidfile.c_str(), "w");
if (fp == NULL)
return;
fprintf(fp, "%d\n", getpid());
fclose(fp);
}
void
config::add_attach(int prio, event_proc *p)
{
p->set_priority(prio);
_attach_list.push_back(p);
}
void
config::add_detach(int prio, event_proc *p)
{
p->set_priority(prio);
_detach_list.push_back(p);
}
void
config::add_directory(const char *dir)
{
_dir_list.push_back(string(dir));
}
void
config::add_nomatch(int prio, event_proc *p)
{
p->set_priority(prio);
_nomatch_list.push_back(p);
}
void
config::set_pidfile(const char *fn)
{
_pidfile = string(fn);
}
void
config::push_var_table()
{
var_list *vl;
vl = new var_list();
_var_list_table.push_back(vl);
if (dflag)
fprintf(stderr, "Pushing table\n");
}
void
config::pop_var_table()
{
delete _var_list_table.back();
_var_list_table.pop_back();
if (dflag)
fprintf(stderr, "Popping table\n");
}
void
config::set_variable(const char *var, const char *val)
{
_var_list_table.back()->set_variable(var, val);
}
const string &
config::get_variable(const string &var)
{
vector<var_list *>::reverse_iterator i;
for (i = _var_list_table.rbegin(); i != _var_list_table.rend(); i++) {
if ((*i)->is_set(var))
return ((*i)->get_variable(var));
}
return (var_list::nothing);
}
bool
config::is_id_char(char ch)
{
return (ch != '\0' && (isalpha(ch) || isdigit(ch) || ch == '_' ||
ch == '-'));
}
// XXX
// imp should learn how to make effective use of the string class.
void
config::expand_one(const char *&src, char *&dst, char *)
{
int count;
const char *var;
char buffer[1024];
string varstr;
src++;
// $$ -> $
if (*src == '$') {
*dst++ = *src++;
return;
}
// $(foo) -> $(foo)
// Not sure if I want to support this or not, so for now we just pass
// it through.
if (*src == '(') {
*dst++ = '$';
count = 1;
while (count > 0) {
if (*src == ')')
count--;
else if (*src == '(')
count++;
*dst++ = *src++;
}
return;
}
// ${^A-Za-z] -> $\1
if (!isalpha(*src)) {
*dst++ = '$';
*dst++ = *src++;
return;
}
// $var -> replace with value
var = src++;
while (is_id_char(*src))
src++;
memcpy(buffer, var, src - var);
buffer[src - var] = '\0';
varstr = get_variable(buffer);
strcpy(dst, varstr.c_str());
dst += strlen(dst);
}
const string
config::expand_string(const string &s)
{
const char *src;
char *dst;
char buffer[1024];
src = s.c_str();
dst = buffer;
while (*src) {
if (*src == '$')
expand_one(src, dst, buffer + sizeof(buffer));
else
*dst++ = *src++;
}
*dst++ = '\0';
return (buffer);
}
bool
config::chop_var(char *&buffer, char *&lhs, char *&rhs)
{
char *walker;
if (*buffer == '\0')
return (false);
walker = lhs = buffer;
while (is_id_char(*walker))
walker++;
if (*walker != '=')
return (false);
walker++; // skip =
if (*walker == '"') {
walker++; // skip "
rhs = walker;
while (*walker && *walker != '"')
walker++;
if (*walker != '"')
return (false);
rhs[-2] = '\0';
*walker++ = '\0';
} else {
rhs = walker;
while (*walker && !isspace(*walker))
walker++;
if (*walker != '\0')
*walker++ = '\0';
rhs[-1] = '\0';
}
while (isspace(*walker))
walker++;
buffer = walker;
return (true);
}
char *
config::set_vars(char *buffer)
{
char *lhs;
char *rhs;
while (1) {
if (!chop_var(buffer, lhs, rhs))
break;
set_variable(lhs, rhs);
}
return (buffer);
}
void
config::find_and_execute(char type)
{
vector<event_proc *> *l;
vector<event_proc *>::const_iterator i;
char *s;
switch (type) {
default:
return;
case nomatch:
l = &_nomatch_list;
s = "nomatch";
break;
case attach:
l = &_attach_list;
s = "attach";
break;
case detach:
l = &_detach_list;
s = "detach";
break;
}
if (dflag)
fprintf(stderr, "Processing %s event\n", s);
for (i = l->begin(); i != l->end(); i++) {
if ((*i)->matches(*this)) {
(*i)->run(*this);
break;
}
}
}
static void
process_event(char *buffer)
{
char type;
char *sp;
sp = buffer + 1;
if (dflag)
fprintf(stderr, "Processing event '%s'\n", buffer);
type = *buffer++;
cfg.push_var_table();
// No match doesn't have a device, and the format is a little
// different, so handle it separately.
if (type != nomatch) {
sp = strchr(sp, ' ');
if (sp == NULL)
return; /* Can't happen? */
*sp++ = '\0';
cfg.set_variable("device-name", buffer);
if (strncmp(sp, "at ", 3) == 0)
sp += 3;
sp = cfg.set_vars(sp);
if (strncmp(sp, "on ", 3) == 0)
cfg.set_variable("bus", sp + 3);
} else {
//?vars at location on bus
sp = cfg.set_vars(sp);
if (strncmp(sp, "at ", 3) == 0)
sp += 3;
sp = cfg.set_vars(sp);
if (strncmp(sp, "on ", 3) == 0)
cfg.set_variable("bus", sp + 3);
}
cfg.find_and_execute(type);
cfg.pop_var_table();
}
static void
event_loop(void)
{
int rv;
int fd;
char buffer[DEVCTL_MAXBUF];
fd = open(PATH_DEVCTL, O_RDONLY);
if (fd == -1)
err(1, "Can't open devctl");
if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0)
err(1, "Can't set close-on-exec flag");
while (1) {
if (romeo_must_die)
break;
rv = read(fd, buffer, sizeof(buffer) - 1);
if (rv > 0) {
buffer[rv] = '\0';
while (buffer[--rv] == '\n')
buffer[rv] = '\0';
process_event(buffer);
} else if (rv < 0) {
if (errno != EINTR)
break;
} else {
/* EOF */
break;
}
}
close(fd);
}
/*
* functions that the parser uses.
*/
void
add_attach(int prio, event_proc *p)
{
cfg.add_attach(prio, p);
}
void
add_detach(int prio, event_proc *p)
{
cfg.add_detach(prio, p);
}
void
add_directory(const char *dir)
{
cfg.add_directory(dir);
free(const_cast<char *>(dir));
}
void
add_nomatch(int prio, event_proc *p)
{
cfg.add_nomatch(prio, p);
}
event_proc *
add_to_event_proc(event_proc *ep, eps *eps)
{
if (ep == NULL)
ep = new event_proc();
ep->add(eps);
return (ep);
}
eps *
new_action(const char *cmd)
{
eps *e = new action(cmd);
free(const_cast<char *>(cmd));
return (e);
}
eps *
new_match(const char *var, const char *re)
{
eps *e = new match(cfg, var, re);
free(const_cast<char *>(var));
free(const_cast<char *>(re));
return (e);
}
void
set_pidfile(const char *name)
{
cfg.set_pidfile(name);
free(const_cast<char *>(name));
}
void
set_variable(const char *var, const char *val)
{
cfg.set_variable(var, val);
free(const_cast<char *>(var));
free(const_cast<char *>(val));
}
static void
gensighand(int)
{
romeo_must_die++;
_exit(0);
}
static void
usage()
{
fprintf(stderr, "usage: %s [-d]\n", getprogname());
exit(1);
}
/*
* main
*/
int
main(int argc, char **argv)
{
int ch;
while ((ch = getopt(argc, argv, "d")) != -1) {
switch (ch) {
case 'd':
dflag++;
break;
default:
usage();
}
}
cfg.parse();
if (!dflag)
daemon(0, 0);
cfg.drop_pidfile();
signal(SIGHUP, gensighand);
signal(SIGINT, gensighand);
signal(SIGTERM, gensighand);
event_loop();
return (0);
}