Welcome devctl(4) and devd(8).
authorHasso Tepper <hasso@dragonflybsd.org>
Fri, 3 Oct 2008 00:26:21 +0000 (00:26 +0000)
committerHasso Tepper <hasso@dragonflybsd.org>
Fri, 3 Oct 2008 00:26:21 +0000 (00:26 +0000)
Obtained-from: FreeBSD

20 files changed:
etc/defaults/rc.conf
etc/devd.conf [new file with mode: 0644]
etc/rc.d/Makefile
etc/rc.d/devd [new file with mode: 0644]
sbin/Makefile
sbin/devd/Makefile [new file with mode: 0644]
sbin/devd/devd.8 [new file with mode: 0644]
sbin/devd/devd.cc [new file with mode: 0644]
sbin/devd/devd.conf.5 [new file with mode: 0644]
sbin/devd/devd.h [new file with mode: 0644]
sbin/devd/devd.hh [new file with mode: 0644]
sbin/devd/parse.y [new file with mode: 0644]
sbin/devd/token.l [new file with mode: 0644]
share/man/man4/Makefile
share/man/man4/devctl.4 [new file with mode: 0644]
sys/dev/acpica5/acpi.c
sys/dev/powermng/coretemp/coretemp.c
sys/kern/subr_bus.c
sys/net/if.c
sys/sys/bus.h

index 2b7f00f..9e72ffe 100644 (file)
@@ -14,7 +14,7 @@
 # All arguments must be in double or single quotes.
 #
 # $FreeBSD: src/etc/defaults/rc.conf,v 1.180 2003/06/26 09:50:50 smkelly Exp $
-# $DragonFly: src/etc/defaults/rc.conf,v 1.51 2008/08/30 16:40:17 hasso Exp $
+# $DragonFly: src/etc/defaults/rc.conf,v 1.52 2008/10/03 00:26:21 hasso Exp $
 
 ##############################################################
 ###  Important initial Boot-time options  ####################
@@ -405,6 +405,8 @@ lpd_program="/usr/sbin/lpd" # path to lpd, if you want a different one.
 lpd_flags=""           # Flags to lpd (if enabled).
 usbd_enable="NO"       # Run the usbd daemon.
 usbd_flags=""          # Flags to usbd (if enabled).
+devd_enable="NO"       # Rund devd(8) daemon.
+devd_flags=""          # Flags to devd(8) (if enabled).
 dumpdev="NO"           # Device name to crashdump to (or NO).
 dumpdir="/var/crash"   # Directory where crash dumps are to be stored
 savecore_flags=""      # Used if dumpdev is enabled above, and present.
diff --git a/etc/devd.conf b/etc/devd.conf
new file mode 100644 (file)
index 0000000..adbcfc7
--- /dev/null
@@ -0,0 +1,282 @@
+# $FreeBSD: src/etc/devd.conf,v 1.42 2008/06/27 12:04:36 rpaulo Exp $
+# $DragonFly: src/etc/devd.conf,v 1.1 2008/10/03 00:26:20 hasso Exp $
+#
+# Refer to devd.conf(5) and devd(8) man pages for the details on how to
+# run and configure devd.
+#
+
+# NB: All regular expressions have an implicit ^$ around them.
+# NB: device-name is shorthand for 'match device-name'
+
+options {
+       # Each directory directive adds a directory the list of directories
+       # that we scan for files.  Files are read-in in the order that they
+       # are returned from readdir(3).  The rule-sets are combined to
+       # create a DFA that's used to match events to actions.
+       directory "/etc/devd";
+       directory "/usr/local/etc/devd";
+
+       # Setup some shorthand for regex that we use later in the file.
+       #XXX Yes, these are gross -- imp
+       set scsi-controller-regex
+               "(aac|adv|adw|aha|ahb|ahc|ahd|aic|amd|amr|asr|bt|ciss|ct|dpt|\
+               esp|ida|iir|ips|isp|mlx|mly|mpt|ncr|ncv|nsp|stg|sym|trm|wds)\
+               [0-9]+";
+};
+
+# Note that the attach/detach with the highest value wins, so that one can
+# override these general rules.
+
+#
+# Configure the interface on attach.  Due to a historical accident, this
+# script is called pccard_ether.
+#
+# notify 0 {
+#      match "system"          "IFNET";
+#      match "type"            "ATTACH";
+#      action "/etc/pccard_ether $subsystem start";
+# };
+#
+# notify 0 {
+#      match "system"          "IFNET";
+#      match "type"            "DETACH";
+#      action "/etc/pccard_ether $subsystem stop";
+# };
+
+#
+# Try to start dhclient on Ethernet like interfaces when the link comes
+# up.  Only devices that are configured to support DHCP will actually
+# run it.  No link down rule exists because dhclient automaticly exits
+# when the link goes down.
+#
+# notify 0 {
+#      match "system"          "IFNET";
+#      match "type"            "LINK_UP";
+#      media-type              "ethernet";
+#      action "/etc/rc.d/dhclient start $subsystem";
+# };
+
+#
+# Like Ethernet devices, but separate because
+# they have a different media type.  We may want
+# to exploit this later.
+#
+# detach 0 {
+#      media-type "802.11";
+#      action "/etc/pccard_ether $device-name stop";
+# };
+# attach 0 {
+#      media-type "802.11";
+#      action "/etc/pccard_ether $device-name start";
+# };
+# notify 0 {
+#      match "system"          "IFNET";
+#      match "type"            "LINK_UP";
+#      media-type              "802.11";
+#      action "/etc/rc.d/dhclient start $subsystem";
+# };
+
+#
+# An entry like this might be in a different file, but is included here
+# as an example of how to override things.  Normally 'ed50' would match
+# the above attach/detach stuff, but the value of 100 makes it
+# hard wired to 1.2.3.4.
+# attach 100 {
+#      device-name "ed50";
+#      action "ifconfig $device-name inet 1.2.3.4 netmask 0xffff0000";
+# };
+# detach 100 {
+#      device-name "ed50";
+# };
+
+#
+# When a USB Bluetooth dongle appears activate it.
+# XXX FIX for DragonFly XXX
+# attach 100 {
+#      device-name "ubt[0-9]+";
+#      action "/etc/rc.d/bluetooth start $device-name";
+# };
+# detach 100 {
+#      device-name "ubt[0-9]+";
+#      action "/etc/rc.d/bluetooth stop $device-name";
+# };
+
+#
+# When a USB keyboard arrives, attach it as the console keyboard.
+# XXX Fix for DragonFly XXX
+# attach 100 {
+#      device-name "ukbd0";
+#      action "/etc/rc.d/syscons setkeyboard /dev/ukbd0";
+# };
+# detach 100 {
+#      device-name "ukbd0";
+#      action "/etc/rc.d/syscons setkeyboard /dev/kbd0";
+# };
+#
+# attach 100 {
+#      device-name "ums[0-9]+";
+#      action "/etc/rc.d/moused start $device-name";
+# };
+#
+# detach 100 { 
+#      device-name "ums[0-9]+"; 
+#      action "/etc/rc.d/moused stop $device-name"; 
+# }; 
+
+#
+# Rescan scsi device-names on attach, but not detach.  However, it is
+# disabled by default due to reports of problems.
+#
+# attach 0 {
+#      device-name "$scsi-controller-regex";
+#      action "camcontrol rescan all";
+# };
+#
+# Don't even try to second guess what to do about drivers that don't
+# match here.  Instead, pass it off to syslog.  Commented out for the
+# moment, as the pnpinfo variable isn't set in devd yet.  Individual
+# variables within the bus supplied pnpinfo are set.
+# nomatch 0 {
+#      action "logger Unknown device: $pnpinfo $location $bus";
+#};
+
+#
+# Various logging of unknown devices.
+# nomatch 10 {
+#      match "bus" "uhub[0-9]+";
+#      action "logger Unknown USB device: vendor $vendor product $product \
+#              bus $bus";
+# };
+
+#
+# Some PC-CARDs don't offer numerical manufacturer/product IDs, just
+# show the CIS info there.
+# nomatch 20 {
+#      match "bus" "pccard[0-9]+";
+#      match "manufacturer" "0xffffffff";
+#      match "product" "0xffffffff";
+#      action "logger Unknown PCCARD device: CISproduct $cisproduct \
+#              CIS-vendor $cisvendor bus $bus";
+# };
+#
+# nomatch 10 {
+#      match "bus" "pccard[0-9]+";
+#      action "logger Unknown PCCARD device: manufacturer $manufacturer \
+#              product $product CISproduct $cisproduct CIS-vendor \
+#              $cisvendor bus $bus";
+# };
+#
+# nomatch 10 {
+#      match "bus" "cardbus[0-9]+";
+#      action "logger Unknown Cardbus device: device $device class $class \
+#              vendor $vendor bus $bus";
+# };
+
+#
+# Switch power profiles when the AC line state changes.
+# notify 10 {
+#      match "system"          "ACPI";
+#      match "subsystem"       "ACAD";
+#      action "/etc/rc.d/power_profile $notify";
+# };
+
+#
+# Notify all users before beginning emergency shutdown when we get
+# a _CRT or _HOT thermal event and we're going to power down the system
+# very soon.
+# notify 10 {
+#      match "system"          "ACPI";
+#      match "subsystem"       "Thermal";
+#      match "notify"          "0xcc";
+#      action "logger -p kern.emerg \
+#              'WARNING: system temperature too high, shutting down soon!'";
+#};
+
+#
+# User requested suspend, so perform preparation steps and then execute
+# the actual suspend process.
+# notify 10 {
+#      match "system"          "ACPI";
+#      match "subsystem"       "Suspend";
+#      action "/etc/rc.suspend acpi $notify";
+# };
+# notify 10 {
+#      match "system"          "ACPI";
+#      match "subsystem"       "Resume";
+#      action "/etc/rc.resume acpi $notify";
+# };
+
+#
+# The next blocks enable volume hotkeys that can be found on the Asus EeePC
+# XXX ASUS-Eee subsystem isn't available in DragonFly
+# notify 0 {
+#      match "system"          "ACPI";
+#      match "subsystem"       "ASUS-Eee";
+#      match "notify"          "0x13";
+#      action                  "mixer 0";
+# };
+#
+# notify 0 {
+#      match "system"          "ACPI";
+#      match "subsystem"       "ASUS-Eee";
+#      match "notify"          "0x14";
+#      action                  "mixer vol -10";
+# };
+#
+# notify 0 {
+#      match "system"          "ACPI";
+#      match "subsystem"       "ASUS-Eee";
+#      match "notify"          "0x15";
+#      action                  "mixer vol +10";
+# };
+
+# 
+# The following might be an example of something that a vendor might
+# install if you were to add their device.  This might reside in
+# /usr/local/etc/devd/deqna.conf.  A deqna is, in this hypothetical
+# example, a pccard ethernet-like device.  Students of history may
+# know other devices by this name, and will get the in-jokes in this
+# entry.
+# nomatch 10 {
+#      match "bus" "pccard[0-9]+";
+#      match "manufacturer" "0x1234";
+#      match "product" "0x2323";
+#      action "kldload if_deqna";
+# };
+# attach 10 {
+#      device-name "deqna[0-9]+";
+#      action "/etc/pccard_ether $device-name start";
+# };
+# detach 10 {
+#      device-name "deqna[0-9]+";
+#      action "/etc/pccard_ether $device-name stop";
+# };
+
+#
+# Examples of notify hooks.  A notify is a generic way for a kernel
+# subsystem to send event notification to userland.
+#
+# Here are some examples of ACPI notify handlers.  ACPI subsystems that
+# generate notifies include the AC adapter, power/sleep buttons,
+# control method batteries, lid switch, and thermal zones.
+#
+# Information returned is not always the same as the ACPI notify
+# events.  See the ACPI specification for more information about
+# notifies.  Here is the information returned for each subsystem:
+#
+# ACAD:                AC line state (0 is offline, 1 is online)
+# Button:      Button pressed (0 for power, 1 for sleep)
+# CMBAT:       ACPI battery events
+# Lid:         Lid state (0 is closed, 1 is open)
+# Suspend, Resume: Suspend and resume notification
+# Thermal:     ACPI thermal zone events
+#
+# This example calls a script when the AC state changes, passing the
+# notify value as the first argument.  If the state is 0x00, it might
+# call some sysctls to implement economy mode.  If 0x01, it might set
+# the mode to performance.
+# notify 10 {
+#      match "system"          "ACPI";
+#      match "subsystem"       "ACAD";
+#      action                  "/etc/acpi_ac $notify";
+# };
index 934c470..dd66bb4 100644 (file)
@@ -1,6 +1,6 @@
 # $NetBSD: Makefile,v 1.16 2001/01/14 15:37:22 minoura Exp $
 # $FreeBSD: src/etc/rc.d/Makefile,v 1.20 2003/06/29 05:15:57 mtm Exp $
-# $DragonFly: src/etc/rc.d/Makefile,v 1.29 2008/08/30 16:40:17 hasso Exp $
+# $DragonFly: src/etc/rc.d/Makefile,v 1.30 2008/10/03 00:26:21 hasso Exp $
 
 .include <bsd.own.mk>
 
@@ -9,7 +9,7 @@
 FILES= DAEMON LOGIN NETWORKING SERVERS abi accounting addswap adjkerntz \
        amd apm apmd atm1 atm2.sh atm3.sh \
        battd bootconf bootparams btconfig bthcid ccd cleanvar \
-       cleartmp cron dhclient diskless dmesg dumpon \
+       cleartmp cron devd dhclient diskless dmesg dumpon \
        fsck ftpd hostapd hostname \
        inetd initdiskless initrandom ip6fw ipfilter ipfs ipfw ipmon \
        ipnat ipsec ipxrouted isdnd jail \
diff --git a/etc/rc.d/devd b/etc/rc.d/devd
new file mode 100644 (file)
index 0000000..abecadd
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+#
+# $FreeBSD: src/etc/rc.d/devd,v 1.11 2008/07/16 19:50:29 dougb Exp $
+# $DragonFly: src/etc/rc.d/devd,v 1.1 2008/10/03 00:26:21 hasso Exp $
+
+# PROVIDE: devd
+# REQUIRE: netif network_ipv6
+# BEFORE: NETWORKING mountcritremote
+# KEYWORD: nojail shutdown
+
+. /etc/rc.subr
+
+name="devd"
+rcvar=`set_rcvar`
+command="/sbin/${name}"
+
+load_rc_config $name
+run_rc_command "$1"
+
+# If devd is disabled, turn it off in the kernel to avoid memory leaks.
+if ! checkyesno ${rcvar}; then
+    sysctl hw.bus.devctl_disable=1
+fi
index f1d000d..27fb6d9 100644 (file)
@@ -1,6 +1,6 @@
 #      @(#)Makefile    8.5 (Berkeley) 3/31/94
 # $FreeBSD: src/sbin/Makefile,v 1.77.2.9 2002/08/08 09:03:46 ru Exp $
-# $DragonFly: src/sbin/Makefile,v 1.19 2008/06/22 16:14:44 swildner Exp $
+# $DragonFly: src/sbin/Makefile,v 1.20 2008/10/03 00:26:21 hasso Exp $
 #
 # XXX MISSING:         icheck ncheck
 
@@ -12,6 +12,7 @@ SUBDIR=       adjkerntz \
        ccdconfig \
        clri \
        comcontrol \
+       devd \
        dhclient \
        disklabel \
        disklabel64 \
diff --git a/sbin/devd/Makefile b/sbin/devd/Makefile
new file mode 100644 (file)
index 0000000..d46eb7d
--- /dev/null
@@ -0,0 +1,21 @@
+# $FreeBSD: src/sbin/devd/Makefile,v 1.9 2007/11/19 00:19:01 jb Exp $
+# $DragonFly: src/sbin/devd/Makefile,v 1.1 2008/10/03 00:26:21 hasso Exp $
+
+PROG_CXX=devd
+SRCS=  devd.cc token.l parse.y y.tab.h
+MAN=   devd.8 devd.conf.5
+
+WARNS= 0
+#WARNS?=       4
+
+NO_SHARED?=YES
+
+DPADD= ${LIBL} ${LIBUTIL}
+LDADD= -ll -lutil
+
+YFLAGS+=-v
+CFLAGS+=-I. -I${.CURDIR}
+
+CLEANFILES= y.output
+
+.include <bsd.prog.mk>
diff --git a/sbin/devd/devd.8 b/sbin/devd/devd.8
new file mode 100644 (file)
index 0000000..29b88a6
--- /dev/null
@@ -0,0 +1,148 @@
+.\"
+.\" Copyright (c) 2002 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.
+.\"
+.\" $FreeBSD: src/sbin/devd/devd.8,v 1.17 2006/09/17 22:49:26 ru Exp $
+.\" $DragonFly: src/sbin/devd/devd.8,v 1.1 2008/10/03 00:26:21 hasso Exp $
+.\"
+.Dd November 24, 2005
+.Dt DEVD 8
+.Os
+.Sh NAME
+.Nm devd
+.Nd "device state change daemon"
+.Sh SYNOPSIS
+.Nm
+.Op Fl Ddn
+.Op Fl f Ar file
+.Sh DESCRIPTION
+The
+.Nm
+daemon provides a way to have userland programs run when certain
+kernel events happen.
+.Pp
+The following options are accepted.
+.Bl -tag -width ".Fl f Ar file"
+.It Fl D
+Enable debugging messages.
+.It Fl d
+Run in the foreground instead of becoming a daemon.
+.It Fl f Ar file
+Use configuration file
+.Ar file
+instead of the default
+.Pa /etc/devd.conf .
+If option
+.Fl f
+is specified more than once, the last file specified is used.
+.It Fl n
+Do not process all pending events before becoming a daemon.
+Instead, call daemon right away.
+.El
+.Sh IMPLEMENTATION NOTES
+The
+.Nm
+utility
+is a system daemon that runs in the background all the time.
+Whenever a device is added to or removed from the device tree,
+.Nm
+will execute actions specified in
+.Xr devd.conf 5 .
+For example,
+.Nm
+might execute
+.Xr dhclient 8
+when an Ethernet adapter is added to the system, and kill the
+.Xr dhclient 8
+instance when the same adapter is removed.
+Another example would be for
+.Nm
+to use a table to locate and load via
+.Xr kldload 8
+the proper driver for an unrecognized device that is added to the system.
+.Pp
+The
+.Nm
+utility
+hooks into the
+.Xr devctl 4
+device driver.
+This device driver has hooks into the device configuration system.
+When nodes are added or deleted from the tree, this device will
+deliver information about the event to
+.Nm .
+Once
+.Nm
+has parsed the message, it will search its action list for that kind
+of event and perform the action with the highest matching value.
+For most mundane uses, the default handlers are adequate.
+However, for more advanced users, the power is present to tweak every
+aspect of what happens.
+.Pp
+The
+.Nm
+utility
+reads
+.Pa /etc/devd.conf
+or the alternate configuration file specified with a
+.Fl f
+option and uses that file to drive the rest of the process.
+While the format of this file is described in
+.Xr devd.conf 5 ,
+some basics are covered here.
+In the
+.Ic options
+section, one can define multiple directories to search
+for config files.
+All files in these directories whose names match the pattern
+.Pa *.conf
+are parsed.
+These files are intended to be installed by third party vendors that
+wish to hook into the
+.Nm
+system without modifying the user's other
+config files.
+.Pp
+All messages that
+.Nm
+receives are forwarded to the
+.Ux
+domain socket at
+.Pa /var/run/devd.pipe .
+.Sh FILES
+.Bl -tag -width ".Pa /var/run/devd.pipe" -compact
+.It Pa /etc/devd.conf
+The default
+.Nm
+configuration file.
+.It Pa /var/run/devd.pipe
+The socket used by
+.Nm
+to communicate with its clients.
+.El
+.Sh SEE ALSO
+.Xr devctl 4 ,
+.Xr devd.conf 5
+.Sh AUTHORS
+.An M. Warner Losh
diff --git a/sbin/devd/devd.cc b/sbin/devd/devd.cc
new file mode 100644 (file)
index 0000000..4da7b6b
--- /dev/null
@@ -0,0 +1,941 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD: src/sbin/devd/devd.cc,v 1.33 2006/09/17 22:49:26 ru Exp $
+ * $DragonFly: src/sbin/devd/devd.cc,v 1.1 2008/10/03 00:26:21 hasso Exp $
+ */
+
+/*
+ * DEVD control daemon.
+ */
+
+// TODO list:
+//     o devd.conf and devd man pages need a lot of help:
+//       - devd needs to document the unix domain socket
+//       - devd.conf needs more details on the supported statements.
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <err.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <list>
+#include <vector>
+
+#include "devd.h"              /* C compatible definitions */
+#include "devd.hh"             /* C++ class definitions */
+
+#define PIPE "/var/run/devd.pipe"
+#define CF "/etc/devd.conf"
+#define SYSCTL "hw.bus.devctl_disable"
+
+using namespace std;
+
+extern FILE *yyin;
+extern int lineno;
+
+static const char notify = '!';
+static const char nomatch = '?';
+static const char attach = '+';
+static const char detach = '-';
+
+static struct pidfh *pfh;
+
+int Dflag;
+int dflag;
+int nflag;
+int romeo_must_die = 0;
+
+static const char *configfile = CF;
+
+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();
+}
+
+config cfg;
+
+event_proc::event_proc() : _prio(-1)
+{
+       // nothing
+}
+
+event_proc::~event_proc()
+{
+       delete_and_clear(_epsvec);
+}
+
+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 | REG_ICASE);
+}
+
+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;
+}
+
+#include <sys/sockio.h>
+#include <net/if.h>
+#include <net/if_media.h>
+
+media::media(config &, const char *var, const char *type)
+       : _var(var), _type(-1)
+{
+       static struct ifmedia_description media_types[] = {
+               { IFM_ETHER,            "Ethernet" },
+               { IFM_IEEE80211,        "802.11" },
+               { IFM_ATM,              "ATM" },
+               { IFM_CARP,             "CARP" },
+               { -1,                   "unknown" },
+               { 0, NULL },
+       };
+       for (int i = 0; media_types[i].ifmt_string != NULL; i++)
+               if (strcasecmp(type, media_types[i].ifmt_string) == 0) {
+                       _type = media_types[i].ifmt_word;
+                       break;
+               }
+}
+
+media::~media()
+{
+}
+
+bool
+media::do_match(config &c)
+{
+       string value;
+       struct ifmediareq ifmr;
+       bool retval;
+       int s;
+
+       // Since we can be called from both a device attach/detach
+       // context where device-name is defined and what we want,
+       // as well as from a link status context, where subsystem is
+       // the name of interest, first try device-name and fall back
+       // to subsystem if none exists.
+       value = c.get_variable("device-name");
+       if (value.length() == 0)
+               value = c.get_variable("subsystem");
+       if (Dflag)
+               fprintf(stderr, "Testing media type of %s against 0x%x\n",
+                   value.c_str(), _type);
+
+       retval = false;
+
+       s = socket(PF_INET, SOCK_DGRAM, 0);
+       if (s >= 0) {
+               memset(&ifmr, 0, sizeof(ifmr));
+               strncpy(ifmr.ifm_name, value.c_str(), sizeof(ifmr.ifm_name));
+
+               if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) >= 0 &&
+                   ifmr.ifm_status & IFM_AVALID) {
+                       if (Dflag)
+                               fprintf(stderr, "%s has media type 0x%x\n", 
+                                   value.c_str(), IFM_TYPE(ifmr.ifm_active));
+                       retval = (IFM_TYPE(ifmr.ifm_active) == _type);
+               } else if (_type == -1) {
+                       if (Dflag)
+                               fprintf(stderr, "%s has unknown media type\n", 
+                                   value.c_str());
+                       retval = true;
+               }
+               close(s);
+       }
+
+       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, "setting %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);
+       delete_and_clear(_notify_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);
+       lineno = 1;
+       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(configfile);
+       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);
+       sort_vector(_notify_list);
+}
+
+void
+config::open_pidfile()
+{
+       if (pidfile(NULL))
+               errx(1, "devd already running");
+}
+
+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::add_notify(int prio, event_proc *p)
+{
+       p->set_priority(prio);
+       _notify_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 == '-'));
+}
+
+void
+config::expand_one(const char *&src, string &dst)
+{
+       int count;
+       string buffer, varstr;
+
+       src++;
+       // $$ -> $
+       if (*src == '$') {
+               dst.append(src++, 1);
+               return;
+       }
+               
+       // $(foo) -> $(foo)
+       // Not sure if I want to support this or not, so for now we just pass
+       // it through.
+       if (*src == '(') {
+               dst.append("$");
+               count = 1;
+               /* If the string ends before ) is matched , return. */
+               while (count > 0 && *src) {
+                       if (*src == ')')
+                               count--;
+                       else if (*src == '(')
+                               count++;
+                       dst.append(src++, 1);
+               }
+               return;
+       }
+       
+       // ${^A-Za-z] -> $\1
+       if (!isalpha(*src)) {
+               dst.append("$");
+               dst.append(src++, 1);
+               return;
+       }
+
+       // $var -> replace with value
+       do {
+               buffer.append(src++, 1);
+       } while (is_id_char(*src));
+       buffer.append("", 1);
+       varstr = get_variable(buffer.c_str());
+       dst.append(varstr);
+}
+
+const string
+config::expand_string(const string &s)
+{
+       const char *src;
+       string dst;
+
+       src = s.c_str();
+       while (*src) {
+               if (*src == '$')
+                       expand_one(src, dst);
+               else
+                       dst.append(src++, 1);
+       }
+       dst.append("", 1);
+
+       return (dst);
+}
+
+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;
+       const char *s;
+
+       switch (type) {
+       default:
+               return;
+       case notify:
+               l = &_notify_list;
+               s = "notify";
+               break;
+       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;
+               }
+       }
+
+}
+
+\f
+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.
+       switch (type) {
+       case notify:
+               sp = cfg.set_vars(sp);
+               break;
+       case nomatch:
+               //? at location pnp-info on bus
+               sp = strchr(sp, ' ');
+               if (sp == NULL)
+                       return; /* Can't happen? */
+               *sp++ = '\0';
+               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);
+               break;
+       case attach:    /*FALLTHROUGH*/
+       case detach:
+               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);
+               break;
+       }
+       
+       cfg.find_and_execute(type);
+       cfg.pop_var_table();
+}
+
+int
+create_socket(const char *name)
+{
+       int fd, slen;
+       struct sockaddr_un sun;
+
+       if ((fd = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0)
+               err(1, "socket");
+       bzero(&sun, sizeof(sun));
+       sun.sun_family = AF_UNIX;
+       strlcpy(sun.sun_path, name, sizeof(sun.sun_path));
+       slen = SUN_LEN(&sun);
+       unlink(name);
+       if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
+               err(1, "fcntl");
+       if (bind(fd, (struct sockaddr *) & sun, slen) < 0)
+               err(1, "bind");
+       listen(fd, 4);
+       chown(name, 0, 0);      /* XXX - root.wheel */
+       chmod(name, 0666);
+       return (fd);
+}
+
+list<int> clients;
+
+void
+notify_clients(const char *data, int len)
+{
+       list<int> bad;
+       list<int>::const_iterator i;
+
+       for (i = clients.begin(); i != clients.end(); i++) {
+               if (write(*i, data, len) <= 0) {
+                       bad.push_back(*i);
+                       close(*i);
+               }
+       }
+
+       for (i = bad.begin(); i != bad.end(); i++)
+               clients.erase(find(clients.begin(), clients.end(), *i));
+}
+
+void
+new_client(int fd)
+{
+       int s;
+
+       s = accept(fd, NULL, NULL);
+       if (s != -1)
+               clients.push_back(s);
+}
+
+static void
+event_loop(void)
+{
+       int rv;
+       int fd;
+       char buffer[DEVCTL_MAXBUF];
+       int once = 0;
+       int server_fd, max_fd;
+       timeval tv;
+       fd_set fds;
+
+       fd = open(PATH_DEVCTL, O_RDONLY);
+       if (fd == -1)
+               err(1, "Can't open devctl device %s", PATH_DEVCTL);
+       if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0)
+               err(1, "Can't set close-on-exec flag on devctl");
+       server_fd = create_socket(PIPE);
+       max_fd = max(fd, server_fd) + 1;
+       while (1) {
+               if (romeo_must_die)
+                       break;
+               if (!once && !dflag && !nflag) {
+                       // Check to see if we have any events pending.
+                       tv.tv_sec = 0;
+                       tv.tv_usec = 0;
+                       FD_ZERO(&fds);
+                       FD_SET(fd, &fds);
+                       rv = select(fd + 1, &fds, &fds, &fds, &tv);
+                       // No events -> we've processed all pending events
+                       if (rv == 0) {
+                               if (Dflag)
+                                       fprintf(stderr, "Calling daemon\n");
+                               daemon(0, 0);
+                               cfg.open_pidfile();
+                               once++;
+                       }
+               }
+               FD_ZERO(&fds);
+               FD_SET(fd, &fds);
+               FD_SET(server_fd, &fds);
+               rv = select(max_fd, &fds, NULL, NULL, NULL);
+               if (rv == -1) {
+                       if (errno == EINTR)
+                               continue;
+                       err(1, "select");
+               }
+               if (FD_ISSET(fd, &fds)) {
+                       rv = read(fd, buffer, sizeof(buffer) - 1);
+                       if (rv > 0) {
+                               notify_clients(buffer, rv);
+                               buffer[rv] = '\0';
+                               while (buffer[--rv] == '\n')
+                                       buffer[rv] = '\0';
+                               process_event(buffer);
+                       } else if (rv < 0) {
+                               if (errno != EINTR)
+                                       break;
+                       } else {
+                               /* EOF */
+                               break;
+                       }
+               }
+               if (FD_ISSET(server_fd, &fds))
+                       new_client(server_fd);
+       }
+       close(fd);
+}
+\f
+/*
+ * 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);
+}
+
+void
+add_notify(int prio, event_proc *p)
+{
+       cfg.add_notify(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);
+}
+
+eps *
+new_media(const char *var, const char *re)
+{
+       eps *e = new media(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));
+}
+
+\f
+
+static void
+gensighand(int)
+{
+       romeo_must_die++;
+       unlink("/var/run/devd.pid");    /* XXX */
+       _exit(0);
+}
+
+static void
+usage()
+{
+       fprintf(stderr, "usage: %s [-Ddn] [-f file]\n", getprogname());
+       exit(1);
+}
+
+static void
+check_devd_enabled()
+{
+       int val = 0;
+       size_t len;
+
+       len = sizeof(val);
+       if (sysctlbyname(SYSCTL, &val, &len, NULL, 0) != 0)
+               errx(1, "devctl sysctl missing from kernel!");
+       if (val) {
+               warnx("Setting " SYSCTL " to 0");
+               val = 0;
+               sysctlbyname(SYSCTL, NULL, NULL, &val, sizeof(val));
+       }
+}
+
+/*
+ * main
+ */
+int
+main(int argc, char **argv)
+{
+       int ch;
+
+       check_devd_enabled();
+       while ((ch = getopt(argc, argv, "Ddf:n")) != -1) {
+               switch (ch) {
+               case 'D':
+                       Dflag++;
+                       break;
+               case 'd':
+                       dflag++;
+                       break;
+               case 'f':
+                       configfile = optarg;
+                       break;
+               case 'n':
+                       nflag++;
+                       break;
+               default:
+                       usage();
+               }
+       }
+
+       cfg.parse();
+       if (!dflag && nflag) {
+               if (Dflag)
+                       fprintf(stderr, "Calling daemon\n");
+               daemon(0, 0);
+               cfg.open_pidfile();
+       }
+       signal(SIGPIPE, SIG_IGN);
+       signal(SIGHUP, gensighand);
+       signal(SIGINT, gensighand);
+       signal(SIGTERM, gensighand);
+       event_loop();
+       return (0);
+}
diff --git a/sbin/devd/devd.conf.5 b/sbin/devd/devd.conf.5
new file mode 100644 (file)
index 0000000..4d06ff3
--- /dev/null
@@ -0,0 +1,432 @@
+.\"
+.\" Copyright (c) 2002 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. The name of the author may not be used to endorse or promote products
+.\"    derived from this software without specific prior written permission.
+.\"
+.\" 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.
+.\"
+.\" $FreeBSD: src/sbin/devd/devd.conf.5,v 1.11 2006/10/24 20:20:41 ru Exp $
+.\" $DragonFly: src/sbin/devd/devd.conf.5,v 1.1 2008/10/03 00:26:21 hasso Exp $
+.\"
+.\" The section on comments was taken from named.conf.5, which has the
+.\" following copyright:
+.\" Copyright (c) 1999-2000 by Internet Software Consortium
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+.\" ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+.\" CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+.\" DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+.\" PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+.\" ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+.\" SOFTWARE.
+.\"
+.Dd October 25, 2006
+.Dt DEVD.CONF 5
+.Os
+.Sh NAME
+.Nm devd.conf
+.Nd configuration file for
+.Xr devd 8
+.Sh DESCRIPTION
+.Ss General Syntax
+A
+.Xr devd 8
+configuration consists of two general features, statements
+and comments.
+All statements end with a semicolon.
+Many statements can contain substatements, which are also
+terminated with a semicolon.
+.Pp
+The following statements are supported:
+.Bl -tag -width ".Ic options"
+.It Ic attach
+Specifies various matching criteria and actions to perform when
+a newly attached device matches said criteria.
+.It Ic detach
+Specifies various matching criteria and actions to perform when
+a newly detached device matches said criteria.
+.It Ic nomatch
+Specifies various matching criteria and actions to perform when
+no device driver currently loaded in the kernel claims a (new)
+device.
+.It Ic notify
+Specifies various matching criteria and actions to perform when the kernel
+sends an event notification to userland.
+.It Ic options
+Specifies various options and parameters for the operation of
+.Xr devd 8 .
+.El
+.Pp
+Statements may occur in any order in the configuration file, and may be
+repeated as often as required.
+Further details on the syntax and meaning of each statement and their
+substatements are explained below.
+.Pp
+Each statement, except
+.Ic options
+has a priority (an arbitrary number) associated with it, where
+.Ql 0
+is defined as the lowest priority.
+If two statements match the same event, only the action of the statement with
+highest priority will be executed.
+In this way generic statements can be overridden for devices or
+notifications that require special attention.
+.Pp
+The general syntax of a statement is:
+.Pp
+.Bd -literal -offset indent
+statement priority {
+       substatement "value";
+       ...
+       substatement "value";
+};
+.Ed
+.Ss Sub-statements
+The following sub-statements are supported within the
+.Ic options
+statement.
+.Bl -tag -width ".Ic directory"
+.It Ic directory Qq Ar /some/path ;
+Adds the given directory to the list of directories from which
+.Xr devd 8
+will read
+configuration files.
+Any number of
+.Ic directory
+statements can be used.
+.\" .It Ic pid-file Qq Pa /var/run/devd.pid ;
+.\" Specifies PID file.
+.It Ic set Ar regexp-name Qq Ar (some|regexp) ;
+Creates a regular expression and assigns it to the variable
+.Ar regexp-name .
+The variable is avaiable throughout the rest of
+the configuration file.
+All regular expressions have an implicit
+.Ql ^$
+around them.
+.El
+.Pp
+The following sub-statements are supported within the
+.Ic attach
+and
+.Ic detach
+statements.
+.Bl -tag -width ".Ic directory"
+.It Ic action Qq Ar command ;
+Command to execute upon a successful match.
+Example
+.Dq Li "/etc/pccard_ether $device-name start" .
+.It Ic class Qq Ar string ;
+This is shorthand for
+.Dq Ic match Qo Li class Qc Qq Ar string .
+.It Ic device-name Qq string ;
+This is shorthand for
+.Dq Ic match Qo Li device-name Qc Qq Ar string .
+This matches a device named
+.Ar string ,
+which is allowed to be a regular expression or a variable previously created
+containing a regular expression.
+The
+.Dq Li device-name
+variable
+is available for later use with the
+.Ic action
+statement.
+.It Ic match Qo Ar variable Qc Qq Ar value ;
+Matches the content of
+.Ar value
+against
+.Ar variable ;
+the content of
+.Ar value
+may be a regular expression.
+Not required during
+.Ic attach
+nor
+.Ic detach
+events since the
+.Ic device-name
+statement takes care of all device matching.
+For a partial list of variables, see below.
+.It Ic media-type Qq Ar string ;
+For network devices,
+.Ic media-type
+will match devices that have the given media type.
+Valid media types are:
+.Dq Li Ethernet ,
+.Dq Li 802.11 ,
+.Dq Li ATM ,
+and
+.Dq Li CARP .
+.It Ic subdevice Qq Ar string ;
+This is shorthand for
+.Dq Ic match Qo Li subdevice Qc Qq Ar string .
+.El
+.Pp
+The following sub-statements are supported within the
+.Ic nomatch
+statement.
+.Bl -tag -width ".Ic directory"
+.It Ic action Qq Ar command ;
+Same as above.
+.It Ic match Qo Ar variable Qc Qq Ar value ;
+Matches the content of
+.Ar value
+against
+.Ar variable ;
+the content of
+.Ar value
+may be a regular expression.
+For a partial list of variables, see below.
+.El
+.Pp
+The following sub-statements are supported within the
+.Ic notify
+statement.
+The
+.Dq Li notify
+variable is avaiable inside this statement and contains, a value, depending
+on which system and subsystem that delivered the event.
+.Bl -tag -width ".Ic directory"
+.It Ic action Qq Ar command ;
+Command to execute upon a successful match.
+Example
+.Dq Li "/etc/rc.d/power_profile $notify" .
+.It Ic match Qo Ar system | subsystem | type | notify Qc Qq Ar value ;
+Any number of
+.Ic match
+statements can exist within a
+.Ic notify
+statement;
+.Ar value
+can be either a fixed string or a regular expression.
+Below is a list of avaiable systems, subsystems, and types.
+.It Ic media-type Qq Ar string ;
+See above.
+.El
+.Ss Variables that can be used with the match statement
+A partial list of variables and their possible values that can be used together
+with the
+.Ic match
+statement.
+.Pp
+.Bl -tag -width ".Li manufacturer" -compact
+.It Ic Variable
+.Ic Description
+.It Li bus
+Device name of parent bus.
+.It Li cisproduct
+CIS-product.
+.It Li cisvendor
+CIS-vendor.
+.It Li class
+Device class.
+.It Li device
+Device ID.
+.It Li device-name
+Name of attached/detached device.
+.It Li function
+Card functions.
+.It Li manufacturer
+Manufacturer ID (pccard).
+.It Li notify
+Match the value of the
+.Dq Li notify
+variable.
+.It Li product
+Product ID (pccard).
+.It Li serial
+Serial Number (USB).
+.It Li slot
+Card slot.
+.It Li subvendor
+Sub-vendor ID.
+.It Li subdevice
+Sub-device ID.
+.It Li subsystem
+Matches a subsystem of a system, see below.
+.It Li system
+Matches a system type, see below.
+.It Li type
+Type of notification, see below.
+.It Li vendor
+Vendor ID.
+.El
+.Ss Notify matching
+A partial list of systems, subsystems, and types used within the
+.Ic notify
+mechanism.
+.Pp
+.Bl -tag -width ".Li IFNET" -compact
+.It Sy System
+.It Li ACPI
+Events related to the ACPI subsystem.
+.Bl -tag -width ".Sy Subsystem" -compact
+.It Sy Subsystem
+.It Li ACAD
+AC line state ($notify=0x00 is offline, 0x01 is online).
+.It Li Button
+Button state ($notify=0x00 is power, 0x01 is sleep).
+.It Li CMBAT
+Battery events.
+.It Li Lid
+Lid state ($notify=0x00 is closed, 0x01 is open).
+.It Li Thermal
+Thermal zone events.
+.El
+.Pp
+.It Li IFNET
+Events related to the network subsystem.
+.Bl -tag -width ".Sy Subsystem" -compact
+.It Sy Subsystem
+.It Ar interface
+The
+.Dq subsystem
+is the actual name of the network interface on which the event
+took place.
+.Bl -tag -width ".Li LINK_DOWN" -compact
+.It Sy Type
+.It Li LINK_UP
+Carrier status changed to UP.
+.It Li LINK_DOWN
+Carrier status changed to DOWN.
+.El
+.El
+.El
+.Pp
+A link state change to UP on the interface
+.Dq Li fxp0
+would result in the following notify event:
+.Bd -literal -offset indent
+system=IFNET, subsystem=fxp0, type=LINK_UP
+.Ed
+.Pp
+An AC line state change to
+.Dq offline
+would result in the following event:
+.Bd -literal -offset indent
+system=ACPI, subsystem=ACAD, notify=0x00
+.Ed
+.Ss Comments
+Comments may appear anywhere that whitespace may appear in a
+configuration file.
+To appeal to programmers of all kinds, they can
+be written in C, C++, or shell/Perl constructs.
+.Pp
+C-style comments start with the two characters
+.Ql /*
+(slash, star) and end with
+.Ql */
+(star, slash).
+Because they are completely delimited with these characters,
+they can be used to comment only a portion of a line or to span
+multiple lines.
+.Pp
+C-style comments cannot be nested.
+For example, the following is
+not valid because the entire comment ends with the first
+.Ql */ :
+.Bd -literal -offset indent
+/* This is the start of a comment.
+   This is still part of the comment.
+/* This is an incorrect attempt at nesting a comment. */
+   This is no longer in any comment. */
+.Ed
+.Pp
+C++-style comments start with the two characters
+.Ql //
+(slash, slash) and continue to the end of the physical line.
+They cannot be continued across multiple physical lines; to have
+one logical comment span multiple lines, each line must use the
+.Ql //
+pair.
+For example:
+.Bd -literal -offset indent
+// This is the start of a comment.  The next line
+// is a new comment, even though it is logically
+// part of the previous comment.
+.Ed
+.Sh FILES
+.Bl -tag -width ".Pa /etc/devd.conf" -compact
+.It Pa /etc/devd.conf
+The
+.Xr devd 8
+configuration file.
+.El
+.Sh EXAMPLES
+.Bd -literal
+#
+# This will catch link down events on the interfaces fxp0 and ath0
+#
+notify 0 {
+       match "system"                  "IFNET";
+       match "subsystem"               "(fxp0|ath0)";
+       match "type"                    "LINK_DOWN";
+       action "logger $subsystem is DOWN";
+};
+
+#
+# Match lid open/close events
+# These can be combined to a single event, by passing the
+# value of $notify to the external script.
+#
+notify 0 {
+       match "system"                  "ACPI";
+       match "subsystem"               "Lid";
+       match "notify"                  "0x00";
+       action "logger Lid closed, we can sleep now!";
+};
+
+notify 0 {
+       match "system"                  "ACPI";
+       match "subsystem"               "Lid";
+       match "notify"                  "0x01";
+       action "logger Lid opened, the sleeper must awaken!";
+};
+
+#
+# Try to configure ath and wi devices with pccard_ether
+# as they are attached.
+#
+attach 0 {
+        device-name "(ath|wi)[0-9]+";
+        action "/etc/pccard_ether $device-name start";
+};
+
+#
+# Stop ath and wi devices as they are detached from
+# the system.
+#
+detach 0 {
+        device-name "(ath|wi)[0-9]+";
+        action "/etc/pccard_ether $device-name stop";
+};
+.Ed
+.Pp
+The installed
+.Pa /etc/devd.conf
+has many additional examples.
+.Sh SEE ALSO
+.Xr devd 8
diff --git a/sbin/devd/devd.h b/sbin/devd/devd.h
new file mode 100644 (file)
index 0000000..82395eb
--- /dev/null
@@ -0,0 +1,59 @@
+/*-
+ * DEVD (Device action daemon)
+ *
+ * Copyright (c) 2002 M. Warner Losh <imp@freebsd.org>.
+ * 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.
+ *
+ * $FreeBSD: src/sbin/devd/devd.h,v 1.5 2005/07/10 03:37:15 imp Exp $
+ * $DragonFly: src/sbin/devd/devd.h,v 1.1 2008/10/03 00:26:21 hasso Exp $
+ */
+
+#ifndef DEVD_H
+#define DEVD_H
+
+/** @warning This file needs to be purely 'C' compatible.
+ */
+struct event_proc;
+struct eps;
+__BEGIN_DECLS
+void add_attach(int, struct event_proc *);
+void add_detach(int, struct event_proc *);
+void add_directory(const char *);
+void add_nomatch(int, struct event_proc *);
+void add_notify(int, struct event_proc *);
+struct event_proc *add_to_event_proc(struct event_proc *, struct eps *);
+struct eps *new_match(const char *, const char *);
+struct eps *new_media(const char *, const char *);
+struct eps *new_action(const char *);
+void set_pidfile(const char *);
+void set_variable(const char *, const char *);
+void yyerror(const char *s);
+int  yylex(void);
+int  yyparse(void);
+__END_DECLS
+
+#define PATH_DEVCTL    "/dev/devctl"
+#define DEVCTL_MAXBUF  1025
+
+#endif /* DEVD_H */
diff --git a/sbin/devd/devd.hh b/sbin/devd/devd.hh
new file mode 100644 (file)
index 0000000..338b5d3
--- /dev/null
@@ -0,0 +1,184 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD: src/sbin/devd/devd.hh,v 1.5 2007/12/21 01:00:04 imp Exp $
+ * $DragonFly: src/sbin/devd/devd.hh,v 1.1 2008/10/03 00:26:21 hasso Exp $
+ */
+
+#ifndef DEVD_HH
+#define DEVD_HH
+
+class config;
+
+/**
+ * var_list is a collection of variables.  These collections of variables
+ * are stacked up and popped down for each event that we have to process.
+ * We have multiple levels so that we can push variables that are unique
+ * to the event in question, in addition to having global variables.  This
+ * allows for future flexibility.
+ */
+class var_list
+{
+public:
+       var_list() {}
+       virtual ~var_list() {}
+       /** Set a variable in this var list.
+        */
+       void set_variable(const std::string &var, const std::string &val);
+       /** Get the variable out of this, and no other, var_list.  If
+        * no variable of %var is set, then %bogus will be returned.
+        */
+       const std::string &get_variable(const std::string &var) const;
+       /** Is there a variable of %var set in thi stable?
+        */
+       bool is_set(const std::string &var) const;
+       /** A completely bogus string.
+        */
+       static const std::string bogus;
+       static const std::string nothing;
+private:
+       std::map<std::string, std::string> _vars;
+};
+
+/**
+ * eps is short for event_proc_single.  It is a single entry in an
+ * event_proc.  Each keyword needs its own subclass from eps.
+ */
+class eps
+{
+public:
+       eps() {}
+       virtual ~eps() {}
+       /** Does this eps match the current config?
+        */
+       virtual bool do_match(config &) = 0;
+       /** Perform some action for this eps.
+        */
+       virtual bool do_action(config &) = 0;
+};
+
+/**
+ * match is the subclass used to match an individual variable.  Its
+ * actions are nops.
+ */
+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:
+       std::string _var;
+       std::string _re;
+       regex_t _regex;
+};
+
+/**
+ * media is the subclass used to match an individual variable.  Its
+ * actions are nops.
+ */
+class media : public eps
+{
+public:
+       media(config &, const char *var, const char *type);
+       virtual ~media();
+       virtual bool do_match(config &);
+       virtual bool do_action(config &) { return true; }
+private:
+       std::string _var;
+       int _type;
+};
+
+/**
+ * action is used to fork a process.  It matches everything.
+ */
+class action : public eps
+{
+public:
+       action(const char *cmd);
+       virtual ~action();
+       virtual bool do_match(config &) { return true; }
+       virtual bool do_action(config &);
+private:
+       std::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;
+       std::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 add_notify(int, event_proc *);
+       void set_pidfile(const char *);
+       void reset();
+       void parse();
+       void open_pidfile();
+       void write_pidfile();
+       void remove_pidfile();
+       void push_var_table();
+       void pop_var_table();
+       void set_variable(const char *var, const char *val);
+       const std::string &get_variable(const std::string &var);
+       const std::string expand_string(const std::string &var);
+       char *set_vars(char *);
+       void find_and_execute(char);
+protected:
+       void sort_vector(std::vector<event_proc *> &);
+       void parse_one_file(const char *fn);
+       void parse_files_in_dir(const char *dirname);
+       void expand_one(const char *&src, std::string &dst);
+       bool is_id_char(char);
+       bool chop_var(char *&buffer, char *&lhs, char *&rhs);
+private:
+       std::vector<std::string> _dir_list;
+       std::string _pidfile;
+       std::vector<var_list *> _var_list_table;
+       std::vector<event_proc *> _attach_list;
+       std::vector<event_proc *> _detach_list;
+       std::vector<event_proc *> _nomatch_list;
+       std::vector<event_proc *> _notify_list;
+};
+
+#endif /* DEVD_HH */
diff --git a/sbin/devd/parse.y b/sbin/devd/parse.y
new file mode 100644 (file)
index 0000000..2b32c9d
--- /dev/null
@@ -0,0 +1,153 @@
+%{
+/*-
+ * DEVD (Device action daemon)
+ *
+ * Copyright (c) 2002 M. Warner Losh <imp@freebsd.org>.
+ * 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.
+ *
+ * $FreeBSD: src/sbin/devd/parse.y,v 1.5 2005/07/10 03:37:15 imp Exp $
+ * $DragonFly: src/sbin/devd/parse.y,v 1.1 2008/10/03 00:26:21 hasso Exp $
+ */
+
+#include "devd.h"
+#include <stdio.h>
+#include <string.h>
+
+%}
+
+%union {
+       char *str;
+       int i;
+       struct eps *eps;        /* EventProcStatement */
+       struct event_proc *eventproc;
+}
+
+%token SEMICOLON BEGINBLOCK ENDBLOCK COMMA
+%token <i> NUMBER
+%token <str> STRING
+%token <str> ID
+%token OPTIONS SET DIRECTORY PID_FILE DEVICE_NAME ACTION MATCH
+%token ATTACH DETACH NOMATCH NOTIFY MEDIA_TYPE CLASS SUBDEVICE
+
+%type <eventproc> match_or_action_list
+%type <eps> match_or_action match action
+
+%%
+
+config_file
+       : config_list
+       |
+       ;
+
+config_list
+       : config
+       | config_list config
+       ;
+
+config
+       : option_block
+       | attach_block
+       | detach_block
+       | nomatch_block
+       | notify_block
+       ;
+
+option_block
+       : OPTIONS BEGINBLOCK options ENDBLOCK SEMICOLON
+       ;
+
+options
+       : option
+       | options option
+
+option
+       : directory_option
+       | pid_file_option
+       | set_option
+       ;
+
+directory_option
+       : DIRECTORY STRING SEMICOLON { add_directory($2); }
+       ;
+
+pid_file_option
+       : PID_FILE STRING SEMICOLON { set_pidfile($2); }
+       ;
+
+set_option
+       : SET ID STRING SEMICOLON { set_variable($2, $3); }
+       ;
+
+attach_block
+       : ATTACH NUMBER BEGINBLOCK match_or_action_list ENDBLOCK SEMICOLON
+               { add_attach($2, $4); }
+       | ATTACH NUMBER BEGINBLOCK ENDBLOCK SEMICOLON
+       ;
+
+detach_block
+       : DETACH NUMBER BEGINBLOCK match_or_action_list ENDBLOCK SEMICOLON
+               { add_detach($2, $4); }
+       | DETACH NUMBER BEGINBLOCK ENDBLOCK SEMICOLON
+       ;
+
+nomatch_block
+       : NOMATCH NUMBER BEGINBLOCK match_or_action_list ENDBLOCK SEMICOLON
+               { add_nomatch($2, $4); }
+       | NOMATCH NUMBER BEGINBLOCK ENDBLOCK SEMICOLON
+       ;
+
+notify_block
+       : NOTIFY NUMBER BEGINBLOCK match_or_action_list ENDBLOCK SEMICOLON
+               { add_notify($2, $4); }
+       | NOTIFY NUMBER BEGINBLOCK ENDBLOCK SEMICOLON
+       ;
+
+match_or_action_list
+       : match_or_action { $$ = add_to_event_proc( NULL, $1); }
+       | match_or_action_list match_or_action
+                       { $$ = add_to_event_proc($1, $2); }
+       ;
+
+match_or_action
+       : match
+       | action
+       ;
+
+match
+       : MATCH STRING STRING SEMICOLON { $$ = new_match($2, $3); }
+       | DEVICE_NAME STRING SEMICOLON
+               { $$ = new_match(strdup("device-name"), $2); }
+       | MEDIA_TYPE STRING SEMICOLON
+               { $$ = new_media(strdup("media-type"), $2); }
+       | CLASS STRING SEMICOLON
+               { $$ = new_match(strdup("class"), $2); }
+       | SUBDEVICE STRING SEMICOLON
+               { $$ = new_match(strdup("subdevice"), $2); }
+       ;
+
+action
+       : ACTION STRING SEMICOLON       { $$ = new_action($2); }
+       ;
+
+%%
diff --git a/sbin/devd/token.l b/sbin/devd/token.l
new file mode 100644 (file)
index 0000000..83cc337
--- /dev/null
@@ -0,0 +1,111 @@
+%{
+/*-
+ * DEVD (Device action daemon)
+ *
+ * Copyright (c) 2002 M. Warner Losh <imp@freebsd.org>.
+ * 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.
+ *
+ * $FreeBSD: src/sbin/devd/token.l,v 1.7 2008/03/21 20:38:28 imp Exp $
+ * $DragonFly: src/sbin/devd/token.l,v 1.1 2008/10/03 00:26:21 hasso Exp $
+ */
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include "devd.h"
+#include "y.tab.h"
+
+int lineno = 1;
+#define YY_NO_UNPUT
+
+static void
+update_lineno(const char *cp)
+{
+       while (*cp)     
+               if (*cp++ == '\n')
+                       lineno++;
+}
+
+%}
+
+%%
+
+[ \t]+                 ;
+\n                     lineno++;
+;                      { return SEMICOLON; }
+#.*$                   ;
+\/\/.*$                        ;
+\/\*([^*]|(\*+([^*\/])))*\*+\/ { update_lineno(yytext); }
+\{                     { return BEGINBLOCK; }
+\}                     { return ENDBLOCK; }
+[0-9]+                 { yylval.i = atoi(yytext); return NUMBER; }
+\"[^"]+\"              {
+                               int len = strlen(yytext) - 2;
+                               char *walker;
+                               int i;
+                               update_lineno(yytext);
+                               if ((yylval.str = (char *) malloc(len + 1)) == NULL)
+                                       goto out;
+                               walker = yylval.str;
+                               for (i = 1; i <= len; i++) {
+                                       if (yytext[i] == '\\' && 
+                                           yytext[i + 1] == '\n') {
+                                               i += 2;
+                                               while(isspace(yytext[i]))
+                                                       i++;
+                                       }
+                                       *walker++ = yytext[i];
+                               }
+                               *walker++ = '\0';
+                       out:;
+                               return STRING;
+                       }
+
+
+options                        { return OPTIONS; }
+set                    { return SET; }
+directory              { return DIRECTORY; }
+pid-file               { return PID_FILE; }
+attach                 { return ATTACH; }
+detach                 { return DETACH; }
+device-name            { return DEVICE_NAME; }
+media-type             { return MEDIA_TYPE; }
+class                  { return CLASS; }
+subdevice              { return SUBDEVICE; }
+action                 { return ACTION; }
+match                  { return MATCH; }
+nomatch                        { return NOMATCH; }
+notify                 { return NOTIFY; }
+[A-Za-z][A-Za-z0-9_-]* {
+                               yylval.str = strdup(yytext);
+                               return ID;
+                       }
+%%
+
+void
+yyerror(const char *s)
+{
+       syslog(LOG_ERR, "line %d: %s%s %s.\n", lineno, yytext, yytext?":":"", s);
+}
index 99ee3ed..77cf212 100644 (file)
@@ -1,6 +1,6 @@
 #      @(#)Makefile    8.1 (Berkeley) 6/18/93
 # $FreeBSD: src/share/man/man4/Makefile,v 1.83.2.66 2003/06/04 17:10:30 sam Exp $
-# $DragonFly: src/share/man/man4/Makefile,v 1.87 2008/08/28 10:32:27 hasso Exp $
+# $DragonFly: src/share/man/man4/Makefile,v 1.88 2008/10/03 00:26:21 hasso Exp $
 
 MAN=   aac.4 \
        acpi.4 \
@@ -55,6 +55,7 @@ MAN=  aac.4 \
        dcons_crom.4 \
        ddb.4 \
        de.4 \
+       devctl.4 \
        disc.4 \
        divert.4 \
        dpt.4 \
diff --git a/share/man/man4/devctl.4 b/share/man/man4/devctl.4
new file mode 100644 (file)
index 0000000..5b9ace3
--- /dev/null
@@ -0,0 +1,129 @@
+.\"
+.\" Copyright (c) 2002 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. The name of the author may not be used to endorse or promote products
+.\"    derived from this software without specific prior written permission.
+.\"
+.\" 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.
+.\"
+.\" $FreeBSD: src/share/man/man4/devctl.4,v 1.5 2006/08/04 07:56:32 yar Exp $
+.\" $DragonFly: src/share/man/man4/devctl.4,v 1.1 2008/10/03 00:26:21 hasso Exp $
+.\"
+.Dd February 11, 2003
+.Dt DEVCTL 4
+.Os
+.Sh NAME
+.Nm devctl
+.Nd "device event reporting and device control interface"
+.Sh DESCRIPTION
+The
+.Nm
+device is used to report device events from the kernel.
+Future versions will allow for some device control as well.
+.Sh IMPLEMENTATION NOTES
+This design allows only one reader for
+.Pa /dev/devctl .
+This is not desirable
+in the long run, but will get a lot of hair out of this implementation.
+Maybe we should make this device a clonable device.
+.Pp
+Also note: we specifically do not attach a device to the
+.Vt device_t
+tree
+to avoid potential chicken and egg problems.
+One could argue that all of this belongs to the root node.
+One could also further argue that the
+.Xr sysctl 3
+interface that we have now might more properly be an
+.Xr ioctl 2
+interface.
+.Pp
+.Dv SIGIO
+support is included in the driver.
+However, the author is not sure that the
+.Dv SIGIO
+support is done correctly.
+It was copied from a driver that had
+.Dv SIGIO
+support that likely has not been
+tested since
+.Fx 3.4
+or
+.Fx 2.2.8 !
+.Pp
+The read channel for this device is used to report changes to
+userland in realtime.
+We return one record at a time.
+If you try to read this device a character at a time, you will lose
+the rest of the data.
+Listening programs are expected to cope.
+.Pp
+The sysctl and boot parameter
+.Va hw.bus.devctl_disable
+is used to disable
+.Nm
+when no
+.Xr devd 8
+is running.
+.Sh PROTOCOL
+The
+.Nm
+device
+uses an
+.Tn ASCII
+protocol.
+The driver returns one record at a time to its readers.
+Each record is terminated with a newline.
+The first character of the record is the event type.
+.Pp
+.Bl -column -compact "Type" "Description"
+.Em "Type      Description"
+!      A notify event, such as a link state change.
++      Device node in tree attached.
+-      Device node in tree detached.
+?      Unknown device detected.
+.El
+.Ss Message Formats
+Except for the first character in the record, attach and detach
+messages have the same format.
+.Pp
+.D1 Ar T Ns Ar dev Li at Ar parent Li on Ar location
+.Pp
+.Bl -column -compact "location" "Description"
+.Em "Part      Description"
+.It Ar T Ta "+ or -"
+.It Ar dev Ta "The device name that was attached/detached."
+.It Ar parent Ta "The device name of the parent bus that attached the device."
+.It Ar location Ta "Bus specific location information."
+.El
+.Pp
+The nomatch messages can be used to load devices driver.
+If you load a device driver, then one of two things can happen.
+If the device driver attaches to something, you will get a device
+attached message.
+If it does not, then nothing will happen.
+.Pp
+The attach and detach messages arrive after the event.
+This means one cannot use the attach message to load an alternate
+driver.
+The attach message driver has already claimed this device.
+One cannot use the detach messages to flush data to the device.
+The device is already gone.
+.Sh SEE ALSO
+.Xr devd 8
index 92893c1..16bb416 100644 (file)
@@ -27,7 +27,7 @@
  * SUCH DAMAGE.
  *
  *     $FreeBSD: src/sys/dev/acpica/acpi.c,v 1.160 2004/06/14 03:52:19 njl Exp $
- *     $DragonFly: src/sys/dev/acpica5/acpi.c,v 1.36 2008/09/05 10:28:35 hasso Exp $
+ *     $DragonFly: src/sys/dev/acpica5/acpi.c,v 1.37 2008/10/03 00:26:21 hasso Exp $
  */
 
 #include "opt_acpi.h"
@@ -2758,9 +2758,7 @@ acpi_UserNotify(const char *subsystem, ACPI_HANDLE h, uint8_t notify)
     if (ACPI_FAILURE(status))
        return;
     ksnprintf(notify_buf, sizeof(notify_buf), "notify=0x%02x", notify);
-#if 0
     devctl_notify("ACPI", subsystem, handle_buf.Pointer, notify_buf);
-#endif
     AcpiOsFree(handle_buf.Pointer);
 }
 
index abc3429..55d54e5 100644 (file)
@@ -24,7 +24,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * $FreeBSD: src/sys/dev/coretemp/coretemp.c,v 1.2 2007/08/23 10:53:03 des Exp $
- * $DragonFly: src/sys/dev/powermng/coretemp/coretemp.c,v 1.2 2008/09/02 18:12:48 hasso Exp $
+ * $DragonFly: src/sys/dev/powermng/coretemp/coretemp.c,v 1.3 2008/10/03 00:26:21 hasso Exp $
  */
 
 /*
@@ -269,9 +269,7 @@ coretemp_get_temp(device_t dev)
                device_printf(dev, "critical temperature detected, "
                    "suggest system shutdown\n");
                ksnprintf(stemp, sizeof(stemp), "%d", temp);
-#if 0
                devctl_notify("coretemp", "Thermal", stemp, "notify=0xcc");
-#endif
        }
 
        return (temp);
index cc30836..709f2b6 100644 (file)
@@ -24,7 +24,7 @@
  * SUCH DAMAGE.
  *
  * $FreeBSD: src/sys/kern/subr_bus.c,v 1.54.2.9 2002/10/10 15:13:32 jhb Exp $
- * $DragonFly: src/sys/kern/subr_bus.c,v 1.45 2008/09/30 12:20:29 hasso Exp $
+ * $DragonFly: src/sys/kern/subr_bus.c,v 1.46 2008/10/03 00:26:21 hasso Exp $
  */
 
 #include "opt_bus.h"
 #include <sys/systm.h>
 #include <sys/bus.h>
 #include <sys/rman.h>
+#include <sys/device.h>
+#include <sys/lock.h>
+#include <sys/conf.h>
+#include <sys/selinfo.h>
+#include <sys/uio.h>
+#include <sys/filio.h>
+#include <sys/poll.h>
+#include <sys/signalvar.h>
 
 #include <machine/stdarg.h>    /* for device_printf() */
 
@@ -100,6 +108,424 @@ static int do_async_attach = 0;
 static int numasyncthreads;
 TUNABLE_INT("kern.do_async_attach", &do_async_attach);
 
+/*
+ * /dev/devctl implementation
+ */
+
+/*
+ * This design allows only one reader for /dev/devctl.  This is not desirable
+ * in the long run, but will get a lot of hair out of this implementation.
+ * Maybe we should make this device a clonable device.
+ *
+ * Also note: we specifically do not attach a device to the device_t tree
+ * to avoid potential chicken and egg problems.  One could argue that all
+ * of this belongs to the root node.  One could also further argue that the
+ * sysctl interface that we have not might more properly be an ioctl
+ * interface, but at this stage of the game, I'm not inclined to rock that
+ * boat.
+ *
+ * I'm also not sure that the SIGIO support is done correctly or not, as
+ * I copied it from a driver that had SIGIO support that likely hasn't been
+ * tested since 3.4 or 2.2.8!
+ */
+
+static int sysctl_devctl_disable(SYSCTL_HANDLER_ARGS);
+static int devctl_disable = 0;
+TUNABLE_INT("hw.bus.devctl_disable", &devctl_disable);
+SYSCTL_PROC(_hw_bus, OID_AUTO, devctl_disable, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
+    sysctl_devctl_disable, "I", "devctl disable");
+
+#define        CDEV_MAJOR      188
+
+static d_open_t                devopen;
+static d_close_t       devclose;
+static d_read_t                devread;
+static d_ioctl_t       devioctl;
+static d_poll_t                devpoll;
+
+static struct dev_ops devctl_ops = {
+       { "devctl", CDEV_MAJOR, 0 },
+       .d_open =       devopen,
+       .d_close =      devclose,
+       .d_read =       devread,
+       .d_ioctl =      devioctl,
+       .d_poll =       devpoll,
+};
+
+struct dev_event_info
+{
+       char *dei_data;
+       TAILQ_ENTRY(dev_event_info) dei_link;
+};
+
+TAILQ_HEAD(devq, dev_event_info);
+
+static struct dev_softc
+{
+       int     inuse;
+       int     nonblock;
+       struct lock lock;
+       struct selinfo sel;
+       struct devq devq;
+       struct proc *async_proc;
+} devsoftc;
+
+static void
+devinit(void)
+{
+       dev_ops_add(&devctl_ops, -1, 0);
+       make_dev(&devctl_ops, 0, UID_ROOT, GID_WHEEL, 0600, "devctl");
+       lockinit(&devsoftc.lock, "dev mtx", 0, 0);
+       TAILQ_INIT(&devsoftc.devq);
+}
+
+static int
+devopen(struct dev_open_args *ap)
+{
+       if (devsoftc.inuse)
+               return (EBUSY);
+       /* move to init */
+       devsoftc.inuse = 1;
+       devsoftc.nonblock = 0;
+       devsoftc.async_proc = NULL;
+       return (0);
+}
+
+static int
+devclose(struct dev_close_args *ap)
+{
+       devsoftc.inuse = 0;
+       lockmgr(&devsoftc.lock, LK_EXCLUSIVE);
+       wakeup(&devsoftc);
+       lockmgr(&devsoftc.lock, LK_RELEASE);
+
+       return (0);
+}
+
+/*
+ * The read channel for this device is used to report changes to
+ * userland in realtime.  We are required to free the data as well as
+ * the n1 object because we allocate them separately.  Also note that
+ * we return one record at a time.  If you try to read this device a
+ * character at a time, you will lose the rest of the data.  Listening
+ * programs are expected to cope.
+ */
+static int
+devread(struct dev_read_args *ap)
+{
+       struct uio *uio = ap->a_uio;
+       struct dev_event_info *n1;
+       int rv;
+
+       lockmgr(&devsoftc.lock, LK_EXCLUSIVE);
+       while (TAILQ_EMPTY(&devsoftc.devq)) {
+               if (devsoftc.nonblock) {
+                       lockmgr(&devsoftc.lock, LK_RELEASE);
+                       return (EAGAIN);
+               }
+               crit_enter();
+               tsleep_interlock(&devsoftc);
+               lockmgr(&devsoftc.lock, LK_RELEASE);
+               rv = tsleep(&devsoftc, PCATCH, "devctl", 0);
+               crit_exit();
+               lockmgr(&devsoftc.lock, LK_EXCLUSIVE);
+               if (rv) {
+                       /*
+                        * Need to translate ERESTART to EINTR here? -- jake
+                        */
+                       lockmgr(&devsoftc.lock, LK_RELEASE);
+                       return (rv);
+               }
+       }
+       n1 = TAILQ_FIRST(&devsoftc.devq);
+       TAILQ_REMOVE(&devsoftc.devq, n1, dei_link);
+       lockmgr(&devsoftc.lock, LK_RELEASE);
+       rv = uiomove(n1->dei_data, strlen(n1->dei_data), uio);
+       kfree(n1->dei_data, M_BUS);
+       kfree(n1, M_BUS);
+       return (rv);
+}
+
+static int
+devioctl(struct dev_ioctl_args *ap)
+{
+       switch (ap->a_cmd) {
+
+       case FIONBIO:
+               if (*(int*)ap->a_data)
+                       devsoftc.nonblock = 1;
+               else
+                       devsoftc.nonblock = 0;
+               return (0);
+       case FIOASYNC:
+               if (*(int*)ap->a_data)
+                       devsoftc.async_proc = curproc;
+               else
+                       devsoftc.async_proc = NULL;
+               return (0);
+
+               /* (un)Support for other fcntl() calls. */
+       case FIOCLEX:
+       case FIONCLEX:
+       case FIONREAD:
+       case FIOSETOWN:
+       case FIOGETOWN:
+       default:
+               break;
+       }
+       return (ENOTTY);
+}
+
+static int
+devpoll(struct dev_poll_args *ap)
+{
+       int     revents = 0;
+
+       lockmgr(&devsoftc.lock, LK_EXCLUSIVE);
+       if (ap->a_events & (POLLIN | POLLRDNORM)) {
+               if (!TAILQ_EMPTY(&devsoftc.devq))
+                       revents = ap->a_events & (POLLIN | POLLRDNORM);
+               else
+                       selrecord(curthread, &devsoftc.sel);
+       }
+       lockmgr(&devsoftc.lock, LK_RELEASE);
+
+       ap->a_events = revents;
+       return (0);
+}
+
+/**
+ * @brief Return whether the userland process is running
+ */
+boolean_t
+devctl_process_running(void)
+{
+       return (devsoftc.inuse == 1);
+}
+
+/**
+ * @brief Queue data to be read from the devctl device
+ *
+ * Generic interface to queue data to the devctl device.  It is
+ * assumed that @p data is properly formatted.  It is further assumed
+ * that @p data is allocated using the M_BUS malloc type.
+ */
+void
+devctl_queue_data(char *data)
+{
+       struct dev_event_info *n1 = NULL;
+       struct proc *p;
+
+       n1 = kmalloc(sizeof(*n1), M_BUS, M_NOWAIT);
+       if (n1 == NULL)
+               return;
+       n1->dei_data = data;
+       lockmgr(&devsoftc.lock, LK_EXCLUSIVE);
+       TAILQ_INSERT_TAIL(&devsoftc.devq, n1, dei_link);
+       wakeup(&devsoftc);
+       lockmgr(&devsoftc.lock, LK_RELEASE);
+       get_mplock();   /* XXX */
+       selwakeup(&devsoftc.sel);
+       rel_mplock();   /* XXX */
+       p = devsoftc.async_proc;
+       if (p != NULL)
+               ksignal(p, SIGIO);
+}
+
+/**
+ * @brief Send a 'notification' to userland, using standard ways
+ */
+void
+devctl_notify(const char *system, const char *subsystem, const char *type,
+    const char *data)
+{
+       int len = 0;
+       char *msg;
+
+       if (system == NULL)
+               return;         /* BOGUS!  Must specify system. */
+       if (subsystem == NULL)
+               return;         /* BOGUS!  Must specify subsystem. */
+       if (type == NULL)
+               return;         /* BOGUS!  Must specify type. */
+       len += strlen(" system=") + strlen(system);
+       len += strlen(" subsystem=") + strlen(subsystem);
+       len += strlen(" type=") + strlen(type);
+       /* add in the data message plus newline. */
+       if (data != NULL)
+               len += strlen(data);
+       len += 3;       /* '!', '\n', and NUL */
+       msg = kmalloc(len, M_BUS, M_NOWAIT);
+       if (msg == NULL)
+               return;         /* Drop it on the floor */
+       if (data != NULL)
+               ksnprintf(msg, len, "!system=%s subsystem=%s type=%s %s\n",
+                   system, subsystem, type, data);
+       else
+               ksnprintf(msg, len, "!system=%s subsystem=%s type=%s\n",
+                   system, subsystem, type);
+       devctl_queue_data(msg);
+}
+
+/*
+ * Common routine that tries to make sending messages as easy as possible.
+ * We allocate memory for the data, copy strings into that, but do not
+ * free it unless there's an error.  The dequeue part of the driver should
+ * free the data.  We don't send data when the device is disabled.  We do
+ * send data, even when we have no listeners, because we wish to avoid
+ * races relating to startup and restart of listening applications.
+ *
+ * devaddq is designed to string together the type of event, with the
+ * object of that event, plus the plug and play info and location info
+ * for that event.  This is likely most useful for devices, but less
+ * useful for other consumers of this interface.  Those should use
+ * the devctl_queue_data() interface instead.
+ */
+static void
+devaddq(const char *type, const char *what, device_t dev)
+{
+       char *data = NULL;
+       char *loc = NULL;
+       char *pnp = NULL;
+       const char *parstr;
+
+       if (devctl_disable)
+               return;
+       data = kmalloc(1024, M_BUS, M_NOWAIT);
+       if (data == NULL)
+               goto bad;
+
+       /* get the bus specific location of this device */
+       loc = kmalloc(1024, M_BUS, M_NOWAIT);
+       if (loc == NULL)
+               goto bad;
+       *loc = '\0';
+       bus_child_location_str(dev, loc, 1024);
+
+       /* Get the bus specific pnp info of this device */
+       pnp = kmalloc(1024, M_BUS, M_NOWAIT);
+       if (pnp == NULL)
+               goto bad;
+       *pnp = '\0';
+       bus_child_pnpinfo_str(dev, pnp, 1024);
+
+       /* Get the parent of this device, or / if high enough in the tree. */
+       if (device_get_parent(dev) == NULL)
+               parstr = ".";   /* Or '/' ? */
+       else
+               parstr = device_get_nameunit(device_get_parent(dev));
+       /* String it all together. */
+       ksnprintf(data, 1024, "%s%s at %s %s on %s\n", type, what, loc, pnp,
+         parstr);
+       kfree(loc, M_BUS);
+       kfree(pnp, M_BUS);
+       devctl_queue_data(data);
+       return;
+bad:
+       kfree(pnp, M_BUS);
+       kfree(loc, M_BUS);
+       kfree(data, M_BUS);
+       return;
+}
+
+/*
+ * A device was added to the tree.  We are called just after it successfully
+ * attaches (that is, probe and attach success for this device).  No call
+ * is made if a device is merely parented into the tree.  See devnomatch
+ * if probe fails.  If attach fails, no notification is sent (but maybe
+ * we should have a different message for this).
+ */
+static void
+devadded(device_t dev)
+{
+       char *pnp = NULL;
+       char *tmp = NULL;
+
+       pnp = kmalloc(1024, M_BUS, M_NOWAIT);
+       if (pnp == NULL)
+               goto fail;
+       tmp = kmalloc(1024, M_BUS, M_NOWAIT);
+       if (tmp == NULL)
+               goto fail;
+       *pnp = '\0';
+       bus_child_pnpinfo_str(dev, pnp, 1024);
+       ksnprintf(tmp, 1024, "%s %s", device_get_nameunit(dev), pnp);
+       devaddq("+", tmp, dev);
+fail:
+       if (pnp != NULL)
+               kfree(pnp, M_BUS);
+       if (tmp != NULL)
+               kfree(tmp, M_BUS);
+       return;
+}
+
+/*
+ * A device was removed from the tree.  We are called just before this
+ * happens.
+ */
+static void
+devremoved(device_t dev)
+{
+       char *pnp = NULL;
+       char *tmp = NULL;
+
+       pnp = kmalloc(1024, M_BUS, M_NOWAIT);
+       if (pnp == NULL)
+               goto fail;
+       tmp = kmalloc(1024, M_BUS, M_NOWAIT);
+       if (tmp == NULL)
+               goto fail;
+       *pnp = '\0';
+       bus_child_pnpinfo_str(dev, pnp, 1024);
+       ksnprintf(tmp, 1024, "%s %s", device_get_nameunit(dev), pnp);
+       devaddq("-", tmp, dev);
+fail:
+       if (pnp != NULL)
+               kfree(pnp, M_BUS);
+       if (tmp != NULL)
+               kfree(tmp, M_BUS);
+       return;
+}
+
+/*
+ * Called when there's no match for this device.  This is only called
+ * the first time that no match happens, so we don't keep getitng this
+ * message.  Should that prove to be undesirable, we can change it.
+ * This is called when all drivers that can attach to a given bus
+ * decline to accept this device.  Other errrors may not be detected.
+ */
+static void
+devnomatch(device_t dev)
+{
+       devaddq("?", "", dev);
+}
+
+static int
+sysctl_devctl_disable(SYSCTL_HANDLER_ARGS)
+{
+       struct dev_event_info *n1;
+       int dis, error;
+
+       dis = devctl_disable;
+       error = sysctl_handle_int(oidp, &dis, 0, req);
+       if (error || !req->newptr)
+               return (error);
+       lockmgr(&devsoftc.lock, LK_EXCLUSIVE);
+       devctl_disable = dis;
+       if (dis) {
+               while (!TAILQ_EMPTY(&devsoftc.devq)) {
+                       n1 = TAILQ_FIRST(&devsoftc.devq);
+                       TAILQ_REMOVE(&devsoftc.devq, n1, dei_link);
+                       kfree(n1->dei_data, M_BUS);
+                       kfree(n1, M_BUS);
+               }
+       }
+       lockmgr(&devsoftc.lock, LK_RELEASE);
+       return (0);
+}
+
+/* End of /dev/devctl code */
+
 TAILQ_HEAD(,device)    bus_data_devices;
 static int bus_data_generation = 1;
 
@@ -1167,6 +1593,7 @@ device_probe_and_attach(device_t dev)
        if (error) {
                if (!(dev->flags & DF_DONENOMATCH)) {
                        BUS_PROBE_NOMATCH(bus, dev);
+                       devnomatch(dev);
                        dev->flags |= DF_DONENOMATCH;
                }
                return(error);
@@ -1239,6 +1666,7 @@ device_doattach(device_t dev)
                dev->state = DS_ATTACHED;
                if (bootverbose && !device_is_quiet(dev))
                        device_print_child(bus, dev);
+               devadded(dev);
        } else {
                kprintf("device_probe_and_attach: %s%d attach returned %d\n",
                       dev->driver->name, dev->unit, error);
@@ -1264,6 +1692,7 @@ device_detach(device_t dev)
 
        if ((error = DEVICE_DETACH(dev)) != 0)
                return(error);
+       devremoved(dev);
        device_printf(dev, "detached\n");
        if (dev->parent)
                BUS_CHILD_DETACHED(dev->parent, dev);
@@ -2541,6 +2970,7 @@ root_bus_module_handler(module_t mod, int what, void* arg)
                root_bus->driver = &root_driver;
                root_bus->state = DS_ALIVE;
                root_devclass = devclass_find_internal("root", NULL, FALSE);
+               devinit();
                return(0);
 
        case MOD_SHUTDOWN:
index 502d347..808be96 100644 (file)
@@ -32,7 +32,7 @@
  *
  *     @(#)if.c        8.3 (Berkeley) 1/4/94
  * $FreeBSD: src/sys/net/if.c,v 1.185 2004/03/13 02:35:03 brooks Exp $
- * $DragonFly: src/sys/net/if.c,v 1.80 2008/09/23 11:28:49 sephe Exp $
+ * $DragonFly: src/sys/net/if.c,v 1.81 2008/10/03 00:26:21 hasso Exp $
  */
 
 #include "opt_compat.h"
@@ -60,6 +60,7 @@
 #include <sys/thread2.h>
 #include <sys/serialize.h>
 #include <sys/msgport2.h>
+#include <sys/bus.h>
 
 #include <net/if.h>
 #include <net/if_arp.h>
@@ -523,6 +524,7 @@ if_attach(struct ifnet *ifp, lwkt_serialize_t serializer)
        ifa_iflink(ifa, ifp, 0 /* Insert head */);
 
        EVENTHANDLER_INVOKE(ifnet_attach_event, ifp);
+       devctl_notify("IFNET", ifp->if_xname, "ATTACH", NULL);
 
        ifq = &ifp->if_snd;
        ifq->altq_type = 0;
@@ -713,6 +715,7 @@ if_detach(struct ifnet *ifp)
 
        /* Announce that the interface is gone. */
        rt_ifannouncemsg(ifp, IFAN_DEPARTURE);
+       devctl_notify("IFNET", ifp->if_xname, "DETACH", NULL);
 
        SLIST_FOREACH(dp, &domains, dom_next)
                if (dp->dom_ifdetach && ifp->if_afdata[dp->dom_family])
@@ -1077,7 +1080,11 @@ if_up(struct ifnet *ifp)
 void
 if_link_state_change(struct ifnet *ifp)
 {
+       int link_state = ifp->if_link_state;
+
        rt_ifmsg(ifp);
+       devctl_notify("IFNET", ifp->if_xname,
+           (link_state == LINK_STATE_UP) ? "LINK_UP" : "LINK_DOWN", NULL);
 }
 
 /*
index 01b0eae..40d230b 100644 (file)
@@ -24,7 +24,7 @@
  * SUCH DAMAGE.
  *
  * $FreeBSD: src/sys/sys/bus.h,v 1.30.2.5 2004/03/17 17:54:25 njl Exp $
- * $DragonFly: src/sys/sys/bus.h,v 1.30 2008/09/30 12:20:29 hasso Exp $
+ * $DragonFly: src/sys/sys/bus.h,v 1.31 2008/10/03 00:26:21 hasso Exp $
  */
 
 #ifndef _SYS_BUS_H_
@@ -143,6 +143,16 @@ SLIST_HEAD(resource_list, resource_list_entry);
 #endif /* _KERNEL || _KERNEL_STRUCTURES */
 #ifdef _KERNEL
 
+/**
+ * devctl hooks.  Typically one should use the devctl_notify
+ * hook to send the message.  However, devctl_queue_data is also
+ * included in case devctl_notify isn't sufficiently general.
+ */
+boolean_t devctl_process_running(void);
+void devctl_notify(const char *__system, const char *__subsystem,
+    const char *__type, const char *__data);
+void devctl_queue_data(char *__data);
+
 /*
  * Initialise a resource list.
  */